queue: pre-register Series 2 (Q12-Q20) targeting unproven book hypotheses

Series 1 (Q01-Q11, exp 33-43) executed and folded into book/CLAIMS.md.
Series 2 covers the remaining HYPOTHESIS rows and open questions:
  Q12 22d label + weekly recompute (untested combo)
  Q13 weekly rebalance reproduction on a 2nd OOS window
  Q14 out-of-universe validation (single-stock panel, needs lake backfill)
  Q15 5-seed vs single-seed clean A/B
  Q16 hmm family as features
  Q17 realized-moments family as features
  Q18 OptimalStopControl clean re-test
  Q19/Q20 martingale-VR + effective-names scripted studies

Each workflow pins one-variable change vs exp-26 reference and acceptance.
This commit is contained in:
zhaoli
2026-08-20 03:51:58 +00:00
parent c0eb65efa7
commit 7124ef5d8e
17 changed files with 502 additions and 627 deletions
+47 -56
View File
@@ -1,78 +1,69 @@
# TradeAC Experiment Queue — hypotheses that would prove "better trading performance" # TradeAC Experiment Queue — Series 2 (Q12+)
**Purpose.** A staging queue of experiment runs, each designed to PROVE (or **Purpose.** The next pre-registered batch of experiments, continuing Series 1
REFUTE) one hypothesis about how to achieve better trading performance on the (Q01–Q11, exp 33–43, all executed and folded into `book/CLAIMS.md` /
TradeAC stack. Every item is pre-registered: hypothesis, change-vs-reference, `book/EVIDENCE.md`). Each entry targets a still-unproven `HYPOTHESIS` from the
and acceptance metric are fixed BEFORE the run (book ch.02 isolation + falsification book or an open question flagged in `CLAIMS.md`/`book/README.md`, and follows the
discipline). Nothing here is executed yet — each entry carries its execution Series-1 discipline: one variable changed vs the exp-26 reference, acceptance
command and can be run by tracing first (`rd_trace_start` → `rd_run_workflow` / fixed BEFORE the run, sequential execution, trace-first, verify-then-close.
`rd_risk_calibrate` → `rd_trace_finish`).
**Source.** Mined from the `book` branch of this repo (`book/CLAIMS.md`, **Reference / control (MUST reproduce first).** exp 26 (`21afc6af…`, mlflow exp
`book/EVIDENCE.md`, `book/chapters/*`, `book/references/chat-ideas.md`). Only 25) is the campaign baseline; exp 39 (Q07, weekly rebalance) is the best
clean-lake (exp 21+) facts are cited as reference numbers; pre-clean-lake claims construction. Reference config is byte-reproduced in `workflows/exp26/` on the
are idea material that the queue is designed to test. `exp/26-…` branch and in this dir's `workflows/*.yaml`.
## Reference / control (MUST reproduce first) | Config element | exp-26 reference value |
The exp-26 reference — the campaign's best clean-lake result (EVIDENCE#015, run
`21afc6af…`, mlflow exp 25, branch `exp/26-test-whether-reducing-topkdropout-daily`):
| Config element | Reference value |
|---|---| |---|---|
| Universe | 50-ETF panel (same `UNIVERSE` list as exp-24/26) | | Universe | 50-ETF panel (`UNIVERSE` below) |
| Features | compact stochastic: `$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` | | Features | compact stochastic 25-field set (no ou/hmm/moments/garch) |
| Label | `Ref($close,-6)/Ref($close,-1)-1` (5d) | | Label | `Ref($close,-6)/Ref($close,-1)-1` (5d) |
| Model | `RankICEnsembleLGBModel` (tac_qlib.contrib.model.rank_ensemble), seeds `42,7,2026,99,123`, lr 0.02, num_leaves 31, 3000 rounds, early_stop 200, min_data_in_leaf 20, lambda_l2 0.5, colsample/subsample 0.8 | | Model | `RankICEnsembleLGBModel`, seeds `42,7,2026,99,123`, lr 0.02, leaves 31, 3000 rounds, ES 200 |
| Train / valid / test | 2016-01-04..2025-09-01 / 2025-09-03..2026-01-03 / 2026-01-04..2026-08-10 | | Segments | train 2016-01-04..2025-09-01 / valid 2025-09-03..2026-01-03 / test 2026-01-04..2026-08-10 |
| Strategy | TopkDropout, topk 10, n_drop 1, risk_degree 0.95 | | Strategy | TopkDropout, topk 10, n_drop 1, risk_degree 0.95 |
| Costs / benchmark | open 0.0005 / close 0.0015 / min $5, deal $close, SPY, $1M | | Costs | open 0.0005 / close 0.0015 / min $5, deal $close, SPY benchmark, $1M |
Reference metrics to beat (EVIDENCE#015): **net_ann_return +2.13%, net_IR 0.21, **Reference metrics to beat (EVIDENCE#015):** net_ann +2.13%, net_IR 0.21, gross
gross +7.02%, net_max_drawdown −7.69%, RankIC 0.0663, RankICIR 0.2545, L/S Sharpe 4.54.** +7.02%, maxDD −7.69%, RankIC 0.0663, RankICIR 0.2545, L/S Sharpe 4.54. Weekly
(Q07, EVIDENCE#028): net +12.51%, IR 1.24, maxDD −4.13%, ~1.1pp cost drag.
## The queue (ordered by value × feasibility) ## The queue (ordered by value × feasibility)
| ID | Title / hypothesis | Change vs reference (ONE var) | Acceptance | Config | Ready? | | ID | Title / hypothesis | Change vs reference (ONE var) | Acceptance | Config | Ready? |
|----|--------------------|-------------------------------|------------|--------|--------| |----|--------------------|-------------------------------|------------|--------|--------|
| Q01 | **M2 Sharpe-drift reproduction** — adding `sp_sharpe_22` (risk-adjusted 22d drift) improves net perf (exp 30: +6.53% IR 0.62, unreproduced → promote HYPOTHESIS) | +`sp_sharpe_22` to features | net_IR > 0.21, net_ann > +2.13% | `workflows/q01_m2_sharpe22_repro.yaml` | ✅ | | Q12 | **22d label + weekly recompute** — the untested combo: Q05's label edge (IC 0.097, RankIC 0.117) with Q07's cost relief | label → 22d AND strategy → weekly (two coupled, explicitly pre-registered) | net_IR > 0.5, net_ann > +5%, cost drag ≤ 2pp | `workflows/q12_label22d_weekly.yaml` | ✅ |
| Q02 | **Seed count 10 vs 5** — more seeds → higher ICIR/net; tests whether averaging saturates (exp 28 proved 5>2) | seeds → 10 | net_IR ≥ 0.21, ICIR/RankICIR ≥ ref | `workflows/q02_seed10.yaml` | ✅ | | Q13 | **Weekly rebalance reproduction on a 2nd window** — Q07 was a single OOS window; reproduce on test 2025-01-02..2025-12-31 before promoting to a live round | segments only (shifted) | net_IR > 0.21, net_ann > +2.13% on the new window | `workflows/q13_weekly_second_window.yaml` | ✅ |
| Q03 | **topk 20 diversification** — effective book is ~4 independent names; wider book cuts drawdown without hurting weak signal | topk 10→20 | net_IR > 0.21, MDD < 7.69%, net_ann ≥ +2.13% | `workflows/q03_topk20.yaml` | ✅ | | Q14 | **Out-of-universe validation** — compact stochastic set generalizes off the 50-ETF panel to a single-stock universe | universe → 30 liquid single names | RankIC > 0.03, ICIR > 0.15, net IR > 0 on stocks | `workflows/q14_out_of_universe.yaml` | ⚠️ needs stock-lake backfill (see design) |
| Q04 | **10-day non-overlapping label** — longer horizon captures trend/reversal 5d blurs, lowers churn | label → 10d | net_IR > 0.21, net_ann > +2.13% | `workflows/q04_label10d.yaml` | ✅ | | Q15 | **5-seed vs single-model clean A/B** — seed-count claim (exp 12 idea, re-validated exp 22–24, never a clean A/B) | seeds → 1 (`2026`) | single-model RankIC/IR < 5-seed ref; net_IR ≥ 0.21 acceptable if ≥ single | `workflows/q15_single_seed.yaml` | ✅ |
| Q05 | **22-day label** — true trend-following; 5d can't see 1–12m drift (submartingale) | label → 22d | net_IR > 0.21, net_ann > +2.13%, cost ≤ ref | `workflows/q05_label22d.yaml` | ✅ | | Q16 | **HMM family added as features** — settles "dropping model-specific (ou,hmm) improves signal" (exp 25 tested OU; hmm-as-feature untested) | features += `sp_hmm_p_regime1,sp_hmm_state` | no improvement: RankIC ≤ 0.0663, net_IR ≤ 0.21 | `workflows/q16_hmm_features.yaml` | ✅ |
| Q06 | **Fractional-Kelly sizing** (re-run exp 15 on clean lake) — sizing by edge magnitude beats equal-weight net of costs | custom strategy (sizing) | net_IR > 0.21, net_ann > +2.13%, cost ≤ ref | `designs/q06_kelly_sizing.md` | ⚠️ needs `kelly_dropout.py` | | Q17 | **Realized-moments family added** — settles "moment/volatility families regress" (exp 11 idea, never clean A/B) | features += `sp_rskew_5,sp_rskew_22,sp_rkurt_5,sp_rkurt_22,sp_dsv_5,sp_dsv_22` | no improvement: RankIC ≤ 0.0663, net_IR ≤ 0.21 | `workflows/q17_moments_features.yaml` | ✅ |
| Q07 | **Weekly rebalance** — next turnover lever after n_drop 1; cut forced churn at same signal | custom strategy (weekly) | cost/turnover ↓ AND net_IR > 0.21, net_ann > +2.13% | `designs/q07_weekly_rebalance.md` | ⚠️ needs `weekly_rebalance.py` | | Q18 | **OptimalStopControl clean re-test** — exp 13/14 claim (TopkDropout > stop-control) never re-tested post-reset | strategy → `OptimalStopControl` (exp-13 params) | TopkDropout net_IR ≥ stop-control net_IR; document cost drag | `workflows/q18_optstop.yaml` | ✅ (module verified in venv) |
| Q08 | **Risk-limit re-validation** — $5M liquidity floor improves net IR / cuts DD on post-reset signal (exp 18 pre-clean-lake) | `rd_risk_calibrate` A/B on exp-26 pred | net_IR > 0.21, MDD < 7.69% vs no-limit | `designs/q08_risk_limit_ab.md` | ✅ tool-only | | Q19 | **Martingale / variance-ratio study close-out** — exp 19 never closed; VR<1 at 5–20d on clean lake | ad-hoc script (no qrun) | VR stats + drift decomposition on 50-ETF panel | `designs/q19_martingale_vr.md` | ✅ script |
| Q09 | **Long-short construction** — the L/S edge (Sharpe 4.54) realizes more net of costs than long-only | custom strategy (top+bottom) | net_IR > 0.21, net_ann > +2.13%, cost ≤ 2× ref | `designs/q09_long_short.md` | ⚠️ needs `top_bottom.py` | | Q20 | **Effective independent names (≈4)** — eigenvalue analysis on clean-lake covariance | ad-hoc script | eigenvalue spectrum + effective-rank count | `designs/q20_effective_names.md` | ✅ script |
| Q10 | **HMM regime overlay** — regime as overlay (not feature) cuts drawdown; exp 25 proved features fail, overlay untested | custom strategy (regime gate) | MDD < 7.69%, net_IR ≥ 0.21 | `designs/q10_hmm_regime_overlay.md` | ⚠️ needs `regime_gate.py` + `get_lake_sp` |
| Q11 | **Standalone 5-day reversal** — reversal (β −0.53, t −24) tradable net of 20bp round-trip; unisolated | single-feature model/backtest | net_ann > 0 standalone | `designs/q11_standalone_reversal.md` | ⚠️ partial |
### Deferred (methodology / infra, P3) ### Deferred (methodology / infra, P3)
- **Q12 Purged / walk-forward CV** on the exp-26 reference (book ch.02 open - Purged / walk-forward CV (was queue's old Q12) — methodology, not an alpha lever.
question) — methodology improvement, not a direct alpha lever. - PSI-based drift-aware retraining cadence — needs a drift-gate module + a retrain decision rule.
- **Q13 Out-of-universe validation** — non-ETF universe for the compact - No-trade buffer band / notional-vs-qty sizing — siblings of Q12/Q13; queue only if weekly reproduces.
stochastic feature set (book README open question; needs new lake symbols). - Macro/drift overlays (SPY>200d regime gate, momentum tilt) — needs new data pipeline.
## Execution protocol (per queued run) ## Execution protocol (per queued run)
1. **Validate the lake first** (`validate_lake_dataset` + `rd_status`) — the 1. **Validate the lake first** (`validate_lake_dataset` + `rd_status`) — clean-lake lesson: silent NaN-drops and hollow coverage invalidate a run. Q14 additionally requires backfilling the single-stock universe (bars + sp/ta features, full range, explicit `start`/`end`).
clean-lake lesson: silent NaN-drops and hollow coverage invalidate a run. 2. **Trace before running** (`rd_trace_start` with the hypothesis as `rational`, fresh `experiment_name`, `evolved_from=auto`).
2. **Trace before running** (`rd_trace_start` with the hypothesis as `rational`, 3. **Run** `rd_run_workflow config_path=<abs path to the queue YAML> experiment_name=<fresh name>` — `wait=false`, poll `rd_exp_get_run` until `FINISHED`.
`evolved_from=auto` for lineage → it will fork from the closest prior
experiment). Use a FRESH experiment name per run, e.g. `tac-rd-q01-m2-...`.
3. **Run** `rd_run_workflow config_path=<abs path to the queue YAML>
experiment_name=<fresh name>` — use `wait=false`, poll `rd_exp_get_run` until
`FINISHED` (4-year trains outlive the MCP call).
4. **Verify against acceptance** via `rd_exp_result` (headline + backtest risk). 4. **Verify against acceptance** via `rd_exp_result` (headline + backtest risk).
5. **Finish the trace** (`rd_trace_finish` with `metrics` + `evaluation`), 5. **Finish the trace** (`rd_trace_finish` with `metrics` + `evaluation`), snapshot any changed contrib modules.
snapshot any new custom modules (`rd_trace_snapshot`). 6. **Report to the book** — PROVE/REFUTE → update `book/CLAIMS.md` + `book/EVIDENCE.md`.
6. **Report to the book** — on PROVE, update `book/CLAIMS.md`/`EVIDENCE.md`;
on REFUTE, record the negative (falsification is the output).
Sequential execution only (concurrent runs hang — chat-ideas.md ops lesson). Sequential execution only (concurrent runs hang — chat-ideas.md ops lesson). Any
custom strategy/module changed here must be copied into the venv site-packages
snapshot before `rd_run_workflow` can import it (see `/app/AGENTS.md`). As of
2026-08-20 `WeeklyRebalanceDropoutStrategy` and `OptimalStopControl` are verified
in sync with the venv snapshot; the lake already persists the `sp_hmm_*` and
`sp_moments` families on the 50-ETF panel.
## Provenance ## Provenance
Mined 2026-08-19 from `book/` on the `book` branch (HEAD `436692a`). Reference Mined 2026-08-20 from `book/CLAIMS.md`, `book/EVIDENCE.md`, `book/README.md`,
config reproduced byte-for-byte from the exp-26 run artifact config `book/references/chat-ideas.md`, and Series-1 `queue/` (Q01–Q11, executed exp
(`/home/data/lake/mlruns/25/21afc6afdb674a399b59dd76c97628ce/artifacts/config`). 33–43). Reference numbers are post-clean-lake (exp 21+).
-33
View File
@@ -1,33 +0,0 @@
# QUEUE-06 — Fractional-Kelly sizing vs equal-weight top-k (re-run exp 15 on clean lake)
**Status:** QUEUED · **Priority:** P1 · **Effort:** custom strategy module + run
## Hypothesis (prove)
Fractional-Kelly sizing — sizing each name by the edge magnitude of its score
instead of equal-weight × risk_degree — is a sizing rule (not a strategy) that
throws away less edge and beats equal-weight top-k **net of costs** on the clean
lake. Source: `book/README.md` open questions (exp 15 run never finished),
`book/references/chat-ideas.md` ("Kelly sizing is a sizing rule, not a strategy").
## Change vs exp-26 reference (ONE variable)
- **Strategy**: equal-weight `TopkDropoutStrategy` (topk 10, n_drop 1) →
custom `FractionalKellyDropoutStrategy` (same topk/n_drop selection, sizing ∝
score magnitude, capped at a fraction f of the equal-weight notional; f as a
parameter, e.g. 0.5).
- All signal/config unchanged (compact stochastic features, 5-seed RankIC
ensemble, 5d label, train/valid/test, SPY benchmark, 5bp/15bp/$5 costs).
## Acceptance
- `net_IR > 0.21` AND `net_ann_return > +2.13%` (exp-26 reference), with
`total_cost` not higher than the reference book.
- If sizing flattens the book (over-concentration) and net degrades → REFUTED
(recorded negative; equal-weight stays canonical).
## Execution prerequisites
1. New contrib module `tac_qlib/contrib/strategy/kelly_dropout.py`
(`FractionalKellyDropoutStrategy` subclassing
`qlib.contrib.strategy.signal_strategy.TopkDropoutStrategy`), copy to the
venv site-packages copy (`/opt/venv/lib/python3.12/site-packages/tac_qlib/...`).
2. Workflow YAML with `strategy.class=FractionalKellyDropoutStrategy`,
`module_path=tac_qlib.contrib.strategy.kelly_dropout`.
3. Trace (rd_trace_start → run → rd_trace_finish), snapshot the new module.
-37
View File
@@ -1,37 +0,0 @@
# QUEUE-07 — Turnover relief: weekly rebalance vs daily (next cost lever after n_drop 1)
**Status:** QUEUED · **Priority:** P1 · **Effort:** custom strategy module + run
## Hypothesis (prove)
n_drop 2→1 proved the cost/turnover frontier is the binding constraint
(EVIDENCE#015, ch.03/ch.09: identical IC/RankIC, net flips −3.21% → +2.13%).
The next lever in the same direction: rebalance the TopkDropout book only
**weekly** (e.g. on Mondays) instead of daily — cutting forced churn further
should lift net performance at the same signal quality.
Source: `book/references/chat-ideas.md` ("weekly rebalance" among the turnover
reduction ideas), ch.09 claim inventory.
## Change vs exp-26 reference (ONE variable)
- **Strategy**: daily TopkDropout (topk 10, n_drop 1) → custom
`WeeklyRebalanceDropoutStrategy` that recomputes the target book once per
week and otherwise holds (no-trade buffer band for small deltas).
- All signal/config unchanged.
## Acceptance
- `total_cost`/turnover strictly below the reference AND `net_IR > 0.21` AND
`net_ann_return > +2.13%`.
- Reference numbers to beat: turnover ~0.74 (round-3 live), est. ~20% daily
book turnover at topk10/n_drop2 (pre-clean-lake estimate).
## Execution prerequisites
1. New contrib module `tac_qlib/contrib/strategy/weekly_rebalance.py`
(`WeeklyRebalanceDropoutStrategy` subclassing `TopkDropoutStrategy`, trade
only when the trade calendar day is the week's first trading day), copy to
the venv site-packages copy.
2. Workflow YAML wiring the strategy.
3. Trace + run + snapshot.
## Sibling (deferred)
No-trade buffer band and notional-vs-qty order sizing are variants of the same
cost lever; queue them only if Q07 reproduces positively.
-34
View File
@@ -1,34 +0,0 @@
# QUEUE-08 — Risk-limit A/B re-validation: $5M liquidity floor on the exp-26 reference
**Status:** QUEUED · **Priority:** P1 · **Effort:** tool-only (no new code)
## Hypothesis (prove)
The $5M liquidity floor improves net IR and cuts drawdown on the **post-reset**
reference signal (pre-reset exp 18, EVIDENCE#008: net IR 0.81→0.98, cumDD
7.93%→5.44%), while size/concentration caps hurt by cutting deployed capital.
Needs re-validation on the exp-26 lineage because exp 18 is pre-clean-lake and
not comparable (EVIDENCE#009/010). Source: `book/CLAIMS.md` open question +
`book/README.md` `TODO(evidence-needed: reconciliation of exp 18 risk-limit spec
on the post-reset reference signal)`.
## Change vs exp-26 reference (ONE variable)
- Reference: the saved exp-26 prediction (run `21afc6af…`, mlflow exp 25).
- A/B via `rd_risk_calibrate` (runs limit-vs-no-limit A/B + sensitivity grid
over size_cap_pct, concentration_cap_pct, liquidity_floor_adv) and/or
`rd_backtest` with `risk_limits` on the SAME saved `pred.pkl`:
- baseline: no limits (this must reproduce the exp-26 net +2.13% / IR 0.21);
- candidate: `{"liquidity_floor_adv": 5000000, "size_cap_pct": 0.12,
"concentration_cap_pct": 0.95, "drawdown_pause_pct": 0.10}` (round-3 spec).
- Pick the spec (B2 calibration) that keeps live ≈ backtest.
## Acceptance
- Candidate spec: `net_IR > 0.21` AND `net_max_drawdown < 7.69%` vs no-limit on
the same pred. Size/concentration caps expected to REDUCE deployed capital
(record the direction as confirmation of exp 18).
- If the floor is a no-op (gates don't bind at this signal) → report that gates
are no-ops when the signal is the bottleneck (exp 20 pattern) as a PROVEN
clean-lake result.
## Execution prerequisites
- None (uses saved pred + `rd_risk_calibrate`/`rd_backtest`). Trace the A/B as
an experiment; record the spec chosen for the next live round.
-30
View File
@@ -1,30 +0,0 @@
# QUEUE-09 — Long-short construction: capture the long-short edge net of costs
**Status:** QUEUED · **Priority:** P2 · **Effort:** custom strategy module + run
## Hypothesis (prove)
The compact stochastic signal's long-short spread is the real edge (L/S ann
Sharpe 4.54, exp 24; "edge is long-short, not long-only" — chat-ideas.md), but
all canonical constructions are long-only (TopkDropout buys topk, drops, holds).
A market-neutral book (long topk, short bottom topk) should realize more of the
spread net of costs than the long-only book, IF short-side financing + doubled
turnover cost stays below the added spread capture.
## Change vs exp-26 reference (ONE variable)
- **Strategy**: long-only TopkDropout (topk 10, n_drop 1) → custom
`TopBottomDropoutStrategy` (long topk by rank, short bottom topk, equal
weight per side, same risk_degree), realized in a workflow with a cost model
that includes both sides (open/close cost symmetric).
- All signal/config unchanged.
## Acceptance
- `net_IR > 0.21` AND `net_ann_return > +2.13%` AND `total_cost` within ~2× the
reference (doubled side count is the structural cost of this construction).
- Watch: benchmark neutrality (SPY beta ≈ 0) as a secondary sanity metric.
## Execution prerequisites
1. New contrib module `tac_qlib/contrib/strategy/top_bottom.py`
(`TopBottomDropoutStrategy` subclassing `BaseSignalStrategy`), copy to the
venv site-packages copy.
2. Workflow YAML wiring the strategy; PortAnaRecord benchmark SPY.
3. Trace + run + snapshot.
-35
View File
@@ -1,35 +0,0 @@
# QUEUE-10 — HMM regime overlay on the exp-26 book (overlay, not feature)
**Status:** QUEUED · **Priority:** P2 · **Effort:** custom strategy + feature compute + run
## Hypothesis (prove)
Regime flags failed as model **features** (exp 9 idea, exp 25 clean-lake
confirmation that model-specific families regress), but the surviving use is as
an **overlay**: a long-only/regime-gate that holds names only in the favourable
HMM state should cut drawdown / improve net IR on the same signal. Source:
`book/chapters/01` regime section + `chat-ideas.md`
(`TODO(evidence-needed: HMM regime gate as overlay on exp-26 book)`).
## Change vs exp-26 reference (ONE variable)
- **Strategy**: plain TopkDropout (topk 10, n_drop 1) → custom
`RegimeGateDropoutStrategy`: identical selection, but when the per-symbol
HMM posterior (`sp_hmm_p_regime1`) is below a calibrated threshold the name
is held in cash instead of bought (entry gate); no new features enter the
model — `sp_hmm_p_regime1` is computed for gating only, fit on the train
window (no lookahead), via `get_lake_sp` with `fit_end=<train end>`.
- All signal/config unchanged.
## Acceptance
- `net_max_drawdown < 7.69%` (reference) AND `net_IR >= 0.21`. If the gate
never binds at a sensible threshold → the gate is a no-op on this signal
(exp 20 pattern) → recorded REFUTED/neutral, not a failure.
- Calibrate the threshold on the valid window only (avoid the exp 13/14
threshold-overfit trap).
## Execution prerequisites
1. Persist `sp_hmm_p_regime1` for the universe (get_lake_sp, fit_end =
2025-09-01) WITHOUT adding it to `feature_fields` of the model.
2. New contrib module `tac_qlib/contrib/strategy/regime_gate.py`, copy to the
venv site-packages copy.
3. Workflow YAML wiring the strategy.
4. Trace + run + snapshot.
-32
View File
@@ -1,32 +0,0 @@
# QUEUE-11 — Standalone 5-day reversal signal net of costs (unisolated)
**Status:** QUEUED · **Priority:** P2 · **Effort:** dataset study + backtest
## Hypothesis (prove)
5-day momentum strongly reverses on this panel (pooled regression:
`sp_trend_slope_5` β = −0.53, t = −24; VR < 1 at 5–20d for ~32/72 assets —
chat-derived, pre-clean-lake idea material). The reversal has never been tested
as a **standalone tradable strategy net of costs**. If it clears the 20bp
round-trip cost, it is an independent alpha source that can be blended with (or
replace) the model book.
Source: `book/ch01` "Timeline" + `chat-ideas.md`
(`TODO(evidence-needed: standalone 5d-reversal strategy net of costs)`).
## Change vs exp-26 reference
- This is NOT a model-construction variant — it isolates a SINGLE-FEATURE
signal: a model trained on `sp_trend_slope_5` (plus raw OHLCV) alone, or a
mechanical reversal book (rank by −`sp_trend_slope_5`, buy the most-reverted
topk), backtested net of costs over the exp-26 window.
- Control: exp-26 compact reference on the same window.
## Acceptance
- Standalone reversal `net_ann_return > 0` (clears 20bp round-trip) — proves
the claim "reversal is tradable net of costs". Secondary: excess vs the
model book is the blend decision for a future round.
## Execution prerequisites
1. `rd_train`/workflow with `feature_fields = $open,$high,$low,$close,$vwap,$volume,sp_trend_slope_5`
(single feature) OR a mechanical rank backtest via `rd_backtest` on a
hand-built pred (pred = −rank(sp_trend_slope_5)).
2. Trace + run + record as a standalone study (dataset-study status, not
necessarily a traced model experiment).
+26
View File
@@ -0,0 +1,26 @@
# QUEUE-19 — Martingale / variance-ratio study close-out (no qrun)
**Status:** QUEUED · **Priority:** P2 · **Effort:** ad-hoc script under `book/data/`
## Hypothesis (settle)
Assets are submartingales long-horizon / mean-reverting short-horizon
(`VR < 1` at 5–20d). CLAIMS.md marks this HYPOTHESIS (chat-derived martingale
study; exp 19 was opened but never closed). It is a market-structure claim, not a
trading claim — settle it with a clean-lake script, then close exp 19 or open a
scripted EVIDENCE entry.
## Method (persist everything under `book/data/evidence/q19-vr/`)
1. Load the 50-ETF panel 1d bars from the lake for 2015-01-01..2026-08-19.
2. Compute the Lo–MacKinlay variance ratio at horizons 5 / 10 / 20d per symbol,
with heteroskedasticity-robust z-stats.
3. Report: per-horizon VR distribution, fraction of symbols with VR < 1 and the
z-significance, pooled drift vs daily variance (submartingale check).
4. Cross-check the pooled `sp_trend_slope_5` regression beta claim (β ≈ −0.53,
t ≈ −24) on the clean lake.
5. Write `VR_stats.csv` + a one-page summary into the evidence dir.
## Acceptance
- VR < 1 at 5–20d for a material fraction of the panel with |z| > 2 → supports
the mean-reversion HYPOTHESIS; else mark REFUTED or REFERENCED.
- The result updates CLAIMS.md's "Assets are submartingales…" row and closes the
exp-19 open thread.
+22
View File
@@ -0,0 +1,22 @@
# QUEUE-20 — Effective independent names in the 50-ETF book (no qrun)
**Status:** QUEUED · **Priority:** P2 · **Effort:** ad-hoc script under `book/data/`
## Hypothesis (settle)
The 50-ETF book has only ~4 effective independent names (CLAIMS.md HYPOTHESIS,
chat-derived eigenvalue analysis, pre-reset). This is a concentration/diversification
claim with direct sizing relevance; verify it on the clean lake.
## Method (persist everything under `book/data/evidence/q20-effective-names/`)
1. Load the 50-ETF panel 1d returns from the lake for the test window 2026-01-04..2026-08-10.
2. Standardize returns; compute the correlation matrix and its eigendecomposition.
3. Count eigenvalues above the Marchenko–Pastur bound (N=50, T≈150) and report the
cumulative-variance share of the top k components.
4. Effective-rank measures: participation ratio `(Σλ)² / Σλ²` and cumulative 80%
variance count.
5. Write `eigenanalysis.csv` + a one-page summary.
## Acceptance
- If effective rank ≈ 4 (top-4 explain ~80%+ variance), the concentration claim is
PROVEN and feeds chapter 08 sizing guidance (why topk 10→20 adds no breadth).
- If effective rank is much larger, mark the claim REFUTED.
-141
View File
@@ -1,141 +0,0 @@
# -----------------------------------------------------------------------------
# QUEUE-04 — Non-overlapping 10-day label horizon.
#
# Hypothesis (book ch.01/chat-ideas): the 5d label is the campaign's best IC
# lever but sees short-horizon reversal only; a non-overlapping 10d label
# (`Ref($close,-11)/Ref($close,-1)-1`) tests whether a longer, cleaner horizon
# captures trend/reversal better and survives cost (lower effective turnover).
#
# Change vs exp-26 reference: ONE variable — label 5d -> 10d. Everything else
# identical (features, model, strategy).
#
# Acceptance: net_IR > 0.21 AND net_ann_return > +2.13%; secondary: ICIR and
# L/S Sharpe >= reference. A flat-but-not-worse result still settles the
# horizon-decomposition question (TODO: 5d can't see 1-12m drift).
# Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q04_label10d.yaml \
# experiment_name=tac-rd-q04-label10d
# -----------------------------------------------------------------------------
{%- 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 FEATURES = "$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" %}
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-q04-label10d"
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: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-10
fit_start_time: 2016-01-04
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-11)/Ref($close,-1)-1"
feature_fields: "{{ FEATURES }}"
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: 1
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,19 +1,12 @@
# ----------------------------------------------------------------------------- # QUEUE-12 — Long-horizon label (22d) + weekly recompute construction.
# QUEUE-05 — Non-overlapping 22-day label horizon. # Untested combination from book/CLAIMS.md open questions: Q05 (exp 37) proved the
# # 22d label has the strongest signal (IC 0.097, RankIC 0.117) but daily turnover
# Hypothesis (book ch.01/chat-ideas): a 22d (~monthly) non-overlapping label # killed the book (net -4.60%); Q07 (exp 39) proved weekly recompute is the cost
# tests true trend-following — the 5d label can't distinguish a 1-12m drift # lever (net +12.51%). Hypothesis: pairing them monetizes the label edge.
# (submartingale) from short-horizon reversal. Long-horizon labels also cut # Change vs exp-26 reference: label 5d -> 22d AND strategy -> WeeklyRebalanceDropoutStrategy.
# the rebalance-implied turnover, attacking the cost constraint directly. # Acceptance: net_IR > 0.5, net_ann > +5%, cost drag <= 2pp.
# # Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q12_label22d_weekly.yaml \
# Change vs exp-26 reference: ONE variable — label 5d -> 22d # experiment_name=tac-rd-q12-label22d-weekly
# (`Ref($close,-23)/Ref($close,-1)-1`). Everything else identical.
#
# Acceptance: net_IR > 0.21 AND net_ann_return > +2.13%; secondary: does the
# long-horizon signal survive cost with LOWER total_cost than the 5d book?
# Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q05_label22d.yaml \
# experiment_name=tac-rd-q05-label22d
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %} {%- 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 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 FEATURES = "$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" %} {%- set FEATURES = "$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" %}
@@ -23,30 +16,19 @@ qlib_init:
region: us region: us
expression_cache: null expression_cache: null
dataset_cache: null dataset_cache: null
calendar_provider: calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
instrument_provider: instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider: feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
exp_manager: exp_manager:
class: MLflowExpManager class: MLflowExpManager
module_path: qlib.workflow.expm module_path: qlib.workflow.expm
kwargs: kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-q12-label22d-weekly" }
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-q05-label22d"
task: task:
model: model:
@@ -67,7 +49,6 @@ task:
reg_alpha: 0.1 reg_alpha: 0.1
reg_lambda: 1.0 reg_lambda: 1.0
seeds: "42,7,2026,99,123" seeds: "42,7,2026,99,123"
parallel: 5
dataset: dataset:
class: DatasetH class: DatasetH
@@ -88,43 +69,27 @@ task:
label: "Ref($close,-23)/Ref($close,-1)-1" label: "Ref($close,-23)/Ref($close,-1)-1"
feature_fields: "{{ FEATURES }}" feature_fields: "{{ FEATURES }}"
infer_processors: infer_processors:
- class: DropAllNaN - { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
kwargs: {} - { class: ProcessInf, kwargs: {} }
- class: ProcessInf - { class: CSRankNorm, kwargs: {} }
kwargs: {} - { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- class: CSRankNorm - { class: Fillna, kwargs: {} }
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments: segments:
train: [2016-01-04, 2025-09-01] train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03] valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10] test: [2026-01-04, 2026-08-10]
record: record:
- class: SignalRecord - { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
module_path: qlib.workflow.record_temp - { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
config: config:
strategy: strategy:
class: TopkDropoutStrategy class: WeeklyRebalanceDropoutStrategy
module_path: qlib.contrib.strategy module_path: tac_qlib.contrib.strategy.weekly_rebalance
kwargs: kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
signal: "<PRED>"
topk: 10
n_drop: 1
only_tradable: true
risk_degree: 0.95
backtest: backtest:
start_time: 2026-01-04 start_time: 2026-01-04
end_time: 2026-08-10 end_time: 2026-08-10
@@ -0,0 +1,106 @@
# QUEUE-13 — Weekly rebalance reproduction on a second OOS window.
# Q07 (exp 39) proved weekly recompute on test 2026-01-04..2026-08-10 (net +12.51%,
# IR 1.24) but that is a single OOS window. Before promoting the weekly construction
# to a live round, reproduce it on a disjoint window: test 2025-01-02..2025-12-31
# with train/valid shifted to end 2024.
# Change vs exp-26 reference: segments shifted only (train ends 2024-08, test = 2025);
# strategy is the SAME weekly recompute as exp 39. Label stays 5d.
# Acceptance: net_IR > 0.21 AND net_ann > +2.13% on the 2025 window.
# Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q13_weekly_second_window.yaml \
# experiment_name=tac-rd-q13-weekly-second-window
{%- 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 FEATURES = "$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" %}
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-q13-weekly-second-window" }
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: 2025-12-31
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: "{{ FEATURES }}"
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: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
+107
View File
@@ -0,0 +1,107 @@
# QUEUE-14 — Out-of-universe validation: compact stochastic set on single-stock names.
# The 50-ETF panel results (compact feature set, RankIC 0.0663) are panel-specific;
# book/CLAIMS.md marks "generalizes to other universes" HYPOTHESIS - TODO(evidence-needed).
# Change vs exp-26 reference: universe -> 30 liquid US single-stock names.
# PREREQUISITE: backfill lake bars + sp/ta features for these symbols (full range,
# explicit start/end) — the stock panel currently has only ~180d of data (2025-12-01+).
# Backfill: get_lake_bars symbols=... start=2000-01-03 then
# get_lake_sp symbol=<s> start=2000-01-03 end=<today> fit_end=<train-end> persist=true
# Acceptance: RankIC > 0.03, ICIR > 0.15, net IR > 0 on the stock universe.
# Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q14_out_of_universe.yaml \
# experiment_name=tac-rd-q14-out-of-universe
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "AAPL,MSFT,NVDA,AMZN,GOOGL,META,TSLA,AVGO,AMD,JPM,UNH,PG,JNJ,MA,V,WMT,DIS,HD,KO,PEP,BAC,XOM,MCD,ABBV,COST,CRM,NFLX,ORCL,IBM,T" %}
{%- set FEATURES = "$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" %}
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-q14-out-of-universe" }
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-10
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: "{{ FEATURES }}"
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-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: 1, 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,19 +1,11 @@
# ----------------------------------------------------------------------------- # QUEUE-15 — 5-seed vs single-model clean A/B on the compact stochastic set.
# QUEUE-02 — Seed-count 10 vs 5 on the compact reference. # CLAIMS.md HYPOTHESIS: "5-seed RankIC ensemble raises performance vs single model
# # on ablated set" — pre-clean-lake exp 12 idea, re-validated directionally by exp
# Hypothesis (book ch.05, EVIDENCE#016 -> exp 28): seed count is load-bearing # 22–24, never a clean A/B post-reset. Seed count is load-bearing (exp 28: 2<5).
# (2 seeds lose to 5). Extending the same direction, does 10 seeds further # Change vs exp-26 reference: seeds "42,7,2026,99,123" -> single seed "2026".
# raise ICIR and net performance? Tests whether averaging benefit saturates. # Acceptance: single-model RankIC < 0.0663, net_IR < 0.21 (ensemble beats single).
# # Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q15_single_seed.yaml \
# Change vs exp-26 reference: ONE variable — seeds "42,7,2026,99,123" -> # experiment_name=tac-rd-q15-single-seed
# "42,7,2026,99,123,17,3,2020,88,55" (parallel: 10). Everything else identical.
#
# Acceptance: net_IR >= 0.21 AND ICIR/RankICIR >= reference (0.235 / 0.243);
# if seed count saturates, expect flat ICIR — that result also settles the
# mechanism question (variance reduction, not family diversification).
# Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q02_seed10.yaml \
# experiment_name=tac-rd-q02-seed10
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %} {%- 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 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 FEATURES = "$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" %} {%- set FEATURES = "$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" %}
@@ -23,30 +15,19 @@ qlib_init:
region: us region: us
expression_cache: null expression_cache: null
dataset_cache: null dataset_cache: null
calendar_provider: calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
instrument_provider: instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider: feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
exp_manager: exp_manager:
class: MLflowExpManager class: MLflowExpManager
module_path: qlib.workflow.expm module_path: qlib.workflow.expm
kwargs: kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-q15-single-seed" }
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-q02-seed10"
task: task:
model: model:
@@ -66,8 +47,7 @@ task:
subsample_freq: 1 subsample_freq: 1
reg_alpha: 0.1 reg_alpha: 0.1
reg_lambda: 1.0 reg_lambda: 1.0
seeds: "42,7,2026,99,123,17,3,2020,88,55" seeds: "2026"
parallel: 10
dataset: dataset:
class: DatasetH class: DatasetH
@@ -88,30 +68,19 @@ task:
label: "Ref($close,-6)/Ref($close,-1)-1" label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "{{ FEATURES }}" feature_fields: "{{ FEATURES }}"
infer_processors: infer_processors:
- class: DropAllNaN - { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
kwargs: {} - { class: ProcessInf, kwargs: {} }
- class: ProcessInf - { class: CSRankNorm, kwargs: {} }
kwargs: {} - { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- class: CSRankNorm - { class: Fillna, kwargs: {} }
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments: segments:
train: [2016-01-04, 2025-09-01] train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03] valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10] test: [2026-01-04, 2026-08-10]
record: record:
- class: SignalRecord - { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
module_path: qlib.workflow.record_temp - { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
@@ -119,12 +88,7 @@ task:
strategy: strategy:
class: TopkDropoutStrategy class: TopkDropoutStrategy
module_path: qlib.contrib.strategy module_path: qlib.contrib.strategy
kwargs: kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
signal: "<PRED>"
topk: 10
n_drop: 1
only_tradable: true
risk_degree: 0.95
backtest: backtest:
start_time: 2026-01-04 start_time: 2026-01-04
end_time: 2026-08-10 end_time: 2026-08-10
@@ -1,52 +1,34 @@
# ----------------------------------------------------------------------------- # QUEUE-16 — HMM family added as model features to the compact set.
# QUEUE-01 — M2 reproduction: risk-adjusted 22d Sharpe drift (sp_sharpe_22). # CLAIMS.md HYPOTHESIS: "Dropping model-specific feature families (ou, hmm)
# # improves the rank signal" — exp 25 cleanly tested OU (adding it hurts: IC 0.0511->0.0343);
# Hypothesis (book ch.01/ch.07, EVIDENCE#018 -> exp 30): adding the # hmm-as-features has NOT been clean A/B'd post-reset (exp 42 tested hmm as an entry
# risk-adjusted 22d Sharpe drift feature (sp_sharpe_22) to the compact # GATE overlay, refuted). This run adds the hmm family columns to the compact set.
# stochastic reference IMPROVES net portfolio performance (exp 30: net +6.53% # Change vs exp-26 reference: features += sp_hmm_p_regime1, sp_hmm_state.
# IR 0.62 vs reference +2.13% IR 0.21) while rank metrics dip (RankIC 0.0576 vs # Acceptance (prune-hypothesis): no improvement — RankIC <= 0.0663, net_IR <= 0.21.
# 0.0663). exp 30 is a SINGLE clean-lake run, unreproduced -> HYPOTHESIS. # Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q16_hmm_features.yaml \
# # experiment_name=tac-rd-q16-hmm-features
# Change vs exp-26 reference (EVIDENCE#015, run 21afc6af...): ONE feature added,
# feature_fields = compact set + sp_sharpe_22. Everything else byte-identical.
#
# Acceptance: net_ann_return > +2.13% AND net_IR > 0.21 (else HYPOTHESIS -> REFUTED).
# Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q01_m2_sharpe22_repro.yaml \
# experiment_name=tac-rd-q01-m2-sharpe22-repro
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %} {%- 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 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 FEATURES = "$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_sharpe_22" %} {%- set FEATURES = "$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_hmm_p_regime1,sp_hmm_state" %}
qlib_init: qlib_init:
provider_uri: "{{ LAKE }}" provider_uri: "{{ LAKE }}"
region: us region: us
expression_cache: null expression_cache: null
dataset_cache: null dataset_cache: null
calendar_provider: calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
instrument_provider: instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider: feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
exp_manager: exp_manager:
class: MLflowExpManager class: MLflowExpManager
module_path: qlib.workflow.expm module_path: qlib.workflow.expm
kwargs: kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-q16-hmm-features" }
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-q01-m2-sharpe22-repro"
task: task:
model: model:
@@ -67,7 +49,6 @@ task:
reg_alpha: 0.1 reg_alpha: 0.1
reg_lambda: 1.0 reg_lambda: 1.0
seeds: "42,7,2026,99,123" seeds: "42,7,2026,99,123"
parallel: 5
dataset: dataset:
class: DatasetH class: DatasetH
@@ -88,30 +69,19 @@ task:
label: "Ref($close,-6)/Ref($close,-1)-1" label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "{{ FEATURES }}" feature_fields: "{{ FEATURES }}"
infer_processors: infer_processors:
- class: DropAllNaN - { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
kwargs: {} - { class: ProcessInf, kwargs: {} }
- class: ProcessInf - { class: CSRankNorm, kwargs: {} }
kwargs: {} - { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- class: CSRankNorm - { class: Fillna, kwargs: {} }
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments: segments:
train: [2016-01-04, 2025-09-01] train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03] valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10] test: [2026-01-04, 2026-08-10]
record: record:
- class: SignalRecord - { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
module_path: qlib.workflow.record_temp - { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
@@ -119,12 +89,7 @@ task:
strategy: strategy:
class: TopkDropoutStrategy class: TopkDropoutStrategy
module_path: qlib.contrib.strategy module_path: qlib.contrib.strategy
kwargs: kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
signal: "<PRED>"
topk: 10
n_drop: 1
only_tradable: true
risk_degree: 0.95
backtest: backtest:
start_time: 2026-01-04 start_time: 2026-01-04
end_time: 2026-08-10 end_time: 2026-08-10
@@ -1,52 +1,34 @@
# ----------------------------------------------------------------------------- # QUEUE-17 — Realized-moments family added to the compact set.
# QUEUE-03 — topk 20 vs 10 diversification on the compact reference. # CLAIMS.md HYPOTHESIS: "Adding moment/volatility families regresses the signal"
# # (idea: pre-clean-lake exp 11). M1 momentum bundle (exp 29) and M3 GARCH (exp 31)
# Hypothesis (book ch.05/chat-ideas): the effective independent names in the # were refuted post-reset; the realized-moments family (sp_rskew/sp_rkurt/sp_dsv)
# 50-ETF book is small (~4, chat-derived eigenvalue analysis); raising topk # has NOT been clean A/B'd. This run adds the moments columns to the compact set.
# diversifies the book and should cut drawdown / raise net IR without hurting # Change vs exp-26 reference: features += sp_rskew_5,sp_rskew_22,sp_rkurt_5,sp_rkurt_22,sp_dsv_5,sp_dsv_22.
# the (weak) rank signal — cost relief by spreading the book wider. # Acceptance (prune-hypothesis): no improvement — RankIC <= 0.0663, net_IR <= 0.21.
# # Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q17_moments_features.yaml \
# Change vs exp-26 reference: ONE variable — strategy topk 10 -> 20 (n_drop 1). # experiment_name=tac-rd-q17-moments-features
# Everything else identical.
#
# Acceptance: net_IR > 0.21 AND net_max_drawdown < 7.69% AND net_ann_return >=
# +2.13%; watch total_cost — more names held must not raise turnover/cost.
# Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q03_topk20.yaml \
# experiment_name=tac-rd-q03-topk20
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %} {%- 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 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 FEATURES = "$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" %} {%- set FEATURES = "$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" %}
qlib_init: qlib_init:
provider_uri: "{{ LAKE }}" provider_uri: "{{ LAKE }}"
region: us region: us
expression_cache: null expression_cache: null
dataset_cache: null dataset_cache: null
calendar_provider: calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
instrument_provider: instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider: feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
exp_manager: exp_manager:
class: MLflowExpManager class: MLflowExpManager
module_path: qlib.workflow.expm module_path: qlib.workflow.expm
kwargs: kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-q17-moments-features" }
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-q03-topk20"
task: task:
model: model:
@@ -67,7 +49,6 @@ task:
reg_alpha: 0.1 reg_alpha: 0.1
reg_lambda: 1.0 reg_lambda: 1.0
seeds: "42,7,2026,99,123" seeds: "42,7,2026,99,123"
parallel: 5
dataset: dataset:
class: DatasetH class: DatasetH
@@ -88,30 +69,19 @@ task:
label: "Ref($close,-6)/Ref($close,-1)-1" label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "{{ FEATURES }}" feature_fields: "{{ FEATURES }}"
infer_processors: infer_processors:
- class: DropAllNaN - { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
kwargs: {} - { class: ProcessInf, kwargs: {} }
- class: ProcessInf - { class: CSRankNorm, kwargs: {} }
kwargs: {} - { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- class: CSRankNorm - { class: Fillna, kwargs: {} }
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments: segments:
train: [2016-01-04, 2025-09-01] train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03] valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10] test: [2026-01-04, 2026-08-10]
record: record:
- class: SignalRecord - { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
module_path: qlib.workflow.record_temp - { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
@@ -119,12 +89,7 @@ task:
strategy: strategy:
class: TopkDropoutStrategy class: TopkDropoutStrategy
module_path: qlib.contrib.strategy module_path: qlib.contrib.strategy
kwargs: kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
signal: "<PRED>"
topk: 20
n_drop: 1
only_tradable: true
risk_degree: 0.95
backtest: backtest:
start_time: 2026-01-04 start_time: 2026-01-04
end_time: 2026-08-10 end_time: 2026-08-10
+106
View File
@@ -0,0 +1,106 @@
# QUEUE-18 — OptimalStopControl clean re-test vs TopkDropout (exp 13/14 claim).
# CLAIMS.md HYPOTHESIS: "TopkDropout beats stochastic-control OptimalStopControl on
# the ensemble signal" — exp 13/14 were pre-clean-lake; never re-tested post-reset.
# Same compact signal as the exp-26 reference; ONLY the strategy changes to
# OptimalStopControl with exp-13 params (entry 0.85 / exit 0.7 / hold 10 / sl -0.08).
# PREREQUISITE: tac_qlib/contrib/strategy/optimal_stop.py must be synced to the venv
# site-packages snapshot before running (see /app/AGENTS.md).
# Acceptance: TopkDropout net_IR >= stop-control net_IR; document cost drag of both.
# Run: rd_run_workflow config_path=<repo>/experiments/queue/workflows/q18_optstop.yaml \
# experiment_name=tac-rd-q18-optstop
{%- 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 FEATURES = "$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" %}
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-q18-optstop" }
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-10
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: "{{ FEATURES }}"
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-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: OptimalStopControl
module_path: tac_qlib.contrib.strategy.optimal_stop
kwargs: { signal: "<PRED>", topk: 10, entry_pct: 0.85, exit_pct: 0.7, max_hold_days: 10, min_hold_days: 2, sl: -0.08 }
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