FutureLeftSide_v001.py 76 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556
  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': '09:00'}
  112. }
  113. # 策略品种选择配置
  114. g.strategy_focus_symbols = ['LH'] # 交易品种列表
  115. # ==================== 底仓左侧多头配置 ====================
  116. # 价格-数量网格:价格越低,买入手数越多
  117. g.base_position_grid = {
  118. 'TL': {118: 1, 117: 1, 116: 1, 115: 1, 114: 2, 113: 2},
  119. 'SA': {1400: 4, 1300: 6, 1200: 8, 1100: 12, 1000: 14, 900: 16},
  120. 'M': {2800: 4, 2750: 6, 2700: 8, 2650: 12, 2600: 14, 2550: 16},
  121. 'UR': {1750: 4, 1700: 6, 1650: 8, 1600: 12, 1550: 14, 1500: 16},
  122. 'LH': {13000: 1, 12500: 1, 12000: 1, 11500: 1, 11000: 2},
  123. }
  124. # 底仓退出价格(止盈)
  125. g.base_position_exit_price = {
  126. 'TL': 121,
  127. 'SA': 1550,
  128. 'M': 3800,
  129. 'UR': 2400,
  130. 'LH': 14600,
  131. }
  132. # ==================== 网格多头配置 ====================
  133. g.grid_config = {
  134. 'TL': {
  135. 'start_price': 118, # 网格开始价格
  136. 'grid_size': 1, # 网格大小
  137. 'quantity_per_grid': 1, # 每网格数量
  138. 'exit_grid_size': 1 # 退出网格大小
  139. },
  140. 'SA': {
  141. 'start_price': 1250, # 开始价格
  142. 'grid_size': 50, # 网格大小
  143. 'quantity_per_grid': 5, # 每网格数量
  144. 'exit_grid_size': 50 # 退出网格大小
  145. },
  146. 'M': {
  147. 'start_price': 2800,
  148. 'grid_size': 100,
  149. 'quantity_per_grid': 10,
  150. 'exit_grid_size': 100
  151. },
  152. 'UR': {
  153. 'start_price': 1800,
  154. 'grid_size': 50,
  155. 'quantity_per_grid': 10,
  156. 'exit_grid_size': 50
  157. },
  158. 'LH': {
  159. 'start_price': 13500,
  160. 'grid_size': 500,
  161. 'quantity_per_grid': 1,
  162. 'exit_grid_size': 500
  163. },
  164. }
  165. # ==================== 空头对冲配置 ====================
  166. g.hedge_stoploss_pct = 0.02 # 止损百分比(1%)
  167. g.hedge_level1_recovery_pct = 0.30 # 一级止盈回升百分比(30%)
  168. g.hedge_level2_recovery_pct = 0.60 # 二级止盈回升百分比(60%)
  169. g.hedge_cost_area_pct = 0.02 # 成本区域百分比(2%)
  170. # ==================== 全局变量初始化 ====================
  171. # 持仓跟踪
  172. g.base_positions = {} # 底仓持仓 {symbol: {level_key: {'entry_price': price, 'quantity': qty, 'entry_date': date}}}
  173. g.grid_positions = {} # 网格持仓 {symbol: [{'entry_price': price, 'quantity': qty, 'target_price': target_price, 'entry_date': date}]}
  174. g.hedge_positions = {} # 对冲持仓 {symbol: {level_key: {'entry_price': price, 'quantity': qty, 'entry_date': date}}}
  175. # 底仓待成交限价单跟踪(问题2修复)
  176. g.base_pending_orders = {} # {symbol: {level_key: {'order_id': xxx, 'price': xxx, 'quantity': xxx, 'submit_time': xxx}}}
  177. # 网格状态跟踪
  178. g.grid_buy_levels = {} # 网格已建仓目标价集合 {symbol: set()}
  179. # 对冲状态跟踪
  180. g.hedge_state = {} # {symbol: {'max_profit': 0, 'max_profit_price': 0, ...}}
  181. # 订单策略信息记录
  182. g.order_strategy_info = {} # {order_id: {'strategy_type': '策略类型', 'target_price': 价格}}
  183. # ==================== 定时任务设置 ====================
  184. # 日盘止损止盈检查(每个时间点)
  185. run_daily(check_open_positions, time='09:35:00', reference_security='IF1808.CCFX')
  186. run_daily(check_stop_profit_loss, time='10:05:00', reference_security='IF1808.CCFX')
  187. run_daily(check_stop_profit_loss, time='10:35:00', reference_security='IF1808.CCFX')
  188. run_daily(check_stop_profit_loss, time='11:05:00', reference_security='IF1808.CCFX')
  189. run_daily(check_stop_profit_loss, time='13:35:00', reference_security='IF1808.CCFX')
  190. run_daily(check_stop_profit_loss, time='14:05:00', reference_security='IF1808.CCFX')
  191. run_daily(check_stop_profit_loss, time='14:35:00', reference_security='IF1808.CCFX')
  192. run_daily(check_stop_profit_loss, time='14:55:00', reference_security='IF1808.CCFX')
  193. # 建仓逻辑(开盘时执行:夜盘21:05,日盘09:05)
  194. run_daily(check_open_positions, time='21:05:00', reference_security='IF1808.CCFX') # 夜盘品种
  195. run_daily(check_open_positions, time='09:05:00', reference_security='IF1808.CCFX') # 日盘品种
  196. # 夜盘止损止盈检查(仅对有夜盘的品种)
  197. run_daily(check_stop_profit_loss, time='21:05:00', reference_security='IF1808.CCFX')
  198. run_daily(check_stop_profit_loss, time='21:35:00', reference_security='IF1808.CCFX')
  199. run_daily(check_stop_profit_loss, time='22:05:00', reference_security='IF1808.CCFX')
  200. # 收盘后
  201. run_daily(after_market_close, time='15:30:00', reference_security='IF1808.CCFX')
  202. log.info("=" * 50)
  203. log.info("策略组件配置:")
  204. log.info(f" 底仓左侧多头: 始终启用")
  205. log.info(f" 网格多头策略: {'启用' if g.enable_grid_long else '禁用'}")
  206. log.info(f" 空头对冲策略: {'启用' if g.enable_hedge_short else '禁用'}")
  207. log.info(f"交易品种: {g.strategy_focus_symbols}")
  208. log.info("=" * 50)
  209. ############################ 主程序执行函数 ###################################
  210. def check_open_positions(context):
  211. """建仓检查函数(仅在14:35执行)"""
  212. log.info("=" * 60)
  213. log.info(f"执行建仓检查逻辑 - 时间: {context.current_dt}")
  214. log.info("=" * 60)
  215. # 先检查换月移仓
  216. switch_success, switch_failed = position_auto_switch(context)
  217. if switch_failed:
  218. log.info("检测到移仓换月失败,跳过后续建仓检查")
  219. for failed in switch_failed:
  220. log.info(f"换月失败详情: {failed}")
  221. return
  222. # 获取当前时间
  223. current_time = str(context.current_dt.time())[:2]
  224. # 筛选可交易品种(根据交易开始时间判断是否为夜盘品种)
  225. tradable_symbols = []
  226. for symbol in g.strategy_focus_symbols:
  227. is_night_symbol = is_night_session_symbol(symbol)
  228. if current_time in ('21', '22'):
  229. # 夜盘时间:只考虑夜盘品种
  230. if is_night_symbol:
  231. tradable_symbols.append(symbol)
  232. else:
  233. # 日盘时间:所有品种都可以交易
  234. tradable_symbols.append(symbol)
  235. if not tradable_symbols:
  236. log.info("当前时段无可交易品种")
  237. return
  238. log.info(f"可交易品种: {tradable_symbols}")
  239. # 对每个品种执行建仓检查
  240. for symbol in tradable_symbols:
  241. try:
  242. # 获取主力合约
  243. dominant_future = get_dominant_future(symbol)
  244. if not dominant_future:
  245. log.warning(f"{symbol} 未找到主力合约")
  246. continue
  247. # 获取当前价格
  248. current_data = get_current_data()[dominant_future]
  249. current_price = current_data.last_price
  250. log.info(f"建仓检查 - 品种: {symbol}, 主力合约: {dominant_future}, 当前价格: {current_price}")
  251. # 策略组件1: 底仓左侧多头建仓(始终启用)
  252. check_base_position_open(context, symbol, dominant_future, current_price)
  253. # 策略组件2: 网格多头建仓(可配置)
  254. if g.enable_grid_long:
  255. check_grid_trading_open(context, symbol, dominant_future, current_price)
  256. # 策略组件3: 空头对冲建仓(可配置)
  257. if g.enable_hedge_short:
  258. check_hedge_position_open(context, symbol, dominant_future, current_price)
  259. except Exception as e:
  260. log.warning(f"建仓检查处理{symbol}时出错: {str(e)}")
  261. continue
  262. def check_stop_profit_loss(context):
  263. """止损止盈检查函数(所有时间点执行)"""
  264. # log.info("=" * 60)
  265. # log.info(f"执行止损止盈检查 - 时间: {context.current_dt}")
  266. # log.info("=" * 60)
  267. # 先检查换月移仓
  268. switch_success, switch_failed = position_auto_switch(context)
  269. if switch_failed:
  270. log.info("检测到移仓换月失败,跳过后续止盈止损检查")
  271. for failed in switch_failed:
  272. log.info(f"换月失败详情: {failed}")
  273. return
  274. # 获取当前时间
  275. current_dt = context.current_dt
  276. current_time = current_dt.time()
  277. current_time_str = current_dt.strftime("%H:%M:%S")
  278. in_night_session = current_time >= time(21, 0) or current_time < time(2, 30)
  279. in_day_session = time(9, 0) <= current_time <= time(15, 0)
  280. # 筛选可交易品种(根据交易开始时间判断是否为夜盘品种)
  281. tradable_symbols = []
  282. for symbol in g.strategy_focus_symbols:
  283. is_night_symbol = is_night_session_symbol(symbol)
  284. if not is_night_symbol:
  285. if in_night_session:
  286. log.info(f"{symbol} 为日盘合约,当前夜间时段 {current_time_str} 跳过止损止盈检查")
  287. continue
  288. if not in_day_session:
  289. log.info(f"{symbol} 为日盘合约,当前非交易时段 {current_time_str} 跳过止损止盈检查")
  290. continue
  291. else:
  292. if in_night_session:
  293. log.info(f"{symbol} 夜盘合约在夜间时段 {current_time_str} 执行止损止盈检查")
  294. elif not in_day_session:
  295. log.info(f"{symbol} 夜盘合约当前非活跃时段 {current_time_str} 仍执行止损止盈检查以覆盖全天风险")
  296. tradable_symbols.append(symbol)
  297. if not tradable_symbols:
  298. return
  299. # 对每个品种执行止损止盈检查
  300. for symbol in tradable_symbols:
  301. try:
  302. # 获取主力合约
  303. dominant_future = get_dominant_future(symbol)
  304. if not dominant_future:
  305. continue
  306. # 获取当前价格
  307. current_data = get_current_data()[dominant_future]
  308. current_price = current_data.last_price
  309. # 策略组件1: 底仓左侧多头止盈(始终启用)
  310. check_base_position_close(context, symbol, dominant_future, current_price)
  311. # 策略组件2: 网格多头止盈(可配置)
  312. if g.enable_grid_long:
  313. check_grid_trading_close(context, symbol, dominant_future, current_price)
  314. check_grid_trading_open(context, symbol, dominant_future, current_price)
  315. # 策略组件3: 空头对冲止损止盈(可配置)
  316. if g.enable_hedge_short:
  317. check_hedge_position_close(context, symbol, dominant_future, current_price)
  318. check_hedge_position_open(context, symbol, dominant_future, current_price)
  319. except Exception as e:
  320. log.warning(f"止损止盈检查处理{symbol}时出错: {str(e)}")
  321. continue
  322. ############################ 策略组件1: 底仓左侧多头 ###################################
  323. def update_base_pending_orders(context, symbol, dominant_future):
  324. """更新底仓待成交限价单状态
  325. 检查待成交限价单的状态:
  326. - 如果订单已完全成交,移到 g.base_positions
  327. - 如果订单被取消或失败,从 g.base_pending_orders 中删除
  328. - 如果订单部分成交或仍在挂单,保持在 g.base_pending_orders 中
  329. Args:
  330. context: 上下文对象
  331. symbol: 品种代码
  332. dominant_future: 主力合约
  333. """
  334. if dominant_future not in g.base_pending_orders:
  335. return
  336. if not g.base_pending_orders[dominant_future]:
  337. return
  338. # 获取所有订单
  339. try:
  340. all_orders = get_orders()
  341. except Exception as e:
  342. log.warning(f"更新待成交限价单失败: 无法获取订单信息 - {str(e)}")
  343. return
  344. levels_to_remove = []
  345. levels_to_add_to_positions = []
  346. for level_key, pending_info in g.base_pending_orders[dominant_future].items():
  347. order_id = pending_info.get('order_id')
  348. if not order_id or order_id not in all_orders:
  349. # 订单不存在,可能已被系统清理,从待成交列表中移除
  350. log.info(f"底仓限价单 {level_key} 订单 {order_id} 不存在于订单列表,移除待成交记录")
  351. levels_to_remove.append(level_key)
  352. continue
  353. order_obj = all_orders[order_id]
  354. status = str(order_obj.status).lower()
  355. filled_amount = getattr(order_obj, 'filled', 0) or 0
  356. order_amount = getattr(order_obj, 'amount', 0) or 0
  357. # 检查订单状态
  358. if status in ('filled', 'held'):
  359. # 订单已完全成交
  360. actual_entry_price = pending_info['price'] # 限价单成交价等于限价
  361. levels_to_add_to_positions.append((level_key, {
  362. 'entry_price': actual_entry_price,
  363. 'quantity': pending_info['quantity'],
  364. 'entry_date': pending_info['submit_time']
  365. }))
  366. levels_to_remove.append(level_key)
  367. log.info(f"底仓限价单 {level_key} 已成交,订单 {order_id},移至持仓记录")
  368. elif status in ('canceled', 'rejected'):
  369. # 订单已取消或被拒绝
  370. levels_to_remove.append(level_key)
  371. log.info(f"底仓限价单 {level_key} 已{status},订单 {order_id},从待成交列表移除")
  372. elif status in ('open', 'pending', 'new', 'part_filled'):
  373. # 订单仍在等待成交或部分成交,保持在待成交列表
  374. log.info(f"底仓限价单 {level_key} 状态 {status},已成交 {filled_amount}/{order_amount} 手,继续等待")
  375. else:
  376. # 未知状态,记录日志
  377. log.warning(f"底仓限价单 {level_key} 订单 {order_id} 状态未知: {status}")
  378. # 将已成交的订单移到持仓记录
  379. for level_key, position_info in levels_to_add_to_positions:
  380. g.base_positions[dominant_future][level_key] = position_info
  381. # 从待成交列表中移除
  382. for level_key in levels_to_remove:
  383. del g.base_pending_orders[dominant_future][level_key]
  384. def check_base_position_open(context, symbol, dominant_future, current_price):
  385. """检查底仓左侧多头建仓
  386. 逻辑:
  387. 1. 遍历所有未建仓的价格档位
  388. 2. 如果档位价格 >= 当前价格:使用市价单立即成交
  389. 3. 如果档位价格 < 当前价格:记录到待挂限价单列表
  390. 4. 从待挂限价单列表中选择最高的两个档位挂限价单
  391. """
  392. # 时间检查:判断是否已达到该品种的交易开始时间
  393. if not is_trading_time_reached(context, symbol):
  394. log.info(f"底仓建仓跳过: {symbol} 未达到交易开始时间")
  395. return
  396. # 检查是否有底仓配置
  397. if symbol not in g.base_position_grid:
  398. return
  399. grid = g.base_position_grid[symbol]
  400. # 初始化持仓记录
  401. if dominant_future not in g.base_positions:
  402. g.base_positions[dominant_future] = {}
  403. # 初始化待成交限价单记录(问题2修复)
  404. if dominant_future not in g.base_pending_orders:
  405. g.base_pending_orders[dominant_future] = {}
  406. # 问题2修复:检查待成交限价单的状态,将已成交的订单移到持仓记录
  407. update_base_pending_orders(context, symbol, dominant_future)
  408. log.info(f"底仓建仓检查: {symbol} 当前价格 {current_price}")
  409. log.info(f"底仓持仓: {g.base_positions[dominant_future]}")
  410. log.info(f"底仓待成交限价单: {g.base_pending_orders[dominant_future]}")
  411. # 收集未建仓的档位
  412. market_order_levels = [] # 需要市价单的档位
  413. limit_order_levels = [] # 需要限价单的档位
  414. for price_level, quantity in sorted(grid.items(), reverse=True):
  415. level_key = f"{price_level}"
  416. # 跳过已建仓的档位
  417. if level_key in g.base_positions[dominant_future]:
  418. continue
  419. # 问题2修复:跳过已有待成交限价单的档位
  420. if level_key in g.base_pending_orders[dominant_future]:
  421. log.info(f" 档位 {price_level} 已有待成交限价单,跳过")
  422. continue
  423. # 问题1修复:过滤价格过低的档位,避免无效挂单
  424. # 当档位价格 < 当前价格的95%时,跳过该档位(相当于当前价格 > 档位价格 * 1.05)
  425. if price_level < current_price * 0.95:
  426. log.info(f" 档位 {price_level} 过低(< 当前价 {current_price} * 0.95 = {current_price * 0.95:.2f}),跳过挂单")
  427. continue
  428. # 判断使用市价单还是限价单
  429. if price_level >= current_price:
  430. # 档位价格 >= 当前价格,使用市价单
  431. market_order_levels.append((price_level, quantity))
  432. log.info(f" 档位 {price_level}({quantity}手)>= 当前价 {current_price},将使用市价单")
  433. else:
  434. # 档位价格 < 当前价格,加入限价单候选
  435. limit_order_levels.append((price_level, quantity))
  436. # 执行市价单
  437. for price_level, quantity in market_order_levels:
  438. level_key = f"{price_level}"
  439. strategy_info = {'strategy_type': '基础左侧', 'target_price': price_level}
  440. _, success = open_position(context, dominant_future, quantity, 'long',
  441. f'底仓市价建仓(目标档位{price_level})',
  442. strategy_info=strategy_info)
  443. if success:
  444. # 获取实际成交价格(从订单记录中获取)
  445. actual_price = current_price # 市价单按当前价记录
  446. g.base_positions[dominant_future][level_key] = {
  447. 'entry_price': actual_price,
  448. 'quantity': quantity,
  449. 'entry_date': context.current_dt
  450. }
  451. log.info(f"底仓市价单成交: {symbol} 目标档位 {price_level}, 实际价格 {actual_price}, 数量 {quantity}")
  452. # 执行限价单(固定挂两个档位)
  453. if limit_order_levels:
  454. # 选择价格最高的两个档位
  455. top_two_levels = sorted(limit_order_levels, reverse=True)[:2]
  456. log.info(f" 准备挂限价单,候选档位: {[p for p, q in limit_order_levels]}, 选择最高两个: {[p for p, q in top_two_levels]}")
  457. for price_level, quantity in top_two_levels:
  458. level_key = f"{price_level}"
  459. strategy_info = {'strategy_type': '基础左侧', 'target_price': price_level}
  460. order_id, success = open_position(context, dominant_future, quantity, 'long',
  461. f'底仓限价建仓@{price_level}',
  462. limit_price=price_level,
  463. strategy_info=strategy_info)
  464. if success:
  465. # 问题2修复:限价单提交成功后,记录到待成交列表,而不是直接记录到持仓
  466. g.base_pending_orders[dominant_future][level_key] = {
  467. 'order_id': order_id,
  468. 'price': price_level,
  469. 'quantity': quantity,
  470. 'submit_time': context.current_dt
  471. }
  472. log.info(f"底仓限价单挂单: {symbol} 档位 {price_level}, 数量 {quantity}, order_id={order_id}, 待成交确认")
  473. def check_base_position_close(context, symbol, dominant_future, current_price):
  474. """检查底仓左侧多头平仓(止盈)"""
  475. # 时间检查:判断是否已达到该品种的交易开始时间
  476. if not is_trading_time_reached(context, symbol):
  477. log.info(f"底仓止盈跳过: {symbol} 未达到交易开始时间")
  478. return
  479. # 检查是否有底仓配置
  480. if symbol not in g.base_position_exit_price:
  481. return
  482. exit_price = g.base_position_exit_price.get(symbol)
  483. # 检查退出条件(止盈)
  484. if exit_price and current_price >= exit_price:
  485. # 检查是否有持仓需要平仓
  486. subportfolio = context.subportfolios[0]
  487. if dominant_future in subportfolio.long_positions:
  488. position = subportfolio.long_positions[dominant_future]
  489. if position.total_amount > 0:
  490. log.info(f"底仓触发止盈: {symbol} 当前价格 {current_price} >= 退出价格 {exit_price}")
  491. close_position(context, dominant_future, position.total_amount, 'long', '底仓止盈')
  492. if dominant_future in g.base_positions:
  493. g.base_positions[dominant_future] = {}
  494. return
  495. ############################ 策略组件2: 网格多头策略 ###################################
  496. def check_grid_trading_open(context, symbol, dominant_future, current_price):
  497. """检查网格多头建仓
  498. 逻辑:
  499. 1. 找出所有应该买入但未买入的网格层级
  500. 2. 对于每个层级的目标价格:
  501. - 如果目标价格 >= 当前价格:使用市价单
  502. - 如果目标价格 < 当前价格:记录到待挂限价单列表
  503. 3. 从待挂限价单列表中选择价格最高的两个层级挂限价单
  504. """
  505. # 时间检查:判断是否已达到该品种的交易开始时间
  506. if not is_trading_time_reached(context, symbol):
  507. log.info(f"网格建仓跳过: {symbol} 未达到交易开始时间")
  508. return
  509. # 检查是否有网格配置
  510. if symbol not in g.grid_config:
  511. return
  512. config = g.grid_config[symbol]
  513. start_price = config['start_price']
  514. grid_size = config['grid_size']
  515. quantity_per_grid = config['quantity_per_grid']
  516. # 初始化网格持仓记录
  517. if dominant_future not in g.grid_positions:
  518. g.grid_positions[dominant_future] = []
  519. if dominant_future not in g.grid_buy_levels:
  520. g.grid_buy_levels[dominant_future] = set()
  521. # 只在价格低于起始价格时才执行网格交易
  522. if current_price >= start_price:
  523. return
  524. log.info(f"网格建仓检查: {symbol} 当前价格 {current_price}, 起始价格 {start_price}")
  525. # 计算当前价格对应的最低网格档位数量
  526. max_grid_index = int((start_price - current_price) / grid_size)
  527. # 收集需要建仓的网格档位
  528. market_order_grids = [] # 需要市价单的网格
  529. limit_order_grids = [] # 需要限价单的网格
  530. # 遍历所有应该触发的网格档位(从0到max_grid_index)
  531. for grid_index in range(max_grid_index + 1):
  532. # 计算该档位的目标买入价格
  533. target_price = start_price - grid_index * grid_size
  534. # 跳过已经买入的目标价
  535. if target_price in g.grid_buy_levels[dominant_future]:
  536. continue
  537. grid_info = (grid_index, target_price)
  538. # 判断使用市价单还是限价单
  539. if target_price >= current_price:
  540. # 目标价格 >= 当前价格,使用市价单
  541. market_order_grids.append(grid_info)
  542. log.info(f" 网格目标价 {target_price} >= 当前价 {current_price},将使用市价单 (档位索引 {grid_index})")
  543. else:
  544. # 目标价格 < 当前价格,加入限价单候选
  545. limit_order_grids.append(grid_info)
  546. # 执行市价单
  547. for _, target_price in market_order_grids:
  548. strategy_info = {'strategy_type': '网格', 'target_price': target_price}
  549. _, success = open_position(context, dominant_future, quantity_per_grid, 'long',
  550. f'网格市价买入(目标价{target_price})',
  551. strategy_info=strategy_info)
  552. if success:
  553. # 市价单按实际成交价记录(这里用当前价近似)
  554. g.grid_positions[dominant_future].append({
  555. 'entry_price': current_price,
  556. 'quantity': quantity_per_grid,
  557. 'target_price': target_price,
  558. 'entry_date': context.current_dt
  559. })
  560. g.grid_buy_levels[dominant_future].add(target_price)
  561. log.info(f"网格市价单成交: {symbol} 目标价 {target_price}, 实际价 {current_price}, 数量 {quantity_per_grid}")
  562. # 执行限价单(固定挂两个层级)
  563. if limit_order_grids:
  564. # 选择价格最高的两个目标价(档位数越小价格越高)
  565. top_two_grids = sorted(limit_order_grids, key=lambda x: x[1], reverse=True)[:2]
  566. log.info(f" 准备挂限价单,候选目标价: {[(l, p) for l, p in limit_order_grids]}, 选择最高两个: {[(l, p) for l, p in top_two_grids]}")
  567. for _, target_price in top_two_grids:
  568. strategy_info = {'strategy_type': '网格', 'target_price': target_price}
  569. _, success = open_position(context, dominant_future, quantity_per_grid, 'long',
  570. f'网格限价买入@目标价{target_price}',
  571. limit_price=target_price,
  572. strategy_info=strategy_info)
  573. if success:
  574. g.grid_positions[dominant_future].append({
  575. 'entry_price': target_price, # 限价单按委托价记录
  576. 'quantity': quantity_per_grid,
  577. 'target_price': target_price,
  578. 'entry_date': context.current_dt
  579. })
  580. g.grid_buy_levels[dominant_future].add(target_price)
  581. log.info(f"网格限价单挂单: {symbol} 目标价 {target_price}, 数量 {quantity_per_grid}")
  582. def check_grid_trading_close(context, symbol, dominant_future, current_price):
  583. """检查网格多头平仓(止盈)"""
  584. # 时间检查:判断是否已达到该品种的交易开始时间
  585. if not is_trading_time_reached(context, symbol):
  586. log.info(f"网格止盈跳过: {symbol} 未达到交易开始时间")
  587. return
  588. # 检查是否有网格配置
  589. if symbol not in g.grid_config:
  590. return
  591. config = g.grid_config[symbol]
  592. exit_grid_size = config['exit_grid_size']
  593. # 初始化网格持仓记录
  594. if dominant_future not in g.grid_positions:
  595. g.grid_positions[dominant_future] = []
  596. if dominant_future not in g.grid_buy_levels:
  597. g.grid_buy_levels[dominant_future] = set()
  598. # 检查卖出条件:对每个持仓检查是否达到退出条件
  599. positions_to_remove = []
  600. for i, position in enumerate(g.grid_positions[dominant_future]):
  601. target_price = position['target_price']
  602. exit_price = target_price + exit_grid_size
  603. if current_price >= exit_price:
  604. # 执行网格卖出
  605. success = close_position(context, dominant_future, position['quantity'], 'long',
  606. f'网格卖出@目标价{target_price}')
  607. if success:
  608. profit_per_unit = current_price - position['entry_price']
  609. log.info(f"网格卖出: {symbol} 目标价 {target_price}, 计划退出价 {exit_price}, "
  610. f"买入价 {position['entry_price']}, 卖出价 {current_price}, "
  611. f"盈利 {profit_per_unit * position['quantity'] * get_multiplier(symbol)}")
  612. positions_to_remove.append(i)
  613. # 移除该网格目标价标记,允许重新买入
  614. if target_price in g.grid_buy_levels[dominant_future]:
  615. g.grid_buy_levels[dominant_future].remove(target_price)
  616. # 移除已平仓的网格持仓记录
  617. for i in reversed(positions_to_remove):
  618. g.grid_positions[dominant_future].pop(i)
  619. ############################ 策略组件3: 空头对冲策略 ###################################
  620. def check_hedge_position_open(context, symbol, dominant_future, current_price):
  621. """检查空头对冲建仓
  622. 对冲逻辑:
  623. 1. 对冲数量只对应基础左侧持仓,不包括网格持仓
  624. 2. 对冲永远使用限价单,价格为各档位的目标价格
  625. 3. 针对每个基础左侧档位分别建立对应的空头对冲单
  626. 4. **关键约束**:只对今天开立的左侧持仓建立对冲
  627. """
  628. # 时间检查:判断是否已达到该品种的交易开始时间
  629. if not is_trading_time_reached(context, symbol):
  630. log.info(f"对冲建仓跳过: {symbol} 未达到交易开始时间")
  631. return
  632. # 初始化对冲状态
  633. if dominant_future not in g.hedge_state:
  634. g.hedge_state[dominant_future] = {
  635. 'max_profit': 0,
  636. 'max_profit_price': 0,
  637. 'profit_stage': 0 # 0: 未盈利, 1: 一级盈利, 2: 二级盈利
  638. }
  639. # 初始化对冲持仓记录
  640. if dominant_future not in g.hedge_positions:
  641. g.hedge_positions[dominant_future] = {}
  642. # 检查是否有底仓持仓(作为建立对冲的条件)
  643. if dominant_future not in g.base_positions or not g.base_positions[dominant_future]:
  644. log.info(f"对冲建仓检查: {symbol} 当前无基础左侧持仓,无需对冲")
  645. return
  646. # **关键检查**:筛选今天开立的左侧持仓
  647. today_date = context.current_dt.date()
  648. today_opened_positions = {}
  649. for level_key, position_info in g.base_positions[dominant_future].items():
  650. entry_date = position_info.get('entry_date')
  651. if entry_date is None:
  652. log.warning(f"对冲建仓检查: {symbol} 档位 {level_key} 缺少entry_date,跳过")
  653. continue
  654. # 将entry_date转换为date对象进行比较
  655. if hasattr(entry_date, 'date'):
  656. entry_date_obj = entry_date.date()
  657. elif hasattr(entry_date, 'to_pydatetime'):
  658. entry_date_obj = entry_date.to_pydatetime().date()
  659. else:
  660. entry_date_obj = entry_date
  661. # 只记录今天开立的持仓
  662. if entry_date_obj == today_date:
  663. today_opened_positions[level_key] = position_info
  664. log.info(f"对冲建仓检查: {symbol} 档位 {level_key} 今天开立,可建立对冲")
  665. else:
  666. log.info(f"对冲建仓检查: {symbol} 档位 {level_key} 开立日期 {entry_date_obj} ≠ 今天 {today_date},跳过对冲")
  667. # **关键判断**:如果今天没有开立任何左侧持仓,完全跳过对冲逻辑
  668. if not today_opened_positions:
  669. # log.info(f"对冲建仓检查: {symbol} 今天未开立任何左侧持仓,跳过所有对冲建仓逻辑")
  670. return
  671. log.info(f"对冲建仓检查: {symbol} 当前价格 {current_price}, 今天开立的左侧持仓档位: {list(today_opened_positions.keys())}")
  672. # 收集对应基础左侧订单的成交情况,仅在订单成交后才建立对冲
  673. level_fill_status = {}
  674. order_strategy_info_map = getattr(g, 'order_strategy_info', {}) if hasattr(g, 'order_strategy_info') else {}
  675. try:
  676. all_orders = get_orders()
  677. except Exception as e:
  678. log.warning(f"对冲建仓检查: {symbol} 获取订单信息失败,等待下次检查。错误: {str(e)}")
  679. all_orders = {}
  680. if all_orders:
  681. for order_id, order_obj in all_orders.items():
  682. if order_id not in order_strategy_info_map:
  683. continue
  684. strategy_info = order_strategy_info_map[order_id]
  685. if strategy_info.get('strategy_type') != '基础左侧':
  686. continue
  687. if getattr(order_obj, 'security', None) != dominant_future:
  688. continue
  689. target_price_key = f"{strategy_info.get('target_price')}"
  690. if target_price_key not in today_opened_positions:
  691. continue
  692. status_value = getattr(order_obj, 'status', '')
  693. status_str = str(status_value)
  694. status_lower = status_str.lower()
  695. filled_amount = getattr(order_obj, 'filled', 0) or 0
  696. order_amount = getattr(order_obj, 'amount', 0) or 0
  697. info = level_fill_status.setdefault(target_price_key, {
  698. 'filled': 0,
  699. 'amount': 0,
  700. 'pending': False,
  701. 'waiting': False,
  702. 'statuses': set()
  703. })
  704. info['filled'] += filled_amount
  705. info['amount'] += order_amount
  706. info['statuses'].add(status_str)
  707. if status_lower in ('open', 'part_filled', 'pending', 'submitted'):
  708. info['pending'] = True
  709. if status_lower in ('open', 'submitted') and filled_amount == 0:
  710. info['waiting'] = True
  711. # 只针对今天开立的基础左侧档位建立对应的对冲单
  712. for level_key, position_info in today_opened_positions.items():
  713. # 跳过已经建立对冲的档位
  714. if level_key in g.hedge_positions[dominant_future]:
  715. log.info(f" 对冲档位 {level_key}: 已存在对冲持仓,跳过")
  716. continue
  717. fill_info = level_fill_status.get(level_key)
  718. if not fill_info:
  719. log.info(f" 对冲档位 {level_key}: 底仓订单未找到成交记录,等待成交后再建立对冲")
  720. continue
  721. required_quantity = position_info['quantity']
  722. filled_quantity = fill_info.get('filled', 0)
  723. status_list = sorted(fill_info.get('statuses'))
  724. if fill_info.get('waiting', False):
  725. log.info(f" 对冲档位 {level_key}: 底仓限价单仍在挂单等待,状态集合 {status_list}")
  726. continue
  727. if filled_quantity < required_quantity:
  728. # log.info(f" 对冲档位 {level_key}: 底仓订单成交量不足 (成交 {filled_quantity} 手 / 目标 {required_quantity} 手,状态集合 {status_list}),跳过对冲")
  729. continue
  730. target_price = level_key # 目标价格本质就是对冲档位
  731. quantity = position_info['quantity']
  732. log.info(f" 对冲档位 {level_key}: 目标价格 {target_price}, 数量 {quantity}手(对应今天开立的左侧持仓)")
  733. # 建立空头对冲(永远使用限价单,价格为档位目标价)
  734. strategy_info = {'strategy_type': '对冲', 'target_price': target_price}
  735. _, success = open_position(context, dominant_future, quantity, 'short',
  736. f'建立空头对冲@{target_price}',
  737. limit_price=target_price,
  738. strategy_info=strategy_info)
  739. if success:
  740. try:
  741. entry_price_value = float(target_price)
  742. except (TypeError, ValueError):
  743. log.error(f"对冲建仓记录失败: 档位 {level_key} 目标价无法转换为浮点数 -> {target_price}")
  744. entry_price_value = target_price
  745. g.hedge_positions[dominant_future][level_key] = {
  746. 'entry_price': entry_price_value,
  747. 'quantity': quantity,
  748. 'entry_date': context.current_dt
  749. }
  750. log.info(f"对冲限价单挂单: {symbol} 档位 {level_key}, 价格 {target_price}, 数量 {quantity}手")
  751. def check_hedge_position_close(context, symbol, dominant_future, current_price):
  752. """检查空头对冲平仓(止损止盈)
  753. 对冲平仓逻辑:
  754. 1. 分别检查每个档位的对冲持仓
  755. 2. 每个档位独立计算盈亏和止损止盈条件
  756. """
  757. # 时间检查:判断是否已达到该品种的交易开始时间
  758. if not is_trading_time_reached(context, symbol):
  759. log.info(f"对冲止损止盈跳过: {symbol} 未达到交易开始时间")
  760. return
  761. # 检查是否已有对冲持仓记录
  762. if dominant_future not in g.hedge_positions or not g.hedge_positions[dominant_future]:
  763. return
  764. # 检查实际空头持仓
  765. subportfolio = context.subportfolios[0]
  766. has_hedge_position = dominant_future in subportfolio.short_positions and \
  767. subportfolio.short_positions[dominant_future].total_amount > 0
  768. if not has_hedge_position:
  769. return
  770. # 问题1修复:获取当前日期,用于过滤当日新开仓的对冲头寸
  771. today_date = context.current_dt.date()
  772. # 遍历每个档位的对冲持仓
  773. levels_to_remove = []
  774. for level_key, hedge_info in g.hedge_positions[dominant_future].items():
  775. # 问题1修复:检查对冲头寸的开仓日期
  776. entry_date = hedge_info.get('entry_date')
  777. if entry_date is not None:
  778. # 将entry_date转换为date对象进行比较
  779. if hasattr(entry_date, 'date'):
  780. entry_date_obj = entry_date.date()
  781. elif hasattr(entry_date, 'to_pydatetime'):
  782. entry_date_obj = entry_date.to_pydatetime().date()
  783. else:
  784. entry_date_obj = entry_date
  785. # 如果是当日新开仓的对冲头寸,跳过止损止盈检查
  786. if entry_date_obj == today_date:
  787. log.info(f"对冲止损止盈检查: {symbol} 档位 {level_key} 于今日开仓,跳过当日止损止盈检查")
  788. continue
  789. try:
  790. entry_price = float(hedge_info['entry_price'])
  791. except (TypeError, ValueError):
  792. log.error(f"对冲止损止盈计算失败: 档位 {level_key} 入场价无法转换为浮点数 -> {hedge_info.get('entry_price')}")
  793. continue
  794. quantity = hedge_info['quantity']
  795. # 计算当前盈亏率(空头:入场价格 > 当前价格为盈利)
  796. profit_rate = (entry_price - current_price) / entry_price
  797. # 更新最大盈利
  798. if profit_rate > g.hedge_state[dominant_future]['max_profit']:
  799. g.hedge_state[dominant_future]['max_profit'] = profit_rate
  800. g.hedge_state[dominant_future]['max_profit_price'] = current_price
  801. # 更新盈利阶段
  802. if profit_rate >= g.hedge_level2_recovery_pct:
  803. g.hedge_state[dominant_future]['profit_stage'] = 2
  804. elif profit_rate >= g.hedge_level1_recovery_pct:
  805. g.hedge_state[dominant_future]['profit_stage'] = 1
  806. # 检查止损条件
  807. if profit_rate <= -g.hedge_stoploss_pct:
  808. log.info(f"对冲触发止损: {symbol} 档位 {level_key}, 入场价 {entry_price}, 亏损率 {profit_rate:.2%}")
  809. close_position(context, dominant_future, quantity, 'short', f'对冲止损@档位{level_key}')
  810. levels_to_remove.append(level_key)
  811. continue
  812. # 检查成本区域止盈条件
  813. if abs(profit_rate) <= g.hedge_cost_area_pct:
  814. profit_stage = g.hedge_state[dominant_future]['profit_stage']
  815. if profit_stage >= 1: # 曾经达到过一级或二级盈利
  816. log.info(f"对冲触发成本区域止盈: {symbol} 档位 {level_key}, 当前盈利 {profit_rate:.2%} "
  817. f"在成本区域内,曾达到盈利阶段 {profit_stage}")
  818. close_position(context, dominant_future, quantity, 'short', f'对冲成本区域止盈@档位{level_key}')
  819. levels_to_remove.append(level_key)
  820. continue
  821. # 移除已平仓的档位
  822. for level_key in levels_to_remove:
  823. del g.hedge_positions[dominant_future][level_key]
  824. # 如果所有对冲持仓都已平仓,重置对冲状态
  825. if not g.hedge_positions[dominant_future]:
  826. g.hedge_state[dominant_future] = {'max_profit': 0, 'max_profit_price': 0, 'profit_stage': 0}
  827. ############################ 交易执行函数 ###################################
  828. def open_position(context, security, quantity, direction, reason='', limit_price=None, strategy_info=None):
  829. """开仓
  830. Args:
  831. context: 上下文对象
  832. security: 合约代码
  833. quantity: 开仓数量
  834. direction: 方向 ('long' 或 'short')
  835. reason: 开仓原因
  836. limit_price: 限价单价格,None表示使用市价单
  837. strategy_info: 策略信息字典 {'strategy_type': '策略类型', 'target_price': 目标价格}
  838. Returns:
  839. tuple: (order_id, success) - 订单ID和是否成功
  840. 注:订单提交后filled=0,实际成交信息需要在收盘后通过get_trades()获取
  841. """
  842. try:
  843. order_type = "限价单" if limit_price is not None else "市价单"
  844. price_info = f"@{limit_price}" if limit_price is not None else ""
  845. log.info(f"提交开仓订单: {security} {direction} {quantity}手 {order_type}{price_info} - {reason}")
  846. # 使用order按手数开仓(避免order_target取消之前的未完成订单)
  847. # 根据是否有限价选择订单类型
  848. if limit_price is not None:
  849. order_obj = order(security, quantity, LimitOrderStyle(limit_price), side=direction)
  850. else:
  851. order_obj = order(security, quantity, side=direction)
  852. if order_obj is not None:
  853. log.info(f"订单已提交: order_id={order_obj.order_id}, 状态={order_obj.status}, "
  854. f"已成交={order_obj.filled}/{quantity}手")
  855. # 保存策略信息到订单记录中,用于收盘后的汇总显示
  856. if strategy_info:
  857. if not hasattr(g, 'order_strategy_info'):
  858. g.order_strategy_info = {}
  859. g.order_strategy_info[order_obj.order_id] = strategy_info
  860. log.info(f"订单策略信息已记录: {strategy_info}")
  861. return order_obj.order_id, True
  862. else:
  863. log.warning(f"订单提交失败: {security} {direction} {quantity}手")
  864. return None, False
  865. except Exception as e:
  866. log.warning(f"开仓异常 {security}: {str(e)}")
  867. return None, False
  868. def close_position(context, security, quantity, direction, reason=''):
  869. """平仓
  870. 注:平仓使用order_target是安全的,因为通常不会有多个平仓订单同时进行
  871. 订单提交后filled=0,实际成交信息需要在收盘后通过get_trades()获取
  872. """
  873. try:
  874. log.info(f"提交平仓订单: {security} {direction} {quantity}手 - {reason}")
  875. # 使用order_target平仓
  876. subportfolio = context.subportfolios[0]
  877. current_position = 0
  878. if direction == 'long' and security in subportfolio.long_positions:
  879. current_position = subportfolio.long_positions[security].total_amount
  880. elif direction == 'short' and security in subportfolio.short_positions:
  881. current_position = subportfolio.short_positions[security].total_amount
  882. target_quantity = max(0, current_position - quantity)
  883. order_obj = order_target(security, target_quantity, side=direction)
  884. if order_obj is not None:
  885. log.info(f"平仓订单已提交: order_id={order_obj.order_id}, 状态={order_obj.status}, "
  886. f"已成交={order_obj.filled}/{quantity}手")
  887. return True
  888. else:
  889. log.warning(f"平仓订单提交失败: {security} {direction} {quantity}手")
  890. return False
  891. except Exception as e:
  892. log.warning(f"平仓异常 {security}: {str(e)}")
  893. return False
  894. ############################ 辅助函数 ###################################
  895. def get_futures_config(underlying_symbol, config_key=None, default_value=None):
  896. """获取期货品种配置信息的辅助函数"""
  897. if underlying_symbol not in g.futures_config:
  898. if config_key and default_value is not None:
  899. return default_value
  900. return {}
  901. if config_key is None:
  902. return g.futures_config[underlying_symbol]
  903. return g.futures_config[underlying_symbol].get(config_key, default_value)
  904. def is_night_session_symbol(symbol):
  905. """判断品种是否为夜盘品种
  906. Args:
  907. symbol: 期货品种代码(如'IF'、'AU'等)
  908. Returns:
  909. bool: True表示夜盘品种,False表示日盘品种
  910. """
  911. trading_start_time = get_futures_config(symbol, 'trading_start_time', '09:00')
  912. # 解析配置的时间
  913. try:
  914. start_hour, _ = map(int, trading_start_time.split(':'))
  915. # 如果开始时间 >= 21:00,则为夜盘品种
  916. return start_hour >= 21
  917. except (ValueError, AttributeError):
  918. log.warning(f"{symbol} 交易开始时间配置格式错误: {trading_start_time},默认判断为日盘品种")
  919. return False
  920. def is_trading_time_reached(context, symbol):
  921. """判断当前时间是否已达到该品种的交易开始时间
  922. Args:
  923. context: 上下文对象
  924. symbol: 期货品种代码(如'IF'、'AU'等)
  925. Returns:
  926. bool: True表示已达到交易时间,False表示未达到
  927. """
  928. # 获取配置的交易开始时间
  929. trading_start_time = get_futures_config(symbol, 'trading_start_time', '09:05')
  930. # 解析配置的时间(格式为 "HH:MM")
  931. try:
  932. start_hour, start_minute = map(int, trading_start_time.split(':'))
  933. configured_start_time = time(start_hour, start_minute)
  934. except (ValueError, AttributeError):
  935. log.warning(f"{symbol} 交易开始时间配置格式错误: {trading_start_time},默认使用09:05")
  936. configured_start_time = time(9, 5)
  937. # 获取当前时间
  938. current_time = context.current_dt.time()
  939. # 判断逻辑:
  940. # 1. 如果配置时间是夜盘时间(>=21:00),且当前时间也在夜盘时段(>=21:00 或 <03:00),直接比较
  941. # 2. 如果配置时间是日盘时间(<21:00),且当前时间也在日盘时段(09:00-15:00),直接比较
  942. # 3. 其他情况返回False(例如日盘品种在夜盘时段)
  943. is_night_start = configured_start_time >= time(21, 0)
  944. is_current_night = current_time >= time(21, 0) or current_time < time(3, 0)
  945. is_current_day = time(9, 0) <= current_time <= time(15, 0)
  946. if is_night_start:
  947. # 配置为夜盘开始时间
  948. if is_current_night:
  949. # 当前也在夜盘时段
  950. if current_time >= time(21, 0):
  951. # 当前在21:00之后
  952. result = current_time >= configured_start_time
  953. else:
  954. # 当前在凌晨(已经过了配置的开始时间)
  955. result = True
  956. # log.debug(f"[时间检查] {symbol} 配置夜盘开始 {trading_start_time}, 当前时间 {current_time.strftime('%H:%M:%S')}, 判断结果: {result}")
  957. return result
  958. elif is_current_day:
  959. # 夜盘品种在日盘时段,允许交易
  960. # log.debug(f"[时间检查] {symbol} 夜盘品种在日盘时段 {current_time.strftime('%H:%M:%S')}, 允许交易")
  961. return True
  962. else:
  963. # 其他时段不允许交易
  964. # log.debug(f"[时间检查] {symbol} 夜盘品种在非交易时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
  965. return False
  966. else:
  967. # 配置为日盘开始时间
  968. if is_current_night:
  969. # 日盘品种在夜盘时段,不允许交易
  970. # log.debug(f"[时间检查] {symbol} 日盘品种在夜盘时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
  971. return False
  972. elif is_current_day:
  973. # 当前在日盘时段,比较时间
  974. result = current_time >= configured_start_time
  975. # log.debug(f"[时间检查] {symbol} 配置日盘开始 {trading_start_time}, 当前时间 {current_time.strftime('%H:%M:%S')}, 判断结果: {result}")
  976. return result
  977. else:
  978. # 其他时段(如午休、收盘后)不允许建仓,但允许止盈止损
  979. # log.debug(f"[时间检查] {symbol} 日盘品种在非开盘时段 {current_time.strftime('%H:%M:%S')}, 不允许交易")
  980. return False
  981. def get_margin_rate(underlying_symbol, direction, default_rate=0.10):
  982. """获取保证金比例的辅助函数"""
  983. return g.futures_config.get(underlying_symbol, {}).get('margin_rate', {}).get(direction, default_rate)
  984. def get_multiplier(underlying_symbol, default_multiplier=10):
  985. """获取合约乘数的辅助函数"""
  986. return g.futures_config.get(underlying_symbol, {}).get('multiplier', default_multiplier)
  987. def after_market_close(context):
  988. """收盘后运行函数
  989. 使用get_trades()获取当日所有成交记录
  990. 使用get_open_orders()获取所有未成交订单
  991. """
  992. log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
  993. # 获取当日所有成交记录
  994. trades = get_trades()
  995. # 获取当日所有未完成订单
  996. open_orders = get_open_orders()
  997. # 打印交易汇总
  998. print_daily_trading_summary(context, trades, open_orders)
  999. log.info('##############################################################')
  1000. def print_daily_trading_summary(context, trades, open_orders):
  1001. """打印当日交易汇总
  1002. Args:
  1003. context: 上下文对象
  1004. trades: 当日成交记录字典 {trade_id: UserTrade对象}
  1005. open_orders: 未完成订单字典 {order_id: Order对象}
  1006. 注:UserTrade对象只有基本属性(trade_id, order_id, time, amount, price)
  1007. 需要通过order_id关联订单来获取完整信息
  1008. """
  1009. log.info("=" * 60)
  1010. log.info("当日交易汇总")
  1011. log.info("=" * 60)
  1012. current_data = {}
  1013. try:
  1014. current_data = get_current_data()
  1015. except Exception as e:
  1016. log.warning(f"获取当前行情数据失败: {str(e)}")
  1017. current_data = {}
  1018. def format_datetime_value(dt_value):
  1019. if hasattr(dt_value, 'to_pydatetime'):
  1020. dt_value = dt_value.to_pydatetime()
  1021. if isinstance(dt_value, datetime):
  1022. return dt_value.strftime('%Y-%m-%d %H:%M:%S')
  1023. if isinstance(dt_value, date):
  1024. return datetime.combine(dt_value, datetime.min.time()).strftime('%Y-%m-%d %H:%M:%S')
  1025. return str(dt_value) if dt_value is not None else ''
  1026. # 统计成交记录
  1027. if trades:
  1028. log.info(f"\n【成交记录】共 {len(trades)} 笔")
  1029. log.info("-" * 60)
  1030. # 获取所有订单(包括已完成和未完成的)用于关联成交记录
  1031. all_orders = get_orders()
  1032. for trade_id, trade in trades.items():
  1033. # 从trade获取基本信息
  1034. security = None
  1035. # 尝试通过order_id获取订单信息
  1036. order_info = None
  1037. if hasattr(trade, 'order_id') and trade.order_id in all_orders:
  1038. order_info = all_orders[trade.order_id]
  1039. security = order_info.security
  1040. # 如果无法获取security,跳过这条记录
  1041. trade_time = getattr(trade, 'time', None)
  1042. trade_datetime_str = format_datetime_value(trade_time)
  1043. if not security:
  1044. log.info(f"成交记录 trade_id={trade_id}: {trade.amount}手 @{trade.price:.2f} "
  1045. f"成交日期时间:{trade_datetime_str} (无法获取详细信息)")
  1046. continue
  1047. underlying_symbol = security.split('.')[0][:-4]
  1048. dominant_code = None
  1049. if hasattr(g, 'dominant_contracts'):
  1050. dominant_code = g.dominant_contracts.get(underlying_symbol)
  1051. display_symbol = dominant_code if dominant_code else security.split('.')[0]
  1052. # 从订单获取action和side信息
  1053. if order_info:
  1054. action_str = "开仓" if order_info.action == "open" else "平仓"
  1055. side_str = "多" if order_info.side == "long" else "空"
  1056. # 获取策略信息
  1057. strategy_str = ""
  1058. if hasattr(g, 'order_strategy_info') and trade.order_id in g.order_strategy_info:
  1059. strategy_info = g.order_strategy_info[trade.order_id]
  1060. strategy_type = strategy_info.get('strategy_type', '未知')
  1061. target_price = strategy_info.get('target_price', 0)
  1062. strategy_str = f", 策略:{strategy_type}, 策略对应价格:{target_price}"
  1063. # 获取当前价格
  1064. try:
  1065. current_price = current_data[security].last_price
  1066. except Exception:
  1067. current_price = 0
  1068. log.info(f"{action_str}{side_str}: {display_symbol}, "
  1069. f"数量:{trade.amount}手, 成交价:{trade.price:.2f}{strategy_str}, "
  1070. f"标的价格:{current_price:.2f}, "
  1071. f"成交日期时间:{trade_datetime_str}")
  1072. else:
  1073. log.info(f"成交: {underlying_symbol} {trade.amount}手 @{trade.price:.2f} "
  1074. f"成交日期时间:{trade_datetime_str}")
  1075. log.info("-" * 60)
  1076. else:
  1077. log.info("\n【成交记录】无")
  1078. # 统计未完成订单
  1079. if open_orders:
  1080. log.info(f"\n【未完成订单】共 {len(open_orders)} 个")
  1081. log.info("-" * 60)
  1082. for order_id, order_obj in open_orders.items():
  1083. dominant_code = None
  1084. try:
  1085. underlying_symbol = order_obj.security.split('.')[0][:-4]
  1086. full_symbol = order_obj.security.split('.')[0]
  1087. if hasattr(g, 'dominant_contracts'):
  1088. dominant_code = g.dominant_contracts.get(underlying_symbol)
  1089. except Exception:
  1090. underlying_symbol = order_obj.security
  1091. display_symbol = dominant_code if dominant_code else full_symbol
  1092. action_str = "开仓" if order_obj.action == "open" else "平仓"
  1093. side_str = "多" if order_obj.side == "long" else "空"
  1094. # 判断订单类型
  1095. order_type = "市价单"
  1096. limit_price_str = ""
  1097. if hasattr(order_obj, 'style') and order_obj.style:
  1098. if hasattr(order_obj.style, 'limit_price') and order_obj.style.limit_price > 0:
  1099. order_type = "限价单"
  1100. limit_price_str = f" @{order_obj.style.limit_price:.2f}"
  1101. log.info(f"{action_str}{side_str}: {display_symbol} "
  1102. f"{order_obj.amount}手 {order_type}{limit_price_str} "
  1103. f"状态:{order_obj.status}")
  1104. log.info("-" * 60)
  1105. else:
  1106. log.info("\n【未完成订单】无")
  1107. # 打印持仓信息
  1108. log.info("\n【当前持仓】")
  1109. log.info("-" * 60)
  1110. subportfolio = context.subportfolios[0]
  1111. holdings_logged = False
  1112. for security, position in subportfolio.long_positions.items():
  1113. if position.total_amount <= 0:
  1114. continue
  1115. underlying_symbol = security.split('.')[0][:-4]
  1116. full_symbol = security.split('.')[0]
  1117. dominant_code = None
  1118. if hasattr(g, 'dominant_contracts'):
  1119. dominant_code = g.dominant_contracts.get(underlying_symbol)
  1120. display_symbol = dominant_code if dominant_code else full_symbol
  1121. try:
  1122. current_price = current_data[security].last_price
  1123. except Exception:
  1124. current_price = 0
  1125. symbol_logged = False
  1126. base_entries = g.base_positions.get(security, {}) if hasattr(g, 'base_positions') else {}
  1127. for level_key, entry_info in sorted(base_entries.items()):
  1128. quantity = entry_info.get('quantity', 0)
  1129. if quantity <= 0:
  1130. continue
  1131. entry_price = float(entry_info.get('entry_price', 0))
  1132. entry_date = entry_info.get('entry_date')
  1133. log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
  1134. f"策略:基础左侧, 策略对应价格:{level_key}, 当前标的价格:{current_price:.2f}, "
  1135. f"成交日期时间: {format_datetime_value(entry_date)}")
  1136. symbol_logged = True
  1137. holdings_logged = True
  1138. grid_entries = g.grid_positions.get(security, []) if hasattr(g, 'grid_positions') else []
  1139. for entry_info in sorted(grid_entries, key=lambda x: x.get('target_price', 0), reverse=True):
  1140. quantity = entry_info.get('quantity', 0)
  1141. if quantity <= 0:
  1142. continue
  1143. entry_price = float(entry_info.get('entry_price', 0))
  1144. target_price = entry_info.get('target_price', 0)
  1145. entry_date = entry_info.get('entry_date')
  1146. log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
  1147. f"策略:网格, 策略对应价格:{target_price}, 当前标的价格:{current_price:.2f}, "
  1148. f"成交日期时间: {format_datetime_value(entry_date)}")
  1149. symbol_logged = True
  1150. holdings_logged = True
  1151. if not symbol_logged:
  1152. log.info(f"标的种类: {display_symbol}, 数量:{position.total_amount}手, 成交价:{position.avg_cost:.2f}, "
  1153. f"策略:未分类, 策略对应价格:--, 当前标的价格:{current_price:.2f}, 成交日期时间: --")
  1154. holdings_logged = True
  1155. for security, position in subportfolio.short_positions.items():
  1156. if position.total_amount <= 0:
  1157. continue
  1158. underlying_symbol = security.split('.')[0][:-4]
  1159. dominant_code = None
  1160. if hasattr(g, 'dominant_contracts'):
  1161. dominant_code = g.dominant_contracts.get(underlying_symbol)
  1162. display_symbol = dominant_code if dominant_code else full_symbol
  1163. try:
  1164. current_price = current_data[security].last_price
  1165. except Exception:
  1166. current_price = 0
  1167. symbol_logged = False
  1168. hedge_entries = g.hedge_positions.get(security, {}) if hasattr(g, 'hedge_positions') else {}
  1169. for level_key, entry_info in sorted(hedge_entries.items()):
  1170. quantity = entry_info.get('quantity', 0)
  1171. if quantity <= 0:
  1172. continue
  1173. entry_price = float(entry_info.get('entry_price', 0))
  1174. entry_date = entry_info.get('entry_date')
  1175. log.info(f"标的种类: {display_symbol}, 数量:{quantity}手, 成交价:{entry_price:.2f}, "
  1176. f"策略:对冲, 策略对应价格:{level_key}, 当前标的价格:{current_price:.2f}, "
  1177. f"成交日期时间: {format_datetime_value(entry_date)}")
  1178. symbol_logged = True
  1179. holdings_logged = True
  1180. if not symbol_logged:
  1181. log.info(f"标的种类: {display_symbol}, 数量:{position.total_amount}手, 成交价:{position.avg_cost:.2f}, "
  1182. f"策略:未分类(空), 策略对应价格:--, 当前标的价格:{current_price:.2f}, 成交日期时间: --")
  1183. holdings_logged = True
  1184. if not holdings_logged:
  1185. log.info("无持仓")
  1186. log.info("-" * 60)
  1187. log.info(f"账户总资产: {context.portfolio.total_value:.2f}")
  1188. log.info(f"可用资金: {context.portfolio.available_cash:.2f}")
  1189. log.info("=" * 60)
  1190. ########################## 自动移仓换月函数 #################################
  1191. def position_auto_switch(context, pindex=0, switch_func=None, callback=None):
  1192. """
  1193. 期货自动移仓换月。默认使用市价单进行开平仓。
  1194. """
  1195. import re
  1196. subportfolio = context.subportfolios[pindex]
  1197. symbols = set(subportfolio.long_positions.keys()) | set(subportfolio.short_positions.keys())
  1198. switch_result = []
  1199. switch_failed = []
  1200. for symbol in symbols:
  1201. match = re.match(r"(?P<underlying_symbol>[A-Z]{1,})", symbol)
  1202. if not match:
  1203. raise ValueError("未知期货标的: {}".format(symbol))
  1204. else:
  1205. underlying_symbol = match.groupdict()["underlying_symbol"]
  1206. # 时间检查:判断是否已达到该品种的交易开始时间
  1207. if not is_trading_time_reached(context, underlying_symbol):
  1208. log.info(f"换月操作跳过: {underlying_symbol} 未达到交易开始时间")
  1209. continue
  1210. dominant = get_dominant_future(underlying_symbol)
  1211. cur = get_current_data()
  1212. symbol_last_price = cur[symbol].last_price
  1213. dominant_last_price = cur[dominant].last_price
  1214. # log.debug(f'检查换月中,当前持仓合约: {symbol}, 当前主力合约: {dominant}')
  1215. if dominant > symbol:
  1216. for positions_ in (subportfolio.long_positions, subportfolio.short_positions):
  1217. if symbol not in positions_.keys():
  1218. continue
  1219. else :
  1220. p = positions_[symbol]
  1221. if switch_func is not None:
  1222. switch_func(context, pindex, p, dominant)
  1223. else:
  1224. amount = p.total_amount
  1225. # 跌停不能开空和平多,涨停不能开多和平空。
  1226. if p.side == "long":
  1227. symbol_low_limit = cur[symbol].low_limit
  1228. dominant_high_limit = cur[dominant].high_limit
  1229. if symbol_last_price <= symbol_low_limit:
  1230. log.warning("标的{}跌停,无法平仓。移仓换月取消。".format(symbol))
  1231. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_close_failed"})
  1232. break
  1233. elif dominant_last_price >= dominant_high_limit:
  1234. log.warning("标的{}涨停,无法开仓。移仓换月取消。".format(dominant))
  1235. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_open_failed"})
  1236. break
  1237. else:
  1238. log.info("进行移仓换月: ({0},long) -> ({1},long)".format(symbol, dominant))
  1239. order_old = order_target(symbol, 0, side='long')
  1240. if order_old != None and order_old.filled > 0:
  1241. order_new = order_target(dominant, amount, side='long')
  1242. if order_new != None and order_new.filled > 0:
  1243. switch_result.append({"before": symbol, "after": dominant, "side": "long"})
  1244. # 换月成功,更新持仓记录
  1245. update_positions_after_switch(symbol, dominant, 'long')
  1246. else:
  1247. log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
  1248. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "open_order_failed"})
  1249. break
  1250. else:
  1251. log.info("标的{}平仓委托未成交,移仓换月失败,等待下次重试。".format(symbol))
  1252. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "close_order_not_filled"})
  1253. break
  1254. if switch_failed:
  1255. break
  1256. if p.side == "short":
  1257. symbol_high_limit = cur[symbol].high_limit
  1258. dominant_low_limit = cur[dominant].low_limit
  1259. if symbol_last_price >= symbol_high_limit:
  1260. log.warning("标的{}涨停,无法平仓。移仓换月取消。".format(symbol))
  1261. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_close_failed"})
  1262. break
  1263. elif dominant_last_price <= dominant_low_limit:
  1264. log.warning("标的{}跌停,无法开仓。移仓换月取消。".format(dominant))
  1265. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "limit_open_failed"})
  1266. break
  1267. else:
  1268. log.info("进行移仓换月: ({0},short) -> ({1},short)".format(symbol, dominant))
  1269. order_old = order_target(symbol, 0, side='short')
  1270. if order_old != None and order_old.filled > 0:
  1271. order_new = order_target(dominant, amount, side='short')
  1272. if order_new != None and order_new.filled > 0:
  1273. switch_result.append({"before": symbol, "after": dominant, "side": "short"})
  1274. # 换月成功,更新持仓记录
  1275. update_positions_after_switch(symbol, dominant, 'short')
  1276. else:
  1277. log.warning("标的{}交易失败,无法开仓。移仓换月失败。".format(dominant))
  1278. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "open_order_failed"})
  1279. break
  1280. else:
  1281. log.info("标的{}平仓委托未成交,移仓换月失败,等待下次重试。".format(symbol))
  1282. switch_failed.append({"underlying": match.groupdict()["underlying_symbol"], "before": symbol, "after": dominant, "side": p.side, "reason": "close_order_not_filled"})
  1283. break
  1284. if callback:
  1285. callback(context, pindex, p, dominant)
  1286. if switch_failed:
  1287. break
  1288. if switch_failed:
  1289. break
  1290. if switch_failed:
  1291. break
  1292. return switch_result, switch_failed
  1293. def update_positions_after_switch(old_symbol, new_symbol, side):
  1294. """合约切换后更新持仓记录"""
  1295. # 更新底仓持仓记录
  1296. if old_symbol in g.base_positions:
  1297. g.base_positions[new_symbol] = g.base_positions[old_symbol]
  1298. del g.base_positions[old_symbol]
  1299. log.info(f"底仓持仓记录更新: {old_symbol} -> {new_symbol}")
  1300. # 更新底仓待成交限价单记录(问题2修复)
  1301. if old_symbol in g.base_pending_orders:
  1302. g.base_pending_orders[new_symbol] = g.base_pending_orders[old_symbol]
  1303. del g.base_pending_orders[old_symbol]
  1304. log.info(f"底仓待成交限价单记录更新: {old_symbol} -> {new_symbol}")
  1305. # 更新网格持仓记录
  1306. if old_symbol in g.grid_positions:
  1307. g.grid_positions[new_symbol] = g.grid_positions[old_symbol]
  1308. del g.grid_positions[old_symbol]
  1309. log.info(f"网格持仓记录更新: {old_symbol} -> {new_symbol}")
  1310. if old_symbol in g.grid_buy_levels:
  1311. g.grid_buy_levels[new_symbol] = g.grid_buy_levels[old_symbol]
  1312. del g.grid_buy_levels[old_symbol]
  1313. # 更新对冲持仓记录
  1314. if old_symbol in g.hedge_positions:
  1315. g.hedge_positions[new_symbol] = g.hedge_positions[old_symbol]
  1316. del g.hedge_positions[old_symbol]
  1317. log.info(f"对冲持仓记录更新: {old_symbol} -> {new_symbol}")
  1318. if old_symbol in g.hedge_state:
  1319. g.hedge_state[new_symbol] = g.hedge_state[old_symbol]
  1320. del g.hedge_state[old_symbol]