Skip to content

Commit da92f33

Browse files
committed
Update project implementation and supporting files
1 parent cedcb4d commit da92f33

26 files changed

Lines changed: 2669 additions & 243 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ __pycache__/
1010
*.pyd
1111
*.so
1212
.pytest_cache/
13+
.pytest_candidates*/
1314
.mypy_cache/
1415
.ruff_cache/
1516
tmp/

README.md

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,38 @@ flowchart LR
155155

156156
See [Qlib and ML winner-selection research notes](docs/research/2026-07-17-qlib-winner-selection-research.md).
157157

158+
The controlled U.S. replication is now implemented with the exact 158-feature contract, a
159+
Qlib-style LightGBM regressor, raw and sector-neutral residual targets, identical temporal folds,
160+
and a fixed 40-stock portfolio. Build the cached feature panel and run all four arms with:
161+
162+
```bash
163+
pip install -e ".[tuning]"
164+
python -m src.cli build-qlib-alpha158-dataset
165+
python -m src.cli run-qlib-alpha158-comparison
166+
python -m src.cli run-alpha158-feature-ablation
167+
python -m src.cli run-double-ensemble-v10 \
168+
--output reports/experiments/double_ensemble_v10_consensus
169+
```
170+
171+
The Ridge-controlled feature ablation found no verified incremental Alpha158 benefit: adding
172+
Alpha158 raised mean Rank IC from 0.0102 to 0.0114, but the paired confidence interval included
173+
zero and top-40 excess return fell from 0.1006% to 0.0646% per week. See the
174+
[Alpha158 U.S. experiment record](docs/experiments/2026-07-20-qlib-alpha158-us.md).
175+
176+
The model-specific v10 study gave Ridge, LightGBM, and XGBoost independent Optuna searches and
177+
fold-safe shuffle-ablation selection. XGBoost achieved the strongest top-40 point estimate
178+
(0.2121% weekly excess) but its paired confidence interval included zero. LightGBM and the
179+
validation-weighted ensemble failed out of sample, so the ensemble remains disabled. See the
180+
[v10 experiment record](docs/experiments/2026-07-20-double-ensemble-v10.md).
181+
158182
## Key Modules
159183

160184
| Module | Role |
161185
|---|---|
162186
| `src/data` | Market data, macro data, point-in-time universe, price repair, and caching |
163-
| `src/features` | Technical, cross-sectional, macro, and NLP feature generation |
187+
| `src/features` | Technical, Alpha158, cross-sectional, macro, and NLP feature generation |
164188
| `src/targets` | Weekly residual-return ranking targets |
165-
| `src/models` | Ridge and XGBoost ranking models |
189+
| `src/models` | Ridge, XGBoost ranking, and LightGBM cross-sectional models |
166190
| `src/portfolio` | Sparse ranking, active tilts, risk models, and costs |
167191
| `src/backtest` | Walk-forward execution, accounting, and metrics |
168192
| `src/nlp` | FinBERT, topic, event, and text-source pipelines |
@@ -220,6 +244,26 @@ the latest close, and a frozen paper portfolio plus `model_selection.csv` are sa
220244
next-session returns; otherwise the forward test stops being clean. This daily proxy is designed to
221245
collect evidence faster than waiting ten calendar weeks for ten weekly rebalance observations.
222246

247+
For the recurrent after-close experiment, use the one-call daily pipeline instead of running the
248+
monitor, two model families, and dashboard separately:
249+
250+
```bash
251+
python -m src.cli run-live-forward-daily --as-of 2026-07-22
252+
```
253+
254+
The [live-forward operating guide](docs/LIVE_FORWARD_MONITOR.md) documents the close-data guard,
255+
frozen-snapshot contract, dashboard timestamps, model-overlap metrics, and generated artifacts.
256+
257+
The daily command first refreshes the latest prior Ridge and XGBoost portfolios against the
258+
completed session. Yahoo's final hourly bar begins at 15:30, so the command requires that bar and
259+
retries five times at 60-second intervals if it is not available yet. It then requires an official
260+
daily price row for the decision date, independently tunes and refits Ridge and XGBoost, saves both
261+
new top-40 portfolios, writes `selection_stability_YYYY-MM-DD.json`, and rebuilds the shared
262+
dashboard. The stability report tracks same-day model overlap, day-over-day retention, top-10
263+
persistence, rank correlation, entrants/exits, implied replacement, and multi-day consensus names.
264+
This ordering prevents a successful model retrain from leaving the performance chart stuck at an
265+
earlier intraday bar.
266+
223267
The July 20, 2026 production-style run requested 25 Optuna trials with a 600-second timeout. Ten
224268
trials completed, using three 42-session validation folds separated from training by a one-session
225269
purge. The selected model was Ridge with `alpha=6156.9973`, aggregate validation Rank IC of
@@ -261,9 +305,10 @@ Build the static monitoring dashboard:
261305
python -m src.cli build-live-dashboard
262306
```
263307

264-
The dashboard reads every saved snapshot under `reports/live_forward/`, supports changing the
265-
snapshot and evaluation horizon, and shows portfolio return, benchmark return, active return,
266-
stock-level winners/losers versus SPY, model-selection evidence, and experiment history.
308+
The dashboard reads every saved snapshot under `reports/live_forward/`, overlays Ridge, XGBoost,
309+
and SPY on one multi-session chart, provides date-range controls, identifies the next frozen
310+
portfolios whose returns are pending, and shows human-readable model and stock-level evidence. It
311+
is refreshed by the daily pipeline, snapshot, intraday-report, and evaluation commands.
267312

268313
Build the earlier conservative final-reference package:
269314

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
experiment:
2+
id: double_ensemble_v10_development
3+
status: development_only
4+
description: Fully tuned member models with fold-safe shuffle feature selection
5+
6+
residual_rank_v2:
7+
seed: 42
8+
tuning:
9+
trials_per_model: 15
10+
timeout_seconds_per_model: 900
11+
feature_counts: [20, 40, 60, 80, 120]
12+
max_feature_candidates: 120
13+
fold_stability_penalty: 0.25
14+
ensemble:
15+
shrink_to_equal: 0.50
16+
nested_validation:
17+
outer_min_train_weeks: 104
18+
outer_test_weeks: 26
19+
inner_folds: 3
20+
inner_min_train_weeks: 52
21+
inner_validation_weeks: 13
22+
purge_weeks: 1
23+
portfolio:
24+
holding_candidates: [40]
25+
max_sector_fraction: 0.25
26+
invested_weight: 0.70
27+
active_l1_budget: 0.10
28+
max_active_weight: 0.01
29+
calibration_shrink_observations: 500
30+
starting_capital: 1000000
31+
one_way_cost_bps: 8.0
32+
fixed_fee_per_trade: 10.0
33+
minimum_trade_weight: 0.0025
34+
statistics:
35+
permutation_samples: 100
36+
moving_block_weeks: 8
37+
bootstrap_samples: 5000
38+
diagnostics:
39+
top_k: 40
40+
lockbox:
41+
opened: false
42+
policy: development comparison only
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
experiment:
2+
id: qlib_alpha158_us_development
3+
manifest_version: 1
4+
status: development_only
5+
description: Controlled Alpha158-LightGBM target comparison on repaired U.S. PIT rows
6+
development_end: "2023-12-31"
7+
8+
residual_rank_v2:
9+
models:
10+
feature_counts: [20, 40, all]
11+
ridge_alphas: [10.0, 100.0]
12+
xgb_candidates: []
13+
# Qlib's published Alpha158 LightGBM shape, expressed with sklearn names.
14+
lightgbm_candidates:
15+
- n_estimators: 500
16+
learning_rate: 0.2
17+
colsample_bytree: 0.8879
18+
subsample: 0.8789
19+
subsample_freq: 1
20+
reg_alpha: 205.6999
21+
reg_lambda: 580.9768
22+
max_depth: 8
23+
num_leaves: 210
24+
n_jobs: 4
25+
random_state: 42
26+
nested_validation:
27+
outer_min_train_weeks: 104
28+
outer_test_weeks: 26
29+
inner_folds: 2
30+
inner_min_train_weeks: 52
31+
inner_validation_weeks: 13
32+
purge_weeks: 1
33+
portfolio:
34+
holding_candidates: [40]
35+
max_sector_fraction: 0.25
36+
invested_weight: 0.70
37+
active_l1_budget: 0.10
38+
max_active_weight: 0.01
39+
calibration_shrink_observations: 500
40+
starting_capital: 1000000
41+
one_way_cost_bps: 8.0
42+
fixed_fee_per_trade: 10.0
43+
minimum_trade_weight: 0.0025
44+
statistics:
45+
permutation_samples: 100
46+
moving_block_weeks: 8
47+
bootstrap_samples: 5000
48+
diagnostics:
49+
top_k: 40
50+
lockbox:
51+
opened: false
52+
policy: development comparison only; no confirmation or lockbox data

docs/LIVE_FORWARD_MONITOR.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Live-Forward Monitoring
2+
3+
## Purpose
4+
5+
The live-forward experiment tests whether two independently tuned ranking models can select stocks
6+
using only information available at each decision time. It is an evidence-collection workflow, not
7+
yet a production trading recommendation. Ridge is the stable linear benchmark and the XGBoost
8+
ranker is the nonlinear challenger. Both select 40 equal-weight names and share SPY as the market
9+
benchmark.
10+
11+
## Daily after-close workflow
12+
13+
Run the production-style workflow after Yahoo Finance publishes the final regular-session bar:
14+
15+
```bash
16+
python -m src.cli run-live-forward-daily --as-of YYYY-MM-DD
17+
```
18+
19+
The command performs these steps in order:
20+
21+
1. Incrementally refreshes market data and evaluates portfolios frozen before the current session.
22+
2. Waits for the final 15:30 hourly bar and verifies the official daily close is available.
23+
3. Rebuilds price features through the decision date.
24+
4. Builds forward-return training labels only where the future outcome was already known before the
25+
decision date.
26+
5. Tunes Ridge and XGBoost independently on temporal validation data, refits the selected
27+
configurations, and freezes both top-40 lists for the next evaluation session.
28+
6. Writes model-agreement and day-over-day selection-stability diagnostics.
29+
7. Rebuilds `reports/live_forward/dashboard.html`.
30+
31+
If final market data is unavailable, the command retries and then fails closed. It must not create a
32+
snapshot from a partial trading day.
33+
34+
## Point-in-time and leakage controls
35+
36+
- A portfolio is evaluated only after its holdings have been frozen.
37+
- Same-session partial prices cannot enter model fitting or stock selection.
38+
- Training labels end early enough that their complete forward horizon is known at the decision
39+
time.
40+
- Hyperparameters and feature counts are selected using temporal validation, not the live session.
41+
- Ridge and XGBoost are displayed together; neither is chosen retrospectively using realized live
42+
returns.
43+
- The July 20 session is a setup/test observation. The official comparison baseline begins at the
44+
July 20 SPY close, and portfolio performance begins on July 21.
45+
46+
## Dashboard time conventions
47+
48+
Yahoo's hourly timestamps identify the **start** of each bar. The dashboard displays the corresponding
49+
bar-end time, so a Yahoo 15:30 bar appears as 16:00. Daily compounded returns are anchored to the
50+
previous official close, not to the session open.
51+
52+
The date controls aggregate every completed observation in the selected range. Because the top-40
53+
holdings can change each day, each session uses the list frozen before that session.
54+
55+
## Selection-stability metrics
56+
57+
All dashboard insights are deterministic calculations; no LLM API is required.
58+
59+
For two 40-stock sets, A and B:
60+
61+
- **Overlap rate:** `|A intersection B| / 40`
62+
- **Jaccard similarity:** `|A intersection B| / |A union B|`
63+
- **Model disagreement:** `1 - overlap rate`
64+
- **Day-over-day replacement:** `1 - |A_today intersection A_prior| / 40`
65+
- **Rank stability:** Spearman correlation of ranks among names retained on both dates
66+
- **Average rank movement:** mean absolute rank change among retained names
67+
68+
The dashboard also reports top-10 retention, entrants, exits, current Ridge-XGBoost consensus, and
69+
persistent consensus across the available sessions. These describe model agreement and stability;
70+
they do not by themselves prove forecast quality.
71+
72+
## Generated artifacts
73+
74+
Generated outputs remain under the ignored `reports/live_forward/` directory:
75+
76+
- dated Ridge and XGBoost snapshot directories with manifests, tuning results, predictions, and
77+
selected holdings;
78+
- intraday and close-to-close evaluation files for each frozen portfolio;
79+
- `selection_stability_YYYY-MM-DD.json`;
80+
- `dashboard.html`.
81+
82+
Keep source code, configurations, tests, and documentation in Git. Do not commit cached prices,
83+
trained artifacts, generated reports, credentials, or temporary pytest directories.
84+
85+
## Interpretation boundary
86+
87+
The live sample is currently too short for a performance claim. Compare Ridge, XGBoost, and SPY over
88+
many independent sessions, then report active return, Rank IC, turnover, drawdown, and uncertainty.
89+
Model overlap is diagnostically useful, but high consensus is neither necessary nor sufficient for
90+
outperformance.

docs/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [Final research-reference version](FINAL_VERSION.md)
66
- [2026-07-16 consolidation release notes](RELEASE_NOTES_2026-07-16.md)
77
- [GitHub push plan](GITHUB_PUSH_PLAN.md)
8+
- [Live-forward monitoring and daily operations](LIVE_FORWARD_MONITOR.md)
89

910
## Branch Policy
1011

@@ -43,6 +44,8 @@ decision explicitly says they are active.
4344

4445
## Experiment Logs
4546

47+
- [2026-07-20 - Model-specific tuning and ensemble v10](experiments/2026-07-20-double-ensemble-v10.md)
48+
- [2026-07-20 - Qlib Alpha158 and LightGBM U.S. comparison](experiments/2026-07-20-qlib-alpha158-us.md)
4649
- [2026-07-17 - Survival-risk filter diagnostic for v8](experiments/2026-07-17-survival-filter-v8.md)
4750
- [2026-07-17 - Repaired rank-40 ML stock-selection evidence](experiments/2026-07-17-repaired-rank40-selection.md)
4851
- [2026-07-16 - Transparent smart-universe experiment v7](experiments/2026-07-16-smart-universe-v7.md)

0 commit comments

Comments
 (0)