diff --git a/code/MANIFEST.txt b/code/MANIFEST.txt index b862502..d318473 100644 --- a/code/MANIFEST.txt +++ b/code/MANIFEST.txt @@ -1,5 +1,5 @@ # TradeAC custom-qlib-code snapshot (auto-generated) -# parent repo HEAD : fd5382caa4c4e96417a16d5a22006d9a7d625d00 +# parent repo HEAD : c142ef0fee61fd84ce4ea2aa554c48ab90e58fe5 # tac-qlib/tac_qlib/contrib # tac-qlib/tac_qlib/data # per-file hashes (git hash-object): diff --git a/queue/README.md b/queue/README.md new file mode 100644 index 0000000..bdd3848 --- /dev/null +++ b/queue/README.md @@ -0,0 +1,69 @@ +# TradeAC Experiment Queue — Series 2 (Q12+) + +**Purpose.** The next pre-registered batch of experiments, continuing Series 1 +(Q01–Q11, exp 33–43, all executed and folded into `book/CLAIMS.md` / +`book/EVIDENCE.md`). Each entry targets a still-unproven `HYPOTHESIS` from the +book or an open question flagged in `CLAIMS.md`/`book/README.md`, and follows the +Series-1 discipline: one variable changed vs the exp-26 reference, acceptance +fixed BEFORE the run, sequential execution, trace-first, verify-then-close. + +**Reference / control (MUST reproduce first).** exp 26 (`21afc6af…`, mlflow exp +25) is the campaign baseline; exp 39 (Q07, weekly rebalance) is the best +construction. Reference config is byte-reproduced in `workflows/exp26/` on the +`exp/26-…` branch and in this dir's `workflows/*.yaml`. + +| Config element | exp-26 reference value | +|---|---| +| Universe | 50-ETF panel (`UNIVERSE` below) | +| Features | compact stochastic 25-field set (no ou/hmm/moments/garch) | +| Label | `Ref($close,-6)/Ref($close,-1)-1` (5d) | +| Model | `RankICEnsembleLGBModel`, seeds `42,7,2026,99,123`, lr 0.02, leaves 31, 3000 rounds, ES 200 | +| 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 | +| Costs | open 0.0005 / close 0.0015 / min $5, deal $close, SPY benchmark, $1M | + +**Reference metrics to beat (EVIDENCE#015):** net_ann +2.13%, net_IR 0.21, gross ++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) + +| ID | Title / hypothesis | Change vs reference (ONE var) | Acceptance | Config | Ready? | +|----|--------------------|-------------------------------|------------|--------|--------| +| 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` | ✅ | +| 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` | ✅ | +| 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) | +| 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` | ✅ | +| 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` | ✅ | +| 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` | ✅ | +| 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) | +| 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 | +| 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 | + +### Deferred (methodology / infra, P3) +- Purged / walk-forward CV (was queue's old Q12) — methodology, not an alpha lever. +- PSI-based drift-aware retraining cadence — needs a drift-gate module + a retrain decision rule. +- No-trade buffer band / notional-vs-qty sizing — siblings of Q12/Q13; queue only if weekly reproduces. +- Macro/drift overlays (SPY>200d regime gate, momentum tilt) — needs new data pipeline. + +## Execution protocol (per queued run) + +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`). +2. **Trace before running** (`rd_trace_start` with the hypothesis as `rational`, fresh `experiment_name`, `evolved_from=auto`). +3. **Run** `rd_run_workflow config_path= experiment_name=` — `wait=false`, poll `rd_exp_get_run` until `FINISHED`. +4. **Verify against acceptance** via `rd_exp_result` (headline + backtest risk). +5. **Finish the trace** (`rd_trace_finish` with `metrics` + `evaluation`), snapshot any changed contrib modules. +6. **Report to the book** — PROVE/REFUTE → update `book/CLAIMS.md` + `book/EVIDENCE.md`. + +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 + +Mined 2026-08-20 from `book/CLAIMS.md`, `book/EVIDENCE.md`, `book/README.md`, +`book/references/chat-ideas.md`, and Series-1 `queue/` (Q01–Q11, executed exp +33–43). Reference numbers are post-clean-lake (exp 21+). \ No newline at end of file diff --git a/queue/designs/q19_martingale_vr.md b/queue/designs/q19_martingale_vr.md new file mode 100644 index 0000000..fa48c1e --- /dev/null +++ b/queue/designs/q19_martingale_vr.md @@ -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. \ No newline at end of file diff --git a/queue/designs/q20_effective_names.md b/queue/designs/q20_effective_names.md new file mode 100644 index 0000000..0d926b8 --- /dev/null +++ b/queue/designs/q20_effective_names.md @@ -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. \ No newline at end of file diff --git a/queue/workflows/q12_label22d_weekly.yaml b/queue/workflows/q12_label22d_weekly.yaml new file mode 100644 index 0000000..d5cd407 --- /dev/null +++ b/queue/workflows/q12_label22d_weekly.yaml @@ -0,0 +1,105 @@ +# QUEUE-12 — Long-horizon label (22d) + weekly recompute construction. +# 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 +# killed the book (net -4.60%); Q07 (exp 39) proved weekly recompute is the cost +# lever (net +12.51%). Hypothesis: pairing them monetizes the label edge. +# Change vs exp-26 reference: label 5d -> 22d AND strategy -> WeeklyRebalanceDropoutStrategy. +# Acceptance: net_IR > 0.5, net_ann > +5%, cost drag <= 2pp. +# Run: rd_run_workflow config_path=/experiments/queue/workflows/q12_label22d_weekly.yaml \ +# experiment_name=tac-rd-q12-label22d-weekly +{%- 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-q12-label22d-weekly" } + +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,-23)/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: WeeklyRebalanceDropoutStrategy + module_path: tac_qlib.contrib.strategy.weekly_rebalance + kwargs: { signal: "", 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 \ No newline at end of file diff --git a/queue/workflows/q13_weekly_second_window.yaml b/queue/workflows/q13_weekly_second_window.yaml new file mode 100644 index 0000000..2d780a4 --- /dev/null +++ b/queue/workflows/q13_weekly_second_window.yaml @@ -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=/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: "", 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 \ No newline at end of file diff --git a/queue/workflows/q14_out_of_universe.yaml b/queue/workflows/q14_out_of_universe.yaml new file mode 100644 index 0000000..3434f9b --- /dev/null +++ b/queue/workflows/q14_out_of_universe.yaml @@ -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= start=2000-01-03 end= fit_end= persist=true +# Acceptance: RankIC > 0.03, ICIR > 0.15, net IR > 0 on the stock universe. +# Run: rd_run_workflow config_path=/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: "", 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 \ No newline at end of file diff --git a/queue/workflows/q15_single_seed.yaml b/queue/workflows/q15_single_seed.yaml new file mode 100644 index 0000000..6b54ffe --- /dev/null +++ b/queue/workflows/q15_single_seed.yaml @@ -0,0 +1,104 @@ +# QUEUE-15 — 5-seed vs single-model clean A/B on the compact stochastic set. +# 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 +# 22–24, never a clean A/B post-reset. Seed count is load-bearing (exp 28: 2<5). +# Change vs exp-26 reference: seeds "42,7,2026,99,123" -> single seed "2026". +# Acceptance: single-model RankIC < 0.0663, net_IR < 0.21 (ensemble beats single). +# Run: rd_run_workflow config_path=/experiments/queue/workflows/q15_single_seed.yaml \ +# experiment_name=tac-rd-q15-single-seed +{%- 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-q15-single-seed" } + +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: "2026" + + 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: "", 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 \ No newline at end of file diff --git a/queue/workflows/q16_hmm_features.yaml b/queue/workflows/q16_hmm_features.yaml new file mode 100644 index 0000000..21784ad --- /dev/null +++ b/queue/workflows/q16_hmm_features.yaml @@ -0,0 +1,105 @@ +# QUEUE-16 — HMM family added as model features to the compact set. +# 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); +# hmm-as-features has NOT been clean A/B'd post-reset (exp 42 tested hmm as an entry +# GATE overlay, refuted). This run adds the hmm family columns to the compact set. +# Change vs exp-26 reference: features += sp_hmm_p_regime1, sp_hmm_state. +# Acceptance (prune-hypothesis): no improvement — RankIC <= 0.0663, net_IR <= 0.21. +# Run: rd_run_workflow config_path=/experiments/queue/workflows/q16_hmm_features.yaml \ +# experiment_name=tac-rd-q16-hmm-features +{%- 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,sp_hmm_p_regime1,sp_hmm_state" %} + +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-q16-hmm-features" } + +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: "", 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 \ No newline at end of file diff --git a/queue/workflows/q17_moments_features.yaml b/queue/workflows/q17_moments_features.yaml new file mode 100644 index 0000000..35a7439 --- /dev/null +++ b/queue/workflows/q17_moments_features.yaml @@ -0,0 +1,105 @@ +# QUEUE-17 — Realized-moments family added to the compact set. +# 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) +# were refuted post-reset; the realized-moments family (sp_rskew/sp_rkurt/sp_dsv) +# has NOT been clean A/B'd. This run adds the moments columns to the compact set. +# Change vs exp-26 reference: features += sp_rskew_5,sp_rskew_22,sp_rkurt_5,sp_rkurt_22,sp_dsv_5,sp_dsv_22. +# Acceptance (prune-hypothesis): no improvement — RankIC <= 0.0663, net_IR <= 0.21. +# Run: rd_run_workflow config_path=/experiments/queue/workflows/q17_moments_features.yaml \ +# experiment_name=tac-rd-q17-moments-features +{%- 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,sp_rskew_5,sp_rskew_22,sp_rkurt_5,sp_rkurt_22,sp_dsv_5,sp_dsv_22" %} + +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-q17-moments-features" } + +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: "", 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 \ No newline at end of file diff --git a/queue/workflows/q18_optstop.yaml b/queue/workflows/q18_optstop.yaml new file mode 100644 index 0000000..5b7799a --- /dev/null +++ b/queue/workflows/q18_optstop.yaml @@ -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=/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: "", 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 \ No newline at end of file