# 买入日历价差策略-50ETF # 参考资料: # - 原始策略来源: https://www.joinquant.com/view/community/detail/0d2479d4f374fce4a4b900f2c77d3ba3 # - 研究网址: https://www.joinquant.com/research?target=research&url=/user/75474983526/notebooks/Options/%E4%B9%B0%E5%85%A5%E6%97%A5%E5%8E%86%E4%BB%B7%E5%B7%AE%E7%AD%96%E7%95%A5-50ETF.ipynb 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 getContractForCode(code): 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.code == code, ) contract_info = opt.run_query(q_contract_info) return(contract_info) #获得虚值合约 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.underlying_type == 'ETF',#ETF类型 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 getContractForExerciseDate(exercisedate,date,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.underlying_type == 'ETF',#ETF类型 opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG', #上交所 opt.OPT_CONTRACT_INFO.exercise_date == exercisedate, #指定行权日期 opt.OPT_CONTRACT_INFO.last_trade_date >= date, opt.OPT_CONTRACT_INFO.list_date <= date ) #在交易前上市 contract_info = opt.run_query(q_contract_info) etf_cls = get_price('510050.XSHG',date,date,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 getContractForExerciseDateAndPrice(exercisedate,exerciseprice,date,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.underlying_type == 'ETF',#ETF类型 opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG', #上交所 opt.OPT_CONTRACT_INFO.exercise_date == exercisedate, #指定行权日期 opt.OPT_CONTRACT_INFO.exercise_price == exerciseprice, #指定行权价格 opt.OPT_CONTRACT_INFO.last_trade_date >= date, opt.OPT_CONTRACT_INFO.list_date <= date ) #在交易前上市 contract_info = opt.run_query(q_contract_info) return(contract_info) #获得指定行权日期,以及最接近行权价格的合约 def getContractForExerciseDateAndClosePrice(exercisedate,exerciseprice,date,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.underlying_type == 'ETF',#ETF类型 opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG', #上交所 opt.OPT_CONTRACT_INFO.exercise_date == exercisedate, #指定行权日期 #opt.OPT_CONTRACT_INFO.exercise_price == exerciseprice, #指定行权价格 opt.OPT_CONTRACT_INFO.last_trade_date >= date, opt.OPT_CONTRACT_INFO.list_date <= date ) #在交易前上市 contract_info = opt.run_query(q_contract_info) if not contract_info.empty: contract_info['difference'] = (contract_info['exercise_price'] - exerciseprice).abs() closest_row = contract_info.loc[[contract_info['difference'].idxmin()]] return(closest_row) else: 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 i in range(0,len(data2.index)): t = data2.index[i] 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==data2.index[i-1]) 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) # 返回相对于当前月份,可以交易合约下,之前,或之后的合约,用来定位近月或远月合约,pos = 0 表示返回当月可以交易的合约; # pos=-1,返回当月之前一个周期的可交易合约;pos=1,返回当月之后一个周期的可交易合约 def findContractsForMonth(symbol,date,pos=0): dominant = get_dominant_future(symbol, date=date) contracts_list = get_future_contracts(symbol, date=date) pattern = re.compile(r'\d+') numbers_list = [pattern.findall(contract)[0] for contract in contracts_list] sorted_combined = sorted(zip(contracts_list, numbers_list), key=lambda x: x[1]) sorted_contracts, sorted_B = map(list, zip(*sorted_combined)) num = sorted_contracts.index(dominant) find_pos = num + pos if find_pos<0: find_pos = 0 elif find_pos >= len(contracts_list): find_pos = len(contracts_list) return(sorted_contracts[find_pos]) #获取可用合约的行权日期,并按日期排序 def getAvailableContractExerciseDate(date): q_contract_info = query(opt.OPT_CONTRACT_INFO.underlying_type, 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, opt.OPT_CONTRACT_INFO.exercise_date ).filter(opt.OPT_CONTRACT_INFO.contract_type == 'CO', #期权类型 opt.OPT_CONTRACT_INFO.underlying_type == 'ETF', opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG', #上交所 opt.OPT_CONTRACT_INFO.last_trade_date >= date, #时间-到期月结束 opt.OPT_CONTRACT_INFO.list_date <= date) #在交易前上市 contract_info = opt.run_query(q_contract_info) exercise_date = contract_info['exercise_date'].drop_duplicates() exercise_date = exercise_date.sort_values(ascending=True) return(exercise_date) #获取交易时间和时间间隔(频率:月) #根据不同交易日分割月份 #指定回测的起始时间 starttime = '2021-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) #获取可用合约 avail_exercise_date = getAvailableContractExerciseDate(trade_days.index[0]) close_date = avail_exercise_date.iloc[0] futher_date = avail_exercise_date.iloc[-1] ##持仓情况 holding_contract_close = pd.Series(index=trade_days.index) holding_contract_after = pd.Series(index=trade_days.index) #获取首个近月持仓认购合约 contract_info_close = getContractForExerciseDate(close_date,trade_days.index[0],'CO') holding_contract_close[trade_days.index[0]] = contract_info_close['code'].iloc[0] #获取首个远月持仓认购合约 contract_info_after = getContractForExerciseDateAndClosePrice(futher_date,contract_info_close['exercise_price'].iloc[0],trade_days.index[0],'CO') holding_contract_after[trade_days.index[0]] = contract_info_after['code'].iloc[0] #循环访问每一个交易日,判断交易情况 #规则:同时卖出一份近月认购期权以及买入最接近行权价的远月认购,待行权价低于现价的95%时,平仓原期权合约,重新调整持仓;到期前1天移仓换月至次月合约 pre_hold_close = holding_contract_close[0] pre_hold_after = holding_contract_after[0] error_date = [] for i in range(1,len(trade_days)): #print(i) pre_day = trade_days.index[i-1] cur_day = trade_days.index[i] expire_close = getContractForCode(pre_hold_close) expire_close = expire_close['last_trade_date'].iloc[0] expire_after = getContractForCode(pre_hold_after) expire_after = expire_after['last_trade_date'].iloc[0] pre_cls = get_price('510050.XSHG',cur_day,cur_day,fields=['pre_close']).values[0][0] newest_exercise_price = getContractForCode(pre_hold_close) newest_exercise_price = newest_exercise_price['exercise_price'].iloc[0] if expire_close < cur_day.date() or expire_after < cur_day.date() or pre_cls*0.95 >= newest_exercise_price: #主力合约切换或者合约到期或者原虚值变为实值,重新开仓略虚值期权 avail_exercise_date = getAvailableContractExerciseDate(cur_day) close_date = avail_exercise_date.iloc[0] futher_date = avail_exercise_date.iloc[-2] contract_info_close = getContractForExerciseDate(close_date,cur_day,'CO') holding_contract_close[cur_day] = contract_info_close['code'].iloc[0] contract_info_after = getContractForExerciseDateAndClosePrice(futher_date,contract_info_close['exercise_price'].iloc[0],cur_day,'CO') if contract_info_after.empty: #没有匹配的远期合约,则该日清仓 error_date.append(cur_day) continue holding_contract_after[cur_day] = contract_info_after['code'].iloc[0] else: holding_contract_close[cur_day] = pre_hold_close holding_contract_after[cur_day] = pre_hold_after holding_contract_close = holding_contract_close.fillna(method='ffill') holding_contract_after = holding_contract_after.fillna(method='ffill') holding_contract_close = holding_contract_close.drop(error_date) holding_contract_after = holding_contract_after.drop(error_date) data_close = contractChange(holding_contract_close) data_after = contractChange(holding_contract_after) #计算期权的收益 opt_ret_close = optionProfit(data_close) opt_ret_after = optionProfit(data_after) opt_ret = opt_ret_close + opt_ret_after 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 + (etf_ret + opt_ret).cumsum() pfl_ret2 = (ass2/ass2.shift(1) - 1).fillna(0) pfl_nv2 = (1 + pfl_ret2).cumprod() pfl_nv2 #绘制净值图 plt.figure(figsize=(30,20)) 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()