FutureLeftSide_v001.py 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317
  1. # 导入函数库
  2. from jqdata import *
  3. from jqdata import finance
  4. import pandas as pd
  5. import numpy as np
  6. from datetime import date, datetime, timedelta, time
  7. import re
  8. # 期货左侧交易策略(带网格和对冲)v001
  9. # 基于网格交易理念,结合左侧底仓建仓、网格交易和空头对冲三个策略组件
  10. #
  11. # 交易逻辑分离:
  12. # 1. 建仓逻辑:在开盘时执行(夜盘品种21:05,日盘品种09:05)
  13. # - 底仓左侧多头:档位价格>=当前价用市价单,否则挂最高两档限价单
  14. # - 网格多头:目标价格>=当前价用市价单,否则挂最高两档限价单
  15. # - 空头对冲:永远使用限价单
  16. # 2. 平仓逻辑:在所有时间点执行(check_stop_profit_loss函数),包括止盈和止损
  17. # 设置以便完整打印 DataFrame
  18. pd.set_option('display.max_rows', None)
  19. pd.set_option('display.max_columns', None)
  20. pd.set_option('display.width', None)
  21. pd.set_option('display.max_colwidth', 20)
  22. ## 初始化函数,设定基准等等
  23. def initialize(context):
  24. # 设定沪深300作为基准
  25. set_benchmark('000300.XSHG')
  26. # 开启动态复权模式(真实价格)
  27. set_option('use_real_price', True)
  28. # 输出内容到日志
  29. log.info('期货左侧交易策略(带网格和对冲)初始化开始')
  30. ### 期货相关设定 ###
  31. # 设定账户为金融账户
  32. set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
  33. # 期货类每笔交易时的手续费是: 买入时万分之0.23,卖出时万分之0.23,平今仓为万分之23
  34. set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023), type='index_futures')
  35. # 设置期货交易的滑点
  36. set_slippage(StepRelatedSlippage(2))
  37. # ==================== 策略组件开关配置 ====================
  38. g.enable_grid_long = True # 是否启用网格多头策略组件
  39. g.enable_hedge_short = True # 是否启用空头对冲策略组件
  40. # 注意:底仓左侧多头始终启用
  41. # ==================== 资金管理配置 ====================
  42. g.usage_percentage = 0.8 # 最大资金使用比例
  43. g.max_margin_per_position = 20000 # 单个标的最大持仓保证金(元)
  44. # ==================== 交易品种配置 ====================
  45. # 期货品种完整配置字典
  46. g.futures_config = {
  47. # 贵金属(夜盘品种)
  48. 'AU': {'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 1000, 'trading_start_time': '21:00'},
  49. 'AG': {'margin_rate': {'long': 0.14, 'short': 0.14}, 'multiplier': 15, 'trading_start_time': '21:00'},
  50. # 有色金属(夜盘品种)
  51. 'CU': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
  52. 'AL': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
  53. 'ZN': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
  54. 'PB': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '21:00'},
  55. 'NI': {'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '21:00'},
  56. 'SN': {'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1, 'trading_start_time': '21:00'},
  57. 'SS': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
  58. # 黑色系(夜盘品种)
  59. 'RB': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
  60. 'HC': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
  61. 'I': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 100, 'trading_start_time': '21:00'},
  62. 'JM': {'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 100, 'trading_start_time': '21:00'},
  63. 'J': {'margin_rate': {'long': 0.22, 'short': 0.22}, 'multiplier': 60, 'trading_start_time': '21:00'},
  64. # 能源化工(夜盘品种)
  65. 'SP': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 10, 'trading_start_time': '21:00'},
  66. 'FU': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10, 'trading_start_time': '21:00'},
  67. 'BU': {'margin_rate': {'long': 0.04, 'short': 0.04}, 'multiplier': 10, 'trading_start_time': '21:00'},
  68. 'RU': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
  69. 'BR': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
  70. 'SC': {'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 1000, 'trading_start_time': '21:00'},
  71. 'NR': {'margin_rate': {'long': 0.13, 'short': 0.13}, 'multiplier': 10, 'trading_start_time': '21:00'},
  72. 'LU': {'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 10, 'trading_start_time': '21:00'},
  73. 'LC': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 1, 'trading_start_time': '09:00'},
  74. # 化工(夜盘品种)
  75. 'FG': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'},
  76. 'TA': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '21:00'},
  77. 'MA': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
  78. 'SA': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'},
  79. 'L': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
  80. 'V': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
  81. 'EG': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
  82. 'PP': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 5, 'trading_start_time': '21:00'},
  83. 'EB': {'margin_rate': {'long': 0.12, 'short': 0.12}, 'multiplier': 5, 'trading_start_time': '21:00'},
  84. 'PG': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'},
  85. # 农产品(夜盘品种)
  86. 'RM': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
  87. 'OI': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
  88. 'CF': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '21:00'},
  89. 'SR': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
  90. 'PF': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 5, 'trading_start_time': '21:00'},
  91. 'C': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
  92. 'CS': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
  93. 'CY': {'margin_rate': {'long': 0.15, 'short': 0.15}, 'multiplier': 5, 'trading_start_time': '21:00'},
  94. 'A': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
  95. 'B': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
  96. 'M': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '21:00'},
  97. 'Y': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
  98. 'P': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 10, 'trading_start_time': '21:00'},
  99. # 股指期货(日盘品种,9:30开始交易)
  100. 'IF': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300, 'trading_start_time': '09:30'},
  101. 'IH': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 300, 'trading_start_time': '09:30'},
  102. 'IC': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200, 'trading_start_time': '09:30'},
  103. 'IM': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 200, 'trading_start_time': '09:30'},
  104. 'TL': {'margin_rate': {'long': 0.01, 'short': 0.01}, 'multiplier': 10000, 'trading_start_time': '09:30'},
  105. # 其他日盘品种(9:00开始交易)
  106. 'AP': {'margin_rate': {'long': 0.08, 'short': 0.08}, 'multiplier': 10, 'trading_start_time': '09:00'},
  107. 'CJ': {'margin_rate': {'long': 0.09, 'short': 0.09}, 'multiplier': 5, 'trading_start_time': '09:00'},
  108. 'PK': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 5, 'trading_start_time': '09:00'},
  109. 'JD': {'margin_rate': {'long': 0.07, 'short': 0.07}, 'multiplier': 10, 'trading_start_time': '09:00'},
  110. 'LH': {'margin_rate': {'long': 0.1, 'short': 0.1}, 'multiplier': 16, 'trading_start_time': '09:00'},
  111. 'UR': {'margin_rate': {'long': 0.05, 'short': 0.05}, 'multiplier': 20, 'trading_start_time': '21:00'}
  112. }
  113. # 策略品种选择配置
  114. g.strategy_focus_symbols = ['TL'] # 交易品种列表
  115. # ==================== 底仓左侧多头配置 ====================
  116. # 价格-数量网格:价格越低,买入手数越多
  117. g.base_position_grid = {
  118. 'TL': {118: 1, 117: 1, 116: 1, 115: 1, 114: 2, 113: 2},
  119. }
  120. # 底仓退出价格(止盈)
  121. g.base_position_exit_price = {
  122. 'TL': 121,
  123. }
  124. # ==================== 网格多头配置 ====================
  125. g.grid_config = {
  126. 'TL': {
  127. 'start_price': 118, # 网格开始价格
  128. 'grid_size': 1, # 网格大小
  129. 'quantity_per_grid': 1, # 每网格数量
  130. 'exit_grid_size': 1 # 退出网格大小
  131. },
  132. }
  133. # ==================== 空头对冲配置 ====================
  134. g.hedge_profit_pullback_pct = 0.003 # 止盈回撤百分比(0.3%)
  135. g.hedge_stoploss_pct = 0.01 # 止损百分比(1%)
  136. g.hedge_level1_recovery_pct = 0.30 # 一级止盈回升百分比(30%)
  137. g.hedge_level2_recovery_pct = 0.60 # 二级止盈回升百分比(60%)
  138. g.hedge_cost_area_pct = 0.02 # 成本区域百分比(2%)
  139. # ==================== 全局变量初始化 ====================
  140. # 持仓跟踪
  141. g.base_positions = {} # 底仓持仓 {symbol: {level_key: {'entry_price': price, 'quantity': qty, 'entry_date': date}}}
  142. g.grid_positions = {} # 网格持仓 {symbol: [{'entry_price': price, 'quantity': qty, 'target_price': target_price, 'entry_date': date}]}
  143. g.hedge_positions = {} # 对冲持仓 {symbol: {level_key: {'entry_price': price, 'quantity': qty, 'entry_date': date}}}
  144. # 网格状态跟踪
  145. g.grid_buy_levels = {} # 网格已建仓目标价集合 {symbol: set()}
  146. # 对冲状态跟踪
  147. g.hedge_state = {} # {symbol: {'max_profit': 0, 'max_profit_price': 0, ...}}
  148. # 订单策略信息记录
  149. g.order_strategy_info = {} # {order_id: {'strategy_type': '策略类型', 'target_price': 价格}}
  150. # ==================== 定时任务设置 ====================
  151. # 日盘止损止盈检查(每个时间点)
  152. run_daily(check_open_positions, time='09:35:00', reference_security='IF1808.CCFX')
  153. run_daily(check_stop_profit_loss, time='10:05:00', reference_security='IF1808.CCFX')
  154. run_daily(check_stop_profit_loss, time='10:35:00', reference_security='IF1808.CCFX')
  155. run_daily(check_stop_profit_loss, time='11:05:00', reference_security='IF1808.CCFX')
  156. run_daily(check_stop_profit_loss, time='13:35:00', reference_security='IF1808.CCFX')
  157. run_daily(check_stop_profit_loss, time='14:05:00', reference_security='IF1808.CCFX')
  158. run_daily(check_stop_profit_loss, time='14:55:00', reference_security='IF1808.CCFX')
  159. # 建仓逻辑(开盘时执行:夜盘21:05,日盘09:05)
  160. run_daily(check_open_positions, time='21:05:00', reference_security='IF1808.CCFX') # 夜盘品种
  161. run_daily(check_open_positions, time='09:05:00', reference_security='IF1808.CCFX') # 日盘品种
  162. # 夜盘止损止盈检查(仅对有夜盘的品种)
  163. run_daily(check_stop_profit_loss, time='21:05:00', reference_security='IF1808.CCFX')
  164. run_daily(check_stop_profit_loss, time='21:35:00', reference_security='IF1808.CCFX')
  165. run_daily(check_stop_profit_loss, time='22:05:00', reference_security='IF1808.CCFX')
  166. # 收盘后
  167. run_daily(after_market_close, time='15:30:00', reference_security='IF1808.CCFX')
  168. log.info("=" * 50)
  169. log.info("策略组件配置:")
  170. log.info(f" 底仓左侧多头: 始终启用")
  171. log.info(f" 网格多头策略: {'启用' if g.enable_grid_long else '禁用'}")
  172. log.info(f" 空头对冲策略: {'启用' if g.enable_hedge_short else '禁用'}")
  173. log.info(f"交易品种: {g.strategy_focus_symbols}")
  174. log.info("=" * 50)
  175. ############################ 主程序执行函数 ###################################
  176. def check_open_positions(context):
  177. """建仓检查函数(仅在14:35执行)"""
  178. log.info("=" * 60)
  179. log.info(f"执行建仓检查逻辑 - 时间: {context.current_dt}")
  180. log.info("=" * 60)
  181. # 先检查换月移仓
  182. switch_success, switch_failed = position_auto_switch(context)
  183. if switch_failed:
  184. log.info("检测到移仓换月失败,跳过后续建仓检查")
  185. for failed in switch_failed:
  186. log.info(f"换月失败详情: {failed}")
  187. return
  188. # 获取当前时间
  189. current_time = str(context.current_dt.time())[:2]
  190. # 筛选可交易品种(根据交易开始时间判断是否为夜盘品种)
  191. tradable_symbols = []
  192. for symbol in g.strategy_focus_symbols:
  193. is_night_symbol = is_night_session_symbol(symbol)
  194. if current_time in ('21', '22'):
  195. # 夜盘时间:只考虑夜盘品种
  196. if is_night_symbol:
  197. tradable_symbols.append(symbol)
  198. else:
  199. # 日盘时间:所有品种都可以交易
  200. tradable_symbols.append(symbol)
  201. if not tradable_symbols:
  202. log.info("当前时段无可交易品种")
  203. return
  204. log.info(f"可交易品种: {tradable_symbols}")
  205. # 对每个品种执行建仓检查
  206. for symbol in tradable_symbols:
  207. try:
  208. # 获取主力合约
  209. dominant_future = get_dominant_future(symbol)
  210. if not dominant_future:
  211. log.warning(f"{symbol} 未找到主力合约")
  212. continue
  213. # 获取当前价格
  214. current_data = get_current_data()[dominant_future]
  215. current_price = current_data.last_price
  216. log.info(f"建仓检查 - 品种: {symbol}, 主力合约: {dominant_future}, 当前价格: {current_price}")
  217. # 策略组件1: 底仓左侧多头建仓(始终启用)
  218. check_base_position_open(context, symbol, dominant_future, current_price)
  219. # 策略组件2: 网格多头建仓(可配置)
  220. if g.enable_grid_long:
  221. check_grid_trading_open(context, symbol, dominant_future, current_price)
  222. # 策略组件3: 空头对冲建仓(可配置)
  223. if g.enable_hedge_short:
  224. check_hedge_position_open(context, symbol, dominant_future, current_price)
  225. except Exception as e:
  226. log.warning(f"建仓检查处理{symbol}时出错: {str(e)}")
  227. continue
  228. def check_stop_profit_loss(context):
  229. """止损止盈检查函数(所有时间点执行)"""
  230. # log.info("=" * 60)
  231. # log.info(f"执行止损止盈检查 - 时间: {context.current_dt}")
  232. # log.info("=" * 60)
  233. # 先检查换月移仓
  234. switch_success, switch_failed = position_auto_switch(context)
  235. if switch_failed:
  236. log.info("检测到移仓换月失败,跳过后续止盈止损检查")
  237. for failed in switch_failed:
  238. log.info(f"换月失败详情: {failed}")
  239. return
  240. # 获取当前时间
  241. current_dt = context.current_dt
  242. current_time = current_dt.time()
  243. current_time_str = current_dt.strftime("%H:%M:%S")
  244. in_night_session = current_time >= time(21, 0) or current_time < time(2, 30)
  245. in_day_session = time(9, 0) <= current_time <= time(15, 0)
  246. # 筛选可交易品种(根据交易开始时间判断是否为夜盘品种)
  247. tradable_symbols = []
  248. for symbol in g.strategy_focus_symbols:
  249. is_night_symbol = is_night_session_symbol(symbol)
  250. if not is_night_symbol:
  251. if in_night_session:
  252. log.info(f"{symbol} 为日盘合约,当前夜间时段 {current_time_str} 跳过止损止盈检查")
  253. continue
  254. if not in_day_session:
  255. log.info(f"{symbol} 为日盘合约,当前非交易时段 {current_time_str} 跳过止损止盈检查")
  256. continue
  257. else:
  258. if in_night_session:
  259. log.info(f"{symbol} 夜盘合约在夜间时段 {current_time_str} 执行止损止盈检查")
  260. elif not in_day_session:
  261. log.info(f"{symbol} 夜盘合约当前非活跃时段 {current_time_str} 仍执行止损止盈检查以覆盖全天风险")
  262. tradable_symbols.append(symbol)
  263. if not tradable_symbols:
  264. return
  265. # 对每个品种执行止损止盈检查
  266. for symbol in tradable_symbols:
  267. try:
  268. # 获取主力合约
  269. dominant_future = get_dominant_future(symbol)
  270. if not dominant_future:
  271. continue
  272. # 获取当前价格
  273. current_data = get_current_data()[dominant_future]
  274. current_price = current_data.last_price
  275. # 策略组件1: 底仓左侧多头止盈(始终启用)
  276. check_base_position_close(context, symbol, dominant_future, current_price)
  277. # 策略组件2: 网格多头止盈(可配置)
  278. if g.enable_grid_long:
  279. check_grid_trading_close(context, symbol, dominant_future, current_price)
  280. # 策略组件3: 空头对冲止损止盈(可配置)
  281. if g.enable_hedge_short:
  282. check_hedge_position_close(context, symbol, dominant_future, current_price)
  283. except Exception as e:
  284. log.warning(f"止损止盈检查处理{symbol}时出错: {str(e)}")
  285. continue
  286. ############################ 策略组件1: 底仓左侧多头 ###################################
  287. def check_base_position_open(context, symbol, dominant_future, current_price):
  288. """检查底仓左侧多头建仓
  289. 逻辑:
  290. 1. 遍历所有未建仓的价格档位
  291. 2. 如果档位价格 >= 当前价格:使用市价单立即成交
  292. 3. 如果档位价格 < 当前价格:记录到待挂限价单列表
  293. 4. 从待挂限价单列表中选择最高的两个档位挂限价单
  294. """
  295. # 时间检查:判断是否已达到该品种的交易开始时间
  296. if not is_trading_time_reached(context, symbol):
  297. log.info(f"底仓建仓跳过: {symbol} 未达到交易开始时间")
  298. return
  299. # 检查是否有底仓配置
  300. if symbol not in g.base_position_grid:
  301. return
  302. grid = g.base_position_grid[symbol]
  303. # 初始化持仓记录
  304. if dominant_future not in g.base_positions:
  305. g.base_positions[dominant_future] = {}
  306. log.info(f"底仓建仓检查: {symbol} 当前价格 {current_price}")
  307. log.info(f"底仓持仓: {g.base_positions[dominant_future]}")
  308. # 收集未建仓的档位
  309. market_order_levels = [] # 需要市价单的档位
  310. limit_order_levels = [] # 需要限价单的档位
  311. for price_level, quantity in sorted(grid.items(), reverse=True):
  312. level_key = f"{price_level}"
  313. # 跳过已建仓的档位
  314. if level_key in g.base_positions[dominant_future]:
  315. continue
  316. # 判断使用市价单还是限价单
  317. if price_level >= current_price:
  318. # 档位价格 >= 当前价格,使用市价单
  319. market_order_levels.append((price_level, quantity))
  320. log.info(f" 档位 {price_level}({quantity}手)>= 当前价 {current_price},将使用市价单")
  321. else:
  322. # 档位价格 < 当前价格,加入限价单候选
  323. limit_order_levels.append((price_level, quantity))
  324. # 执行市价单
  325. for price_level, quantity in market_order_levels:
  326. level_key = f"{price_level}"
  327. strategy_info = {'strategy_type': '基础左侧', 'target_price': price_level}
  328. success = open_position(context, dominant_future, quantity, 'long',
  329. f'底仓市价建仓(目标档位{price_level})',
  330. strategy_info=strategy_info)
  331. if success:
  332. # 获取实际成交价格(从订单记录中获取)
  333. actual_price = current_price # 市价单按当前价记录
  334. g.base_positions[dominant_future][level_key] = {
  335. 'entry_price': actual_price,
  336. 'quantity': quantity,
  337. 'entry_date': context.current_dt
  338. }
  339. log.info(f"底仓市价单成交: {symbol} 目标档位 {price_level}, 实际价格 {actual_price}, 数量 {quantity}")
  340. # 执行限价单(固定挂两个档位)
  341. if limit_order_levels:
  342. # 选择价格最高的两个档位
  343. top_two_levels = sorted(limit_order_levels, reverse=True)[:2]
  344. log.info(f" 准备挂限价单,候选档位: {[p for p, q in limit_order_levels]}, 选择最高两个: {[p for p, q in top_two_levels]}")
  345. for price_level, quantity in top_two_levels:
  346. level_key = f"{price_level}"
  347. strategy_info = {'strategy_type': '基础左侧', 'target_price': price_level}
  348. success = open_position(context, dominant_future, quantity, 'long',
  349. f'底仓限价建仓@{price_level}',
  350. limit_price=price_level,
  351. strategy_info=strategy_info)
  352. if success:
  353. g.base_positions[dominant_future][level_key] = {
  354. 'entry_price': price_level, # 限价单按委托价记录
  355. 'quantity': quantity,
  356. 'entry_date': context.current_dt
  357. }
  358. log.info(f"底仓限价单挂单: {symbol} 档位 {price_level}, 数量 {quantity}")
  359. def check_base_position_close(context, symbol, dominant_future, current_price):
  360. """检查底仓左侧多头平仓(止盈)"""
  361. # 时间检查:判断是否已达到该品种的交易开始时间
  362. if not is_trading_time_reached(context, symbol):
  363. log.info(f"底仓止盈跳过: {symbol} 未达到交易开始时间")
  364. return
  365. # 检查是否有底仓配置
  366. if symbol not in g.base_position_exit_price:
  367. return
  368. exit_price = g.base_position_exit_price.get(symbol)
  369. # 检查退出条件(止盈)
  370. if exit_price and current_price >= exit_price:
  371. # 检查是否有持仓需要平仓
  372. subportfolio = context.subportfolios[0]
  373. if dominant_future in subportfolio.long_positions:
  374. position = subportfolio.long_positions[dominant_future]
  375. if position.total_amount > 0:
  376. log.info(f"底仓触发止盈: {symbol} 当前价格 {current_price} >= 退出价格 {exit_price}")
  377. close_position(context, dominant_future, position.total_amount, 'long', '底仓止盈')
  378. if dominant_future in g.base_positions:
  379. g.base_positions[dominant_future] = {}
  380. return
  381. ############################ 策略组件2: 网格多头策略 ###################################
  382. def check_grid_trading_open(context, symbol, dominant_future, current_price):
  383. """检查网格多头建仓
  384. 逻辑:
  385. 1. 找出所有应该买入但未买入的网格层级
  386. 2. 对于每个层级的目标价格:
  387. - 如果目标价格 >= 当前价格:使用市价单
  388. - 如果目标价格 < 当前价格:记录到待挂限价单列表
  389. 3. 从待挂限价单列表中选择价格最高的两个层级挂限价单
  390. """
  391. # 时间检查:判断是否已达到该品种的交易开始时间
  392. if not is_trading_time_reached(context, symbol):
  393. log.info(f"网格建仓跳过: {symbol} 未达到交易开始时间")
  394. return
  395. # 检查是否有网格配置
  396. if symbol not in g.grid_config:
  397. return
  398. config = g.grid_config[symbol]
  399. start_price = config['start_price']
  400. grid_size = config['grid_size']
  401. quantity_per_grid = config['quantity_per_grid']
  402. # 初始化网格持仓记录
  403. if dominant_future not in g.grid_positions:
  404. g.grid_positions[dominant_future] = []
  405. if dominant_future not in g.grid_buy_levels:
  406. g.grid_buy_levels[dominant_future] = set()
  407. # 只在价格低于起始价格时才执行网格交易
  408. if current_price >= start_price:
  409. return
  410. log.info(f"网格建仓检查: {symbol} 当前价格 {current_price}, 起始价格 {start_price}")
  411. # 计算当前价格对应的最低网格档位数量
  412. max_grid_index = int((start_price - current_price) / grid_size)
  413. # 收集需要建仓的网格档位
  414. market_order_grids = [] # 需要市价单的网格
  415. limit_order_grids = [] # 需要限价单的网格
  416. # 遍历所有应该触发的网格档位(从0到max_grid_index)
  417. for grid_index in range(max_grid_index + 1):
  418. # 计算该档位的目标买入价格
  419. target_price = start_price - grid_index * grid_size
  420. # 跳过已经买入的目标价
  421. if target_price in g.grid_buy_levels[dominant_future]:
  422. continue
  423. grid_info = (grid_index, target_price)
  424. # 判断使用市价单还是限价单
  425. if target_price >= current_price:
  426. # 目标价格 >= 当前价格,使用市价单
  427. market_order_grids.append(grid_info)
  428. log.info(f" 网格目标价 {target_price} >= 当前价 {current_price},将使用市价单 (档位索引 {grid_index})")
  429. else:
  430. # 目标价格 < 当前价格,加入限价单候选
  431. limit_order_grids.append(grid_info)
  432. # 执行市价单
  433. for _, target_price in market_order_grids:
  434. strategy_info = {'strategy_type': '网格', 'target_price': target_price}
  435. success = open_position(context, dominant_future, quantity_per_grid, 'long',
  436. f'网格市价买入(目标价{target_price})',
  437. strategy_info=strategy_info)
  438. if success:
  439. # 市价单按实际成交价记录(这里用当前价近似)
  440. g.grid_positions[dominant_future].append({
  441. 'entry_price': current_price,
  442. 'quantity': quantity_per_grid,
  443. 'target_price': target_price,
  444. 'entry_date': context.current_dt
  445. })
  446. g.grid_buy_levels[dominant_future].add(target_price)
  447. log.info(f"网格市价单成交: {symbol} 目标价 {target_price}, 实际价 {current_price}, 数量 {quantity_per_grid}")
  448. # 执行限价单(固定挂两个层级)
  449. if limit_order_grids:
  450. # 选择价格最高的两个目标价(档位数越小价格越高)
  451. top_two_grids = sorted(limit_order_grids, key=lambda x: x[1], reverse=True)[:2]
  452. log.info(f" 准备挂限价单,候选目标价: {[(l, p) for l, p in limit_order_grids]}, 选择最高两个: {[(l, p) for l, p in top_two_grids]}")
  453. for _, target_price in top_two_grids:
  454. strategy_info = {'strategy_type': '网格', 'target_price': target_price}
  455. success = open_position(context, dominant_future, quantity_per_grid, 'long',
  456. f'网格限价买入@目标价{target_price}',
  457. limit_price=target_price,
  458. strategy_info=strategy_info)
  459. if success:
  460. g.grid_positions[dominant_future].append({
  461. 'entry_price': target_price, # 限价单按委托价记录
  462. 'quantity': quantity_per_grid,
  463. 'target_price': target_price,
  464. 'entry_date': context.current_dt
  465. })
  466. g.grid_buy_levels[dominant_future].add(target_price)
  467. log.info(f"网格限价单挂单: {symbol} 目标价 {target_price}, 数量 {quantity_per_grid}")
  468. def check_grid_trading_close(context, symbol, dominant_future, current_price):
  469. """检查网格多头平仓(止盈)"""
  470. # 时间检查:判断是否已达到该品种的交易开始时间
  471. if not is_trading_time_reached(context, symbol):
  472. log.info(f"网格止盈跳过: {symbol} 未达到交易开始时间")
  473. return
  474. # 检查是否有网格配置
  475. if symbol not in g.grid_config:
  476. return
  477. config = g.grid_config[symbol]
  478. exit_grid_size = config['exit_grid_size']
  479. # 初始化网格持仓记录
  480. if dominant_future not in g.grid_positions:
  481. g.grid_positions[dominant_future] = []
  482. if dominant_future not in g.grid_buy_levels:
  483. g.grid_buy_levels[dominant_future] = set()
  484. # 检查卖出条件:对每个持仓检查是否达到退出条件
  485. positions_to_remove = []
  486. for i, position in enumerate(g.grid_positions[dominant_future]):
  487. target_price = position['target_price']
  488. exit_price = target_price + exit_grid_size
  489. if current_price >= exit_price:
  490. # 执行网格卖出
  491. success = close_position(context, dominant_future, position['quantity'], 'long',
  492. f'网格卖出@目标价{target_price}')
  493. if success:
  494. profit_per_unit = current_price - position['entry_price']
  495. log.info(f"网格卖出: {symbol} 目标价 {target_price}, 计划退出价 {exit_price}, "
  496. f"买入价 {position['entry_price']}, 卖出价 {current_price}, "
  497. f"盈利 {profit_per_unit * position['quantity'] * get_multiplier(symbol)}")
  498. positions_to_remove.append(i)
  499. # 移除该网格目标价标记,允许重新买入
  500. if target_price in g.grid_buy_levels[dominant_future]:
  501. g.grid_buy_levels[dominant_future].remove(target_price)
  502. # 移除已平仓的网格持仓记录
  503. for i in reversed(positions_to_remove):
  504. g.grid_positions[dominant_future].pop(i)
  505. ############################ 策略组件3: 空头对冲策略 ###################################
  506. def check_hedge_position_open(context, symbol, dominant_future, current_price):
  507. """检查空头对冲建仓
  508. 对冲逻辑:
  509. 1. 对冲数量只对应基础左侧持仓,不包括网格持仓
  510. 2. 对冲永远使用限价单,价格为各档位的目标价格
  511. 3. 针对每个基础左侧档位分别建立对应的空头对冲单
  512. """
  513. # 时间检查:判断是否已达到该品种的交易开始时间
  514. if not is_trading_time_reached(context, symbol):
  515. log.info(f"对冲建仓跳过: {symbol} 未达到交易开始时间")
  516. return
  517. # 初始化对冲状态
  518. if dominant_future not in g.hedge_state:
  519. g.hedge_state[dominant_future] = {
  520. 'max_profit': 0,
  521. 'max_profit_price': 0,
  522. 'profit_stage': 0 # 0: 未盈利, 1: 一级盈利, 2: 二级盈利
  523. }
  524. # 初始化对冲持仓记录
  525. if dominant_future not in g.hedge_positions:
  526. g.hedge_positions[dominant_future] = {}
  527. # 检查是否有底仓持仓(作为建立对冲的条件)
  528. if dominant_future not in g.base_positions or not g.base_positions[dominant_future]:
  529. log.info(f"对冲建仓检查: {symbol} 当前无基础左侧持仓,无需对冲")
  530. return
  531. log.info(f"对冲建仓检查: {symbol} 当前价格 {current_price}")
  532. # 针对每个基础左侧档位建立对应的对冲单
  533. for level_key, position_info in g.base_positions[dominant_future].items():
  534. # 跳过已经建立对冲的档位
  535. if level_key in g.hedge_positions[dominant_future]:
  536. continue
  537. target_price = level_key # 目标价格本质就是对冲档位
  538. quantity = position_info['quantity']
  539. log.info(f" 对冲档位 {level_key}: 目标价格 {target_price}, 数量 {quantity}手")
  540. # 建立空头对冲(永远使用限价单,价格为档位目标价)
  541. strategy_info = {'strategy_type': '对冲', 'target_price': target_price}
  542. success = open_position(context, dominant_future, quantity, 'short',
  543. f'建立空头对冲@{target_price}',
  544. limit_price=target_price,
  545. strategy_info=strategy_info)
  546. if success:
  547. try:
  548. entry_price_value = float(target_price)
  549. except (TypeError, ValueError):
  550. log.error(f"对冲建仓记录失败: 档位 {level_key} 目标价无法转换为浮点数 -> {target_price}")
  551. entry_price_value = target_price
  552. g.hedge_positions[dominant_future][level_key] = {
  553. 'entry_price': entry_price_value,
  554. 'quantity': quantity,
  555. 'entry_date': context.current_dt
  556. }
  557. log.info(f"对冲限价单挂单: {symbol} 档位 {level_key}, 价格 {target_price}, 数量 {quantity}手")
  558. def check_hedge_position_close(context, symbol, dominant_future, current_price):
  559. """检查空头对冲平仓(止损止盈)
  560. 对冲平仓逻辑:
  561. 1. 分别检查每个档位的对冲持仓
  562. 2. 每个档位独立计算盈亏和止损止盈条件
  563. """
  564. # 时间检查:判断是否已达到该品种的交易开始时间
  565. if not is_trading_time_reached(context, symbol):
  566. log.info(f"对冲止损止盈跳过: {symbol} 未达到交易开始时间")
  567. return
  568. # 检查是否已有对冲持仓记录
  569. if dominant_future not in g.hedge_positions or not g.hedge_positions[dominant_future]:
  570. return
  571. # 检查实际空头持仓
  572. subportfolio = context.subportfolios[0]
  573. has_hedge_position = dominant_future in subportfolio.short_positions and \
  574. subportfolio.short_positions[dominant_future].total_amount > 0
  575. if not has_hedge_position:
  576. return
  577. # 遍历每个档位的对冲持仓
  578. levels_to_remove = []
  579. for level_key, hedge_info in g.hedge_positions[dominant_future].items():
  580. try:
  581. entry_price = float(hedge_info['entry_price'])
  582. except (TypeError, ValueError):
  583. log.error(f"对冲止损止盈计算失败: 档位 {level_key} 入场价无法转换为浮点数 -> {hedge_info.get('entry_price')}")
  584. continue
  585. quantity = hedge_info['quantity']
  586. # 计算当前盈亏率(空头:入场价格 > 当前价格为盈利)
  587. profit_rate = (entry_price - current_price) / entry_price
  588. # 更新最大盈利
  589. if profit_rate > g.hedge_state[dominant_future]['max_profit']:
  590. g.hedge_state[dominant_future]['max_profit'] = profit_rate
  591. g.hedge_state[dominant_future]['max_profit_price'] = current_price
  592. # 更新盈利阶段
  593. if profit_rate >= g.hedge_level2_recovery_pct * g.hedge_profit_pullback_pct:
  594. g.hedge_state[dominant_future]['profit_stage'] = 2
  595. elif profit_rate >= g.hedge_level1_recovery_pct * g.hedge_profit_pullback_pct:
  596. g.hedge_state[dominant_future]['profit_stage'] = 1
  597. # 检查止损条件
  598. if profit_rate <= -g.hedge_stoploss_pct:
  599. log.info(f"对冲触发止损: {symbol} 档位 {level_key}, 入场价 {entry_price}, 亏损率 {profit_rate:.2%}")
  600. close_position(context, dominant_future, quantity, 'short', f'对冲止损@档位{level_key}')
  601. levels_to_remove.append(level_key)
  602. continue
  603. # 检查回撤止盈条件
  604. max_profit = g.hedge_state[dominant_future]['max_profit']
  605. profit_drawdown = max_profit - profit_rate
  606. if profit_drawdown >= g.hedge_profit_pullback_pct:
  607. log.info(f"对冲触发回撤止盈: {symbol} 档位 {level_key}, 最大盈利 {max_profit:.2%}, "
  608. f"当前盈利 {profit_rate:.2%}, 回撤 {profit_drawdown:.2%}")
  609. close_position(context, dominant_future, quantity, 'short', f'对冲回撤止盈@档位{level_key}')
  610. levels_to_remove.append(level_key)
  611. continue
  612. # 检查成本区域止盈条件
  613. if abs(profit_rate) <= g.hedge_cost_area_pct:
  614. profit_stage = g.hedge_state[dominant_future]['profit_stage']
  615. if profit_stage >= 1: # 曾经达到过一级或二级盈利
  616. log.info(f"对冲触发成本区域止盈: {symbol} 档位 {level_key}, 当前盈利 {profit_rate:.2%} "
  617. f"在成本区域内,曾达到盈利阶段 {profit_stage}")
  618. close_position(context, dominant_future, quantity, 'short', f'对冲成本区域止盈@档位{level_key}')
  619. levels_to_remove.append(level_key)
  620. continue
  621. # 移除已平仓的档位
  622. for level_key in levels_to_remove:
  623. del g.hedge_positions[dominant_future][level_key]
  624. # 如果所有对冲持仓都已平仓,重置对冲状态
  625. if not g.hedge_positions[dominant_future]:
  626. g.hedge_state[dominant_future] = {'max_profit': 0, 'max_profit_price': 0, 'profit_stage': 0}
  627. ############################ 交易执行函数 ###################################
  628. def open_position(context, security, quantity, direction, reason='', limit_price=None, strategy_info=None):
  629. """开仓
  630. Args:
  631. context: 上下文对象
  632. security: 合约代码
  633. quantity: 开仓数量
  634. direction: 方向 ('long' 或 'short')
  635. reason: 开仓原因
  636. limit_price: 限价单价格,None表示使用市价单
  637. strategy_info: 策略信息字典 {'strategy_type': '策略类型', 'target_price': 目标价格}
  638. 注:订单提交后filled=0,实际成交信息需要在收盘后通过get_trades()获取
  639. """
  640. try:
  641. order_type = "限价单" if limit_price is not None else "市价单"
  642. price_info = f"@{limit_price}" if limit_price is not None else ""
  643. log.info(f"提交开仓订单: {security} {direction} {quantity}手 {order_type}{price_info} - {reason}")
  644. # 使用order按手数开仓(避免order_target取消之前的未完成订单)
  645. # 根据是否有限价选择订单类型
  646. if limit_price is not None:
  647. order_obj = order(security, quantity, LimitOrderStyle(limit_price), side=direction)
  648. else:
  649. order_obj = order(security, quantity, side=direction)
  650. if order_obj is not None:
  651. log.info(f"订单已提交: order_id={order_obj.order_id}, 状态={order_obj.status}, "
  652. f"已成交={order_obj.filled}/{quantity}手")
  653. # 保存策略信息到订单记录中,用于收盘后的汇总显示
  654. if strategy_info:
  655. if not hasattr(g, 'order_strategy_info'):
  656. g.order_strategy_info = {}
  657. g.order_strategy_info[order_obj.order_id] = strategy_info
  658. log.info(f"订单策略信息已记录: {strategy_info}")
  659. return True
  660. else:
  661. log.warning(f"订单提交失败: {security} {direction} {quantity}手")
  662. return False
  663. except Exception as e:
  664. log.warning(f"开仓异常 {security}: {str(e)}")
  665. return False
  666. def close_position(context, security, quantity, direction, reason=''):
  667. """平仓
  668. 注:平仓使用order_target是安全的,因为通常不会有多个平仓订单同时进行
  669. 订单提交后filled=0,实际成交信息需要在收盘后通过get_trades()获取
  670. """
  671. try:
  672. log.info(f"提交平仓订单: {security} {direction} {quantity}手 - {reason}")
  673. # 使用order_target平仓
  674. subportfolio = context.subportfolios[0]
  675. current_position = 0
  676. if direction == 'long' and security in subportfolio.long_positions:
  677. current_position = subportfolio.long_positions[security].total_amount
  678. elif direction == 'short' and security in subportfolio.short_positions:
  679. current_position = subportfolio.short_positions[security].total_amount
  680. target_quantity = max(0, current_position - quantity)
  681. order_obj = order_target(security, target_quantity, side=direction)
  682. if order_obj is not None:
  683. log.info(f"平仓订单已提交: order_id={order_obj.order_id}, 状态={order_obj.status}, "
  684. f"已成交={order_obj.filled}/{quantity}手")
  685. return True
  686. else:
  687. log.warning(f"平仓订单提交失败: {security} {direction} {quantity}手")
  688. return False
  689. except Exception as e:
  690. log.warning(f"平仓异常 {security}: {str(e)}")
  691. return False
  692. ############################ 辅助函数 ###################################
  693. def get_futures_config(underlying_symbol, config_key=None, default_value=None):
  694. """获取期货品种配置信息的辅助函数"""
  695. if underlying_symbol not in g.futures_config:
  696. if config_key and default_value is not None:
  697. return default_value
  698. return {}
  699. if config_key is None:
  700. return g.futures_config[underlying_symbol]
  701. return g.futures_config[underlying_symbol].get(config_key, default_value)
  702. def is_night_session_symbol(symbol):
  703. """判断品种是否为夜盘品种
  704. Args:
  705. symbol: 期货品种代码(如'IF'、'AU'等)
  706. Returns:
  707. bool: True表示夜盘品种,False表示日盘品种
  708. """
  709. trading_start_time = get_futures_config(symbol, 'trading_start_time', '09:00')
  710. # 解析配置的时间
  711. try:
  712. start_hour, _ = map(int, trading_start_time.split(':'))
  713. # 如果开始时间 >= 21:00,则为夜盘品种
  714. return start_hour >= 21
  715. except (ValueError, AttributeError):
  716. log.warning(f"{symbol} 交易开始时间配置格式错误: {trading_start_time},默认判断为日盘品种")
  717. return False
  718. def is_trading_time_reached(context, symbol):
  719. """判断当前时间是否已达到该品种的交易开始时间
  720. Args:
  721. context: 上下文对象
  722. symbol: 期货品种代码(如'IF'、'AU'等)
  723. Returns:
  724. bool: True表示已达到交易时间,False表示未达到
  725. """
  726. # 获取配置的交易开始时间
  727. trading_start_time = get_futures_config(symbol, 'trading_start_time', '09:05')
  728. # 解析配置的时间(格式为 "HH:MM")
  729. try:
  730. start_hour, start_minute = map(int, trading_start_time.split(':'))
  731. configured_start_time = time(start_hour, start_minute)
  732. except (ValueError, AttributeError):
  733. log.warning(f"{symbol} 交易开始时间配置格式错误: {trading_start_time},默认使用09:05")
  734. configured_start_time = time(9, 5)
  735. # 获取当前时间
  736. current_time = context.current_dt.time()
  737. # 判断逻辑:
  738. # 1. 如果配置时间是夜盘时间(>=21:00),且当前时间也在夜盘时段(>=21:00 或 <03:00),直接比较
  739. # 2. 如果配置时间是日盘时间(<21:00),且当前时间也在日盘时段(09:00-15:00),直接比较
  740. # 3. 其他情况返回False(例如日盘品种在夜盘时段)
  741. is_night_start = configured_start_time >= time(21, 0)
  742. is_current_night = current_time >= time(21, 0) or current_time < time(3, 0)
  743. is_current_day = time(9, 0) <= current_time <= time(15, 0)
  744. if is_night_start:
  745. # 配置为夜盘开始时间
  746. if is_current_night:
  747. # 当前也在夜盘时段
  748. if current_time >= time(21, 0):
  749. # 当前在21:00之后
  750. result = current_time >= configured_start_time
  751. else:
  752. # 当前在凌晨(已经过了配置的开始时间)
  753. result = True
  754. log.info(f"[时间检查] {symbol} 配置夜盘开始 {trading_start_time}, 当前时间 {current_time.strftime('%H:%M:%S')}, 判断结果: {result}")
  755. return result
  756. elif is_current_day:
  757. # 夜盘品种在日盘时段,允许交易
  758. log.info(f"[时间检查] {symbol} 夜盘品种在日盘时段 {current_time.strftime('%H:%M:%S')}, 允许交易")
  759. return True
  760. else:
  761. # 其他时段不允许交易
  762. log.info(f"[时间检查] {symbol} 夜盘品种在非交易时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
  763. return False
  764. else:
  765. # 配置为日盘开始时间
  766. if is_current_night:
  767. # 日盘品种在夜盘时段,不允许交易
  768. # log.debug(f"[时间检查] {symbol} 日盘品种在夜盘时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
  769. return False
  770. elif is_current_day:
  771. # 当前在日盘时段,比较时间
  772. result = current_time >= configured_start_time
  773. # log.debug(f"[时间检查] {symbol} 配置日盘开始 {trading_start_time}, 当前时间 {current_time.strftime('%H:%M:%S')}, 判断结果: {result}")
  774. return result
  775. else:
  776. # 其他时段(如午休、收盘后)不允许建仓,但允许止盈止损
  777. # log.debug(f"[时间检查] {symbol} 日盘品种在非开盘时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
  778. return False
  779. def get_margin_rate(underlying_symbol, direction, default_rate=0.10):
  780. """获取保证金比例的辅助函数"""
  781. return g.futures_config.get(underlying_symbol, {}).get('margin_rate', {}).get(direction, default_rate)
  782. def get_multiplier(underlying_symbol, default_multiplier=10):
  783. """获取合约乘数的辅助函数"""
  784. return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
  785. def after_market_close(context):
  786. """收盘后运行函数
  787. 使用get_trades()获取当日所有成交记录
  788. 使用get_open_orders()获取所有未成交订单
  789. """
  790. log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
  791. # 获取当日所有成交记录
  792. trades = get_trades()
  793. # 获取当日所有未完成订单
  794. open_orders = get_open_orders()
  795. # 打印交易汇总
  796. print_daily_trading_summary(context, trades, open_orders)
  797. log.info('##############################################################')
  798. def print_daily_trading_summary(context, trades, open_orders):
  799. """打印当日交易汇总
  800. Args:
  801. context: 上下文对象
  802. trades: 当日成交记录字典 {trade_id: UserTrade对象}
  803. open_orders: 未完成订单字典 {order_id: Order对象}
  804. 注:UserTrade对象只有基本属性(trade_id, order_id, time, amount, price)
  805. 需要通过order_id关联订单来获取完整信息
  806. """
  807. log.info("=" * 60)
  808. log.info("当日交易汇总")
  809. log.info("=" * 60)
  810. current_data = {}
  811. try:
  812. current_data = get_current_data()
  813. except Exception as e:
  814. log.warning(f"获取当前行情数据失败: {str(e)}")
  815. current_data = {}
  816. def format_datetime_value(dt_value):
  817. if hasattr(dt_value, 'to_pydatetime'):
  818. dt_value = dt_value.to_pydatetime()
  819. if isinstance(dt_value, datetime):
  820. return dt_value.strftime('%Y-%m-%d %H:%M:%S')
  821. if isinstance(dt_value, date):
  822. return datetime.combine(dt_value, datetime.min.time()).strftime('%Y-%m-%d %H:%M:%S')
  823. return str(dt_value) if dt_value is not None else ''
  824. # 统计成交记录
  825. if trades:
  826. log.info(f"\n【成交记录】共 {len(trades)} 笔")
  827. log.info("-" * 60)
  828. # 获取所有订单(包括已完成和未完成的)用于关联成交记录
  829. all_orders = get_orders()
  830. for trade_id, trade in trades.items():
  831. # 从trade获取基本信息
  832. security = None
  833. # 尝试通过order_id获取订单信息
  834. order_info = None
  835. if hasattr(trade, 'order_id') and trade.order_id in all_orders:
  836. order_info = all_orders[trade.order_id]
  837. security = order_info.security
  838. # 如果无法获取security,跳过这条记录
  839. trade_time = getattr(trade, 'time', None)
  840. trade_datetime_str = format_datetime_value(trade_time)
  841. if not security:
  842. log.info(f"成交记录 trade_id={trade_id}: {trade.amount}手 @{trade.price:.2f} "
  843. f"成交日期时间:{trade_datetime_str} (无法获取详细信息)")
  844. continue
  845. underlying_symbol = security.split('.')[0][:-4]
  846. dominant_code = None
  847. if hasattr(g, 'dominant_contracts'):
  848. dominant_code = g.dominant_contracts.get(underlying_symbol)
  849. display_symbol = dominant_code if dominant_code else security.split('.')[0]
  850. # 从订单获取action和side信息
  851. if order_info:
  852. action_str = "开仓" if order_info.action == "open" else "平仓"
  853. side_str = "多" if order_info.side == "long" else "空"
  854. # 获取策略信息
  855. strategy_str = ""
  856. if hasattr(g, 'order_strategy_info') and trade.order_id in g.order_strategy_info:
  857. strategy_info = g.order_strategy_info[trade.order_id]
  858. strategy_type = strategy_info.get('strategy_type', '未知')
  859. target_price = strategy_info.get('target_price', 0)
  860. strategy_str = f", 策略:{strategy_type}, 策略对应价格:{target_price}"
  861. # 获取当前价格
  862. try:
  863. current_price = current_data[security].last_price
  864. except Exception:
  865. current_price = 0
  866. log.info(f"{action_str}{side_str}: {display_symbol}, "
  867. f"数量:{trade.amount}手, 成交价:{trade.price:.2f}{strategy_str}, "
  868. f"标的价格:{current_price:.2f}, "
  869. f"成交日期时间:{trade_datetime_str}")
  870. else:
  871. log.info(f"成交: {underlying_symbol} {trade.amount}手 @{trade.price:.2f} "
  872. f"成交日期时间:{trade_datetime_str}")
  873. log.info("-" * 60)
  874. else:
  875. log.info("\n【成交记录】无")
  876. # 统计未完成订单
  877. if open_orders:
  878. log.info(f"\n【未完成订单】共 {len(open_orders)} 个")
  879. log.info("-" * 60)
  880. for order_id, order_obj in open_orders.items():
  881. dominant_code = None
  882. try:
  883. underlying_symbol = order_obj.security.split('.')[0][:-4]
  884. full_symbol = order_obj.security.split('.')[0]
  885. if hasattr(g, 'dominant_contracts'):
  886. dominant_code = g.dominant_contracts.get(underlying_symbol)
  887. except Exception:
  888. underlying_symbol = order_obj.security
  889. display_symbol = dominant_code if dominant_code else full_symbol
  890. action_str = "开仓" if order_obj.action == "open" else "平仓"
  891. side_str = "多" if order_obj.side == "long" else "空"
  892. # 判断订单类型
  893. order_type = "市价单"
  894. limit_price_str = ""
  895. if hasattr(order_obj, 'style') and order_obj.style:
  896. if hasattr(order_obj.style, 'limit_price') and order_obj.style.limit_price > 0:
  897. order_type = "限价单"
  898. limit_price_str = f" @{order_obj.style.limit_price:.2f}"
  899. log.info(f"{action_str}{side_str}: {display_symbol} "
  900. f"{order_obj.amount}手 {order_type}{limit_price_str} "
  901. f"状态:{order_obj.status}")
  902. log.info("-" * 60)
  903. else:
  904. log.info("\n【未完成订单】无")
  905. # 打印持仓信息
  906. log.info("\n【当前持仓】")
  907. log.info("-" * 60)
  908. subportfolio = context.subportfolios[0]
  909. holdings_logged = False
  910. for security, position in subportfolio.long_positions.items():
  911. if position.total_amount <= 0:
  912. continue
  913. underlying_symbol = security.split('.')[0][:-4]
  914. full_symbol = security.split('.')[0]
  915. dominant_code = None
  916. if hasattr(g, 'dominant_contracts'):
  917. dominant_code = g.dominant_contracts.get(underlying_symbol)
  918. display_symbol = dominant_code if dominant_code else full_symbol
  919. try:
  920. current_price = current_data[security].last_price
  921. except Exception:
  922. current_price = 0
  923. symbol_logged = False
  924. base_entries = g.base_positions.get(security, {}) if hasattr(g, 'base_positions') else {}
  925. for level_key, entry_info in sorted(base_entries.items()):
  926. quantity = entry_info.get('quantity', 0)
  927. if quantity <= 0:
  928. continue
  929. entry_price = float(entry_info.get('entry_price', 0))
  930. entry_date = entry_info.get('entry_date')
  931. # log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
  932. # f"策略:基础左侧, 策略对应价格:{level_key}, 当前标的价格:{current_price:.2f}, "
  933. # f"成交日期时间: {format_datetime_value(entry_date)}")
  934. symbol_logged = True
  935. holdings_logged = True
  936. grid_entries = g.grid_positions.get(security, []) if hasattr(g, 'grid_positions') else []
  937. for entry_info in sorted(grid_entries, key=lambda x: x.get('target_price', 0), reverse=True):
  938. quantity = entry_info.get('quantity', 0)
  939. if quantity <= 0:
  940. continue
  941. entry_price = float(entry_info.get('entry_price', 0))
  942. target_price = entry_info.get('target_price', 0)
  943. entry_date = entry_info.get('entry_date')
  944. # log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
  945. # f"策略:网格, 策略对应价格:{target_price}, 当前标的价格:{current_price:.2f}, "
  946. # f"成交日期时间: {format_datetime_value(entry_date)}")
  947. symbol_logged = True
  948. holdings_logged = True
  949. if not symbol_logged:
  950. # log.info(f"标的种类: {display_symbol}, 数量:{position.total_amount}手, 成交价:{position.avg_cost:.2f}, "
  951. # f"策略:未分类, 策略对应价格:--, 当前标的价格:{current_price:.2f}, 成交日期时间: --")
  952. holdings_logged = True
  953. for security, position in subportfolio.short_positions.items():
  954. if position.total_amount <= 0:
  955. continue
  956. underlying_symbol = security.split('.')[0][:-4]
  957. dominant_code = None
  958. if hasattr(g, 'dominant_contracts'):
  959. dominant_code = g.dominant_contracts.get(underlying_symbol)
  960. display_symbol = dominant_code if dominant_code else full_symbol
  961. try:
  962. current_price = current_data[security].last_price
  963. except Exception:
  964. current_price = 0
  965. symbol_logged = False
  966. hedge_entries = g.hedge_positions.get(security, {}) if hasattr(g, 'hedge_positions') else {}
  967. for level_key, entry_info in sorted(hedge_entries.items()):
  968. quantity = entry_info.get('quantity', 0)
  969. if quantity <= 0:
  970. continue
  971. entry_price = float(entry_info.get('entry_price', 0))
  972. entry_date = entry_info.get('entry_date')
  973. log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
  974. f"策略:对冲, 策略对应价格:{level_key}, 当前标的价格:{current_price:.2f}, "
  975. f"成交日期时间: {format_datetime_value(entry_date)}")
  976. symbol_logged = True
  977. holdings_logged = True
  978. if not symbol_logged:
  979. log.info(f"标的种类: {display_symbol}, 数量:{position.total_amount}手, 成交价:{position.avg_cost:.2f}, "
  980. f"策略:未分类(空), 策略对应价格:--, 当前标的价格:{current_price:.2f}, 成交日期时间: --")
  981. holdings_logged = True
  982. if not holdings_logged:
  983. log.info("无持仓")
  984. log.info("-" * 60)
  985. log.info(f"账户总资产: {context.portfolio.total_value:.2f}")
  986. log.info(f"可用资金: {context.portfolio.available_cash:.2f}")
  987. log.info("=" * 60)
  988. ########################## 自动移仓换月函数 #################################
  989. def position_auto_switch(context, pindex=0, switch_func=None, callback=None):
  990. """
  991. 期货自动移仓换月。默认使用市价单进行开平仓。
  992. """
  993. import re
  994. subportfolio = context.subportfolios[pindex]
  995. symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
  996. switch_result = []
  997. switch_failed = []
  998. for symbol in symbols:
  999. match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
  1000. if not match:
  1001. raise ValueError("未知期货标的: {}".format(symbol))
  1002. else:
  1003. underlying_symbol = match.groupdict()["underlying_symbol"]
  1004. # 时间检查:判断是否已达到该品种的交易开始时间
  1005. if not is_trading_time_reached(context, underlying_symbol):
  1006. log.info(f"换月操作跳过: {underlying_symbol} 未达到交易开始时间")
  1007. continue
  1008. dominant = get_dominant_future(underlying_symbol)
  1009. cur = get_current_data()
  1010. symbol_last_price = cur[symbol].last_price
  1011. dominant_last_price = cur[dominant].last_price
  1012. # log.debug(f'检查换月中,当前持仓合约: {symbol}, 当前主力合约: {dominant}')
  1013. if dominant > symbol:
  1014. for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
  1015. if symbol not in positions_.keys():
  1016. continue
  1017. else :
  1018. p = positions_[symbol]
  1019. if switch_func is not None:
  1020. switch_func(context, pindex, p, dominant)
  1021. else:
  1022. amount = p.total_amount
  1023. # 跌停不能开空和平多,涨停不能开多和平空。
  1024. if p.side == "long":
  1025. symbol_low_limit = cur[symbol].low_limit
  1026. dominant_high_limit = cur[dominant].high_limit
  1027. if symbol_last_price <= symbol_low_limit:
  1028. log.warning("标的{}跌停,无法平仓。移仓换月取消。".format(symbol))
  1029. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_close_failed"})
  1030. break
  1031. elif dominant_last_price >= dominant_high_limit:
  1032. log.warning("标的{}涨停,无法开仓。移仓换月取消。".format(dominant))
  1033. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_open_failed"})
  1034. break
  1035. else:
  1036. log.info("进行移仓换月: ({0},long) -> ({1},long)".format(symbol, dominant))
  1037. order_old = order_target(symbol, 0, side='long')
  1038. if order_old != None and order_old.filled > 0:
  1039. order_new = order_target(dominant, amount, side='long')
  1040. if order_new != None and order_new.filled > 0:
  1041. switch_result.append({"before": symbol, "after": dominant, "side": "long"})
  1042. # 换月成功,更新持仓记录
  1043. update_positions_after_switch(symbol, dominant, 'long')
  1044. else:
  1045. log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
  1046. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "open_order_failed"})
  1047. break
  1048. else:
  1049. log.info("标的{}平仓委托未成交,移仓换月失败,等待下次重试。".format(symbol))
  1050. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "close_order_not_filled"})
  1051. break
  1052. if switch_failed:
  1053. break
  1054. if p.side == "short":
  1055. symbol_high_limit = cur[symbol].high_limit
  1056. dominant_low_limit = cur[dominant].low_limit
  1057. if symbol_last_price >= symbol_high_limit:
  1058. log.warning("标的{}涨停,无法平仓。移仓换月取消。".format(symbol))
  1059. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_close_failed"})
  1060. break
  1061. elif dominant_last_price <= dominant_low_limit:
  1062. log.warning("标的{}跌停,无法开仓。移仓换月取消。".format(dominant))
  1063. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_open_failed"})
  1064. break
  1065. else:
  1066. log.info("进行移仓换月: ({0},short) -> ({1},short)".format(symbol, dominant))
  1067. order_old = order_target(symbol, 0, side='short')
  1068. if order_old != None and order_old.filled > 0:
  1069. order_new = order_target(dominant, amount, side='short')
  1070. if order_new != None and order_new.filled > 0:
  1071. switch_result.append({"before": symbol, "after": dominant, "side": "short"})
  1072. # 换月成功,更新持仓记录
  1073. update_positions_after_switch(symbol, dominant, 'short')
  1074. else:
  1075. log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
  1076. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "open_order_failed"})
  1077. break
  1078. else:
  1079. log.info("标的{}平仓委托未成交,移仓换月失败,等待下次重试。".format(symbol))
  1080. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "close_order_not_filled"})
  1081. break
  1082. if callback:
  1083. callback(context, pindex, p, dominant)
  1084. if switch_failed:
  1085. break
  1086. if switch_failed:
  1087. break
  1088. if switch_failed:
  1089. break
  1090. return switch_result, switch_failed
  1091. def update_positions_after_switch(old_symbol, new_symbol, side):
  1092. """合约切换后更新持仓记录"""
  1093. # 更新底仓持仓记录
  1094. if old_symbol in g.base_positions:
  1095. g.base_positions[new_symbol] = g.base_positions[old_symbol]
  1096. del g.base_positions[old_symbol]
  1097. log.info(f"底仓持仓记录更新: {old_symbol} -> {new_symbol}")
  1098. # 更新网格持仓记录
  1099. if old_symbol in g.grid_positions:
  1100. g.grid_positions[new_symbol] = g.grid_positions[old_symbol]
  1101. del g.grid_positions[old_symbol]
  1102. log.info(f"网格持仓记录更新: {old_symbol} -> {new_symbol}")
  1103. if old_symbol in g.grid_buy_levels:
  1104. g.grid_buy_levels[new_symbol] = g.grid_buy_levels[old_symbol]
  1105. del g.grid_buy_levels[old_symbol]
  1106. # 更新对冲持仓记录
  1107. if old_symbol in g.hedge_positions:
  1108. g.hedge_positions[new_symbol] = g.hedge_positions[old_symbol]
  1109. del g.hedge_positions[old_symbol]
  1110. log.info(f"对冲持仓记录更新: {old_symbol} -> {new_symbol}")
  1111. if old_symbol in g.hedge_state:
  1112. g.hedge_state[new_symbol] = g.hedge_state[old_symbol]
  1113. del g.hedge_state[old_symbol]