上一篇 PCP策略
文章目錄
編程金融小白學 股票期權
隱含波動率
波動率
-
波動率
- 波動率爲期權價格影響的一個重要因素
- 沒有波動,期權就沒有存在的價值
- 不可觀測變量
-
在統計中的對應概念:價格(對數)收益率的年化標準差
天數
第n天的收益率
平均收益率
一年中國交易天數
波動率分類
-
歷史波動率 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
看漲期權價格
p
看跌期權價格
S
標的資產現價
X
行權價
r
無風險利率
T
到期時刻
t
當前時刻
𝜎
波動率
N()
爲標準正態分佈累積分佈函數
- 隱含波動率偏高 期權價格偏高
- 隱含波動率偏低 期權價格偏低
實例
- 來看一下 具體實例:
- 導入 需要的庫(還是實用 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()
- 看跌期權波動率 普遍比 看漲波動率高,說明投資者比較偏愛於購買看跌期權。