思路

我们想要一个期权作为一个类,然后自带多种计算价格的方法。这样可以方便调用,高内聚低耦合,更轻松地嵌入到你的其他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=lnS0k+(r+0.5σ2dv)tσtd_1 = \frac{ln\frac{S0}{k} + (r+ 0.5 \cdot \sigma^2 - dv)t}{\sigma \cdot \sqrt{t}}

d2=d1σtd_2 = d_1 - \sigma \sqrt{t}

而看涨期权(涉及股利)的价格为

P=S0edvtN(d1)kertN(d2)P = S_0 \cdot e^{-dv \cdot t} \cdot N(d_1) - k \cdot e^{-rt}N(d_2)

看跌期权的价格就是

P=kert[1N(d2)]S0[1N(d1)]P = ke^{-rt}[1-N(d_2)] - S_0[1-N(d_1)]

这里运用了一些小技巧,将kind表示成一个flag标记,使得同一个式子能应用于看涨看跌两种情况。注意

N(d)=1N(d)N(d) = 1 - N(-d)

这是我们的公式能正确运行的原因。

蒙特卡罗模拟计算方法

蒙特卡罗算法本身只能用于欧式期权,由于美式看涨期权和欧式看涨期权价格相等,因此我们将扩展到仅仅是不能计算美式看跌期权

蒙特卡洛模拟计算方法需要指定迭代次数iteration。

注意我们生成的 zt 是一个列表,不是一个单一的值,它的所有值的分布符合一个标准正态分布,总共有iteration个值,它代表波动的上涨或下跌。

接下来我们根据这个公式

st=s0e(rdv0.5σ2)t+σt0.05ztst = s0 * e^{(r-dv-0.5*\sigma^2)*t + \sigma *t ^{0.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,ks0un)max(0,k-s_0u^n)

这是看跌期权,看涨期权则为

max(0,s0unk)max(0,s_0u^n-k)

这样我们得到了二叉树最后一层叶子节点的所有期权价值。

每次往前推的过程是这样,

n-1节点的期权价值等于n步对应的两个节点的风险中性概率加权再无风险利率贴现的值,(美式同时和提前行权的价值取较大值)。

举例来说,s0 u^500 和 s0 u^498的父节点是s0 u^499,它的期权价值等于

p *(500的节点的期权价值) * (1-p) *(499的节点的期权价值) × 无风险利率贴现

无风险利率贴现就是 erte^{-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:

# european 为 欧式期权 (False 为欧式期权)
# kind 看涨或看跌(Put 为 -1 , Call 为 1)
# s0 标的资产现价
# k 期权执行价
# t 期权到期时间 - 现在时间,以天计
# r 适用的无风险利率,连续复利
# sigma 适用的波动率,
# dv 股利信息,连续复利
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

# B-S-M 计算价格方法
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
# 引入option包
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图形客户端的期权价格计算器。