Tushare隐含波动率策略

本文详细介绍了基于隐含波动率的期权套利策略,首先阐述了隐含波动率的理论基础,指出认沽和认购期权隐含波动率应相等的原理。接着,通过筛选剩余有效期为30天的期权,计算期权的隐含波动率,寻找隐含波动率差异大的期权对。然后,构建组合并进行对冲,以期在市场波动中实现套利。最后,通过实际数据计算交易盈亏,并讨论了可能的扩展研究方向。
摘要由CSDN通过智能技术生成

ID: 406010

1. 策略思路

1.1. 理论基础
隐含波动率可以理解为市场对标的资产未来波动率的预期
相同到期时间和行权价格的认沽和认购期权的隐含波动率应该相等(对相同标的资产的预期相同)
 当认沽和认购期权之间的隐含波动率不同时,就存在套利空间
 隐含波动率存在套利空间时,可以通过做多隐含波动率低的期权,做空隐含波动率高的期权实现套利
反向操作认沽和认购期权得到的组合,其收益等同于一个forward,可以通过基础资产进行对冲,减少基础资产价格波动带来的风险

1.2. 实现思路
1. 筛选出所有剩余有效期为30天的期权(持有时间30天)
 计算期权剩余有效期
 根据剩余有效期对期权数据进行筛选
2. 计算合约的隐含波动率
定义函数计算欧式期权的价值
定义函数计算期权的Vega值
使用数值方法计算的隐含波动率
 3. 根据隐含波动率筛选可以交易的期权对(隐含波动率之差大的期权对)
将相同到期日的认沽和认购期权配对
计算期权对的隐含波动率之差
4. 计算筛选结果的盈亏情况
计算期权和标的资产(50ETF)的期初和期末价格
计算期权和标的资产组合的收益率

#!/usr/bin/env python
# coding: utf-8

# # 隐含波动率套利策略



from math import log, e, sqrt, pi

from scipy import stats
import tushare as ts
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns

sns.set_style('darkgrid')
mpl.rcParams['figure.figsize'] = (12,6)    # 设置matplotlie全局参数


# In[2]:


pip install tushare


# In[4]:



import tushare as ts


pro = ts.pro_api('#settoken')


# In[5]:




df = pro.opt_basic(exchange='DCE', fields='ts_code,name,exercise_type,list_date,delist_date')
df.tail()


# In[19]:


# 读入期权交易数据
filepath=r"C:\Users\pc\Documents\WeChat Files\wxid_ehvkz0rvd75q12\FileStorage\File\2020-11\option1.csv"
data = pd.read_csv(filepath)
data


# ### 2.1. 根据期权调整标记筛选数据

# In[20]:


def get_modify_mark(ticker):
    """ 定义函数,从ticker字符串中获取期权合约调整标记 """
    return ticker[-6]

ticker = '510050C1503M02200'
get_modify_mark(ticker)


# In[6]:


# # 将get_modify_mark()函数映射到ticker列上,获取所有数据的合约调整标记
modifiy_mark = data.ticker.map(get_modify_mark)
modifiy_mark.head()


# In[7]:


# 筛选并保留原始期权数据中调整标记为M的期权数据,即不考虑基础资产分红等情况
data = data[modifiy_mark == 'M'].copy()


# ### 2.2. 获取期权类型和行权价

# In[8]:


def get_opt_type(ticker: str):
    """ 定义函数,从ticker字符串中获取期权类型 """
    return ticker[6].lower()

ticker = '510050C1503M02200'
get_opt_type(ticker)


# In[9]:


def get_strick(ticker: str):
    """ 定义函数,从ticker字符串中提取出期权的行权价格 """
    return int(ticker[-5:]) / 1000

ticker = '510050C1503M02200'
get_strick(ticker)


# In[10]:


# 从合约代码中获取合约行权价格
data['strikePrice'] = data.ticker.map(get_strick)

# 从合约代码中获取合约类型
data['optType'] = data.ticker.map(get_opt_type)
data


# ### 2.3. 计算期权剩余有效期

# In[11]:


# 将字符串日期转换为日期时间类型
data['tradeDate'] = pd.to_datetime(data.tradeDate)


# In[12]:


# 根据交易日期计算合约到期日期(仅对已经到期的合约有效,未到期合约只能获得数据中最后一个交易日的日期
last_trade_date = data.groupby('ticker').tradeDate.last()
last_trade_date


# In[13]:


# 将合约到期日期填入 data 数据表中
data['lastTradeDate'] = data.ticker.map(last_trade_date)
data


# In[14]:


# 计算合约剩余时间
data['daysLeft'] = (data.lastTradeDate - data.tradeDate).map(lambda x: x.days)
data


# ### 2.4. 计算期权匹配字符串

# In[56]:


def get_match_mark(ticker: str):
    return ticker[-10:]


# In[57]:


# 根据合约代码计算match_mark,match_mark的作用是筛选出相同到期时间的认沽和认购期权
data['match_mark'] = data.ticker.map(get_match_mark)
data.head()


# In[58]:


etf =ts.pro_bar('510050', start_date='20140101')
etf


# In[59]:


pro.opt_daily('510050C1503M02200',trade_date='2018-01-01')


# In[60]:


df = pro.opt_daily(ts_code= '10002495.SH')
df


# In[61]:


ts.pro_bar(ts_code='000001.SZ')


# ### 2.5. 获取50ETF行情数据

# In[62]:


# 获取50etf价格数据
etf =ts.pro_bar('510050', '2014-01-01')
# 将日期列转换为日期时间格式,并设为索引,为数据拼接做准备
etf['date'] = pd.to_datetime(etf.date)
etf.set_index('date', inplace=True)
# 计算50etf日收益率
etf['d_return'] = etf.close.pct_change()
# 计算50etf的历史波动率
etf['volatility'] = etf.d_return.rolling(120).std() * sqrt(252)
etf


# ## 3. 策略实现

# ### 3.1. 计算隐含波动率

# In[63]:


# 筛选特定到期时间的合约
days_left_selected = data[data.daysLeft == 30].copy()
days_left_selected


# In[64]:


# 将50etf数据填入到筛选后的数据表
days_left_selected['etfClose'] = days_left_selected.tradeDate.map(etf.close)
days_left_selected['volatility'] = days_left_selected.tradeDate.map(etf.volatility)
days_left_selected


# In[65]:


def get_option_price(S, X, r, q, sigma, T, opt_type='call'):
    """
    根据BSM模型计算期权价格
    param S: 标的资产当前价格
    param X: 期权行权价格
    param r: 无风险收益率
    param q: 基础资产分红收益率
    param sigma:标的资产年化波动率
    param T: 以年计算的期权到期时间
    param opt_type: 'call'或'put'
    return 返回期权价格
    """
    d1 = (log(S/X) + (r + sigma**2/2) * T) / (sigma * sqrt(T))
    d2 = d1 - sigma * sqrt(T)
    N1, N2 = stats.norm.cdf([d1, d2])
    call = S * N1 - X * e ** (-r * T) * N2      # 计算认购期权价格
             
    if opt_type.lower()[0] == 'c':
        return call
    else: 
        put = call + X * e ** (-r * T) - S  # 利用期权平价模型计算put价格
        return put


# In[66]:


def vega(S, X, r, q, sigma, T):
    """
    定义函数计算期权的Vega
    param S: 标的资产当前价格
    param X: 期权行权价格
    param r: 复合无风险收益率
    param q: 基础资产分红收益率
    param sigma:标的资产年化波动率
    param T: 以年计算的期权到期时间
    return:返回期权的 Vega 值
    """
    d1 = (log(e**(-q*T)*S/X) + (r + sigma**2/2) * T) / (sigma * sqrt(T))
#     N_dash = e ** (-d1 ** 2 / 2)/sqrt(2 * pi)
    N_dash = stats.norm.pdf(d1)
    return S * N_dash * sqrt(T)


# In[67]:


S = 2.676
X = 2.2
r = 0.0246
q = 0
sigma = 0.4
# T = 30/365
T = 0.08219

vega(S, X, r, q, sigma, T)


# In[68]:


# 牛顿法计算隐含波动率
def get_implied_vol(S, X, r, q, T, V_mkt, opt_type='call'):
    """
    根据期权数据计算隐含波动率
    param S: 标的资产当前价格
    param X: 期权行权价格
    param r: 复合无风险收益率
    param q: 基础资产分红收益率
    param T: 以年计算的期权到期时间
    param V_mkt: 期权的市场价格
    param opt_type:期权类型,put或call
    return:返回隐含波动率
    """
    sigma = 0.5
    error = 10**(-6)
    
    for i in range(100):
        V = get_option_price(S, X, r, q, sigma, T, opt_type)      # BSM计算的期权价格
        if abs(V - V_mkt) < error:                                # 精度达到要求
            return sigma
        Vega = vega(S, X, r, q, sigma, T)  
        if Vega < 0.0001:                   # Vega太小的情况下会导致计算不收敛,因此在Vega过小时直接返回一个很小的隐含波动率
            return np.nan
        sigma = sigma - (V - V_mkt)/ Vega   # 迭代计算sigma,g(x)=(V-V_mkt), g'(x)=vega
    return sigma


# In[77]:


get_implied_vol(S, X, r, q, T, 1, opt_type='call')


# In[117]:


def get_iv_wrapper(option_record):
    """ 定义函数,根据期权数据信息计算对应的隐含波动率 """
    S = option_record.etfClose
    X = option_record.strikePrice
    r = 0.0246
    q = 0
    T = option_record.daysLeft / 365
    V_mkt = option_record.closePrice
    opt_type = option_record.optType
    return get_implied_vol(S, X, r, q, T, V_mkt, opt_type)


# In[118]:


record = days_left_selected.iloc[6]
record


# In[119]:


# 测试get_iv_wrapper()函数
get_iv_wrapper(record)


# In[120]:


# 计算合约对应的隐含波动率
days_left_selected['impliedVol'] = days_left_selected.apply(get_iv_wrapper, axis=1)
days_left_selected


# ### 3.2. 根据隐含波动率筛选可以交易的期权对

# In[122]:


# 仅保留隐含波动率不为空值的期权合约做下一步处理
iv_selcted = days_left_selected[days_left_selected.impliedVol.notna()].copy()
iv_selcted


# In[146]:


# 将认沽和认购合约分开,为计算差值做准备
call = iv_selcted[iv_selcted.optType == 'c'].set_index(['match_mark', 'tradeDate'])
put = iv_selcted[iv_selcted.optType == 'p'].set_index(['match_mark', 'tradeDate'])
call


# In[148]:


len(put)


# In[124]:


# 计算认沽和认购合约的隐含波动率差值
iv_diff = call.impliedVol - put.impliedVol
iv_diff


# In[125]:


iv_diff.name = 'iv_diff'


# In[126]:


# 观察认沽和认购期权隐含波动率差值的分布情况
iv_diff.hist(bins=50, alpha=0.5)
plt.show()


# In[127]:


# 筛选出隐含波动率只差大于0.1的期权对
selected = pd.DataFrame(iv_diff[iv_diff < -0.2])
selected


# ### 3.3. 根据筛选结果计算每次交易的盈亏

# #### 3.3.1. 处理单一期权组合

# In[128]:


# 获取期权对代码和起始交易时间
match_mark_sample = selected.index[1][0]
start_date_sample = selected.index[1][1]
start_date_sample


# In[149]:


match_mark_sample


# In[129]:


# 获取期权到期时间
data_selected = data[(data.match_mark == match_mark_sample) & (data.tradeDate >= start_date_sample)]
end_date_sample = data_selected.tradeDate.sort_values().iloc[-1]
end_date_sample


# In[130]:


columns = ['start_date', 'end_date']


# In[131]:


# 获得起始和终止时间的认购期权的收盘价
call = data_selected[(data_selected.optType == 'c')]
call_close = call.closePrice.iloc[[0, -1]]
call_close


# In[132]:


# 获得起始和终止时间的认沽期权的收盘价
put = data_selected[(data_selected.optType == 'p')]
put_close = put.closePrice.iloc[[0, -1]]
put_close


# In[133]:


# 获取起始和终止时间50etf的收盘价
etf_close = etf.close.loc[[start_date_sample, end_date_sample]]
etf_close


# In[134]:


# 单笔交易的价格变动和盈亏情况
portfolio = pd.DataFrame([call_close.values, put_close.values, etf_close.values], index=['call', 'put', 'etf'], columns=['start', 'end'])
portfolio['factor'] = [1, -1, -1]
portfolio['profit'] = (portfolio.end - portfolio.start) * portfolio.factor
portfolio


# In[135]:


# 计算交易盈亏
profit = portfolio.profit.sum()
profit


# In[150]:


# 计算初始资金
initial_cost = portfolio.at['call', 'start'] + portfolio.at['etf', 'start'] * 0.5 - portfolio.at['put', 'start']    # 按照50%的做空保证金计算初始占用资金的情况
initial_cost


# In[151]:


# 计算收益率
rate_of_return = profit / initial_cost
rate_of_return


# #### 3.3.2. 计算所有筛选出的期权对的交易结果

# In[138]:


# 定义函数,获得一个期权组合对应的起止价格和50_etf的价格
def get_close_data(iv_diff_series, data_df, etf_df):
    match_mark = iv_diff_series.name[0]
    start_date = iv_diff_series.name[1]
    data_selected = data_df[(data_df.match_mark == match_mark) & (data_df.tradeDate >= start_date)]
    end_date = data_selected.tradeDate.sort_values().iloc[-1]
    call = data_selected[(data_selected.optType == 'c')]
    call_close = call.closePrice.iloc[[0, -1]]  
    put = data_selected[(data_selected.optType == 'p')]
    put_close = put.closePrice.iloc[[0, -1]]
    etf_close = etf.close.loc[[start_date, end_date]]
    result = pd.concat([call_close, put_close, etf_close], ignore_index=True)
    result.name =iv_diff_series.name 
    return result


# In[139]:


# 对所有期权组合应用get_close_data()函数,获得相关的价格数据
# selected = pd.DataFrame(selected)
cost_profit = selected.apply(get_close_data, args=[data, etf], axis=1)
cost_profit.columns = ['call_start', 'call_end', 'put_start', 'put_end', 'etf_start', 'etf_end']
data_concat = pd.concat([selected, cost_profit], axis=1)
data_concat


# In[140]:


# 计算收益
data_concat['call_pl'] = data_concat.call_end - data_concat.call_start
data_concat['put_pl'] = data_concat.put_start - data_concat.put_end
data_concat['etf_pl'] = data_concat.etf_start - data_concat.etf_end
data_concat['total_pl'] = data_concat.call_pl + data_concat.put_pl + data_concat.etf_pl
data_concat['initial_cost'] = data_concat.call_start - data_concat.put_start + data_concat.etf_start * 0.5     # 按照50%融券保证金比例计算
data_concat['rate_of_return'] = data_concat.total_pl / data_concat.initial_cost
data_concat


# In[144]:


data_concat.rate_of_return.hist(bins=100, alpha=0.5)
plt.show()


# In[145]:


data_concat[data_concat.rate_of_return == data_concat.rate_of_return.min()]


# ### 3.4. 策略细节说明:
# * 选择剩余到期时间为一个月的合约主要考虑到期权合约存在时间价值,剩余时间较长的合约,会存在比较大的时间价值的减值
# * 只选择认购期权波动率低而认沽期权波动率高的机会进行操作,因为这种情况下的对冲操作是做空基础资产,占用资金相对较少,可以实现更高的资金利用率
# 
# ## 4. 扩展方向:
# * 不同到期时间对套利结果的影响
# * 不同行权价格对套利结果的影响(平价 vs. 深度价内或价外)
# * 不进行delta对冲的情况下组合整体的收益情况

# In[6]:


from IPython.core.interactiveshell import InteractiveShell


# In[7]:


InteractiveShell.ast_node_interactivity = "all"


# In[ ]:




 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值