Skip to content

Commit 892203f

Browse files
authored
Merge pull request #5 from HanSun103/agent/live-forward-readme
Redesign live-forward research and documentation
2 parents a9a7ccf + e8d66d1 commit 892203f

32 files changed

Lines changed: 10657 additions & 369 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@ data/raw/fnspid_source/
3939
Thumbs.db
4040

4141
/pokestock-catalog-fix/
42+
43+
# Local Codex research skills and accumulated private knowledge
44+
/local_skills/

README.md

Lines changed: 303 additions & 349 deletions
Large diffs are not rendered by default.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Preregistered matched comparison of one-, two-, and four-week forecast horizons.
2+
experiment:
3+
id: forecast_horizon_sweep_v1
4+
status: development_horizon_selection
5+
6+
forecast_horizon_sweep:
7+
horizons: [5, 10, 20]
8+
training_cutoff: "2021-01-01"
9+
test_start: "2021-01-04"
10+
selection_end: "2022-12-30"
11+
confirmation_start: "2023-01-03"
12+
test_end: "2023-12-29"
13+
ridge_alpha: 10.0
14+
feature_count: 40
15+
holdings: 40
16+
maximum_sector_fraction: 0.25
17+
sentiment_arms: [false, true]
18+
19+
inputs:
20+
features: data/processed/residual_rank_v2_repaired/features.parquet
21+
prices: data/processed/residual_rank_v2_repaired/prices_repaired_aliases.parquet
22+
23+
output: reports/experiments/forecast_horizon_sweep_v1
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
experiment:
2+
id: live_forward_horizon_matched_v2
3+
status: paper_live_research
4+
version: 2
5+
6+
live_forward_20d:
7+
horizons: [20, 10, 5]
8+
primary_horizon_sessions: 20
9+
comparison_design: horizon_matched_refit_and_rebalance
10+
refit_sessions_by_horizon:
11+
5: 5
12+
10: 10
13+
20: 20
14+
rebalance_policy:
15+
active: full_top40_refresh_on_horizon_clock
16+
status: under_audit
17+
buffer_enabled: false
18+
next_comparison:
19+
- full_refresh
20+
- frozen_holdings
21+
- rank_margin_buffer
22+
- score_percentile_buffer
23+
training_lookback_sessions: 756
24+
holdings: 40
25+
invested_weight: 1.0
26+
maximum_sector_fraction: 0.25
27+
feature_count: 40
28+
minimum_sentiment_coverage: 0.01
29+
30+
live_data:
31+
refresh_by_default: true
32+
recent_training_start: "2021-01-01"
33+
lookback_days: 2200
34+
nlp_history_days: 90
35+
maximum_news_staleness_hours: 72
36+
finbert_local_files_only: true
37+
split_cache_file: data/cache/yfinance/split_events.csv
38+
sector_file: data/processed/residual_rank_v2_repaired/inferred_sector_history.csv
39+
cache_root: data/cache/live_forward_20d
40+
41+
model_selection:
42+
enabled: true
43+
rank_config: configs/experiments/sparse_rank_v3_fnspid_2018_2023.yaml
44+
validation_sessions: 42
45+
folds: 5
46+
ridge_optuna_trials: 30
47+
xgb_optuna_trials: 25
48+
timeout_seconds: 7200
49+
selection_metric: top40_raw_excess
50+
51+
models:
52+
ridge:
53+
alpha: 10.0
54+
xgb_ranker:
55+
n_estimators: 200
56+
max_depth: 3
57+
learning_rate: 0.03
58+
subsample: 0.8
59+
colsample_bytree: 0.8
60+
reg_lambda: 10.0
61+
random_state: 42
62+
n_jobs: 4
63+
64+
universe:
65+
maximum_names: 250
66+
liquidity_lookback_sessions: 63
67+
minimum_names: 40
68+
69+
survival_guardrail:
70+
label_horizon_sessions: 63
71+
validation_sessions: 252
72+
training_lookback_sessions: 756
73+
training_stride_sessions: 5
74+
target_recall: 0.90
75+
minimum_yearly_recall: 0.80
76+
maximum_exclusion_fraction: 0.25
77+
activate_ml_exclusions: true
78+
hard_event_types:
79+
- index_deletion
80+
- delisting
81+
- bankruptcy
82+
- completed_acquisition
83+
- trading_suspension
84+
features:
85+
- momentum_21
86+
- momentum_63
87+
- vol_21d
88+
- vol_63d
89+
- rolling_beta
90+
- idiosyncratic_vol
91+
- cs_return_rank
92+
- log_volume
93+
- volume_ratio_20d
94+
- rsi_14
95+
- bb_pct
96+
- atr_14
97+
- overnight_gap
98+
- sector
99+
100+
tactical_sentiment:
101+
enabled: true
102+
timezone: America/New_York
103+
strategic_news_cutoff: "15:45"
104+
same_close_available_cutoff: "15:30"
105+
order_freeze_time: "15:45"
106+
processing_latency_minutes: 15
107+
maximum_news_age_hours: 4
108+
minimum_scope_confidence: 0.70
109+
minimum_relevance: 0.70
110+
minimum_sentiment_confidence: 0.70
111+
minimum_absolute_sentiment: 0.25
112+
minimum_confirming_sources: 2
113+
maximum_pre_signal_move: 0.03
114+
maximum_overlay_weight: 0.05
115+
maximum_combined_ticker_weight: 0.15
116+
maximum_daily_turnover: 0.10
117+
signal_expiry_sessions: 2
118+
cooldown_sessions: 2
119+
same_close_enabled: false
120+
broker_cutoff: null
121+
primary_sources:
122+
- sec
123+
- exchange_notice
124+
- court
125+
- issuer_release
126+
127+
evidence_gates:
128+
early_matured_forecasts: 60
129+
preliminary_matured_forecasts: 252
130+
promotion_matured_forecasts: 504
131+
promotion_nonoverlapping_blocks: 24
132+
133+
inputs:
134+
features: data/processed/residual_rank_v2_repaired/features.parquet
135+
prices: data/processed/residual_rank_v2_repaired/prices_repaired_aliases.parquet
136+
membership: data/raw/sp500_ticker_start_end_source.csv
137+
posts: data/raw/nlp_posts.parquet
138+
corporate_events: data/raw/corporate_events.csv
139+
140+
output: reports/live_forward_horizon_matched_v2
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Historical replay of the current live-forward monitor. This is intentionally
2+
# separate from the weekly five-session final reference.
3+
experiment:
4+
id: live_settings_backtest_v1
5+
status: observed_historical_diagnostic
6+
description: Daily one-session replay of the current live monitor contract
7+
8+
live_settings_backtest:
9+
start_date: "2023-01-03"
10+
end_date: "2023-12-28"
11+
holdings: 40
12+
label_horizon_sessions: 1
13+
training_lookback_sessions: 644
14+
validation_days: 42
15+
tuning_folds: 5
16+
purge_days: 1
17+
ridge_optuna_trials: 50
18+
xgb_optuna_trials: 50
19+
selection_metric: top40_raw_excess
20+
one_way_cost_bps: 8.0
21+
fixed_fee_per_trade: 10.0
22+
starting_capital: 1000000.0
23+
bootstrap_samples: 5000
24+
block_days: 20
25+
permutation_samples: 100
26+
seed: 42
27+
28+
inputs:
29+
prices: data/processed/residual_rank_v2_repaired/prices_repaired_aliases.parquet
30+
membership: data/raw/sp500_ticker_start_end_source.csv
31+
sector_history: data/processed/residual_rank_v2_repaired/inferred_sector_history.csv
32+
live_panel_cache: data/processed/live_settings_backtest_v1/features.parquet
33+
rank_config: configs/experiments/sparse_rank_v3_fnspid_2018_2023.yaml
34+
spy_prices: data/cache/yfinance/spy_2017_2023.parquet
35+
36+
output: reports/experiments/live_settings_backtest_v1

docs/LIVE_FORWARD_MONITOR.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Live-Forward Monitoring
22

3+
> **Legacy specification:** This document describes the archived one-session
4+
> live experiment. Do not add its outcomes to the new 20-session evidence
5+
> record. See
6+
> [the horizon decision](experiments/2026-07-28-live-horizon-decision.md) and
7+
> [the 20-session redesign draft](design/LIVE_FORWARD_20_SESSION_DRAFT.md).
8+
39
## Purpose
410

511
The live-forward experiment tests whether two independently tuned ranking models can select stocks
@@ -179,3 +185,18 @@ The complete historical portfolio comparison is recorded in the
179185
[Ridge-XGBoost ensemble experiment](experiments/2026-07-27-ridge-xgb-rank-ensemble.md).
180186
It includes compounding, identical execution costs, SPY, risk metrics, moving-block confidence
181187
intervals, a shuffled-score placebo, and a final chronological-fold check.
188+
189+
## Live rebalancing ablation
190+
191+
`run_live_rebalance_ablation()` compares each model's actual daily frozen selections with two
192+
controls beginning from the same decision date:
193+
194+
- **Frozen names, buy and hold:** buy the first top 40 once and allow weights to drift.
195+
- **Frozen names, equal weight:** keep the first top 40 but apply the normal execution threshold
196+
around equal weights. This separates membership changes from weight drift.
197+
- **Daily selection:** use the top 40 that was genuinely frozen after each completed close.
198+
199+
All arms use the same cached adjusted closes, starting capital, spread/slippage assumption, fixed
200+
trade fee, and completed close-to-close sessions. The report intentionally emphasizes cumulative
201+
return, cost drag, turnover, trades, and drawdown; annualized statistics are not meaningful for a
202+
handful of live observations.

docs/PROJECT_STATUS.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,45 @@
11
# Project status, limitations, and improvement roadmap
22

3-
Status date: 2026-07-17
3+
Status date: 2026-07-29
44

55
## Executive summary
66

7+
The live research direction was revised after a matched zero-cost forecast-
8+
horizon comparison. Using the same frozen Ridge winner-selection design,
9+
20-session forecasts produced the strongest 2021-2023 point estimate and rank
10+
IC, while 10-session forecasts were a statistically indistinguishable
11+
challenger. The project therefore adopts 20 sessions as the leading live-test
12+
horizon, preserves 10 sessions as a mandatory paired challenger, and archives
13+
the one-session monitor as a legacy diagnostic. This is a research-design
14+
decision, not a validated-alpha claim. The paper-live runner is now implemented
15+
as `python -m src.cli run-live-forward-20d`; it creates daily overlapping
16+
forecast cohorts and separately tracked horizon-matched strategies. Five-day
17+
arms refit and rebalance every 5 sessions, ten-day arms every 10, and
18+
twenty-day arms every 20. The first refreshed v1 run was frozen on 2026-07-28
19+
under the earlier shared-20-session clock after live market and news refresh,
20+
strict FinBERT processing, and independent purged Optuna searches. It remains
21+
an immutable operational record and has no matured outcomes. The corrected v2
22+
history begins in `reports/live_forward_horizon_matched_v2/`. See
23+
[`experiments/2026-07-28-live-horizon-decision.md`](experiments/2026-07-28-live-horizon-decision.md)
24+
and
25+
[`design/LIVE_FORWARD_20_SESSION_DRAFT.md`](design/LIVE_FORWARD_20_SESSION_DRAFT.md).
26+
The first-run audit is
27+
[`experiments/2026-07-28-live-forward-20d-first-run.md`](experiments/2026-07-28-live-forward-20d-first-run.md).
28+
29+
A six-session July 20–28 ablation now audits the assumption that every
30+
rebalance should fully replace the top 40. Freezing the July 20 names improved
31+
Ridge by 3.93 percentage points net, while daily XGBoost replacement improved
32+
by only 0.53 point net after costs. Daily turnover was 615.6% for Ridge and
33+
661.6% for XGBoost including initial purchases, compared with approximately
34+
100% for frozen buy-and-hold portfolios. This supports eliminating daily full
35+
replacement but does not validate a permanent freeze or a buffer from only six
36+
sessions. The horizon-matched v2 full-refresh policy is therefore explicitly
37+
under audit; the next preregistered comparison is full refresh versus frozen
38+
holdings versus model-specific rank- and score-margin buffers. See
39+
[`experiments/2026-07-29-live-rebalancing-ablation.md`](experiments/2026-07-29-live-rebalancing-ablation.md).
40+
The controlled follow-up protocol is documented in
41+
[`design/REBALANCE_BUFFER_EXPERIMENT.md`](design/REBALANCE_BUFFER_EXPERIMENT.md).
42+
743
The newest repaired development run supports a sharper project direction: ML-based weekly stock
844
selection is the strongest current signal. After repairing suspicious price-history truncations,
945
the full weekly rank-40 portfolio returned 37.22% net CAGR from 2020-04-24 through 2023-06-30,

docs/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ decision explicitly says they are active.
3636
## Final Architecture And Design History
3737

3838
- [Final evidence-backed system proposal](design/FINAL_SYSTEM_PROPOSAL.md)
39+
- [Rebalance-buffer experiment draft](design/REBALANCE_BUFFER_EXPERIMENT.md)
3940
- [Next research direction: transparent selection, topics, LLM events, and pairs](design/NEXT_RESEARCH_DIRECTION.md)
4041
- [Sentiment and event overlay](design/SENTIMENT_EVENT_OVERLAY.md)
4142

@@ -45,6 +46,12 @@ decision explicitly says they are active.
4546

4647
## Experiment Logs
4748

49+
- [2026-07-29 - Live daily-selection versus frozen-holdings ablation](experiments/2026-07-29-live-rebalancing-ablation.md)
50+
- [2026-07-28 - First refreshed 20-session live-forward run](experiments/2026-07-28-live-forward-20d-first-run.md)
51+
- [2026-07-28 - Live horizon evidence and design decision](experiments/2026-07-28-live-horizon-decision.md)
52+
- [2026-07-28 - Matched 5/10/20-session forecast-horizon sweep](experiments/2026-07-28-forecast-horizon-sweep.md)
53+
- [2026-07-28 - Legacy live-settings backtest](experiments/2026-07-28-live-settings-backtest.md)
54+
- [2026-07-27 - Ridge-XGBoost rank ensemble](experiments/2026-07-27-ridge-xgb-rank-ensemble.md)
4855
- [2026-07-20 - Model-specific tuning and ensemble v10](experiments/2026-07-20-double-ensemble-v10.md)
4956
- [2026-07-20 - Qlib Alpha158 and LightGBM U.S. comparison](experiments/2026-07-20-qlib-alpha158-us.md)
5057
- [2026-07-17 - Survival-risk filter diagnostic for v8](experiments/2026-07-17-survival-filter-v8.md)

0 commit comments

Comments
 (0)