思路
我们想要一个期权作为一个类,然后自带多种计算价格的方法。这样可以方便调用,高内聚低耦合,更轻松地嵌入到你的其他Project或论文当中。
引入库
为了加速计算,引入了numpy库和scipy库。
1 2
| import numpy as np import scipy.stats as sps
|
如果没有安装过的话,请在控制台使用pip install package_name
来按照。
Option 的属性
我们需要理清楚,一个期权的价格是由哪些因素决定的呢?期权有看涨看跌之分,欧式美式之别,决定价格的还有现价,执行价,到期时间,无风险利率,波动率以及可能有股利利率。
european
为是否是欧式期权 (True 为欧式期权),这是一个布尔变量。
kind
看涨或看跌(Put
为 -1 ,Call
为 1)
s0
标的资产现价
k
期权执行价
t
期权到期时间 - 现在时间
r
适用的无风险利率
sigma
适用的波动率
dv
股利利率(不是所有期权都有的)
那我们可以这样写一个期权类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Option: def __init__(self, european, kind, s0, k, t, r, sigma, dv = 0): self.european = european self.kind = kind self.s0 = s0 self.k = k self.t = t /365 self.sigma = sigma self.r = r self.dv = dv self.bsprice = None self.mcprice = None self.btprice = None
|
后面三者是使用三种方法计算出来的期权价格,一开始没计算,自然是空。
这样子,我们就可以使用
some_option = Option(True,-1,100,100,100,1,1,1,0)
来新建一个期权类了!
注意几点!
- 我假设你输入进去的是连续复利,这个要依据你原始数据的情况作相应更改。如果输入的是单利的话,要用
np.log(1 + r)
来转换一下。
- 同理,这里的时间为天,即30天换算下来大概是1/12年,你输入30,我当作1/12。这点也要根据实际情况改。
- 还有波动率无风险利率有没有除过100,也要考虑实际数据情况。
Option的方法
即计算价格的方法。
B-S-M 计算方法
因为涉及到了股利利率,所以严格来说不是BS算法而是BSM算法。这个函数会在期权是欧式的情况下返回一个数值,而美式的情况下返回一个字符串。
1 2 3 4 5 6 7 8 9 10
| def bs(self): if self.european or self.kind == 1: d_1 = (np.log(self.s0 / self.k) + ( self.r - self.dv + .5 * self.sigma ** 2) * self.t) / self.sigma / np.sqrt( self.t) d_2 = d_1 - self.sigma * np.sqrt(self.t) self.bsprice = self.kind * self.s0 * np.exp(-self.dv * self.t) * sps.norm.cdf( self.kind * d_1) - self.kind * self.k * np.exp(-self.r * self.t) * sps.norm.cdf(self.kind * d_2) else: self.bsprice = "美式看跌期权不适合这种计算方法"
|
BSM 算法本身只能用于欧式期权,由于美式看涨期权和欧式看涨期权价格相等,因此我们将扩展到仅仅是不能计算美式看跌期权。
其中我们算了d1 和 d2 它们是用于最终计算的中间变量。涉及到有股利情况下,它们是
d1=σ⋅tlnkS0+(r+0.5⋅σ2−dv)t
d2=d1−σt
而看涨期权(涉及股利)的价格为
P=S0⋅e−dv⋅t⋅N(d1)−k⋅e−rtN(d2)
看跌期权的价格就是
P=ke−rt[1−N(d2)]−S0[1−N(d1)]
这里运用了一些小技巧,将kind表示成一个flag标记,使得同一个式子能应用于看涨看跌两种情况。注意
N(d)=1−N(−d)
这是我们的公式能正确运行的原因。
蒙特卡罗模拟计算方法
蒙特卡罗算法本身只能用于欧式期权,由于美式看涨期权和欧式看涨期权价格相等,因此我们将扩展到仅仅是不能计算美式看跌期权。
蒙特卡洛模拟计算方法需要指定迭代次数iteration。
注意我们生成的 zt
是一个列表,不是一个单一的值,它的所有值的分布符合一个标准正态分布,总共有iteration个值,它代表波动的上涨或下跌。
接下来我们根据这个公式
st=s0∗e(r−dv−0.5∗σ2)∗t+σ∗t0.05∗zt
来计算最终价值,这里根据迭代次数生成了迭代次数个最终价值。这些最终价值要根据看涨或看跌进行 k- x 或者 x-k 的处理,并取处理后和0相比的较大值。
我们计算这些最终价值的平均值,再贴现到当前日期。贴现是指原价值乘以e^(-r*t)
1 2 3 4 5 6 7 8 9
| def mc(self, iteration): if self.european or self.kind == 1: zt = np.random.normal(0, 1, iteration) st = self.s0 * np.exp((self.r - self.dv - .5 * self.sigma ** 2) * self.t + self.sigma * self.t ** .5 * zt) st = np.maximum(self.kind * (st - self.k), 0) self.mcprice = np.average(st) * np.exp(-self.r * self.t) else: self.mcprice = "美式看跌期权不适合这种计算方法"
|
二叉树计算方法
此方法最难,但是适用于所有期权,因此也最为必要。我们首先要计算u,d,p。u代表上涨,d代表下跌,p是一个风险中性概率。每一期可能上涨,也可能下跌,u,d即衡量上涨会涨的倍数和下跌会下跌的倍数。p即上涨的概率,1-p 是下跌的概率。从最开始的单一起点(标的资产价值)慢慢往未来推,可能上涨可能下降,下降后又可能上涨可能下降,这样子慢慢形成一棵二叉树。这时候二叉树的价格不是期权价值,是站在未来时间的估计现价。 而我们需要的是期权价格。
我们需要从树的叶子节点从后往前推导期权价值。举例来说,最后一步最上面节点的期权价值等于(n = 迭代次数),每一个节点类似,只是下面的节点需要将u替换成d,n以每个节点减少2的等差往下降。
max(0,k−s0un)
这是看跌期权,看涨期权则为
max(0,s0un−k)
这样我们得到了二叉树最后一层叶子节点的所有期权价值。
每次往前推的过程是这样,
n-1节点的期权价值等于n步对应的两个节点的风险中性概率加权再无风险利率贴现的值,(美式同时和提前行权的价值取较大值)。
举例来说,s0 u^500 和 s0 u^498的父节点是s0 u^499,它的期权价值等于
p *(500的节点的期权价值) * (1-p) *(499的节点的期权价值) × 无风险利率贴现
无风险利率贴现就是 e−rt
注意,如果是美式期权的话,这个价值还要和 k - s0 u^499 相比,(看涨是 s0 u^499 - k)取较大值。
这样一层层往前推,就推导到了我们的根节点,就是站在此时此刻的期权价值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| def bt(self, iteration): if iteration % 2 != 0: iteration += 1 delta = self.t / iteration u = np.exp(self.sigma * np.sqrt(delta)) d = 1 / u p = (np.exp((self.r - self.dv) * delta) - d) / (u - d) tree = [] for j in range(int(iteration / 2) + 1): i = j * 2 temp = self.s0 * np.power(u, iteration - i) temp = np.max([(temp - self.k) * self.kind, 0]) tree.append(temp) for j in range(1, int(iteration / 2) + 1): i = j * 2 temp = self.s0 * np.power(d, i) temp = np.max([(temp - self.k) * self.kind, 0]) tree.append(temp) for j in range(0, iteration): newtree = [] for i in (range(len(tree) - 1)): temp = tree[i] * p + (1 - p) * tree[i + 1] temp = temp * np.exp(-self.r * delta) if not self.european: k = iteration - j - 1 if i < (k + 1) / 2: power = k - i * 2 compare = self.s0 * np.power(u, power) else: power = i * 2 - k compare = self.s0 * np.power(d, power) temp = np.max([temp, (compare - self.k) * self.kind]) newtree.append(temp) tree = newtree self.btprice = tree[0]
|
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| import scipy.stats as sps import numpy as np
class Option:
def __init__(self, european, kind, s0, k, t, r, sigma, dv): self.european = european self.kind = kind self.s0 = s0 self.k = k self.t = t /365 self.sigma = sigma self.r = r self.dv = dv self.bsprice = None self.mcprice = None self.btprice = None
def bs(self): if self.european or self.kind == 1: d_1 = (np.log(self.s0 / self.k) + ( self.r - self.dv + .5 * self.sigma ** 2) * self.t) / self.sigma / np.sqrt( self.t) d_2 = d_1 - self.sigma * np.sqrt(self.t) self.bsprice = self.kind * self.s0 * np.exp(-self.dv * self.t) * sps.norm.cdf( self.kind * d_1) - self.kind * self.k * np.exp(-self.r * self.t) * sps.norm.cdf(self.kind * d_2) else: self.bsprice = "美式看跌期权不适合这种计算方法"
def mc(self, iteration): if self.european or self.kind == 1: zt = np.random.normal(0, 1, iteration) st = self.s0 * np.exp((self.r - self.dv - .5 * self.sigma ** 2) * self.t + self.sigma * self.t ** .5 * zt) st = np.maximum(self.kind * (st - self.k), 0) self.mcprice = np.average(st) * np.exp(-self.r * self.t) else: self.mcprice = "美式看跌期权不适合这种计算方法"
def bt(self, iteration): if iteration % 2 != 0: iteration += 1 delta = self.t / iteration u = np.exp(self.sigma * np.sqrt(delta)) d = 1 / u p = (np.exp((self.r - self.dv) * delta) - d) / (u - d) tree = [] for j in range(int(iteration / 2) + 1): i = j * 2 temp = self.s0 * np.power(u, iteration - i) temp = np.max([(temp - self.k) * self.kind, 0]) tree.append(temp) for j in range(1, int(iteration / 2) + 1): i = j * 2 temp = self.s0 * np.power(d, i) temp = np.max([(temp - self.k) * self.kind, 0]) tree.append(temp) for j in range(0, iteration): newtree = [] for i in (range(len(tree) - 1)): temp = tree[i] * p + (1 - p) * tree[i + 1] temp = temp * np.exp(-self.r * delta) if not self.european: k = iteration - j - 1 if i < (k + 1) / 2: power = k - i * 2 compare = self.s0 * np.power(u, power) else: power = i * 2 - k compare = self.s0 * np.power(d, power) temp = np.max([temp, (compare - self.k) * self.kind]) newtree.append(temp) tree = newtree self.btprice = tree[0]
|
调用方法
将上面的完整代码保存在一个option.py
里。
在同一个目录下,新建一个.py
文件,写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from option import Option
some_option = Option(True,1,100,100,100,0.1,0.1,0.1)
some_option.bs() some_option.mc(1000) some_option.bt(500)
print(some_option.bsprice) print(some_option.mcprice) print(some_option.btprice)
|
怎么样?是不是很简单?
你说太简单了?那你需要看看我的下一篇文章,一个带GUI图形客户端的期权价格计算器。