一、跨式结构
贸易战以来,特朗普的大嘴巴给A股带来各种“惊喜”。如何利用这种惊喜盈利呢?
我们知道,跨式结构或者宽跨(勒式)结构可以在波动较大的环境下盈利。买入跨式结构,如果标的价格不波动,则亏损期权时间价值(亏theta),若价格波动大,无论涨跌,则获得一定比例的股票波动收益。(赚delta)
那么如果在假期前的最后交易日买入跨式或者宽跨式结构,在假期过完第一天平仓了结,会有什么结果。
二、跨在哪里?
平值期权几乎是不存在的,交易所期权的执行价格都落在整数位,比如2.8,2.85,2.9等,而标的价格可以在任何位置。所以期权跨在哪里需要事先定下规则。原则上,希望左右两边尽量对称,保持平衡。
如果现价离最近的执行价格不高于0.01,则选取最近的执行价格建立跨式结构。
举个例子,S=2.903,位于2.9和2.95之间,距离最近的执行价格是2.9,且不超过0.01,那么在2.9处建立跨式结构。
如果现价离最近的执行价格超过0.01,则选取临近两边的执行价格建立宽跨结构。
举个例子,S=2.92,位于2.9和2.95之间,距离最近的执行价格2.9超过了阈值0.01,那么在2.9和2.95建立宽跨结构。即买入执行价格为2.95认购期权,买入执行价格为2.9的认沽期权。
合约期限上,选择远月合约。因为theta会随着到期临近快速衰减。选取近月合约在下半月成本过高且存在到期问题,而选取下季、隔季合约期限过长,影响获利。
三、回测结果
回测起始于贸易战开始后,结果如下:
四、收益与仓位管理
上图列出了每次跨假期开平仓的收益率,那么如果是一个组合产品,实际考虑的收益率是如何的呢?
对于衍生品投资,风险管理要比追逐高收益更重要。所以,维持现金,使得每一次掷硬币都能保持相同的筹码,才能使我们策略长期执行下去。举例来讲,假如说我有20万,每次我都会花10万块购买期权,剩余的钱以现金形式存在。很明显,本策略的最大回撤远不到50%,所以,可以保证每次开仓我都可以购买10万块钱的期权。
基于上述策略,我们的收益就可以表示为每次收益之和,再除以2,自3月底至今,收益接近18%(非年化)。
五、2.0
回测的目的在于在用尽可能简单的办法证明在这个充满不确定的市场中跨式组合是可以盈利的。在回测的过程中,发现了一些实际问题,在此抛砖引玉,为2.0埋下伏笔。
2.天然的,Put的delta要比Call小,从绝对值来看。手中期权并非持有到期,所以更希望持有一个delta neutral的组合。所以,如果股价接近左边执行价格但又不是十分靠近的时候,不如把put跨在右边。这么做应该会提高收益,因为过去半年,股市处于下跌过程,把put跨在右边,put更加实值了,delta负的更多了,在下跌行情中是好事。这个问题最合理的修正方法就是通过调整put和call的数量,构建一个中性组合。不过,我更倾向于现在,简单粗暴,适合告诉普通投资者,也多少避免些调参之嫌疑。
六. 函数
代码中的部分方程可能会协助你的期权策略研究
get_50ETF_option 获得当日所有50ETF期权信息
get_target_option获得构建跨式组合的期权信息,
term=0近月,1远月,2下季,3隔季,
strategy=0 找最近执行价格建立跨式
strategy=1 就是文中提到的宽胯
strategy=2 就是再宽一点
get_50ETF_price_byday 获取某日某期权的收盘价格
七、买Vs卖
今年事件多,买宽胯能赚钱,若是平静的一年,岂不是赚不到钱?
买跨式属于“花小钱,中大奖”。卖跨式属于“赚小钱,亏大钱”。假如硬要逼着一生一世坚持一个策略,选哪个取决于信仰。先抛弃那些回测,毕竟期权历史在中国并不长,其次,业务初期期权定价也不合理。我们从另一个角度来“胡扯”这个问题。
有人说,长期来看,卖权更赚钱,因为卖权人承受更多的不确定性,更大的波动性,所以需要风险补偿。
有人说,长期来看,买权更赚钱,因为历史上的黑天鹅事件远比人们想象的要多。
对于这个问题,想起一句老话,“占小便宜吃大亏”,“吃亏是福”。不知道说这话的老人是不是炒期权的。
import datetime
import jqdata
from jqdata import *
from jqdata import jy
import numpy as np
import pandas as pd
from scipy import stats
import seaborn as sns
import matplotlib.pyplot as plt
# 获得交易日日期序列
end_date=datetime.date.today()
start_date=datetime.date(2018,3,25)
date_list=list(jqdata.get_trade_days(start_date=start_date,end_date=end_date))
# 获得假期前一个工作日
open_date_list=[]
close_date_list=[]
for i in range(len(date_list)-1):
if (date_list[i+1]-date_list[i]).days!=1:
open_date_list.append(date_list[i])
close_date_list.append(date_list[i+1])
def get_50ETF_option(date,dfSignal=False):
S0=get_price('510050.XSHG',start_date=date,end_date=date,fields=['close']).values[0][0]
r=0.03
q=query(jy.Opt_DailyPreOpen).filter(jy.Opt_DailyPreOpen.TradingDate==date,jy.Opt_DailyPreOpen.ULAName=='50ETF')
df=jy.run_query(q).loc[:,['ContractCode','TradingCode','StrikePrice','ExerciseDate']]
exercise_date_list=sorted(df['ExerciseDate'].unique())
key_list=[]
Contract_dict={}
Price_dict={}
impVol_dict={}
for exercise_date in exercise_date_list:
#获得T型代码
df1=df[df['ExerciseDate']==exercise_date]
#去除调整合约
check=[]
for i in df1['TradingCode']:
x=True if i[11]=='M' and i[6]=='C' else False
check.append(x)
df_C=df1[check][['ContractCode','StrikePrice']]
df_C.index=df_C.StrikePrice.values
del df_C['StrikePrice']
df_C.columns=['Call']
df_C=df_C.sort_index()
#去除调整合约
check=[]
for i in df1['TradingCode']:
x=True if i[11]=='M' and i[6]=='P' else False
check.append(x)
df_P=df1[check][['ContractCode','StrikePrice']]
df_P.index=df_P.StrikePrice.values
del df_P['StrikePrice']
df_P.columns=['Put']
df_P=df_P.sort_index()
dfT=pd.concat([df_C,df_P],axis=1)
exercise_date=datetime.datetime.strptime(str(exercise_date)[:10],'%Y-%m-%d')
exercise_date=datetime.date(exercise_date.year,exercise_date.month,exercise_date.day)
Contract_dict[exercise_date]=dfT
#T型价格
q=query(jy.Opt_DailyQuote).filter(jy.Opt_DailyQuote.TradingDate==date)
df2=jy.run_query(q).loc[:,['ContractCode','ClosePrice']]
df2.index=df2['ContractCode'].values
del df2['ContractCode']
dfPrice=dfT.copy()
dfPrice['Call']=df2.loc[dfT.loc[:,'Call'].values,:].values
dfPrice['Put']=df2.loc[dfT.loc[:,'Put'].values,:].values
dfPrice=dfPrice
Price_dict[exercise_date]=dfPrice
key_list.append(exercise_date)
strike_price=list(Contract_dict[key_list[0]].index)
atm_index=list(abs(np.round(S0-strike_price,3))).index(min(abs(np.round(S0-strike_price,3))))
atm_K=strike_price[atm_index]
if dfSignal:
value_list=[]
for key,value in Contract_dict.items():
value['exercise_date']=key
value_list.append(value)
Contract_df=pd.concat(value_list).sort('exercise_date')
value_list=[]
for key,value in Price_dict.items():
value['exercise_date']=key
value_list.append(value)
Price_df=pd.concat(value_list).sort('exercise_date')
return Contract_df,Price_df,key_list,S0
return Contract_dict,Price_dict,key_list,S0
def get_target_option(Contract_dict,Price_dict,key_list,S0,term=0,strategy=1):
# Contract合约数据 Price价格数据 key_list是四个合约的到期日
#S0当前标的价格 term远近合约选择 strategy策略
expiry_date=key_list[term]
Contract=Contract_dict[expiry_date]
Price=Price_dict[expiry_date]
strike_price=list(Contract.index)
#选择跨式期权位置
check=min(abs(np.round(S0-strike_price,3)))
if check<0.01:
strategy=0
else:
strategy=strategy
if strategy==0:
atm_index=list(abs(np.round(S0-strike_price,3))).index(min(abs(np.round(S0-strike_price,3))))
atm_k=strike_price[atm_index]
result=pd.DataFrame(index=['Strike','ticker','Price'],columns=['Call','Put'])
result.loc['Strike','Call']=atm_k
result.loc['Strike','Put']=atm_k
result.loc['ticker','Call']=Contract.loc[atm_k,'Call']
result.loc['ticker','Put']=Contract.loc[atm_k,'Put']
result.loc['Price','Call']=Price.loc[atm_k,'Call']
result.loc['Price','Put']=Price.loc[atm_k,'Put']
if strategy==1:
#print(S0)
for i in range(len(strike_price)-1):
if strike_price[i]<=S0 and strike_price[i+1]>S0:
#print(i,i+1)
k1=strike_price[i]
k2=strike_price[i+1]
#print(strike_price)
#print(S0,k1,k2)
result=pd.DataFrame(index=['Strike','ticker','Price'],columns=['Call','Put'])
result.loc['Strike','Call']=k2
result.loc['Strike','Put']=k1
result.loc['ticker','Call']=Contract.loc[k2,'Call']
result.loc['ticker','Put']=Contract.loc[k1,'Put']
result.loc['Price','Call']=Price.loc[k2,'Call']
result.loc['Price','Put']=Price.loc[k1,'Put']
if strategy==2:
for i in range(len(strike_price)-1):
if strike_price[i]<=S0 and strike_price[i+1]>S0:
k1=strike_price[i-1]
k2=strike_price[i+2]
result=pd.DataFrame(index=['Strike','ticker','Price'],columns=['Call','Put'])
result.loc['Strike','Call']=k2
result.loc['Strike','Put']=k1
result.loc['ticker','Call']=Contract.loc[k2,'Call']
result.loc['ticker','Put']=Contract.loc[k1,'Put']
result.loc['Price','Call']=Price.loc[k2,'Call']
result.loc['Price','Put']=Price.loc[k1,'Put']
return result
def get_50ETF_price_byday(date,Code):
q=query(jy.Opt_DailyQuote).filter(jy.Opt_DailyQuote.TradingDate==date,jy.Opt_DailyQuote.ContractCode==Code)
df=jy.run_query(q).loc[:,['ContractCode','ClosePrice']]
price=df.loc[0,'ClosePrice']
return price
N=len(open_date_list)
columns=['t','St','CK','PK','Callcode','Putcode','Callt','Putt','T','ST','CallT','PutT']
df=pd.DataFrame(index=list(range(N)),columns=columns)
for i in range(N):
open_date=open_date_list[i]
Contract_dict,Price_dict,key_list,S0=get_50ETF_option(open_date,dfSignal=False)
table=get_target_option(Contract_dict,Price_dict,key_list,S0,1,1)
df.loc[i,'t']=open_date
df.loc[i,'St']=S0
df.loc[i,'CK']=table.loc['Strike','Call']
df.loc[i,'PK']=table.loc['Strike','Put']
Callcode=table.loc['ticker','Call']
Putcode=table.loc['ticker','Put']
df.loc[i,'Callcode']=Callcode
df.loc[i,'Putcode']=Putcode
df.loc[i,'Callt']=table.loc['Price','Call']
df.loc[i,'Putt']=table.loc['Price','Put']
close_date=close_date_list[i]
df.loc[i,'T']=close_date
df.loc[i,'ST']=get_price('510050.XSHG',start_date=close_date,end_date=close_date,fields=['close']).values[0][0]
df.loc[i,'CallT']=get_50ETF_price_byday(close_date,Callcode)
df.loc[i,'PutT']=get_50ETF_price_byday(close_date,Putcode)
/opt/conda/lib/python3.5/site-packages/sqlalchemy/dialects/mysql/base.py:1936: SAWarning: MariaDB (10, 2, 6) before 10.2.9 has known issues regarding CHECK constraints, which impact handling of NULL values with SQLAlchemy's boolean datatype (MDEV-13596). An additional issue prevents proper migrations of columns with CHECK constraints (MDEV-11114). Please upgrade to MariaDB 10.2.9 or greater, or use the MariaDB 10.1 series, to avoid these issues. "series, to avoid these issues." % (mdb_version, ))
df['ret']=(df['CallT']+df['PutT'])/(df['Callt']+df['Putt'])-1
df
t | St | CK | PK | Callcode | Putcode | Callt | Putt | T | ST | CallT | PutT | ret | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2018-03-30 | 2.713 | 2.75 | 2.7 | 10001288 | 10001296 | 0.0838 | 0.0926 | 2018-04-02 | 2.702 | 0.0741 | 0.0998 | -0.0141723 |
1 | 2018-04-04 | 2.694 | 2.7 | 2.7 | 10001287 | 10001296 | 0.093 | 0.0962 | 2018-04-09 | 2.711 | 0.0991 | 0.0866 | -0.0184989 |
2 | 2018-04-13 | 2.717 | 2.75 | 2.7 | 10001288 | 10001296 | 0.0771 | 0.077 | 2018-04-16 | 2.655 | 0.0527 | 0.1082 | 0.0441272 |
3 | 2018-04-20 | 2.637 | 2.65 | 2.6 | 10001286 | 10001294 | 0.068 | 0.0549 | 2018-04-23 | 2.658 | 0.0772 | 0.0411 | -0.0374288 |
4 | 2018-04-27 | 2.642 | 2.65 | 2.65 | 10001167 | 10001169 | 0.1098 | 0.0932 | 2018-05-02 | 2.646 | 0.1124 | 0.092 | 0.00689655 |
5 | 2018-05-04 | 2.634 | 2.65 | 2.6 | 10001167 | 10001240 | 0.1048 | 0.077 | 2018-05-07 | 2.674 | 0.1224 | 0.059 | -0.00220022 |
6 | 2018-05-11 | 2.716 | 2.75 | 2.7 | 10001131 | 10001140 | 0.074 | 0.067 | 2018-05-14 | 2.745 | 0.0797 | 0.0487 | -0.0893617 |
7 | 2018-05-18 | 2.733 | 2.75 | 2.7 | 10001131 | 10001140 | 0.064 | 0.0411 | 2018-05-21 | 2.736 | 0.0603 | 0.0375 | -0.0694577 |
8 | 2018-05-25 | 2.654 | 2.65 | 2.65 | 10001339 | 10001348 | 0.0877 | 0.0618 | 2018-05-28 | 2.668 | 0.0921 | 0.0538 | -0.0240803 |
9 | 2018-06-01 | 2.643 | 2.65 | 2.65 | 10001339 | 10001348 | 0.0832 | 0.0747 | 2018-06-04 | 2.686 | 0.1008 | 0.0558 | -0.00823306 |
10 | 2018-06-08 | 2.652 | 2.65 | 2.65 | 10001339 | 10001348 | 0.0771 | 0.0644 | 2018-06-11 | 2.663 | 0.0846 | 0.0602 | 0.0233216 |
11 | 2018-06-15 | 2.678 | 2.7 | 2.65 | 10001340 | 10001348 | 0.0645 | 0.054 | 2018-06-19 | 2.612 | 0.0431 | 0.0976 | 0.187342 |
12 | 2018-06-22 | 2.598 | 2.6 | 2.6 | 10001338 | 10001347 | 0.0642 | 0.0592 | 2018-06-25 | 2.548 | 0.0413 | 0.0883 | 0.0502431 |
13 | 2018-06-29 | 2.495 | 2.5 | 2.5 | 10001390 | 10001399 | 0.0842 | 0.0816 | 2018-07-02 | 2.402 | 0.0516 | 0.1525 | 0.231001 |
14 | 2018-07-06 | 2.425 | 2.45 | 2.4 | 10001389 | 10001397 | 0.0759 | 0.0751 | 2018-07-09 | 2.496 | 0.1052 | 0.0402 | -0.0370861 |
15 | 2018-07-13 | 2.509 | 2.5 | 2.5 | 10001390 | 10001399 | 0.0858 | 0.0715 | 2018-07-16 | 2.49 | 0.072 | 0.0747 | -0.0673872 |
16 | 2018-07-20 | 2.54 | 2.55 | 2.5 | 10001391 | 10001399 | 0.0597 | 0.0448 | 2018-07-23 | 2.569 | 0.0751 | 0.036 | 0.0631579 |
17 | 2018-07-27 | 2.575 | 2.6 | 2.55 | 10001241 | 10001276 | 0.08 | 0.0713 | 2018-07-30 | 2.587 | 0.0849 | 0.0645 | -0.0125578 |
18 | 2018-08-03 | 2.457 | 2.45 | 2.45 | 10001311 | 10001312 | 0.0965 | 0.0824 | 2018-08-06 | 2.455 | 0.0956 | 0.0829 | -0.00223589 |
19 | 2018-08-10 | 2.551 | 2.55 | 2.55 | 10001275 | 10001276 | 0.091 | 0.083 | 2018-08-13 | 2.526 | 0.0778 | 0.0933 | -0.0166667 |
20 | 2018-08-17 | 2.42 | 2.45 | 2.4 | 10001311 | 10001358 | 0.0726 | 0.0692 | 2018-08-20 | 2.458 | 0.088 | 0.0528 | -0.00705219 |
21 | 2018-08-24 | 2.523 | 2.55 | 2.5 | 10001448 | 10001456 | 0.073 | 0.0716 | 2018-08-27 | 2.571 | 0.0933 | 0.0524 | 0.00760719 |
22 | 2018-08-31 | 2.521 | 2.55 | 2.5 | 10001448 | 10001456 | 0.0713 | 0.071 | 2018-09-03 | 2.512 | 0.0698 | 0.0733 | 0.00562193 |
23 | 2018-09-07 | 2.491 | 2.5 | 2.5 | 10001447 | 10001456 | 0.0822 | 0.084 | 2018-09-10 | 2.466 | 0.0722 | 0.0938 | -0.00120337 |
24 | 2018-09-14 | 2.475 | 2.5 | 2.45 | 10001447 | 10001455 | 0.0652 | 0.0544 | 2018-09-17 | 2.452 | 0.0489 | 0.0603 | -0.0869565 |
25 | 2018-09-21 | 2.632 | 2.65 | 2.6 | 10001450 | 10001458 | 0.0542 | 0.045 | 2018-09-25 | 2.6 | 0.0419 | 0.055 | -0.0231855 |
26 | 2018-09-28 | 2.659 | 2.65 | 2.65 | 10001475 | 10001484 | 0.1075 | 0.0802 | 2018-10-08 | 2.533 | 0.0533 | 0.1492 | 0.0788492 |
27 | 2018-10-12 | 2.494 | 2.5 | 2.5 | 10001472 | 10001481 | 0.098 | 0.0863 | 2018-10-15 | 2.463 | 0.0816 | 0.1041 | 0.00759631 |
28 | 2018-10-19 | 2.491 | 2.5 | 2.5 | 10001472 | 10001481 | 0.0892 | 0.086 | 2018-10-22 | 2.599 | 0.1499 | 0.0452 | 0.113584 |
29 | 2018-10-26 | 2.516 | 2.55 | 2.5 | 10001314 | 10001322 | 0.1108 | 0.1014 | 2018-10-29 | 2.431 | 0.0781 | 0.1415 | 0.0348728 |
30 | 2018-11-02 | 2.594 | 2.6 | 2.6 | 10001315 | 10001324 | 0.1116 | 0.1117 | 2018-11-05 | 2.558 | 0.1009 | 0.1306 | 0.0367219 |
31 | 2018-11-09 | 2.48 | 2.5 | 2.45 | 10001313 | 10001332 | 0.112 | 0.0865 | 2018-11-12 | 2.49 | 0.1124 | 0.0826 | -0.0176322 |
sum(df['ret'])
0.3555466432564187
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程