07_50ETF备兑认购策略改进版.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. # 50ETF-备兑看涨策略-改进版
  2. # 参考资料:
  3. # - 原始策略来源: https://www.joinquant.com/view/community/detail/22955d161ec0a36d836a0e4f13fe66d7
  4. # - 研究网址: https://www.joinquant.com/research?target=research&url=/user/75474983526/notebooks/Options/50ETF-%E5%A4%87%E5%85%91%E7%9C%8B%E6%B6%A8%E7%AD%96%E7%95%A5-%E6%94%B9%E8%BF%9B%E7%89%88.ipynb
  5. # TODO: 添加50ETF备兑看涨策略改进版相关代码
  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. plt.rcParams['font.sans-serif']=['SimHei']
  13. plt.rcParams['axes.unicode_minus'] = False
  14. from datetime import datetime, timedelta
  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. #根据不同交易日分割月份
  33. #指定回测的起始时间
  34. starttime = '2020-01-01'
  35. endtime = '2024-04-10'
  36. trade_days = pd.Series(index=jqdata.get_trade_days(starttime,endtime))
  37. trade_days.index = pd.to_datetime(trade_days.index)
  38. last_day = get_last_day_of_month(endtime)
  39. month_split = list(trade_days.resample('M',label='left').mean().index) + [pd.to_datetime(last_day)]
  40. month_split
  41. ##持仓情况
  42. holding_contract2 = pd.Series(index=trade_days.index)
  43. #获取首个持仓合约
  44. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  45. opt.OPT_CONTRACT_INFO.trading_code,
  46. opt.OPT_CONTRACT_INFO.name, #合约代码,合约交易代码,合约简称
  47. opt.OPT_CONTRACT_INFO.exercise_price,
  48. opt.OPT_CONTRACT_INFO.last_trade_date,
  49. opt.OPT_CONTRACT_INFO.list_date
  50. ).filter(opt.OPT_CONTRACT_INFO.contract_type == 'CO', # 看涨期权
  51. opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG', # 上交所
  52. opt.OPT_CONTRACT_INFO.last_trade_date > month_split[0], # 时间-到期月开始
  53. opt.OPT_CONTRACT_INFO.last_trade_date < month_split[1], # 时间-到期月结束
  54. opt.OPT_CONTRACT_INFO.list_date < trade_days.index[0]) # 在交易前上市
  55. contract_info = opt.run_query(q_contract_info)
  56. contract_info
  57. etf_cls = get_price('510050.XSHG',trade_days.index[0],trade_days.index[0],fields=['close']).values[0][0]
  58. etf_cls
  59. contract_info['price_spread'] = contract_info['exercise_price'] - etf_cls
  60. contract_info
  61. etf_cls = get_price('510050.XSHG',trade_days.index[0],trade_days.index[0],fields=['close']).values[0][0]
  62. contract_info['price_spread'] = contract_info['exercise_price'] - etf_cls
  63. if contract_info['price_spread'].max() > 0:
  64. contract_info = contract_info[contract_info['price_spread'] > 0] #选出虚值期权
  65. contract_info = contract_info.sort_values('exercise_price')
  66. else: #全是实值期权
  67. contract_info = contract_info.sort_values('exercise_price',ascending=False)
  68. contract_info
  69. holding_contract2[trade_days.index[0]] = contract_info['code'].iloc[0]
  70. newest_exercise_price = contract_info['exercise_price'].iloc[0]
  71. newest_expire_date = contract_info['last_trade_date'].iloc[0]
  72. print(newest_exercise_price)
  73. print(newest_expire_date)
  74. # 循环访问每一个交易日,判断交易情况
  75. # 规则:持有略虚值看涨期权
  76. # 待行权价低于现价的95%时,平仓原期权合约,重新开仓略虚值看涨期权
  77. # 到期前 1 天移仓换月至次月合约
  78. for t in trade_days.index[1:]:
  79. #到期前一天
  80. if t >= pd.to_datetime(get_trade_days(end_date=pd.to_datetime(newest_expire_date),count=2)[0]):
  81. #寻找month_idx
  82. for month_idx in range(len(month_split)):
  83. if month_split[month_idx] >= t:
  84. break
  85. if month_idx == len(month_split) -1:
  86. lasttradeday = t
  87. break
  88. else:
  89. lasttradeday = ''
  90. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  91. opt.OPT_CONTRACT_INFO.trading_code,
  92. opt.OPT_CONTRACT_INFO.name,
  93. opt.OPT_CONTRACT_INFO.exercise_price,
  94. opt.OPT_CONTRACT_INFO.last_trade_date,
  95. opt.OPT_CONTRACT_INFO.list_date
  96. ).filter(opt.OPT_CONTRACT_INFO.contract_type == 'CO', # 看涨期权
  97. opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG', # 上交所
  98. opt.OPT_CONTRACT_INFO.last_trade_date > month_split[month_idx], # 时间-到期月开始
  99. opt.OPT_CONTRACT_INFO.last_trade_date <= month_split[month_idx+1], # 时间-到期月结束
  100. opt.OPT_CONTRACT_INFO.list_date < t) # 在交易前上市
  101. contract_info = opt.run_query(q_contract_info)
  102. etf_cls = get_price('510050.XSHG',t,t,fields=['close']).values[0][0] #现货收盘价
  103. contract_info['price_spread'] = contract_info['exercise_price'] - etf_cls
  104. if contract_info['price_spread'].max() > 0:
  105. contract_info = contract_info[contract_info['price_spread'] > 0] #选出虚值期权
  106. contract_info = contract_info.sort_values('exercise_price')
  107. else: #全是实值期权
  108. contract_info = contract_info.sort_values('exercise_price',ascending=False)
  109. if contract_info['last_trade_date'].iloc[0] >= newest_expire_date:
  110. holding_contract2[t] = contract_info['code'].iloc[0]
  111. newest_exercise_price = contract_info['exercise_price'].iloc[0]
  112. newest_expire_date = contract_info['last_trade_date'].iloc[0]
  113. else:
  114. #获取昨日50etf收盘价
  115. pre_cls = get_price('510050.XSHG',t,t,fields=['pre_close']).values[0][0]
  116. if pre_cls*0.95 >= newest_exercise_price: #原虚值变为实值,重新开仓略虚值期权
  117. #寻找month_idx
  118. for month_idx in range(len(month_split)):
  119. if month_split[month_idx] >= t:
  120. break
  121. q_contract_info = query(opt.OPT_CONTRACT_INFO.code,
  122. opt.OPT_CONTRACT_INFO.trading_code,
  123. opt.OPT_CONTRACT_INFO.name,
  124. opt.OPT_CONTRACT_INFO.exercise_price,
  125. opt.OPT_CONTRACT_INFO.last_trade_date, #行权价格,最后交易日
  126. opt.OPT_CONTRACT_INFO.list_date
  127. ).filter(opt.OPT_CONTRACT_INFO.contract_type == 'CO', #看涨期权
  128. opt.OPT_CONTRACT_INFO.exchange_code == 'XSHG', #上交所
  129. opt.OPT_CONTRACT_INFO.last_trade_date > month_split[month_idx-1], #时间-到期月开始
  130. opt.OPT_CONTRACT_INFO.last_trade_date <= month_split[month_idx], #时间-到期月结束
  131. opt.OPT_CONTRACT_INFO.list_date < t) #在交易前上市
  132. contract_info = opt.run_query(q_contract_info)
  133. etf_cls = get_price('510050.XSHG',t,t,fields=['close']).values[0][0]
  134. contract_info['price_spread'] = contract_info['exercise_price'] - etf_cls
  135. if contract_info['price_spread'].max() > 0:
  136. contract_info = contract_info[contract_info['price_spread'] > 0] #选出虚值期权
  137. contract_info = contract_info.sort_values('exercise_price')
  138. else: #全是实值期权
  139. contract_info = contract_info.sort_values('exercise_price',ascending=False)
  140. if contract_info['last_trade_date'].iloc[0] >= newest_expire_date:
  141. holding_contract2[t] = contract_info['code'].iloc[0]
  142. newest_exercise_price = contract_info['exercise_price'].iloc[0]
  143. newest_expire_date = contract_info['last_trade_date'].iloc[0]
  144. holding_contract2 = holding_contract2.fillna(method='ffill')
  145. holding_contract2
  146. if lasttradeday != '':
  147. holding_contract2 = holding_contract2[holding_contract2.index < lasttradeday]
  148. data2 = pd.DataFrame(holding_contract2)
  149. data2.columns = ['holding_contract']
  150. data2 = data2.reindex(columns=['holding_contract','close','last_close'])
  151. last_contract = holding_contract2.iloc[0] #记录上个持仓
  152. for t in data2.index:
  153. if last_contract == data2.loc[t,'holding_contract']: #期权未换仓
  154. #收盘价
  155. q_price = query(opt.OPT_DAILY_PRICE.code,
  156. opt.OPT_DAILY_PRICE.date,
  157. opt.OPT_DAILY_PRICE.close,
  158. ).filter(opt.OPT_DAILY_PRICE.code==last_contract,
  159. opt.OPT_DAILY_PRICE.date==t)
  160. price = opt.run_query(q_price)['close'][0]
  161. data2.loc[t,'close'] = price
  162. else:
  163. #收盘价,新
  164. q_price = query(opt.OPT_DAILY_PRICE.code,
  165. opt.OPT_DAILY_PRICE.date,
  166. opt.OPT_DAILY_PRICE.close,
  167. ).filter(opt.OPT_DAILY_PRICE.code==data2.loc[t,'holding_contract'],
  168. opt.OPT_DAILY_PRICE.date==t)
  169. price = opt.run_query(q_price)['close'][0]
  170. data2.loc[t,'close'] = price
  171. #收盘价,旧
  172. q_price = query(opt.OPT_DAILY_PRICE.code,
  173. opt.OPT_DAILY_PRICE.date,
  174. opt.OPT_DAILY_PRICE.close,
  175. ).filter(opt.OPT_DAILY_PRICE.code==last_contract,
  176. opt.OPT_DAILY_PRICE.date==t)
  177. price = opt.run_query(q_price)['close'][0]
  178. data2.loc[t,'last_close'] = price
  179. last_contract = data2.loc[t,'holding_contract']
  180. data2
  181. #计算卖出期权的收益
  182. opt_ret2 = pd.Series(0,index=data2.index)
  183. pre_close2 = data2['close'].iloc[0]
  184. for t in data2.index[1:]:
  185. if data2.isna().loc[t,'last_close']: #未换仓,last为空
  186. opt_ret2[t] = -10000*(data2.loc[t,'close'] - pre_close2)
  187. else:
  188. opt_ret2[t] = -10000*(data2.loc[t,'last_close'] - pre_close2) - 5 #手续费5元
  189. pre_close2 = data2.loc[t,'close']
  190. opt_ret2
  191. #计算持仓收益
  192. etf_price = get_price('510050.XSHG',trade_days.index[0],trade_days.index[-1],fields=['close'])['close']
  193. etf_ret = 10000*etf_price.diff(1).fillna(0)
  194. etf_ret
  195. #计算净值
  196. init_asset2 = etf_price.iloc[0]*10000
  197. ass2 = init_asset2 + (etf_ret + opt_ret2).cumsum()
  198. pfl_ret2 = (ass2/ass2.shift(1) - 1).fillna(0)
  199. pfl_nv2 = (1 + pfl_ret2).cumprod()
  200. pfl_nv2
  201. ### 绘制净值图
  202. plt.figure(figsize=(30, 20))
  203. plt.plot(etf_price/etf_price.iloc[0], label='50ETF现货净值')
  204. plt.plot(pfl_nv2, label='备兑看涨策略净值')
  205. plt.legend(loc='upper left', fontsize='large')
  206. plt.xlabel('时间',size=12)
  207. plt.ylabel('净值',size=12)
  208. plt.show()