Compare commits

..
22 changed files with 1406 additions and 1551 deletions
@@ -0,0 +1,46 @@
window,gate,start,end,trade_dates,gate_open,gate_closed,trip_rate,base_ann,base_sharpe,base_maxDD,gated_ann,gated_sharpe,gated_maxDD
2026,hitrate_5d_0.50,2026-01-04,2026-08-19,157,92,65,0.414,0.255023,1.4465,-0.080671,0.649911,5.7351,-0.031795
2026,hitrate_5d_0.60,2026-01-04,2026-08-19,157,49,108,0.6879,0.255023,1.4465,-0.080671,0.489313,6.0111,-0.014864
2026,hitrate_5d_0.70,2026-01-04,2026-08-19,157,15,142,0.9045,0.255023,1.4465,-0.080671,0.229523,4.3611,-0.002029
2026,hitrate_10d_0.50,2026-01-04,2026-08-19,157,109,48,0.3057,0.255023,1.4465,-0.080671,0.335822,2.6391,-0.060142
2026,hitrate_10d_0.60,2026-01-04,2026-08-19,157,36,121,0.7707,0.255023,1.4465,-0.080671,0.251108,3.8579,-0.026842
2026,hitrate_10d_0.70,2026-01-04,2026-08-19,157,9,148,0.9427,0.255023,1.4465,-0.080671,0.039314,1.3415,-0.012061
2026,hitrate_20d_0.40,2026-01-04,2026-08-19,157,157,0,0.0,0.255023,1.4465,-0.080671,0.255023,1.4465,-0.080671
2026,hitrate_20d_0.50,2026-01-04,2026-08-19,157,118,39,0.2484,0.255023,1.4465,-0.080671,0.343819,2.5185,-0.06259
2026,hitrate_20d_0.60,2026-01-04,2026-08-19,157,28,129,0.8217,0.255023,1.4465,-0.080671,0.020214,0.3601,-0.028489
2025,hitrate_5d_0.50,2025-01-02,2025-12-31,250,153,97,0.388,0.177515,0.8573,-0.217417,0.72055,6.9599,-0.048495
2025,hitrate_5d_0.60,2025-01-02,2025-12-31,250,90,160,0.64,0.177515,0.8573,-0.217417,0.48564,5.3623,-0.046673
2025,hitrate_5d_0.70,2025-01-02,2025-12-31,250,39,211,0.844,0.177515,0.8573,-0.217417,0.249221,4.9448,-0.009619
2025,hitrate_10d_0.50,2025-01-02,2025-12-31,250,170,80,0.32,0.177515,0.8573,-0.217417,0.435847,3.5614,-0.055776
2025,hitrate_10d_0.60,2025-01-02,2025-12-31,250,66,184,0.736,0.177515,0.8573,-0.217417,0.324574,5.2279,-0.016028
2025,hitrate_10d_0.70,2025-01-02,2025-12-31,250,14,236,0.944,0.177515,0.8573,-0.217417,0.043002,1.4635,-0.010154
2025,hitrate_20d_0.40,2025-01-02,2025-12-31,250,247,3,0.012,0.177515,0.8573,-0.217417,0.202949,0.9896,-0.217417
2025,hitrate_20d_0.50,2025-01-02,2025-12-31,250,177,73,0.292,0.177515,0.8573,-0.217417,0.340772,2.8303,-0.071731
2025,hitrate_20d_0.60,2025-01-02,2025-12-31,250,48,202,0.808,0.177515,0.8573,-0.217417,0.266028,4.1749,-0.021527
2024,hitrate_5d_0.50,2024-01-02,2024-12-31,253,139,114,0.4506,0.08229,0.5594,-0.10685,0.30351,2.4275,-0.088219
2024,hitrate_5d_0.60,2024-01-02,2024-12-31,253,71,182,0.7194,0.08229,0.5594,-0.10685,0.265365,2.4908,-0.078304
2024,hitrate_5d_0.70,2024-01-02,2024-12-31,253,19,234,0.9249,0.08229,0.5594,-0.10685,0.143125,3.377,-0.003364
2024,hitrate_10d_0.50,2024-01-02,2024-12-31,253,157,96,0.3794,0.08229,0.5594,-0.10685,0.277421,2.5767,-0.043146
2024,hitrate_10d_0.60,2024-01-02,2024-12-31,253,37,216,0.8538,0.08229,0.5594,-0.10685,0.207161,3.6083,-0.011122
2024,hitrate_10d_0.70,2024-01-02,2024-12-31,253,7,246,0.9723,0.08229,0.5594,-0.10685,0.031785,1.7408,-0.002083
2024,hitrate_20d_0.40,2024-01-02,2024-12-31,253,247,6,0.0237,0.08229,0.5594,-0.10685,0.078007,0.5342,-0.10685
2024,hitrate_20d_0.50,2024-01-02,2024-12-31,253,177,76,0.3004,0.08229,0.5594,-0.10685,0.210672,1.8469,-0.056207
2024,hitrate_20d_0.60,2024-01-02,2024-12-31,253,13,240,0.9486,0.08229,0.5594,-0.10685,0.005953,0.3039,-0.014443
2023,hitrate_5d_0.50,2023-01-03,2023-12-29,250,139,111,0.444,-0.047644,-0.2738,-0.197856,0.546654,4.2124,-0.035676
2023,hitrate_5d_0.60,2023-01-03,2023-12-29,250,79,171,0.684,-0.047644,-0.2738,-0.197856,0.556291,5.1026,-0.025449
2023,hitrate_5d_0.70,2023-01-03,2023-12-29,250,34,216,0.864,-0.047644,-0.2738,-0.197856,0.404269,4.4477,-0.013842
2023,hitrate_10d_0.50,2023-01-03,2023-12-29,250,148,102,0.408,-0.047644,-0.2738,-0.197856,0.459869,3.5211,-0.046921
2023,hitrate_10d_0.60,2023-01-03,2023-12-29,250,62,188,0.752,-0.047644,-0.2738,-0.197856,0.368232,4.0148,-0.022983
2023,hitrate_10d_0.70,2023-01-03,2023-12-29,250,13,237,0.948,-0.047644,-0.2738,-0.197856,0.091481,2.1324,-0.010866
2023,hitrate_20d_0.40,2023-01-03,2023-12-29,250,237,13,0.052,-0.047644,-0.2738,-0.197856,0.06639,0.3895,-0.146704
2023,hitrate_20d_0.50,2023-01-03,2023-12-29,250,149,101,0.404,-0.047644,-0.2738,-0.197856,0.366016,2.5969,-0.063998
2023,hitrate_20d_0.60,2023-01-03,2023-12-29,250,40,210,0.84,-0.047644,-0.2738,-0.197856,0.149844,2.2645,-0.032267
2021,hitrate_5d_0.50,2021-01-04,2021-12-31,252,145,107,0.4246,0.18367,1.0979,-0.102651,0.556615,5.9167,-0.028062
2021,hitrate_5d_0.60,2021-01-04,2021-12-31,252,68,184,0.7302,0.18367,1.0979,-0.102651,0.351867,5.8468,-0.011638
2021,hitrate_5d_0.70,2021-01-04,2021-12-31,252,26,226,0.8968,0.18367,1.0979,-0.102651,0.148417,3.9593,-0.0041
2021,hitrate_10d_0.50,2021-01-04,2021-12-31,252,163,89,0.3532,0.18367,1.0979,-0.102651,0.465023,4.2342,-0.040569
2021,hitrate_10d_0.60,2021-01-04,2021-12-31,252,51,201,0.7976,0.18367,1.0979,-0.102651,0.21802,4.7978,-0.011134
2021,hitrate_10d_0.70,2021-01-04,2021-12-31,252,13,239,0.9484,0.18367,1.0979,-0.102651,0.06108,2.7055,-0.000262
2021,hitrate_20d_0.40,2021-01-04,2021-12-31,252,252,0,0.0,0.18367,1.0979,-0.102651,0.18367,1.0979,-0.102651
2021,hitrate_20d_0.50,2021-01-04,2021-12-31,252,166,86,0.3413,0.18367,1.0979,-0.102651,0.289756,2.5787,-0.049244
2021,hitrate_20d_0.60,2021-01-04,2021-12-31,252,38,214,0.8492,0.18367,1.0979,-0.102651,0.104685,2.394,-0.015084
1 window gate start end trade_dates gate_open gate_closed trip_rate base_ann base_sharpe base_maxDD gated_ann gated_sharpe gated_maxDD
2 2026 hitrate_5d_0.50 2026-01-04 2026-08-19 157 92 65 0.414 0.255023 1.4465 -0.080671 0.649911 5.7351 -0.031795
3 2026 hitrate_5d_0.60 2026-01-04 2026-08-19 157 49 108 0.6879 0.255023 1.4465 -0.080671 0.489313 6.0111 -0.014864
4 2026 hitrate_5d_0.70 2026-01-04 2026-08-19 157 15 142 0.9045 0.255023 1.4465 -0.080671 0.229523 4.3611 -0.002029
5 2026 hitrate_10d_0.50 2026-01-04 2026-08-19 157 109 48 0.3057 0.255023 1.4465 -0.080671 0.335822 2.6391 -0.060142
6 2026 hitrate_10d_0.60 2026-01-04 2026-08-19 157 36 121 0.7707 0.255023 1.4465 -0.080671 0.251108 3.8579 -0.026842
7 2026 hitrate_10d_0.70 2026-01-04 2026-08-19 157 9 148 0.9427 0.255023 1.4465 -0.080671 0.039314 1.3415 -0.012061
8 2026 hitrate_20d_0.40 2026-01-04 2026-08-19 157 157 0 0.0 0.255023 1.4465 -0.080671 0.255023 1.4465 -0.080671
9 2026 hitrate_20d_0.50 2026-01-04 2026-08-19 157 118 39 0.2484 0.255023 1.4465 -0.080671 0.343819 2.5185 -0.06259
10 2026 hitrate_20d_0.60 2026-01-04 2026-08-19 157 28 129 0.8217 0.255023 1.4465 -0.080671 0.020214 0.3601 -0.028489
11 2025 hitrate_5d_0.50 2025-01-02 2025-12-31 250 153 97 0.388 0.177515 0.8573 -0.217417 0.72055 6.9599 -0.048495
12 2025 hitrate_5d_0.60 2025-01-02 2025-12-31 250 90 160 0.64 0.177515 0.8573 -0.217417 0.48564 5.3623 -0.046673
13 2025 hitrate_5d_0.70 2025-01-02 2025-12-31 250 39 211 0.844 0.177515 0.8573 -0.217417 0.249221 4.9448 -0.009619
14 2025 hitrate_10d_0.50 2025-01-02 2025-12-31 250 170 80 0.32 0.177515 0.8573 -0.217417 0.435847 3.5614 -0.055776
15 2025 hitrate_10d_0.60 2025-01-02 2025-12-31 250 66 184 0.736 0.177515 0.8573 -0.217417 0.324574 5.2279 -0.016028
16 2025 hitrate_10d_0.70 2025-01-02 2025-12-31 250 14 236 0.944 0.177515 0.8573 -0.217417 0.043002 1.4635 -0.010154
17 2025 hitrate_20d_0.40 2025-01-02 2025-12-31 250 247 3 0.012 0.177515 0.8573 -0.217417 0.202949 0.9896 -0.217417
18 2025 hitrate_20d_0.50 2025-01-02 2025-12-31 250 177 73 0.292 0.177515 0.8573 -0.217417 0.340772 2.8303 -0.071731
19 2025 hitrate_20d_0.60 2025-01-02 2025-12-31 250 48 202 0.808 0.177515 0.8573 -0.217417 0.266028 4.1749 -0.021527
20 2024 hitrate_5d_0.50 2024-01-02 2024-12-31 253 139 114 0.4506 0.08229 0.5594 -0.10685 0.30351 2.4275 -0.088219
21 2024 hitrate_5d_0.60 2024-01-02 2024-12-31 253 71 182 0.7194 0.08229 0.5594 -0.10685 0.265365 2.4908 -0.078304
22 2024 hitrate_5d_0.70 2024-01-02 2024-12-31 253 19 234 0.9249 0.08229 0.5594 -0.10685 0.143125 3.377 -0.003364
23 2024 hitrate_10d_0.50 2024-01-02 2024-12-31 253 157 96 0.3794 0.08229 0.5594 -0.10685 0.277421 2.5767 -0.043146
24 2024 hitrate_10d_0.60 2024-01-02 2024-12-31 253 37 216 0.8538 0.08229 0.5594 -0.10685 0.207161 3.6083 -0.011122
25 2024 hitrate_10d_0.70 2024-01-02 2024-12-31 253 7 246 0.9723 0.08229 0.5594 -0.10685 0.031785 1.7408 -0.002083
26 2024 hitrate_20d_0.40 2024-01-02 2024-12-31 253 247 6 0.0237 0.08229 0.5594 -0.10685 0.078007 0.5342 -0.10685
27 2024 hitrate_20d_0.50 2024-01-02 2024-12-31 253 177 76 0.3004 0.08229 0.5594 -0.10685 0.210672 1.8469 -0.056207
28 2024 hitrate_20d_0.60 2024-01-02 2024-12-31 253 13 240 0.9486 0.08229 0.5594 -0.10685 0.005953 0.3039 -0.014443
29 2023 hitrate_5d_0.50 2023-01-03 2023-12-29 250 139 111 0.444 -0.047644 -0.2738 -0.197856 0.546654 4.2124 -0.035676
30 2023 hitrate_5d_0.60 2023-01-03 2023-12-29 250 79 171 0.684 -0.047644 -0.2738 -0.197856 0.556291 5.1026 -0.025449
31 2023 hitrate_5d_0.70 2023-01-03 2023-12-29 250 34 216 0.864 -0.047644 -0.2738 -0.197856 0.404269 4.4477 -0.013842
32 2023 hitrate_10d_0.50 2023-01-03 2023-12-29 250 148 102 0.408 -0.047644 -0.2738 -0.197856 0.459869 3.5211 -0.046921
33 2023 hitrate_10d_0.60 2023-01-03 2023-12-29 250 62 188 0.752 -0.047644 -0.2738 -0.197856 0.368232 4.0148 -0.022983
34 2023 hitrate_10d_0.70 2023-01-03 2023-12-29 250 13 237 0.948 -0.047644 -0.2738 -0.197856 0.091481 2.1324 -0.010866
35 2023 hitrate_20d_0.40 2023-01-03 2023-12-29 250 237 13 0.052 -0.047644 -0.2738 -0.197856 0.06639 0.3895 -0.146704
36 2023 hitrate_20d_0.50 2023-01-03 2023-12-29 250 149 101 0.404 -0.047644 -0.2738 -0.197856 0.366016 2.5969 -0.063998
37 2023 hitrate_20d_0.60 2023-01-03 2023-12-29 250 40 210 0.84 -0.047644 -0.2738 -0.197856 0.149844 2.2645 -0.032267
38 2021 hitrate_5d_0.50 2021-01-04 2021-12-31 252 145 107 0.4246 0.18367 1.0979 -0.102651 0.556615 5.9167 -0.028062
39 2021 hitrate_5d_0.60 2021-01-04 2021-12-31 252 68 184 0.7302 0.18367 1.0979 -0.102651 0.351867 5.8468 -0.011638
40 2021 hitrate_5d_0.70 2021-01-04 2021-12-31 252 26 226 0.8968 0.18367 1.0979 -0.102651 0.148417 3.9593 -0.0041
41 2021 hitrate_10d_0.50 2021-01-04 2021-12-31 252 163 89 0.3532 0.18367 1.0979 -0.102651 0.465023 4.2342 -0.040569
42 2021 hitrate_10d_0.60 2021-01-04 2021-12-31 252 51 201 0.7976 0.18367 1.0979 -0.102651 0.21802 4.7978 -0.011134
43 2021 hitrate_10d_0.70 2021-01-04 2021-12-31 252 13 239 0.9484 0.18367 1.0979 -0.102651 0.06108 2.7055 -0.000262
44 2021 hitrate_20d_0.40 2021-01-04 2021-12-31 252 252 0 0.0 0.18367 1.0979 -0.102651 0.18367 1.0979 -0.102651
45 2021 hitrate_20d_0.50 2021-01-04 2021-12-31 252 166 86 0.3413 0.18367 1.0979 -0.102651 0.289756 2.5787 -0.049244
46 2021 hitrate_20d_0.60 2021-01-04 2021-12-31 252 38 214 0.8492 0.18367 1.0979 -0.102651 0.104685 2.394 -0.015084
@@ -0,0 +1,722 @@
[
{
"window": "2026",
"gate": "hitrate_5d_0.50",
"start": "2026-01-04",
"end": "2026-08-19",
"trade_dates": 157,
"gate_open": 92,
"gate_closed": 65,
"trip_rate": 0.414,
"base_ann": 0.255023,
"base_sharpe": 1.4465,
"base_maxDD": -0.080671,
"gated_ann": 0.649911,
"gated_sharpe": 5.7351,
"gated_maxDD": -0.031795
},
{
"window": "2026",
"gate": "hitrate_5d_0.60",
"start": "2026-01-04",
"end": "2026-08-19",
"trade_dates": 157,
"gate_open": 49,
"gate_closed": 108,
"trip_rate": 0.6879,
"base_ann": 0.255023,
"base_sharpe": 1.4465,
"base_maxDD": -0.080671,
"gated_ann": 0.489313,
"gated_sharpe": 6.0111,
"gated_maxDD": -0.014864
},
{
"window": "2026",
"gate": "hitrate_5d_0.70",
"start": "2026-01-04",
"end": "2026-08-19",
"trade_dates": 157,
"gate_open": 15,
"gate_closed": 142,
"trip_rate": 0.9045,
"base_ann": 0.255023,
"base_sharpe": 1.4465,
"base_maxDD": -0.080671,
"gated_ann": 0.229523,
"gated_sharpe": 4.3611,
"gated_maxDD": -0.002029
},
{
"window": "2026",
"gate": "hitrate_10d_0.50",
"start": "2026-01-04",
"end": "2026-08-19",
"trade_dates": 157,
"gate_open": 109,
"gate_closed": 48,
"trip_rate": 0.3057,
"base_ann": 0.255023,
"base_sharpe": 1.4465,
"base_maxDD": -0.080671,
"gated_ann": 0.335822,
"gated_sharpe": 2.6391,
"gated_maxDD": -0.060142
},
{
"window": "2026",
"gate": "hitrate_10d_0.60",
"start": "2026-01-04",
"end": "2026-08-19",
"trade_dates": 157,
"gate_open": 36,
"gate_closed": 121,
"trip_rate": 0.7707,
"base_ann": 0.255023,
"base_sharpe": 1.4465,
"base_maxDD": -0.080671,
"gated_ann": 0.251108,
"gated_sharpe": 3.8579,
"gated_maxDD": -0.026842
},
{
"window": "2026",
"gate": "hitrate_10d_0.70",
"start": "2026-01-04",
"end": "2026-08-19",
"trade_dates": 157,
"gate_open": 9,
"gate_closed": 148,
"trip_rate": 0.9427,
"base_ann": 0.255023,
"base_sharpe": 1.4465,
"base_maxDD": -0.080671,
"gated_ann": 0.039314,
"gated_sharpe": 1.3415,
"gated_maxDD": -0.012061
},
{
"window": "2026",
"gate": "hitrate_20d_0.40",
"start": "2026-01-04",
"end": "2026-08-19",
"trade_dates": 157,
"gate_open": 157,
"gate_closed": 0,
"trip_rate": 0.0,
"base_ann": 0.255023,
"base_sharpe": 1.4465,
"base_maxDD": -0.080671,
"gated_ann": 0.255023,
"gated_sharpe": 1.4465,
"gated_maxDD": -0.080671
},
{
"window": "2026",
"gate": "hitrate_20d_0.50",
"start": "2026-01-04",
"end": "2026-08-19",
"trade_dates": 157,
"gate_open": 118,
"gate_closed": 39,
"trip_rate": 0.2484,
"base_ann": 0.255023,
"base_sharpe": 1.4465,
"base_maxDD": -0.080671,
"gated_ann": 0.343819,
"gated_sharpe": 2.5185,
"gated_maxDD": -0.06259
},
{
"window": "2026",
"gate": "hitrate_20d_0.60",
"start": "2026-01-04",
"end": "2026-08-19",
"trade_dates": 157,
"gate_open": 28,
"gate_closed": 129,
"trip_rate": 0.8217,
"base_ann": 0.255023,
"base_sharpe": 1.4465,
"base_maxDD": -0.080671,
"gated_ann": 0.020214,
"gated_sharpe": 0.3601,
"gated_maxDD": -0.028489
},
{
"window": "2025",
"gate": "hitrate_5d_0.50",
"start": "2025-01-02",
"end": "2025-12-31",
"trade_dates": 250,
"gate_open": 153,
"gate_closed": 97,
"trip_rate": 0.388,
"base_ann": 0.177515,
"base_sharpe": 0.8573,
"base_maxDD": -0.217417,
"gated_ann": 0.72055,
"gated_sharpe": 6.9599,
"gated_maxDD": -0.048495
},
{
"window": "2025",
"gate": "hitrate_5d_0.60",
"start": "2025-01-02",
"end": "2025-12-31",
"trade_dates": 250,
"gate_open": 90,
"gate_closed": 160,
"trip_rate": 0.64,
"base_ann": 0.177515,
"base_sharpe": 0.8573,
"base_maxDD": -0.217417,
"gated_ann": 0.48564,
"gated_sharpe": 5.3623,
"gated_maxDD": -0.046673
},
{
"window": "2025",
"gate": "hitrate_5d_0.70",
"start": "2025-01-02",
"end": "2025-12-31",
"trade_dates": 250,
"gate_open": 39,
"gate_closed": 211,
"trip_rate": 0.844,
"base_ann": 0.177515,
"base_sharpe": 0.8573,
"base_maxDD": -0.217417,
"gated_ann": 0.249221,
"gated_sharpe": 4.9448,
"gated_maxDD": -0.009619
},
{
"window": "2025",
"gate": "hitrate_10d_0.50",
"start": "2025-01-02",
"end": "2025-12-31",
"trade_dates": 250,
"gate_open": 170,
"gate_closed": 80,
"trip_rate": 0.32,
"base_ann": 0.177515,
"base_sharpe": 0.8573,
"base_maxDD": -0.217417,
"gated_ann": 0.435847,
"gated_sharpe": 3.5614,
"gated_maxDD": -0.055776
},
{
"window": "2025",
"gate": "hitrate_10d_0.60",
"start": "2025-01-02",
"end": "2025-12-31",
"trade_dates": 250,
"gate_open": 66,
"gate_closed": 184,
"trip_rate": 0.736,
"base_ann": 0.177515,
"base_sharpe": 0.8573,
"base_maxDD": -0.217417,
"gated_ann": 0.324574,
"gated_sharpe": 5.2279,
"gated_maxDD": -0.016028
},
{
"window": "2025",
"gate": "hitrate_10d_0.70",
"start": "2025-01-02",
"end": "2025-12-31",
"trade_dates": 250,
"gate_open": 14,
"gate_closed": 236,
"trip_rate": 0.944,
"base_ann": 0.177515,
"base_sharpe": 0.8573,
"base_maxDD": -0.217417,
"gated_ann": 0.043002,
"gated_sharpe": 1.4635,
"gated_maxDD": -0.010154
},
{
"window": "2025",
"gate": "hitrate_20d_0.40",
"start": "2025-01-02",
"end": "2025-12-31",
"trade_dates": 250,
"gate_open": 247,
"gate_closed": 3,
"trip_rate": 0.012,
"base_ann": 0.177515,
"base_sharpe": 0.8573,
"base_maxDD": -0.217417,
"gated_ann": 0.202949,
"gated_sharpe": 0.9896,
"gated_maxDD": -0.217417
},
{
"window": "2025",
"gate": "hitrate_20d_0.50",
"start": "2025-01-02",
"end": "2025-12-31",
"trade_dates": 250,
"gate_open": 177,
"gate_closed": 73,
"trip_rate": 0.292,
"base_ann": 0.177515,
"base_sharpe": 0.8573,
"base_maxDD": -0.217417,
"gated_ann": 0.340772,
"gated_sharpe": 2.8303,
"gated_maxDD": -0.071731
},
{
"window": "2025",
"gate": "hitrate_20d_0.60",
"start": "2025-01-02",
"end": "2025-12-31",
"trade_dates": 250,
"gate_open": 48,
"gate_closed": 202,
"trip_rate": 0.808,
"base_ann": 0.177515,
"base_sharpe": 0.8573,
"base_maxDD": -0.217417,
"gated_ann": 0.266028,
"gated_sharpe": 4.1749,
"gated_maxDD": -0.021527
},
{
"window": "2024",
"gate": "hitrate_5d_0.50",
"start": "2024-01-02",
"end": "2024-12-31",
"trade_dates": 253,
"gate_open": 139,
"gate_closed": 114,
"trip_rate": 0.4506,
"base_ann": 0.08229,
"base_sharpe": 0.5594,
"base_maxDD": -0.10685,
"gated_ann": 0.30351,
"gated_sharpe": 2.4275,
"gated_maxDD": -0.088219
},
{
"window": "2024",
"gate": "hitrate_5d_0.60",
"start": "2024-01-02",
"end": "2024-12-31",
"trade_dates": 253,
"gate_open": 71,
"gate_closed": 182,
"trip_rate": 0.7194,
"base_ann": 0.08229,
"base_sharpe": 0.5594,
"base_maxDD": -0.10685,
"gated_ann": 0.265365,
"gated_sharpe": 2.4908,
"gated_maxDD": -0.078304
},
{
"window": "2024",
"gate": "hitrate_5d_0.70",
"start": "2024-01-02",
"end": "2024-12-31",
"trade_dates": 253,
"gate_open": 19,
"gate_closed": 234,
"trip_rate": 0.9249,
"base_ann": 0.08229,
"base_sharpe": 0.5594,
"base_maxDD": -0.10685,
"gated_ann": 0.143125,
"gated_sharpe": 3.377,
"gated_maxDD": -0.003364
},
{
"window": "2024",
"gate": "hitrate_10d_0.50",
"start": "2024-01-02",
"end": "2024-12-31",
"trade_dates": 253,
"gate_open": 157,
"gate_closed": 96,
"trip_rate": 0.3794,
"base_ann": 0.08229,
"base_sharpe": 0.5594,
"base_maxDD": -0.10685,
"gated_ann": 0.277421,
"gated_sharpe": 2.5767,
"gated_maxDD": -0.043146
},
{
"window": "2024",
"gate": "hitrate_10d_0.60",
"start": "2024-01-02",
"end": "2024-12-31",
"trade_dates": 253,
"gate_open": 37,
"gate_closed": 216,
"trip_rate": 0.8538,
"base_ann": 0.08229,
"base_sharpe": 0.5594,
"base_maxDD": -0.10685,
"gated_ann": 0.207161,
"gated_sharpe": 3.6083,
"gated_maxDD": -0.011122
},
{
"window": "2024",
"gate": "hitrate_10d_0.70",
"start": "2024-01-02",
"end": "2024-12-31",
"trade_dates": 253,
"gate_open": 7,
"gate_closed": 246,
"trip_rate": 0.9723,
"base_ann": 0.08229,
"base_sharpe": 0.5594,
"base_maxDD": -0.10685,
"gated_ann": 0.031785,
"gated_sharpe": 1.7408,
"gated_maxDD": -0.002083
},
{
"window": "2024",
"gate": "hitrate_20d_0.40",
"start": "2024-01-02",
"end": "2024-12-31",
"trade_dates": 253,
"gate_open": 247,
"gate_closed": 6,
"trip_rate": 0.0237,
"base_ann": 0.08229,
"base_sharpe": 0.5594,
"base_maxDD": -0.10685,
"gated_ann": 0.078007,
"gated_sharpe": 0.5342,
"gated_maxDD": -0.10685
},
{
"window": "2024",
"gate": "hitrate_20d_0.50",
"start": "2024-01-02",
"end": "2024-12-31",
"trade_dates": 253,
"gate_open": 177,
"gate_closed": 76,
"trip_rate": 0.3004,
"base_ann": 0.08229,
"base_sharpe": 0.5594,
"base_maxDD": -0.10685,
"gated_ann": 0.210672,
"gated_sharpe": 1.8469,
"gated_maxDD": -0.056207
},
{
"window": "2024",
"gate": "hitrate_20d_0.60",
"start": "2024-01-02",
"end": "2024-12-31",
"trade_dates": 253,
"gate_open": 13,
"gate_closed": 240,
"trip_rate": 0.9486,
"base_ann": 0.08229,
"base_sharpe": 0.5594,
"base_maxDD": -0.10685,
"gated_ann": 0.005953,
"gated_sharpe": 0.3039,
"gated_maxDD": -0.014443
},
{
"window": "2023",
"gate": "hitrate_5d_0.50",
"start": "2023-01-03",
"end": "2023-12-29",
"trade_dates": 250,
"gate_open": 139,
"gate_closed": 111,
"trip_rate": 0.444,
"base_ann": -0.047644,
"base_sharpe": -0.2738,
"base_maxDD": -0.197856,
"gated_ann": 0.546654,
"gated_sharpe": 4.2124,
"gated_maxDD": -0.035676
},
{
"window": "2023",
"gate": "hitrate_5d_0.60",
"start": "2023-01-03",
"end": "2023-12-29",
"trade_dates": 250,
"gate_open": 79,
"gate_closed": 171,
"trip_rate": 0.684,
"base_ann": -0.047644,
"base_sharpe": -0.2738,
"base_maxDD": -0.197856,
"gated_ann": 0.556291,
"gated_sharpe": 5.1026,
"gated_maxDD": -0.025449
},
{
"window": "2023",
"gate": "hitrate_5d_0.70",
"start": "2023-01-03",
"end": "2023-12-29",
"trade_dates": 250,
"gate_open": 34,
"gate_closed": 216,
"trip_rate": 0.864,
"base_ann": -0.047644,
"base_sharpe": -0.2738,
"base_maxDD": -0.197856,
"gated_ann": 0.404269,
"gated_sharpe": 4.4477,
"gated_maxDD": -0.013842
},
{
"window": "2023",
"gate": "hitrate_10d_0.50",
"start": "2023-01-03",
"end": "2023-12-29",
"trade_dates": 250,
"gate_open": 148,
"gate_closed": 102,
"trip_rate": 0.408,
"base_ann": -0.047644,
"base_sharpe": -0.2738,
"base_maxDD": -0.197856,
"gated_ann": 0.459869,
"gated_sharpe": 3.5211,
"gated_maxDD": -0.046921
},
{
"window": "2023",
"gate": "hitrate_10d_0.60",
"start": "2023-01-03",
"end": "2023-12-29",
"trade_dates": 250,
"gate_open": 62,
"gate_closed": 188,
"trip_rate": 0.752,
"base_ann": -0.047644,
"base_sharpe": -0.2738,
"base_maxDD": -0.197856,
"gated_ann": 0.368232,
"gated_sharpe": 4.0148,
"gated_maxDD": -0.022983
},
{
"window": "2023",
"gate": "hitrate_10d_0.70",
"start": "2023-01-03",
"end": "2023-12-29",
"trade_dates": 250,
"gate_open": 13,
"gate_closed": 237,
"trip_rate": 0.948,
"base_ann": -0.047644,
"base_sharpe": -0.2738,
"base_maxDD": -0.197856,
"gated_ann": 0.091481,
"gated_sharpe": 2.1324,
"gated_maxDD": -0.010866
},
{
"window": "2023",
"gate": "hitrate_20d_0.40",
"start": "2023-01-03",
"end": "2023-12-29",
"trade_dates": 250,
"gate_open": 237,
"gate_closed": 13,
"trip_rate": 0.052,
"base_ann": -0.047644,
"base_sharpe": -0.2738,
"base_maxDD": -0.197856,
"gated_ann": 0.06639,
"gated_sharpe": 0.3895,
"gated_maxDD": -0.146704
},
{
"window": "2023",
"gate": "hitrate_20d_0.50",
"start": "2023-01-03",
"end": "2023-12-29",
"trade_dates": 250,
"gate_open": 149,
"gate_closed": 101,
"trip_rate": 0.404,
"base_ann": -0.047644,
"base_sharpe": -0.2738,
"base_maxDD": -0.197856,
"gated_ann": 0.366016,
"gated_sharpe": 2.5969,
"gated_maxDD": -0.063998
},
{
"window": "2023",
"gate": "hitrate_20d_0.60",
"start": "2023-01-03",
"end": "2023-12-29",
"trade_dates": 250,
"gate_open": 40,
"gate_closed": 210,
"trip_rate": 0.84,
"base_ann": -0.047644,
"base_sharpe": -0.2738,
"base_maxDD": -0.197856,
"gated_ann": 0.149844,
"gated_sharpe": 2.2645,
"gated_maxDD": -0.032267
},
{
"window": "2021",
"gate": "hitrate_5d_0.50",
"start": "2021-01-04",
"end": "2021-12-31",
"trade_dates": 252,
"gate_open": 145,
"gate_closed": 107,
"trip_rate": 0.4246,
"base_ann": 0.18367,
"base_sharpe": 1.0979,
"base_maxDD": -0.102651,
"gated_ann": 0.556615,
"gated_sharpe": 5.9167,
"gated_maxDD": -0.028062
},
{
"window": "2021",
"gate": "hitrate_5d_0.60",
"start": "2021-01-04",
"end": "2021-12-31",
"trade_dates": 252,
"gate_open": 68,
"gate_closed": 184,
"trip_rate": 0.7302,
"base_ann": 0.18367,
"base_sharpe": 1.0979,
"base_maxDD": -0.102651,
"gated_ann": 0.351867,
"gated_sharpe": 5.8468,
"gated_maxDD": -0.011638
},
{
"window": "2021",
"gate": "hitrate_5d_0.70",
"start": "2021-01-04",
"end": "2021-12-31",
"trade_dates": 252,
"gate_open": 26,
"gate_closed": 226,
"trip_rate": 0.8968,
"base_ann": 0.18367,
"base_sharpe": 1.0979,
"base_maxDD": -0.102651,
"gated_ann": 0.148417,
"gated_sharpe": 3.9593,
"gated_maxDD": -0.0041
},
{
"window": "2021",
"gate": "hitrate_10d_0.50",
"start": "2021-01-04",
"end": "2021-12-31",
"trade_dates": 252,
"gate_open": 163,
"gate_closed": 89,
"trip_rate": 0.3532,
"base_ann": 0.18367,
"base_sharpe": 1.0979,
"base_maxDD": -0.102651,
"gated_ann": 0.465023,
"gated_sharpe": 4.2342,
"gated_maxDD": -0.040569
},
{
"window": "2021",
"gate": "hitrate_10d_0.60",
"start": "2021-01-04",
"end": "2021-12-31",
"trade_dates": 252,
"gate_open": 51,
"gate_closed": 201,
"trip_rate": 0.7976,
"base_ann": 0.18367,
"base_sharpe": 1.0979,
"base_maxDD": -0.102651,
"gated_ann": 0.21802,
"gated_sharpe": 4.7978,
"gated_maxDD": -0.011134
},
{
"window": "2021",
"gate": "hitrate_10d_0.70",
"start": "2021-01-04",
"end": "2021-12-31",
"trade_dates": 252,
"gate_open": 13,
"gate_closed": 239,
"trip_rate": 0.9484,
"base_ann": 0.18367,
"base_sharpe": 1.0979,
"base_maxDD": -0.102651,
"gated_ann": 0.06108,
"gated_sharpe": 2.7055,
"gated_maxDD": -0.000262
},
{
"window": "2021",
"gate": "hitrate_20d_0.40",
"start": "2021-01-04",
"end": "2021-12-31",
"trade_dates": 252,
"gate_open": 252,
"gate_closed": 0,
"trip_rate": 0.0,
"base_ann": 0.18367,
"base_sharpe": 1.0979,
"base_maxDD": -0.102651,
"gated_ann": 0.18367,
"gated_sharpe": 1.0979,
"gated_maxDD": -0.102651
},
{
"window": "2021",
"gate": "hitrate_20d_0.50",
"start": "2021-01-04",
"end": "2021-12-31",
"trade_dates": 252,
"gate_open": 166,
"gate_closed": 86,
"trip_rate": 0.3413,
"base_ann": 0.18367,
"base_sharpe": 1.0979,
"base_maxDD": -0.102651,
"gated_ann": 0.289756,
"gated_sharpe": 2.5787,
"gated_maxDD": -0.049244
},
{
"window": "2021",
"gate": "hitrate_20d_0.60",
"start": "2021-01-04",
"end": "2021-12-31",
"trade_dates": 252,
"gate_open": 38,
"gate_closed": 214,
"trip_rate": 0.8492,
"base_ann": 0.18367,
"base_sharpe": 1.0979,
"base_maxDD": -0.102651,
"gated_ann": 0.104685,
"gated_sharpe": 2.394,
"gated_maxDD": -0.015084
}
]
+293
View File
@@ -0,0 +1,293 @@
"""Signal-quality gate walk-forward backtest.
Gates trades based on whether the model's recent topk predictions were correct
(hit rate). This is a retrospective gate — it measures prediction accuracy,
not market state.
Usage:
cd /app && .venv/bin/python book/scripts/signal_quality_gate_bt.py
"""
from __future__ import annotations
import json
import pathlib
import sys
import time
import numpy as np
import pandas as pd
LAKE_ROOT = "/home/data/lake"
MARKET = "US"
OUT_DIR = pathlib.Path("/app/experiments/book/data/signal_quality_gate")
WINDOWS = [
{"label": "2026", "start": "2026-01-04", "end": "2026-08-19",
"pred": f"{LAKE_ROOT}/mlruns/52/9f98ea5c550a409f87b56a6cd8fee343/artifacts/pred.pkl"},
{"label": "2025", "start": "2025-01-02", "end": "2025-12-31",
"pred": f"{LAKE_ROOT}/mlruns/52/fe96741654df4780957a3a949999ae6a/artifacts/pred.pkl"},
{"label": "2024", "start": "2024-01-02", "end": "2024-12-31",
"pred": f"{LAKE_ROOT}/mlruns/52/71ed5bfa9984490f8bba8b222f7acc39/artifacts/pred.pkl"},
{"label": "2023", "start": "2023-01-03", "end": "2023-12-29",
"pred": f"{LAKE_ROOT}/mlruns/56/8ca46e554311444c9a42637a788226e8/artifacts/pred.pkl"},
{"label": "2021", "start": "2021-01-04", "end": "2021-12-31",
"pred": f"{LAKE_ROOT}/mlruns/56/4e0700ddab2a4e108b46efece7346ee3/artifacts/pred.pkl"},
]
# Signal-quality gate configs: (lookback_days, threshold, name)
SIGNAL_GATE_CONFIGS = [
(5, 0.50, "hitrate_5d_0.50"),
(5, 0.60, "hitrate_5d_0.60"),
(5, 0.70, "hitrate_5d_0.70"),
(10, 0.50, "hitrate_10d_0.50"),
(10, 0.60, "hitrate_10d_0.60"),
(10, 0.70, "hitrate_10d_0.70"),
(20, 0.40, "hitrate_20d_0.40"),
(20, 0.50, "hitrate_20d_0.50"),
(20, 0.60, "hitrate_20d_0.60"),
]
def load_pred(path: str) -> pd.Series:
df = pd.read_pickle(path)
if isinstance(df, pd.DataFrame):
if "score" in df.columns:
s = df["score"]
else:
s = df.iloc[:, 0]
else:
s = df
idx = s.index
new_dt = pd.to_datetime(idx.get_level_values(0)).normalize()
s.index = pd.MultiIndex.from_arrays([new_dt, idx.get_level_values(1)], names=idx.names)
return s
def load_bars_for_window(start: str, end: str) -> pd.DataFrame:
from tac_qlib.data.config import LakeConfig, resolve_lake_root
cfg = LakeConfig(resolve_lake_root(LAKE_ROOT), MARKET)
sp = cfg.lake_root / "symbols.parquet"
if sp.exists():
syms = pd.read_parquet(sp)
col = "symbol" if "symbol" in syms.columns else syms.columns[0]
symbols = sorted(syms[col].astype(str).str.upper().tolist())
else:
return pd.DataFrame()
closes = {}
for sym in symbols:
p = cfg.bar_path("1d", sym)
if not p.exists():
continue
try:
df = pd.read_parquet(p)
except Exception:
continue
if not len(df):
continue
tcol = df["t"] if "t" in df.columns else df["date"]
ts = pd.to_datetime(tcol)
df = df.assign(_t=ts).set_index("_t").sort_index()
warmup_start = pd.Timestamp(start) - pd.Timedelta(days=60)
df = df.loc[warmup_start:end]
if len(df) >= 22:
closes[sym] = df["c"]
return pd.DataFrame(closes)
def compute_hit_rate_series(
pred: pd.Series, ret_df: pd.DataFrame, topk: int = 10, lookback: int = 10,
) -> pd.Series:
dt_idx = pred.index.get_level_values(0)
trade_dates = sorted(dt_idx.unique())
hit_rates = {}
for i in range(1, len(trade_dates)):
prev_date = trade_dates[i - 1]
curr_date = trade_dates[i]
try:
prev_scores = pred.loc[prev_date]
except KeyError:
continue
if isinstance(prev_scores, pd.DataFrame):
prev_scores = prev_scores.iloc[:, 0]
prev_scores = prev_scores.dropna().sort_values(ascending=False)
topk_syms = list(prev_scores.index[:topk])
if curr_date not in ret_df.index:
continue
today_ret = ret_df.loc[curr_date]
topk_rets = today_ret.reindex(topk_syms).dropna()
if len(topk_rets) > 0:
hit_rate = (topk_rets > 0).mean()
hit_rates[curr_date] = hit_rate
hit_series = pd.Series(hit_rates)
if len(hit_series) == 0:
return hit_series
rolling_hr = hit_series.rolling(lookback, min_periods=max(1, lookback // 2)).mean()
return rolling_hr
def run_backtest(pred, hit_rate, close_df, start, end, topk=10, threshold=0.5):
if not isinstance(pred.index, pd.MultiIndex):
return {"error": "pred must have MultiIndex"}
ret_df = close_df.pct_change()
ret_df.index = pd.to_datetime(ret_df.index).normalize()
dt_idx = pred.index.get_level_values(0)
window_mask = (dt_idx >= pd.Timestamp(start)) & (dt_idx <= pd.Timestamp(end))
window_pred = pred.loc[window_mask]
if len(window_pred) == 0:
return {"error": "no pred data in window"}
trade_dates = sorted(dt_idx[window_mask].unique())
gate_open = {}
for d in trade_dates:
known = hit_rate[hit_rate.index <= d]
if len(known) > 0 and not pd.isna(known.iloc[-1]):
gate_open[d] = bool(known.iloc[-1] >= threshold)
else:
gate_open[d] = True
n_total = len(trade_dates)
n_open = sum(1 for v in gate_open.values() if v)
n_closed = n_total - n_open
holdings_base = []
holdings_gated = []
equity_gated = 1_000_000.0
equity_base = 1_000_000.0
prev_week = None
prev_scores = None
daily_gated = []
daily_base = []
ret_by_date = {rd: ret_df.loc[rd] for rd in ret_df.index}
for d in trade_dates:
try:
day_scores = window_pred.loc[d]
except KeyError:
daily_gated.append(equity_gated)
daily_base.append(equity_base)
prev_scores = None
continue
if isinstance(day_scores, pd.DataFrame):
day_scores = day_scores.iloc[:, 0]
day_scores = day_scores.dropna().sort_values(ascending=False)
if len(day_scores) == 0:
daily_gated.append(equity_gated)
daily_base.append(equity_base)
prev_scores = None
continue
ret_row = ret_by_date.get(d)
if ret_row is None:
daily_gated.append(equity_gated)
daily_base.append(equity_base)
prev_scores = day_scores
continue
cur_week = (d.isocalendar()[0], d.isocalendar()[1]) if hasattr(d, 'isocalendar') else None
gate_val = gate_open.get(d, True)
if cur_week != prev_week or not holdings_base:
if prev_scores is not None:
holdings_base = list(prev_scores.index[:topk])
if holdings_base:
base_rets = ret_row.reindex(holdings_base).dropna()
if len(base_rets) > 0:
equity_base *= (1 + base_rets.mean())
if gate_val:
if cur_week != prev_week or not holdings_gated:
if prev_scores is not None:
holdings_gated = list(prev_scores.index[:topk])
if holdings_gated:
hold_rets = ret_row.reindex(holdings_gated).dropna()
if len(hold_rets) > 0:
equity_gated *= (1 + hold_rets.mean())
else:
holdings_gated = []
prev_week = cur_week
prev_scores = day_scores
daily_gated.append(equity_gated)
daily_base.append(equity_base)
g_series = pd.Series(daily_gated, index=trade_dates)
b_series = pd.Series(daily_base, index=trade_dates)
def _metrics(eq):
if len(eq) < 2:
return {"ann_return": 0, "sharpe": 0, "maxDD": 0}
rets = eq.pct_change().dropna()
ann_ret = float((eq.iloc[-1] / eq.iloc[0]) ** (252 / max(len(eq), 1)) - 1)
vol = float(rets.std() * (252 ** 0.5)) if len(rets) > 1 else 0
sharpe = ann_ret / vol if vol > 0 else 0
peak = eq.cummax()
dd = (eq - peak) / peak
maxDD = float(dd.min())
return {"ann_return": round(ann_ret, 6), "sharpe": round(sharpe, 4), "maxDD": round(maxDD, 6)}
base_m = _metrics(b_series)
gated_m = _metrics(g_series)
return {
"trade_dates": n_total,
"gate_open_days": n_open,
"gate_closed_days": n_closed,
"trip_rate": round(n_closed / n_total, 4) if n_total else 0,
"base": base_m,
"gated": gated_m,
}
def main():
OUT_DIR.mkdir(parents=True, exist_ok=True)
full_start = "2015-01-03"
full_end = "2026-08-19"
print("Loading lake bars...")
close_df = load_bars_for_window(full_start, full_end)
print(f" {close_df.shape[1]} symbols, {close_df.shape[0]} days")
ret_df = close_df.pct_change()
ret_df.index = pd.to_datetime(ret_df.index).normalize()
results = []
for window in WINDOWS:
wl, ws, we = window["label"], window["start"], window["end"]
pred_path = window["pred"]
print(f"\n=== Window {wl} ({ws} to {we}) ===")
pred = load_pred(pred_path)
print(f" pred shape: {pred.shape}")
hit_rates = {}
for lookback, _, name in SIGNAL_GATE_CONFIGS:
if lookback not in hit_rates:
hr = compute_hit_rate_series(pred, ret_df, topk=10, lookback=lookback)
hit_rates[lookback] = hr
print(f" lookback={lookback}: {len(hr)} days with hit rates")
for lookback, threshold, name in SIGNAL_GATE_CONFIGS:
hr = hit_rates[lookback]
bt = run_backtest(pred, hr, close_df, ws, we, topk=10, threshold=threshold)
if "error" in bt:
print(f" {name}: {bt['error']}")
continue
row = {
"window": wl,
"gate": name,
"start": ws,
"end": we,
"trade_dates": bt["trade_dates"],
"gate_open": bt["gate_open_days"],
"gate_closed": bt["gate_closed_days"],
"trip_rate": bt["trip_rate"],
"base_ann": bt["base"]["ann_return"],
"base_sharpe": bt["base"]["sharpe"],
"base_maxDD": bt["base"]["maxDD"],
"gated_ann": bt["gated"]["ann_return"],
"gated_sharpe": bt["gated"]["sharpe"],
"gated_maxDD": bt["gated"]["maxDD"],
}
results.append(row)
print(f" {name}: trip={bt['trip_rate']:.1%}, "
f"base={bt['base']['ann_return']:+.1%} (Sharpe {bt['base']['sharpe']:.2f}), "
f"gated={bt['gated']['ann_return']:+.1%} (Sharpe {bt['gated']['sharpe']:.2f})")
df = pd.DataFrame(results)
out_path = OUT_DIR / "signal_quality_gate_results.csv"
df.to_csv(out_path, index=False)
with open(OUT_DIR / "signal_quality_gate_results.json", "w") as f:
json.dump(df.to_dict(orient="records"), f, indent=2, default=str)
print(f"\nSaved to {out_path}")
print("\n=== Summary: Gated Return by Window ===")
for gate_name in df["gate"].unique():
gdf = df[df["gate"] == gate_name]
print(f"\n{gate_name}:")
for _, r in gdf.iterrows():
print(f" {r['window']}: base={r['base_ann']:+.1%}, gated={r['gated_ann']:+.1%}, "
f"trip={r['trip_rate']:.0%}, diff={r['gated_ann']-r['base_ann']:+.1%}pp")
if __name__ == "__main__":
main()
+4 -2
View File
@@ -1,5 +1,5 @@
# TradeAC custom-qlib-code snapshot (auto-generated)
# parent repo HEAD : 336fa676b651d34455d984d5be51387823d882f9
# parent repo HEAD : ceb1e196e24761a87d1afe1bde00079b79e02693
# tac-qlib/tac_qlib/contrib
# tac-qlib/tac_qlib/data
# per-file hashes (git hash-object):
@@ -15,10 +15,12 @@
74d0da348cbcc3700c96b6f4fe4391488e61efc5 tac-qlib/tac_qlib/contrib/model/__pycache__/rank_gbdt.cpython-312.pyc
d3f051f3a8650c42fedc7b367b966f7c74fb5789 tac-qlib/tac_qlib/contrib/model/rank_ensemble.py
d03e6611338918d4aac5eea4adf26f85a3763652 tac-qlib/tac_qlib/contrib/model/rank_gbdt.py
4afcf9058231111c412925f4c4b84e81d656db87 tac-qlib/tac_qlib/contrib/strategy/__init__.py
2c2f167b693f4366a769998e3c9d4804f29e31e0 tac-qlib/tac_qlib/contrib/strategy/__init__.py
c29e45e562256bf786c36f91a971b097467276e9 tac-qlib/tac_qlib/contrib/strategy/__pycache__/__init__.cpython-312.pyc
6dd1c568a2961842793674390d5abffd1a0e71b8 tac-qlib/tac_qlib/contrib/strategy/__pycache__/optimal_stop.cpython-312.pyc
519a1f4c05dbe0ac018ab8b779eb33d53b4dd545 tac-qlib/tac_qlib/contrib/strategy/ic_gate.py
79aaad9e39fcc740a773f4f63c512ce1086cfde0 tac-qlib/tac_qlib/contrib/strategy/optimal_stop.py
7bcee5f0b09cfa721440f1354f16f2dd9a112b12 tac-qlib/tac_qlib/contrib/strategy/regime_gate.py
fe60bacdfedd48617863be31f24b7c7daebfac5a tac-qlib/tac_qlib/contrib/strategy/weekly_rebalance.py
92e6e90eb0cd0a25142034560f27adb6b705b1a8 tac-qlib/tac_qlib/data/__init__.py
316bf4aa160cc8d15929ea648be03f4b4999667d tac-qlib/tac_qlib/data/__pycache__/__init__.cpython-312.pyc
@@ -1,3 +1,11 @@
from .ic_gate import ICGateTopkDropoutStrategy # noqa: F401
from .optimal_stop import OptimalStopControl # noqa: F401
from .regime_gate import RegimeGateTopkDropoutStrategy # noqa: F401
from .weekly_rebalance import WeeklyRebalanceDropoutStrategy # noqa: F401
__all__ = ["OptimalStopControl"]
__all__ = [
"ICGateTopkDropoutStrategy",
"OptimalStopControl",
"RegimeGateTopkDropoutStrategy",
"WeeklyRebalanceDropoutStrategy",
]
@@ -0,0 +1,117 @@
"""Realized-IC circuit breaker TopkDropout strategy.
Subclass of ``qlib.contrib.strategy.signal_strategy.TopkDropoutStrategy`` that
holds the book (issues NO orders) while the streaming realized RankIC of the
deployed signal is below threshold — i.e. the model's cross-sectional
predictions are no longer earning against realized forward returns. When the
gate is open it behaves exactly like the reference TopkDropoutStrategy.
The gate is evaluated per trade step on the trailing mean realized RankIC of
the signal over the last ``ic_window`` trading days whose label is fully
realized as of the decision date (no lookahead — a 5d fwd label ``close[t+6]/
close[t+1]-1`` is only known at ``t+6``).
Two wiring modes:
* ``ic_gate``: a precomputed ``pd.Series`` indexed by datetime of booleans
(True = gate open / trade allowed). Computed once by the caller (e.g.
``rd_backtest``) and looked up per step. Missing dates default to open.
* realized-IC self-computation: when ``ic_min_rankic`` is given but no
``ic_gate``, the strategy computes the per-date realized RankIC itself from
``self.signal`` (the pred scores) and the lake 1d bars via
``tac_qlib.risk_limits.realized_rankic_series``, then applies the same
trailing-window comparison. Works when instantiated from a workflow YAML
PortAnaRecord config (``lake_root`` / ``market`` must be provided).
"""
from __future__ import annotations
import pandas as pd
from qlib.backtest.decision import TradeDecisionWO
from qlib.contrib.strategy.signal_strategy import TopkDropoutStrategy
from tac_qlib.risk_limits import ic_circuit_breaker, realized_rankic_series
__all__ = ["ICGateTopkDropoutStrategy"]
class ICGateTopkDropoutStrategy(TopkDropoutStrategy):
"""TopkDropout with a streaming realized-IC circuit breaker.
Parameters
----------
topk, n_drop, method_sell, method_buy, hold_thresh, only_tradable,
forbid_all_trade_at_limit : same as ``TopkDropoutStrategy``.
ic_min_rankic : float — pause new trading while trailing realized RankIC is
below this threshold (0 disables the gate).
ic_window : int — trailing window for the realized RankIC mean (default 22).
ic_label_horizon : int — label horizon in trading days (default 6).
ic_min_obs : int — min realized labels before the gate arms (default 10).
ic_gate : pd.Series, optional — precomputed per-date gate (bool indexed by
datetime). When provided, it overrides self-computation.
lake_root, market : str — lake location for self-computed realized IC.
"""
def __init__(
self,
*,
topk,
n_drop,
ic_min_rankic: float = 0.0,
ic_window: int = 22,
ic_label_horizon: int = 6,
ic_min_obs: int = 10,
ic_gate=None,
lake_root: str = "",
market: str = "US",
**kwargs,
):
super().__init__(topk=topk, n_drop=n_drop, **kwargs)
self.ic_min_rankic = float(ic_min_rankic or 0.0)
self.ic_window = int(ic_window or 22)
self.ic_label_horizon = int(ic_label_horizon or 6)
self.ic_min_obs = int(ic_min_obs or 10)
self._ic_gate = ic_gate
self._realized_ic = None
self.lake_root = lake_root or ""
self.market = market or "US"
def _load_realized_ic(self):
if self._realized_ic is None:
pred_start_time, pred_end_time = self.trade_calendar.get_step_time(
self.trade_calendar.get_trade_step(), shift=-self.ic_label_horizon
)
pred = self.signal.get_signal(start_time=pred_start_time, end_time=pred_end_time)
if isinstance(pred, pd.DataFrame):
pred = pred.iloc[:, 0]
self._realized_ic = realized_rankic_series(
pred, self.lake_root, self.market, label_horizon=self.ic_label_horizon
)
return self._realized_ic
def _gate_open(self, trade_start_time) -> bool:
ts = pd.Timestamp(trade_start_time)
if self._ic_gate is not None:
# precomputed gate series: look up the latest known decision date <= ts
known = self._ic_gate[self._ic_gate.index <= ts]
if len(known):
return bool(known.iloc[-1])
return True
if self.ic_min_rankic <= 0:
return True
realized = self._load_realized_ic()
limits = {
"ic_min_rankic": self.ic_min_rankic,
"ic_window": self.ic_window,
"ic_min_obs": self.ic_min_obs,
}
tripped, _reason, _trail = ic_circuit_breaker(realized, ts, limits)
return not tripped
def generate_trade_decision(self, execute_result=None):
trade_step = self.trade_calendar.get_trade_step()
trade_start_time, _ = self.trade_calendar.get_step_time(trade_step)
if not self._gate_open(trade_start_time):
return TradeDecisionWO([], self)
return super().generate_trade_decision(execute_result)
@@ -0,0 +1,215 @@
"""Regime-gate TopkDropout strategy.
Subclass of ``qlib.contrib.strategy.signal_strategy.TopkDropoutStrategy`` that
holds the book (issues NO orders) while a regime detector says the market is in
an unfavorable state. When the gate is open it behaves exactly like the
reference TopkDropoutStrategy.
Three detector types are supported (all causal — no lookahead):
* ``dispersion``: cross-sectional standard deviation of 22-day rolling returns
across the universe. Gate closes when CS dispersion < threshold (low
dispersion means the spread between winners and losers is too narrow for
TopkDropout to exploit).
* ``vol``: cross-sectional mean of 22-day rolling realized volatility. Gate
closes when avg vol is outside a band ``[vol_low, vol_high]`` (strategy
needs moderate vol — too calm or too turbulent both hurt).
* ``hmm``: pre-computed HMM posterior for regime 1 (``sp_hmm_p_regime1``).
Gate closes when posterior < threshold (model is not confident the calm
regime is active).
The gate is provided as a precomputed ``pd.Series`` of booleans indexed by
datetime (True = trade allowed). The companion ``compute_regime_gate``
function builds this series from lake bars; call it once before backtesting
and pass the result as the ``regime_gate`` parameter.
"""
from __future__ import annotations
import pandas as pd
from qlib.backtest.decision import TradeDecisionWO
from qlib.contrib.strategy.signal_strategy import TopkDropoutStrategy
__all__ = ["RegimeGateTopkDropoutStrategy", "compute_regime_gate"]
class RegimeGateTopkDropoutStrategy(TopkDropoutStrategy):
"""TopkDropout with a regime-gate circuit breaker.
Parameters
----------
topk, n_drop, method_sell, method_buy, hold_thresh, only_tradable,
forbid_all_trade_at_limit : same as ``TopkDropoutStrategy``.
regime_gate : pd.Series — precomputed per-date gate (bool indexed by
datetime). True = trade allowed, False = no orders. Missing dates
default to open (trade allowed).
"""
def __init__(self, *, regime_gate=None, **kwargs):
super().__init__(**kwargs)
self._regime_gate = regime_gate
def _gate_open(self, trade_start_time) -> bool:
if self._regime_gate is None:
return True
ts = pd.Timestamp(trade_start_time)
known = self._regime_gate[self._regime_gate.index <= ts]
if len(known):
return bool(known.iloc[-1])
return True # default open if no history yet
def generate_trade_decision(self, execute_result=None):
trade_step = self.trade_calendar.get_trade_step()
trade_start_time, _ = self.trade_calendar.get_step_time(trade_step)
if not self._gate_open(trade_start_time):
return TradeDecisionWO([], self)
return super().generate_trade_decision(execute_result)
# ---------------------------------------------------------------------------
# Precomputation helper
# ---------------------------------------------------------------------------
def compute_regime_gate(
detector: str,
threshold: float = 0.0,
*,
lake_root: str = "",
market: str = "US",
start: str = "2015-01-03",
end: str = "2026-08-19",
vol_low: float = 0.0,
vol_high: float = 999.0,
hmm_field: str = "sp_hmm_p_regime1",
) -> pd.Series:
"""Build a per-date regime gate series from lake bars.
Parameters
----------
detector : str — ``"dispersion"``, ``"vol"``, or ``"hmm"``.
threshold : float — for ``dispersion``: min CS dispersion to allow trading.
For ``hmm``: min HMM posterior to allow trading.
Ignored for ``vol`` (uses ``vol_low``/``vol_high`` band instead).
lake_root, market : str — lake location.
start, end : str — date window.
vol_low, vol_high : float — annualized vol band for the ``vol`` detector.
hmm_field : str — HMM feature column name for the ``hmm`` detector.
Returns
-------
pd.Series — bool, indexed by datetime. True = trade allowed.
"""
from tac_qlib.data.config import LakeConfig, resolve_lake_root
cfg = LakeConfig(resolve_lake_root(lake_root or None), market)
symbols = _universe_symbols(cfg)
close_df, vol_df = _load_daily_bars(symbols, cfg, start, end)
if close_df.empty:
return pd.Series(dtype=bool)
if detector == "dispersion":
return _dispersion_gate(close_df, threshold)
elif detector == "vol":
return _vol_gate(close_df, vol_low, vol_high)
elif detector == "hmm":
return _hmm_gate(cfg, symbols, threshold, start, end, hmm_field)
else:
raise ValueError(f"Unknown detector: {detector!r}")
def _universe_symbols(cfg) -> list:
"""Read symbols from the lake symbols.parquet."""
import pathlib
sp = cfg.lake_root / "symbols.parquet"
if sp.exists():
df = pd.read_parquet(sp)
col = "symbol" if "symbol" in df.columns else df.columns[0]
return sorted(df[col].astype(str).str.upper().tolist())
return []
def _load_daily_bars(symbols, cfg, start, end):
"""Load daily close prices for all symbols into a wide DataFrame."""
closes = {}
vols = {}
for sym in symbols:
p = cfg.bar_path("1d", sym)
if not p.exists():
continue
try:
df = pd.read_parquet(p)
except Exception:
continue
if not len(df):
continue
tcol = df["t"] if "t" in df.columns else df["date"]
ts = pd.to_datetime(tcol)
df = df.assign(_t=ts).set_index("_t").sort_index()
df = df.loc[start:end]
if len(df) < 22:
continue
closes[sym] = df["c"]
if "v" in df.columns:
vols[sym] = df["v"]
close_df = pd.DataFrame(closes)
vol_df = pd.DataFrame(vols) if vols else None
return close_df, vol_df
def _dispersion_gate(close_df, threshold):
"""Cross-sectional dispersion of 22-day rolling returns."""
if close_df.empty or close_df.shape[1] < 2:
return pd.Series(dtype=bool)
ret = close_df.pct_change(22)
cs_disp = ret.std(axis=1)
gate = cs_disp >= threshold
gate.iloc[:22] = True # warmup: allow trading
return gate
def _vol_gate(close_df, vol_low, vol_high):
"""Cross-sectional mean of 22-day rolling realized vol."""
if close_df.empty or close_df.shape[1] < 2:
return pd.Series(dtype=bool)
import numpy as np
log_ret = np.log(close_df / close_df.shift(1))
rv22 = log_ret.rolling(22).std() * (252 ** 0.5)
cs_mean_vol = rv22.mean(axis=1)
gate = (cs_mean_vol >= vol_low) & (cs_mean_vol <= vol_high)
gate.iloc[:22] = True # warmup
return gate
def _hmm_gate(cfg, symbols, threshold, start, end, hmm_field):
"""HMM regime posterior gate from persisted SP features."""
feat_root = cfg.lake_root / "features"
all_posteriors = {}
for sym in symbols:
# check both ta and sp family paths
for family in ("sp", "ta"):
p = feat_root / f"market=US" / f"timeframe=1d" / f"family={family}" / f"symbol={sym}.parquet"
if not p.exists():
continue
try:
df = pd.read_parquet(p)
except Exception:
continue
if hmm_field not in df.columns:
continue
tcol = df["t"] if "t" in df.columns else df["date"]
ts = pd.to_datetime(tcol)
s = pd.Series(df[hmm_field].values, index=ts, name=sym)
s = s.loc[start:end].dropna()
if len(s) > 0:
all_posteriors[sym] = s
break
if not all_posteriors:
# no HMM features found — default open
idx = pd.date_range(start, end, freq="B")
return pd.Series(True, index=idx)
post_df = pd.DataFrame(all_posteriors)
cs_mean = post_df.mean(axis=1)
gate = cs_mean >= threshold
return gate
@@ -1,133 +0,0 @@
# -----------------------------------------------------------------------------
# ABLATION A (baseline): LightGBM with RankIC early-stopping on the 50-ETF SP-5d
# panel, using ALL 24 sp_* feature columns (ou,hmm,jump,har,trend,hurst,
# signature). Copy of the canonical workflow_lgb_sp5d_rankic.yaml with a
# distinct experiment name so the ablation runs are isolated.
#
# Run:
# rd_run_workflow config_path=tac-qlib/workflows/ablate_baseline_all_sp_fields.yaml \
# experiment_name=tac-rd-rank-ablate
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_ou_zscore,sp_ou_half_life,sp_ou_revert,sp_hmm_p_regime1,sp_hmm_state,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs:
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-rank-ablate"
task:
model:
class: RankICLGBModel
module_path: tac_qlib.contrib.model.rank_gbdt
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seed: 42
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-10
fit_start_time: 2015-01-03
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}"
infer_processors:
- class: DropAllNaN
kwargs: {}
- class: ProcessInf
kwargs: {}
- class: CSRankNorm
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments:
train: [2015-01-03, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,134 +0,0 @@
# -----------------------------------------------------------------------------
# ABLATION B (generic-only): same panel/model as the baseline, but feature
# fields restricted to the model-free / generic stochastic-process families
# (jump,har,trend,hurst,signature). Drops the model-specific ou (OU/AR-1
# half-life) and hmm (2-state regime) families to test whether the generic
# families alone dominate the rank dimension.
#
# Run:
# rd_run_workflow config_path=tac-qlib/workflows/ablate_generic_only_sp_fields.yaml \
# experiment_name=tac-rd-rank-ablate
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs:
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-rank-ablate"
task:
model:
class: RankICLGBModel
module_path: tac_qlib.contrib.model.rank_gbdt
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seed: 42
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-10
fit_start_time: 2015-01-03
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}"
infer_processors:
- class: DropAllNaN
kwargs: {}
- class: ProcessInf
kwargs: {}
- class: CSRankNorm
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments:
train: [2015-01-03, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,95 +0,0 @@
# Walk-forward: A-weekly / test 2024
# 3x3 re-validation (trace exp 52). Strategy WeeklyRebalanceDropoutStrategy n_drop=1, parallel=5.
{% set LAKE = TAC_LAKE_DIR %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-bt-3x3-windows" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
start_time: "2015-01-03"
end_time: "2025-01-07"
fit_start_time: "2016-01-04"
fit_end_time: "2023-08-31"
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2023-08-31" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2023-08-31" } }
- { class: Fillna, kwargs: {} }
segments:
train: ["2016-01-04", "2023-08-31"]
valid: ["2023-09-01", "2023-12-29"]
test: ["2024-01-02", "2024-12-31"]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: WeeklyRebalanceDropoutStrategy
module_path: tac_qlib.contrib.strategy.weekly_rebalance
kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: "2024-01-02"
end_time: "2024-12-31"
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,95 +0,0 @@
# Walk-forward: A-weekly / test 2025
# 3x3 re-validation (trace exp 52). Strategy WeeklyRebalanceDropoutStrategy n_drop=1, parallel=5.
{% set LAKE = TAC_LAKE_DIR %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-bt-3x3-windows" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
start_time: "2015-01-03"
end_time: "2026-01-07"
fit_start_time: "2016-01-04"
fit_end_time: "2024-08-30"
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2024-08-30" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2024-08-30" } }
- { class: Fillna, kwargs: {} }
segments:
train: ["2016-01-04", "2024-08-30"]
valid: ["2024-09-03", "2024-12-31"]
test: ["2025-01-02", "2025-12-31"]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: WeeklyRebalanceDropoutStrategy
module_path: tac_qlib.contrib.strategy.weekly_rebalance
kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: "2025-01-02"
end_time: "2025-12-31"
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,95 +0,0 @@
# Walk-forward: A-weekly / test 2026
# 3x3 re-validation (trace exp 52). Strategy WeeklyRebalanceDropoutStrategy n_drop=1, parallel=5.
{% set LAKE = TAC_LAKE_DIR %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-bt-3x3-windows" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
start_time: "2015-01-03"
end_time: "2026-08-19"
fit_start_time: "2016-01-04"
fit_end_time: "2025-09-01"
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- { class: Fillna, kwargs: {} }
segments:
train: ["2016-01-04", "2025-09-01"]
valid: ["2025-09-03", "2026-01-03"]
test: ["2026-01-04", "2026-08-19"]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: WeeklyRebalanceDropoutStrategy
module_path: tac_qlib.contrib.strategy.weekly_rebalance
kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: "2026-01-04"
end_time: "2026-08-19"
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,95 +0,0 @@
# Walk-forward: B-moments / test 2024
# 3x3 re-validation (trace exp 52). Strategy TopkDropoutStrategy n_drop=1, parallel=default(auto).
{% set LAKE = TAC_LAKE_DIR %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-bt-3x3-windows" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
start_time: "2015-01-03"
end_time: "2025-01-07"
fit_start_time: "2016-01-04"
fit_end_time: "2023-08-31"
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead,sp_rskew_5,sp_rskew_22,sp_rkurt_5,sp_rkurt_22,sp_dsv_5,sp_dsv_22"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2023-08-31" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2023-08-31" } }
- { class: Fillna, kwargs: {} }
segments:
train: ["2016-01-04", "2023-08-31"]
valid: ["2023-09-01", "2023-12-29"]
test: ["2024-01-02", "2024-12-31"]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: "2024-01-02"
end_time: "2024-12-31"
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,95 +0,0 @@
# Walk-forward: B-moments / test 2025
# 3x3 re-validation (trace exp 52). Strategy TopkDropoutStrategy n_drop=1, parallel=default(auto).
{% set LAKE = TAC_LAKE_DIR %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-bt-3x3-windows" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
start_time: "2015-01-03"
end_time: "2026-01-07"
fit_start_time: "2016-01-04"
fit_end_time: "2024-08-30"
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead,sp_rskew_5,sp_rskew_22,sp_rkurt_5,sp_rkurt_22,sp_dsv_5,sp_dsv_22"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2024-08-30" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2024-08-30" } }
- { class: Fillna, kwargs: {} }
segments:
train: ["2016-01-04", "2024-08-30"]
valid: ["2024-09-03", "2024-12-31"]
test: ["2025-01-02", "2025-12-31"]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: "2025-01-02"
end_time: "2025-12-31"
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,95 +0,0 @@
# Walk-forward: B-moments / test 2026
# 3x3 re-validation (trace exp 52). Strategy TopkDropoutStrategy n_drop=1, parallel=default(auto).
{% set LAKE = TAC_LAKE_DIR %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-bt-3x3-windows" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
start_time: "2015-01-03"
end_time: "2026-08-19"
fit_start_time: "2016-01-04"
fit_end_time: "2025-09-01"
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead,sp_rskew_5,sp_rskew_22,sp_rkurt_5,sp_rkurt_22,sp_dsv_5,sp_dsv_22"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- { class: Fillna, kwargs: {} }
segments:
train: ["2016-01-04", "2025-09-01"]
valid: ["2025-09-03", "2026-01-03"]
test: ["2026-01-04", "2026-08-19"]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: "2026-01-04"
end_time: "2026-08-19"
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,95 +0,0 @@
# Walk-forward: C-ndrop2 / test 2024
# 3x3 re-validation (trace exp 52). Strategy TopkDropoutStrategy n_drop=2, parallel=5.
{% set LAKE = TAC_LAKE_DIR %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-bt-3x3-windows" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
start_time: "2015-01-03"
end_time: "2025-01-07"
fit_start_time: "2016-01-04"
fit_end_time: "2023-08-31"
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2023-08-31" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2023-08-31" } }
- { class: Fillna, kwargs: {} }
segments:
train: ["2016-01-04", "2023-08-31"]
valid: ["2023-09-01", "2023-12-29"]
test: ["2024-01-02", "2024-12-31"]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs: { signal: "<PRED>", topk: 10, n_drop: 2, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: "2024-01-02"
end_time: "2024-12-31"
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,95 +0,0 @@
# Walk-forward: C-ndrop2 / test 2025
# 3x3 re-validation (trace exp 52). Strategy TopkDropoutStrategy n_drop=2, parallel=5.
{% set LAKE = TAC_LAKE_DIR %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-bt-3x3-windows" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
start_time: "2015-01-03"
end_time: "2026-01-07"
fit_start_time: "2016-01-04"
fit_end_time: "2024-08-30"
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2024-08-30" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2024-08-30" } }
- { class: Fillna, kwargs: {} }
segments:
train: ["2016-01-04", "2024-08-30"]
valid: ["2024-09-03", "2024-12-31"]
test: ["2025-01-02", "2025-12-31"]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs: { signal: "<PRED>", topk: 10, n_drop: 2, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: "2025-01-02"
end_time: "2025-12-31"
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,95 +0,0 @@
# Walk-forward: C-ndrop2 / test 2026
# 3x3 re-validation (trace exp 52). Strategy TopkDropoutStrategy n_drop=2, parallel=5.
{% set LAKE = TAC_LAKE_DIR %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-bt-3x3-windows" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
start_time: "2015-01-03"
end_time: "2026-08-19"
fit_start_time: "2016-01-04"
fit_end_time: "2025-09-01"
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- { class: Fillna, kwargs: {} }
segments:
train: ["2016-01-04", "2025-09-01"]
valid: ["2025-09-03", "2026-01-03"]
test: ["2026-01-04", "2026-08-19"]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs: { signal: "<PRED>", topk: 10, n_drop: 2, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: "2026-01-04"
end_time: "2026-08-19"
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
-141
View File
@@ -1,141 +0,0 @@
# -----------------------------------------------------------------------------
# ISOLATION: multi-seed RankIC ensemble, ablate-B generic-only feature set.
#
# Isolates the ensemble effect on the SP-5d rank signal. Same panel, segments,
# history (full backfilled 2016+) and feature set as the exp-9 ablate-B winner
# (generic-only sp_* families: jump,har,trend,hurst,signature), but replaces the
# single RankICLGBModel with a 5-seed RankICEnsembleLGBModel (42,7,2026,99,123)
# that averages per-day predictions.
#
# Differs from exp-15 (tac-rd-rank-ensemble, mlflow exp 15) ONLY by dropping the
# TA subset (rsi_14,roc_10,macd_hist,willr_14,atr_14) and the inter-asset xr_*
# features, so any change vs exp-15 is attributable to the feature set alone,
# and any change vs exp-9 is attributable to the ensemble + full history alone.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp12_isolation_ensemble.yaml \
# experiment_name=tac-rd-rank-ensemble-isolated
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs:
uri: "sqlite:///mlruns.db"
default_exp_name: "tac-rd-rank-ensemble-isolated"
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-14
fit_start_time: 2016-01-04
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}"
infer_processors:
- class: DropAllNaN
kwargs: {}
- class: ProcessInf
kwargs: {}
- class: CSRankNorm
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments:
train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,136 +0,0 @@
# -----------------------------------------------------------------------------
# EXP 14 - Strategy A (baseline): reference model + TopkDropout.
#
# Model = RankICEnsembleLGBModel (5-seed RankIC-early-stopped LGB), the class
# wired by the tac-rd-rank-ensemble-isolated reference (run 0cea66d9...).
# Strategy = TopkDropout topk=10 n_drop=2 risk_degree=0.95 (the reference's own
# recorded backtest strategy), so this run reproduces the reference baseline on
# the same 50-ETF SP-5d panel.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp14-optstop-v2/a_topk_baseline.yaml \
# experiment_name=tac-rd-optstop-v2
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs:
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-optstop-v2"
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-14
fit_start_time: 2016-01-04
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}"
infer_processors:
- class: DropAllNaN
kwargs: {}
- class: ProcessInf
kwargs: {}
- class: CSRankNorm
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments:
train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,149 +0,0 @@
# -----------------------------------------------------------------------------
# EXP 14 - Strategy B (enhanced): reference model + OptimalStopControlV2.
#
# Model = RankICEnsembleLGBModel (5-seed RankIC-early-stopped LGB), identical to
# Strategy A. Strategy = OptimalStopControlV2 (tac_qlib.contrib.strategy.
# optimal_stop_v2) with the controls that address OptimalStopControl's documented
# weaknesses:
# - turnover / cost control: rebalance_band=0.05 (skip small rebalances),
# cooldown_days=3 (no whipsaw re-entries), max_turnover=0.30 (cap daily
# traded notional, priority exits > opens > rebalances)
# - robust thresholds (no valid-window overfit): entry 0.85 / exit 0.70 /
# max_hold 10 / min_hold 2 / sl -0.08
# Sizing = equal-weight control (risk_degree fraction of total value split
# across targets) - the "proper allocation" that replaces cash-heuristic sizing.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp14-optstop-v2/b_optstop_v2.yaml \
# experiment_name=tac-rd-optstop-v2
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs:
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-optstop-v2"
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-14
fit_start_time: 2016-01-04
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}"
infer_processors:
- class: DropAllNaN
kwargs: {}
- class: ProcessInf
kwargs: {}
- class: CSRankNorm
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments:
train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: OptimalStopControlV2
module_path: tac_qlib.contrib.strategy.optimal_stop_v2
kwargs:
signal: "<PRED>"
topk: 10
entry_pct: 0.85
exit_pct: 0.70
max_hold_days: 10
min_hold_days: 2
sl: -0.08
risk_degree: 0.95
notional: 20000
rebalance_band: 0.05
cooldown_days: 3
max_turnover: 0.30
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d