future_grid_trading_analysis2.py 183 KB

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