上一篇 PCP策略
文章目录
编程金融小白学 股票期权
隐含波动率
波动率 σ \sigma σ
-
波动率 σ \sigma σ
- 波动率为期权价格影响的一个重要因素
- 没有波动,期权就没有存在的价值
- 不可观测变量
-
在统计中的对应概念:价格(对数)收益率的年化标准差
N N N 天数
R n R_n Rn 第n天的收益率
R ˉ \bar{R} Rˉ 平均收益率
242 242 242 一年中国交易天数
242 × 1 N ∑ n = 1 N ( R n − R ˉ ) 2 \sqrt{242\times\frac{1}{N}\sum_{n=1}^N(R_n-\bar{R})^2} 242×N1n=1∑N(Rn−Rˉ)2
波动率分类
-
历史波动率 Historical volatility & 未来波动率 Future Volatility
-
历史波动率法:
- 基于标的资产已发生的历史价格数据估计波动率:
- 标准差
- 极差波动率
- 已实现波动率 Realized Volatility (RV)
- 根据历史波动率预测未来的波动率:
- 预测值 = 历史值
- GARCH, EWMA
- HAR-RV
- 基于标的资产已发生的历史价格数据估计波动率:
-
隐含波动率法:
- 从期权价格倒推市场预测未来波动率
- Black-Scholes 隐含波动率
- 无模型隐含波动率 (如 VIX)
- 从期权价格倒推市场预测未来波动率
隐含波动率
-
Implied Volatility
-
用 Python 把这公式写出来:
import numpy as np
from scipy.stats import norm
class BlackScholes:
def __init__(self, S0, X, r, T, sigma=0.3,t=0):
self.S0 = S0
self.X = X
self.r = r
self.sigma = sigma
self.dT = T-t
def d1(self):
return(np.log(self.S0/self.X)+(self.r+self.sigma**2/2)*(self.dT))/(self.sigma*np.sqrt(self.dT))
def d2(self):
return self.d1()-self.sigma*np.sqrt(self.dT)
def calc(self, call_put):
if call_put in {'c','C','call','Call','CALL'}:
return self.S0 * norm.cdf(self.d1())- \
self.X*np.exp(-self.r*self.dT)*norm.cdf(self.d2())
elif call_put in {'p','P','put','Put','PUT'}:
return self.X*np.exp(-self.r*self.dT)*norm.cdf(-self.d2())- \
self.S0 * norm.cdf(-self.d1())
raise NameError('Must be call or Put!',call_put)
def imp_vol(self,call_put,mktprice):
price = 0
sigma = 0.3
up, low = 1,0
loop = 0
while abs(price-mktprice)>1e-6 and loop<50:
price = BlackScholes(self.S0,self.X,self.r,self.dT,sigma).calc(call_put)
if (price-mktprice)>0:
up = sigma
sigma = (sigma+low)/2
else:
low = sigma
sigma = (sigma+up)/2
loop+=1
return sigma
c = S N ( d 1 ) − X e − r ( T − t ) N ( d 2 ) p = X e − r ( T − t ) N ( − d 2 ) − S N ( − d 1 ) d 1 = ln ( S / X ) + ( r + σ 2 / 2 ) ( T − t ) σ T − t d 2 = ln ( S / X ) + ( r − σ 2 / 2 ) ( T − t ) σ T − t = d 1 − σ T − t \begin{aligned}c &= SN(d_1)-Xe^{-r(T-t)}N(d_2)\\ p &= Xe^{-r(T-t)}N(-d_2)-SN(-d_1)\\ d_1 &= \frac{\ln(S/X)+(r+\sigma^2/2)(T-t)}{\sigma \sqrt{T-t}} \\ d_2 &= \frac{\ln(S/X)+(r-\sigma^2/2)(T-t)}{\sigma \sqrt{T-t}}\\ &= d_1 -\sigma\sqrt{T-t}\end{aligned} cpd1d2=SN(d1)−Xe−r(T−t)N(d2)=Xe−r(T−t)N(−d2)−SN(−d1)=σT−tln(S/X)+(r+σ2/2)(T−t)=σT−tln(S/X)+(r−σ2/2)(T−t)=d1−σT−t
c
看涨期权价格
p
看跌期权价格
S
标的资产现价
X
行权价
r
无风险利率
T
到期时刻
t
当前时刻
𝜎
波动率
N()
为标准正态分布累积分布函数
- 隐含波动率偏高 → \to → 期权价格偏高
- 隐含波动率偏低 → \to → 期权价格偏低
实例
- 来看一下 具体实例:
- 导入 需要的库(还是实用 tushare 的数据)
import pandas as pd
import tushare as tus
import matplotlib.pyplot as plt # 画图
plt.rcParams['font.sans-serif'] = ['FangSong'] # 设置中文
plt.rcParams['axes.unicode_minus'] = False # 设置中文负号
pro = tus.pro_api()
print(tus.__version__)
1.2.54
- 以上证 50 ETF 为例
- 获取上证 50 ETF 数据
opt_name = pro.opt_basic(exchange='SSE', fields='ts_code,name,exercise_type,list_date,delist_date')
opt_name.head()
ts_code | name | exercise_type | list_date | delist_date | |
---|---|---|---|---|---|
0 | 10000579.SH | 华夏上证50ETF期权1604认购2.15 | 欧式 | 20160225 | 20160427 |
1 | 10000108.SH | 华夏上证50ETF期权1505认购2.65 | 欧式 | 20150326 | 20150527 |
2 | 10000111.SH | 华夏上证50ETF期权1505认沽2.55 | 欧式 | 20150326 | 20150527 |
3 | 10001067.SH | 华夏上证50ETF期权1712认购3.24 | 欧式 | 20171123 | 20171227 |
4 | 10001068.SH | 华夏上证50ETF期权1712认沽3.24 | 欧式 | 20171123 | 20171227 |
- 提取 需要的期权名 到期日期 价格 认购标签等
- 当然 Tushare 本身带有 这一系列简单的提取方式,在上式 修改 fields 所需参数 即可。
# 把 name 里的数据提取出来
opt_name['new_name']= opt_name['name'].str.extract(r'([\u4e00-\u9fa5]+)') # 提取期权名
opt_name['delist'] = opt_name['name'].str.extract(r'(期权)(\d+)')[1].astype(int) # 期权到期日期
opt_name['type']= opt_name['name'].str.extract(r'(\d+)') # 提取期权类型 300 或 50
opt_name['callput']= opt_name['name'].str.extract(r'(\w\w)(\d+\.\d+)')[0]# 认购或认沽
opt_name['price'] = opt_name['name'].str.extract(r'(\d+\.\d+)').astype(float) # 价格
opt_name.drop(columns=['name'],inplace=True)
opt_name['callput'].replace({'认购':'Call', '认沽':'Put'},inplace=True)
opt_name.head()
ts_code | exercise_type | list_date | delist_date | new_name | delist | type | callput | price | |
---|---|---|---|---|---|---|---|---|---|
0 | 10000579.SH | 欧式 | 20160225 | 20160427 | 华夏上证 | 1604 | 50 | Call | 2.15 |
1 | 10000108.SH | 欧式 | 20150326 | 20150527 | 华夏上证 | 1505 | 50 | Call | 2.65 |
2 | 10000111.SH | 欧式 | 20150326 | 20150527 | 华夏上证 | 1505 | 50 | Put | 2.55 |
3 | 10001067.SH | 欧式 | 20171123 | 20171227 | 华夏上证 | 1712 | 50 | Call | 3.24 |
4 | 10001068.SH | 欧式 | 20171123 | 20171227 | 华夏上证 | 1712 | 50 | Put | 3.24 |
- 以 昨天 2020年 4 月 29 日数据为例
- 提取 2020年 4 月 29 日期权交易数据
# 找到 4月 29日的期权交易数据
DATE = '20200429'
opt_date = pro.opt_daily(trade_date=DATE,exchange='SSE')
- 合并交易名与交易具体数据
new_date = pd.merge(opt_name,opt_date,on=['ts_code']) # 正在交易的名字和4月29日的数据交集
- 提取 上证2020年06月到期的 50ETF 认购 4月29日 数据
- 并按行权价 排序
call_date_2006 = new_date.query('delist == 2006 and type == "50" and callput == "Call"').sort_values(by='price')
call_date_2006.head(5)
ts_code | exercise_type | list_date | delist_date | new_name | delist | type | callput | price | trade_date | ... | pre_settle | pre_close | open | high | low | close | settle | vol | amount | oi | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
20 | 10002421.SH | 欧式 | 20200320 | 20200624 | 华夏上证 | 2006 | 50 | Call | 2.35 | 20200429 | ... | 0.46 | 0.4327 | 0.4300 | 0.4542 | 0.4300 | 0.4498 | 0.479 | 0.0221 | 985895.0 | 2353.0 |
46 | 10002401.SH | 欧式 | 20200319 | 20200624 | 华夏上证 | 2006 | 50 | Call | 2.40 | 20200429 | ... | 0.41 | 0.3842 | 0.3816 | 0.4050 | 0.3808 | 0.3950 | 0.429 | 0.0043 | 170508.0 | 2079.0 |
47 | 10002402.SH | 欧式 | 20200319 | 20200624 | 华夏上证 | 2006 | 50 | Call | 2.45 | 20200429 | ... | 0.36 | 0.3373 | 0.3362 | 0.3576 | 0.3362 | 0.3549 | 0.379 | 0.0104 | 364896.0 | 1075.0 |
96 | 10002291.SH | 欧式 | 20200204 | 20200624 | 华夏上证 | 2006 | 50 | Call | 2.50 | 20200429 | ... | 0.31 | 0.2909 | 0.2889 | 0.3130 | 0.2864 | 0.3069 | 0.329 | 0.0162 | 493784.0 | 2725.0 |
97 | 10002292.SH | 欧式 | 20200204 | 20200624 | 华夏上证 | 2006 | 50 | Call | 2.55 | 20200429 | ... | 0.26 | 0.2458 | 0.2430 | 0.2673 | 0.2430 | 0.2620 | 0.279 | 0.0127 | 328143.0 | 2058.0 |
5 rows × 21 columns
- 为了解释 标的资产现价 与 行权价的关系,绘制图表
- 可见 越接近 标的资产现价 它的时间价值或者说 期权价 越高
current = 2.829 # 当天收盘价为 2.829元
plt.figure(figsize=(8,5), dpi=800)
plt.plot(call_date_2006['price'],current-call_date_2006['price'],color='red',label='股票收益')
plt.stem(call_date_2006['price'],call_date_2006['settle'],use_line_collection=True)
plt.ylabel('结算价 & 股票收益')
plt.xlabel('行权价格')
plt.ylim((-.1,.5))
plt.title('4月29日 华夏上证50ETF期权2006 认购')
plt.show()
- 具体行权价与期权价到期收益情况绘图
- 更改一下之前作简要介绍的期权类,就变如下:
class OptionPlot:
'''
输入行权价 与 到期价格区间 绘制 期权收益图像
ST: 到期价区间
opt: 期权价
X: 行权价
'''
def __init__(self, X, ST, buy=True):
self.ST = ST
self.X = X
self.buy = 1 if buy else -1
def call_opt(self, opt:float=0):
return self.buy*((self.ST-self.X)*(self.ST>=self.X)-opt)
def put_opt(self, opt:float=0):
return self.buy*((self.X-self.ST)*(self.ST<=self.X)-opt)
plt.figure(figsize=(8,5), dpi=800)
plt.axhline(y=0,ls="-",c="black")
plt.axvline(x=current,ls="-",c="black")
ST_period = np.linspace(2.35,3.5,101)
plt.plot(ST_period,ST_period-current,color='red',linewidth = '3',label='股票收益')
for i in call_date_2006['settle'].index[6:18]:
option_price = call_date_2006['settle'][i] # 期权价
price = call_date_2006['price'][i] # 行权价
if not price*100%5:
buy = OptionPlot(price,ST_period)
plt.plot(ST_period,buy.call_opt(option_price),label=f'行权价({price})')
plt.ylabel('收益')
plt.xlabel('到期价格')
plt.xlim((2.6,3.0))
plt.ylim((-.2,.2))
plt.title('4月29日 华夏上证50ETF期权2006 认购')
plt.legend(loc='upper left')
plt.grid(True)
plt.show()
* 从之前所学,可知 虚值期权 它的风险是相当高,同时当你买入实值越大的期权时,随着期权费越高,它和普通股票价格直线越接近。
- 接下来 我们 来看看之前构造的 BlackScholes 定价系统
- 输入当天价格
current
、行权价call_date_2006['price']
、无风险利率5%
、到期时间40/250=0.16
年、默认波动率30%
- 对比 BS 期权价 与 实际 期权价
- 输入当天价格
BS = [BlackScholes(current, x, 0.05, 0.16) for x in call_date_2006['price']]
BS_price = np.array([bs.calc('c') for bs in BS])
plt.figure(figsize=(8,5), dpi=800)
plt.stem(call_date_2006['price'],call_date_2006['settle'],'b',use_line_collection=True,label='市场 期权价')
plt.plot(call_date_2006['price'],BS_price,'r',label='BS计算 期权价')
plt.xlabel('认购期权价格')
plt.title('4月29日 华夏上证50ETF期权2006 BS波动率30% 认购期权价格')
plt.legend(loc='upper right')
plt.grid(True)
plt.show()
marketprice = list(call_date_2006['settle']) # 市场期权价
imp_vol = np.array([bs.imp_vol('c',marketprice[i]) for i,bs in enumerate(BS)])
plt.figure(figsize=(8,5), dpi=800)
plt.plot(call_date_2006['price'],imp_vol)
plt.ylabel('隐含波动率')
plt.ylim((-.1,1.1))
plt.yticks(np.arange(0,1.1,0.1), [f'{int(y*100)}%' for y in np.arange(0,1.1,0.1)])
plt.xlabel('行权价格')
plt.title('4月29日 华夏上证50ETF2006认购期权 隐含波动率')
plt.grid(True)
plt.show()
绘制期权隐含波动率
- 结合上面的分步绘图 我们可以绘制出 并对比 当前所有可购买 50EFT 期权的隐含波动率
opt_name = pro.opt_basic(exchange='SSE')
opt_name['type']= opt_name['name'].str.extract(r'(\d+)') # 提取期权类型 300 或 50
opt_name.head(2) # 预览
ts_code | exchange | name | per_unit | opt_code | opt_type | call_put | exercise_type | exercise_price | s_month | maturity_date | list_price | list_date | delist_date | last_edate | last_ddate | quote_unit | min_price_chg | type | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 10000579.SH | SSE | 华夏上证50ETF期权1604认购2.15 | 10000.0 | OP510050.SH | ETF期权 | C | 欧式 | 2.15 | 201604 | 20160427 | 0.0412 | 20160225 | 20160427 | 20160427 | 20160428 | 人民币元 | 0.0001 | 50 |
1 | 10000108.SH | SSE | 华夏上证50ETF期权1505认购2.65 | 10000.0 | OP510050.SH | ETF期权 | C | 欧式 | 2.65 | 201505 | 20150527 | 0.1006 | 20150326 | 20150527 | 20150527 | 20150528 | 人民币元 | 0.0001 | 50 |
# 找到 4月 29日的期权交易数据
DATE = '20200429'
opt_date = pro.opt_daily(trade_date=DATE,exchange='SSE')
new_date = pd.merge(opt_name,opt_date,on=['ts_code'])
current = 2.829
plt.figure(figsize=(8,5), dpi=800)
for s_month in new_date.s_month.unique():
call_date = new_date[new_date['s_month'] == s_month].query(f'type == "50" and call_put == "C"').sort_values(by='exercise_price')
ex_price = call_date['exercise_price'] # 行权价
opt_price = call_date['settle'] # 期权价
comb_BS = [(BlackScholes(current, ex_price[i], 0.05, 0.16), opt_price[i])for i in ex_price.index]
imp_vol = np.array([bs.imp_vol('c',marketprice) for bs,marketprice in comb_BS])
plt.plot(ex_price,imp_vol,label=f'认购期权{s_month[2:]}')
for s_month in new_date.s_month.unique():
put_date = new_date[new_date['s_month'] == s_month].query(f'type == "50" and call_put == "P"').sort_values(by='exercise_price')
ex_price = put_date['exercise_price'] # 行权价
opt_price = put_date['settle'] # 期权价
comb_BS = [(BlackScholes(current, ex_price[i], 0.05, 0.16), opt_price[i])for i in ex_price.index]
imp_vol = np.array([bs.imp_vol('p',marketprice) for bs,marketprice in comb_BS])
plt.plot(ex_price,imp_vol,'--',label=f'认沽期权{s_month[2:]}')
plt.ylabel('隐含波动率')
plt.ylim((-.1,1.1))
plt.yticks(np.arange(0,1.1,0.1), [f'{int(y*100)}%' for y in np.arange(0,1.1,0.1)])
plt.xlabel('行权价格')
plt.title(f'{DATE[4:6]}月{DATE[6:]}日 华夏上证50ETF认购期权 隐含波动率')
plt.grid(True)
plt.legend(loc='upper right')
plt.show()
- 看跌期权波动率 普遍比 看涨波动率高,说明投资者比较偏爱于购买看跌期权。