Просмотр исходного кода

Evaluate layered BB squeeze position variants

lxy 3 недель назад
Родитель
Сommit
a025e40361

BIN
reports/eth-exploration/layered-bb-squeeze-position-equity.csv


+ 24 - 0
reports/eth-exploration/layered-bb-squeeze-position-events.csv

@@ -0,0 +1,24 @@
+variant,entry_events,middle_exit_events,stop_events,t_reduce_events,t_readd_events
+baseline,1622,1085,537,0,0
+dynamic_exposure,1622,1085,537,0,0
+dynamic_vol_only,1622,1085,537,0,0
+dynamic_ratio_soft,1622,1085,537,0,0
+regime_directional,1208,812,396,0,0
+regime_vol_scaled,1622,1085,537,0,0
+t_overlay_p012_f20_never,1622,1085,537,839,0
+t_overlay_p012_f30,1622,1085,537,884,445
+t_overlay_p012_f30_never,1622,1085,537,839,0
+t_overlay_p018_f20_middle_trend,1622,1085,537,753,252
+t_overlay_p018_f30,1622,1085,537,768,387
+t_overlay_p024_f20_never,1622,1085,537,655,0
+dynamic_t_p012_f30,1622,1085,537,884,444
+dynamic_t_p018_f20_middle_trend,1622,1085,537,753,252
+vol_dynamic_t_p024_f20_never,1622,1085,537,655,0
+ratio_dynamic_t_p024_f20_never,1622,1085,537,655,0
+regime_directional_dynamic,1208,812,396,0,0
+regime_directional_t_p012_f30,1208,812,396,633,324
+layered_directional_dynamic_t_p012_f30,1208,812,396,633,324
+layered_vol_dynamic_t_p012_f30,1622,1085,537,880,434
+layered_vol_dynamic_t_p018_f20_middle_trend,1622,1085,537,751,244
+layered_vol_t_p024_f20_never,1622,1085,537,655,0
+layered_vol_ratio_t_p024_f20_never,1622,1085,537,655,0

+ 162 - 0
reports/eth-exploration/layered-bb-squeeze-position-horizons.csv

@@ -0,0 +1,162 @@
+variant,horizon,start,end,total_return,annualized_return,max_drawdown,calmar,trades,trades_per_30d,win_rate,profit_factor
+baseline,full,2020-01-04 00:00,2026-05-19 08:30,3.754391599334882,0.2769947309613683,0.9460905268197939,0.29277825230156723,1622,20.90786211094501,0.28175092478421704,1.1521229865756015
+baseline,3y,2023-05-19 08:30,2026-05-19 08:30,3.8151086447363785,0.6878263328427929,0.6741193609408634,1.0203331526968737,749,20.501824817518248,0.28437917222963954,1.2428387050716823
+baseline,1y,2025-05-19 08:30,2026-05-19 08:30,3.750688985155435,3.750688985155435,0.4001942731265516,9.372170560695084,250,20.54794520547945,0.348,1.546014007675074
+baseline,6m,2025-11-19 08:30,2026-05-19 08:30,2.3724798833648153,10.605110436197682,0.2790647594175302,38.00232769745951,124,20.552486187845304,0.33064516129032256,1.8503355399167825
+baseline,3m,2026-02-19 08:30,2026-05-19 08:30,0.9230343901221716,13.6105308396119,0.21414150892558723,63.558582863733676,64,21.57303370786517,0.375,1.9703580595438641
+baseline,30d,2026-04-19 08:30,2026-05-19 08:30,0.02728638918339721,0.38754474732799693,0.17665849909791037,2.1937509336202723,22,22.0,0.3181818181818182,1.638357369407204
+baseline,14d,2026-05-05 08:30,2026-05-19 08:30,-0.06506701408726945,-0.8269365167854368,0.13570966072238133,-6.093424096587236,11,23.57142857142857,0.18181818181818182,0.5990869866211429
+dynamic_exposure,full,2020-01-04 00:00,2026-05-19 08:30,4.372306031921977,0.3017015970488881,0.9220647173487678,0.32720219239748927,1622,20.90786211094501,0.28175092478421704,1.1521229865756015
+dynamic_exposure,3y,2023-05-19 08:30,2026-05-19 08:30,4.013130100832524,0.7106325439206687,0.62342089270355,1.1398920893377722,749,20.501824817518248,0.28437917222963954,1.242838705071682
+dynamic_exposure,1y,2025-05-19 08:30,2026-05-19 08:30,3.672166500324698,3.672166500324698,0.3246926521529839,11.309669239433546,250,20.54794520547945,0.348,1.5460140076750737
+dynamic_exposure,6m,2025-11-19 08:30,2026-05-19 08:30,2.2300839193855837,9.638186976769001,0.27440240365926405,35.12428042990872,124,20.552486187845304,0.33064516129032256,1.8503355399167833
+dynamic_exposure,3m,2026-02-19 08:30,2026-05-19 08:30,0.942167380283218,14.2159546682101,0.21414150892558714,66.38579666098298,64,21.57303370786517,0.375,1.9703580595438648
+dynamic_exposure,30d,2026-04-19 08:30,2026-05-19 08:30,0.02728638918339721,0.38754474732799693,0.17665849909791026,2.1937509336202736,22,22.0,0.3181818181818182,1.638357369407204
+dynamic_exposure,14d,2026-05-05 08:30,2026-05-19 08:30,-0.06506701408726956,-0.8269365167854373,0.13570966072238128,-6.093424096587243,11,23.57142857142857,0.18181818181818182,0.5990869866211425
+dynamic_vol_only,full,2020-01-04 00:00,2026-05-19 08:30,3.930933699036144,0.28431747584759637,0.9409858586712201,0.3021485107641099,1622,20.90786211094501,0.28175092478421704,1.1521229865756013
+dynamic_vol_only,3y,2023-05-19 08:30,2026-05-19 08:30,3.8919854160666496,0.6967532398329974,0.6690686836149881,1.0413777492445608,749,20.501824817518248,0.28437917222963954,1.2428387050716818
+dynamic_vol_only,1y,2025-05-19 08:30,2026-05-19 08:30,3.695782030371598,3.695782030371598,0.3954950652126873,9.344698216105678,250,20.54794520547945,0.348,1.546014007675074
+dynamic_vol_only,6m,2025-11-19 08:30,2026-05-19 08:30,2.28579396812894,10.011430839156976,0.2742828560757186,36.500388622150034,124,20.552486187845304,0.33064516129032256,1.850335539916783
+dynamic_vol_only,3m,2026-02-19 08:30,2026-05-19 08:30,0.9357897168962033,14.012079113239446,0.21414150892558736,65.43373670776059,64,21.57303370786517,0.375,1.9703580595438637
+dynamic_vol_only,30d,2026-04-19 08:30,2026-05-19 08:30,0.02728638918339721,0.38754474732799693,0.17665849909791037,2.1937509336202723,22,22.0,0.3181818181818182,1.638357369407203
+dynamic_vol_only,14d,2026-05-05 08:30,2026-05-19 08:30,-0.06506701408726945,-0.8269365167854368,0.13570966072238141,-6.093424096587232,11,23.57142857142857,0.18181818181818182,0.5990869866211429
+dynamic_ratio_soft,full,2020-01-04 00:00,2026-05-19 08:30,3.6020511503685286,0.270489185114859,0.9385293495500662,0.28820535579897666,1622,20.90786211094501,0.28175092478421704,1.1521229865756017
+dynamic_ratio_soft,3y,2023-05-19 08:30,2026-05-19 08:30,3.2723111855524785,0.6219189647938879,0.6465541405484017,0.9618977372357117,749,20.501824817518248,0.28437917222963954,1.2428387050716823
+dynamic_ratio_soft,1y,2025-05-19 08:30,2026-05-19 08:30,3.7047912168091095,3.7047912168091095,0.39770721335786785,9.315373451563316,250,20.54794520547945,0.348,1.5460140076750744
+dynamic_ratio_soft,6m,2025-11-19 08:30,2026-05-19 08:30,2.224533986087022,9.601359122326029,0.2359276758227118,40.69619678507317,124,20.552486187845304,0.33064516129032256,1.850335539916783
+dynamic_ratio_soft,3m,2026-02-19 08:30,2026-05-19 08:30,0.8050614383653745,10.269518112448411,0.21414150892558736,47.95669071341509,64,21.57303370786517,0.375,1.970358059543864
+dynamic_ratio_soft,30d,2026-04-19 08:30,2026-05-19 08:30,0.02244887962143105,0.3101052119989207,0.16828442270496424,1.8427446047255145,22,22.0,0.3181818181818182,1.6383573694072056
+dynamic_ratio_soft,14d,2026-05-05 08:30,2026-05-19 08:30,-0.06946962985904626,-0.8469755528823082,0.12691910016049285,-6.673349809534446,11,23.57142857142857,0.18181818181818182,0.5990869866211415
+regime_directional,full,2020-01-04 00:00,2026-05-19 08:30,1.7216582220835912,0.170025014604692,0.9183827763314653,0.1851352387986489,1208,15.57133010482218,0.26572847682119205,1.1385882212708867
+regime_directional,3y,2023-05-19 08:30,2026-05-19 08:30,1.2347354371985588,0.30708097367822207,0.5353375910906923,0.5736211668838309,571,15.629562043795621,0.25569176882662,1.171039948544735
+regime_directional,1y,2025-05-19 08:30,2026-05-19 08:30,1.0294162939701357,1.0294162939701357,0.3200839671173072,3.2160820276039197,173,14.21917808219178,0.2774566473988439,1.3431331390659436
+regime_directional,6m,2025-11-19 08:30,2026-05-19 08:30,0.9394421414081602,2.8029601991604305,0.3200839671173072,8.756952822111133,82,13.591160220994475,0.24390243902439024,1.6440401622430523
+regime_directional,3m,2026-02-19 08:30,2026-05-19 08:30,0.004098121719316339,0.016914008196060593,0.21536869605387549,0.07853512839131215,48,16.179775280898877,0.25,1.0034656211893638
+regime_directional,30d,2026-04-19 08:30,2026-05-19 08:30,-0.06713754926869708,-0.5706801684688294,0.15485179165989393,-3.685331389140353,18,18.0,0.2222222222222222,1.020735185268847
+regime_directional,14d,2026-05-05 08:30,2026-05-19 08:30,-0.030657125804896657,-0.5559344570397098,0.10319943409027593,-5.38699133324117,11,23.57142857142857,0.18181818181818182,0.5990869866211425
+regime_vol_scaled,full,2020-01-04 00:00,2026-05-19 08:30,5.416188941837433,0.3384607415373935,0.8929527742792698,0.37903543310067633,1622,20.90786211094501,0.28175092478421704,1.1521229865756017
+regime_vol_scaled,3y,2023-05-19 08:30,2026-05-19 08:30,2.5184437721086357,0.5203749540994504,0.5511492168239064,0.944163464656998,749,20.501824817518248,0.28437917222963954,1.2428387050716825
+regime_vol_scaled,1y,2025-05-19 08:30,2026-05-19 08:30,1.957334739942569,1.957334739942569,0.3296889988487569,5.93691250474659,250,20.54794520547945,0.348,1.5460140076750746
+regime_vol_scaled,6m,2025-11-19 08:30,2026-05-19 08:30,1.3444409185468662,4.574575905583815,0.23592767582271199,19.389738357874485,124,20.552486187845304,0.33064516129032256,1.850335539916783
+regime_vol_scaled,3m,2026-02-19 08:30,2026-05-19 08:30,0.5152306709576846,4.497505479890258,0.19230347108073126,23.38754186086507,64,21.57303370786517,0.375,1.9703580595438628
+regime_vol_scaled,30d,2026-04-19 08:30,2026-05-19 08:30,0.048824620694040366,0.7860144283230961,0.1654028791019787,4.75212059542495,22,22.0,0.3181818181818182,1.6383573694072007
+regime_vol_scaled,14d,2026-05-05 08:30,2026-05-19 08:30,-0.045465077071893845,-0.7027348910581259,0.1084982785048922,-6.476922037306237,11,23.57142857142857,0.18181818181818182,0.599086986621142
+t_overlay_p012_f20_never,full,2020-01-04 00:00,2026-05-19 08:30,2.0016754539872235,0.1881333022900593,0.9318879004909579,0.20188404870472376,2461,31.722718036396838,0.52661519707436,1.8102687225293919
+t_overlay_p012_f20_never,3y,2023-05-19 08:30,2026-05-19 08:30,1.8862894358947147,0.42333333415840224,0.661870919718495,0.6396010484014816,1117,30.574817518248175,0.5201432408236347,1.894845567516775
+t_overlay_p012_f20_never,1y,2025-05-19 08:30,2026-05-19 08:30,2.435887154829337,2.435887154829337,0.3588021180245764,6.788943076034153,380,31.232876712328764,0.5710526315789474,2.279762132572039
+t_overlay_p012_f20_never,6m,2025-11-19 08:30,2026-05-19 08:30,1.6578017315686377,6.179289162797497,0.25456699599449245,24.273724638409867,185,30.662983425414364,0.5513513513513514,2.613749417499986
+t_overlay_p012_f20_never,3m,2026-02-19 08:30,2026-05-19 08:30,0.7317820195673286,8.508022426085587,0.2011513942333045,42.29661175610644,95,32.022471910112365,0.5789473684210527,3.005648271902043
+t_overlay_p012_f20_never,30d,2026-04-19 08:30,2026-05-19 08:30,0.03190329227824318,0.4653492391651275,0.15491470344851746,3.003906206487212,31,31.000000000000004,0.5161290322580645,2.8236960702782894
+t_overlay_p012_f20_never,14d,2026-05-05 08:30,2026-05-19 08:30,-0.05956176575099881,-0.7983109910268413,0.12667231863987477,-6.302173984013138,14,30.0,0.35714285714285715,1.3306263448463551
+t_overlay_p012_f30,full,2020-01-04 00:00,2026-05-19 08:30,0.7749496291084157,0.09415779203297525,0.9361321418332746,0.10058173181467876,2506,32.30277586314932,0.5279329608938548,1.7573224054941736
+t_overlay_p012_f30,3y,2023-05-19 08:30,2026-05-19 08:30,1.1733870150824446,0.2950200341667113,0.6745930186142289,0.43733039925724565,1146,31.368613138686133,0.5226876090750436,1.8562821760399666
+t_overlay_p012_f30,1y,2025-05-19 08:30,2026-05-19 08:30,2.089254537430174,2.089254537430174,0.3503604421482936,5.963157611685728,393,32.3013698630137,0.5750636132315522,2.273536731713545
+t_overlay_p012_f30,6m,2025-11-19 08:30,2026-05-19 08:30,1.4814366215801877,5.250984420240899,0.2431669131973546,21.59415666875367,192,31.8232044198895,0.5625,2.5426517668209163
+t_overlay_p012_f30,3m,2026-02-19 08:30,2026-05-19 08:30,0.6518176657331394,6.832304037418955,0.1973425141767887,34.62155159986589,98,33.03370786516854,0.5918367346938775,2.8340788603156253
+t_overlay_p012_f30,30d,2026-04-19 08:30,2026-05-19 08:30,0.018467259102908695,0.2493651238495791,0.15077841842580336,1.653851568766052,31,31.000000000000004,0.5161290322580645,2.324162090862002
+t_overlay_p012_f30,14d,2026-05-05 08:30,2026-05-19 08:30,-0.0609639742538014,-0.8060064280718818,0.12472484456007406,-6.4622764687725605,14,30.0,0.35714285714285715,1.2305134720611635
+t_overlay_p012_f30_never,full,2020-01-04 00:00,2026-05-19 08:30,1.269080672884081,0.13712473237936873,0.9250925881879453,0.14822811698012434,2461,31.722718036396838,0.52661519707436,1.8102687225293916
+t_overlay_p012_f30_never,3y,2023-05-19 08:30,2026-05-19 08:30,1.1887446141640217,0.2980603902573249,0.6584737299764951,0.4526534266871428,1117,30.574817518248175,0.5201432408236347,1.8948455675167744
+t_overlay_p012_f30_never,1y,2025-05-19 08:30,2026-05-19 08:30,1.8992703993643119,1.8992703993643119,0.33725203941103093,5.631605379410464,380,31.232876712328764,0.5710526315789474,2.2797621325720385
+t_overlay_p012_f30_never,6m,2025-11-19 08:30,2026-05-19 08:30,1.3474721909667124,4.589120349562917,0.24165280724027205,18.99055261129252,185,30.662983425414364,0.5513513513513514,2.6137494174999865
+t_overlay_p012_f30_never,3m,2026-02-19 08:30,2026-05-19 08:30,0.6401734745641461,6.608333900218723,0.19731336549792483,33.491567505031604,95,32.022471910112365,0.5789473684210527,3.0056482719020488
+t_overlay_p012_f30_never,30d,2026-04-19 08:30,2026-05-19 08:30,0.03417349765014732,0.5050574681559723,0.1438081823028427,3.5120217783740713,31,31.000000000000004,0.5161290322580645,2.8236960702782854
+t_overlay_p012_f30_never,14d,2026-05-05 08:30,2026-05-19 08:30,-0.05681261664829007,-0.7823628042475463,0.12211263386380594,-6.406894843658252,14,30.0,0.35714285714285715,1.3306263448463547
+t_overlay_p018_f20_middle_trend,full,2020-01-04 00:00,2026-05-19 08:30,1.6888436175391353,0.1678013081775116,0.9474696134887112,0.17710468577419017,2375,30.614163078603205,0.5061052631578947,1.893074015596976
+t_overlay_p018_f20_middle_trend,3y,2023-05-19 08:30,2026-05-19 08:30,1.9611118446045133,0.4355165946655173,0.6887491584210019,0.6323297667092106,1075,29.425182481751822,0.4967441860465116,1.9991807521615685
+t_overlay_p018_f20_middle_trend,1y,2025-05-19 08:30,2026-05-19 08:30,2.970853049101802,2.970853049101802,0.35353834059430567,8.403199053623812,380,31.232876712328764,0.5657894736842105,2.548978371597761
+t_overlay_p018_f20_middle_trend,6m,2025-11-19 08:30,2026-05-19 08:30,1.884289785551788,7.466477097466383,0.25221380085684036,29.603761063433822,186,30.82872928176795,0.5483870967741935,2.8430262286431396
+t_overlay_p018_f20_middle_trend,3m,2026-02-19 08:30,2026-05-19 08:30,0.7759403885169918,9.542330603257804,0.2018209675798018,47.28116566721286,96,32.359550561797754,0.5833333333333334,3.1113976074249785
+t_overlay_p018_f20_middle_trend,30d,2026-04-19 08:30,2026-05-19 08:30,0.03304399508478961,0.48517950356116635,0.14995545713348105,3.235490810643121,31,31.000000000000004,0.5161290322580645,2.6439429345109344
+t_overlay_p018_f20_middle_trend,14d,2026-05-05 08:30,2026-05-19 08:30,-0.0549436080879,-0.7708352584316155,0.1268150433823639,-6.078421280884213,14,30.0,0.35714285714285715,1.4498808053309613
+t_overlay_p018_f30,full,2020-01-04 00:00,2026-05-19 08:30,0.8962986700715125,0.10556489224174204,0.9480063704932575,0.11135462326779041,2390,30.807515687520702,0.5054393305439331,1.8270026569013547
+t_overlay_p018_f30,3y,2023-05-19 08:30,2026-05-19 08:30,1.3315243553158127,0.3256682028471678,0.7004551412687363,0.4649379862603108,1085,29.69890510948905,0.4967741935483871,1.9330348778578297
+t_overlay_p018_f30,1y,2025-05-19 08:30,2026-05-19 08:30,2.573307139863559,2.573307139863559,0.3379949346891199,7.613448829434172,382,31.3972602739726,0.56282722513089,2.427129637751288
+t_overlay_p018_f30,6m,2025-11-19 08:30,2026-05-19 08:30,1.6654413871743077,6.220964680433979,0.2381159541067649,26.125778525720552,187,30.994475138121544,0.5508021390374331,2.7010815973774878
+t_overlay_p018_f30,3m,2026-02-19 08:30,2026-05-19 08:30,0.7153632172023674,8.14372810560457,0.19737440931489383,41.260303875625304,97,32.69662921348315,0.5876288659793815,2.996263222342747
+t_overlay_p018_f30,30d,2026-04-19 08:30,2026-05-19 08:30,0.027351789961831185,0.38861988571691186,0.1433702908889348,2.7106026172323627,31,31.000000000000004,0.5161290322580645,2.4543415746166275
+t_overlay_p018_f30,14d,2026-05-05 08:30,2026-05-19 08:30,-0.052772356434166334,-0.7567058708016388,0.12503109896145556,-6.052141243954956,14,30.0,0.35714285714285715,1.414653386437704
+t_overlay_p024_f20_never,full,2020-01-04 00:00,2026-05-19 08:30,2.564606077436442,0.2205967757722389,0.9441174410074549,0.23365395679677636,2277,29.350926033675584,0.4883618796662275,1.9764770668395168
+t_overlay_p024_f20_never,3y,2023-05-19 08:30,2026-05-19 08:30,2.2811193983902,0.4854238880652795,0.6787975481487823,0.7151232195654333,1027,28.111313868613134,0.4780915287244401,2.0816992727723487
+t_overlay_p024_f20_never,1y,2025-05-19 08:30,2026-05-19 08:30,3.147160699141409,3.147160699141409,0.35903686576908594,8.765564205781311,360,29.58904109589041,0.5472222222222223,2.6371506771077105
+t_overlay_p024_f20_never,6m,2025-11-19 08:30,2026-05-19 08:30,1.9020725469986344,7.5720703683307296,0.24665359492693312,30.699209434080313,177,29.337016574585633,0.5310734463276836,2.958890825461753
+t_overlay_p024_f20_never,3m,2026-02-19 08:30,2026-05-19 08:30,0.8189195119355732,10.628593801226776,0.19835257443565724,53.58435014754266,92,31.011235955056176,0.5652173913043478,3.3568918980870817
+t_overlay_p024_f20_never,30d,2026-04-19 08:30,2026-05-19 08:30,0.03410686915024774,0.5038781388048064,0.15419972667733992,3.2676980022095763,30,30.0,0.5,2.886873740145675
+t_overlay_p024_f20_never,14d,2026-05-05 08:30,2026-05-19 08:30,-0.05404295615065402,-0.7650727847365055,0.12681504338236435,-6.032981295678846,14,30.0,0.35714285714285715,1.5151898042163494
+dynamic_t_p012_f30,full,2020-01-04 00:00,2026-05-19 08:30,0.43344081345136076,0.05809608855297288,0.9431163528341999,0.0616001285296197,2506,32.30277586314932,0.5279329608938548,1.7612122386611613
+dynamic_t_p012_f30,3y,2023-05-19 08:30,2026-05-19 08:30,1.1556848019132935,0.2914976826421709,0.6713507302650518,0.43419582269179413,1146,31.368613138686133,0.5226876090750436,1.8616940345460526
+dynamic_t_p012_f30,1y,2025-05-19 08:30,2026-05-19 08:30,2.0048668778203393,2.0048668778203393,0.34737906854777784,5.771409561899363,393,32.3013698630137,0.5750636132315522,2.268843338823847
+dynamic_t_p012_f30,6m,2025-11-19 08:30,2026-05-19 08:30,1.3871879510210454,4.781446904856207,0.23801203614916813,20.089097098684345,192,31.8232044198895,0.5625,2.532975916695981
+dynamic_t_p012_f30,3m,2026-02-19 08:30,2026-05-19 08:30,0.6625001253660106,7.042127653274539,0.1973425141767884,35.68479748345499,98,33.03370786516854,0.5918367346938775,2.834078860315621
+dynamic_t_p012_f30,30d,2026-04-19 08:30,2026-05-19 08:30,0.01846725910290803,0.2493651238495691,0.15077841842580386,1.6538515687659803,31,31.000000000000004,0.5161290322580645,2.324162090861994
+dynamic_t_p012_f30,14d,2026-05-05 08:30,2026-05-19 08:30,-0.06096397425380151,-0.8060064280718824,0.12472484456007396,-6.462276468772569,14,30.0,0.35714285714285715,1.2305134720611608
+dynamic_t_p018_f20_middle_trend,full,2020-01-04 00:00,2026-05-19 08:30,1.3526569303785183,0.1435935640380437,0.9491026336864032,0.15129403179539486,2375,30.614163078603205,0.5061052631578947,1.894086293898235
+dynamic_t_p018_f20_middle_trend,3y,2023-05-19 08:30,2026-05-19 08:30,1.9387390392191315,0.4318953850394036,0.6785576172338584,0.6364903643702742,1075,29.425182481751822,0.4967441860465116,1.9958915739051868
+dynamic_t_p018_f20_middle_trend,1y,2025-05-19 08:30,2026-05-19 08:30,2.9156505696132444,2.9156505696132444,0.34110420787630014,8.54768279689646,380,31.232876712328764,0.5657894736842105,2.5406984714287835
+dynamic_t_p018_f20_middle_trend,6m,2025-11-19 08:30,2026-05-19 08:30,1.7725887947998178,6.818286310534685,0.24722668532371123,27.57908719120278,186,30.82872928176795,0.5483870967741935,2.8259171263217193
+dynamic_t_p018_f20_middle_trend,3m,2026-02-19 08:30,2026-05-19 08:30,0.7874255614294052,9.824754512768113,0.2018209675798014,48.68054410096581,96,32.359550561797754,0.5833333333333334,3.111397607424981
+dynamic_t_p018_f20_middle_trend,30d,2026-04-19 08:30,2026-05-19 08:30,0.03304399508479028,0.4851795035611779,0.14995545713348077,3.2354908106432037,31,31.000000000000004,0.5161290322580645,2.64394293451094
+dynamic_t_p018_f20_middle_trend,14d,2026-05-05 08:30,2026-05-19 08:30,-0.054943608087899776,-0.770835258431614,0.12681504338236385,-6.078421280884204,14,30.0,0.35714285714285715,1.4498808053309633
+vol_dynamic_t_p024_f20_never,full,2020-01-04 00:00,2026-05-19 08:30,2.565495645065059,0.22064454237402908,0.939800870226845,0.2347779719769475,2277,29.350926033675584,0.4883618796662275,1.9764770668395157
+vol_dynamic_t_p024_f20_never,3y,2023-05-19 08:30,2026-05-19 08:30,2.336003818260167,0.4936529955162041,0.673917580693483,0.7325124164415169,1027,28.111313868613134,0.4780915287244401,2.0816992727723473
+vol_dynamic_t_p024_f20_never,1y,2025-05-19 08:30,2026-05-19 08:30,3.115459760815426,3.115459760815426,0.3552368275489058,8.770092285509216,360,29.58904109589041,0.5472222222222223,2.6371506771077113
+vol_dynamic_t_p024_f20_never,6m,2025-11-19 08:30,2026-05-19 08:30,1.8440515273223927,7.2299793647899975,0.2416567110333811,29.918388501907934,177,29.337016574585633,0.5310734463276836,2.958890825461753
+vol_dynamic_t_p024_f20_never,3m,2026-02-19 08:30,2026-05-19 08:30,0.8309842534033149,10.948188059427089,0.19835257443565738,55.19559345562474,92,31.011235955056176,0.5652173913043478,3.3568918980870803
+vol_dynamic_t_p024_f20_never,30d,2026-04-19 08:30,2026-05-19 08:30,0.03410686915024774,0.5038781388048064,0.15419972667733992,3.2676980022095763,30,30.0,0.5,2.886873740145671
+vol_dynamic_t_p024_f20_never,14d,2026-05-05 08:30,2026-05-19 08:30,-0.05404295615065391,-0.7650727847365048,0.1268150433823644,-6.032981295678837,14,30.0,0.35714285714285715,1.5151898042163476
+ratio_dynamic_t_p024_f20_never,full,2020-01-04 00:00,2026-05-19 08:30,2.54817465815279,0.21971265479371138,0.9362551603997749,0.23467176907190077,2277,29.350926033675584,0.4883618796662275,1.9764770668395157
+ratio_dynamic_t_p024_f20_never,3y,2023-05-19 08:30,2026-05-19 08:30,2.059522096204523,0.4512319344250726,0.662742354561477,0.6808557372550054,1027,28.111313868613134,0.4780915287244401,2.0816992727723487
+ratio_dynamic_t_p024_f20_never,1y,2025-05-19 08:30,2026-05-19 08:30,3.1432947575684533,3.1432947575684533,0.35585714795469003,8.83302408181127,360,29.58904109589041,0.5472222222222223,2.6371506771077122
+ratio_dynamic_t_p024_f20_never,6m,2025-11-19 08:30,2026-05-19 08:30,1.783458486043319,6.880219319266394,0.2151220840156312,31.982859178541908,177,29.337016574585633,0.5310734463276836,2.9588908254617534
+ratio_dynamic_t_p024_f20_never,3m,2026-02-19 08:30,2026-05-19 08:30,0.7113306401295434,8.055892743232517,0.1983525744356576,40.61400647888097,92,31.011235955056176,0.5652173913043478,3.3568918980870817
+ratio_dynamic_t_p024_f20_never,30d,2026-04-19 08:30,2026-05-19 08:30,0.028873247935842228,0.41384830067406453,0.14559722565653804,2.8424188634632865,30,30.0,0.5,2.8868737401456745
+ratio_dynamic_t_p024_f20_never,14d,2026-05-05 08:30,2026-05-19 08:30,-0.058830450557952885,-0.7941818403141793,0.11793401707204729,-6.734120146429034,14,30.0,0.35714285714285715,1.5151898042163463
+regime_directional_dynamic,full,2020-01-04 00:00,2026-05-19 08:30,1.6318640021874264,0.16388509035403787,0.8946046480332189,0.18319275527389434,1208,15.57133010482218,0.26572847682119205,1.1385882212708864
+regime_directional_dynamic,3y,2023-05-19 08:30,2026-05-19 08:30,1.212959570466634,0.3028254717969958,0.4678766490008019,0.6472335656068974,571,15.629562043795621,0.25569176882662,1.1710399485447343
+regime_directional_dynamic,1y,2025-05-19 08:30,2026-05-19 08:30,0.7436044253871501,0.7436044253871501,0.3441550551457199,2.160666868810914,173,14.21917808219178,0.2774566473988439,1.3431331390659431
+regime_directional_dynamic,6m,2025-11-19 08:30,2026-05-19 08:30,0.7415975753576307,2.0611825778342165,0.3156868864190641,6.529199236670425,82,13.591160220994475,0.24390243902439024,1.644040162243052
+regime_directional_dynamic,3m,2026-02-19 08:30,2026-05-19 08:30,-0.04306622075375022,-0.1651773024643467,0.21536869605387546,-0.7669513048592115,48,16.179775280898877,0.25,1.0034656211893638
+regime_directional_dynamic,30d,2026-04-19 08:30,2026-05-19 08:30,-0.06713754926869675,-0.5706801684688276,0.15485179165989382,-3.685331389140344,18,18.0,0.2222222222222222,1.020735185268848
+regime_directional_dynamic,14d,2026-05-05 08:30,2026-05-19 08:30,-0.030657125804896435,-0.5559344570397071,0.1031994340902757,-5.3869913332411565,11,23.57142857142857,0.18181818181818182,0.5990869866211441
+regime_directional_t_p012_f30,full,2020-01-04 00:00,2026-05-19 08:30,0.19095180514440258,0.027785578325503213,0.9047168332085817,0.030711905986054836,1841,23.73081020114042,0.5111352525801195,1.7088231988303608
+regime_directional_t_p012_f30,3y,2023-05-19 08:30,2026-05-19 08:30,0.34024919125570996,0.1024439103804684,0.5078825084021087,0.2017078924469671,866,23.704379562043798,0.5023094688221709,1.7417301541080938
+regime_directional_t_p012_f30,1y,2025-05-19 08:30,2026-05-19 08:30,0.6634236173337829,0.6634236173337829,0.2780596060654611,2.3859043272096088,268,22.027397260273972,0.5298507462686567,2.021366155636813
+regime_directional_t_p012_f30,6m,2025-11-19 08:30,2026-05-19 08:30,0.6372004646432177,1.7024170237634237,0.23726949893332677,7.1750352717767845,125,20.718232044198892,0.504,2.312631701254245
+regime_directional_t_p012_f30,3m,2026-02-19 08:30,2026-05-19 08:30,-0.029934843699834013,-0.11718667030472874,0.2115833638824677,-0.553855785986202,70,23.59550561797753,0.4857142857142857,1.6298771737177336
+regime_directional_t_p012_f30,30d,2026-04-19 08:30,2026-05-19 08:30,-0.07057392354048331,-0.589530669710113,0.14494026895904596,-4.067404275872357,23,23.0,0.391304347826087,1.3620891542971116
+regime_directional_t_p012_f30,14d,2026-05-05 08:30,2026-05-19 08:30,-0.027626980674278934,-0.5182892726604436,0.09573521993894431,-5.413778471402536,14,30.0,0.35714285714285715,1.230518513902298
+layered_directional_dynamic_t_p012_f30,full,2020-01-04 00:00,2026-05-19 08:30,0.3061293063093795,0.04277392714158079,0.8790553915598875,0.04865896683220187,1841,23.73081020114042,0.5111352525801195,1.715243781107706
+layered_directional_dynamic_t_p012_f30,3y,2023-05-19 08:30,2026-05-19 08:30,0.3284421923082972,0.09919996843801959,0.5038259222496542,0.19689333965802647,866,23.704379562043798,0.5023094688221709,1.746615339239338
+layered_directional_dynamic_t_p012_f30,1y,2025-05-19 08:30,2026-05-19 08:30,0.6126934940392428,0.6126934940392428,0.2734443222733042,2.2406517310199012,268,22.027397260273972,0.5298507462686567,2.0214066805223077
+layered_directional_dynamic_t_p012_f30,6m,2025-11-19 08:30,2026-05-19 08:30,0.5710168060065199,1.4866420753268978,0.22941750395075874,6.480072573913039,125,20.718232044198892,0.504,2.312717330190989
+layered_directional_dynamic_t_p012_f30,3m,2026-02-19 08:30,2026-05-19 08:30,-0.020283301748009408,-0.08060509555779438,0.21158336388246776,-0.38096140489840036,70,23.59550561797753,0.4857142857142857,1.6298771737177342
+layered_directional_dynamic_t_p012_f30,30d,2026-04-19 08:30,2026-05-19 08:30,-0.07057392354048264,-0.5895306697101095,0.1449402689590454,-4.067404275872348,23,23.0,0.391304347826087,1.3620891542971116
+layered_directional_dynamic_t_p012_f30,14d,2026-05-05 08:30,2026-05-19 08:30,-0.0276269806742786,-0.5182892726604393,0.09573521993894417,-5.413778471402501,14,30.0,0.35714285714285715,1.2305185139022987
+layered_vol_dynamic_t_p012_f30,full,2020-01-04 00:00,2026-05-19 08:30,1.085413065494194,0.12217099436109224,0.8820064979540826,0.13851484614283702,2502,32.25121516743799,0.526378896882494,1.7352525396310887
+layered_vol_dynamic_t_p012_f30,3y,2023-05-19 08:30,2026-05-19 08:30,0.9174726147031298,0.24210144924399168,0.5509296606123668,0.4394416684244086,1145,31.34124087591241,0.5213973799126638,1.8506840132548235
+layered_vol_dynamic_t_p012_f30,1y,2025-05-19 08:30,2026-05-19 08:30,1.1558265542582031,1.1558265542582031,0.2841290344829979,4.06796354466677,393,32.3013698630137,0.5750636132315522,2.2426524887519843
+layered_vol_dynamic_t_p012_f30,6m,2025-11-19 08:30,2026-05-19 08:30,0.852811265329787,2.468179414321079,0.20444303013410903,12.072700217278236,192,31.8232044198895,0.5625,2.525660292317928
+layered_vol_dynamic_t_p012_f30,3m,2026-02-19 08:30,2026-05-19 08:30,0.38623697144355584,2.816743947100165,0.17503765299510185,16.092217296692084,98,33.03370786516854,0.5918367346938775,2.8030628405963944
+layered_vol_dynamic_t_p012_f30,30d,2026-04-19 08:30,2026-05-19 08:30,0.040404817640389945,0.6191806817309893,0.13868533704452887,4.464644171663106,31,31.000000000000004,0.5161290322580645,2.351801183787263
+layered_vol_dynamic_t_p012_f30,14d,2026-05-05 08:30,2026-05-19 08:30,-0.043510999396608185,-0.6864552874956372,0.10737422875394961,-6.393110297152071,14,30.0,0.35714285714285715,1.2305263823872603
+layered_vol_dynamic_t_p018_f20_middle_trend,full,2020-01-04 00:00,2026-05-19 08:30,2.225572788894392,0.2016141679824346,0.8927715751858751,0.22582951069030002,2373,30.588382730747544,0.5056890012642224,1.874925149926483
+layered_vol_dynamic_t_p018_f20_middle_trend,3y,2023-05-19 08:30,2026-05-19 08:30,1.338323710681133,0.3269544459103426,0.5766794867756964,0.566960423264568,1075,29.425182481751822,0.4967441860465116,2.005494902318045
+layered_vol_dynamic_t_p018_f20_middle_trend,1y,2025-05-19 08:30,2026-05-19 08:30,1.6000358920483957,1.6000358920483957,0.2838762496966667,5.636385198684635,380,31.232876712328764,0.5657894736842105,2.5469610027483687
+layered_vol_dynamic_t_p018_f20_middle_trend,6m,2025-11-19 08:30,2026-05-19 08:30,1.0572600299047483,3.2832262473052234,0.21253105352360954,15.448218944345928,186,30.82872928176795,0.5483870967741935,2.8748833184002973
+layered_vol_dynamic_t_p018_f20_middle_trend,3m,2026-02-19 08:30,2026-05-19 08:30,0.46079270039211995,3.7314924352552037,0.1796405570924982,20.77199322719664,96,32.359550561797754,0.5833333333333334,3.1586669353193173
+layered_vol_dynamic_t_p018_f20_middle_trend,30d,2026-04-19 08:30,2026-05-19 08:30,0.04769818577101215,0.7628160707790044,0.14405749438270155,5.295219620803036,31,31.000000000000004,0.5161290322580645,2.6439429345109375
+layered_vol_dynamic_t_p018_f20_middle_trend,14d,2026-05-05 08:30,2026-05-19 08:30,-0.0415375608699633,-0.6691459259542767,0.10530541842505961,-6.354335189603496,14,30.0,0.35714285714285715,1.4498808053309595
+layered_vol_t_p024_f20_never,full,2020-01-04 00:00,2026-05-19 08:30,3.8102728536803676,0.2793370650357616,0.8800000183376352,0.31742847638053867,2277,29.350926033675584,0.4883618796662275,1.9764770668395162
+layered_vol_t_p024_f20_never,3y,2023-05-19 08:30,2026-05-19 08:30,1.6905726971090482,0.39043549395310784,0.5429897064202688,0.7190476897381818,1027,28.111313868613134,0.4780915287244401,2.081699272772348
+layered_vol_t_p024_f20_never,1y,2025-05-19 08:30,2026-05-19 08:30,1.746498795721481,1.746498795721481,0.2963205008024198,5.8939519573976735,360,29.58904109589041,0.5472222222222223,2.6371506771077096
+layered_vol_t_p024_f20_never,6m,2025-11-19 08:30,2026-05-19 08:30,1.1399486753039043,3.63749029092562,0.2125310535236095,17.115100267084223,177,29.337016574585633,0.5310734463276836,2.958890825461751
+layered_vol_t_p024_f20_never,3m,2026-02-19 08:30,2026-05-19 08:30,0.48498463291013727,4.061193945137245,0.1760757815823141,23.065034320115554,92,31.011235955056176,0.5652173913043478,3.3568918980870843
+layered_vol_t_p024_f20_never,30d,2026-04-19 08:30,2026-05-19 08:30,0.04877613718003415,0.7850101925198991,0.14833121243473985,5.292279215106356,30,30.0,0.5,2.886873740145682
+layered_vol_t_p024_f20_never,14d,2026-05-05 08:30,2026-05-19 08:30,-0.04062413278253052,-0.6608264179638188,0.10530541842505971,-6.275331581670643,14,30.0,0.35714285714285715,1.5151898042163525
+layered_vol_ratio_t_p024_f20_never,full,2020-01-04 00:00,2026-05-19 08:30,3.0073452816524764,0.24321515962063667,0.8815177027482238,0.2759050202422344,2277,29.350926033675584,0.4883618796662275,1.9764770668395157
+layered_vol_ratio_t_p024_f20_never,3y,2023-05-19 08:30,2026-05-19 08:30,1.2883407154325468,0.31744012345993333,0.5610397097426038,0.5658068723969109,1027,28.111313868613134,0.4780915287244401,2.081699272772348
+layered_vol_ratio_t_p024_f20_never,1y,2025-05-19 08:30,2026-05-19 08:30,1.697779055066912,1.697779055066912,0.2925001930146286,5.804369007653961,360,29.58904109589041,0.5472222222222223,2.63715067710771
+layered_vol_ratio_t_p024_f20_never,6m,2025-11-19 08:30,2026-05-19 08:30,1.066683727118491,3.32288394188586,0.21253105352360988,15.634816121196726,177,29.337016574585633,0.5310734463276836,2.9588908254617508
+layered_vol_ratio_t_p024_f20_never,3m,2026-02-19 08:30,2026-05-19 08:30,0.4312247477098703,3.350879794875615,0.17607578158231377,19.030895474452915,92,31.011235955056176,0.5652173913043478,3.3568918980870794
+layered_vol_ratio_t_p024_f20_never,30d,2026-04-19 08:30,2026-05-19 08:30,0.04158415403239535,0.6416532515138835,0.1396690236822662,4.594098495122182,30,30.0,0.5,2.8868737401456674
+layered_vol_ratio_t_p024_f20_never,14d,2026-05-05 08:30,2026-05-19 08:30,-0.04720305351182141,-0.7165285674159795,0.0962056211158575,-7.447886714988161,14,30.0,0.35714285714285715,1.5151898042163443

+ 99 - 0
reports/eth-exploration/layered-bb-squeeze-position-report.md

@@ -0,0 +1,99 @@
+# Layered BB squeeze position exploration
+
+Scope: offline local-candle research only. No live executor, deployment, credentials, or order path was changed.
+
+Run command: `rtk .venv/bin/python /home/lxy/okx-codex-trader/scripts/evaluate_layered_bb_squeeze_position.py --bar 15m --output-dir reports/eth-exploration`
+
+Layer model: long-term regime cap + medium-term live BB squeeze signal + optional dynamic exposure + optional in-position T overlay.
+
+Output files:
+- `reports/eth-exploration/layered-bb-squeeze-position-summary.csv`
+- `reports/eth-exploration/layered-bb-squeeze-position-horizons.csv`
+- `reports/eth-exploration/layered-bb-squeeze-position-events.csv`
+- `reports/eth-exploration/layered-bb-squeeze-position-equity.csv`
+- `reports/eth-exploration/layered-bb-squeeze-position-report.md`
+
+## Summary Ranking
+
+| variant | full_total_return | full_max_drawdown | full_calmar | recent_min_return | recent_min_calmar | trades | t_reduce_events | t_readd_events |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| layered_vol_t_p024_f20_never | 3.81027 | 0.88 | 0.317428 | -0.0406241 | -6.27533 | 2277 | 655 | 0 |
+| layered_vol_dynamic_t_p018_f20_middle_trend | 2.22557 | 0.892772 | 0.22583 | -0.0415376 | -6.35434 | 2373 | 751 | 244 |
+| layered_vol_dynamic_t_p012_f30 | 1.08541 | 0.882006 | 0.138515 | -0.043511 | -6.39311 | 2502 | 880 | 434 |
+| regime_vol_scaled | 5.41619 | 0.892953 | 0.379035 | -0.0454651 | -6.47692 | 1622 | 0 | 0 |
+| layered_vol_ratio_t_p024_f20_never | 3.00735 | 0.881518 | 0.275905 | -0.0472031 | -7.44789 | 2277 | 655 | 0 |
+| t_overlay_p018_f30 | 0.896299 | 0.948006 | 0.111355 | -0.0527724 | -6.05214 | 2390 | 768 | 387 |
+| vol_dynamic_t_p024_f20_never | 2.5655 | 0.939801 | 0.234778 | -0.054043 | -6.03298 | 2277 | 655 | 0 |
+| t_overlay_p024_f20_never | 2.56461 | 0.944117 | 0.233654 | -0.054043 | -6.03298 | 2277 | 655 | 0 |
+| dynamic_t_p018_f20_middle_trend | 1.35266 | 0.949103 | 0.151294 | -0.0549436 | -6.07842 | 2375 | 753 | 252 |
+| t_overlay_p018_f20_middle_trend | 1.68884 | 0.94747 | 0.177105 | -0.0549436 | -6.07842 | 2375 | 753 | 252 |
+| t_overlay_p012_f30_never | 1.26908 | 0.925093 | 0.148228 | -0.0568126 | -6.40689 | 2461 | 839 | 0 |
+| ratio_dynamic_t_p024_f20_never | 2.54817 | 0.936255 | 0.234672 | -0.0588305 | -6.73412 | 2277 | 655 | 0 |
+| t_overlay_p012_f20_never | 2.00168 | 0.931888 | 0.201884 | -0.0595618 | -6.30217 | 2461 | 839 | 0 |
+| t_overlay_p012_f30 | 0.77495 | 0.936132 | 0.100582 | -0.060964 | -6.46228 | 2506 | 884 | 445 |
+| dynamic_t_p012_f30 | 0.433441 | 0.943116 | 0.0616001 | -0.060964 | -6.46228 | 2506 | 884 | 444 |
+| dynamic_vol_only | 3.93093 | 0.940986 | 0.302149 | -0.065067 | -6.09342 | 1622 | 0 | 0 |
+| baseline | 3.75439 | 0.946091 | 0.292778 | -0.065067 | -6.09342 | 1622 | 0 | 0 |
+| dynamic_exposure | 4.37231 | 0.922065 | 0.327202 | -0.065067 | -6.09342 | 1622 | 0 | 0 |
+| regime_directional_dynamic | 1.63186 | 0.894605 | 0.183193 | -0.0671375 | -5.38699 | 1208 | 0 | 0 |
+| regime_directional | 1.72166 | 0.918383 | 0.185135 | -0.0671375 | -5.38699 | 1208 | 0 | 0 |
+| dynamic_ratio_soft | 3.60205 | 0.938529 | 0.288205 | -0.0694696 | -6.67335 | 1622 | 0 | 0 |
+| layered_directional_dynamic_t_p012_f30 | 0.306129 | 0.879055 | 0.048659 | -0.0705739 | -5.41378 | 1841 | 633 | 324 |
+| regime_directional_t_p012_f30 | 0.190952 | 0.904717 | 0.0307119 | -0.0705739 | -5.41378 | 1841 | 633 | 324 |
+
+## Baseline Horizons
+
+| horizon | total_return | annualized_return | max_drawdown | calmar | trades | trades_per_30d | win_rate | profit_factor |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| full | 3.75439 | 0.276995 | 0.946091 | 0.292778 | 1622 | 20.9079 | 0.281751 | 1.15212 |
+| 3y | 3.81511 | 0.687826 | 0.674119 | 1.02033 | 749 | 20.5018 | 0.284379 | 1.24284 |
+| 1y | 3.75069 | 3.75069 | 0.400194 | 9.37217 | 250 | 20.5479 | 0.348 | 1.54601 |
+| 6m | 2.37248 | 10.6051 | 0.279065 | 38.0023 | 124 | 20.5525 | 0.330645 | 1.85034 |
+| 3m | 0.923034 | 13.6105 | 0.214142 | 63.5586 | 64 | 21.573 | 0.375 | 1.97036 |
+| 30d | 0.0272864 | 0.387545 | 0.176658 | 2.19375 | 22 | 22 | 0.318182 | 1.63836 |
+| 14d | -0.065067 | -0.826937 | 0.13571 | -6.09342 | 11 | 23.5714 | 0.181818 | 0.599087 |
+
+## Best Variant Horizons
+
+| horizon | total_return | annualized_return | max_drawdown | calmar | trades | trades_per_30d | win_rate | profit_factor |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| full | 3.81027 | 0.279337 | 0.88 | 0.317428 | 2277 | 29.3509 | 0.488362 | 1.97648 |
+| 3y | 1.69057 | 0.390435 | 0.54299 | 0.719048 | 1027 | 28.1113 | 0.478092 | 2.0817 |
+| 1y | 1.7465 | 1.7465 | 0.296321 | 5.89395 | 360 | 29.589 | 0.547222 | 2.63715 |
+| 6m | 1.13995 | 3.63749 | 0.212531 | 17.1151 | 177 | 29.337 | 0.531073 | 2.95889 |
+| 3m | 0.484985 | 4.06119 | 0.176076 | 23.065 | 92 | 31.0112 | 0.565217 | 3.35689 |
+| 30d | 0.0487761 | 0.78501 | 0.148331 | 5.29228 | 30 | 30 | 0.5 | 2.88687 |
+| 14d | -0.0406241 | -0.660826 | 0.105305 | -6.27533 | 14 | 30 | 0.357143 | 1.51519 |
+
+## Event Counts
+
+| variant | entry_events | middle_exit_events | stop_events | t_reduce_events | t_readd_events |
+| --- | --- | --- | --- | --- | --- |
+| baseline | 1622 | 1085 | 537 | 0 | 0 |
+| dynamic_exposure | 1622 | 1085 | 537 | 0 | 0 |
+| dynamic_vol_only | 1622 | 1085 | 537 | 0 | 0 |
+| dynamic_ratio_soft | 1622 | 1085 | 537 | 0 | 0 |
+| regime_directional | 1208 | 812 | 396 | 0 | 0 |
+| regime_vol_scaled | 1622 | 1085 | 537 | 0 | 0 |
+| t_overlay_p012_f20_never | 1622 | 1085 | 537 | 839 | 0 |
+| t_overlay_p012_f30 | 1622 | 1085 | 537 | 884 | 445 |
+| t_overlay_p012_f30_never | 1622 | 1085 | 537 | 839 | 0 |
+| t_overlay_p018_f20_middle_trend | 1622 | 1085 | 537 | 753 | 252 |
+| t_overlay_p018_f30 | 1622 | 1085 | 537 | 768 | 387 |
+| t_overlay_p024_f20_never | 1622 | 1085 | 537 | 655 | 0 |
+| dynamic_t_p012_f30 | 1622 | 1085 | 537 | 884 | 444 |
+| dynamic_t_p018_f20_middle_trend | 1622 | 1085 | 537 | 753 | 252 |
+| vol_dynamic_t_p024_f20_never | 1622 | 1085 | 537 | 655 | 0 |
+| ratio_dynamic_t_p024_f20_never | 1622 | 1085 | 537 | 655 | 0 |
+| regime_directional_dynamic | 1208 | 812 | 396 | 0 | 0 |
+| regime_directional_t_p012_f30 | 1208 | 812 | 396 | 633 | 324 |
+| layered_directional_dynamic_t_p012_f30 | 1208 | 812 | 396 | 633 | 324 |
+| layered_vol_dynamic_t_p012_f30 | 1622 | 1085 | 537 | 880 | 434 |
+| layered_vol_dynamic_t_p018_f20_middle_trend | 1622 | 1085 | 537 | 751 | 244 |
+| layered_vol_t_p024_f20_never | 1622 | 1085 | 537 | 655 | 0 |
+| layered_vol_ratio_t_p024_f20_never | 1622 | 1085 | 537 | 655 | 0 |
+
+## Decision
+
+- Best ranked variant: `layered_vol_t_p024_f20_never`.
+- Replace live strategy now: No.

+ 24 - 0
reports/eth-exploration/layered-bb-squeeze-position-summary.csv

@@ -0,0 +1,24 @@
+variant,regime,dynamic_exposure,dynamic_mode,t_overlay,t_profit_pct,t_fraction,readd_mode,trades,full_total_return,full_annualized_return,full_max_drawdown,full_calmar,recent_min_return,recent_min_calmar,t_reduce_events,t_readd_events
+baseline,none,False,none,False,0.0,0.0,middle,1622,3.754391599334882,0.2769947309613683,0.9460905268197939,0.29277825230156723,-0.06506701408726945,-6.093424096587236,0,0
+dynamic_exposure,none,True,closed_trade,False,0.0,0.0,middle,1622,4.372306031921977,0.3017015970488881,0.9220647173487678,0.32720219239748927,-0.06506701408726956,-6.093424096587243,0,0
+dynamic_vol_only,none,True,vol_only,False,0.0,0.0,middle,1622,3.930933699036144,0.28431747584759637,0.9409858586712201,0.3021485107641099,-0.06506701408726945,-6.093424096587232,0,0
+dynamic_ratio_soft,none,True,ratio_soft,False,0.0,0.0,middle,1622,3.6020511503685286,0.270489185114859,0.9385293495500662,0.28820535579897666,-0.06946962985904626,-6.673349809534446,0,0
+regime_directional,directional,False,none,False,0.0,0.0,middle,1208,1.7216582220835912,0.170025014604692,0.9183827763314653,0.1851352387986489,-0.06713754926869708,-5.38699133324117,0,0
+regime_vol_scaled,vol_scaled,False,none,False,0.0,0.0,middle,1622,5.416188941837433,0.3384607415373935,0.8929527742792698,0.37903543310067633,-0.045465077071893845,-6.476922037306237,0,0
+t_overlay_p012_f20_never,none,False,none,True,0.012,0.2,never,2461,2.0016754539872235,0.1881333022900593,0.9318879004909579,0.20188404870472376,-0.05956176575099881,-6.302173984013138,839,0
+t_overlay_p012_f30,none,False,none,True,0.012,0.3,middle,2506,0.7749496291084157,0.09415779203297525,0.9361321418332746,0.10058173181467876,-0.0609639742538014,-6.4622764687725605,884,445
+t_overlay_p012_f30_never,none,False,none,True,0.012,0.3,never,2461,1.269080672884081,0.13712473237936873,0.9250925881879453,0.14822811698012434,-0.05681261664829007,-6.406894843658252,839,0
+t_overlay_p018_f20_middle_trend,none,False,none,True,0.018,0.2,middle_trend,2375,1.6888436175391353,0.1678013081775116,0.9474696134887112,0.17710468577419017,-0.0549436080879,-6.078421280884213,753,252
+t_overlay_p018_f30,none,False,none,True,0.018,0.3,middle,2390,0.8962986700715125,0.10556489224174204,0.9480063704932575,0.11135462326779041,-0.052772356434166334,-6.052141243954956,768,387
+t_overlay_p024_f20_never,none,False,none,True,0.024,0.2,never,2277,2.564606077436442,0.2205967757722389,0.9441174410074549,0.23365395679677636,-0.05404295615065402,-6.032981295678846,655,0
+dynamic_t_p012_f30,none,True,closed_trade,True,0.012,0.3,middle,2506,0.43344081345136076,0.05809608855297288,0.9431163528341999,0.0616001285296197,-0.06096397425380151,-6.462276468772569,884,444
+dynamic_t_p018_f20_middle_trend,none,True,closed_trade,True,0.018,0.2,middle_trend,2375,1.3526569303785183,0.1435935640380437,0.9491026336864032,0.15129403179539486,-0.054943608087899776,-6.078421280884204,753,252
+vol_dynamic_t_p024_f20_never,none,True,vol_only,True,0.024,0.2,never,2277,2.565495645065059,0.22064454237402908,0.939800870226845,0.2347779719769475,-0.05404295615065391,-6.032981295678837,655,0
+ratio_dynamic_t_p024_f20_never,none,True,ratio_soft,True,0.024,0.2,never,2277,2.54817465815279,0.21971265479371138,0.9362551603997749,0.23467176907190077,-0.058830450557952885,-6.734120146429034,655,0
+regime_directional_dynamic,directional,True,closed_trade,False,0.0,0.0,middle,1208,1.6318640021874264,0.16388509035403787,0.8946046480332189,0.18319275527389434,-0.06713754926869675,-5.3869913332411565,0,0
+regime_directional_t_p012_f30,directional,False,none,True,0.012,0.3,middle,1841,0.19095180514440258,0.027785578325503213,0.9047168332085817,0.030711905986054836,-0.07057392354048331,-5.413778471402536,633,324
+layered_directional_dynamic_t_p012_f30,directional,True,closed_trade,True,0.012,0.3,middle,1841,0.3061293063093795,0.04277392714158079,0.8790553915598875,0.04865896683220187,-0.07057392354048264,-5.413778471402501,633,324
+layered_vol_dynamic_t_p012_f30,vol_scaled,True,closed_trade,True,0.012,0.3,middle,2502,1.085413065494194,0.12217099436109224,0.8820064979540826,0.13851484614283702,-0.043510999396608185,-6.393110297152071,880,434
+layered_vol_dynamic_t_p018_f20_middle_trend,vol_scaled,True,closed_trade,True,0.018,0.2,middle_trend,2373,2.225572788894392,0.2016141679824346,0.8927715751858751,0.22582951069030002,-0.0415375608699633,-6.354335189603496,751,244
+layered_vol_t_p024_f20_never,vol_scaled,False,none,True,0.024,0.2,never,2277,3.8102728536803676,0.2793370650357616,0.8800000183376352,0.31742847638053867,-0.04062413278253052,-6.275331581670643,655,0
+layered_vol_ratio_t_p024_f20_never,vol_scaled,True,ratio_soft,True,0.024,0.2,never,2277,3.0073452816524764,0.24321515962063667,0.8815177027482238,0.2759050202422344,-0.04720305351182141,-7.447886714988161,655,0

+ 669 - 0
scripts/evaluate_layered_bb_squeeze_position.py

@@ -0,0 +1,669 @@
+from __future__ import annotations
+
+import argparse
+import sys
+from dataclasses import dataclass
+from pathlib import Path
+
+import pandas as pd
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+
+from okx_codex_trader.models import Candle
+from okx_codex_trader.sampled_report import trade_equity
+from scripts.search_live_bb_squeeze_exit_variants import DATA_DIR, INITIAL_EQUITY, LEVERAGE, OUTPUT_DIR, _format_ts
+
+
+ETH_SYMBOL = "ETH-USDT-SWAP"
+BTC_SYMBOL = "BTC-USDT-SWAP"
+BAR = "15m"
+ROUNDTRIP_COST = 0.0021
+WARMUP_BARS = 960
+HORIZONS = (
+    ("full", None),
+    ("3y", pd.DateOffset(years=3)),
+    ("1y", pd.DateOffset(years=1)),
+    ("6m", pd.DateOffset(months=6)),
+    ("3m", pd.DateOffset(months=3)),
+    ("30d", pd.DateOffset(days=30)),
+    ("14d", pd.DateOffset(days=14)),
+)
+
+
+@dataclass(frozen=True)
+class Variant:
+    name: str
+    regime: str
+    dynamic_exposure: bool
+    t_overlay: bool
+    t_profit_pct: float
+    t_fraction: float
+    readd_buffer_pct: float
+    readd_mode: str
+    dynamic_mode: str
+
+
+def load_candles(symbol: str, bar: str) -> list[Candle]:
+    frame = pd.read_csv(DATA_DIR / symbol / f"{bar}.csv")
+    return [
+        Candle(
+            symbol=symbol,
+            ts=int(row.ts),
+            open=float(row.open),
+            high=float(row.high),
+            low=float(row.low),
+            close=float(row.close),
+            volume=float(row.volume),
+        )
+        for row in frame.itertuples(index=False)
+    ]
+
+
+def align_pair(eth: list[Candle], btc: list[Candle]) -> tuple[list[Candle], list[Candle]]:
+    btc_by_ts = {candle.ts: candle for candle in btc}
+    eth_out: list[Candle] = []
+    btc_out: list[Candle] = []
+    for candle in eth:
+        other = btc_by_ts.get(candle.ts)
+        if other is not None:
+            eth_out.append(candle)
+            btc_out.append(other)
+    return eth_out, btc_out
+
+
+def indicators(eth: list[Candle], btc: list[Candle]) -> pd.DataFrame:
+    frame = pd.DataFrame(
+        {
+            "ts": [candle.ts for candle in eth],
+            "open": [candle.open for candle in eth],
+            "high": [candle.high for candle in eth],
+            "low": [candle.low for candle in eth],
+            "close": [candle.close for candle in eth],
+            "btc_close": [candle.close for candle in btc],
+        }
+    )
+    close = frame["close"].astype(float)
+    btc_close = frame["btc_close"].astype(float)
+    middle = close.rolling(48).mean()
+    stdev = close.rolling(48).std(ddof=0)
+    upper = middle + 2.0 * stdev
+    lower = middle - 2.0 * stdev
+    bandwidth = (upper - lower) / middle
+    frame["middle"] = middle
+    frame["upper"] = upper
+    frame["lower"] = lower
+    frame["bandwidth"] = bandwidth
+    frame["bandwidth_threshold"] = bandwidth.rolling(960).quantile(0.25)
+    frame["eth_vol_96"] = close.pct_change().rolling(96).std(ddof=0)
+    frame["eth_ret_90d"] = close / close.shift(96 * 90) - 1.0
+    frame["btc_ret_90d"] = btc_close / btc_close.shift(96 * 90) - 1.0
+    frame["ratio_ret_30d"] = (close / btc_close) / (close / btc_close).shift(96 * 30) - 1.0
+    frame["eth_vol_30d"] = close.pct_change().rolling(96 * 30).std(ddof=0)
+    frame["eth_vol_365d_median"] = frame["eth_vol_30d"].rolling(365).median()
+    return frame
+
+
+def position_equity(position: dict[str, object] | None, mark_price: float) -> float:
+    if position is None:
+        return 0.0
+    side = str(position["side"])
+    total = 0.0
+    for leg in position["legs"]:  # type: ignore[union-attr]
+        total += trade_equity(
+            side=side,
+            margin_used=float(leg["margin"]),
+            entry_price=float(leg["entry_price"]),
+            exit_price=mark_price,
+            leverage=LEVERAGE,
+        )
+    return total
+
+
+def position_margin(position: dict[str, object] | None) -> float:
+    if position is None:
+        return 0.0
+    return sum(float(leg["margin"]) for leg in position["legs"])  # type: ignore[union-attr]
+
+
+def position_return(position: dict[str, object], mark_price: float) -> float:
+    margin = position_margin(position)
+    if margin <= 0.0:
+        return 0.0
+    return position_equity(position, mark_price) / margin - 1.0
+
+
+def account_equity(cash: float, position: dict[str, object] | None, mark_price: float) -> float:
+    return cash + position_equity(position, mark_price)
+
+
+def close_fraction(
+    *,
+    cash: float,
+    position: dict[str, object],
+    fraction: float,
+    exit_price: float,
+    ts: int,
+    reason: str,
+    trades: list[dict[str, object]],
+) -> tuple[float, dict[str, object] | None, float]:
+    side = str(position["side"])
+    closed_margin = 0.0
+    exit_value = 0.0
+    remaining_legs: list[dict[str, float]] = []
+    for leg in position["legs"]:  # type: ignore[union-attr]
+        margin = float(leg["margin"])
+        close_margin = margin * fraction
+        keep_margin = margin - close_margin
+        if close_margin > 0.0:
+            closed_margin += close_margin
+            exit_value += trade_equity(
+                side=side,
+                margin_used=close_margin,
+                entry_price=float(leg["entry_price"]),
+                exit_price=exit_price,
+                leverage=LEVERAGE,
+            )
+        if keep_margin > 1e-9:
+            remaining_legs.append({"margin": keep_margin, "entry_price": float(leg["entry_price"])})
+    if closed_margin <= 0.0:
+        return cash, position, 0.0
+    net_exit_value = exit_value - closed_margin * ROUNDTRIP_COST
+    trades.append(
+        {
+            "side": "Long" if side == "long" else "Short",
+            "entry_time": _format_ts(int(position["entry_ts"])),
+            "exit_time": _format_ts(ts),
+            "exit_ts": ts,
+            "entry_price": weighted_entry_price(position),
+            "exit_price": exit_price,
+            "margin": closed_margin,
+            "return_pct": (net_exit_value / closed_margin - 1.0) * 100.0,
+            "exit_reason": reason,
+        }
+    )
+    cash += net_exit_value
+    if not remaining_legs:
+        return cash, None, closed_margin
+    updated = dict(position)
+    updated["legs"] = remaining_legs
+    updated["t_reduced"] = bool(updated.get("t_reduced")) or reason == "t_reduce"
+    return cash, updated, closed_margin
+
+
+def weighted_entry_price(position: dict[str, object]) -> float:
+    total_margin = position_margin(position)
+    if total_margin <= 0.0:
+        return 0.0
+    return sum(float(leg["margin"]) * float(leg["entry_price"]) for leg in position["legs"]) / total_margin  # type: ignore[union-attr]
+
+
+def add_margin(position: dict[str, object], margin: float, entry_price: float) -> dict[str, object]:
+    updated = dict(position)
+    legs = list(updated["legs"])  # type: ignore[arg-type]
+    legs.append({"margin": margin, "entry_price": entry_price})
+    updated["legs"] = legs
+    updated["t_reduced"] = False
+    return updated
+
+
+def regime_cap(row: pd.Series, side: str, mode: str) -> float:
+    if mode == "none":
+        return 1.0
+    eth_ret = float(row["eth_ret_90d"])
+    btc_ret = float(row["btc_ret_90d"])
+    ratio_ret = float(row["ratio_ret_30d"])
+    if any(value != value for value in (eth_ret, btc_ret, ratio_ret)):
+        return 1.0
+    if mode == "directional":
+        if side == "long":
+            if eth_ret > 0.0 and btc_ret > 0.0 and ratio_ret > -0.05:
+                return 1.0
+            if btc_ret > -0.05 and eth_ret > -0.12:
+                return 0.65
+            return 0.0
+        if eth_ret < 0.0 or ratio_ret < -0.08:
+            return 1.0
+        if ratio_ret < 0.0 or btc_ret < -0.05:
+            return 0.65
+        return 0.0
+    if mode == "vol_scaled":
+        cap = 1.0
+        if float(row["eth_vol_30d"]) > float(row["eth_vol_365d_median"]):
+            cap *= 0.7
+        if side == "long" and ratio_ret < -0.05:
+            cap *= 0.5
+        if side == "short" and ratio_ret > 0.05:
+            cap *= 0.5
+        return cap
+    raise ValueError(f"unknown regime mode: {mode}")
+
+
+def dynamic_multiplier(row: pd.Series, closed_returns: list[float], variant: Variant) -> float:
+    if variant.dynamic_mode == "none":
+        return 1.0
+    if variant.dynamic_mode == "vol_only":
+        vol = float(row["eth_vol_96"])
+        if vol == vol and vol > 0.005:
+            return 0.8
+        return 1.0
+    if variant.dynamic_mode == "ratio_soft":
+        ratio = float(row["ratio_ret_30d"])
+        if ratio != ratio:
+            return 1.0
+        if ratio < -0.08:
+            return 0.75
+        if ratio > 0.08:
+            return 1.05
+        return 1.0
+    if variant.dynamic_mode != "closed_trade":
+        raise ValueError(f"unknown dynamic mode: {variant.dynamic_mode}")
+    mult = 1.0
+    vol = float(row["eth_vol_96"])
+    if vol == vol and vol > 0.005:
+        mult *= 0.7
+    recent = closed_returns[-20:]
+    if len(recent) >= 8:
+        avg = sum(recent) / len(recent)
+        if avg < -0.01:
+            mult *= 0.5
+        elif avg > 0.015:
+            mult *= 1.15
+    return min(1.0, max(0.25, mult))
+
+
+def should_readd(position: dict[str, object], row: pd.Series, variant: Variant) -> bool:
+    if not variant.t_overlay or not bool(position.get("t_reduced")):
+        return False
+    side = str(position["side"])
+    middle = float(row["middle"])
+    close = float(row["close"])
+    if variant.readd_mode == "never":
+        return False
+    if variant.readd_mode == "middle_trend":
+        if side == "long":
+            return close <= middle * (1.0 + variant.readd_buffer_pct) and float(row["ratio_ret_30d"]) >= -0.03
+        return close >= middle * (1.0 - variant.readd_buffer_pct) and float(row["ratio_ret_30d"]) <= 0.03
+    if variant.readd_mode != "middle":
+        raise ValueError(f"unknown readd mode: {variant.readd_mode}")
+    if side == "long":
+        return close <= middle * (1.0 + variant.readd_buffer_pct)
+    return close >= middle * (1.0 - variant.readd_buffer_pct)
+
+
+def target_unit(row: pd.Series, side: str, variant: Variant, closed_returns: list[float]) -> float:
+    return regime_cap(row, side, variant.regime) * dynamic_multiplier(row, closed_returns, variant)
+
+
+def run_variant(frame: pd.DataFrame, variant: Variant) -> dict[str, object]:
+    cash = INITIAL_EQUITY
+    position: dict[str, object] | None = None
+    pending_entry_side: str | None = None
+    pending_full_exit: str | None = None
+    pending_t_reduce = False
+    pending_readd = False
+    cooldown_until = -1
+    middle_exit_streak = 0
+    trades: list[dict[str, object]] = []
+    equity_rows: list[dict[str, object]] = []
+    event_rows: list[dict[str, object]] = []
+    closed_returns: list[float] = []
+
+    for index in range(WARMUP_BARS, len(frame)):
+        row = frame.iloc[index]
+        ts = int(row["ts"])
+        open_price = float(row["open"])
+        close_price = float(row["close"])
+
+        if pending_full_exit and position is not None:
+            margin_before = position_margin(position)
+            cash, position, _ = close_fraction(
+                cash=cash,
+                position=position,
+                fraction=1.0,
+                exit_price=open_price,
+                ts=ts,
+                reason=pending_full_exit,
+                trades=trades,
+            )
+            if trades:
+                closed_returns.append(float(trades[-1]["return_pct"]) / 100.0)
+            event_rows.append({"ts": ts, "event": pending_full_exit, "margin": margin_before})
+            pending_full_exit = None
+            pending_t_reduce = False
+            pending_readd = False
+            middle_exit_streak = 0
+            cooldown_until = index + 24
+
+        if pending_t_reduce and position is not None:
+            cash, position, closed_margin = close_fraction(
+                cash=cash,
+                position=position,
+                fraction=variant.t_fraction,
+                exit_price=open_price,
+                ts=ts,
+                reason="t_reduce",
+                trades=trades,
+            )
+            if closed_margin > 0.0 and trades:
+                closed_returns.append(float(trades[-1]["return_pct"]) / 100.0)
+                event_rows.append({"ts": ts, "event": "t_reduce", "margin": closed_margin})
+            pending_t_reduce = False
+
+        if pending_readd and position is not None:
+            unit = target_unit(row, str(position["side"]), variant, closed_returns)
+            equity = account_equity(cash, position, open_price)
+            desired_margin = equity * unit
+            missing = max(0.0, desired_margin - position_margin(position))
+            add = min(cash, missing)
+            if add > 1.0:
+                cash -= add
+                position = add_margin(position, add, open_price)
+                event_rows.append({"ts": ts, "event": "t_readd", "margin": add})
+            pending_readd = False
+
+        if pending_entry_side is not None and position is None and cash > 1.0:
+            unit = target_unit(row, pending_entry_side, variant, closed_returns)
+            margin = cash * unit
+            if margin > 1.0:
+                cash -= margin
+                position = {
+                    "side": pending_entry_side,
+                    "entry_ts": ts,
+                    "entry_index": index,
+                    "legs": [{"margin": margin, "entry_price": open_price}],
+                    "stop_price": open_price * (0.99 if pending_entry_side == "long" else 1.01),
+                    "t_reduced": False,
+                }
+                event_rows.append({"ts": ts, "event": f"entry_{pending_entry_side}", "margin": margin})
+            pending_entry_side = None
+
+        if position is not None:
+            side = str(position["side"])
+            stop_hit = (side == "long" and float(row["low"]) <= float(position["stop_price"])) or (
+                side == "short" and float(row["high"]) >= float(position["stop_price"])
+            )
+            if stop_hit:
+                margin_before = position_margin(position)
+                cash, position, _ = close_fraction(
+                    cash=cash,
+                    position=position,
+                    fraction=1.0,
+                    exit_price=float(position["stop_price"]),
+                    ts=ts,
+                    reason="stop",
+                    trades=trades,
+                )
+                if trades:
+                    closed_returns.append(float(trades[-1]["return_pct"]) / 100.0)
+                event_rows.append({"ts": ts, "event": "stop", "margin": margin_before})
+                middle_exit_streak = 0
+                cooldown_until = index + 24
+
+        equity = account_equity(cash, position, close_price)
+        equity_rows.append(
+            {
+                "ts": ts,
+                "time": pd.to_datetime(ts, unit="ms", utc=True),
+                "equity": equity,
+                "cash": cash,
+                "position_margin": position_margin(position),
+                "position_side": "flat" if position is None else str(position["side"]),
+            }
+        )
+        if index == len(frame) - 1 or equity <= 0.0:
+            continue
+
+        needed = ("middle", "upper", "lower", "bandwidth", "bandwidth_threshold", "eth_vol_96")
+        if any(float(row[key]) != float(row[key]) for key in needed):
+            continue
+
+        if position is not None:
+            side = str(position["side"])
+            middle_exit = (side == "long" and close_price < float(row["middle"]) * (1.0 - 0.0005)) or (
+                side == "short" and close_price > float(row["middle"]) * (1.0 + 0.0005)
+            )
+            middle_exit_streak = middle_exit_streak + 1 if middle_exit else 0
+            if middle_exit_streak >= 1:
+                pending_full_exit = "middle_exit"
+                continue
+            if variant.t_overlay and not bool(position.get("t_reduced")):
+                band_half = float(row["upper"]) - float(row["middle"])
+                far = (side == "long" and close_price >= float(row["upper"]) + 0.2 * band_half) or (
+                    side == "short" and close_price <= float(row["lower"]) - 0.2 * band_half
+                )
+                if far and position_return(position, close_price) >= variant.t_profit_pct:
+                    pending_t_reduce = True
+                    continue
+            if should_readd(position, row, variant):
+                pending_readd = True
+            continue
+
+        if index < cooldown_until or float(row["eth_vol_96"]) > 0.006:
+            continue
+        compressed = float(row["bandwidth"]) <= float(row["bandwidth_threshold"])
+        if not compressed:
+            continue
+        if close_price > float(row["upper"]):
+            if target_unit(row, "long", variant, closed_returns) > 0.0:
+                pending_entry_side = "long"
+        elif close_price < float(row["lower"]):
+            if target_unit(row, "short", variant, closed_returns) > 0.0:
+                pending_entry_side = "short"
+
+    return {
+        "variant": variant,
+        "equity": pd.DataFrame(equity_rows),
+        "trades": pd.DataFrame(trades),
+        "events": pd.DataFrame(event_rows),
+    }
+
+
+def max_drawdown(values: pd.Series) -> float:
+    peak = values.cummax()
+    return float(((peak - values) / peak).max()) if len(values) else 0.0
+
+
+def metrics(equity: pd.DataFrame, trades: pd.DataFrame, label: str, offset: pd.DateOffset | None) -> dict[str, object]:
+    end = pd.Timestamp(equity["time"].iloc[-1])
+    if offset is None:
+        scoped = equity.copy()
+    else:
+        cutoff = end - offset
+        before = equity[equity["time"] <= cutoff]
+        start_equity = float(before["equity"].iloc[-1]) if len(before) else float(equity["equity"].iloc[0])
+        after = equity[equity["time"] > cutoff]
+        scoped = pd.concat([pd.DataFrame([{"time": cutoff, "equity": start_equity}]), after[["time", "equity"]]], ignore_index=True)
+    start = pd.Timestamp(scoped["time"].iloc[0])
+    years = max((end - start).total_seconds() / 86_400 / 365, 1e-9)
+    total_return = float(scoped["equity"].iloc[-1] / scoped["equity"].iloc[0] - 1.0)
+    annualized = (1.0 + total_return) ** (1.0 / years) - 1.0 if total_return > -1.0 else -1.0
+    if trades.empty:
+        trade_count = 0
+        win_rate = 0.0
+        profit_factor = 0.0
+    else:
+        trade_times = pd.to_datetime(trades["exit_ts"], unit="ms", utc=True)
+        scoped_trades = trades[(trade_times > start) & (trade_times <= end)]
+        trade_count = len(scoped_trades)
+        returns = scoped_trades["return_pct"].astype(float) / 100.0 if trade_count else pd.Series(dtype=float)
+        win_rate = float((returns > 0.0).mean()) if trade_count else 0.0
+        gains = float(returns[returns > 0.0].sum())
+        losses = abs(float(returns[returns < 0.0].sum()))
+        profit_factor = gains / losses if losses > 0.0 else 0.0
+    dd = max_drawdown(scoped["equity"].astype(float))
+    return {
+        "horizon": label,
+        "start": start.strftime("%Y-%m-%d %H:%M"),
+        "end": end.strftime("%Y-%m-%d %H:%M"),
+        "total_return": total_return,
+        "annualized_return": annualized,
+        "max_drawdown": dd,
+        "calmar": annualized / dd if dd else 0.0,
+        "trades": trade_count,
+        "trades_per_30d": trade_count / years / 365 * 30,
+        "win_rate": win_rate,
+        "profit_factor": profit_factor,
+    }
+
+
+def build_variants() -> list[Variant]:
+    return [
+        Variant("baseline", "none", False, False, 0.0, 0.0, 0.0, "middle", "none"),
+        Variant("dynamic_exposure", "none", True, False, 0.0, 0.0, 0.0, "middle", "closed_trade"),
+        Variant("dynamic_vol_only", "none", True, False, 0.0, 0.0, 0.0, "middle", "vol_only"),
+        Variant("dynamic_ratio_soft", "none", True, False, 0.0, 0.0, 0.0, "middle", "ratio_soft"),
+        Variant("regime_directional", "directional", False, False, 0.0, 0.0, 0.0, "middle", "none"),
+        Variant("regime_vol_scaled", "vol_scaled", False, False, 0.0, 0.0, 0.0, "middle", "none"),
+        Variant("t_overlay_p012_f20_never", "none", False, True, 0.012, 0.20, 0.001, "never", "none"),
+        Variant("t_overlay_p012_f30", "none", False, True, 0.012, 0.30, 0.001, "middle", "none"),
+        Variant("t_overlay_p012_f30_never", "none", False, True, 0.012, 0.30, 0.001, "never", "none"),
+        Variant("t_overlay_p018_f20_middle_trend", "none", False, True, 0.018, 0.20, 0.001, "middle_trend", "none"),
+        Variant("t_overlay_p018_f30", "none", False, True, 0.018, 0.30, 0.001, "middle", "none"),
+        Variant("t_overlay_p024_f20_never", "none", False, True, 0.024, 0.20, 0.001, "never", "none"),
+        Variant("dynamic_t_p012_f30", "none", True, True, 0.012, 0.30, 0.001, "middle", "closed_trade"),
+        Variant("dynamic_t_p018_f20_middle_trend", "none", True, True, 0.018, 0.20, 0.001, "middle_trend", "closed_trade"),
+        Variant("vol_dynamic_t_p024_f20_never", "none", True, True, 0.024, 0.20, 0.001, "never", "vol_only"),
+        Variant("ratio_dynamic_t_p024_f20_never", "none", True, True, 0.024, 0.20, 0.001, "never", "ratio_soft"),
+        Variant("regime_directional_dynamic", "directional", True, False, 0.0, 0.0, 0.0, "middle", "closed_trade"),
+        Variant("regime_directional_t_p012_f30", "directional", False, True, 0.012, 0.30, 0.001, "middle", "none"),
+        Variant("layered_directional_dynamic_t_p012_f30", "directional", True, True, 0.012, 0.30, 0.001, "middle", "closed_trade"),
+        Variant("layered_vol_dynamic_t_p012_f30", "vol_scaled", True, True, 0.012, 0.30, 0.001, "middle", "closed_trade"),
+        Variant("layered_vol_dynamic_t_p018_f20_middle_trend", "vol_scaled", True, True, 0.018, 0.20, 0.001, "middle_trend", "closed_trade"),
+        Variant("layered_vol_t_p024_f20_never", "vol_scaled", False, True, 0.024, 0.20, 0.001, "never", "none"),
+        Variant("layered_vol_ratio_t_p024_f20_never", "vol_scaled", True, True, 0.024, 0.20, 0.001, "never", "ratio_soft"),
+    ]
+
+
+def markdown_table(frame: pd.DataFrame) -> str:
+    def cell(value: object) -> str:
+        if isinstance(value, float):
+            return f"{value:.6g}"
+        return str(value).replace("|", "\\|")
+
+    rows = [list(frame.columns), ["---" for _ in frame.columns]]
+    rows.extend(frame.astype(object).where(pd.notna(frame), "").values.tolist())
+    return "\n".join("| " + " | ".join(cell(value) for value in row) + " |" for row in rows)
+
+
+def write_report(summary: pd.DataFrame, horizons: pd.DataFrame, events: pd.DataFrame, paths: list[Path], command: str) -> str:
+    ranking = summary.sort_values(["recent_min_return", "recent_min_calmar", "full_max_drawdown"], ascending=[False, False, True])
+    baseline = horizons[horizons["variant"] == "baseline"]
+    best = ranking.iloc[0]
+    decision = "No"
+    baseline_h = horizons[horizons["variant"] == "baseline"].set_index("horizon")
+    best_h = horizons[horizons["variant"] == best["variant"]].set_index("horizon")
+    medium_not_worse = all(float(best_h.loc[horizon, "total_return"]) >= float(baseline_h.loc[horizon, "total_return"]) for horizon in ("1y", "6m", "3m"))
+    short_not_worse = all(float(best_h.loc[horizon, "total_return"]) >= float(baseline_h.loc[horizon, "total_return"]) for horizon in ("30d", "14d"))
+    if best["variant"] != "baseline" and medium_not_worse and short_not_worse and float(best["full_max_drawdown"]) <= float(summary.loc[summary["variant"] == "baseline", "full_max_drawdown"].iloc[0]):
+        decision = "Paper-observe only"
+    return (
+        "# Layered BB squeeze position exploration\n\n"
+        "Scope: offline local-candle research only. No live executor, deployment, credentials, or order path was changed.\n\n"
+        f"Run command: `{command}`\n\n"
+        "Layer model: long-term regime cap + medium-term live BB squeeze signal + optional dynamic exposure + optional in-position T overlay.\n\n"
+        "Output files:\n"
+        + "\n".join(f"- `{path}`" for path in paths)
+        + "\n\n"
+        "## Summary Ranking\n\n"
+        + markdown_table(ranking[["variant", "full_total_return", "full_max_drawdown", "full_calmar", "recent_min_return", "recent_min_calmar", "trades", "t_reduce_events", "t_readd_events"]])
+        + "\n\n"
+        "## Baseline Horizons\n\n"
+        + markdown_table(baseline[["horizon", "total_return", "annualized_return", "max_drawdown", "calmar", "trades", "trades_per_30d", "win_rate", "profit_factor"]])
+        + "\n\n"
+        "## Best Variant Horizons\n\n"
+        + markdown_table(horizons[horizons["variant"] == best["variant"]][["horizon", "total_return", "annualized_return", "max_drawdown", "calmar", "trades", "trades_per_30d", "win_rate", "profit_factor"]])
+        + "\n\n"
+        "## Event Counts\n\n"
+        + markdown_table(events)
+        + "\n\n"
+        "## Decision\n\n"
+        f"- Best ranked variant: `{best['variant']}`.\n"
+        f"- Replace live strategy now: {decision}.\n"
+    )
+
+
+def main() -> int:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--bar", default=BAR)
+    parser.add_argument("--output-dir", type=Path, default=OUTPUT_DIR)
+    args = parser.parse_args()
+
+    eth, btc = align_pair(load_candles(ETH_SYMBOL, args.bar), load_candles(BTC_SYMBOL, args.bar))
+    frame = indicators(eth, btc)
+    summary_rows: list[dict[str, object]] = []
+    horizon_rows: list[dict[str, object]] = []
+    event_rows: list[dict[str, object]] = []
+    equity_frames: list[pd.DataFrame] = []
+
+    for variant in build_variants():
+        result = run_variant(frame, variant)
+        equity = result["equity"].copy()
+        trades = result["trades"]
+        events = result["events"]
+        equity["variant"] = variant.name
+        equity_frames.append(equity)
+        horizon_metrics = [metrics(equity, trades, label, offset) for label, offset in HORIZONS]
+        for row in horizon_metrics:
+            horizon_rows.append({"variant": variant.name, **row})
+        by_horizon = {row["horizon"]: row for row in horizon_metrics}
+        event_counts = events["event"].value_counts().to_dict() if not events.empty else {}
+        event_rows.append(
+            {
+                "variant": variant.name,
+                "entry_events": int(sum(value for key, value in event_counts.items() if str(key).startswith("entry_"))),
+                "middle_exit_events": int(event_counts.get("middle_exit", 0)),
+                "stop_events": int(event_counts.get("stop", 0)),
+                "t_reduce_events": int(event_counts.get("t_reduce", 0)),
+                "t_readd_events": int(event_counts.get("t_readd", 0)),
+            }
+        )
+        recent_returns = [float(by_horizon[label]["total_return"]) for label in ("1y", "6m", "3m", "30d", "14d")]
+        recent_calmars = [float(by_horizon[label]["calmar"]) for label in ("1y", "6m", "3m", "30d", "14d")]
+        summary_rows.append(
+            {
+                "variant": variant.name,
+                "regime": variant.regime,
+                "dynamic_exposure": variant.dynamic_exposure,
+                "dynamic_mode": variant.dynamic_mode,
+                "t_overlay": variant.t_overlay,
+                "t_profit_pct": variant.t_profit_pct,
+                "t_fraction": variant.t_fraction,
+                "readd_mode": variant.readd_mode,
+                "trades": int(by_horizon["full"]["trades"]),
+                "full_total_return": by_horizon["full"]["total_return"],
+                "full_annualized_return": by_horizon["full"]["annualized_return"],
+                "full_max_drawdown": by_horizon["full"]["max_drawdown"],
+                "full_calmar": by_horizon["full"]["calmar"],
+                "recent_min_return": min(recent_returns),
+                "recent_min_calmar": min(recent_calmars),
+                "t_reduce_events": int(event_counts.get("t_reduce", 0)),
+                "t_readd_events": int(event_counts.get("t_readd", 0)),
+            }
+        )
+        print(f"done {variant.name}")
+
+    summary = pd.DataFrame(summary_rows)
+    horizons = pd.DataFrame(horizon_rows)
+    events = pd.DataFrame(event_rows)
+    equity_out = pd.concat(equity_frames, ignore_index=True)
+
+    args.output_dir.mkdir(parents=True, exist_ok=True)
+    prefix = "layered-bb-squeeze-position"
+    summary_path = args.output_dir / f"{prefix}-summary.csv"
+    horizons_path = args.output_dir / f"{prefix}-horizons.csv"
+    events_path = args.output_dir / f"{prefix}-events.csv"
+    equity_path = args.output_dir / f"{prefix}-equity.csv"
+    report_path = args.output_dir / f"{prefix}-report.md"
+    paths = [summary_path, horizons_path, events_path, equity_path, report_path]
+    summary.to_csv(summary_path, index=False)
+    horizons.to_csv(horizons_path, index=False)
+    events.to_csv(events_path, index=False)
+    equity_out.to_csv(equity_path, index=False)
+    command = f"rtk .venv/bin/python {Path(__file__).as_posix()} --bar {args.bar} --output-dir {args.output_dir.as_posix()}"
+    report_path.write_text(write_report(summary, horizons, events, paths, command), encoding="utf-8")
+    print(summary.sort_values(["recent_min_return", "recent_min_calmar"], ascending=[False, False]).head(10).to_string(index=False))
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())