| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- # 买入日历价差策略-商品期货
- # 参考资料:
- # - 原始策略来源: https://www.joinquant.com/view/community/detail/af07500292294804acd19f7f0f5b23e4
- # - 研究网址: 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-%E5%95%86%E5%93%81%E6%9C%9F%E8%B4%A7.ipynb
- import jqdata
- from jqdata import *
- import pandas as pd
- import numpy as np
- import datetime
- import matplotlib.pyplot as plt
- from datetime import datetime, timedelta
- plt.rcParams['font.sans-serif']=['SimHei']
- plt.rcParams['axes.unicode_minus'] = False
- #获取期权合约信息
- 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(symbol,date,type="CO"): # CO为认购,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.underlying_symbol == symbol,
- opt.OPT_CONTRACT_INFO.contract_type == type, # 期权类型
- opt.OPT_CONTRACT_INFO.last_trade_date >= date,
- opt.OPT_CONTRACT_INFO.list_date <= date
- )
-
- contract_info = opt.run_query(q_contract_info)
-
- commodity_cls = get_price(symbol, date, date, fields=['close']).values[0][0]
-
-
- if type == 'CO':
-
- contract_info['price_spread'] = contract_info['exercise_price'] - commodity_cls
-
- else:
-
- contract_info['price_spread'] = commodity_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['code'].iloc[0])
- return(contract_info)
- #获取期权价格等信息
- def getContractExercisePrice(code,last_trade_date,type='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.code == code,
- opt.OPT_CONTRACT_INFO.contract_type == type, # 期权类型
- opt.OPT_CONTRACT_INFO.last_trade_date >= last_trade_date,
- )
-
- contract_info = opt.run_query(q_contract_info)
- return(contract_info)
- #处理合约切换标记
- def contractChange(holding_contract,main_list,error_date):
-
- data2 = pd.DataFrame(holding_contract)
- data2.columns = ['holding_contract']
- data2 = data2.reindex(columns=['holding_contract','close','last_close'])
- data2 = data2.drop(error_date)
- main_list = main_list.drop(error_date)
- 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,price_gap,fee):
-
- #计算平仓期权的收益
- 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] = -price_gap*(data2.loc[t,'close'] - pre_close2)
-
- else:
-
- opt_ret2[t] = -price_gap*(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.last_trade_date == enddate, # 到期月
- opt.OPT_CONTRACT_INFO.exercise_price == exercise_price) #指定行权价
-
- contract_info = opt.run_query(q_contract_info)
-
- return(contract_info)
- #获得指定行权价以及合约的期权
- def getContractForContractAndPrice(symbol,exercise_price,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.underlying_symbol == symbol,
- opt.OPT_CONTRACT_INFO.contract_type == contratType, #期权类型
- opt.OPT_CONTRACT_INFO.exercise_price == exercise_price, #指定行权价
- opt.OPT_CONTRACT_INFO.list_date <= date)
-
- contract_info = opt.run_query(q_contract_info)
-
- return(contract_info)
- # 返回相对于主力合约之前,或之后的合约,用来定位近月或远月合约,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])
- # 设置各项参数
- # 合约代码
- symbol = 'AL'
- #手续费
- fee = 5
- #合约价差
- price_gap = 10
- #起始时间
- starttime = '2022-01-01'
- endtime = '2024-05-01'
- SUBJECT_MATTER = get_dominant_future(symbol,date = starttime)
- SUBJECT_MATTER
- #指定回测的起始时间
- trade_days = pd.Series(index=jqdata.get_trade_days(starttime,endtime))
- trade_days.index = pd.to_datetime(trade_days.index)
- ##持仓情况
- #主力合约列表
- main_list = pd.Series(index=trade_days.index)
- main_list[trade_days.index[0]] = SUBJECT_MATTER
- close_main_list = pd.Series(index=trade_days.index)
- after_main_list = pd.Series(index=trade_days.index)
- #获取首个近月认购持仓合约
- holding_contract_close = pd.Series(index=trade_days.index)
- close_main = findContractsForMonth(symbol,trade_days.index[0],-1)
- close_main_list[trade_days.index[0]] = close_main
- contract_info = getContract(close_main,trade_days.index[0],type='CO')
- if contract_info.empty: #近月合约已经过期,则采用主力合约替代
- contract_info = getContract(SUBJECT_MATTER,trade_days.index[0],type='CO')
- close_main_list[trade_days.index[0]] = SUBJECT_MATTER
- contract = contract_info['code'].iloc[0]
- holding_contract_close[trade_days.index[0]] = contract
- print(holding_contract_close[:3])
- #获取首个远月认购持仓合约
- holding_contract_after = pd.Series(index=trade_days.index)
- after_main = findContractsForMonth(symbol,trade_days.index[0],1)
- contract_info = getContractForContractAndPrice(after_main,contract_info['exercise_price'].iloc[0],trade_days.index[0],'CO')
- after_main_list[trade_days.index[0]] = after_main
- contract = contract_info['code'].iloc[0]
- holding_contract_after[trade_days.index[0]] = contract
- print(holding_contract_after[:3])
- # 循环访问每一个交易日,判断交易情况
- # 规则: 同时卖出一份近月认购期权以及买入相同行权价的远月认购,待行权价低于现价的95%时
- # 平仓原期权合约,重新调整持仓;到期前1天移仓换月至次月合约
- error_date =[]
- pre_hold_close = holding_contract_close[0]
- pre_hold_after = holding_contract_after[0]
- for i in range(1,len(trade_days)):
-
- pre_day = trade_days.index[i-1]
- cur_day = trade_days.index[i]
- cur_main = get_dominant_future(symbol,date = cur_day) #当前主力合约
- pre_main = get_dominant_future(symbol,date = pre_day) #上一个交易日的主力合约
- main_list[cur_day] = cur_main
- close_main_list[cur_day] = close_main_list[pre_day]
- after_main_list[cur_day] = after_main_list[pre_day]
-
- 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]
-
- if cur_main != pre_main or expire_close < cur_day.date() or expire_after < cur_day.date(): #主力合约切换 或者合约到期
- close_main = findContractsForMonth(symbol,cur_day,-1)
- close_main_list[cur_day] = close_main
- contract_info_close = getContract(close_main,cur_day,type='CO')
- if contract_info_close.empty: #近月合约已经过期,则采用主力合约替代
- contract_info_close = getContract(cur_main,cur_day,type='CO')
- if contract_info_close.empty:
- error_date.append(cur_day)
- continue
- close_main_list[cur_day] = cur_main
- contract_close = contract_info_close['code'].iloc[0]
- #获取相同行权价、到期日的认沽合约
- after_main = findContractsForMonth(symbol,cur_day,1)
- after_main_list[cur_day] = after_main
- contract_info_after = getContractForContractAndPrice(after_main,contract_info_close['exercise_price'].iloc[0],cur_day,'CO')
- if contract_info_after.empty:
- error_date.append(cur_day)
- continue
- contract_after = contract_info_after['code'].iloc[0]
- else:
- pre_cls = get_price(close_main_list[cur_day], cur_day, cur_day, fields=['pre_close']).values[0][0]
- contract_info_close = getContractExercisePrice(pre_hold_close,cur_day,"CO")
- pre_exercise_price_close = contract_info_close
- contract_info_after = getContractExercisePrice(pre_hold_after,cur_day,"CO")
- pre_exercise_price_after = contract_info_after
- if pre_exercise_price_close.empty | pre_exercise_price_after.empty:
- error_date.append(cur_day)
- continue
- else:
- pre_exercise_price_close = pre_exercise_price_close['exercise_price'][0]
- if pre_cls * 0.95 >= pre_exercise_price_close:
- close_main = findContractsForMonth(symbol,cur_day,-1)
- close_main_list[cur_day] = close_main
- contract_info_close = getContract(close_main,cur_day,type='CO')
- if contract_info_close.empty: #近月合约已经过期,则采用主力合约替代
- contract_info_close = getContract(cur_main,cur_day)
- close_main_list[cur_day] = cur_main
- contract_close = contract_info_close['code'].iloc[0]
- after_main = findContractsForMonth(symbol,cur_day,1)
- after_main_list[cur_day] = after_main
- contract_info_after = getContractForContractAndPrice(after_main,contract_info_close['exercise_price'].iloc[0],cur_day,'CO')
- contract_after = contract_info_after['code'].iloc[0]
- else:
- contract_close = pre_hold_close
- contract_after = pre_hold_after
-
- holding_contract_close[cur_day] = contract_close
- pre_hold_close = contract_close
- holding_contract_after[cur_day] = contract_after
- pre_hold_after = contract_after
- holding_contract_close = holding_contract_close.fillna(method='ffill')
- holding_contract_after = holding_contract_after.fillna(method='ffill')
- data_list_close = contractChange(holding_contract_close,close_main_list,error_date)
- data_list_after = contractChange(holding_contract_after,after_main_list,error_date)
- #计算平仓期权的收益
- opt_ret_close = optionProfit(data_list_close,price_gap,fee)
- opt_ret_after = optionProfit(data_list_after,price_gap,fee)
- opt_ret = opt_ret_close + opt_ret_after
- print(opt_ret)
- #计算现货收益
- commodity_price = [get_price(main_list[t],data_list_close.index[t],data_list_close.index[t],fields=['close'])['close'][0] for t in range(0,len(data_list_close.index))]
- commodity_price = pd.Series(commodity_price,index=data_list_close.index)
- commodity_ret = commodity_price.diff(1).fillna(0)
- commodity_ret
- #计算净值
- init_asset2 = commodity_price.iloc[0]*price_gap
- 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=(30,20))
- plt.plot(commodity_price/commodity_price.iloc[0], label='现货净值')
- plt.plot(pfl_nv2, label=symbol+ '买入日历价差策略净值')
- plt.legend(loc='upper left', fontsize='large')
- plt.xlabel('时间',size=12)
- plt.ylabel('净值',size=12)
- plt.show()
|