au_long_term_strategy.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. # 导入函数库
  2. from jqdata import *
  3. import pandas as pd
  4. import numpy as np
  5. import datetime
  6. import re
  7. ## 初始化函数,设定基准等等
  8. def initialize(context):
  9. # 设定沪深300作为基准
  10. set_benchmark('000300.XSHG')
  11. # 开启动态复权模式(真实价格)
  12. set_option('use_real_price', True)
  13. # 过滤掉order系列API产生的比error级别低的log
  14. # log.set_level('order', 'error')
  15. # 输出内容到日志 log.info()
  16. log.info('初始函数开始运行且全局只运行一次')
  17. ### 期货相关设定 ###
  18. # 设定账户为金融账户
  19. set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
  20. # 期货类每笔交易时的手续费是:买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
  21. set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023,close_today_commission=0.0023), type='index_futures')
  22. # 设定保证金比例
  23. set_option('futures_margin_rate', 0.15)
  24. # 设置期货交易的滑点
  25. set_slippage(StepRelatedSlippage(2))
  26. # 运行函数(reference_security为运行时间的参考标的;传入的标的只做种类区分,因此传入'IF8888.CCFX'或'IH1602.CCFX'是一样的)
  27. # 注意:before_open/open/close/after_close等相对时间不可用于有夜盘的交易品种,有夜盘的交易品种请指定绝对时间(如9:30)
  28. # 合约乘数
  29. g.multiplier = {
  30. 'A': 10, 'AG': 15, 'AL': 5, 'AO': 20, 'AP': 10, 'AU': 1000, 'B': 10,
  31. 'BC': 5, 'BR': 5, 'BU': 10, 'C': 10, 'CF': 5, 'CJ': 5, 'CS': 10,
  32. 'CU': 5, 'CY': 5, 'EB': 5, 'EC': 50, 'EG': 10, 'FG': 20, 'FU': 10,
  33. 'HC': 10, 'I': 100, 'J': 60, 'JD': 5, 'JM': 100,
  34. 'L': 5, 'LC': 1, 'LH':16, 'LR': 0.05, 'LU': 10, 'M': 10, 'MA': 10, 'NI': 1, 'NR': 10, 'OI': 10,
  35. 'P': 10, 'PB': 5, 'PF': 5, 'PG': 20, 'PK': 5,
  36. 'PP': 5, 'RB': 10, 'RI': 0.05, 'RM': 10, 'RU': 10,
  37. 'SA': 20, 'SC': 1000, 'SF': 5, 'SH': 30, 'SI': 5, 'SM': 5, 'SN': 1, 'SP': 10, 'SR': 10,
  38. 'SS': 5, 'TA': 5, 'UR': 20, 'V': 5,
  39. 'Y': 10, 'ZC': 0.05, 'ZN': 5}
  40. # 设置AU期货为交易标的
  41. g.security = get_dominant_future('AU')
  42. # 设置交易参数
  43. g.position_size = 0.8 # 仓位比例
  44. g.is_traded = False # 当日是否已交易标志
  45. # 设置明确买入日期,格式为YYYY-MM-DD字符串列表
  46. g.buy_dates = ['2020-08-10']
  47. # 设置止损参数
  48. g.stop_loss_pct = 0.05 # 止损比例,5%
  49. # 设置自动换月参数
  50. g.days_before_expiry = 5 # 到期前多少天换月
  51. # 用于收益跟踪的变量
  52. g.trade_records = [] # 交易记录
  53. g.initial_positions = {} # 初始持仓信息
  54. g.last_roll_date = None # 上次换月日期
  55. g.switch_records = [] # 换月交易记录
  56. # 开盘前运行
  57. run_daily( before_market_open, time='09:00', reference_security='IF8888.CCFX')
  58. # 开盘时运行
  59. run_daily( market_open, time='09:30', reference_security='IF8888.CCFX')
  60. # 每天14:30检查是否需要换月(仅在持有仓位时运行)
  61. run_daily(check_and_switch_position, time='14:30', reference_security='IF8888.CCFX')
  62. # 收盘后运行
  63. run_daily( after_market_close, time='15:30', reference_security='IF8888.CCFX')
  64. ## 开盘前运行函数
  65. def before_market_open(context):
  66. # 更新主力合约
  67. g.security = get_dominant_future('AU')
  68. # log.info("当前主力合约: {}".format(g.security))
  69. ## 开盘时运行函数
  70. def market_open(context):
  71. # 如果是买入日期且没有持仓,则买入
  72. if is_buy_date(context) and not has_position(context) and not g.is_traded:
  73. buy_contract(context)
  74. def is_buy_date(context):
  75. current_date = context.current_dt.date()
  76. date_str = current_date.strftime('%Y-%m-%d')
  77. # 检查当前日期是否在买入日期列表中
  78. if date_str in g.buy_dates:
  79. log.info("今天 {} 是设定的买入日期".format(date_str))
  80. return True
  81. return False
  82. # 买入合约
  83. def buy_contract(context):
  84. # 选择子账户
  85. subportfolio = context.subportfolios[0]
  86. # 计算买入数量
  87. cash_to_use = subportfolio.available_cash * g.position_size
  88. price = get_price(g.security, count=1, fields='close')['close'][0]
  89. # contract_multiplier = get_contract_multiplier(g.security)
  90. # margin_rate = get_future_margin_rate(g.security)
  91. # 计算最多可买入的手数
  92. # max_amount = int(cash_to_use / (price * contract_multiplier * margin_rate))
  93. max_amount = 1
  94. if max_amount > 0:
  95. # 买入合约
  96. order(g.security, max_amount, side='long')
  97. log.info("买入 {} 合约,数量: {} 手".format(g.security, max_amount))
  98. # 记录交易信息用于收益跟踪
  99. g.trade_records.append({
  100. 'date': context.current_dt.strftime('%Y-%m-%d'),
  101. 'action': 'buy',
  102. 'contract': g.security,
  103. 'price': price,
  104. 'amount': max_amount
  105. })
  106. g.is_traded = True
  107. # 检查是否有持仓
  108. def has_position(context):
  109. for subportfolio in context.subportfolios:
  110. positions = list(subportfolio.long_positions.keys()) + list(subportfolio.short_positions.keys())
  111. # log.info(f'当前持仓为: {positions}')
  112. if len(positions) > 0:
  113. return True
  114. return False
  115. # 检查是否换月
  116. def check_and_switch_position(context):
  117. """每天14:30检查持仓并执行换月操作"""
  118. if has_position(context):
  119. # log.info("检测到持仓,开始执行换月检查...")
  120. position_auto_switch(context)
  121. else:
  122. log.info("无持仓,跳过换月检查")
  123. ## 收盘后运行函数
  124. def after_market_close(context):
  125. # 得到当天所有成交记录
  126. trades = get_trades()
  127. for _trade in trades.values():
  128. log.info('成交记录:'+str(_trade))
  129. log.info('##############################################################')
  130. ########################## 获取期货合约信息,请保留 #################################
  131. # 获取金融期货合约到期日
  132. def get_CCFX_end_date(future_code):
  133. # 获取金融期货合约到期日
  134. return get_security_info(future_code).end_date
  135. ########################## 自动移仓换月函数 #################################
  136. def position_auto_switch(context,pindex=0,switch_func=None, callback=None):
  137. """
  138. 期货自动移仓换月。默认使用市价单进行开平仓。
  139. :param context: 上下文对象
  140. :param pindex: 子仓对象
  141. :param switch_func: 用户自定义的移仓换月函数.
  142. 函数原型必须满足:func(context, pindex, previous_dominant_future_position, current_dominant_future_symbol)
  143. :param callback: 移仓换月完成后的回调函数。
  144. 函数原型必须满足:func(context, pindex, previous_dominant_future_position, current_dominant_future_symbol)
  145. :return: 发生移仓换月的标的。类型为列表。
  146. """
  147. import re
  148. subportfolio = context.subportfolios[pindex]
  149. symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
  150. switch_result = []
  151. for symbol in symbols:
  152. match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
  153. if not match:
  154. raise ValueError("未知期货标的:{}".format(symbol))
  155. else:
  156. dominant = get_dominant_future(match.groupdict()["underlying_symbol"])
  157. cur = get_current_data()
  158. # log.info(f'主力合约:{dominant},持仓:{symbol}')
  159. symbol_last_price = cur[symbol].last_price
  160. dominant_last_price = cur[dominant].last_price
  161. if dominant > symbol:
  162. log.debug(f'需要换月')
  163. for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
  164. if symbol not in positions_.keys():
  165. continue
  166. else :
  167. p = positions_[symbol]
  168. if switch_func is not None:
  169. switch_func(context, pindex, p, dominant)
  170. else:
  171. amount = p.total_amount
  172. # 跌停不能开空和平多,涨停不能开多和平空。
  173. if p.side == "long":
  174. symbol_low_limit = cur[symbol].low_limit
  175. dominant_high_limit = cur[dominant].high_limit
  176. if symbol_last_price <= symbol_low_limit:
  177. log.warning("标的{}跌停,无法平仓。移仓换月取消。".format(symbol))
  178. continue
  179. elif dominant_last_price >= dominant_high_limit:
  180. log.warning("标的{}涨停,无法开仓。移仓换月取消。".format(symbol))
  181. continue
  182. else:
  183. log.info("进行移仓换月:({0},long) -> ({1},long)".format(symbol, dominant))
  184. close_order =order_target(symbol,0,side='long')
  185. open_order = order_target(dominant,amount,side='long')
  186. # log.info(f'close_order: {close_order}')
  187. # log.info(f'open_order: {open_order}')
  188. # 记录换月交易信息
  189. key_symbol = re.match(r"([A-Z]+)", symbol).group(1)
  190. contract_multiplier = g.multiplier[key_symbol]
  191. close_money = close_order.amount * close_order.price * contract_multiplier
  192. open_money = open_order.amount * open_order.price * contract_multiplier
  193. switch_cost = abs(close_money - open_money)
  194. log.info(f'contract_multiplier: {contract_multiplier}, close_money: {close_money}, open_money: {open_money}, switch_cost: {switch_cost}')
  195. switch_record = {
  196. 'date': context.current_dt.strftime('%Y-%m-%d'),
  197. 'close_contract': symbol,
  198. 'close_price': close_order.price,
  199. 'close_money': close_money,
  200. 'open_contract': dominant,
  201. 'open_price': open_order.price,
  202. 'open_money': open_money,
  203. 'switch_cost': switch_cost,
  204. 'side': 'long'
  205. }
  206. g.switch_records.append(switch_record)
  207. log.info("换月记录:{}".format(switch_record))
  208. # 计算总换月次数和总换月成本
  209. total_switches = len(g.switch_records)
  210. total_cost = sum(record['switch_cost'] for record in g.switch_records)
  211. log.info(f"总换月次数: {total_switches}, 总换月成本: {total_cost}")
  212. switch_result.append({"before": symbol, "after":dominant, "side": "long"})
  213. if callback:
  214. callback(context, pindex, p, dominant)
  215. if p.side == "short":
  216. symbol_high_limit = cur[symbol].high_limit
  217. dominant_low_limit = cur[dominant].low_limit
  218. if symbol_last_price >= symbol_high_limit:
  219. log.warning("标的{}涨停,无法平仓。移仓换月取消。".format(symbol))
  220. continue
  221. elif dominant_last_price <= dominant_low_limit:
  222. log.warning("标的{}跌停,无法开仓。移仓换月取消。".format(symbol))
  223. continue
  224. else:
  225. log.info("进行移仓换月:({0},short) -> ({1},short)".format(symbol, dominant))
  226. close_order = order_target(symbol,0,side='short')
  227. open_order = order_target(dominant,amount,side='short')
  228. # log.info(f'close_order: {close_order}')
  229. # log.info(f'open_order: {open_order}')
  230. # 记录换月交易信息
  231. key_symbol = re.match(r"([A-Z]+)", symbol).group(1)
  232. contract_multiplier = g.multiplier[key_symbol]
  233. close_money = close_order.amount * close_order.price * contract_multiplier
  234. open_money = open_order.amount * open_order.price * contract_multiplier
  235. switch_cost = abs(close_money - open_money)
  236. log.info(f'contract_multiplier: {contract_multiplier}, close_money: {close_money}, open_money: {open_money}, switch_cost: {switch_cost}')
  237. switch_record = {
  238. 'date': context.current_dt.strftime('%Y-%m-%d'),
  239. 'close_contract': symbol,
  240. 'close_price': close_order.price,
  241. 'close_money': close_money,
  242. 'open_contract': dominant,
  243. 'open_price': open_order.price,
  244. 'open_money': open_money,
  245. 'switch_cost': switch_cost,
  246. 'side': 'short'
  247. }
  248. g.switch_records.append(switch_record)
  249. log.info("换月记录:{}".format(switch_record))
  250. # 计算总换月次数和总换月成本
  251. total_switches = len(g.switch_records)
  252. total_cost = sum(record['switch_cost'] for record in g.switch_records)
  253. log.info(f"总换月次数: {total_switches}, 总换月成本: {total_cost}")
  254. switch_result.append({"before": symbol, "after": dominant, "side": "short"})
  255. if callback:
  256. callback(context, pindex, p, dominant)
  257. return switch_result