09_商品主力合约备兑认购策略.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. # 商品主力合约-备兑看涨策略
  2. # 参考资料:
  3. # - 原始策略来源: https://www.joinquant.com/view/community/detail/e306e04ca7a0c557f759487e8d252c65
  4. # - 研究网址: https://www.joinquant.com/research?target=research&url=/user/75474983526/notebooks/Options/%E5%95%86%E5%93%81%E4%B8%BB%E5%8A%9B%E5%90%88%E7%BA%A6-%E5%A4%87%E5%85%91%E7%9C%8B%E6%B6%A8%E7%AD%96%E7%95%A5.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. def is_last_day_of_month(date_str):
  16. # 将字符串转换为日期对象
  17. date_obj = datetime.strptime(date_str, '%Y-%m-%d')
  18. # 获取下一个日期对象
  19. next_date_obj = date_obj + timedelta(days=1)
  20. # 判断是否为下个月的第一天,如果是,则当前日期为月末
  21. return date_obj.month != next_date_obj.month
  22. def get_last_day_of_month(date_str):
  23. # 将字符串转换为日期对象
  24. date_obj = datetime.strptime(date_str, '%Y-%m-%d')
  25. # 获取下个月的第一天日期对象
  26. next_month_first_day = datetime(date_obj.year, date_obj.month + 1, 1)
  27. # 从下个月的第一天减去一天,得到当前月的月末日期对象
  28. last_day_of_month = next_month_first_day - timedelta(days=1)
  29. # 返回月末日期的字符串形式
  30. return last_day_of_month.strftime('%Y-%m-%d')
  31. #获取看涨期权
  32. def getContract(code,date):
  33. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  34. opt.OPT_CONTRACT_INFO.trading_code,
  35. opt.OPT_CONTRACT_INFO.name,
  36. opt.OPT_CONTRACT_INFO.exercise_price,
  37. opt.OPT_CONTRACT_INFO.last_trade_date,
  38. # 行权价格,最后交易日
  39. opt.OPT_CONTRACT_INFO.list_date
  40. ).filter(opt.OPT_CONTRACT_INFO.underlying_symbol == code,
  41. opt.OPT_CONTRACT_INFO.contract_type == 'CO', # 看涨期权
  42. )
  43. contract_info = opt.run_query(q_contract_info)
  44. commodity_cls = get_price(code, date, date, fields=['close']).values[0][0]
  45. contract_info['price_spread'] = contract_info['exercise_price'] - commodity_cls
  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. # 合约代码
  53. symbol = 'AU'
  54. #合约价差
  55. price_gap = 10
  56. #起始时间
  57. starttime = '2025-01-01'
  58. endtime = '2025-06-01'
  59. SUBJECT_MATTER = get_dominant_future(symbol,date = starttime)
  60. #查询相关的合约,适用于商品
  61. qy = query(opt.OPT_CONTRACT_INFO).filter(
  62. opt.OPT_CONTRACT_INFO.underlying_symbol == SUBJECT_MATTER, ##期权标的物
  63. opt.OPT_CONTRACT_INFO.contract_type == 'CO'
  64. ).order_by(opt.OPT_CONTRACT_INFO.exercise_price)
  65. optList = opt.run_query(qy)
  66. optList[:2]
  67. #获取交易时间和时间间隔(频率:月)
  68. #根据不同交易日分割月份
  69. #指定回测的起始时间
  70. trade_days = pd.Series(index=jqdata.get_trade_days(starttime,endtime))
  71. trade_days.index = pd.to_datetime(trade_days.index)
  72. ##持仓情况
  73. main_list = pd.Series(index=trade_days.index)
  74. main_list[trade_days.index[0]] = SUBJECT_MATTER
  75. holding_contract2 = pd.Series(index=trade_days.index)
  76. #获取首个持仓合约
  77. contract = getContract(SUBJECT_MATTER,trade_days.index[0])
  78. holding_contract2[trade_days.index[0]] = contract
  79. holding_contract2[:3]
  80. #循环访问每一个交易日,判断交易情况
  81. #规则:判断当前主力合约对应的期权,持有略虚值看涨期权,待行权价低于现价的95%时,平仓原期权合约,重新开仓略虚值看涨期权
  82. error_date =[]
  83. pre_hold = holding_contract2[0]
  84. for i in range(1,len(trade_days)):
  85. pre_day = trade_days.index[i-1]
  86. cur_day = trade_days.index[i]
  87. cur_main = get_dominant_future(symbol,date = cur_day) #当前主力合约
  88. pre_main = get_dominant_future(symbol,date = pre_day) #上一个交易日的主力合约
  89. main_list[cur_day] = cur_main
  90. if cur_main != pre_main: #主力合约切换
  91. contract = getContract(cur_main,cur_day)
  92. else:
  93. pre_cls = get_price(cur_main, cur_day, cur_day, fields=['pre_close']).values[0][0]
  94. q_contract_info = query(opt.OPT_CONTRACT_INFO.code, opt.OPT_CONTRACT_INFO.trading_code,
  95. opt.OPT_CONTRACT_INFO.name,
  96. opt.OPT_CONTRACT_INFO.exercise_price, opt.OPT_CONTRACT_INFO.last_trade_date,
  97. # 行权价格,最后交易日
  98. opt.OPT_CONTRACT_INFO.list_date
  99. ).filter(opt.OPT_CONTRACT_INFO.code == pre_hold,
  100. opt.OPT_CONTRACT_INFO.contract_type == 'CO', # 看涨期权
  101. opt.OPT_CONTRACT_INFO.last_trade_date >= cur_day
  102. )
  103. contract_info = opt.run_query(q_contract_info)
  104. pre_exercise_price = contract_info
  105. if pre_exercise_price.empty:
  106. error_date.append(cur_day)
  107. continue
  108. else:
  109. pre_exercise_price = pre_exercise_price['exercise_price'][0]
  110. if pre_cls * 0.95 >= pre_exercise_price:
  111. contract = getContract(pre_main,cur_day)
  112. else:
  113. contract = pre_hold
  114. holding_contract2[cur_day] = contract
  115. pre_hold = contract
  116. holding_contract2 = holding_contract2.fillna(method='ffill')
  117. data2 = pd.DataFrame(holding_contract2)
  118. data2.columns = ['holding_contract']
  119. data2 = data2.reindex(columns=['holding_contract','close','last_close'])
  120. data2 = data2.drop(error_date)
  121. main_list = main_list.drop(error_date)
  122. last_contract = holding_contract2.iloc[0] #记录上个持仓
  123. for i in range(0,len(data2.index)):
  124. t = data2.index[i]
  125. if last_contract == data2.loc[t,'holding_contract']: #期权未换仓
  126. q_price = query(opt.OPT_DAILY_PRICE.code, opt.OPT_DAILY_PRICE.date, opt.OPT_DAILY_PRICE.close,
  127. ).filter(opt.OPT_DAILY_PRICE.code==last_contract,
  128. opt.OPT_DAILY_PRICE.date==t)
  129. price = opt.run_query(q_price)['close'][0]
  130. data2.loc[t,'close'] = price
  131. else: #合约换仓
  132. q_price = query(opt.OPT_DAILY_PRICE.code, opt.OPT_DAILY_PRICE.date, opt.OPT_DAILY_PRICE.close,
  133. ).filter(opt.OPT_DAILY_PRICE.code==data2.loc[t,'holding_contract'],
  134. opt.OPT_DAILY_PRICE.date==t)
  135. price = opt.run_query(q_price)['close'][0]
  136. data2.loc[t,'close'] = price
  137. #收盘价,旧
  138. q_price = query(opt.OPT_DAILY_PRICE.code, opt.OPT_DAILY_PRICE.date, opt.OPT_DAILY_PRICE.close,
  139. ).filter(opt.OPT_DAILY_PRICE.code==last_contract,
  140. opt.OPT_DAILY_PRICE.date==data2.index[i-1])
  141. price = opt.run_query(q_price)['close'][0]
  142. data2.loc[t,'last_close'] = price
  143. last_contract = data2.loc[t,'holding_contract']
  144. print(data2)
  145. #计算卖出期权的收益
  146. opt_ret2 = pd.Series(0,index=data2.index)
  147. pre_close2 = data2['close'].iloc[0]
  148. for t in data2.index[1:]:
  149. if data2.isna().loc[t,'last_close']: #未换仓,last为空
  150. opt_ret2[t] = -price_gap*(data2.loc[t,'close'] - pre_close2)
  151. else:
  152. opt_ret2[t] = -price_gap*(data2.loc[t,'last_close'] - pre_close2) - 5 #手续费5元
  153. pre_close2 = data2.loc[t,'close']
  154. opt_ret2
  155. #计算持仓收益
  156. commodity_price = [get_price(main_list[t],data2.index[t],data2.index[t],fields=['close'])['close'][0] for t in range(0,len(data2.index))]
  157. commodity_price = pd.Series(commodity_price,index=data2.index)
  158. commodity_ret = commodity_price.diff(1).fillna(0)
  159. commodity_ret
  160. #计算净值
  161. init_asset2 = commodity_price.iloc[0]*price_gap
  162. ass2 = init_asset2 + (commodity_ret + opt_ret2).cumsum()
  163. pfl_ret2 = (ass2/ass2.shift(1) - 1).fillna(0)
  164. pfl_nv2 = (1 + pfl_ret2).cumprod()
  165. pfl_nv2
  166. #绘制净值图
  167. plt.figure(figsize=(30,20))
  168. plt.plot(commodity_price/commodity_price.iloc[0], label='现货净值')
  169. plt.plot(pfl_nv2, label=symbol+'备兑看涨策略净值')
  170. plt.legend(loc='upper left', fontsize='large')
  171. plt.xlabel('时间',size=12)
  172. plt.ylabel('净值',size=12)
  173. plt.show()