# 卖出跨式策略-50ETF # 参考资料: # - 原始策略来源: https://www.joinquant.com/view/community/detail/73a7f044b73242b136c8c840ef7f1748 # - 研究网址: https://www.joinquant.com/research?target=research&url=/user/75474983526/notebooks/Options/%E5%8D%96%E5%87%BA%E8%B7%A8%E5%BC%8F%E7%AD%96%E7%95%A5-50ETF.ipynb # TODO: 添加卖出跨式策略50ETF相关代码 import jqdata from jqdata import * import pandas as pd import numpy as np import datetime import matplotlib.pyplot as plt plt.rcParams['font.sans-serif']=['SimHei'] plt.rcParams['axes.unicode_minus'] = False from datetime import datetime, timedelta def is_last_day_of_month(date_str): # 将字符串转换为日期对象 date_obj = datetime.strptime(date_str, '%Y-%m-%d') # 获取下一个日期对象 next_date_obj = date_obj + timedelta(days=1) # 判断是否为下个月的第一天,如果是,则当前日期为月末 return date_obj.month != next_date_obj.month def get_last_day_of_month(date_str): # 将字符串转换为日期对象 date_obj = datetime.strptime(date_str, '%Y-%m-%d') # 获取下个月的第一天日期对象 next_month_first_day = datetime(date_obj.year, date_obj.month + 1, 1) # 从下个月的第一天减去一天,得到当前月的月末日期对象 last_day_of_month = next_month_first_day - timedelta(days=1) # 返回月末日期的字符串形式 return last_day_of_month.strftime('%Y-%m-%d') #获得虚值合约 def getContract(start,end,listdate,contratType='CO'): q_contract_info = query(opt.OPT_CONTRACT_INFO.code, opt.OPT_CONTRACT_INFO.trading_code, opt.OPT_CONTRACT_INFO.name, #合约代码,合约交易代码,合约简称 opt.OPT_CONTRACT_INFO.exercise_price, opt.OPT_CONTRACT_INFO.last_trade_date, opt.OPT_CONTRACT_INFO.list_date ).filter(opt.OPT_CONTRACT_INFO.contract_type == contratType, #期权类型 opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG', #上交所 opt.OPT_CONTRACT_INFO.last_trade_date > start, #时间-到期月开始 opt.OPT_CONTRACT_INFO.last_trade_date <= end, #时间-到期月结束 opt.OPT_CONTRACT_INFO.list_date < listdate) #在交易前上市 contract_info = opt.run_query(q_contract_info) etf_cls = get_price('510050.XSHG',listdate,listdate,fields=['close']).values[0][0] if contratType == 'CO': contract_info['price_spread'] = contract_info['exercise_price'] - etf_cls else: contract_info['price_spread'] = etf_cls - contract_info['exercise_price'] if contract_info['price_spread'].max() > 0: contract_info = contract_info[contract_info['price_spread'] > 0] #选出虚值期权 contract_info = contract_info.sort_values('exercise_price') else: #全是实值期权 contract_info = contract_info.sort_values('exercise_price',ascending=False) return(contract_info) #处理合约切换标记 def contractChange(holding_contract): data2 = pd.DataFrame(holding_contract) data2.columns = ['holding_contract'] data2 = data2.reindex(columns=['holding_contract','close','last_close']) last_contract = holding_contract.iloc[0] #记录上个持仓 for t in data2.index: if last_contract == data2.loc[t,'holding_contract']: #期权未换仓 #收盘价 q_price = query(opt.OPT_DAILY_PRICE.code, opt.OPT_DAILY_PRICE.date, opt.OPT_DAILY_PRICE.close, ).filter(opt.OPT_DAILY_PRICE.code==last_contract, opt.OPT_DAILY_PRICE.date==t) price = opt.run_query(q_price)['close'][0] data2.loc[t,'close'] = price else: #收盘价,新 q_price = query(opt.OPT_DAILY_PRICE.code, opt.OPT_DAILY_PRICE.date, opt.OPT_DAILY_PRICE.close, ).filter(opt.OPT_DAILY_PRICE.code==data2.loc[t,'holding_contract'], opt.OPT_DAILY_PRICE.date==t) price = opt.run_query(q_price)['close'][0] data2.loc[t,'close'] = price #收盘价,旧 q_price = query(opt.OPT_DAILY_PRICE.code, opt.OPT_DAILY_PRICE.date, opt.OPT_DAILY_PRICE.close, ).filter(opt.OPT_DAILY_PRICE.code==last_contract, opt.OPT_DAILY_PRICE.date==t) price = opt.run_query(q_price)['close'][0] data2.loc[t,'last_close'] = price last_contract = data2.loc[t,'holding_contract'] return(data2) #平仓期权收益 def optionProfit(data2,fee=5): #计算平仓期权的收益 opt_ret2 = pd.Series(0,index=data2.index) pre_close2 = data2['close'].iloc[0] for t in data2.index[1:]: if data2.isna().loc[t,'last_close']: #未换仓,last为空 opt_ret2[t] = -10000*(data2.loc[t,'close'] - pre_close2) else: opt_ret2[t] = -10000*(data2.loc[t,'last_close'] - pre_close2) - fee #手续费 pre_close2 = data2.loc[t,'close'] return(opt_ret2) #获得指定行权价以及到期日的合约 def getContractForPrice(exercise_price,enddate,contratType='PO'): q_contract_info = query(opt.OPT_CONTRACT_INFO.code, opt.OPT_CONTRACT_INFO.trading_code, opt.OPT_CONTRACT_INFO.name, #合约代码,合约交易代码,合约简称 opt.OPT_CONTRACT_INFO.exercise_price, opt.OPT_CONTRACT_INFO.last_trade_date, opt.OPT_CONTRACT_INFO.list_date ).filter(opt.OPT_CONTRACT_INFO.contract_type == contratType, #期权类型 opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG', #上交所 opt.OPT_CONTRACT_INFO.last_trade_date == enddate, #到期月 opt.OPT_CONTRACT_INFO.exercise_price == exercise_price) #指定行权价 contract_info = opt.run_query(q_contract_info) return(contract_info) #获取交易时间和时间间隔(频率:月) #根据不同交易日分割月份 #指定回测的起始时间 starttime = '2020-01-01' endtime = '2024-03-01' trade_days = pd.Series(index=jqdata.get_trade_days(starttime,endtime)) trade_days.index = pd.to_datetime(trade_days.index) last_day = get_last_day_of_month(endtime) month_split = list(trade_days.resample('M',label='left').mean().index) + [pd.to_datetime(last_day)] month_split ##持仓情况 holding_contract_CO = pd.Series(index=trade_days.index) holding_contract_PO = pd.Series(index=trade_days.index) #获取首个持仓认购合约 contract_info_CO = getContract(month_split[0],month_split[1],trade_days.index[0],'CO') holding_contract_CO[trade_days.index[0]] = contract_info_CO['code'].iloc[0] #获取首个持仓认沽合约 contract_info_PO = getContractForPrice(contract_info_CO['exercise_price'].iloc[0],contract_info_CO['last_trade_date'].iloc[0],'PO') holding_contract_PO[trade_days.index[0]] = contract_info_PO['code'].iloc[0] newest_exercise_price = contract_info_CO['exercise_price'].iloc[0] newest_expire_date = contract_info_CO['last_trade_date'].iloc[0] # 循环访问每一个交易日,判断交易情况 # 规则:同时卖出认购期权以及同一到期日的和同一行权价的认沽期权,待行权价低于现价的95%时 # 平仓原期权合约,重新卖出认沽以及认购期权;到期前1天移仓换月至次月合约 erro_date = [] for t in trade_days.index[1:]: #到期前一天 if t >= pd.to_datetime(get_trade_days(end_date=pd.to_datetime(newest_expire_date),count=2)[0]): #寻找month_idx for month_idx in range(len(month_split)): if month_split[month_idx] >= t: break if month_idx == len(month_split) -1: lasttradeday = t break else: lasttradeday = '' contract_info_CO = getContract(month_split[month_idx],month_split[month_idx+1],t,'CO') contract_info_PO = contract_info_PO = getContractForPrice(contract_info_CO['exercise_price'].iloc[0],contract_info_CO['last_trade_date'].iloc[0],'PO') if contract_info_CO['last_trade_date'].iloc[0] >= newest_expire_date: holding_contract_CO[t] = contract_info_CO['code'].iloc[0] holding_contract_PO[t] = contract_info_PO['code'].iloc[0] newest_exercise_price = contract_info_CO['exercise_price'].iloc[0] newest_expire_date = contract_info_CO['last_trade_date'].iloc[0] else: #获取昨日50etf收盘价 pre_cls = get_price('510050.XSHG',t,t,fields=['pre_close']).values[0][0] if pre_cls*0.95 >= newest_exercise_price: #原虚值变为实值,重新开仓略虚值期权 #寻找month_idx for month_idx in range(len(month_split)): if month_split[month_idx] >= t: break contract_info_CO = getContract(month_split[month_idx-1],month_split[month_idx],t,'CO') contract_info_PO = contract_info_PO = getContractForPrice(contract_info_CO['exercise_price'].iloc[0],contract_info_CO['last_trade_date'].iloc[0],'PO') if contract_info_CO['last_trade_date'].iloc[0] >= newest_expire_date: holding_contract_CO[t] = contract_info_CO['code'].iloc[0] holding_contract_PO[t] = contract_info_PO['code'].iloc[0] newest_exercise_price = contract_info_CO['exercise_price'].iloc[0] newest_expire_date = contract_info_CO['last_trade_date'].iloc[0] holding_contract_CO = holding_contract_CO.fillna(method='ffill') holding_contract_PO = holding_contract_PO.fillna(method='ffill') if lasttradeday != '': holding_contract_CO = holding_contract_CO[holding_contract_CO.index < lasttradeday] holding_contract_PO = holding_contract_PO[holding_contract_PO.index < lasttradeday] data_CO = contractChange(holding_contract_CO) data_PO = contractChange(holding_contract_PO) #计算期权的收益 opt_ret_CO = optionProfit(data_CO) opt_ret_PO = optionProfit(data_PO) opt_ret = opt_ret_CO + opt_ret_PO opt_ret #计算现货收益 etf_price = get_price('510050.XSHG',trade_days.index[0],trade_days.index[-1],fields=['close'])['close'] etf_ret = 10000*etf_price.diff(1).fillna(0) etf_ret #计算净值 init_asset2 = etf_price.iloc[0]*10000 ass2 = init_asset2 + opt_ret.cumsum() pfl_ret2 = (ass2/ass2.shift(1) - 1).fillna(0) pfl_nv2 = (1 + pfl_ret2).cumprod() pfl_nv2 #绘制净值图 plt.figure(figsize=(8, 5)) plt.plot(etf_price/etf_price.iloc[0], label='50ETF现货净值') plt.plot(pfl_nv2, label='卖出跨式策略净值') plt.legend(loc='upper left', fontsize='large') plt.xlabel('时间',size=12) plt.ylabel('净值',size=12) plt.show()