15_买入日历价差策略商品期货.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. # 买入日历价差策略-商品期货
  2. # 参考资料:
  3. # - 原始策略来源: https://www.joinquant.com/view/community/detail/af07500292294804acd19f7f0f5b23e4
  4. # - 研究网址: 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
  5. import jqdata
  6. from jqdata import *
  7. import pandas as pd
  8. import numpy as np
  9. import datetime
  10. import matplotlib.pyplot as plt
  11. from datetime import datetime, timedelta
  12. plt.rcParams['font.sans-serif']=['SimHei']
  13. plt.rcParams['axes.unicode_minus'] = False
  14. #获取期权合约信息
  15. def getContractForCode(code):
  16. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  17. opt.OPT_CONTRACT_INFO.trading_code,
  18. opt.OPT_CONTRACT_INFO.name,
  19. opt.OPT_CONTRACT_INFO.exercise_price, opt.OPT_CONTRACT_INFO.last_trade_date,
  20. # 行权价格,最后交易日
  21. opt.OPT_CONTRACT_INFO.list_date
  22. ).filter(opt.OPT_CONTRACT_INFO.code == code,)
  23. contract_info = opt.run_query(q_contract_info)
  24. return(contract_info)
  25. def getContract(symbol,date,type="CO"): # CO为认购,PO为认沽
  26. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  27. opt.OPT_CONTRACT_INFO.trading_code,
  28. opt.OPT_CONTRACT_INFO.name,
  29. opt.OPT_CONTRACT_INFO.exercise_price,
  30. opt.OPT_CONTRACT_INFO.last_trade_date,
  31. # 行权价格,最后交易日
  32. opt.OPT_CONTRACT_INFO.list_date
  33. ).filter(opt.OPT_CONTRACT_INFO.underlying_symbol == symbol,
  34. opt.OPT_CONTRACT_INFO.contract_type == type, # 期权类型
  35. opt.OPT_CONTRACT_INFO.last_trade_date >= date,
  36. opt.OPT_CONTRACT_INFO.list_date <= date
  37. )
  38. contract_info = opt.run_query(q_contract_info)
  39. commodity_cls = get_price(symbol, date, date, fields=['close']).values[0][0]
  40. if type == 'CO':
  41. contract_info['price_spread'] = contract_info['exercise_price'] - commodity_cls
  42. else:
  43. contract_info['price_spread'] = commodity_cls - contract_info['exercise_price']
  44. if contract_info['price_spread'].max() > 0:
  45. contract_info = contract_info[contract_info['price_spread'] > 0] # 选出虚值期权
  46. contract_info = contract_info.sort_values('exercise_price')
  47. else: # 全是实值期权
  48. contract_info = contract_info.sort_values('exercise_price', ascending=False)
  49. #return(contract_info['code'].iloc[0])
  50. return(contract_info)
  51. #获取期权价格等信息
  52. def getContractExercisePrice(code,last_trade_date,type='CO'):
  53. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  54. opt.OPT_CONTRACT_INFO.trading_code,
  55. opt.OPT_CONTRACT_INFO.name,
  56. opt.OPT_CONTRACT_INFO.exercise_price,
  57. opt.OPT_CONTRACT_INFO.last_trade_date,
  58. # 行权价格,最后交易日
  59. opt.OPT_CONTRACT_INFO.list_date
  60. ).filter(opt.OPT_CONTRACT_INFO.code == code,
  61. opt.OPT_CONTRACT_INFO.contract_type == type, # 期权类型
  62. opt.OPT_CONTRACT_INFO.last_trade_date >= last_trade_date,
  63. )
  64. contract_info = opt.run_query(q_contract_info)
  65. return(contract_info)
  66. #处理合约切换标记
  67. def contractChange(holding_contract,main_list,error_date):
  68. data2 = pd.DataFrame(holding_contract)
  69. data2.columns = ['holding_contract']
  70. data2 = data2.reindex(columns=['holding_contract','close','last_close'])
  71. data2 = data2.drop(error_date)
  72. main_list = main_list.drop(error_date)
  73. last_contract = holding_contract.iloc[0] #记录上个持仓
  74. for i in range(0,len(data2.index)):
  75. t = data2.index[i]
  76. if last_contract == data2.loc[t,'holding_contract']: #期权未换仓
  77. q_price = query(opt.OPT_DAILY_PRICE.code,
  78. opt.OPT_DAILY_PRICE.date,
  79. opt.OPT_DAILY_PRICE.close,
  80. ).filter(opt.OPT_DAILY_PRICE.code==last_contract,
  81. opt.OPT_DAILY_PRICE.date==t)
  82. price = opt.run_query(q_price)['close'][0]
  83. data2.loc[t,'close'] = price
  84. else: #合约换仓
  85. q_price = query(opt.OPT_DAILY_PRICE.code,
  86. opt.OPT_DAILY_PRICE.date,
  87. opt.OPT_DAILY_PRICE.close,
  88. ).filter(opt.OPT_DAILY_PRICE.code==data2.loc[t,'holding_contract'],
  89. opt.OPT_DAILY_PRICE.date==t)
  90. price = opt.run_query(q_price)['close'][0]
  91. data2.loc[t,'close'] = price
  92. #收盘价,旧
  93. q_price = query(opt.OPT_DAILY_PRICE.code,
  94. opt.OPT_DAILY_PRICE.date,
  95. opt.OPT_DAILY_PRICE.close,
  96. ).filter(opt.OPT_DAILY_PRICE.code==last_contract,
  97. opt.OPT_DAILY_PRICE.date==data2.index[i-1])
  98. price = opt.run_query(q_price)['close'][0]
  99. data2.loc[t,'last_close'] = price
  100. last_contract = data2.loc[t,'holding_contract']
  101. return(data2)
  102. def optionProfit(data2,price_gap,fee):
  103. #计算平仓期权的收益
  104. opt_ret2 = pd.Series(0,index=data2.index)
  105. pre_close2 = data2['close'].iloc[0]
  106. for t in data2.index[1:]:
  107. if data2.isna().loc[t,'last_close']: #未换仓,last为空
  108. opt_ret2[t] = -price_gap*(data2.loc[t,'close'] - pre_close2)
  109. else:
  110. opt_ret2[t] = -price_gap*(data2.loc[t,'last_close'] - pre_close2) - fee #手续费
  111. pre_close2 = data2.loc[t,'close']
  112. return(opt_ret2)
  113. #获得指定行权价以及到期日的合约
  114. def getContractForPrice(exercise_price,enddate,contratType='PO'):
  115. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  116. opt.OPT_CONTRACT_INFO.trading_code,
  117. opt.OPT_CONTRACT_INFO.name, #合约代码,合约交易代码,合约简称
  118. opt.OPT_CONTRACT_INFO.exercise_price,
  119. opt.OPT_CONTRACT_INFO.last_trade_date,
  120. opt.OPT_CONTRACT_INFO.list_date
  121. ).filter(opt.OPT_CONTRACT_INFO.contract_type == contratType, # 期权类型
  122. opt.OPT_CONTRACT_INFO.last_trade_date == enddate, # 到期月
  123. opt.OPT_CONTRACT_INFO.exercise_price == exercise_price) #指定行权价
  124. contract_info = opt.run_query(q_contract_info)
  125. return(contract_info)
  126. #获得指定行权价以及合约的期权
  127. def getContractForContractAndPrice(symbol,exercise_price,date,contratType='CO'):
  128. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  129. opt.OPT_CONTRACT_INFO.trading_code,
  130. opt.OPT_CONTRACT_INFO.name, #合约代码,合约交易代码,合约简称
  131. opt.OPT_CONTRACT_INFO.exercise_price,
  132. opt.OPT_CONTRACT_INFO.last_trade_date,
  133. opt.OPT_CONTRACT_INFO.list_date
  134. ).filter(
  135. opt.OPT_CONTRACT_INFO.underlying_symbol == symbol,
  136. opt.OPT_CONTRACT_INFO.contract_type == contratType, #期权类型
  137. opt.OPT_CONTRACT_INFO.exercise_price == exercise_price, #指定行权价
  138. opt.OPT_CONTRACT_INFO.list_date <= date)
  139. contract_info = opt.run_query(q_contract_info)
  140. return(contract_info)
  141. # 返回相对于主力合约之前,或之后的合约,用来定位近月或远月合约,pos = 0 表示返回主力合约;
  142. # pos=-1,返回主力合约之前一个周期的可交易合约;pos=1,返回主力合约之后一个周期的可交易合约
  143. def findContractsForMonth(symbol,date,pos=0):
  144. dominant = get_dominant_future(symbol, date=date)
  145. contracts_list = get_future_contracts(symbol, date=date)
  146. pattern = re.compile(r'\d+')
  147. numbers_list = [pattern.findall(contract)[0] for contract in contracts_list]
  148. sorted_combined = sorted(zip(contracts_list, numbers_list), key=lambda x: x[1])
  149. sorted_contracts, sorted_B = map(list, zip(*sorted_combined))
  150. num = sorted_contracts.index(dominant)
  151. find_pos = num + pos
  152. if find_pos<0:
  153. find_pos = 0
  154. elif find_pos >= len(contracts_list):
  155. find_pos = len(contracts_list)
  156. return(sorted_contracts[find_pos])
  157. # 设置各项参数
  158. # 合约代码
  159. symbol = 'AL'
  160. #手续费
  161. fee = 5
  162. #合约价差
  163. price_gap = 10
  164. #起始时间
  165. starttime = '2022-01-01'
  166. endtime = '2024-05-01'
  167. SUBJECT_MATTER = get_dominant_future(symbol,date = starttime)
  168. SUBJECT_MATTER
  169. #指定回测的起始时间
  170. trade_days = pd.Series(index=jqdata.get_trade_days(starttime,endtime))
  171. trade_days.index = pd.to_datetime(trade_days.index)
  172. ##持仓情况
  173. #主力合约列表
  174. main_list = pd.Series(index=trade_days.index)
  175. main_list[trade_days.index[0]] = SUBJECT_MATTER
  176. close_main_list = pd.Series(index=trade_days.index)
  177. after_main_list = pd.Series(index=trade_days.index)
  178. #获取首个近月认购持仓合约
  179. holding_contract_close = pd.Series(index=trade_days.index)
  180. close_main = findContractsForMonth(symbol,trade_days.index[0],-1)
  181. close_main_list[trade_days.index[0]] = close_main
  182. contract_info = getContract(close_main,trade_days.index[0],type='CO')
  183. if contract_info.empty: #近月合约已经过期,则采用主力合约替代
  184. contract_info = getContract(SUBJECT_MATTER,trade_days.index[0],type='CO')
  185. close_main_list[trade_days.index[0]] = SUBJECT_MATTER
  186. contract = contract_info['code'].iloc[0]
  187. holding_contract_close[trade_days.index[0]] = contract
  188. print(holding_contract_close[:3])
  189. #获取首个远月认购持仓合约
  190. holding_contract_after = pd.Series(index=trade_days.index)
  191. after_main = findContractsForMonth(symbol,trade_days.index[0],1)
  192. contract_info = getContractForContractAndPrice(after_main,contract_info['exercise_price'].iloc[0],trade_days.index[0],'CO')
  193. after_main_list[trade_days.index[0]] = after_main
  194. contract = contract_info['code'].iloc[0]
  195. holding_contract_after[trade_days.index[0]] = contract
  196. print(holding_contract_after[:3])
  197. # 循环访问每一个交易日,判断交易情况
  198. # 规则: 同时卖出一份近月认购期权以及买入相同行权价的远月认购,待行权价低于现价的95%时
  199. # 平仓原期权合约,重新调整持仓;到期前1天移仓换月至次月合约
  200. error_date =[]
  201. pre_hold_close = holding_contract_close[0]
  202. pre_hold_after = holding_contract_after[0]
  203. for i in range(1,len(trade_days)):
  204. pre_day = trade_days.index[i-1]
  205. cur_day = trade_days.index[i]
  206. cur_main = get_dominant_future(symbol,date = cur_day) #当前主力合约
  207. pre_main = get_dominant_future(symbol,date = pre_day) #上一个交易日的主力合约
  208. main_list[cur_day] = cur_main
  209. close_main_list[cur_day] = close_main_list[pre_day]
  210. after_main_list[cur_day] = after_main_list[pre_day]
  211. expire_close = getContractForCode(pre_hold_close)
  212. expire_close = expire_close['last_trade_date'].iloc[0]
  213. expire_after = getContractForCode(pre_hold_after)
  214. expire_after = expire_after['last_trade_date'].iloc[0]
  215. if cur_main != pre_main or expire_close < cur_day.date() or expire_after < cur_day.date(): #主力合约切换 或者合约到期
  216. close_main = findContractsForMonth(symbol,cur_day,-1)
  217. close_main_list[cur_day] = close_main
  218. contract_info_close = getContract(close_main,cur_day,type='CO')
  219. if contract_info_close.empty: #近月合约已经过期,则采用主力合约替代
  220. contract_info_close = getContract(cur_main,cur_day,type='CO')
  221. if contract_info_close.empty:
  222. error_date.append(cur_day)
  223. continue
  224. close_main_list[cur_day] = cur_main
  225. contract_close = contract_info_close['code'].iloc[0]
  226. #获取相同行权价、到期日的认沽合约
  227. after_main = findContractsForMonth(symbol,cur_day,1)
  228. after_main_list[cur_day] = after_main
  229. contract_info_after = getContractForContractAndPrice(after_main,contract_info_close['exercise_price'].iloc[0],cur_day,'CO')
  230. if contract_info_after.empty:
  231. error_date.append(cur_day)
  232. continue
  233. contract_after = contract_info_after['code'].iloc[0]
  234. else:
  235. pre_cls = get_price(close_main_list[cur_day], cur_day, cur_day, fields=['pre_close']).values[0][0]
  236. contract_info_close = getContractExercisePrice(pre_hold_close,cur_day,"CO")
  237. pre_exercise_price_close = contract_info_close
  238. contract_info_after = getContractExercisePrice(pre_hold_after,cur_day,"CO")
  239. pre_exercise_price_after = contract_info_after
  240. if pre_exercise_price_close.empty | pre_exercise_price_after.empty:
  241. error_date.append(cur_day)
  242. continue
  243. else:
  244. pre_exercise_price_close = pre_exercise_price_close['exercise_price'][0]
  245. if pre_cls * 0.95 >= pre_exercise_price_close:
  246. close_main = findContractsForMonth(symbol,cur_day,-1)
  247. close_main_list[cur_day] = close_main
  248. contract_info_close = getContract(close_main,cur_day,type='CO')
  249. if contract_info_close.empty: #近月合约已经过期,则采用主力合约替代
  250. contract_info_close = getContract(cur_main,cur_day)
  251. close_main_list[cur_day] = cur_main
  252. contract_close = contract_info_close['code'].iloc[0]
  253. after_main = findContractsForMonth(symbol,cur_day,1)
  254. after_main_list[cur_day] = after_main
  255. contract_info_after = getContractForContractAndPrice(after_main,contract_info_close['exercise_price'].iloc[0],cur_day,'CO')
  256. contract_after = contract_info_after['code'].iloc[0]
  257. else:
  258. contract_close = pre_hold_close
  259. contract_after = pre_hold_after
  260. holding_contract_close[cur_day] = contract_close
  261. pre_hold_close = contract_close
  262. holding_contract_after[cur_day] = contract_after
  263. pre_hold_after = contract_after
  264. holding_contract_close = holding_contract_close.fillna(method='ffill')
  265. holding_contract_after = holding_contract_after.fillna(method='ffill')
  266. data_list_close = contractChange(holding_contract_close,close_main_list,error_date)
  267. data_list_after = contractChange(holding_contract_after,after_main_list,error_date)
  268. #计算平仓期权的收益
  269. opt_ret_close = optionProfit(data_list_close,price_gap,fee)
  270. opt_ret_after = optionProfit(data_list_after,price_gap,fee)
  271. opt_ret = opt_ret_close + opt_ret_after
  272. print(opt_ret)
  273. #计算现货收益
  274. 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))]
  275. commodity_price = pd.Series(commodity_price,index=data_list_close.index)
  276. commodity_ret = commodity_price.diff(1).fillna(0)
  277. commodity_ret
  278. #计算净值
  279. init_asset2 = commodity_price.iloc[0]*price_gap
  280. ass2 = init_asset2 + opt_ret.cumsum()
  281. pfl_ret2 = (ass2/ass2.shift(1) - 1).fillna(0)
  282. pfl_nv2 = (1 + pfl_ret2).cumprod()
  283. pfl_nv2
  284. #绘制净值图
  285. plt.figure(figsize=(30,20))
  286. plt.plot(commodity_price/commodity_price.iloc[0], label='现货净值')
  287. plt.plot(pfl_nv2, label=symbol+ '买入日历价差策略净值')
  288. plt.legend(loc='upper left', fontsize='large')
  289. plt.xlabel('时间',size=12)
  290. plt.ylabel('净值',size=12)
  291. plt.show()