future_grid_trading_analysis.py 157 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213
  1. """
  2. 期货网格交易研究分析工具(带主力合约切换)
  3. 研究期货网格交易策略在不同配置下的表现,支持多种交易场景的对比分析
  4. 核心功能包括主力合约自动切换、强制平仓和重新建仓逻辑
  5. 本程序实现完整的网格交易分析流程:
  6. 1. 主力合约监控与切换 - 自动检测主力合约变化并处理切换
  7. 2. 合约选择逻辑 - 基于主力合约选择算法
  8. 3. 基础头寸交易 - 价格-数量网格配置,支持合约切换时重新建仓
  9. 4. 网格交易策略 - 限价订单网格买入卖出,合约切换时根据价格条件重新建仓
  10. 5. 网格对冲策略 - 带止损的增强网格交易,合约切换时根据价格条件重新建仓
  11. 6. 移动平均线过滤器 - 基于MA趋势过滤对冲入场,避免强下跌趋势中的对冲
  12. 7. 统计分析对比 - 四种交易场景性能分析
  13. 主要特点:
  14. - 主力合约自动监控:每日检测主力合约变化
  15. - 强制平仓机制:合约切换时立即平掉旧合约所有头寸
  16. - 智能重新建仓:根据价格条件在新合约中重新建立头寸
  17. - MA趋势过滤:检测强下跌趋势(30K<20K<10K<5K或30K<20K<5K<10K),阻止对冲入场
  18. - 智能数据扩展:当MA计算所需数据不足时,自动扩展历史数据范围并缓存
  19. - 标准期货盈亏计算:使用正确的合约倍数和期货盈亏公式
  20. - 最终持仓结算:分析期结束时对所有未平仓头寸进行市值计价
  21. - 完整交易记录:记录所有交易包括合约切换引起的强制平仓
  22. 期货盈亏计算公式:
  23. - 多头:(出场价格 - 入场价格) × 合约倍数 × 数量
  24. - 空头:(入场价格 - 出场价格) × 合约倍数 × 数量
  25. 注:程序支持多个核心商品同时分析,生成详细的交易记录和统计报告
  26. 作者: jukuan研究团队
  27. 日期: 2025-09
  28. 适用平台: 聚宽在线研究平台
  29. """
  30. import pandas as pd
  31. import numpy as np
  32. from jqdata import *
  33. import datetime
  34. import warnings
  35. warnings.filterwarnings('ignore')
  36. # =====================================================================================
  37. # 分析配置参数 - 集中配置部分
  38. # =====================================================================================
  39. class GridTradingConfig:
  40. """期货网格交易分析配置参数"""
  41. # ==================== 时间范围设置 ====================
  42. START_DATE = datetime.datetime(2024, 11, 7) # 分析开始日期
  43. END_DATE = datetime.datetime(2025, 9, 19) # 分析结束日期(修正为9月19日避免数据缺失)
  44. # ==================== 期货合约倍数配置 ====================
  45. FUTURES_MULTIPLIER = {
  46. # 贵金属
  47. 'AU': 1000, # 黄金
  48. 'AG': 15, # 白银
  49. # 有色金属
  50. 'CU': 5, 'AL': 5, 'ZN': 5, 'PB': 5, 'NI': 1, 'SN': 1, 'SS': 5,
  51. # 黑色系
  52. 'RB': 10, 'HC': 10, 'I': 100, 'JM': 100, 'J': 60,
  53. # 能源化工
  54. 'SP': 10, 'FU': 10, 'BU': 10, 'RU': 10, 'BR': 5, 'SC': 1000,
  55. 'NR': 10, 'LU': 10, 'LC': 1,
  56. # 化工
  57. 'FG': 20, 'TA': 5, 'MA': 10, 'SA': 20, 'L': 5, 'V': 5, 'EG': 10,
  58. 'PP': 5, 'EB': 5, 'PG': 20, 'UR': 20,
  59. # 农产品
  60. 'RM': 10, 'OI': 10, 'CF': 5, 'SR': 10, 'PF': 5, 'C': 10, 'CS': 10,
  61. 'CY': 5, 'A': 10, 'B': 10, 'M': 10, 'Y': 10, 'P': 10,
  62. # 股指期货
  63. 'IF': 300, 'IH': 300, 'IC': 200, 'IM': 200, 'TL': 10000,
  64. # 其他
  65. 'AP': 10, 'CJ': 5, 'PK': 5, 'JD': 10, 'LH': 16
  66. }
  67. # ==================== 核心商品配置 ====================
  68. CORE_COMMODITIES = {
  69. # 'SA': ['SA2501.XZCE', 'SA2505.XZCE', 'SA2509.XZCE', 'SA2601.XZCE'], # 纯碱
  70. # 'M': ['M2501.XDCE', 'M2505.XDCE', 'M2509.XDCE', 'M2605.XDCE'], # 豆粕
  71. 'UR': ['UR2501.XZCE', 'UR2505.XZCE', 'UR2509.XZCE', 'UR2601.XZCE'], # 尿素
  72. # 'LH': ['LH2501.XDCE', 'LH2505.XDCE', 'LH2509.XDCE', 'LH2601.XDCE'], # 生猪
  73. # 'TL': ['TL2503.CCFX', 'TL2506.CCFX', 'TL2509.CCFX', 'TL2512.CCFX'] # 30年期国债
  74. }
  75. # ==================== 合约切换配置 ====================
  76. REQUIRED_TRADING_DAYS = 30 # 合约切换前需要的最少有效交易日数
  77. # ==================== 基础头寸交易配置 ====================
  78. BASE_POSITION_GRID = {
  79. 'SA': {1400: 4, 1300: 6, 1200: 8, 1100: 12, 1000: 14, 900: 16},
  80. 'M': {2800: 4, 2750: 6, 2700: 8, 2650: 12, 2600: 14, 2550: 16},
  81. 'UR': {1750: 4, 1700: 6, 1650: 8, 1600: 12, 1550: 14, 1500: 16},
  82. 'LH': {13000: 1, 12500: 1, 12000: 1, 11500: 1, 11000: 2},
  83. 'TL': {118: 1, 117: 1, 116: 1, 115: 1, 114: 2, 113: 2},
  84. }
  85. # 统一退出价格(无止损)
  86. BASE_POSITION_EXIT_PRICE = {
  87. 'SA': 1500,
  88. 'M': 3800,
  89. 'UR': 2400,
  90. 'LH': 20000,
  91. 'TL': 121,
  92. }
  93. # ==================== 网格交易配置 ====================
  94. GRID_TRADING_CONFIG = {
  95. 'SA': {
  96. 'start_price': 1250, # 开始价格
  97. 'grid_size': 50, # 网格大小
  98. 'quantity_per_grid': 5, # 每网格数量
  99. 'exit_grid_size': 50, # 退出网格大小
  100. 'hedge_stop_price': 1100 # 对冲停止价格(低于此价格不触发对冲)
  101. },
  102. 'M': {
  103. 'start_price': 2800,
  104. 'grid_size': 100,
  105. 'quantity_per_grid': 10,
  106. 'exit_grid_size': 100,
  107. 'hedge_stop_price': 2400
  108. },
  109. 'UR': {
  110. 'start_price': 1800,
  111. 'grid_size': 50,
  112. 'quantity_per_grid': 10,
  113. 'exit_grid_size': 50,
  114. 'hedge_stop_price': 1600
  115. },
  116. 'LH': {
  117. 'start_price': 13500,
  118. 'grid_size': 500,
  119. 'quantity_per_grid': 1,
  120. 'exit_grid_size': 500,
  121. 'hedge_stop_price': 12000
  122. },
  123. 'TL': {
  124. 'start_price': 118,
  125. 'grid_size': 1,
  126. 'quantity_per_grid': 1,
  127. 'exit_grid_size': 1,
  128. 'hedge_stop_price': 113 # 对冲停止价格(低于113不触发对冲)
  129. },
  130. }
  131. # ==================== 网格对冲策略配置 ====================
  132. GRID_HEDGE_CONFIG = {
  133. 'stop_loss_percentage': 0.01, # 2%止损百分比
  134. 'enable_hedge_strategy': True, # 是否启用网格对冲策略
  135. 'use_ma_filter': True # 是否启用移动平均线过滤器(阻止特定趋势下的对冲入场)
  136. }
  137. # ==================== 输出设置 ====================
  138. OUTPUT_ENCODING = 'utf-8-sig' # 输出文件编码格式
  139. VERBOSE_LOGGING = True # 是否打印详细日志
  140. @classmethod
  141. def print_config(cls):
  142. """打印当前配置信息"""
  143. print("=== 期货网格交易分析配置 ===")
  144. print(f"分析时间范围: {cls.START_DATE.strftime('%Y-%m-%d')} 至 {cls.END_DATE.strftime('%Y-%m-%d')}")
  145. print(f"核心商品数量: {len(cls.CORE_COMMODITIES)}")
  146. print("核心商品列表:")
  147. for commodity, contracts in cls.CORE_COMMODITIES.items():
  148. print(f" {commodity}: {contracts}")
  149. print(f"\n网格交易配置:")
  150. for commodity, config in cls.GRID_TRADING_CONFIG.items():
  151. hedge_stop = config.get('hedge_stop_price', '未设置')
  152. print(f" {commodity}: 起始价{config['start_price']}, 网格大小{config['grid_size']}, "
  153. f"对冲停止价{hedge_stop}")
  154. print(f"\n网格对冲策略: {'启用' if cls.GRID_HEDGE_CONFIG['enable_hedge_strategy'] else '禁用'}")
  155. print(f"止损百分比: {cls.GRID_HEDGE_CONFIG['stop_loss_percentage']*100}%")
  156. print(f"MA过滤器: {'启用' if cls.GRID_HEDGE_CONFIG.get('use_ma_filter', False) else '禁用'}")
  157. print(f"详细日志: {'开启' if cls.VERBOSE_LOGGING else '关闭'}")
  158. print("=" * 50)
  159. class FutureGridTradingAnalyzer:
  160. """期货网格交易分析器"""
  161. def __init__(self, config=None):
  162. """初始化分析器"""
  163. if config is None:
  164. config = GridTradingConfig
  165. self.config = config
  166. self.start_date = config.START_DATE
  167. self.end_date = config.END_DATE
  168. self.core_commodities = config.CORE_COMMODITIES
  169. self.base_position_grid = config.BASE_POSITION_GRID
  170. self.base_position_exit_price = config.BASE_POSITION_EXIT_PRICE
  171. self.grid_trading_config = config.GRID_TRADING_CONFIG
  172. self.grid_hedge_config = config.GRID_HEDGE_CONFIG
  173. self.verbose_logging = config.VERBOSE_LOGGING
  174. self.output_encoding = config.OUTPUT_ENCODING
  175. # 存储结果的字典
  176. self.selected_contracts = {} # 选中的合约
  177. self.price_data = {} # 价格数据
  178. self.dominant_contract_history = {} # 主力合约历史变化
  179. self.active_positions = { # 当前活跃头寸跟踪
  180. 'base_position': {},
  181. 'grid_trading': {},
  182. 'grid_hedge': {}
  183. }
  184. self.trading_results = { # 四种交易场景的结果
  185. 'base_position': [],
  186. 'grid_trading': [],
  187. 'grid_hedge': [],
  188. 'combined': []
  189. }
  190. # MA过滤器扩展数据缓存(避免重复获取)
  191. self.ma_extended_data_cache = {}
  192. if self.verbose_logging:
  193. print("初始化期货网格交易分析器")
  194. print(f"核心商品: {list(self.core_commodities.keys())}")
  195. print(f"分析期间: {self.start_date.strftime('%Y-%m-%d')} - {self.end_date.strftime('%Y-%m-%d')}")
  196. def select_contracts(self):
  197. """
  198. 合约选择逻辑
  199. 1. 首先获取商品的主导合约
  200. 2. 如果主导合约在可用列表中,选择它
  201. 3. 如果主导合约不在列表中,选择未来到期日期最近且晚于主导合约的合约
  202. """
  203. if self.verbose_logging:
  204. print("\n=== 步骤1: 合约选择逻辑 ===")
  205. for commodity, available_contracts in self.core_commodities.items():
  206. if self.verbose_logging:
  207. print(f"\n处理商品: {commodity}")
  208. print(f"可用合约: {available_contracts}")
  209. try:
  210. # 获取商品的主导合约
  211. dominant_contract = get_dominant_future(commodity, self.start_date.date())
  212. if self.verbose_logging:
  213. print(f"主导合约: {dominant_contract}")
  214. if dominant_contract in available_contracts:
  215. # 主导合约在可用列表中,直接选择
  216. selected_contract = dominant_contract
  217. if self.verbose_logging:
  218. print(f"选择主导合约: {selected_contract}")
  219. else:
  220. # 主导合约不在列表中,选择最近的未来合约
  221. selected_contract = self._select_nearest_future_contract(
  222. commodity, dominant_contract, available_contracts
  223. )
  224. if self.verbose_logging:
  225. print(f"选择最近的未来合约: {selected_contract}")
  226. self.selected_contracts[commodity] = selected_contract
  227. except Exception as e:
  228. if self.verbose_logging:
  229. print(f"获取{commodity}主导合约失败: {str(e)}")
  230. # 默认选择第一个可用合约
  231. self.selected_contracts[commodity] = available_contracts[0]
  232. if self.verbose_logging:
  233. print(f"默认选择第一个合约: {available_contracts[0]}")
  234. if self.verbose_logging:
  235. print(f"\n合约选择完成,共选择{len(self.selected_contracts)}个合约")
  236. for commodity, contract in self.selected_contracts.items():
  237. print(f" {commodity}: {contract}")
  238. return self.selected_contracts
  239. def _select_nearest_future_contract(self, commodity, dominant_contract, available_contracts):
  240. """选择最近的未来到期合约"""
  241. if not dominant_contract:
  242. return available_contracts[0]
  243. # 解析主导合约的到期月份
  244. try:
  245. # 提取合约代码中的月份信息 (例如 SA2507 -> 2507)
  246. dominant_year_month = dominant_contract.split('.')[0][-4:] # 取最后4位
  247. dominant_year = int(dominant_year_month[:2]) + 2000 # 假设是21世纪
  248. dominant_month = int(dominant_year_month[2:])
  249. except:
  250. return available_contracts[0]
  251. # 找到最近的未来合约
  252. best_contract = available_contracts[0]
  253. best_diff = float('inf')
  254. for contract in available_contracts:
  255. try:
  256. contract_year_month = contract.split('.')[0][-4:]
  257. contract_year = int(contract_year_month[:2]) + 2000
  258. contract_month = int(contract_year_month[2:])
  259. # 计算月份差异
  260. contract_total_months = contract_year * 12 + contract_month
  261. dominant_total_months = dominant_year * 12 + dominant_month
  262. # 只选择晚于主导合约的合约
  263. if contract_total_months > dominant_total_months:
  264. diff = contract_total_months - dominant_total_months
  265. if diff < best_diff:
  266. best_diff = diff
  267. best_contract = contract
  268. except:
  269. continue
  270. return best_contract
  271. def _get_futures_multiplier(self, commodity):
  272. """获取期货合约倍数"""
  273. return self.config.FUTURES_MULTIPLIER.get(commodity, 10) # 默认倍数为10
  274. def _calculate_futures_pnl(self, entry_price, exit_price, quantity, commodity, is_long=True):
  275. """
  276. 计算期货盈亏
  277. 参数:
  278. entry_price: 入场价格
  279. exit_price: 出场价格
  280. quantity: 数量(手数)
  281. commodity: 商品代码
  282. is_long: 是否多头,True为多头,False为空头
  283. 返回:
  284. 实际盈亏金额
  285. """
  286. multiplier = self._get_futures_multiplier(commodity)
  287. if is_long:
  288. # 多头:(出场价格 - 入场价格) × 合约倍数 × 数量
  289. pnl = (exit_price - entry_price) * multiplier * quantity
  290. else:
  291. # 空头:(入场价格 - 出场价格) × 合约倍数 × 数量
  292. pnl = (entry_price - exit_price) * multiplier * quantity
  293. return pnl
  294. def build_dominant_contract_history(self):
  295. """
  296. 构建主力合约历史变化记录
  297. 为每个商品在整个分析期间构建主力合约变化的时间序列
  298. 只有当合约真正发生变化时才记录为合约切换
  299. """
  300. if self.verbose_logging:
  301. print("\n=== 步骤2:构建主力合约历史变化记录 ===")
  302. for commodity in self.core_commodities.keys():
  303. if self.verbose_logging:
  304. print(f"构建 {commodity} 主力合约历史...")
  305. contract_history = []
  306. current_date = self.start_date.date()
  307. end_date = self.end_date.date()
  308. current_selected_contract = None # 跟踪选择的合约而不是主力合约
  309. while current_date <= end_date:
  310. # 跳过非交易日
  311. if current_date.weekday() >= 5: # 周六周日
  312. current_date += datetime.timedelta(days=1)
  313. continue
  314. try:
  315. # 获取当日主力合约
  316. dominant_contract = get_dominant_future(commodity, current_date)
  317. # print(f"日期: {current_date}, 主力合约: {dominant_contract}")
  318. selected_contract = self._match_to_available_contract(commodity, dominant_contract)
  319. # 只有当选择的合约真正发生变化时才记录
  320. if selected_contract != current_selected_contract:
  321. contract_history.append({
  322. 'date': current_date,
  323. 'dominant_contract': dominant_contract,
  324. 'selected_contract': selected_contract,
  325. 'is_initial': current_selected_contract is None # 标记是否为初始合约
  326. })
  327. if self.verbose_logging:
  328. if current_selected_contract is None:
  329. print(f" {current_date}: 初始合约设置为 {selected_contract}")
  330. else:
  331. print(f" {current_date}: 合约切换 {current_selected_contract} -> {selected_contract}")
  332. current_selected_contract = selected_contract
  333. except Exception as e:
  334. if self.verbose_logging:
  335. print(f" 获取 {current_date} 的主力合约时出错: {str(e)}")
  336. current_date += datetime.timedelta(days=1)
  337. self.dominant_contract_history[commodity] = contract_history
  338. if self.verbose_logging:
  339. total_changes = sum(len(history) for history in self.dominant_contract_history.values())
  340. actual_switches = sum(
  341. sum(1 for change in history if not change.get('is_initial', False))
  342. for history in self.dominant_contract_history.values()
  343. )
  344. initial_setups = total_changes - actual_switches
  345. print(f"主力合约历史构建完成,共 {total_changes} 次记录({initial_setups} 次初始设置,{actual_switches} 次真实切换)")
  346. return self.dominant_contract_history
  347. def _match_to_available_contract(self, commodity, dominant_contract):
  348. """将主力合约匹配到可用合约列表"""
  349. available_contracts = self.core_commodities.get(commodity, [])
  350. if dominant_contract in available_contracts:
  351. return dominant_contract
  352. else:
  353. return self._select_nearest_future_contract(commodity, dominant_contract, available_contracts)
  354. def collect_price_data(self):
  355. """收集所有可能用到的合约价格数据(优化日期范围)"""
  356. if self.verbose_logging:
  357. print("\n=== 步骤3: 收集价格数据(优化日期范围) ===")
  358. # 清除之前的调整建议
  359. if hasattr(self, 'adjustment_suggestions'):
  360. self.adjustment_suggestions = []
  361. # 为每个商品创建数据存储结构
  362. for commodity in self.core_commodities.keys():
  363. print(f'收集{commodity}的价格数据:')
  364. self.price_data[commodity] = {}
  365. # 根据主力合约历史确定每个合约的数据获取范围
  366. contract_date_ranges = self._determine_contract_date_ranges(commodity)
  367. for contract, date_range in contract_date_ranges.items():
  368. start_date, end_date = date_range
  369. if self.verbose_logging:
  370. print(f"获取 {contract} 价格数据...")
  371. print(f" 优化日期范围: {start_date} 至 {end_date}")
  372. try:
  373. # 获取价格数据(使用优化的日期范围)
  374. data = get_price(
  375. contract,
  376. start_date=start_date,
  377. end_date=end_date,
  378. frequency='daily',
  379. fields=['open', 'close', 'high', 'low', 'volume'],
  380. skip_paused=False,
  381. panel=False
  382. )
  383. if data is not None and len(data) > 0:
  384. # print(f"第一条有数据的日期是: {data.index[0].date()},数据是: {data.iloc[0]}")
  385. # print(f"最后一条有数据的日期是: {data.index[-1].date()}, 数据是: {data.iloc[-1]}")
  386. self.price_data[commodity][contract] = data
  387. # 检查这个数据里有多少条空值数据
  388. empty_data = data[data.isna().any(axis=1)]
  389. # 检查有效交易日数据并收集调整建议
  390. adjustment_info = self._check_thirty_day_trading_data(commodity, contract, data, start_date, end_date)
  391. if adjustment_info and adjustment_info.get('needs_adjustment'):
  392. # 暂存调整建议,稍后统一处理
  393. if not hasattr(self, 'adjustment_suggestions'):
  394. self.adjustment_suggestions = []
  395. self.adjustment_suggestions.append(adjustment_info)
  396. if self.verbose_logging:
  397. print(f" ✅ 成功获取{len(data)}条数据记录")
  398. print(f" 空值数据: {len(empty_data)}条")
  399. print(f" 价格范围: {data['low'].min():.2f} - {data['high'].max():.2f}")
  400. print(f" 数据日期范围: {data.index[0].date()} 至 {data.index[-1].date()}")
  401. else:
  402. if self.verbose_logging:
  403. print(f" ⚠️ 未获取到{contract}的数据")
  404. # 如果优化日期范围没有数据,尝试使用更宽泛的日期范围
  405. if self.verbose_logging:
  406. print(f" 尝试使用更宽泛的日期范围获取数据...")
  407. try:
  408. fallback_data = get_price(
  409. contract,
  410. start_date=self.start_date,
  411. end_date=self.end_date,
  412. frequency='daily',
  413. fields=['open', 'close', 'high', 'low', 'volume'],
  414. skip_paused=False,
  415. panel=False
  416. )
  417. if fallback_data is not None and len(fallback_data) > 0:
  418. self.price_data[commodity][contract] = fallback_data
  419. # 检查有效交易日数据并收集调整建议(回退方案)
  420. adjustment_info = self._check_thirty_day_trading_data(commodity, contract, fallback_data, self.start_date, self.end_date)
  421. if adjustment_info and adjustment_info.get('needs_adjustment'):
  422. if not hasattr(self, 'adjustment_suggestions'):
  423. self.adjustment_suggestions = []
  424. self.adjustment_suggestions.append(adjustment_info)
  425. if self.verbose_logging:
  426. print(f" ✅ 回退方案成功获取{len(fallback_data)}条数据记录")
  427. print(f" 数据日期范围: {fallback_data.index[0].date()} 至 {fallback_data.index[-1].date()}")
  428. else:
  429. if self.verbose_logging:
  430. print(f" ❌ 回退方案也未获取到{contract}的数据")
  431. except Exception as fallback_e:
  432. if self.verbose_logging:
  433. print(f" ❌ 回退方案出错: {str(fallback_e)}")
  434. except Exception as e:
  435. if self.verbose_logging:
  436. print(f" ❌ 获取{contract}数据时出错: {str(e)}")
  437. continue
  438. # 处理动态调整建议
  439. if hasattr(self, 'adjustment_suggestions') and self.adjustment_suggestions:
  440. self._apply_dynamic_adjustments()
  441. if self.verbose_logging:
  442. total_contracts = sum(len(contracts) for contracts in self.price_data.values())
  443. print(f"价格数据收集完成,共{total_contracts}个合约")
  444. return self.price_data
  445. def _determine_contract_date_ranges(self, commodity):
  446. """
  447. 根据主力合约历史确定每个合约的最优数据获取日期范围
  448. """
  449. contract_ranges = {}
  450. if commodity not in self.dominant_contract_history:
  451. # 如果没有主力合约历史,使用全范围
  452. for contract in self.core_commodities[commodity]:
  453. contract_ranges[contract] = (self.start_date, self.end_date)
  454. return contract_ranges
  455. contract_history = self.dominant_contract_history[commodity]
  456. # 分析每个合约的活跃期间
  457. for contract in self.core_commodities[commodity]:
  458. contract_start = self.start_date
  459. contract_end = self.end_date
  460. # 查找该合约在主力合约历史中的使用时间段
  461. for i, history_record in enumerate(contract_history):
  462. if history_record['selected_contract'] == contract:
  463. # 该合约开始使用的日期
  464. if history_record.get('is_initial', False):
  465. # 初始设置的合约,从分析开始日期或历史记录日期开始
  466. contract_start = max(self.start_date.date(), history_record['date'])
  467. else:
  468. # 切换到的合约,从切换日期开始
  469. contract_start = history_record['date']
  470. # 查找该合约结束使用的日期
  471. for j in range(i + 1, len(contract_history)):
  472. next_record = contract_history[j]
  473. if next_record['selected_contract'] != contract:
  474. # 找到下一次切换,该合约在此日期结束使用
  475. contract_end = next_record['date']
  476. break
  477. else:
  478. # 该合约一直使用到分析结束
  479. contract_end = self.end_date.date()
  480. break
  481. # 转换为datetime格式并添加缓冲区
  482. if isinstance(contract_start, datetime.date):
  483. contract_start = datetime.datetime.combine(contract_start, datetime.time.min)
  484. if isinstance(contract_end, datetime.date):
  485. contract_end = datetime.datetime.combine(contract_end, datetime.time.max)
  486. # 添加缓冲期以确保有足够的历史数据满足最低交易日要求
  487. # 使用REQUIRED_TRADING_DAYS作为缓冲,保证数据充足性
  488. contract_start_buffered = contract_start - datetime.timedelta(days=self.config.REQUIRED_TRADING_DAYS)
  489. contract_end_buffered = contract_end # + datetime.timedelta(days=self.config.REQUIRED_TRADING_DAYS)
  490. # 确保不超出总体分析范围
  491. contract_start_final = max(contract_start_buffered, self.start_date)
  492. contract_end_final = min(contract_end_buffered, self.end_date)
  493. contract_ranges[contract] = (contract_start_final, contract_end_final)
  494. if self.verbose_logging:
  495. print(f" {contract}: {contract_start_final.date()} 至 {contract_end_final.date()}")
  496. return contract_ranges
  497. def _check_thirty_day_trading_data(self, commodity, contract, data, start_date, end_date):
  498. """
  499. 检查合约是否有足够的有效交易日数据并进行动态调整
  500. 返回调整建议信息
  501. """
  502. if data is None or len(data) == 0:
  503. print(f" ⚠️ {contract}: 无价格数据")
  504. return None
  505. required_days = self.config.REQUIRED_TRADING_DAYS
  506. # 检查空值数据
  507. empty_data = data[data.isna().any(axis=1)]
  508. empty_count = len(empty_data)
  509. # 过滤出非空的收盘价数据
  510. valid_close_data = data['close'].dropna()
  511. valid_count = len(valid_close_data)
  512. print(f" 📊 {contract}: 有效收盘价数据共{valid_count}天")
  513. adjustment_info = {
  514. 'contract': contract,
  515. 'commodity': commodity,
  516. 'empty_count': empty_count,
  517. 'valid_count': valid_count,
  518. 'required_days': required_days,
  519. 'needs_adjustment': False,
  520. 'suggested_switch_date': None
  521. }
  522. # 检查是否有空值数据且需要调整
  523. if empty_count > 0:
  524. print(f" ⚠️ {contract}: 检测到{empty_count}条空值数据")
  525. if valid_count >= required_days:
  526. # 找到第N个有效收盘价的日期
  527. nth_date = valid_close_data.index[required_days - 1] # 索引从0开始
  528. nth_price = valid_close_data.iloc[required_days - 1]
  529. print(f" 📍 {contract}: 第{required_days}个有效收盘价日期为{nth_date.date()},价格{nth_price:.2f}")
  530. # 检查当前切换日期是否需要调整
  531. if commodity in self.dominant_contract_history:
  532. for history_record in self.dominant_contract_history[commodity]:
  533. if (history_record['selected_contract'] == contract and
  534. not history_record.get('is_initial', False)):
  535. current_switch_date = history_record['date']
  536. # 转换日期格式进行比较
  537. if isinstance(current_switch_date, datetime.date):
  538. current_switch_datetime = datetime.datetime.combine(current_switch_date, datetime.time.min)
  539. else:
  540. current_switch_datetime = current_switch_date
  541. if nth_date > current_switch_datetime:
  542. print(f" ❌ {contract}: 切换日期过早(当前:{current_switch_date}),建议调整至{nth_date.date()}")
  543. adjustment_info.update({
  544. 'needs_adjustment': True,
  545. 'suggested_switch_date': nth_date.date(),
  546. 'current_switch_date': current_switch_date
  547. })
  548. else:
  549. print(f" ✅ {contract}: 切换日期{current_switch_date}合理,在第{required_days}个有效交易日之后")
  550. break
  551. else:
  552. print(f" ❌ {contract}: 有效交易日不足{required_days}天(仅{valid_count}天),不符合切换要求")
  553. adjustment_info['needs_adjustment'] = True
  554. else:
  555. # 没有空值数据,检查是否有足够的交易日
  556. if valid_count >= required_days:
  557. nth_date = valid_close_data.index[required_days - 1]
  558. nth_price = valid_close_data.iloc[required_days - 1]
  559. print(f" ✅ {contract}: 第{required_days}个有效收盘价日期为{nth_date.date()},价格{nth_price:.2f}")
  560. else:
  561. print(f" ❌ {contract}: 有效交易日不足{required_days}天(仅{valid_count}天)")
  562. return adjustment_info
  563. def _apply_dynamic_adjustments(self):
  564. """应用动态调整建议,更新合约切换日期并重新获取数据"""
  565. if self.verbose_logging:
  566. print(f"\n=== 应用动态调整建议(共{len(self.adjustment_suggestions)}个) ===")
  567. adjustments_applied = []
  568. for suggestion in self.adjustment_suggestions:
  569. if suggestion.get('suggested_switch_date'):
  570. commodity = suggestion['commodity']
  571. contract = suggestion['contract']
  572. new_switch_date = suggestion['suggested_switch_date']
  573. print(f"📅 调整{commodity}的{contract}切换日期至{new_switch_date}")
  574. # 更新合约历史
  575. if self._update_contract_switch_date(commodity, contract, new_switch_date):
  576. adjustments_applied.append(suggestion)
  577. # 如果有调整,重新获取相关的价格数据
  578. if adjustments_applied:
  579. print(f"✅ 完成{len(adjustments_applied)}个调整,重新获取相关价格数据")
  580. self._refresh_price_data_for_adjustments(adjustments_applied)
  581. def _update_contract_switch_date(self, commodity, contract, new_switch_date):
  582. """更新指定合约的切换日期"""
  583. if commodity not in self.dominant_contract_history:
  584. return False
  585. # 查找并更新对应的历史记录
  586. for history_record in self.dominant_contract_history[commodity]:
  587. if (history_record['selected_contract'] == contract and
  588. not history_record.get('is_initial', False)):
  589. old_date = history_record['date']
  590. history_record['date'] = new_switch_date
  591. print(f" 📝 {contract}: 切换日期从{old_date}更新为{new_switch_date}")
  592. return True
  593. return False
  594. def _refresh_price_data_for_adjustments(self, adjustments):
  595. """为调整的合约重新获取价格数据"""
  596. affected_commodities = set()
  597. for adjustment in adjustments:
  598. commodity = adjustment['commodity']
  599. affected_commodities.add(commodity)
  600. for commodity in affected_commodities:
  601. print(f"🔄 重新获取{commodity}的价格数据...")
  602. # 重新计算日期范围
  603. contract_date_ranges = self._determine_contract_date_ranges(commodity)
  604. # 重新获取每个合约的数据
  605. for contract, date_range in contract_date_ranges.items():
  606. start_date, end_date = date_range
  607. try:
  608. # 获取价格数据(使用新的日期范围)
  609. data = get_price(
  610. contract,
  611. start_date=start_date,
  612. end_date=end_date,
  613. frequency='daily',
  614. fields=['open', 'close', 'high', 'low', 'volume'],
  615. skip_paused=False,
  616. panel=False
  617. )
  618. if data is not None and len(data) > 0:
  619. self.price_data[commodity][contract] = data
  620. # 检查调整后的数据
  621. empty_data = data[data.isna().any(axis=1)]
  622. empty_count = len(empty_data)
  623. print(f" ✅ {contract}: 重新获取{len(data)}条数据记录,空值{empty_count}条")
  624. if empty_count == 0:
  625. print(f" 🎉 {contract}: 空值数据已消除")
  626. except Exception as e:
  627. print(f" ❌ 重新获取{contract}数据时出错: {str(e)}")
  628. def simulate_with_contract_switching(self):
  629. """
  630. 模拟带有主力合约切换逻辑的交易
  631. """
  632. if self.verbose_logging:
  633. print("\n=== 步骤3: 带合约切换的交易模拟 ===")
  634. # 打印对冲配置信息
  635. if self.grid_hedge_config['enable_hedge_strategy']:
  636. print("\n网格对冲策略配置:")
  637. for commodity, config in self.grid_trading_config.items():
  638. start_price = config['start_price']
  639. hedge_stop_price = config.get('hedge_stop_price', 0)
  640. grid_size = config['grid_size']
  641. exit_grid_size = config['exit_grid_size']
  642. # 计算对冲范围
  643. hedge_levels = []
  644. current_grid = start_price
  645. while current_grid >= hedge_stop_price and current_grid > 0:
  646. hedge_entry = current_grid + exit_grid_size
  647. hedge_levels.append(hedge_entry)
  648. current_grid -= grid_size
  649. print(f" {commodity}: 对冲范围 {min(hedge_levels)}-{max(hedge_levels)} "
  650. f"(对应主网格 {hedge_stop_price}-{start_price}), "
  651. f"共{len(hedge_levels)}个对冲级别")
  652. # 按日期顺序处理所有交易日
  653. current_date = self.start_date.date()
  654. end_date = self.end_date.date()
  655. while current_date <= end_date:
  656. # 跳过非交易日
  657. if current_date.weekday() >= 5:
  658. current_date += datetime.timedelta(days=1)
  659. continue
  660. # 检查每个商品的主力合约切换
  661. for commodity in self.core_commodities.keys():
  662. self._check_and_handle_contract_switch(commodity, current_date)
  663. # 处理正常的交易逻辑
  664. self._process_daily_trading(current_date)
  665. current_date += datetime.timedelta(days=1)
  666. # 在交易循环结束后,计算所有未平仓头寸的最终盈亏
  667. self._calculate_final_positions_pnl()
  668. if self.verbose_logging:
  669. print("带合约切换的交易模拟完成")
  670. def _calculate_final_positions_pnl(self):
  671. """
  672. 计算分析期结束时所有未平仓头寸的最终盈亏
  673. 将这些盈亏作为最终交易记录加入结果中
  674. """
  675. if self.verbose_logging:
  676. print("\n=== 计算最终持仓盈亏 ===")
  677. final_date = self.end_date.date()
  678. final_pnl_records = []
  679. # 添加诊断信息
  680. if self.verbose_logging:
  681. print(f"分析结束日期: {final_date}")
  682. print(f"活跃头寸概览:")
  683. for strategy_name in ['base_position', 'grid_trading', 'grid_hedge']:
  684. strategy_positions = self.active_positions.get(strategy_name, {})
  685. total_positions = 0
  686. open_positions = 0
  687. for commodity, positions in strategy_positions.items():
  688. commodity_total = len(positions)
  689. commodity_open = sum(1 for p in positions.values() if p['status'] == 'open')
  690. total_positions += commodity_total
  691. open_positions += commodity_open
  692. if commodity_total > 0:
  693. print(f" {strategy_name} - {commodity}: 总计 {commodity_total} 个头寸, 未平仓 {commodity_open} 个")
  694. # 详细列出所有头寸信息
  695. print(f" 详细头寸列表:")
  696. for pos_id, pos_info in positions.items():
  697. status = pos_info.get('status', 'Unknown')
  698. entry_price = pos_info.get('entry_price', 'N/A')
  699. contract = pos_info.get('contract', 'N/A')
  700. entry_date = pos_info.get('entry_date', 'N/A')
  701. quantity = pos_info.get('quantity', 'N/A')
  702. print(f" {pos_id}: 状态={status}, 合约={contract}, 开仓价格={entry_price}, 日期={entry_date}, 数量={quantity}")
  703. print(f" {strategy_name} 策略总计: {open_positions}/{total_positions} 个未平仓头寸")
  704. # 验证头寸计数的准确性
  705. actual_count = len(positions)
  706. open_count_verify = len([p for p in positions.values() if p.get('status') == 'open'])
  707. if actual_count != commodity_total or open_count_verify != commodity_open:
  708. print(f" ⚠️ 计数不匹配!实际头寸数: {actual_count}, 预期: {commodity_total}; 实际未平仓: {open_count_verify}, 预期: {commodity_open}")
  709. # 检查是否有重复的开仓价格(同一合约同一状态)
  710. open_positions_by_price = {}
  711. for pos_id, pos_info in positions.items():
  712. if pos_info.get('status') == 'open':
  713. price = pos_info.get('entry_price')
  714. contract = pos_info.get('contract')
  715. key = f"{contract}_{price}"
  716. if key not in open_positions_by_price:
  717. open_positions_by_price[key] = []
  718. open_positions_by_price[key].append(pos_id)
  719. # for key, pos_ids in open_positions_by_price.items():
  720. # if len(pos_ids) > 1:
  721. # print(f" ⚠️ 发现重复的未平仓头寸: {key} -> {pos_ids}")
  722. print(f" {strategy_name} 策略总计: {open_positions}/{total_positions} 个未平仓头寸")
  723. for strategy_name in ['base_position', 'grid_trading', 'grid_hedge']:
  724. strategy_positions = self.active_positions.get(strategy_name, {})
  725. for commodity, positions in strategy_positions.items():
  726. # 获取当前合约和最终价格
  727. current_contract = self._get_current_contract(commodity, final_date)
  728. if not current_contract:
  729. if self.verbose_logging:
  730. print(f" 警告: 无法确定 {commodity} 在 {final_date} 的当前合约")
  731. continue
  732. final_price = self._get_price_on_date(commodity, current_contract, final_date, 'close')
  733. if final_price is None:
  734. if self.verbose_logging:
  735. print(f" 警告: 无法获取 {commodity} {current_contract} 在 {final_date} 的价格")
  736. continue
  737. if self.verbose_logging and len(positions) > 0:
  738. print(f" {commodity} {strategy_name}: 当前合约 {current_contract}, 结算价格 {final_price:.2f}")
  739. for position_id, position in positions.items():
  740. if self.verbose_logging:
  741. print(f" 检查头寸 {position_id}: 状态={position['status']}, 合约={position['contract']}, 开仓价格={position.get('entry_price', 'N/A')}")
  742. if position['status'] == 'open' and position['contract'] == current_contract:
  743. if self.verbose_logging:
  744. print(f" 匹配头寸进行结算: {position_id}")
  745. print(f" 头寸详情: 开仓日期={position.get('entry_date', 'N/A')}, 开仓价格={position['entry_price']}, 数量={position.get('quantity', 'N/A')}")
  746. # 计算最终盈亏
  747. if strategy_name == 'grid_hedge':
  748. # 网格对冲是做空
  749. profit_loss = self._calculate_futures_pnl(
  750. position['entry_price'], final_price, position['quantity'], commodity, is_long=False
  751. )
  752. else:
  753. # 基础头寸和网格交易都是做多
  754. profit_loss = self._calculate_futures_pnl(
  755. position['entry_price'], final_price, position['quantity'], commodity, is_long=True
  756. )
  757. profit_loss_pct = (final_price - position['entry_price']) / position['entry_price']
  758. if strategy_name == 'grid_hedge':
  759. profit_loss_pct = (position['entry_price'] - final_price) / position['entry_price']
  760. # 计算持有天数
  761. entry_date = datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()
  762. days_held = (final_date - entry_date).days
  763. # 创建最终持仓盈亏记录
  764. final_record = {
  765. 'commodity': commodity,
  766. 'contract': current_contract,
  767. 'strategy': strategy_name,
  768. 'entry_date': position['entry_date'],
  769. 'exit_date': final_date.strftime('%Y-%m-%d'),
  770. 'entry_price': position['entry_price'],
  771. 'exit_price': final_price,
  772. 'quantity': position['quantity'],
  773. 'profit_loss': profit_loss,
  774. 'profit_loss_pct': profit_loss_pct,
  775. 'days_held': days_held,
  776. 'exit_reason': 'final_settlement'
  777. }
  778. if self.verbose_logging:
  779. print(f" 创建最终结算记录: 头寸ID={position_id}, 开仓价格={position['entry_price']}, 结算价格={final_price:.2f}")
  780. final_pnl_records.append(final_record)
  781. # 将头寸标记为已平仓
  782. self.active_positions[strategy_name][commodity][position_id]['status'] = 'final_settled'
  783. if self.verbose_logging:
  784. print(f" {commodity} {strategy_name} 最终结算: {position['entry_price']} -> {final_price:.2f}, 盈亏: {profit_loss:.2f}")
  785. # 将最终盈亏记录添加到交易结果中
  786. for record in final_pnl_records:
  787. strategy_name = record['strategy']
  788. self.trading_results[strategy_name].append(record)
  789. # 更新组合策略结果
  790. combined_final_records = []
  791. for record in final_pnl_records:
  792. if record['strategy'] in ['grid_trading', 'grid_hedge']:
  793. combined_record = record.copy()
  794. combined_record['strategy'] = f"combined_{record['strategy']}"
  795. combined_final_records.append(combined_record)
  796. self.trading_results['combined'].extend(combined_final_records)
  797. if self.verbose_logging:
  798. total_final_records = len(final_pnl_records)
  799. total_final_pnl = sum(record['profit_loss'] for record in final_pnl_records)
  800. print(f"最终持仓结算完成,共 {total_final_records} 个头寸,总未实现盈亏: {total_final_pnl:.2f}")
  801. # 显示所有最终结算记录的详情
  802. if final_pnl_records:
  803. print(f"最终结算记录详情:")
  804. for i, record in enumerate(final_pnl_records, 1):
  805. print(f" {i}. {record['commodity']} {record['strategy']}: {record['entry_price']} -> {record['exit_price']:.2f}, 盈亏: {record['profit_loss']:.2f}, 合约: {record['contract']}")
  806. def _check_and_handle_contract_switch(self, commodity, current_date):
  807. """
  808. 检查并处理主力合约切换
  809. 只有真正的合约切换才会触发平仓和重新建仓,初始设置不会
  810. """
  811. if commodity not in self.dominant_contract_history:
  812. return
  813. # 检查当天是否有合约变化
  814. contract_changes = self.dominant_contract_history[commodity]
  815. for change in contract_changes:
  816. if change['date'] == current_date:
  817. # 检查是否为初始合约设置
  818. if change.get('is_initial', False):
  819. # 初始合约设置,不需要平仓和重新建仓,只需要启动正常交易逻辑
  820. if self.verbose_logging:
  821. print(f"\n{current_date}: {commodity} 初始合约设置为 {change['selected_contract']}")
  822. return
  823. # 真正的合约切换
  824. old_contract = self._get_current_contract(commodity, current_date - datetime.timedelta(days=1))
  825. new_contract = change['selected_contract']
  826. if self.verbose_logging:
  827. print(f"\n{current_date}: {commodity} 合约切换 {old_contract} -> {new_contract}")
  828. # 平掉旧合约的所有头寸
  829. self._close_all_positions_on_switch(commodity, old_contract, current_date)
  830. # 在新合约中重新建仓
  831. self._reestablish_positions_in_new_contract(commodity, new_contract, current_date)
  832. break
  833. def _get_current_contract(self, commodity, date):
  834. """获取指定日期的当前合约"""
  835. if commodity not in self.dominant_contract_history:
  836. return None
  837. contract_changes = self.dominant_contract_history[commodity]
  838. current_contract = None
  839. for change in contract_changes:
  840. if change['date'] <= date:
  841. current_contract = change['selected_contract']
  842. else:
  843. break
  844. return current_contract
  845. def _close_all_positions_on_switch(self, commodity, old_contract, switch_date):
  846. """
  847. 在合约切换时平掉旧合约的所有头寸
  848. """
  849. if self.verbose_logging:
  850. print(f" 平掉 {old_contract} 的所有头寸")
  851. # 获取当日收盘价
  852. close_price = self._get_price_on_date(commodity, old_contract, switch_date, 'close')
  853. if close_price is None:
  854. if self.verbose_logging:
  855. print(f" 无法获取 {switch_date} 的价格数据,跳过平仓")
  856. return
  857. # 平掉基础头寸交易的头寸
  858. if commodity in self.active_positions['base_position']:
  859. positions = self.active_positions['base_position'][commodity].copy()
  860. for position_id, position in positions.items():
  861. if position['contract'] == old_contract and position['status'] == 'open':
  862. # 使用正确的期货盈亏计算公式(基础头寸都是多头)
  863. profit_loss = self._calculate_futures_pnl(
  864. position['entry_price'], close_price, position['quantity'], commodity, is_long=True
  865. )
  866. profit_loss_pct = (close_price - position['entry_price']) / position['entry_price']
  867. trade_record = {
  868. 'commodity': commodity,
  869. 'contract': old_contract,
  870. 'strategy': 'base_position',
  871. 'entry_date': position['entry_date'],
  872. 'exit_date': switch_date.strftime('%Y-%m-%d'),
  873. 'entry_price': position['entry_price'],
  874. 'exit_price': close_price,
  875. 'quantity': position['quantity'],
  876. 'profit_loss': profit_loss,
  877. 'profit_loss_pct': profit_loss_pct,
  878. 'days_held': (switch_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
  879. 'exit_reason': 'contract_switch'
  880. }
  881. self.trading_results['base_position'].append(trade_record)
  882. self.active_positions['base_position'][commodity][position_id]['status'] = 'closed'
  883. self.active_positions['base_position'][commodity][position_id]['close_reason'] = 'contract_switch'
  884. if self.verbose_logging:
  885. print(f" 基础头寸平仓: {position['entry_price']} -> {close_price:.2f}, 盈亏: {profit_loss:.2f}")
  886. # 平掉网格交易的头寸
  887. if commodity in self.active_positions['grid_trading']:
  888. positions = self.active_positions['grid_trading'][commodity].copy()
  889. for position_id, position in positions.items():
  890. if position['contract'] == old_contract and position['status'] == 'open':
  891. # 使用正确的期货盈亏计算公式(网格交易都是多头)
  892. profit_loss = self._calculate_futures_pnl(
  893. position['entry_price'], close_price, position['quantity'], commodity, is_long=True
  894. )
  895. profit_loss_pct = (close_price - position['entry_price']) / position['entry_price']
  896. trade_record = {
  897. 'commodity': commodity,
  898. 'contract': old_contract,
  899. 'strategy': 'grid_trading',
  900. 'entry_date': position['entry_date'],
  901. 'exit_date': switch_date.strftime('%Y-%m-%d'),
  902. 'entry_price': position['entry_price'],
  903. 'exit_price': close_price,
  904. 'quantity': position['quantity'],
  905. 'profit_loss': profit_loss,
  906. 'profit_loss_pct': profit_loss_pct,
  907. 'days_held': (switch_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
  908. 'exit_reason': 'contract_switch'
  909. }
  910. self.trading_results['grid_trading'].append(trade_record)
  911. self.active_positions['grid_trading'][commodity][position_id]['status'] = 'closed'
  912. self.active_positions['grid_trading'][commodity][position_id]['close_reason'] = 'contract_switch'
  913. if self.verbose_logging:
  914. print(f" 网格头寸平仓: {position['entry_price']} -> {close_price:.2f}, 盈亏: {profit_loss:.2f}")
  915. # 平掉网格对冲的头寸
  916. if commodity in self.active_positions['grid_hedge']:
  917. positions = self.active_positions['grid_hedge'][commodity].copy()
  918. for position_id, position in positions.items():
  919. if position['contract'] == old_contract and position['status'] == 'open':
  920. # 使用正确的期货盈亏计算公式(网格对冲是做空)
  921. profit_loss = self._calculate_futures_pnl(
  922. position['entry_price'], close_price, position['quantity'], commodity, is_long=False
  923. )
  924. profit_loss_pct = (position['entry_price'] - close_price) / position['entry_price']
  925. trade_record = {
  926. 'commodity': commodity,
  927. 'contract': old_contract,
  928. 'strategy': 'grid_hedge',
  929. 'entry_date': position['entry_date'],
  930. 'exit_date': switch_date.strftime('%Y-%m-%d'),
  931. 'entry_price': position['entry_price'],
  932. 'exit_price': close_price,
  933. 'quantity': position['quantity'],
  934. 'profit_loss': profit_loss,
  935. 'profit_loss_pct': profit_loss_pct,
  936. 'days_held': (switch_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
  937. 'exit_reason': 'contract_switch'
  938. }
  939. self.trading_results['grid_hedge'].append(trade_record)
  940. self.active_positions['grid_hedge'][commodity][position_id]['status'] = 'closed'
  941. self.active_positions['grid_hedge'][commodity][position_id]['close_reason'] = 'contract_switch'
  942. if self.verbose_logging:
  943. print(f" 对冲头寸平仓: {position['entry_price']} -> {close_price:.2f}, 盈亏: {profit_loss:.2f}")
  944. def _reestablish_positions_in_new_contract(self, commodity, new_contract, switch_date):
  945. """
  946. 在新合约中重新建仓
  947. """
  948. if self.verbose_logging:
  949. print(f" 在 {new_contract} 中重新建仓")
  950. # 获取当日收盘价
  951. close_price = self._get_price_on_date(commodity, new_contract, switch_date, 'close')
  952. if close_price is None:
  953. if self.verbose_logging:
  954. print(f" 无法获取 {switch_date} 的价格数据,跳过重新建仓")
  955. return
  956. # 基础头寸交易重新建仓
  957. self._reestablish_base_positions(commodity, new_contract, close_price, switch_date)
  958. # 网格交易重新建仓
  959. self._reestablish_grid_positions(commodity, new_contract, close_price, switch_date)
  960. # 网格对冲重新建仓
  961. self._reestablish_hedge_positions(commodity, new_contract, close_price, switch_date)
  962. def _reestablish_base_positions(self, commodity, new_contract, close_price, switch_date):
  963. """重新建立基础头寸"""
  964. if commodity not in self.base_position_grid:
  965. return
  966. # 获取之前被平掉的基础头寸信息(按价格水平记录)
  967. closed_positions = {} # price_level -> quantity
  968. if commodity in self.active_positions['base_position']:
  969. for position in self.active_positions['base_position'][commodity].values():
  970. if position['status'] == 'closed' and 'contract_switch' in position.get('close_reason', ''):
  971. # 只处理因合约切换而平掉的头寸
  972. original_price = position.get('original_price_level', position['entry_price'])
  973. if original_price not in closed_positions:
  974. closed_positions[original_price] = 0
  975. closed_positions[original_price] += position['quantity']
  976. if self.verbose_logging:
  977. print(f" 发现需重建的基础头寸: {original_price}水平 {position['quantity']}手 (原合约: {position['contract']})")
  978. # 根据当前价格和原始价格水平重建头寸
  979. price_grid = self.base_position_grid[commodity]
  980. reestablish_count = 0
  981. for target_price, configured_quantity in price_grid.items():
  982. # 只有当目标价格大于等于当前价格时才重建头寸
  983. # 这确保了只重建"应该持有"的价格水平头寸
  984. if target_price >= close_price:
  985. # 检查是否有该价格水平的平仓头寸需要重建
  986. if target_price in closed_positions:
  987. quantity_to_reestablish = closed_positions[target_price]
  988. if self.verbose_logging:
  989. print(f" 重建条件检查: {target_price} >= {close_price:.2f} ✓ (重建原有平仓头寸)")
  990. else:
  991. # 当前价格低于目标价格,应该建立该价格水平的头寸
  992. quantity_to_reestablish = configured_quantity
  993. if self.verbose_logging:
  994. print(f" 重建条件检查: {target_price} >= {close_price:.2f} ✓ (建立新头寸)")
  995. else:
  996. # 当前价格高于目标价格,不重建
  997. if self.verbose_logging:
  998. print(f" 重建条件检查: {target_price} >= {close_price:.2f} ✗ (跳过重建)")
  999. continue
  1000. if quantity_to_reestablish > 0:
  1001. position_id = f"{commodity}_{new_contract}_{switch_date}_base_reestablish_{target_price}"
  1002. if commodity not in self.active_positions['base_position']:
  1003. self.active_positions['base_position'][commodity] = {}
  1004. self.active_positions['base_position'][commodity][position_id] = {
  1005. 'contract': new_contract,
  1006. 'entry_date': switch_date.strftime('%Y-%m-%d'),
  1007. 'entry_price': close_price, # 实际成交价格
  1008. 'original_price_level': target_price, # 原始价格水平
  1009. 'quantity': quantity_to_reestablish,
  1010. 'status': 'open',
  1011. 'exit_target': self.base_position_exit_price.get(commodity)
  1012. }
  1013. if self.verbose_logging:
  1014. print(f" 创建重建头寸: {position_id}")
  1015. print(f" 实际成交价格: {close_price}, 原始价格水平: {target_price}, 数量: {quantity_to_reestablish}")
  1016. reestablish_count += quantity_to_reestablish
  1017. if self.verbose_logging:
  1018. print(f" 重建基础头寸 {target_price}水平: {quantity_to_reestablish} 手 @ {close_price:.2f}")
  1019. if reestablish_count > 0 and self.verbose_logging:
  1020. print(f" 基础头寸重建完成,总计: {reestablish_count} 手")
  1021. def _reestablish_grid_positions(self, commodity, new_contract, close_price, switch_date):
  1022. """重新建立网格交易头寸"""
  1023. if commodity not in self.grid_trading_config:
  1024. return
  1025. config = self.grid_trading_config[commodity]
  1026. grid_size = config['grid_size']
  1027. quantity_per_grid = config['quantity_per_grid']
  1028. exit_grid_size = config['exit_grid_size']
  1029. # 获取之前的网格头寸信息
  1030. previous_grid_levels = set()
  1031. if commodity in self.active_positions['grid_trading']:
  1032. for position in self.active_positions['grid_trading'][commodity].values():
  1033. if position['status'] == 'closed' and 'contract_switch' in position.get('close_reason', ''):
  1034. # 只处理因合约切换而平掉的头寸
  1035. previous_grid_levels.add(position['entry_price'])
  1036. if self.verbose_logging:
  1037. print(f" 发现需重建的网格头寸: {position['entry_price']}水平 {position['quantity']}手")
  1038. # 仅在原始网格开仓价格大于等于当前价格时重新建仓
  1039. # 这确保了只重建"应该持有"的网格水平头寸
  1040. reestablish_count = 0
  1041. for grid_level in previous_grid_levels:
  1042. if grid_level >= close_price:
  1043. if self.verbose_logging:
  1044. print(f" 网格重建条件检查: {grid_level} >= {close_price:.2f} ✓ (重建网格头寸)")
  1045. position_id = f"{commodity}_{new_contract}_{switch_date}_grid_{grid_level}"
  1046. if commodity not in self.active_positions['grid_trading']:
  1047. self.active_positions['grid_trading'][commodity] = {}
  1048. self.active_positions['grid_trading'][commodity][position_id] = {
  1049. 'contract': new_contract,
  1050. 'entry_date': switch_date.strftime('%Y-%m-%d'),
  1051. 'entry_price': close_price,
  1052. 'original_grid_level': grid_level,
  1053. 'quantity': quantity_per_grid,
  1054. 'status': 'open',
  1055. 'exit_target': grid_level + exit_grid_size # 保持原始退出价格
  1056. }
  1057. reestablish_count += 1
  1058. else:
  1059. if self.verbose_logging:
  1060. print(f" 网格重建条件检查: {grid_level} >= {close_price:.2f} ✗ (跳过重建)")
  1061. continue
  1062. # 同时检查是否需要开立新的网格头寸(价格更低的情况)
  1063. start_price = config['start_price']
  1064. current_level = start_price
  1065. while current_level > close_price:
  1066. current_level -= grid_size
  1067. if current_level not in previous_grid_levels and current_level > 0:
  1068. # 这是一个新的网格水平
  1069. position_id = f"{commodity}_{new_contract}_{switch_date}_grid_new_{current_level}"
  1070. if commodity not in self.active_positions['grid_trading']:
  1071. self.active_positions['grid_trading'][commodity] = {}
  1072. self.active_positions['grid_trading'][commodity][position_id] = {
  1073. 'contract': new_contract,
  1074. 'entry_date': switch_date.strftime('%Y-%m-%d'),
  1075. 'entry_price': close_price,
  1076. 'original_grid_level': current_level,
  1077. 'quantity': quantity_per_grid,
  1078. 'status': 'open',
  1079. 'exit_target': current_level + exit_grid_size
  1080. }
  1081. reestablish_count += 1
  1082. if self.verbose_logging and reestablish_count > 0:
  1083. print(f" 重建网格头寸: {reestablish_count} 个网格 @ {close_price:.2f}")
  1084. def _reestablish_hedge_positions(self, commodity, new_contract, close_price, switch_date):
  1085. """重新建立网格对冲头寸"""
  1086. if not self.grid_hedge_config['enable_hedge_strategy'] or commodity not in self.grid_trading_config:
  1087. return
  1088. config = self.grid_trading_config[commodity]
  1089. grid_size = config['grid_size']
  1090. quantity_per_grid = config['quantity_per_grid']
  1091. stop_loss_pct = self.grid_hedge_config['stop_loss_percentage']
  1092. # 获取之前的对冲头寸信息
  1093. previous_hedge_levels = set()
  1094. if commodity in self.active_positions['grid_hedge']:
  1095. for position in self.active_positions['grid_hedge'][commodity].values():
  1096. if position['status'] == 'closed' and 'contract_switch' in position.get('close_reason', ''):
  1097. # 只处理因合约切换而平掉的头寸
  1098. previous_hedge_levels.add(position['entry_price'])
  1099. if self.verbose_logging:
  1100. print(f" 发现需重建的对冲头寸: {position['entry_price']}水平 {position['quantity']}手")
  1101. # 仅在当前价格高于原始对冲开仓价格时重新建仓
  1102. # 对冲是做空策略,只有价格高于对冲水平时才适合重建做空头寸
  1103. reestablish_count = 0
  1104. for hedge_level in previous_hedge_levels:
  1105. if close_price > hedge_level:
  1106. if self.verbose_logging:
  1107. print(f" 对冲重建条件检查: {close_price:.2f} > {hedge_level} ✓ (重建对冲头寸)")
  1108. position_id = f"{commodity}_{new_contract}_{switch_date}_hedge_{hedge_level}"
  1109. if commodity not in self.active_positions['grid_hedge']:
  1110. self.active_positions['grid_hedge'][commodity] = {}
  1111. self.active_positions['grid_hedge'][commodity][position_id] = {
  1112. 'contract': new_contract,
  1113. 'entry_date': switch_date.strftime('%Y-%m-%d'),
  1114. 'entry_price': close_price,
  1115. 'original_grid_level': hedge_level,
  1116. 'quantity': quantity_per_grid,
  1117. 'status': 'open',
  1118. 'profit_target': hedge_level - grid_size, # 保持原始退出价格
  1119. 'stop_loss': close_price * (1 + stop_loss_pct)
  1120. }
  1121. reestablish_count += 1
  1122. else:
  1123. if self.verbose_logging:
  1124. print(f" 对冲重建条件检查: {close_price:.2f} > {hedge_level} ✗ (跳过重建)")
  1125. continue
  1126. if self.verbose_logging and reestablish_count > 0:
  1127. print(f" 重建对冲头寸: {reestablish_count} 个网格 @ {close_price:.2f}")
  1128. def _get_price_on_date(self, commodity, contract, date, price_type='close'):
  1129. """获取指定日期和合约的价格(增强NaN问题诊断)"""
  1130. if commodity not in self.price_data or contract not in self.price_data[commodity]:
  1131. if self.verbose_logging:
  1132. print(f" ❌ 价格数据不存在: {commodity} -> {contract}")
  1133. return None
  1134. price_data = self.price_data[commodity][contract]
  1135. # 找到日期对应的价格
  1136. target_date = date if isinstance(date, datetime.date) else date.date()
  1137. for idx, row in price_data.iterrows():
  1138. if idx.date() == target_date:
  1139. price_value = row[price_type]
  1140. if self.verbose_logging:
  1141. print(f'{price_type}的价格是: {price_value}')
  1142. # 如果价格为NaN,进行详细诊断
  1143. if pd.isna(price_value):
  1144. self._diagnose_nan_price_issue(commodity, contract, target_date, price_type, row)
  1145. return None
  1146. else:
  1147. return price_value
  1148. # 如果没有找到精确日期,尝试查找最近的交易日
  1149. if self.verbose_logging:
  1150. print(f" ⚠️ 未找到 {contract} 在 {target_date} 的数据,尝试查找最近交易日...")
  1151. return self._get_nearest_trading_day_price(commodity, contract, target_date, price_type)
  1152. def _diagnose_nan_price_issue(self, commodity, contract, date, price_type, row):
  1153. """诊断NaN价格问题的根本原因"""
  1154. if self.verbose_logging:
  1155. print(f" 🔍 NaN价格问题诊断: {commodity} {contract} {date}")
  1156. print(f" 目标价格类型: {price_type}")
  1157. print(f" 该日所有价格数据: 开盘={row['open']}, 收盘={row['close']}, 最高={row['high']}, 最低={row['low']}, 成交量={row['volume']}")
  1158. # 检查是否所有价格都是NaN
  1159. price_fields = ['open', 'close', 'high', 'low']
  1160. nan_fields = [field for field in price_fields if pd.isna(row[field])]
  1161. valid_fields = [field for field in price_fields if not pd.isna(row[field])]
  1162. if len(nan_fields) == len(price_fields):
  1163. print(f" ❌ 所有价格字段都为NaN - 可能该合约在此日期未开始交易")
  1164. else:
  1165. print(f" ⚠️ 部分价格字段为NaN: {nan_fields}")
  1166. print(f" ✅ 有效价格字段: {valid_fields}")
  1167. # 如果有有效价格,尝试使用替代方案
  1168. if valid_fields:
  1169. fallback_price = row[valid_fields[0]]
  1170. print(f" 💡 建议使用替代价格: {valid_fields[0]} = {fallback_price}")
  1171. # 检查成交量是否为0或NaN
  1172. if pd.isna(row['volume']) or row['volume'] == 0:
  1173. print(f" ⚠️ 成交量异常: {row['volume']} - 可能该合约在此日期无交易活动")
  1174. # 检查是否是合约刚上市的情况
  1175. price_data = self.price_data[commodity][contract]
  1176. first_valid_date = None
  1177. for idx, data_row in price_data.iterrows():
  1178. if not pd.isna(data_row['close']):
  1179. first_valid_date = idx.date()
  1180. break
  1181. if first_valid_date and date < first_valid_date:
  1182. print(f" 🔍 合约首次有效交易日: {first_valid_date} (查询日期 {date} 早于首次交易日)")
  1183. print(f" 💡 建议: 合约 {contract} 在 {date} 可能尚未开始交易")
  1184. # 提供解决建议
  1185. print(f" 📋 解决建议:")
  1186. print(f" 1. 检查合约 {contract} 的上市日期")
  1187. print(f" 2. 验证合约代码是否正确")
  1188. print(f" 3. 考虑调整合约切换日期")
  1189. if valid_fields:
  1190. print(f" 4. 临时使用替代价格: {valid_fields[0]} = {row[valid_fields[0]]}")
  1191. def _get_nearest_trading_day_price(self, commodity, contract, target_date, price_type):
  1192. """获取最近交易日的价格"""
  1193. price_data = self.price_data[commodity][contract]
  1194. # 查找最近的交易日(前后5天范围内)
  1195. search_range = 5
  1196. for offset in range(1, search_range + 1):
  1197. # 先查找之后的日期
  1198. future_date = target_date + datetime.timedelta(days=offset)
  1199. for idx, row in price_data.iterrows():
  1200. if idx.date() == future_date:
  1201. price_value = row[price_type]
  1202. if not pd.isna(price_value):
  1203. if self.verbose_logging:
  1204. print(f" ✅ 使用后续交易日 {future_date} 的价格: {price_value}")
  1205. return price_value
  1206. break
  1207. # 再查找之前的日期
  1208. past_date = target_date - datetime.timedelta(days=offset)
  1209. for idx, row in price_data.iterrows():
  1210. if idx.date() == past_date:
  1211. price_value = row[price_type]
  1212. if not pd.isna(price_value):
  1213. if self.verbose_logging:
  1214. print(f" ✅ 使用前期交易日 {past_date} 的价格: {price_value}")
  1215. return price_value
  1216. break
  1217. if self.verbose_logging:
  1218. print(f" ❌ 在 {search_range} 天范围内未找到有效的 {price_type} 价格")
  1219. return None
  1220. def _process_daily_trading(self, current_date):
  1221. """处理每日的正常交易逻辑"""
  1222. for commodity in self.core_commodities.keys():
  1223. current_contract = self._get_current_contract(commodity, current_date)
  1224. if not current_contract:
  1225. continue
  1226. # 获取当日价格数据
  1227. daily_prices = self._get_daily_prices(commodity, current_contract, current_date)
  1228. if not daily_prices:
  1229. continue
  1230. # 检查基础头寸入场和退出机会
  1231. self._check_base_position_trading(commodity, current_contract, current_date, daily_prices)
  1232. # 检查网格交易入场和退出机会
  1233. self._check_grid_trading(commodity, current_contract, current_date, daily_prices)
  1234. # 检查网格对冲入场和退出机会
  1235. self._check_grid_hedge_trading(commodity, current_contract, current_date, daily_prices)
  1236. def _get_daily_prices(self, commodity, contract, date):
  1237. """获取指定日期的价格数据"""
  1238. if commodity not in self.price_data or contract not in self.price_data[commodity]:
  1239. return None
  1240. price_data = self.price_data[commodity][contract]
  1241. for idx, row in price_data.iterrows():
  1242. if idx.date() == date:
  1243. return {
  1244. 'open': row['open'],
  1245. 'close': row['close'],
  1246. 'high': row['high'],
  1247. 'low': row['low'],
  1248. 'volume': row['volume']
  1249. }
  1250. return None
  1251. def _check_base_position_trading(self, commodity, contract, current_date, daily_prices):
  1252. """检查基础头寸交易机会
  1253. 逻辑:每日主动检查所有网格水平
  1254. - 如果当前收盘价低于某个网格水平价格
  1255. - 且该网格水平没有未平仓头寸
  1256. - 则以当日收盘价在该网格水平开仓
  1257. """
  1258. if commodity not in self.base_position_grid:
  1259. return
  1260. # 检查入场机会 - 遍历所有网格水平
  1261. price_grid = self.base_position_grid[commodity]
  1262. current_close_price = daily_prices['close']
  1263. for entry_price, quantity in price_grid.items():
  1264. # 检查是否已经有这个价格水平的头寸
  1265. position_exists = False
  1266. if commodity in self.active_positions['base_position']:
  1267. for position in self.active_positions['base_position'][commodity].values():
  1268. if (position['contract'] == contract and position['status'] == 'open'):
  1269. # 检查原始价格水平
  1270. position_price_level = position.get('original_price_level', position['entry_price'])
  1271. if position_price_level == entry_price:
  1272. position_exists = True
  1273. break
  1274. # 主动开仓逻辑:当前价格低于网格水平价格 且 该水平没有头寸
  1275. if not position_exists and current_close_price < entry_price:
  1276. # 以当日收盘价建立头寸
  1277. position_id = f"{commodity}_{contract}_{current_date}_base_{entry_price}"
  1278. if commodity not in self.active_positions['base_position']:
  1279. self.active_positions['base_position'][commodity] = {}
  1280. self.active_positions['base_position'][commodity][position_id] = {
  1281. 'contract': contract,
  1282. 'entry_date': current_date.strftime('%Y-%m-%d'),
  1283. 'entry_price': current_close_price, # 使用收盘价作为入场价格
  1284. 'original_price_level': entry_price, # 记录原始网格水平
  1285. 'quantity': quantity,
  1286. 'status': 'open',
  1287. 'exit_target': self.base_position_exit_price.get(commodity)
  1288. }
  1289. if self.verbose_logging:
  1290. print(f" {current_date}: {commodity} 基础头寸入场 @ 网格{entry_price} (实际价格: {current_close_price:.2f}), 数量: {quantity},当天收盘价{current_close_price:.2f}低于网格价格{entry_price}")
  1291. # 检查退出机会
  1292. exit_target = self.base_position_exit_price.get(commodity)
  1293. if exit_target and commodity in self.active_positions['base_position']:
  1294. positions_to_close = []
  1295. for position_id, position in self.active_positions['base_position'][commodity].items():
  1296. if position['contract'] == contract and position['status'] == 'open' and daily_prices['high'] >= exit_target:
  1297. positions_to_close.append(position_id)
  1298. for position_id in positions_to_close:
  1299. position = self.active_positions['base_position'][commodity][position_id]
  1300. exit_price = min(exit_target, daily_prices['high'])
  1301. # 使用正确的期货盈亏计算公式(基础头寸都是多头)
  1302. profit_loss = self._calculate_futures_pnl(
  1303. position['entry_price'], exit_price, position['quantity'], commodity, is_long=True
  1304. )
  1305. profit_loss_pct = (exit_price - position['entry_price']) / position['entry_price']
  1306. trade_record = {
  1307. 'commodity': commodity,
  1308. 'contract': contract,
  1309. 'strategy': 'base_position',
  1310. 'entry_date': position['entry_date'],
  1311. 'exit_date': current_date.strftime('%Y-%m-%d'),
  1312. 'entry_price': position['entry_price'],
  1313. 'exit_price': exit_price,
  1314. 'quantity': position['quantity'],
  1315. 'profit_loss': profit_loss,
  1316. 'profit_loss_pct': profit_loss_pct,
  1317. 'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
  1318. 'exit_reason': 'target_reached'
  1319. }
  1320. self.trading_results['base_position'].append(trade_record)
  1321. self.active_positions['base_position'][commodity][position_id]['status'] = 'closed'
  1322. if self.verbose_logging:
  1323. print(f" {current_date}: {commodity} 基础头寸退出 {position['entry_price']} -> {exit_price:.2f}, 盈亏: {profit_loss:.2f}")
  1324. def _check_grid_trading(self, commodity, contract, current_date, daily_prices):
  1325. """检查网格交易机会"""
  1326. if commodity not in self.grid_trading_config:
  1327. return
  1328. config = self.grid_trading_config[commodity]
  1329. start_price = config['start_price']
  1330. grid_size = config['grid_size']
  1331. quantity_per_grid = config['quantity_per_grid']
  1332. exit_grid_size = config['exit_grid_size']
  1333. # 检查入场机会
  1334. current_level = start_price
  1335. while current_level > daily_prices['low'] and current_level > 0:
  1336. position_exists = False
  1337. if commodity in self.active_positions['grid_trading']:
  1338. for position in self.active_positions['grid_trading'][commodity].values():
  1339. if (position['contract'] == contract and
  1340. position.get('original_grid_level', position['entry_price']) == current_level and
  1341. position['status'] == 'open'):
  1342. position_exists = True
  1343. break
  1344. if not position_exists and daily_prices['low'] <= current_level <= daily_prices['high']:
  1345. position_id = f"{commodity}_{contract}_{current_date}_grid_{current_level}"
  1346. if commodity not in self.active_positions['grid_trading']:
  1347. self.active_positions['grid_trading'][commodity] = {}
  1348. self.active_positions['grid_trading'][commodity][position_id] = {
  1349. 'contract': contract,
  1350. 'entry_date': current_date.strftime('%Y-%m-%d'),
  1351. 'entry_price': current_level,
  1352. 'original_grid_level': current_level,
  1353. 'quantity': quantity_per_grid,
  1354. 'status': 'open',
  1355. 'exit_target': current_level + exit_grid_size
  1356. }
  1357. if self.verbose_logging:
  1358. print(f" {current_date}: {commodity} 网格入场 @ {current_level},数量:{quantity_per_grid},当天最低价为{daily_prices['low']},最高价为{daily_prices['high']}")
  1359. current_level -= grid_size
  1360. # 检查退出机会
  1361. if commodity in self.active_positions['grid_trading']:
  1362. positions_to_close = []
  1363. for position_id, position in self.active_positions['grid_trading'][commodity].items():
  1364. if position['contract'] == contract and position['status'] == 'open':
  1365. if daily_prices['high'] >= position['exit_target']:
  1366. positions_to_close.append(position_id)
  1367. for position_id in positions_to_close:
  1368. position = self.active_positions['grid_trading'][commodity][position_id]
  1369. exit_price = position['exit_target']
  1370. # 使用正确的期货盈亏计算公式(网格交易都是多头)
  1371. profit_loss = self._calculate_futures_pnl(
  1372. position['entry_price'], exit_price, position['quantity'], commodity, is_long=True
  1373. )
  1374. profit_loss_pct = (exit_price - position['entry_price']) / position['entry_price']
  1375. trade_record = {
  1376. 'commodity': commodity,
  1377. 'contract': contract,
  1378. 'strategy': 'grid_trading',
  1379. 'entry_date': position['entry_date'],
  1380. 'exit_date': current_date.strftime('%Y-%m-%d'),
  1381. 'entry_price': position['entry_price'],
  1382. 'exit_price': exit_price,
  1383. 'quantity': position['quantity'],
  1384. 'profit_loss': profit_loss,
  1385. 'profit_loss_pct': profit_loss_pct,
  1386. 'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
  1387. 'exit_reason': 'target_reached'
  1388. }
  1389. self.trading_results['grid_trading'].append(trade_record)
  1390. self.active_positions['grid_trading'][commodity][position_id]['status'] = 'closed'
  1391. if self.verbose_logging:
  1392. print(f" {current_date}: {commodity} 网格退出 {position['entry_price']} -> {exit_price:.2f}, 盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
  1393. def _extend_price_data_for_ma(self, commodity, contract, current_date, required_days=30):
  1394. """扩展价格数据以满足MA计算需求
  1395. 参数:
  1396. commodity: 商品代码
  1397. contract: 合约代码
  1398. current_date: 当前日期
  1399. required_days: 所需的最少数据天数
  1400. 返回:
  1401. 扩展后的价格数据DataFrame,如果获取失败则返回None
  1402. """
  1403. cache_key = f"{commodity}_{contract}"
  1404. # 检查缓存
  1405. if cache_key in self.ma_extended_data_cache:
  1406. cached_data = self.ma_extended_data_cache[cache_key]
  1407. target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
  1408. historical_data = cached_data[cached_data.index.date <= target_date]
  1409. if len(historical_data) >= required_days:
  1410. if self.verbose_logging:
  1411. print(f" ℹ️ MA过滤器:使用缓存的扩展数据,共{len(historical_data)}天")
  1412. return historical_data
  1413. # 获取现有数据
  1414. if commodity not in self.price_data or contract not in self.price_data[commodity]:
  1415. return None
  1416. existing_data = self.price_data[commodity][contract]
  1417. target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
  1418. existing_historical = existing_data[existing_data.index.date <= target_date]
  1419. if len(existing_historical) >= required_days:
  1420. return existing_historical
  1421. # 数据不足,需要扩展获取
  1422. existing_count = len(existing_historical)
  1423. shortage = required_days - existing_count
  1424. if self.verbose_logging:
  1425. print(f" 📊 MA过滤器:数据不足,开始扩展获取(当前{existing_count}天,需要{required_days}天,缺少{shortage}天)")
  1426. # 计算扩展的开始日期:从现有数据最早日期往前推至少30个交易日
  1427. if len(existing_data) > 0:
  1428. earliest_date = existing_data.index.min().date()
  1429. else:
  1430. earliest_date = target_date
  1431. # 往前推60个自然日(约等于40-45个交易日,提供充足缓冲)
  1432. extended_start_date = earliest_date - datetime.timedelta(days=60)
  1433. extended_end_date = target_date
  1434. if self.verbose_logging:
  1435. print(f" 📊 扩展日期范围: {extended_start_date} 至 {extended_end_date}")
  1436. try:
  1437. # 获取扩展的价格数据
  1438. extended_data = get_price(
  1439. contract,
  1440. start_date=extended_start_date,
  1441. end_date=extended_end_date,
  1442. frequency='daily',
  1443. fields=['open', 'close', 'high', 'low', 'volume'],
  1444. skip_paused=False,
  1445. panel=False
  1446. )
  1447. if extended_data is None or len(extended_data) == 0:
  1448. if self.verbose_logging:
  1449. print(f" ⚠️ 扩展数据获取失败:未获取到数据")
  1450. return existing_historical
  1451. # 合并数据:将扩展数据与现有数据合并,保留所有日期的数据
  1452. # 使用concat合并,然后去重并按日期排序
  1453. combined_data = pd.concat([existing_data, extended_data])
  1454. combined_data = combined_data[~combined_data.index.duplicated(keep='first')]
  1455. combined_data = combined_data.sort_index()
  1456. # 过滤到当前日期(仅用于返回给MA计算)
  1457. combined_historical = combined_data[combined_data.index.date <= target_date]
  1458. if self.verbose_logging:
  1459. print(f" ✅ 扩展数据获取成功:从{len(existing_historical)}天扩展到{len(combined_historical)}天")
  1460. print(f" 合并后完整数据范围:{combined_data.index.min().date()} 至 {combined_data.index.max().date()}(共{len(combined_data)}天)")
  1461. # 缓存扩展后的完整数据
  1462. self.ma_extended_data_cache[cache_key] = combined_data
  1463. # 更新主price_data,保留原有数据和新扩展的数据
  1464. self.price_data[commodity][contract] = combined_data
  1465. return combined_historical
  1466. except Exception as e:
  1467. if self.verbose_logging:
  1468. print(f" ⚠️ 扩展数据获取异常:{str(e)}")
  1469. return existing_historical
  1470. def _should_block_hedge_by_ma_filter(self, commodity, contract, current_date):
  1471. """检查是否应该通过移动平均线过滤器阻止对冲入场
  1472. 返回值:
  1473. True: 应该阻止对冲入场(满足以下趋势之一)
  1474. False: 允许对冲入场
  1475. 阻止条件(强下跌趋势):
  1476. 1. 30K < 20K < 10K < 5K(所有均线呈下降顺序)
  1477. 2. 30K < 20K < 5K < 10K(5K与10K交叉的下跌趋势)
  1478. """
  1479. try:
  1480. # 获取价格数据
  1481. if commodity not in self.price_data or contract not in self.price_data[commodity]:
  1482. if self.verbose_logging:
  1483. print(f" ⚠️ MA过滤器:无法获取 {commodity} {contract} 的价格数据")
  1484. return False # 没有数据时不阻止
  1485. price_data = self.price_data[commodity][contract]
  1486. # 获取当前日期及之前的数据(确保有足够的历史数据来计算30日均线)
  1487. target_date = current_date if isinstance(current_date, datetime.date) else current_date.date()
  1488. # 过滤出当前日期及之前的数据
  1489. historical_data = price_data[price_data.index.date <= target_date]
  1490. # 如果数据不足,尝试扩展获取
  1491. if len(historical_data) < 30:
  1492. if self.verbose_logging:
  1493. print(f" ⚠️ MA过滤器:{commodity} {contract} 历史数据不足(需要30天,实际{len(historical_data)}天)")
  1494. # 尝试扩展数据获取
  1495. extended_data = self._extend_price_data_for_ma(commodity, contract, current_date, required_days=30)
  1496. if extended_data is not None and len(extended_data) >= 30:
  1497. historical_data = extended_data
  1498. if self.verbose_logging:
  1499. print(f" ✅ 扩展数据成功,现在有{len(historical_data)}天数据,继续MA过滤器检查")
  1500. else:
  1501. if self.verbose_logging:
  1502. print(f" ⚠️ 扩展数据失败,数据仍不足,阻止对冲入场")
  1503. return True # 扩展失败,数据仍不足时阻止
  1504. # 使用收盘价计算移动平均线
  1505. close_prices = historical_data['close']
  1506. # 计算最近的移动平均线值
  1507. ma5 = close_prices.iloc[-5:].mean()
  1508. ma10 = close_prices.iloc[-10:].mean()
  1509. ma20 = close_prices.iloc[-20:].mean()
  1510. ma30 = close_prices.iloc[-30:].mean()
  1511. # 检查是否为NaN
  1512. if pd.isna(ma5) or pd.isna(ma10) or pd.isna(ma20) or pd.isna(ma30):
  1513. if self.verbose_logging:
  1514. print(f" ⚠️ MA过滤器:{commodity} {contract} 移动平均线计算结果包含NaN")
  1515. return True # 计算失败时阻止
  1516. # 检查阻止条件
  1517. # 条件1:30K < 20K < 10K < 5K(所有均线呈下降顺序的强上涨趋势)
  1518. condition1 = (ma30 < ma20) and (ma20 < ma10) and (ma10 < ma5)
  1519. # 条件2:30K < 20K < 5K < 10K(5K与10K交叉的上涨趋势)
  1520. condition2 = (ma30 < ma20) and (ma20 < ma5) and (ma5 < ma10)
  1521. should_block = condition1 or condition2
  1522. if self.verbose_logging and should_block:
  1523. print(f" 🚫 MA过滤器触发:{commodity} {contract} 检测到强上涨趋势,阻止对冲入场")
  1524. print(f" MA5={ma5:.2f}, MA10={ma10:.2f}, MA20={ma20:.2f}, MA30={ma30:.2f}")
  1525. if condition1:
  1526. print(f" 触发条件1: 30K < 20K < 10K < 5K")
  1527. if condition2:
  1528. print(f" 触发条件2: 30K < 20K < 5K < 10K")
  1529. return should_block
  1530. except Exception as e:
  1531. if self.verbose_logging:
  1532. print(f" ⚠️ MA过滤器异常:{commodity} {contract} - {str(e)}")
  1533. return True # 异常时阻止
  1534. def _check_grid_hedge_trading(self, commodity, contract, current_date, daily_prices):
  1535. """检查网格对冲交易机会
  1536. 逻辑:独立的对冲网格系统,不依赖主网格交易执行
  1537. - 基于主网格结构创建独立的对冲网格
  1538. - 对冲网格水平 = 主网格水平 + exit_grid_size
  1539. - 当价格突破网格水平时(如118→119),立即触发对冲空头头寸
  1540. - 对冲范围受 hedge_stop_price 限制,低于该价格不触发对冲
  1541. 示例:start_price=118, hedge_stop_price=113, exit_grid_size=1
  1542. - 主网格水平:118, 117, 116, 115, 114, 113, 112, ...
  1543. - 对冲触发价格:119, 118, 117, 116, 115, 114(对应主网格113-118)
  1544. - 低于113的主网格不触发对冲
  1545. """
  1546. if not self.grid_hedge_config['enable_hedge_strategy'] or commodity not in self.grid_trading_config:
  1547. return
  1548. config = self.grid_trading_config[commodity]
  1549. start_price = config['start_price']
  1550. grid_size = config['grid_size']
  1551. quantity_per_grid = config['quantity_per_grid']
  1552. exit_grid_size = config['exit_grid_size']
  1553. hedge_stop_price = config.get('hedge_stop_price', 0) # 对冲停止价格,默认为0(不限制)
  1554. stop_loss_pct = self.grid_hedge_config['stop_loss_percentage']
  1555. # 检查入场机会(独立对冲网格系统)
  1556. # 从start_price开始,向下遍历所有可能的网格级别
  1557. # 只有在 hedge_stop_price 及以上的网格级别才会触发对冲
  1558. current_grid_level = start_price
  1559. while current_grid_level >= hedge_stop_price and current_grid_level > 0:
  1560. # 计算该网格级别对应的对冲开仓价格(独立触发,不依赖主网格交易)
  1561. hedge_entry_level = current_grid_level + exit_grid_size
  1562. # 检查是否已经有这个对冲级别的头寸
  1563. position_exists = False
  1564. if commodity in self.active_positions['grid_hedge']:
  1565. for position in self.active_positions['grid_hedge'][commodity].values():
  1566. if (position['contract'] == contract and
  1567. position.get('original_grid_level', position['entry_price']) == hedge_entry_level and
  1568. position['status'] == 'open'):
  1569. position_exists = True
  1570. break
  1571. # 当价格触及对冲级别时独立开仓(不需要等待主网格交易)
  1572. if not position_exists and daily_prices['low'] <= hedge_entry_level <= daily_prices['high']:
  1573. # 检查MA过滤器:如果启用且检测到强上涨趋势,则阻止对冲入场
  1574. if self.grid_hedge_config.get('use_ma_filter', False):
  1575. if self._should_block_hedge_by_ma_filter(commodity, contract, current_date):
  1576. if self.verbose_logging:
  1577. print(f" {current_date}: {commodity} 对冲入场被MA过滤器阻止 @ {hedge_entry_level}")
  1578. # 跳过此次对冲入场
  1579. current_grid_level -= grid_size
  1580. continue
  1581. position_id = f"{commodity}_{contract}_{current_date}_hedge_{hedge_entry_level}"
  1582. if commodity not in self.active_positions['grid_hedge']:
  1583. self.active_positions['grid_hedge'][commodity] = {}
  1584. self.active_positions['grid_hedge'][commodity][position_id] = {
  1585. 'contract': contract,
  1586. 'entry_date': current_date.strftime('%Y-%m-%d'),
  1587. 'entry_price': hedge_entry_level,
  1588. 'original_grid_level': hedge_entry_level,
  1589. 'base_grid_level': current_grid_level, # 记录对应的基础网格级别
  1590. 'quantity': quantity_per_grid,
  1591. 'status': 'open',
  1592. 'profit_target': hedge_entry_level - grid_size, # 对冲平仓目标:回到原网格级别
  1593. 'stop_loss': hedge_entry_level * (1 + stop_loss_pct)
  1594. }
  1595. if self.verbose_logging:
  1596. print(f" {current_date}: {commodity} 对冲入场 @ {hedge_entry_level}(对应网格级别{current_grid_level}),数量:{quantity_per_grid},目标:{hedge_entry_level - grid_size},当天价格区间[{daily_prices['low']}, {daily_prices['high']}]")
  1597. current_grid_level -= grid_size
  1598. # 检查退出机会
  1599. if commodity in self.active_positions['grid_hedge']:
  1600. positions_to_close = []
  1601. for position_id, position in self.active_positions['grid_hedge'][commodity].items():
  1602. if position['contract'] == contract and position['status'] == 'open':
  1603. if daily_prices['low'] <= position['profit_target']:
  1604. positions_to_close.append((position_id, position['profit_target'], 'profit_target'))
  1605. elif daily_prices['high'] >= position['stop_loss']:
  1606. positions_to_close.append((position_id, position['stop_loss'], 'stop_loss'))
  1607. for position_id, exit_price, exit_reason in positions_to_close:
  1608. position = self.active_positions['grid_hedge'][commodity][position_id]
  1609. # 使用正确的期货盈亏计算公式(网格对冲是做空)
  1610. profit_loss = self._calculate_futures_pnl(
  1611. position['entry_price'], exit_price, position['quantity'], commodity, is_long=False
  1612. )
  1613. profit_loss_pct = (position['entry_price'] - exit_price) / position['entry_price']
  1614. trade_record = {
  1615. 'commodity': commodity,
  1616. 'contract': contract,
  1617. 'strategy': 'grid_hedge',
  1618. 'entry_date': position['entry_date'],
  1619. 'exit_date': current_date.strftime('%Y-%m-%d'),
  1620. 'entry_price': position['entry_price'],
  1621. 'exit_price': exit_price,
  1622. 'quantity': position['quantity'],
  1623. 'profit_loss': profit_loss,
  1624. 'profit_loss_pct': profit_loss_pct,
  1625. 'days_held': (current_date - datetime.datetime.strptime(position['entry_date'], '%Y-%m-%d').date()).days,
  1626. 'exit_reason': exit_reason
  1627. }
  1628. self.trading_results['grid_hedge'].append(trade_record)
  1629. self.active_positions['grid_hedge'][commodity][position_id]['status'] = 'closed'
  1630. if self.verbose_logging:
  1631. print(f" {current_date}: {commodity} 对冲退出 {position['entry_price']} -> {exit_price:.2f} ({exit_reason}),数量:{position['quantity']},当天最低价为{daily_prices['low']},最高价为{daily_prices['high']}")
  1632. def simulate_base_position_trading(self):
  1633. """
  1634. 模拟基础头寸交易
  1635. 为每种商品配置价格-数量网格,以指定价格水平和数量开立多头头寸
  1636. 所有头寸使用统一的退出价格(无止损)
  1637. """
  1638. if self.verbose_logging:
  1639. print("\n=== 步骤3: 基础头寸交易模拟 ===")
  1640. base_position_results = []
  1641. for commodity in self.price_data.keys():
  1642. if commodity not in self.base_position_grid:
  1643. continue
  1644. price_grid = self.base_position_grid[commodity]
  1645. exit_price = self.base_position_exit_price[commodity]
  1646. price_data = self.price_data[commodity]['data']
  1647. contract = self.price_data[commodity]['contract']
  1648. if self.verbose_logging:
  1649. print(f"\n分析 {commodity} ({contract}) 基础头寸交易")
  1650. print(f"价格网格: {price_grid}")
  1651. print(f"退出价格: {exit_price}")
  1652. # 遍历每个价格水平
  1653. for entry_price, quantity in price_grid.items():
  1654. # 查找触发入场的日期
  1655. entry_dates = []
  1656. for date, row in price_data.iterrows():
  1657. if row['low'] <= entry_price <= row['high']:
  1658. entry_dates.append(date)
  1659. if not entry_dates:
  1660. continue
  1661. # 使用第一个触发日期作为入场点
  1662. entry_date = entry_dates[0]
  1663. # 查找退出点
  1664. exit_date = None
  1665. exit_price_actual = exit_price
  1666. # 在入场后查找价格达到退出价格的日期
  1667. for date, row in price_data.iterrows():
  1668. if date > entry_date and row['high'] >= exit_price:
  1669. exit_date = date
  1670. exit_price_actual = min(exit_price, row['high'])
  1671. break
  1672. # 如果没有达到退出价格,使用最后一日的收盘价退出
  1673. if exit_date is None:
  1674. exit_date = price_data.index[-1]
  1675. exit_price_actual = price_data.iloc[-1]['close']
  1676. # 计算盈亏
  1677. profit_loss = (exit_price_actual - entry_price) * quantity
  1678. profit_loss_pct = (exit_price_actual - entry_price) / entry_price
  1679. trade_record = {
  1680. 'commodity': commodity,
  1681. 'contract': contract,
  1682. 'strategy': 'base_position',
  1683. 'entry_date': entry_date.strftime('%Y-%m-%d'),
  1684. 'exit_date': exit_date.strftime('%Y-%m-%d'),
  1685. 'entry_price': entry_price,
  1686. 'exit_price': exit_price_actual,
  1687. 'quantity': quantity,
  1688. 'profit_loss': profit_loss,
  1689. 'profit_loss_pct': profit_loss_pct,
  1690. 'days_held': (exit_date - entry_date).days
  1691. }
  1692. base_position_results.append(trade_record)
  1693. if self.verbose_logging:
  1694. print(f" 入场: {entry_date.strftime('%Y-%m-%d')} @ {entry_price}, 数量: {quantity}")
  1695. print(f" 出场: {exit_date.strftime('%Y-%m-%d')} @ {exit_price_actual:.2f}")
  1696. print(f" 盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
  1697. self.trading_results['base_position'] = base_position_results
  1698. if self.verbose_logging:
  1699. print(f"\n基础头寸交易模拟完成,共{len(base_position_results)}笔交易")
  1700. return base_position_results
  1701. def simulate_grid_trading(self):
  1702. """
  1703. 模拟网格交易策略
  1704. 从start_price开始,每次价格下降grid_size时买入quantity_per_grid
  1705. 当价格从入场价格上涨exit_grid_size时退出
  1706. """
  1707. if self.verbose_logging:
  1708. print("\n=== 步骤4: 网格交易策略模拟 ===")
  1709. grid_trading_results = []
  1710. for commodity in self.price_data.keys():
  1711. if commodity not in self.grid_trading_config:
  1712. continue
  1713. config = self.grid_trading_config[commodity]
  1714. start_price = config['start_price']
  1715. grid_size = config['grid_size']
  1716. quantity_per_grid = config['quantity_per_grid']
  1717. exit_grid_size = config['exit_grid_size']
  1718. price_data = self.price_data[commodity]['data']
  1719. contract = self.price_data[commodity]['contract']
  1720. if self.verbose_logging:
  1721. print(f"\n分析 {commodity} ({contract}) 网格交易")
  1722. print(f"起始价格: {start_price}, 网格大小: {grid_size}")
  1723. print(f"每网格数量: {quantity_per_grid}, 退出网格大小: {exit_grid_size}")
  1724. # 生成网格价格水平
  1725. grid_levels = []
  1726. current_level = start_price
  1727. min_price = price_data['low'].min()
  1728. while current_level > min_price:
  1729. grid_levels.append(current_level)
  1730. current_level -= grid_size
  1731. # 模拟每个网格水平的交易
  1732. for entry_price in grid_levels:
  1733. exit_price = entry_price + exit_grid_size
  1734. # 查找入场机会
  1735. entry_date = None
  1736. for date, row in price_data.iterrows():
  1737. if row['low'] <= entry_price <= row['high']:
  1738. entry_date = date
  1739. break
  1740. if entry_date is None:
  1741. continue
  1742. # 查找退出机会
  1743. exit_date = None
  1744. exit_price_actual = exit_price
  1745. for date, row in price_data.iterrows():
  1746. if date > entry_date and row['high'] >= exit_price:
  1747. exit_date = date
  1748. exit_price_actual = exit_price
  1749. break
  1750. # 如果没有达到退出价格,使用最后一日收盘价
  1751. if exit_date is None:
  1752. exit_date = price_data.index[-1]
  1753. exit_price_actual = price_data.iloc[-1]['close']
  1754. # 计算盈亏
  1755. profit_loss = (exit_price_actual - entry_price) * quantity_per_grid
  1756. profit_loss_pct = (exit_price_actual - entry_price) / entry_price
  1757. trade_record = {
  1758. 'commodity': commodity,
  1759. 'contract': contract,
  1760. 'strategy': 'grid_trading',
  1761. 'entry_date': entry_date.strftime('%Y-%m-%d'),
  1762. 'exit_date': exit_date.strftime('%Y-%m-%d'),
  1763. 'entry_price': entry_price,
  1764. 'exit_price': exit_price_actual,
  1765. 'quantity': quantity_per_grid,
  1766. 'profit_loss': profit_loss,
  1767. 'profit_loss_pct': profit_loss_pct,
  1768. 'days_held': (exit_date - entry_date).days
  1769. }
  1770. grid_trading_results.append(trade_record)
  1771. if self.verbose_logging:
  1772. print(f" 网格 {entry_price}: {entry_date.strftime('%Y-%m-%d')} -> {exit_date.strftime('%Y-%m-%d')}")
  1773. print(f" 盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
  1774. self.trading_results['grid_trading'] = grid_trading_results
  1775. if self.verbose_logging:
  1776. print(f"\n网格交易模拟完成,共{len(grid_trading_results)}笔交易")
  1777. return grid_trading_results
  1778. def simulate_grid_hedge_strategy(self):
  1779. """
  1780. 模拟网格对冲策略
  1781. 类似于网格交易,但有额外的止损百分比
  1782. """
  1783. if not self.grid_hedge_config['enable_hedge_strategy']:
  1784. if self.verbose_logging:
  1785. print("\n网格对冲策略已禁用,跳过模拟")
  1786. return []
  1787. if self.verbose_logging:
  1788. print("\n=== 步骤5: 网格对冲策略模拟 ===")
  1789. grid_hedge_results = []
  1790. stop_loss_pct = self.grid_hedge_config['stop_loss_percentage']
  1791. for commodity in self.price_data.keys():
  1792. if commodity not in self.grid_trading_config:
  1793. continue
  1794. config = self.grid_trading_config[commodity]
  1795. start_price = config['start_price']
  1796. grid_size = config['grid_size']
  1797. quantity_per_grid = config['quantity_per_grid']
  1798. exit_grid_size = config['exit_grid_size']
  1799. price_data = self.price_data[commodity]['data']
  1800. contract = self.price_data[commodity]['contract']
  1801. if self.verbose_logging:
  1802. print(f"\n分析 {commodity} ({contract}) 网格对冲策略")
  1803. print(f"起始价格: {start_price}, 网格大小: {grid_size}")
  1804. print(f"止损百分比: {stop_loss_pct:.1%}")
  1805. # 生成网格价格水平(对冲策略:在start_price + grid_size入场)
  1806. grid_levels = []
  1807. current_level = start_price + grid_size
  1808. max_price = price_data['high'].max()
  1809. while current_level < max_price:
  1810. grid_levels.append(current_level)
  1811. current_level += grid_size
  1812. # 模拟每个网格水平的对冲交易
  1813. for entry_price in grid_levels:
  1814. profit_target = entry_price - grid_size # 获利了结
  1815. stop_loss = entry_price * (1 + stop_loss_pct) # 止损
  1816. # 查找入场机会
  1817. entry_date = None
  1818. for date, row in price_data.iterrows():
  1819. if row['low'] <= entry_price <= row['high']:
  1820. entry_date = date
  1821. break
  1822. if entry_date is None:
  1823. continue
  1824. # 查找退出机会(获利或止损)
  1825. exit_date = None
  1826. exit_price_actual = None
  1827. exit_reason = 'hold'
  1828. for date, row in price_data.iterrows():
  1829. if date <= entry_date:
  1830. continue
  1831. # 检查止损
  1832. if row['high'] >= stop_loss:
  1833. exit_date = date
  1834. exit_price_actual = stop_loss
  1835. exit_reason = 'stop_loss'
  1836. break
  1837. # 检查获利了结
  1838. if row['low'] <= profit_target:
  1839. exit_date = date
  1840. exit_price_actual = profit_target
  1841. exit_reason = 'profit_target'
  1842. break
  1843. # 如果没有触发退出条件,使用最后一日收盘价
  1844. if exit_date is None:
  1845. exit_date = price_data.index[-1]
  1846. exit_price_actual = price_data.iloc[-1]['close']
  1847. exit_reason = 'end_of_data'
  1848. # 计算盈亏(做空,所以是entry_price - exit_price)
  1849. profit_loss = (entry_price - exit_price_actual) * quantity_per_grid
  1850. profit_loss_pct = (entry_price - exit_price_actual) / entry_price
  1851. trade_record = {
  1852. 'commodity': commodity,
  1853. 'contract': contract,
  1854. 'strategy': 'grid_hedge',
  1855. 'entry_date': entry_date.strftime('%Y-%m-%d'),
  1856. 'exit_date': exit_date.strftime('%Y-%m-%d'),
  1857. 'entry_price': entry_price,
  1858. 'exit_price': exit_price_actual,
  1859. 'quantity': quantity_per_grid,
  1860. 'profit_loss': profit_loss,
  1861. 'profit_loss_pct': profit_loss_pct,
  1862. 'days_held': (exit_date - entry_date).days,
  1863. 'exit_reason': exit_reason
  1864. }
  1865. grid_hedge_results.append(trade_record)
  1866. if self.verbose_logging:
  1867. print(f" 对冲网格 {entry_price}: {entry_date.strftime('%Y-%m-%d')} -> {exit_date.strftime('%Y-%m-%d')}")
  1868. print(f" 退出原因: {exit_reason}, 盈亏: {profit_loss:.2f} ({profit_loss_pct:.2%})")
  1869. self.trading_results['grid_hedge'] = grid_hedge_results
  1870. if self.verbose_logging:
  1871. print(f"\n网格对冲策略模拟完成,共{len(grid_hedge_results)}笔交易")
  1872. return grid_hedge_results
  1873. def simulate_combined_strategy(self):
  1874. """
  1875. 模拟组合策略:网格交易 + 网格对冲策略
  1876. """
  1877. if self.verbose_logging:
  1878. print("\n=== 步骤5: 组合策略模拟 ===")
  1879. # 简单将网格交易和网格对冲的结果合并
  1880. grid_results = self.trading_results['grid_trading']
  1881. hedge_results = self.trading_results['grid_hedge']
  1882. combined_results = []
  1883. # 添加网格交易结果
  1884. for record in grid_results:
  1885. combined_record = record.copy()
  1886. combined_record['strategy'] = 'combined_grid'
  1887. combined_results.append(combined_record)
  1888. # 添加网格对冲结果
  1889. for record in hedge_results:
  1890. combined_record = record.copy()
  1891. combined_record['strategy'] = 'combined_hedge'
  1892. combined_results.append(combined_record)
  1893. self.trading_results['combined'] = combined_results
  1894. if self.verbose_logging:
  1895. print(f"组合策略模拟完成,共{len(combined_results)}笔交易")
  1896. print(f" 其中网格交易: {len(grid_results)}笔")
  1897. print(f" 其中网格对冲: {len(hedge_results)}笔")
  1898. return combined_results
  1899. def calculate_performance_statistics(self):
  1900. """
  1901. 计算多品种多级聚合的性能统计
  1902. 包括品种-策略级、品种级、策略级和总体级统计
  1903. """
  1904. if self.verbose_logging:
  1905. print("\n=== 步骤6: 多级性能统计分析 ===")
  1906. # 多级统计结构
  1907. performance_stats = {
  1908. 'by_commodity_strategy': {}, # 品种-策略级统计
  1909. 'by_commodity': {}, # 品种级汇总
  1910. 'by_strategy': {}, # 策略级汇总
  1911. 'overall': {} # 总体汇总
  1912. }
  1913. if self.verbose_logging:
  1914. print("\n--- 第一级:品种-策略级统计 ---")
  1915. # 第一步:计算品种-策略级统计
  1916. for strategy_name, results in self.trading_results.items():
  1917. if strategy_name not in performance_stats['by_commodity_strategy']:
  1918. performance_stats['by_commodity_strategy'][strategy_name] = {}
  1919. # 按品种分组交易结果
  1920. commodity_results = {}
  1921. for result in results:
  1922. commodity = result['commodity']
  1923. if commodity not in commodity_results:
  1924. commodity_results[commodity] = []
  1925. commodity_results[commodity].append(result)
  1926. # 为每个品种计算统计
  1927. for commodity in self.core_commodities.keys():
  1928. comm_results = commodity_results.get(commodity, [])
  1929. stats = self._calculate_single_strategy_stats(strategy_name, comm_results, commodity)
  1930. performance_stats['by_commodity_strategy'][strategy_name][commodity] = stats
  1931. if self.verbose_logging:
  1932. print(f"\n{commodity}-{strategy_name} 策略统计:")
  1933. self._print_strategy_stats(stats)
  1934. if self.verbose_logging:
  1935. print("\n--- 第二级:品种级汇总统计 ---")
  1936. # 第二步:计算品种级汇总统计
  1937. for commodity in self.core_commodities.keys():
  1938. commodity_stats = self._calculate_commodity_summary(commodity, performance_stats['by_commodity_strategy'])
  1939. performance_stats['by_commodity'][commodity] = commodity_stats
  1940. if self.verbose_logging:
  1941. print(f"\n{commodity} 品种汇总统计:")
  1942. self._print_strategy_stats(commodity_stats)
  1943. if self.verbose_logging:
  1944. print("\n--- 第三级:策略级汇总统计 ---")
  1945. # 第三步:计算策略级汇总统计
  1946. for strategy_name in self.trading_results.keys():
  1947. strategy_stats = self._calculate_strategy_summary(strategy_name, performance_stats['by_commodity_strategy'])
  1948. performance_stats['by_strategy'][strategy_name] = strategy_stats
  1949. if self.verbose_logging:
  1950. print(f"\n{strategy_name} 策略汇总统计:")
  1951. self._print_strategy_stats(strategy_stats)
  1952. if self.verbose_logging:
  1953. print("\n--- 第四级:整体汇总统计 ---")
  1954. # 第四步:计算总体统计
  1955. overall_stats = self._calculate_overall_summary(performance_stats['by_strategy'])
  1956. performance_stats['overall'] = overall_stats
  1957. if self.verbose_logging:
  1958. print(f"\n整体汇总统计:")
  1959. self._print_strategy_stats(overall_stats)
  1960. return performance_stats
  1961. def _calculate_single_strategy_stats(self, strategy_name, results, commodity):
  1962. """计算单个策略在特定品种下的统计数据"""
  1963. # 计算已平仓交易的盈亏
  1964. closed_profit_loss = sum(r['profit_loss'] for r in results) if results else 0.0
  1965. # 计算未平仓头寸的未实现盈亏(特定品种)
  1966. unrealized_profit_loss = self._calculate_unrealized_pnl_for_commodity(strategy_name, commodity)
  1967. # 总盈亏 = 已实现盈亏 + 未实现盈亏
  1968. total_profit_loss = closed_profit_loss + unrealized_profit_loss
  1969. if not results and unrealized_profit_loss == 0:
  1970. return {
  1971. 'total_trades': 0,
  1972. 'open_positions': 0,
  1973. 'profitable_trades': 0,
  1974. 'losing_trades': 0,
  1975. 'win_rate': 0.0,
  1976. 'closed_profit_loss': 0.0,
  1977. 'unrealized_profit_loss': 0.0,
  1978. 'total_profit_loss': 0.0,
  1979. 'avg_profit_loss': 0.0,
  1980. 'avg_profit_loss_pct': 0.0,
  1981. 'max_profit': 0.0,
  1982. 'max_loss': 0.0,
  1983. 'avg_holding_days': 0.0,
  1984. 'profit_factor': 0.0
  1985. }
  1986. # 基本统计
  1987. total_trades = len(results)
  1988. open_positions = self._count_open_positions_for_commodity(strategy_name, commodity)
  1989. profitable_trades = sum(1 for r in results if r['profit_loss'] > 0)
  1990. losing_trades = sum(1 for r in results if r['profit_loss'] < 0)
  1991. win_rate = profitable_trades / total_trades if total_trades > 0 else 0
  1992. # 平均盈亏(基于已平仓交易)
  1993. avg_profit_loss = closed_profit_loss / total_trades if total_trades > 0 else 0
  1994. avg_profit_loss_pct = sum(r['profit_loss_pct'] for r in results) / total_trades if total_trades > 0 else 0
  1995. # 最大盈亏(基于已平仓交易)
  1996. profit_losses = [r['profit_loss'] for r in results]
  1997. max_profit = max(profit_losses) if profit_losses else 0
  1998. max_loss = min(profit_losses) if profit_losses else 0
  1999. # 平均持有天数
  2000. avg_holding_days = sum(r['days_held'] for r in results) / total_trades if total_trades > 0 else 0
  2001. # 盈亏比(基于已平仓交易)
  2002. total_profits = sum(r['profit_loss'] for r in results if r['profit_loss'] > 0)
  2003. total_losses = abs(sum(r['profit_loss'] for r in results if r['profit_loss'] < 0))
  2004. profit_factor = total_profits / total_losses if total_losses > 0 else float('inf') if total_profits > 0 else 0
  2005. return {
  2006. 'total_trades': total_trades,
  2007. 'open_positions': open_positions,
  2008. 'profitable_trades': profitable_trades,
  2009. 'losing_trades': losing_trades,
  2010. 'win_rate': win_rate,
  2011. 'closed_profit_loss': closed_profit_loss,
  2012. 'unrealized_profit_loss': unrealized_profit_loss,
  2013. 'total_profit_loss': total_profit_loss,
  2014. 'avg_profit_loss': avg_profit_loss,
  2015. 'avg_profit_loss_pct': avg_profit_loss_pct,
  2016. 'max_profit': max_profit,
  2017. 'max_loss': max_loss,
  2018. 'avg_holding_days': avg_holding_days,
  2019. 'profit_factor': profit_factor
  2020. }
  2021. def _print_strategy_stats(self, stats):
  2022. """打印策略统计信息"""
  2023. print(f" 已平仓交易: {stats['total_trades']}")
  2024. print(f" 未平仓头寸: {stats['open_positions']}")
  2025. print(f" 盈利交易: {stats['profitable_trades']}")
  2026. print(f" 亏损交易: {stats['losing_trades']}")
  2027. print(f" 胜率: {stats['win_rate']:.2%}")
  2028. print(f" 已实现盈亏: {stats['closed_profit_loss']:.2f}")
  2029. print(f" 未实现盈亏: {stats['unrealized_profit_loss']:.2f}")
  2030. print(f" 总盈亏: {stats['total_profit_loss']:.2f}")
  2031. print(f" 平均盈亏: {stats['avg_profit_loss']:.2f}")
  2032. print(f" 平均盈亏率: {stats['avg_profit_loss_pct']:.2%}")
  2033. print(f" 最大盈利: {stats['max_profit']:.2f}")
  2034. print(f" 最大亏损: {stats['max_loss']:.2f}")
  2035. print(f" 平均持有天数: {stats['avg_holding_days']:.1f}")
  2036. profit_factor_str = f"{stats['profit_factor']:.2f}" if stats['profit_factor'] != float('inf') else "∞"
  2037. print(f" 盈亏比: {profit_factor_str}")
  2038. def _calculate_commodity_summary(self, commodity, by_commodity_strategy):
  2039. """计算品种级汇总统计"""
  2040. total_stats = {
  2041. 'total_trades': 0,
  2042. 'open_positions': 0,
  2043. 'profitable_trades': 0,
  2044. 'losing_trades': 0,
  2045. 'closed_profit_loss': 0.0,
  2046. 'unrealized_profit_loss': 0.0,
  2047. 'total_profit_loss': 0.0,
  2048. 'max_profit': 0.0,
  2049. 'max_loss': 0.0,
  2050. 'total_holding_days': 0.0,
  2051. 'total_profits': 0.0,
  2052. 'total_losses': 0.0,
  2053. 'all_pct': []
  2054. }
  2055. for strategy_name, commodity_stats in by_commodity_strategy.items():
  2056. if commodity in commodity_stats:
  2057. stats = commodity_stats[commodity]
  2058. total_stats['total_trades'] += stats['total_trades']
  2059. total_stats['open_positions'] += stats['open_positions']
  2060. total_stats['profitable_trades'] += stats['profitable_trades']
  2061. total_stats['losing_trades'] += stats['losing_trades']
  2062. total_stats['closed_profit_loss'] += stats['closed_profit_loss']
  2063. total_stats['unrealized_profit_loss'] += stats['unrealized_profit_loss']
  2064. total_stats['total_profit_loss'] += stats['total_profit_loss']
  2065. total_stats['max_profit'] = max(total_stats['max_profit'], stats['max_profit'])
  2066. total_stats['max_loss'] = min(total_stats['max_loss'], stats['max_loss'])
  2067. total_stats['total_holding_days'] += stats['avg_holding_days'] * stats['total_trades']
  2068. if stats['profit_factor'] != float('inf'):
  2069. profits = stats['closed_profit_loss'] if stats['closed_profit_loss'] > 0 else 0
  2070. losses = abs(stats['closed_profit_loss']) if stats['closed_profit_loss'] < 0 else 0
  2071. total_stats['total_profits'] += profits
  2072. total_stats['total_losses'] += losses
  2073. # 收集所有百分比数据
  2074. if stats['total_trades'] > 0:
  2075. total_stats['all_pct'].extend([stats['avg_profit_loss_pct']] * stats['total_trades'])
  2076. # 计算汇总指标
  2077. win_rate = total_stats['profitable_trades'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
  2078. avg_profit_loss = total_stats['closed_profit_loss'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
  2079. avg_profit_loss_pct = sum(total_stats['all_pct']) / len(total_stats['all_pct']) if total_stats['all_pct'] else 0
  2080. avg_holding_days = total_stats['total_holding_days'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
  2081. profit_factor = total_stats['total_profits'] / total_stats['total_losses'] if total_stats['total_losses'] > 0 else float('inf') if total_stats['total_profits'] > 0 else 0
  2082. return {
  2083. 'total_trades': total_stats['total_trades'],
  2084. 'open_positions': total_stats['open_positions'],
  2085. 'profitable_trades': total_stats['profitable_trades'],
  2086. 'losing_trades': total_stats['losing_trades'],
  2087. 'win_rate': win_rate,
  2088. 'closed_profit_loss': total_stats['closed_profit_loss'],
  2089. 'unrealized_profit_loss': total_stats['unrealized_profit_loss'],
  2090. 'total_profit_loss': total_stats['total_profit_loss'],
  2091. 'avg_profit_loss': avg_profit_loss,
  2092. 'avg_profit_loss_pct': avg_profit_loss_pct,
  2093. 'max_profit': total_stats['max_profit'],
  2094. 'max_loss': total_stats['max_loss'],
  2095. 'avg_holding_days': avg_holding_days,
  2096. 'profit_factor': profit_factor
  2097. }
  2098. def _calculate_strategy_summary(self, strategy_name, by_commodity_strategy):
  2099. """计算策略级汇总统计"""
  2100. if strategy_name not in by_commodity_strategy:
  2101. return self._get_empty_stats()
  2102. strategy_stats = by_commodity_strategy[strategy_name]
  2103. total_stats = {
  2104. 'total_trades': 0,
  2105. 'open_positions': 0,
  2106. 'profitable_trades': 0,
  2107. 'losing_trades': 0,
  2108. 'closed_profit_loss': 0.0,
  2109. 'unrealized_profit_loss': 0.0,
  2110. 'total_profit_loss': 0.0,
  2111. 'max_profit': 0.0,
  2112. 'max_loss': 0.0,
  2113. 'total_holding_days': 0.0,
  2114. 'total_profits': 0.0,
  2115. 'total_losses': 0.0,
  2116. 'all_pct': []
  2117. }
  2118. for commodity, stats in strategy_stats.items():
  2119. total_stats['total_trades'] += stats['total_trades']
  2120. total_stats['open_positions'] += stats['open_positions']
  2121. total_stats['profitable_trades'] += stats['profitable_trades']
  2122. total_stats['losing_trades'] += stats['losing_trades']
  2123. total_stats['closed_profit_loss'] += stats['closed_profit_loss']
  2124. total_stats['unrealized_profit_loss'] += stats['unrealized_profit_loss']
  2125. total_stats['total_profit_loss'] += stats['total_profit_loss']
  2126. total_stats['max_profit'] = max(total_stats['max_profit'], stats['max_profit'])
  2127. total_stats['max_loss'] = min(total_stats['max_loss'], stats['max_loss'])
  2128. total_stats['total_holding_days'] += stats['avg_holding_days'] * stats['total_trades']
  2129. if stats['profit_factor'] != float('inf'):
  2130. profits = stats['closed_profit_loss'] if stats['closed_profit_loss'] > 0 else 0
  2131. losses = abs(stats['closed_profit_loss']) if stats['closed_profit_loss'] < 0 else 0
  2132. total_stats['total_profits'] += profits
  2133. total_stats['total_losses'] += losses
  2134. # 收集所有百分比数据
  2135. if stats['total_trades'] > 0:
  2136. total_stats['all_pct'].extend([stats['avg_profit_loss_pct']] * stats['total_trades'])
  2137. # 计算汇总指标
  2138. win_rate = total_stats['profitable_trades'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
  2139. avg_profit_loss = total_stats['closed_profit_loss'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
  2140. avg_profit_loss_pct = sum(total_stats['all_pct']) / len(total_stats['all_pct']) if total_stats['all_pct'] else 0
  2141. avg_holding_days = total_stats['total_holding_days'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
  2142. profit_factor = total_stats['total_profits'] / total_stats['total_losses'] if total_stats['total_losses'] > 0 else float('inf') if total_stats['total_profits'] > 0 else 0
  2143. return {
  2144. 'total_trades': total_stats['total_trades'],
  2145. 'open_positions': total_stats['open_positions'],
  2146. 'profitable_trades': total_stats['profitable_trades'],
  2147. 'losing_trades': total_stats['losing_trades'],
  2148. 'win_rate': win_rate,
  2149. 'closed_profit_loss': total_stats['closed_profit_loss'],
  2150. 'unrealized_profit_loss': total_stats['unrealized_profit_loss'],
  2151. 'total_profit_loss': total_stats['total_profit_loss'],
  2152. 'avg_profit_loss': avg_profit_loss,
  2153. 'avg_profit_loss_pct': avg_profit_loss_pct,
  2154. 'max_profit': total_stats['max_profit'],
  2155. 'max_loss': total_stats['max_loss'],
  2156. 'avg_holding_days': avg_holding_days,
  2157. 'profit_factor': profit_factor
  2158. }
  2159. def _calculate_overall_summary(self, by_strategy):
  2160. """计算总体汇总统计"""
  2161. total_stats = {
  2162. 'total_trades': 0,
  2163. 'open_positions': 0,
  2164. 'profitable_trades': 0,
  2165. 'losing_trades': 0,
  2166. 'closed_profit_loss': 0.0,
  2167. 'unrealized_profit_loss': 0.0,
  2168. 'total_profit_loss': 0.0,
  2169. 'max_profit': 0.0,
  2170. 'max_loss': 0.0,
  2171. 'total_holding_days': 0.0,
  2172. 'total_profits': 0.0,
  2173. 'total_losses': 0.0,
  2174. 'all_pct': []
  2175. }
  2176. for strategy_name, stats in by_strategy.items():
  2177. total_stats['total_trades'] += stats['total_trades']
  2178. total_stats['open_positions'] += stats['open_positions']
  2179. total_stats['profitable_trades'] += stats['profitable_trades']
  2180. total_stats['losing_trades'] += stats['losing_trades']
  2181. total_stats['closed_profit_loss'] += stats['closed_profit_loss']
  2182. total_stats['unrealized_profit_loss'] += stats['unrealized_profit_loss']
  2183. total_stats['total_profit_loss'] += stats['total_profit_loss']
  2184. total_stats['max_profit'] = max(total_stats['max_profit'], stats['max_profit'])
  2185. total_stats['max_loss'] = min(total_stats['max_loss'], stats['max_loss'])
  2186. total_stats['total_holding_days'] += stats['avg_holding_days'] * stats['total_trades']
  2187. if stats['profit_factor'] != float('inf'):
  2188. profits = stats['closed_profit_loss'] if stats['closed_profit_loss'] > 0 else 0
  2189. losses = abs(stats['closed_profit_loss']) if stats['closed_profit_loss'] < 0 else 0
  2190. total_stats['total_profits'] += profits
  2191. total_stats['total_losses'] += losses
  2192. # 收集所有百分比数据
  2193. if stats['total_trades'] > 0:
  2194. total_stats['all_pct'].extend([stats['avg_profit_loss_pct']] * stats['total_trades'])
  2195. # 计算汇总指标
  2196. win_rate = total_stats['profitable_trades'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
  2197. avg_profit_loss = total_stats['closed_profit_loss'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
  2198. avg_profit_loss_pct = sum(total_stats['all_pct']) / len(total_stats['all_pct']) if total_stats['all_pct'] else 0
  2199. avg_holding_days = total_stats['total_holding_days'] / total_stats['total_trades'] if total_stats['total_trades'] > 0 else 0
  2200. profit_factor = total_stats['total_profits'] / total_stats['total_losses'] if total_stats['total_losses'] > 0 else float('inf') if total_stats['total_profits'] > 0 else 0
  2201. return {
  2202. 'total_trades': total_stats['total_trades'],
  2203. 'open_positions': total_stats['open_positions'],
  2204. 'profitable_trades': total_stats['profitable_trades'],
  2205. 'losing_trades': total_stats['losing_trades'],
  2206. 'win_rate': win_rate,
  2207. 'closed_profit_loss': total_stats['closed_profit_loss'],
  2208. 'unrealized_profit_loss': total_stats['unrealized_profit_loss'],
  2209. 'total_profit_loss': total_stats['total_profit_loss'],
  2210. 'avg_profit_loss': avg_profit_loss,
  2211. 'avg_profit_loss_pct': avg_profit_loss_pct,
  2212. 'max_profit': total_stats['max_profit'],
  2213. 'max_loss': total_stats['max_loss'],
  2214. 'avg_holding_days': avg_holding_days,
  2215. 'profit_factor': profit_factor
  2216. }
  2217. def _get_empty_stats(self):
  2218. """返回空的统计数据"""
  2219. return {
  2220. 'total_trades': 0,
  2221. 'open_positions': 0,
  2222. 'profitable_trades': 0,
  2223. 'losing_trades': 0,
  2224. 'win_rate': 0.0,
  2225. 'closed_profit_loss': 0.0,
  2226. 'unrealized_profit_loss': 0.0,
  2227. 'total_profit_loss': 0.0,
  2228. 'avg_profit_loss': 0.0,
  2229. 'avg_profit_loss_pct': 0.0,
  2230. 'max_profit': 0.0,
  2231. 'max_loss': 0.0,
  2232. 'avg_holding_days': 0.0,
  2233. 'profit_factor': 0.0
  2234. }
  2235. def _calculate_unrealized_pnl_for_commodity(self, strategy_name, commodity):
  2236. """计算特定品种未平仓头寸的未实现盈亏"""
  2237. if strategy_name == 'combined':
  2238. return (self._calculate_unrealized_pnl_for_commodity('grid_trading', commodity) +
  2239. self._calculate_unrealized_pnl_for_commodity('grid_hedge', commodity))
  2240. unrealized_pnl = 0.0
  2241. strategy_positions = self.active_positions.get(strategy_name, {})
  2242. if commodity in strategy_positions:
  2243. current_contract = self._get_current_contract(commodity, self.end_date.date())
  2244. if not current_contract:
  2245. return 0.0
  2246. end_price = self._get_price_on_date(commodity, current_contract, self.end_date.date(), 'close')
  2247. if end_price is None:
  2248. return 0.0
  2249. positions = strategy_positions[commodity]
  2250. for position_id, position in positions.items():
  2251. if position['status'] == 'open' and position['contract'] == current_contract:
  2252. if strategy_name == 'grid_hedge':
  2253. pnl = self._calculate_futures_pnl(
  2254. position['entry_price'], end_price, position['quantity'], commodity, is_long=False
  2255. )
  2256. else:
  2257. pnl = self._calculate_futures_pnl(
  2258. position['entry_price'], end_price, position['quantity'], commodity, is_long=True
  2259. )
  2260. unrealized_pnl += pnl
  2261. return unrealized_pnl
  2262. def _count_open_positions_for_commodity(self, strategy_name, commodity):
  2263. """计算特定品种的未平仓头寸数量"""
  2264. if strategy_name == 'combined':
  2265. return (self._count_open_positions_for_commodity('grid_trading', commodity) +
  2266. self._count_open_positions_for_commodity('grid_hedge', commodity))
  2267. count = 0
  2268. strategy_positions = self.active_positions.get(strategy_name, {})
  2269. if commodity in strategy_positions:
  2270. positions = strategy_positions[commodity]
  2271. for position_id, position in positions.items():
  2272. if position['status'] == 'open':
  2273. count += 1
  2274. return count
  2275. def _calculate_unrealized_pnl(self, strategy_name):
  2276. """
  2277. 计算未平仓头寸的未实现盈亏
  2278. """
  2279. unrealized_pnl = 0.0
  2280. if strategy_name == 'combined':
  2281. # 组合策略的未实现盈亏是网格交易和网格对冲的总和
  2282. return (self._calculate_unrealized_pnl('grid_trading') +
  2283. self._calculate_unrealized_pnl('grid_hedge'))
  2284. # 获取策略对应的头寸字典
  2285. strategy_positions = self.active_positions.get(strategy_name, {})
  2286. for commodity, positions in strategy_positions.items():
  2287. # 获取当前合约和最新价格
  2288. current_contract = self._get_current_contract(commodity, self.end_date.date())
  2289. if not current_contract:
  2290. continue
  2291. # 获取结束日期的收盘价
  2292. end_price = self._get_price_on_date(commodity, current_contract, self.end_date.date(), 'close')
  2293. if end_price is None:
  2294. continue
  2295. for position_id, position in positions.items():
  2296. if position['status'] == 'open' and position['contract'] == current_contract:
  2297. # 使用正确的期货盈亏计算公式
  2298. if strategy_name == 'grid_hedge':
  2299. # 网格对冲是做空
  2300. pnl = self._calculate_futures_pnl(
  2301. position['entry_price'], end_price, position['quantity'], commodity, is_long=False
  2302. )
  2303. else:
  2304. # 基础头寸和网格交易都是做多
  2305. pnl = self._calculate_futures_pnl(
  2306. position['entry_price'], end_price, position['quantity'], commodity, is_long=True
  2307. )
  2308. unrealized_pnl += pnl
  2309. return unrealized_pnl
  2310. def _count_open_positions(self, strategy_name):
  2311. """
  2312. 计算未平仓头寸数量
  2313. """
  2314. if strategy_name == 'combined':
  2315. return (self._count_open_positions('grid_trading') +
  2316. self._count_open_positions('grid_hedge'))
  2317. count = 0
  2318. strategy_positions = self.active_positions.get(strategy_name, {})
  2319. for commodity, positions in strategy_positions.items():
  2320. for position_id, position in positions.items():
  2321. if position['status'] == 'open':
  2322. count += 1
  2323. return count
  2324. def generate_comparison_report(self, performance_stats):
  2325. """
  2326. 生成多级聚合的对比报告
  2327. 包括品种-策略级、品种级、策略级和总体级报告
  2328. """
  2329. if self.verbose_logging:
  2330. print("\n=== 多级聚合对比分析报告 ===")
  2331. strategies = ['base_position', 'grid_trading', 'grid_hedge', 'combined']
  2332. strategy_names = {
  2333. 'base_position': '基础头寸交易',
  2334. 'grid_trading': '网格交易',
  2335. 'grid_hedge': '网格对冲策略',
  2336. 'combined': '组合策略'
  2337. }
  2338. all_comparison_data = {
  2339. 'by_commodity_strategy': [],
  2340. 'by_commodity': [],
  2341. 'by_strategy': [],
  2342. 'overall': []
  2343. }
  2344. # 1. 品种-策略级对比报告
  2345. if self.verbose_logging:
  2346. print("\n--- 品种-策略级对比 ---")
  2347. by_comm_strategy_data = []
  2348. for strategy in strategies:
  2349. for commodity in self.core_commodities.keys():
  2350. stats = performance_stats['by_commodity_strategy'].get(strategy, {}).get(commodity, {})
  2351. if stats.get('total_trades', 0) > 0 or stats.get('open_positions', 0) > 0:
  2352. by_comm_strategy_data.append({
  2353. '品种': commodity,
  2354. '策略': strategy_names[strategy],
  2355. '已平仓': stats.get('total_trades', 0),
  2356. '未平仓': stats.get('open_positions', 0),
  2357. '胜率': f"{stats.get('win_rate', 0):.2%}",
  2358. '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
  2359. '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
  2360. '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
  2361. '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
  2362. '最大盈利': f"{stats.get('max_profit', 0):.2f}",
  2363. '最大亏损': f"{stats.get('max_loss', 0):.2f}",
  2364. '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
  2365. '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
  2366. })
  2367. if by_comm_strategy_data and self.verbose_logging:
  2368. df_comm_strategy = pd.DataFrame(by_comm_strategy_data)
  2369. print(df_comm_strategy.to_string(index=False))
  2370. all_comparison_data['by_commodity_strategy'] = by_comm_strategy_data
  2371. # 2. 品种级汇总对比报告
  2372. if self.verbose_logging:
  2373. print("\n--- 品种级汇总对比 ---")
  2374. by_commodity_data = []
  2375. for commodity in self.core_commodities.keys():
  2376. stats = performance_stats['by_commodity'].get(commodity, {})
  2377. if stats.get('total_trades', 0) > 0 or stats.get('open_positions', 0) > 0:
  2378. by_commodity_data.append({
  2379. '品种': commodity,
  2380. '已平仓': stats.get('total_trades', 0),
  2381. '未平仓': stats.get('open_positions', 0),
  2382. '胜率': f"{stats.get('win_rate', 0):.2%}",
  2383. '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
  2384. '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
  2385. '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
  2386. '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
  2387. '最大盈利': f"{stats.get('max_profit', 0):.2f}",
  2388. '最大亏损': f"{stats.get('max_loss', 0):.2f}",
  2389. '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
  2390. '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
  2391. })
  2392. if by_commodity_data and self.verbose_logging:
  2393. df_commodity = pd.DataFrame(by_commodity_data)
  2394. print(df_commodity.to_string(index=False))
  2395. all_comparison_data['by_commodity'] = by_commodity_data
  2396. # 3. 策略级汇总对比报告
  2397. if self.verbose_logging:
  2398. print("\n--- 策略级汇总对比 ---")
  2399. by_strategy_data = []
  2400. for strategy in strategies:
  2401. stats = performance_stats['by_strategy'].get(strategy, {})
  2402. by_strategy_data.append({
  2403. '策略': strategy_names[strategy],
  2404. '已平仓': stats.get('total_trades', 0),
  2405. '未平仓': stats.get('open_positions', 0),
  2406. '胜率': f"{stats.get('win_rate', 0):.2%}",
  2407. '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
  2408. '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
  2409. '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
  2410. '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
  2411. '最大盈利': f"{stats.get('max_profit', 0):.2f}",
  2412. '最大亏损': f"{stats.get('max_loss', 0):.2f}",
  2413. '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
  2414. '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
  2415. })
  2416. if by_strategy_data and self.verbose_logging:
  2417. df_strategy = pd.DataFrame(by_strategy_data)
  2418. print(df_strategy.to_string(index=False))
  2419. all_comparison_data['by_strategy'] = by_strategy_data
  2420. # 4. 总体汇总报告
  2421. if self.verbose_logging:
  2422. print("\n--- 整体汇总 ---")
  2423. overall_data = []
  2424. stats = performance_stats['overall']
  2425. overall_data.append({
  2426. '项目': '整体表现',
  2427. '已平仓': stats.get('total_trades', 0),
  2428. '未平仓': stats.get('open_positions', 0),
  2429. '胜率': f"{stats.get('win_rate', 0):.2%}",
  2430. '已实现': f"{stats.get('closed_profit_loss', 0):.2f}",
  2431. '未实现': f"{stats.get('unrealized_profit_loss', 0):.2f}",
  2432. '总盈亏': f"{stats.get('total_profit_loss', 0):.2f}",
  2433. '平均盈亏': f"{stats.get('avg_profit_loss', 0):.2f}",
  2434. '最大盈利': f"{stats.get('max_profit', 0):.2f}",
  2435. '最大亏损': f"{stats.get('max_loss', 0):.2f}",
  2436. '平均天数': f"{stats.get('avg_holding_days', 0):.1f}",
  2437. '盈亏比': f"{stats.get('profit_factor', 0):.2f}" if stats.get('profit_factor', 0) != float('inf') else "∞"
  2438. })
  2439. if overall_data and self.verbose_logging:
  2440. df_overall = pd.DataFrame(overall_data)
  2441. print(df_overall.to_string(index=False))
  2442. all_comparison_data['overall'] = overall_data
  2443. return all_comparison_data
  2444. def generate_csv_output(self, performance_stats):
  2445. """
  2446. 生成CSV输出文件
  2447. """
  2448. if self.verbose_logging:
  2449. print("\n=== 步骤8: 生成CSV输出文件 ===")
  2450. timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
  2451. # 1. 生成交易记录CSV
  2452. all_trades = []
  2453. for strategy_name, trades in self.trading_results.items():
  2454. all_trades.extend(trades)
  2455. if all_trades:
  2456. df_trades = pd.DataFrame(all_trades)
  2457. # 添加列顺序,确保合约切换相关字段在前面
  2458. column_order = ['commodity', 'contract', 'strategy', 'entry_date', 'exit_date',
  2459. 'entry_price', 'exit_price', 'quantity', 'profit_loss', 'profit_loss_pct',
  2460. 'days_held', 'exit_reason']
  2461. # 重新排列DataFrame的列顺序
  2462. existing_columns = [col for col in column_order if col in df_trades.columns]
  2463. other_columns = [col for col in df_trades.columns if col not in column_order]
  2464. df_trades = df_trades[existing_columns + other_columns]
  2465. trades_filename = f'grid_trading_records_{timestamp}.csv'
  2466. df_trades.to_csv(trades_filename, index=False, encoding=self.output_encoding)
  2467. if self.verbose_logging:
  2468. print(f"交易记录已保存至: {trades_filename}")
  2469. # 2. 生成多级性能统计CSV
  2470. csv_files = []
  2471. # 2.1 品种-策略级统计CSV
  2472. by_comm_strategy_data = []
  2473. for strategy, commodity_data in performance_stats['by_commodity_strategy'].items():
  2474. for commodity, stats in commodity_data.items():
  2475. stats_record = {
  2476. '品种': commodity,
  2477. '策略': strategy,
  2478. **stats
  2479. }
  2480. by_comm_strategy_data.append(stats_record)
  2481. if by_comm_strategy_data:
  2482. df_comm_strategy = pd.DataFrame(by_comm_strategy_data)
  2483. comm_strategy_filename = f'grid_trading_by_commodity_strategy_{timestamp}.csv'
  2484. df_comm_strategy.to_csv(comm_strategy_filename, index=False, encoding=self.output_encoding)
  2485. csv_files.append(comm_strategy_filename)
  2486. if self.verbose_logging:
  2487. print(f"品种-策略级统计已保存至: {comm_strategy_filename}")
  2488. # 2.2 品种级汇总统计CSV
  2489. by_commodity_data = []
  2490. for commodity, stats in performance_stats['by_commodity'].items():
  2491. stats_record = {
  2492. '品种': commodity,
  2493. **stats
  2494. }
  2495. by_commodity_data.append(stats_record)
  2496. if by_commodity_data:
  2497. df_commodity = pd.DataFrame(by_commodity_data)
  2498. commodity_filename = f'grid_trading_by_commodity_{timestamp}.csv'
  2499. df_commodity.to_csv(commodity_filename, index=False, encoding=self.output_encoding)
  2500. csv_files.append(commodity_filename)
  2501. if self.verbose_logging:
  2502. print(f"品种级汇总统计已保存至: {commodity_filename}")
  2503. # 2.3 策略级汇总统计CSV
  2504. by_strategy_data = []
  2505. for strategy, stats in performance_stats['by_strategy'].items():
  2506. stats_record = {
  2507. '策略': strategy,
  2508. **stats
  2509. }
  2510. by_strategy_data.append(stats_record)
  2511. if by_strategy_data:
  2512. df_strategy = pd.DataFrame(by_strategy_data)
  2513. strategy_filename = f'grid_trading_by_strategy_{timestamp}.csv'
  2514. df_strategy.to_csv(strategy_filename, index=False, encoding=self.output_encoding)
  2515. csv_files.append(strategy_filename)
  2516. if self.verbose_logging:
  2517. print(f"策略级汇总统计已保存至: {strategy_filename}")
  2518. # 2.4 整体汇总统计CSV
  2519. overall_data = [{
  2520. '项目': '整体汇总',
  2521. **performance_stats['overall']
  2522. }]
  2523. if overall_data:
  2524. df_overall = pd.DataFrame(overall_data)
  2525. overall_filename = f'grid_trading_overall_{timestamp}.csv'
  2526. df_overall.to_csv(overall_filename, index=False, encoding=self.output_encoding)
  2527. csv_files.append(overall_filename)
  2528. if self.verbose_logging:
  2529. print(f"整体汇总统计已保存至: {overall_filename}")
  2530. return trades_filename if all_trades else None, csv_files
  2531. def run_complete_analysis(self):
  2532. """执行完整的网格交易分析流程(带主力合约切换)"""
  2533. if self.verbose_logging:
  2534. print("开始执行期货网格交易分析(带主力合约切换)")
  2535. print("=" * 60)
  2536. try:
  2537. # 步骤1: 合约选择
  2538. self.select_contracts()
  2539. if not self.selected_contracts:
  2540. if self.verbose_logging:
  2541. print("未选择到有效合约,分析终止")
  2542. return None
  2543. # 步骤2: 构建主力合约历史变化
  2544. self.build_dominant_contract_history()
  2545. if not self.dominant_contract_history:
  2546. if self.verbose_logging:
  2547. print("未获取到主力合约历史,分析终止")
  2548. return None
  2549. # 步骤3: 收集价格数据
  2550. self.collect_price_data()
  2551. if not self.price_data:
  2552. if self.verbose_logging:
  2553. print("未获取到有效价格数据,分析终止")
  2554. return None
  2555. # 步骤4: 带合约切换的交易模拟
  2556. self.simulate_with_contract_switching()
  2557. # 步骤5: 组合策略模拟
  2558. self.simulate_combined_strategy()
  2559. # 步骤6: 性能统计分析
  2560. performance_stats = self.calculate_performance_statistics()
  2561. # 步骤7: 生成对比报告
  2562. comparison_report = self.generate_comparison_report(performance_stats)
  2563. # 步骤8: 生成CSV输出
  2564. # trades_file, stats_files = self.generate_csv_output(performance_stats)
  2565. # 分析汇总
  2566. total_commodities = len(self.selected_contracts)
  2567. total_trades = sum(len(trades) for trades in self.trading_results.values())
  2568. contract_switches = sum(len(history) for history in self.dominant_contract_history.values())
  2569. if self.verbose_logging:
  2570. print("\n" + "=" * 60)
  2571. print("分析完成汇总:")
  2572. print(f"分析商品数: {total_commodities}")
  2573. print(f"合约切换次数: {contract_switches}")
  2574. print(f"总交易笔数: {total_trades}")
  2575. # print(f"交易记录文件: {trades_file}")
  2576. # print(f"性能统计文件: {stats_file}")
  2577. return {
  2578. 'selected_contracts': self.selected_contracts,
  2579. 'dominant_contract_history': self.dominant_contract_history,
  2580. 'price_data': self.price_data,
  2581. 'trading_results': self.trading_results,
  2582. 'performance_stats': performance_stats,
  2583. 'comparison_report': comparison_report,
  2584. # 'output_files': {
  2585. # 'trades_file': trades_file,
  2586. # 'stats_files': stats_files
  2587. # },
  2588. 'summary': {
  2589. 'total_commodities': total_commodities,
  2590. 'contract_switches': contract_switches,
  2591. 'total_trades': total_trades
  2592. }
  2593. }
  2594. except Exception as e:
  2595. if self.verbose_logging:
  2596. print(f"分析过程中出现错误: {str(e)}")
  2597. import traceback
  2598. traceback.print_exc()
  2599. return None
  2600. # =====================================================================================
  2601. # 主程序入口
  2602. # =====================================================================================
  2603. def run_grid_trading_analysis(config=None):
  2604. """运行期货网格交易分析"""
  2605. if config is None:
  2606. config = GridTradingConfig
  2607. # 打印配置信息
  2608. config.print_config()
  2609. # 创建分析器并运行
  2610. analyzer = FutureGridTradingAnalyzer(config)
  2611. results = analyzer.run_complete_analysis()
  2612. return results
  2613. # 执行分析
  2614. if __name__ == "__main__":
  2615. print("期货网格交易研究分析工具(带主力合约切换)")
  2616. print("研究期货网格交易策略在不同配置下的表现")
  2617. print("核心功能包括主力合约自动切换、强制平仓和重新建仓逻辑")
  2618. print("")
  2619. print("支持四种交易场景的对比分析:")
  2620. print(" 1. 基础头寸交易 - 价格-数量网格配置")
  2621. print(" 2. 网格交易策略 - 限价订单网格买入卖出")
  2622. print(" 3. 网格对冲策略 - 带止损的增强网格交易")
  2623. print(" 4. 组合策略 - 网格交易+网格对冲组合")
  2624. print("")
  2625. print("主要特点:")
  2626. print(" - 主力合约自动监控:每日检测主力合约变化")
  2627. print(" - 强制平仓机制:合约切换时立即平掉旧合约所有头寸")
  2628. print(" - 智能重新建仓:根据价格条件在新合约中重新建立头寸")
  2629. print(" - 完整交易记录:记录所有交易包括合约切换引起的强制平仓")
  2630. print("")
  2631. print("适用于聚宽在线研究平台")
  2632. results = run_grid_trading_analysis()
  2633. if results:
  2634. print("\n✅ 分析执行成功!")
  2635. summary = results['summary']
  2636. print(f"📊 结果摘要:")
  2637. print(f" - 分析商品数: {summary['total_commodities']}")
  2638. print(f" - 合约切换次数: {summary['contract_switches']}")
  2639. print(f" - 总交易笔数: {summary['total_trades']}")
  2640. print(f" - 整体总盈亏: {results['performance_stats']['overall']['total_profit_loss']:.2f}")
  2641. print(f" - 整体胜率: {results['performance_stats']['overall']['win_rate']:.2%}")
  2642. print(f" - 未平仓头寸: {results['performance_stats']['overall']['open_positions']}")
  2643. print(f"📂 输出文件:")
  2644. print(f" - 交易记录文件: {results['output_files']['trades_file']}")
  2645. for i, stats_file in enumerate(results['output_files']['stats_files'], 1):
  2646. print(f" - 统计文件{i}: {stats_file}")
  2647. print(f"\n📈 多级汇总:")
  2648. # 品种级汇总简要显示
  2649. print(f" 品种表现:")
  2650. for commodity in ['SA', 'M']:
  2651. comm_stats = results['performance_stats']['by_commodity'].get(commodity, {})
  2652. if comm_stats.get('total_trades', 0) > 0 or comm_stats.get('open_positions', 0) > 0:
  2653. print(f" {commodity}: 交易{comm_stats.get('total_trades', 0)}笔, 盈亏{comm_stats.get('total_profit_loss', 0):.2f}, 胜率{comm_stats.get('win_rate', 0):.2%}")
  2654. # 策略级汇总简要显示
  2655. print(f" 策略表现:")
  2656. strategy_names = {
  2657. 'base_position': '基础头寸',
  2658. 'grid_trading': '网格交易',
  2659. 'grid_hedge': '网格对冲',
  2660. 'combined': '组合策略'
  2661. }
  2662. for strategy, name in strategy_names.items():
  2663. strat_stats = results['performance_stats']['by_strategy'].get(strategy, {})
  2664. print(f" {name}: 交易{strat_stats.get('total_trades', 0)}笔, 盈亏{strat_stats.get('total_profit_loss', 0):.2f}, 胜率{strat_stats.get('win_rate', 0):.2%}")
  2665. else:
  2666. print("\n❌ 分析执行失败,请检查错误信息")