14_卖出跨式策略商品主力合约.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. # 卖出跨式策略-商品主力合约
  2. # 参考资料:
  3. # - 原始策略来源: https://www.joinquant.com/view/community/detail/dc14876fee244d726f18c652eb44c7d7
  4. # - 研究网址: 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-%E5%95%86%E5%93%81%E4%B8%BB%E5%8A%9B%E5%90%88%E7%BA%A6.ipynb
  5. # TODO: 添加卖出跨式策略商品主力合约相关代码
  6. import jqdata
  7. from jqdata import *
  8. import pandas as pd
  9. import numpy as np
  10. import datetime
  11. import matplotlib.pyplot as plt
  12. from datetime import datetime, timedelta
  13. plt.rcParams['font.sans-serif']=['SimHei']
  14. plt.rcParams['axes.unicode_minus'] = False
  15. #获取期权合约信息
  16. def getContractForCode(code):
  17. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  18. opt.OPT_CONTRACT_INFO.trading_code,
  19. opt.OPT_CONTRACT_INFO.name,
  20. opt.OPT_CONTRACT_INFO.exercise_price,
  21. opt.OPT_CONTRACT_INFO.last_trade_date,
  22. # 行权价格,最后交易日
  23. opt.OPT_CONTRACT_INFO.list_date
  24. ).filter(opt.OPT_CONTRACT_INFO.code == code,
  25. )
  26. contract_info = opt.run_query(q_contract_info)
  27. return(contract_info)
  28. def getContract(symbol,date,type="CO"): #CO为认购,PO为认沽
  29. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  30. opt.OPT_CONTRACT_INFO.trading_code,
  31. opt.OPT_CONTRACT_INFO.name,
  32. opt.OPT_CONTRACT_INFO.exercise_price,
  33. opt.OPT_CONTRACT_INFO.last_trade_date,
  34. # 行权价格,最后交易日
  35. opt.OPT_CONTRACT_INFO.list_date
  36. ).filter(opt.OPT_CONTRACT_INFO.underlying_symbol == symbol,
  37. opt.OPT_CONTRACT_INFO.contract_type == type, # 期权类型
  38. opt.OPT_CONTRACT_INFO.last_trade_date >= date
  39. )
  40. contract_info = opt.run_query(q_contract_info)
  41. commodity_cls = get_price(symbol, date, date, fields=['close']).values[0][0]
  42. if type == 'CO':
  43. contract_info['price_spread'] = contract_info['exercise_price'] - commodity_cls
  44. else:
  45. contract_info['price_spread'] = commodity_cls - contract_info['exercise_price']
  46. if contract_info['price_spread'].max() > 0:
  47. contract_info = contract_info[contract_info['price_spread'] > 0] # 选出虚值期权
  48. contract_info = contract_info.sort_values('exercise_price')
  49. else: # 全是实值期权
  50. contract_info = contract_info.sort_values('exercise_price', ascending=False)
  51. #return(contract_info['code'].iloc[0])
  52. return(contract_info)
  53. #获取期权价格等信息
  54. def getContractPrice(code,last_trade_date,type='CO'):
  55. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  56. opt.OPT_CONTRACT_INFO.trading_code,
  57. opt.OPT_CONTRACT_INFO.name,
  58. opt.OPT_CONTRACT_INFO.exercise_price,
  59. opt.OPT_CONTRACT_INFO.last_trade_date,
  60. # 行权价格,最后交易日
  61. opt.OPT_CONTRACT_INFO.list_date
  62. ).filter(opt.OPT_CONTRACT_INFO.code == code,
  63. opt.OPT_CONTRACT_INFO.contract_type == type, # 期权类型
  64. opt.OPT_CONTRACT_INFO.last_trade_date >= last_trade_date
  65. )
  66. contract_info = opt.run_query(q_contract_info)
  67. return(contract_info)
  68. #处理合约切换标记
  69. def contractChange(holding_contract,main_list,error_date):
  70. data2 = pd.DataFrame(holding_contract)
  71. data2.columns = ['holding_contract']
  72. data2 = data2.reindex(columns=['holding_contract','close','last_close'])
  73. data2 = data2.drop(error_date)
  74. main_list = main_list.drop(error_date)
  75. last_contract = holding_contract.iloc[0] #记录上个持仓
  76. for i in range(0,len(data2.index)):
  77. t = data2.index[i]
  78. if last_contract == data2.loc[t,'holding_contract']: #期权未换仓
  79. q_price = query(opt.OPT_DAILY_PRICE.code,
  80. opt.OPT_DAILY_PRICE.date, opt.OPT_DAILY_PRICE.close,
  81. ).filter(opt.OPT_DAILY_PRICE.code==last_contract,
  82. opt.OPT_DAILY_PRICE.date==t)
  83. price = opt.run_query(q_price)['close'][0]
  84. data2.loc[t,'close'] = price
  85. else: #合约换仓
  86. q_price = query(opt.OPT_DAILY_PRICE.code,
  87. opt.OPT_DAILY_PRICE.date,
  88. opt.OPT_DAILY_PRICE.close,
  89. ).filter(opt.OPT_DAILY_PRICE.code==data2.loc[t,'holding_contract'],
  90. opt.OPT_DAILY_PRICE.date==t)
  91. price = opt.run_query(q_price)['close'][0]
  92. data2.loc[t,'close'] = price
  93. #收盘价,旧
  94. q_price = query(opt.OPT_DAILY_PRICE.code,
  95. opt.OPT_DAILY_PRICE.date,
  96. opt.OPT_DAILY_PRICE.close,
  97. ).filter(opt.OPT_DAILY_PRICE.code==last_contract,
  98. opt.OPT_DAILY_PRICE.date==data2.index[i-1])
  99. price = opt.run_query(q_price)['close'][0]
  100. data2.loc[t,'last_close'] = price
  101. last_contract = data2.loc[t,'holding_contract']
  102. return(data2)
  103. def optionProfit(data2,price_gap,fee):
  104. #计算平仓期权的收益
  105. opt_ret2 = pd.Series(0,index=data2.index)
  106. pre_close2 = data2['close'].iloc[0]
  107. for t in data2.index[1:]:
  108. if data2.isna().loc[t,'last_close']: #未换仓,last为空
  109. opt_ret2[t] = -price_gap*(data2.loc[t,'close'] - pre_close2)
  110. else:
  111. opt_ret2[t] = -price_gap*(data2.loc[t,'last_close'] - pre_close2) - fee #手续费
  112. pre_close2 = data2.loc[t,'close']
  113. return(opt_ret2)
  114. #获得指定行权价以及到期日的合约
  115. def getContractForPrice(exercise_price,enddate,contratType='PO'):
  116. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  117. opt.OPT_CONTRACT_INFO.trading_code,
  118. opt.OPT_CONTRACT_INFO.name, #合约代码,合约交易代码,合约简称
  119. opt.OPT_CONTRACT_INFO.exercise_price,
  120. opt.OPT_CONTRACT_INFO.last_trade_date,
  121. opt.OPT_CONTRACT_INFO.list_date
  122. ).filter(opt.OPT_CONTRACT_INFO.contract_type == contratType, #期权类型
  123. opt.OPT_CONTRACT_INFO.last_trade_date == enddate, #到期月
  124. opt.OPT_CONTRACT_INFO.exercise_price == exercise_price) #指定行权价
  125. contract_info = opt.run_query(q_contract_info)
  126. return(contract_info)
  127. # 设置各项参数
  128. # 合约代码
  129. symbol = 'AL'
  130. #手续费
  131. fee = 5
  132. #合约价差
  133. price_gap = 10
  134. #起始时间
  135. starttime = '2022-01-01'
  136. endtime = '2024-05-01'
  137. SUBJECT_MATTER = get_dominant_future(symbol,date = starttime)
  138. SUBJECT_MATTER
  139. #获取交易时间和时间间隔(频率:月)
  140. #根据不同交易日分割月份
  141. #指定回测的起始时间
  142. trade_days = pd.Series(index=jqdata.get_trade_days(starttime,endtime))
  143. trade_days.index = pd.to_datetime(trade_days.index)
  144. ##持仓情况
  145. #主力合约列表
  146. main_list = pd.Series(index=trade_days.index)
  147. main_list[trade_days.index[0]] = SUBJECT_MATTER
  148. holding_contract_CO = pd.Series(index=trade_days.index)
  149. #获取首个认购持仓合约
  150. contract_info = getContract(SUBJECT_MATTER,trade_days.index[0],type='CO')
  151. contract = contract_info['code'].iloc[0]
  152. holding_contract_CO[trade_days.index[0]] = contract
  153. print(holding_contract_CO[:3])
  154. #获取首个认沽持仓合约
  155. holding_contract_PO = pd.Series(index=trade_days.index)
  156. contract_info = getContractForPrice(contract_info['exercise_price'].iloc[0],contract_info['last_trade_date'].iloc[0],'PO')
  157. contract = contract_info['code'].iloc[0]
  158. holding_contract_PO[trade_days.index[0]] = contract
  159. print(holding_contract_PO[:3])
  160. # 循环访问每一个交易日,判断交易情况
  161. # 规则:同时卖出认购期权以及同一到期日的和同一行权价的认沽期权
  162. # 待行权价低于现价的95%时,平仓原期权合约,重新卖出认沽以及认购期权;到期前1天移仓换月至次月合约
  163. error_date =[]
  164. pre_hold_CO = holding_contract_CO[0]
  165. pre_hold_PO = holding_contract_PO[0]
  166. for i in range(1,len(trade_days)):
  167. pre_day = trade_days.index[i-1]
  168. cur_day = trade_days.index[i]
  169. cur_main = get_dominant_future(symbol,date = cur_day) #当前主力合约
  170. pre_main = get_dominant_future(symbol,date = pre_day) #上一个交易日的主力合约
  171. main_list[cur_day] = cur_main
  172. if cur_main != pre_main: #主力合约切换
  173. contract_info_CO = getContract(cur_main,cur_day,type='CO')
  174. contract_CO = contract_info_CO['code'].iloc[0]
  175. #获取相同行权价、到期日的认沽合约
  176. contract_info_PO = getContractForPrice(contract_info_CO['exercise_price'].iloc[0],
  177. contract_info_CO['last_trade_date'].iloc[0],'PO')
  178. contract_PO = contract_info_PO['code'].iloc[0]
  179. else:
  180. pre_cls = get_price(cur_main, cur_day, cur_day, fields=['pre_close']).values[0][0]
  181. contract_info_CO = getContractPrice(pre_hold_CO,cur_day,"CO")
  182. pre_exercise_price_CO = contract_info_CO
  183. contract_info_PO = getContractPrice(pre_hold_PO,cur_day,"PO")
  184. pre_exercise_price_PO = contract_info_PO
  185. if contract_info_CO.empty | contract_info_PO.empty:
  186. error_date.append(cur_day)
  187. continue
  188. else:
  189. pre_exercise_price_CO = pre_exercise_price_CO['exercise_price'][0]
  190. if pre_cls * 0.95 >= pre_exercise_price_CO:
  191. contract_info_CO = getContract(pre_main,cur_day)
  192. contract_CO = contract_info_CO['code'].iloc[0]
  193. contract_info_PO = getContractForPrice(contract_info_CO['exercise_price'].iloc[0],
  194. contract_info_CO['last_trade_date'].iloc[0],'PO')
  195. contract_PO = contract_info_PO['code'].iloc[0]
  196. else:
  197. contract_CO = pre_hold_CO
  198. contract_PO = pre_hold_PO
  199. holding_contract_CO[cur_day] = contract_CO
  200. pre_hold_CO = contract_CO
  201. holding_contract_PO[cur_day] = contract_PO
  202. pre_hold_PO = contract_PO
  203. holding_contract_CO = holding_contract_CO.fillna(method='ffill')
  204. holding_contract_PO = holding_contract_PO.fillna(method='ffill')
  205. data_list_CO = contractChange(holding_contract_CO,main_list,error_date)
  206. data_list_PO = contractChange(holding_contract_PO,main_list,error_date)
  207. #计算平仓期权的收益
  208. opt_ret_CO = optionProfit(data_list_CO,price_gap,fee)
  209. opt_ret_PO = optionProfit(data_list_PO,price_gap,fee)
  210. opt_ret = opt_ret_CO + opt_ret_PO
  211. print(opt_ret)
  212. #计算现货收益
  213. commodity_price = [get_price(main_list[t],
  214. data_list_CO.index[t],
  215. data_list_CO.index[t],
  216. fields=['close'])['close'][0] for t in range(0,len(data_list_CO.index))]
  217. commodity_price = pd.Series(commodity_price,index=data_list_CO.index)
  218. commodity_ret = commodity_price.diff(1).fillna(0)
  219. commodity_ret
  220. #计算净值
  221. init_asset2 = commodity_price.iloc[0]*price_gap
  222. ass2 = init_asset2 + opt_ret.cumsum()
  223. pfl_ret2 = (ass2/ass2.shift(1) - 1).fillna(0)
  224. pfl_nv2 = (1 + pfl_ret2).cumprod()
  225. pfl_nv2
  226. #绘制净值图
  227. plt.figure(figsize=(10,6))
  228. plt.plot(commodity_price/commodity_price.iloc[0], label='期货净值')
  229. plt.plot(pfl_nv2, label=symbol+'卖出跨式策略净值')
  230. plt.legend(loc='upper left', fontsize='large')
  231. plt.xlabel('时间',size=12)
  232. plt.ylabel('净值',size=12)
  233. plt.show()