Python for Finance Chapter3

金融数值分析 示例

BSM 模型中的两个基本问题

  • 隐含波动率
    以某些到期日的期权报价和期权定价公式反推期权的隐含波动率,并汇出图表——这是期权交易者和风险管理者每天都要面对的任务。
  • 蒙特卡洛模拟
    欧式期权价值的计算。通过蒙特卡罗技术,模拟股票在一段时间中变化。

像Black-Scholes-Merton(1973)这样有深远影响的期权定价公式中,隐含波动率是在其他条件不变的情况下输入公式,得出不同期权行权价格和到期日测得市场报价的那些波动率值。

BSM公式

\begin{aligned}
C\left(S_{t}, K, t, T, r\right) &=S_{t} \cdot N\left(d_{1}\right)-e^{-r(T-t)} \cdot K \cdot N\left(d_{2}\right) \newline
N(d) &=\frac{1}{\sqrt{2 \pi}} \int_{-\infty}^{d} e^{-1 / 2} x^{2} d x \newline
d_{1} &=\frac{\log \left(S_{T} / K\right)+\left(r+\sigma^{2} / 2\right)(T-t)}{\sigma \sqrt{T-t}} \newline
d_{2} &=\frac{\log \left(S_{T} / K\right)+\left(r-\sigma^{2} / 2\right)(T-t)}{\sigma \sqrt{T-t}}
\end{aligned}

不同参数有如下含义:

$S_t$ 在时点t的标的物价格水平;

$\sigma$ 标的物固定波动率(也就是收益的标准差);

$K$ 期权行权价格;

$T$ 期权到期日;

$r$ 固定无风险短期利率;

现在考虑欧式看涨期权的一个报价 $C^*$ 已知的情况。隐含波动率 $\sigma^{imp}$ 是上述公式的隐式方程的解,它没有解析表达式,因此对于隐含波动率 $\sigma^{imp}$ 的求解采用数值方法求解,牛顿迭代法

$$
C^{*}=C\left(\sigma_{n}^{i m p}\right)+\left(\sigma_{n+1}^{i m p}-\sigma_{n}^{i m p}\right) \cdot \frac{\partial C\left(\sigma_{n}^{i m p}\right)}{\partial \sigma_{n}^{i m p}}
$$

$$
\Longrightarrow \sigma_{n+1}^{i m p}=\sigma_{n}^{i m p}-\frac{C\left(\sigma_{n}^{i m p}\right)-C^{*}}{\partial C\left(\sigma_{n}^{i m p}\right) / \partial \sigma_{n}^{i m p}}
$$

期权定价公式对于波动率的偏微分称作希腊字母 Vega,可以得出了 Vega 的闭合方式。

$$
\frac{\partial C}{\partial \sigma}=S_tN(d_1)\sqrt{T-t}
$$

Black-Scholes-Merton python计算公式

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
import numpy as np
from math import sqrt, log
from scipy import stats
#
# 欧式期权BSM定价公式
def bsm_call_value(S0, K, T, r, sigma):
"""
Parameters:
==========
S0: float
标的物初始价格水平
K: float
行权价格
T: float
到期日
r: float
固定无风险短期利率
sigma: float
波动因子

Returns
==========
value: float
"""
S0 = float(S0)
d1 = (np.log(S0 /K) + (r + 0.5 * sigma**2) * T )/(sigma * np.sqrt(T))
d2 = (np.log(S0 /K) + (r - 0.5 * sigma**2) * T )/(sigma * np.sqrt(T))
value = (S0 * stats.norm.cdf(d1, 0, 1) - K * np.exp(-r * T) * stats.norm.cdf(d2, 0, 1))
return value

def bsm_vega(S0, K, T, r, sigma):
"""
Vega 计算
"""
S0 = float(S0)
d1 = (np.log(S0/K)) + (r+0.5*sigma**2)*T /(sigma*sqrt(T))
vega = S0 * stats.norm.cdf(d1, 0, 1) * np.sqrt(T)
return vega

def bsm_call_imp_vol(S0, K, T, r, C0, sigma_est, it=100):
for i in range(it):
sigma_est -= ((bsm_call_value(S0, K, T, r, sigma_est) - C0)
/ bsm_vega(S0, K, T, r, sigma_est))
return sigma_est
1
2
3
4
5
6
7
8
S0 = 1
K = 2
T = 2
r = 0.01
sigma = 0.1
C0 = 0.01
bsm_call_imp_vol(S0, K, T, r, C0, sigma, it=100)
# 牛顿迭代的收敛性很好 一般几步就能收敛了,牛顿迭代的关键是初值的选取,选不好很容易发散

结果:0.2800420290586886

vxtoxx 为例,比较特殊的 h5 型数据,类似数据库式的数据,后期可以用 SQL 作为数据储存工具,读取csv。但是书中示例式这个。

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd
h5 = pd.HDFStore('./vstoxx_data_31032014.h5','r') #使用 pandas.HDFStore 创建
futures_data = h5['futures_data']
options_data = h5['options_data']
# 源文件中‘Data' 是 datetime64[ns] 时间类型,但是直接读取成了int64,因此将他的数据类型转换一下
futures_data['DATE'] = pd.to_datetime(futures_data['DATE'])
options_data['DATE'] = pd.to_datetime(options_data['DATE'])
futures_data['MATURITY'] = pd.to_datetime(futures_data['MATURITY'])
options_data['MATURITY'] = pd.to_datetime(options_data['MATURITY'])
h5.close()
options_data.info() # 看一下期权数据的整体情况

结果如下所示,因为在每个交易日,根据行权价格,看涨看跌,期权行权时间,更甚还可以各种障碍条款的亚式、欧式、美式的各种期权,因此期权的种类是多种多样的。在2014-3-31日 vstoxx 欧式期权的种类就有395种看涨期权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<class 'pandas.core.frame.DataFrame'>
Int64Index: 395 entries, 46170 to 46564
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 DATE 395 non-null datetime64[ns]
1 EXP_YEAR 395 non-null int64
2 EXP_MONTH 395 non-null int64
3 TYPE 395 non-null object
4 STRIKE 395 non-null float64
5 PRICE 395 non-null float64
6 MATURITY 395 non-null datetime64[ns]
7 TTM 395 non-null float64
dtypes: datetime64[ns](2), float64(3), int64(2), object(1)
memory usage: 27.8+ KB
1
options_data.head() # 看一下头几行长什么样
DATEEXP_YEAREXP_MONTHTYPESTRIKEPRICEMATURITYTTM
461702014-03-3120144C1.016.852014-04-180.049
461712014-03-3120144C2.015.852014-04-180.049
461722014-03-3120144C3.014.852014-04-180.049
461732014-03-3120144C4.013.852014-04-180.049
461742014-03-3120144C5.012.852014-04-180.049
1
futures_data.head() # 看一下期货的头几行
DATEEXP_YEAREXP_MONTHPRICEMATURITYTTM
4962014-03-312014417.852014-04-180.049
4972014-03-312014519.552014-05-160.126
4982014-03-312014619.952014-06-200.222
4992014-03-312014720.402014-07-180.299
5002014-03-312014820.702014-08-150.375

可以发现,一些期权是很便宜的价内期权(指数水平远高于期权的行权价格)。因此我们应该根据对应到期日的期货价值,分析具有一定(远期)价值的看涨期权。允许与期货水平之间的最大偏差为50%

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
V0 = 17.6639
r = 0.01
# imp_vol -> implied volality
options_data['IMP_VOL'] = 0.0 # new colum for implied volatilities
tol = 0.5 # tolerance level for moneyness
for option in options_data.index:
# iterating over all option quotes
item = options_data.loc[option]
forward = futures_data[futures_data['MATURITY']== \
item['MATURITY']]['PRICE'].values[0]
# picking the right futures value
if (forward * (1 - tol) < item['STRIKE']
< forward*(1 + tol)):
# only for options with moneyness tolerness
imp_vol = bsm_call_imp_vol(V0,
item['STRIKE'],
item['TTM'],
r,
item['PRICE'],
sigma_est=2.,
it=100)
options_data['IMP_VOL'].loc[option] = imp_vol
  • 准备画图数据
1
2
3
plot_data = options_data[options_data['IMP_VOL']>0]
maturies = sorted(set(options_data['MATURITY'])) #到期日排序和去重
matures # 可以看一下到期日情况
1
2
3
4
5
6
7
8
[Timestamp('2014-04-18 00:00:00'),
Timestamp('2014-05-16 00:00:00'),
Timestamp('2014-06-20 00:00:00'),
Timestamp('2014-07-18 00:00:00'),
Timestamp('2014-08-15 00:00:00'),
Timestamp('2014-09-19 00:00:00'),
Timestamp('2014-10-17 00:00:00'),
Timestamp('2014-11-21 00:00:00')]
  • 画图
1
2
3
4
5
6
7
8
9
10
11
12
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 6))
for maturity in maturies:
data = plot_data[options_data.MATURITY==maturity]
plt.plot(data['STRIKE'], data['IMP_VOL'], label=maturity.date(), lw=1.5
)
plt.plot(data['STRIKE'], data['IMP_VOL'], 'r.')
plt.grid(True)
plt.xlabel('strike')
plt.ylabel('implied volatility of volatility')
plt.legend()
plt.show()

21

在股票或外汇市场中,你将注意到所谓的隐含波动率微笑,而且到期日越短,隐含波动率微笑越明显;到期日越长,越不明显。

期权定价的蒙特卡罗模拟

蒙特卡罗是金融学和数值科学中最重要的算法之一。它之所以重要,是因为在期权定价或者风险管理问题上有很强的能力。和其他数值方法相比,蒙特卡罗方法很容易处理高维问题,在这种问题上复杂度和计算需求通常以线性方式增大。一下例子阐述了python的基于蒙特卡罗模拟的欧式期权估值方法。

Black-Scholes-Merton随机微分方程
$$
dS_t=rS_tdt+\sigma S_tdZ_t
$$
其中, $Z_t$ 是一个布朗运动

上述 SDE 方程的欧拉离散
$$
S_t=S_{t-\Delta_t}\ exp\lgroup \lgroup r -\frac{1}{2}\sigma^2\rgroup \Delta t+\sigma \sqrt{\Delta t}z_t\rgroup
$$
变量Z是标准正态分布随机变量,$0<\Delta_t<T$,是一个足够小的时间间隔。以 $S0=100、$$K=105、$$T=1.0、$$r=0.05、$$\sigma=0.2$ 参数化上述模型,利用前面例子中的计算公式,可以得到精确的期权价值:

1
2
3
4
5
6
S0 = 100
K = 105
T = 1.0
r = 0.05
sigma = 0.2
bsm_call_value(S0, K, T, r, sigma)

结果:8.021352235143176

蒙特卡罗算法流程:

  1. 将时间间隔 [0,T] 分为等距的、长度为$\Delta_t$的子时段。

  2. 开始循环 $i=1,2,\cdots, l。$

(a) 对于每个时间步$t\in {\Delta_t, 2\Delta_t,\dots, T }$,取伪随机数 Z_t(i)。

(b) 逐个时间步应用伪随机数,确定指数水平$S_T(i)$的T值,以离散化公式2-2的方案

(c) 确定T时点欧式看涨期权的内在价值:$h_t:h_t(ST(i))=max(ST(i)-K, 0)$

(d) 循环到 $i=I$

  1. 根据公式2-3,加总内在价值,求平均值,并扣除无风险短期利率。

  2. 公式 2-3 欧式看涨期权的蒙特卡罗估算函数:

$$
C_0\approx e^{-rT}\frac{1}{I}\sum_Ih_T(S_T(i))
$$

纯 python

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
from time import time
from math import exp, sqrt, log
from random import gauss, seed
seed(20000)
t0 = time()
# 参数设定
S0 = 100.
K = 105.
T = 1.
r = 0.05
sigma = 0.2
M = 50 # 时间步长
dt = T / M
I = 250000
S = []

# M步循环
for i in range(I):
path = []
for t in range(M+1):
if t == 0 :
path.append(S0)
else:
z = gauss(0, 1)
St = path[t-1] * exp((r - 0.5 * sigma **2) * dt
+ sigma * sqrt(dt) * z)
path.append(St)
S.append(path)
C0 = exp(-r * T) * sum([max(path[-1] - K, 0) for path in S])/ I
print(f'欧式期权定价 {C0}.')
print(f'共计花费时间 {np.round(time()-t0,1) }s.')

结果:

欧式期权定价 7.9990448881765825.

共计花费时间 24.1s.

Numpy 向量化

使用 Numpy 的欧式看涨期权蒙特卡罗估值(第一个版本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import math
import numpy as np
from time import time

np.random.seed(20000)
t0 = time()
# Parameters
S0 = 100.; K = 105.; T = 1.0; r = 0.05; sigma = 0.2
M = 50; dt = T/M; I = 250000

# Simulating I paths with M time steps
S = np.zeros((I, M+1))
S[:,0] = S0
for t in range(1,M+1):
z = np.random.standard_normal(I) # pseudorandom numbers
S[:,t] = S[:,t-1] * np.exp((r - 0.5 * sigma **2) * dt + sigma * math.sqrt(dt) *z)
C0 = math.exp(-r * T) *np.sum(np.maximum(S[:,-1]-K,0)) / I
tnp1 = time() - t0
print('Europen Option Value %7.3f' % C0)
print('Duration in Seconds %7.3F' % tnp1)

结果:

Europen Option Value 8.037

Duration in Seconds 0.746

向量化和纯Python 相比,速度有30倍以上的提升。且估算的蒙特卡罗值和基准值很接近。在对欧拉离散公式进行对数化处理后,我们可以获得更高的效率。

欧拉离散化方法(对数形式):
$$
logS_T=logS_{T-\Delta_t}+(r-(1/2)\sigma^2)\Delta_t + \sigma\sqrt{\Delta_t}z_t
$$
这个版本完全采用递增法,可以在Python层面上不使用任何循环的情况下实现蒙特卡罗算法。

代码:上一例中横着一行表示一条路劲,这里竖着一列表示一条路径,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import math
import numpy as np
np.random.seed(20000)
t0 = time()
# Parameters
S0 = 100; K=105; T=1.; r=0.05; sigma=0.2
# Simulating I paths with M time steps
S = S0 * np.exp(np.cumsum((r-0.5*sigma**2)*dt + sigma * math.sqrt(dt)
* np.random.standard_normal((M+1, I)), axis=0
))
S[0] = S0
C0 = math.exp(-r*T) * np.sum(np.maximum(S[-1]-K, 0))/I

print(f'欧式期权定价 {C0}.')
print(f'共计花费时间 {np.round(time()-t0,1) }s.')

结果:

欧式期权定价 8.165807966259603.

共计花费时间 0.6s.

图形化分析

绘制前30条模拟路径:

1
2
3
4
5
6
import matplotlib.pyplot as plt
plt.plot(S[:, : 30])
plt.grid(True)
plt.xlabel('time step')
plt.ylabel('index level')
plt.show()

22

模拟结束时模拟指数水平的频率:

1
2
3
4
5
plt.hist(S[-1], bins=50)
plt.grid(True)
plt.xlabel('index level')
plt.ylabel('frequency')
plt.show()

23

期末期权内在价值直方图:

1
2
3
4
5
6
plt.hist(np.maximum(S[-1] - K,0),bins = 50)
plt.grid()
plt.xlabel('option inner value')
plt.ylabel('frequency')
plt.ylim(0,50000)
plt.show()

24

扩展-数据获取 API 接口

pandas DataReader 函数

1
2
3
4
5
6
7
8
import numpy as np
import pandas as pd
import pandas_datareader.data as web

sp500 = web.DataReader('^GSPC', data_source='yahoo',
start='3/14/2009', end='4/14/2014')
sp500.index.name = 'Date'
sp500.head()
HighLowOpenCloseVolumeAdj Close
Date
1999-12-311472.4200441458.1899411464.4699711469.2500003740500001469.250000
2000-01-031478.0000001438.3599851469.2500001455.2199719318000001455.219971
2000-01-041455.2199711397.4300541455.2199711399.42004410090000001399.420044
2000-01-051413.2700201377.6800541399.4200441402.10998510855000001402.109985
2000-01-061411.9000241392.0999761402.1099851403.44995110923000001403.449951
1
2
3
sp500['42d'] = np.round(pd.Series.rolling(sp500['Close'],window=42).mean()) # 42日均线
sp500['252d'] = np.round(pd.Series.rolling(sp500['Close'],window=252).mean()) #252日均线
sp500[['Close','42d','252d']].plot(grid = True,figsize=(8,5))

25

tushare 获取数据 下载到 MySQL

  • 从 tushare 注册账号,获取 API 接口码
1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tushare as ts

ts.set_token('tushare码')
pro = ts.pro_api()

pd.set_option('display.max_columns', 100) # 设置显示数据的最大列数,防止出现省略号…,导致数据显示不全
pd.set_option('expand_frame_repr', False) # 当列太多时不自动换行

df = pro.daily(ts_code='600000.SH', start_date='20200401', end_date='20200630')
df.head()
ts_codetrade_dateopenhighlowclosepre_closechangepct_chgvolamount
0600000.SH2020063010.5910.6410.5410.5810.570.010.0946228867.64242601.910
1600000.SH2020062910.6310.7310.4910.5710.60-0.03-0.2830278756.19295223.647
2600000.SH2020062410.5410.6110.5010.6010.480.121.1450228738.84241489.165
3600000.SH2020062310.5110.5710.4710.4810.55-0.07-0.6635196022.12206109.220
4600000.SH2020062210.5710.6610.5310.5510.61-0.06-0.5655246119.03260728.901
  • 保存到 MySQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd
import pymysql
pymysql.install_as_MySQLdb() #为了兼容mysqldb
from sqlalchemy import create_engine
conn=create_engine('mysql+pymysql://用户名:密码@localhost:本地端口/mysql数据库名?charset=utf8')

# 股票每日交易数据入库
def daily_to_mysql():
ts.set_token('token码')#设置token
pro = ts.pro_api()
df = pro.daily(ts_code='000001.SZ', start_date='20180701', end_date='20180718')
df.to_sql('stock_daily_basic',con=conn,if_exists='append',index=False)


if __name__ == "__main__":
daily_to_mysql()

Python for Finance by Yves Hilpisch(O’Reilly).Copyright 2015 Yves Hilpisch,978-1-491-94528-5

感谢您的阅读,本文由 LEE 版权所有。如若转载,请注明出处:LEE(https://ChubbyLEE-Math.github.io/2020/07/16/python%20for%20Finance,Chapter%203/
Python for Finance Chapter1
Python for Finance Chapter5