Version: 2.0
Status: implemented paper-live research workflow
Primary horizon: 20 trading sessions
Prespecified challenger: 10 trading sessions
Diagnostic horizon: 5 trading sessions
Run one daily cycle with:
python -m src.cli run-live-forward-20d --as-of YYYY-MM-DDThe default manifest is
configs/experiments/live_forward_20d.yaml; artifacts are written beneath
reports/live_forward_horizon_matched_v2/. Each run creates separate Ridge and XGBoost
snapshots for 20, 10, and 5 sessions. Re-running the same decision date reuses
the frozen snapshot and refuses to overwrite it with a different model hash.
The default command is a live refresh, not a replay. Before modeling it:
- downloads adjusted daily OHLCV for every point-in-time member overlapping the training window, SPY, and the sector ETFs;
- requires the completed decision-date daily bar;
- updates effective-dated inferred sector history;
- fetches every enabled text source into the deduplicated raw store;
- processes the recent live window with entity linking, locally cached
ProsusAI/finbert, and event tagging; - maps each article to the first trading decision allowed by the 15:45 ET strategic cutoff;
- builds the eight established daily NLP features and combines them with the historical NLP feature record; and
- rebuilds technical and cross-sectional features from the refreshed market panel.
The run fails closed if the market close is unavailable, recent news is stale, the local FinBERT model cannot score every required headline, current sentiment coverage is below the frozen minimum, or required model selection fails. Lexical fallback is not permitted in this experiment.
On a scheduled refit, each model/horizon pair gets an independent search:
- five rolling temporal validation folds;
- a purge equal to that arm's 5-, 10-, or 20-session label horizon;
- fold-local rank-IC feature ordering;
- selection among the research manifest's feature counts;
- 30 Ridge Optuna trials and 25 XGBoost Optuna trials by default;
- top-40 excess return as the selection metric;
- fold-safe XGBoost early stopping; and
- a final refit on all matured labels after the search is frozen.
The selected parameters, features, validation table, model artifact, and hashes are saved under the dated model-version directory. Each arm is retuned only on its matching clock: every 5, 10, or 20 trading sessions.
Historical replay requires an explicit opt-out from refresh:
python -m src.cli run-live-forward-20d \
--offline-replay \
--as-of YYYY-MM-DD \
--output reports/live_forward_horizon_matched_v2_replayIf refresh succeeds but tuning is interrupted, resume the same decision date without fetching again:
python -m src.cli run-live-forward-20d \
--reuse-live-cache \
--as-of YYYY-MM-DDThe command verifies the cached audit date and requires the cached market, feature, processed-news, and audit files. It is not an offline historical replay.
The same-close tactical route is disabled by default. Enabling it requires both a verified broker cutoff and a timestamped intraday quote/fill file:
python -m src.cli run-live-forward-20d \
--as-of YYYY-MM-DD \
--enable-same-close \
--broker-cutoff HH:MM \
--intraday-quotes PATHWithout those inputs, qualifying tactical signals are recorded for next-open execution. This command is a research/paper-live runner; it does not submit broker orders.
The following choices are frozen for this version:
- 100% research exposure;
- horizon-matched strategy clocks: 5-session arms refit and rebalance every 5 sessions, 10-session arms every 10, and 20-session arms every 20;
- scheduled rebalances currently refresh the sector-capped top 40; a model-specific rank/score buffer is a shadow research candidate, not active;
- XGBoost starts immediately as a separately reported challenger; and
- a daily tactical sentiment overlay may buy or sell on any trading day when a news event passes the frozen significance gates; and
- the candidate universe uses point-in-time membership and liquidity, followed by a separately validated high-recall survival-risk guardrail.
The sentiment cutoff and latency contract is explicit in Section 6. The final promotion sample size remains a governance review item.
Test whether the project's machine-learning rankings select future stock winners over 20 trading sessions using only information available at each decision time.
The experiment separates two questions:
- Selection question: Do the selected top-40 stocks outperform the eligible universe over the matching 20-session interval?
- Investment question: Does the strategy outperform SPY and remain economically useful after realistic implementation costs?
The zero-cost selection question is primary because it matches the historical horizon sweep. Costs and SPY remain required secondary diagnostics.
The technical-plus-sentiment 20-session top-40 portfolio has positive mean active return relative to the contemporaneous equal-weight eligible universe.
The per-session active return of the 20-session forecasts exceeds that of the prespecified 10-session forecasts on the same decision dates.
- The 20-session portfolio outperforms SPY over matching intervals.
- Technical-plus-sentiment rankings outperform technical-only rankings.
- The observed result exceeds sector-capped shuffled-score placebos.
- Results are not concentrated in one sector, market regime, or rebalance calendar.
The 5-session arm is descriptive. It cannot replace the primary hypothesis or be promoted retrospectively because it happens to lead during one subperiod.
For a decision made after the close on date t:
- features and sentiment must be available by the declared cutoff on
t; - enter at the open of trading session
t+1; - hold through 20 complete open-to-open intervals;
- exit at the open of trading session
t+21; and - evaluate exactly
open(t+21) / open(t+1) - 1.
A new forecast is frozen after every valid trading-day close:
Monday forecast -> matures 20 sessions later
Tuesday forecast -> matures 20 sessions later
Wednesday forecast -> matures 20 sessions later
...
After the first 20-session warm-up, approximately one new 20-session outcome matures each trading day. These outcomes overlap and are not independent.
| Arm | Horizon | Features | Authority |
|---|---|---|---|
| Primary | 20 sessions | Technical plus timestamp-valid sentiment | Primary evidence |
| Horizon challenger | 10 sessions | Same feature contract | Required paired comparison |
| Short diagnostic | 5 sessions | Same feature contract | Regime diagnostic only |
| Sentiment ablation | 20 sessions | Technical only | Incremental-information test |
| Shuffled placebo | 20 sessions | Primary scores shuffled within date | Selection placebo |
| Universe baseline | 20 sessions | Equal-weight eligible universe | Primary benchmark |
| Market baseline | 20 sessions | SPY | Investment benchmark |
Ridge is the initial authoritative model because it is stable and inexpensive. XGBoost starts immediately as a separately labelled challenger. It receives the same dates, horizon, universe, features, execution timing, and evaluation contract as Ridge. It does not enter an ensemble or replace Ridge retrospectively. A nonconstant-score, reproducibility, and compute-budget failure is recorded as an XGBoost failure, not silently carried into the primary Ridge evidence.
Version 2 specification:
- Ridge rank baseline and separate XGBoost challenger;
- model parameters and feature count selected inside purged temporal folds;
- sector-neutral cross-sectional rank target;
- sector-capped top 40;
- equal weight at each strategic rebalance;
- maximum 25% of names from one sector;
- no daily hyperparameter retuning;
- refit and rebalance each 5-, 10-, or 20-session arm on its matching horizon;
- use only labels fully known before that refit decision;
- use each arm's most recently frozen model for intervening daily forecast snapshots;
- a refit creates a new model version and never rewrites earlier forecasts.
The production feature list, preprocessing statistics, training-data end date, code revision, and model hash must be frozen in every snapshot.
Using fewer names does not by itself reduce survivorship bias. Survivorship bias is controlled by reconstructing membership and eligibility as they were known on each historical or live date.
The base universe is:
- begin with point-in-time S&P 500 membership;
- rank members by trailing point-in-time liquidity;
- retain at most the 250 most liquid eligible names;
- apply the same rule independently on every training and decision date; and
- apply the survival-risk guardrail before selecting the final sector-capped top 40 with the winner-selection model.
This keeps the initial pool below the full S&P 500 while retaining the earlier evidence-backed liquid point-in-time top-250 strategy. The smart 120-name and slow 100-name return filters remain rejected because their activation gates failed and they underperformed the broader point-in-time Ridge selection.
- Apply membership and liquidity eligibility as of each feature date, including training history.
- Do not reuse the current membership list backward through history.
- Require at least 40 eligible, scored securities after all validity checks.
- Join sectors backward by effective date.
- Use only completed daily market bars.
- Missing sentiment means no observed sentiment, not a fabricated neutral observation, unless the frozen preprocessing contract explicitly defines that transformation.
Any feature-schema or timestamp-policy change starts a new experiment version.
The survival guardrail tries to avoid securities likely to leave the eligible index or become untradable. It reduces forward portfolio exposure to such events; it does not replace point-in-time membership as the control for historical survivorship bias.
"Never missing" an unknown future exit is not a valid statistical promise. The implementable objective is very high temporal out-of-sample recall with an explicit false-positive and coverage budget.
The guardrail has two layers:
- Hard public-information rules. Exclude or map a name when information already available at the decision time establishes an index deletion, delisting, bankruptcy, completed acquisition, trading suspension, ticker replacement, or other loss of tradability. Ticker changes and mergers must be handled as corporate-action lineage rather than automatically counted as investment failures.
- High-recall ML watchlist. Estimate the probability of exit or loss of eligibility over the next 20 and 63 sessions using only point-in-time features such as liquidity rank, market-cap rank when available, drawdown, volatility spike, volume collapse, missing-price pattern, sector-relative weakness, and public corporate-event state.
The hard-rule flag and ML flag combine with an OR rule for risk detection. The winner-selection model may score only names that pass the hard rules. The ML score begins as a shadow exclusion/penalty until its activation gates pass.
The prior exit-prediction model is not eligible for activation. Its best reported 126-session temporal-holdout setting captured only 29.78% of exits in the top 20% risk bucket and missed entire periods at narrower thresholds.
Before activating a new high-recall model:
- choose its threshold using training/validation data only;
- target at least 90% exit recall on temporal validation;
- require at least 80% recall in every reported calendar year;
- cap ML-only exclusions at 25% of the liquid top-250 universe;
- report precision, false negatives, excluded-name count, and coverage;
- compare hard-rules-only, ML, combined, and shuffled-exclusion arms;
- require no material degradation in winner-selection active return; and
- freeze the threshold before forward use.
If no threshold satisfies both the recall and exclusion-budget gates, the ML filter remains a warning/watchlist. The live pipeline must not lower the threshold retrospectively after observing which names exited.
For strategic 20-session forecasting:
- timezone: America/New_York;
- news publication cutoff: 15:45 ET on decision date
t; - minimum processing-latency buffer: 15 minutes;
- freeze the final snapshot no earlier than 16:00 ET and only after the completed official daily market bar is available;
- execution: next regular-session open; and
- news published after 15:45 ET enters the next trading day's snapshot.
Sentiment records must retain source publication time, ingestion time, ticker mapping, relevance, model version, and the cutoff decision that admitted or excluded the record.
The tactical overlay is a separate event-driven sleeve. It may change holdings on any trading day, including between scheduled 20-session strategic rebalances, when all of these gates pass:
- news age no more than four hours at the event decision time;
- scope classified as company, industry, or macro with at least 0.70 scope confidence;
- ticker relevance at least 0.70;
- sentiment confidence at least 0.70;
- absolute sentiment score at least 0.25;
- event is novel rather than a stale duplicate;
- at least two independent confirming records, except for a predeclared high-severity primary source such as an exchange notice, regulatory filing, court order, or issuer release;
- pre-signal absolute price move below 3%; and
- the ticker is in the point-in-time eligible universe.
Risk limits:
- maximum absolute tactical overlay per ticker: 5% of portfolio NAV;
- maximum combined ticker weight: 15%;
- maximum one-day tactical turnover: 10%;
- two-trading-day signal expiry;
- two-trading-day cooldown after an event trade; and
- five-trading-day pause or exit review for structural events such as regulation, litigation, earnings, or major product announcements.
A qualifying positive event may initiate or add to an eligible ticker. A qualifying negative event may reduce or fully exit an affected holding. Portfolio gross exposure remains 100%; capital added to an event position is funded pro rata from unaffected strategic holdings, and sale proceeds are redistributed pro rata across unaffected strategic holdings.
Every tactical event must also be evaluated as a separate counterfactual arm:
- strategic portfolio without tactical intervention;
- combined strategic-plus-tactical portfolio; and
- timestamp-shuffled or event-time placebo.
Tactical returns must not be credited to the primary 20-session model-selection hypothesis.
For every event, define:
available_at = max(source publication time,
ingestion completion time,
sentiment/event-model completion time)
Route the order using the first genuinely executable decision:
- Same-close route. Eligible only when
available_at <= 15:30 ET, the system freezes the order by 15:45 ET, timestamped intraday quotes are available, and the broker confirms that the selected closing-auction or market-on-close order remains acceptable. The realized fill, not the unconditionally copied daily close, is the entry or exit price. - Next-open route. Used when the event becomes available after 15:30 ET, processing or validation is incomplete, intraday quotes are unavailable, or the broker/order cutoff cannot be confirmed. The order is frozen after the event decision and evaluated from its actual next-open fill.
The same-close route uses a minimum 15-minute operational buffer between order freeze and the 16:00 close. A broker-specific cutoff earlier than 15:45 ET overrides this proposal. The pipeline fails over to next-open execution rather than assuming an unavailable same-close fill.
Same-close and next-open event returns must be reported separately because they have different information sets and execution prices. A daily OHLCV backtest cannot validate the same-close route; that route requires timestamped intraday quotes, order timestamps, and paper or broker fills.
Each daily forecast is evaluated as an isolated equal-weight top-40 cohort over its complete matching horizon. This is the primary statistical record.
For each decision date, retain:
- all eligible ticker scores;
- selected top-40 holdings and sectors;
- entry and exit prices;
- holding-level returns;
- top-40 mean return;
- eligible-universe mean return;
- SPY return;
- active return against both benchmarks; and
- cross-sectional rank IC.
The primary 20-session research portfolio remains 100% invested and rebalances every 20 sessions. For a fair complete-strategy comparison, the 5- and 10-session Ridge and XGBoost arms also freeze independent sector-capped top-40 portfolios and rebalance every 5 and 10 sessions respectively.
Between each arm's scheduled rebalances, its holdings drift without strategic trading. Only a qualifying, separately labelled tactical news event may alter the primary portfolio.
Daily overlapping forecasts remain part of the statistical scorecard, but they do not cause a full daily portfolio rebuild. This preserves fast evidence accumulation without reintroducing the archived one-day strategy's daily fresh top-40 turnover.
The July 20–28 legacy-live ablation compared six completed sessions of actual daily top-40 changes with the July 20 names held fixed. Ridge improved by 3.93 percentage points net when frozen, while XGBoost improved by 0.53 point net with daily replacement. Daily turnover including initial purchases was 615.6% for Ridge and 661.6% for XGBoost, versus approximately 100% for buy and hold.
This preliminary result supports the decision not to replace the strategic book daily. It does not establish that complete replacement at each 5-, 10-, or 20-session rebalance is optimal. The ablation covers one starting portfolio, six sessions, and the archived one-session model contract.
Before changing the v2 scheduled-rebalance policy, run a model- and horizon-specific comparison with identical scores, dates, sectors, execution, and costs:
- full top-40 refresh;
- frozen holdings;
- retain incumbents unless a challenger exceeds the exit candidate by a predeclared rank margin; and
- retain incumbents unless the score-percentile gap exceeds a predeclared margin.
Choose candidate margins using temporal development folds only. Freeze them before forward evaluation. Report retention, gross and net active return, turnover, trade count, drawdown, and the return contribution of replacements. Ridge and XGBoost may select different policies; neither may use the six observed July sessions to choose its threshold.
The buffer remains inactive until it reduces turnover materially without degrading untouched gross winner-selection evidence and its cost-adjusted return is no worse than full refresh under the same execution contract.
See
2026-07-29-live-rebalancing-ablation.md.
Report:
- zero-cost gross return;
- existing research-cost-model return;
- turnover and trade count;
- SPY and eligible-universe comparisons;
- drawdown and volatility;
- strategic-only return;
- tactical-only attribution; and
- combined strategic-plus-tactical return.
Report results for every matured forecast, but account for overlap:
- Newey-West standard errors with lag at least 20;
- moving-block bootstrap with blocks of at least 20 sessions;
- paired 20-minus-10 per-session active-return test;
- technical-plus-sentiment minus technical-only paired test;
- sector-capped shuffled-score placebo;
- rank IC, IC information ratio, and positive-IC fraction;
- active-return hit rate;
- performance by sector, volatility regime, and calendar vintage; and
- concentration of gains among individual dates and holdings.
Do not annualize a short sample without displaying the unannualized cumulative result and number of matured forecasts beside it.
Proposed gates for discussion:
Before performance interpretation:
- 20 consecutive valid forecast snapshots;
- no future-label or timestamp violation;
- reproducible scores from the frozen model artifact;
- complete entry, exit, universe, and benchmark records; and
- no unresolved constant-score or missing-universe failure.
After 60 matured 20-session forecasts:
- report coverage and data quality;
- report estimates with overlap-aware uncertainty;
- do not make an alpha or superiority claim.
After 252 matured forecasts:
- primary active-return point estimate must be positive;
- no single sector or calendar offset may explain the whole result;
- technical-plus-sentiment must not materially underperform technical-only;
- shuffled-score result must be reported; and
- 20-minus-10 paired comparison must be reported even if inconclusive.
Proposed minimum before changing the reference strategy:
- at least 504 matured daily forecasts;
- at least 24 non-overlapping 20-session evaluation blocks;
- positive primary active return with a 95% overlap-aware interval excluding zero;
- no material data-integrity exception;
- robustness across calendar vintages and major regimes;
- acceptable cost sensitivity; and
- a predeclared decision on whether 20 has beaten 10 or merely remains tied.
Failure to pass a gate does not authorize selecting a different horizon after seeing its result. Any replacement hypothesis requires a new versioned experiment.
After each completed market close:
- Finalize and validate the official daily bars.
- Ingest only sentiment published before the decision cutoff.
- Build point-in-time universe, sector, and liquidity eligibility; apply hard survival rules and record the shadow ML survival watchlist.
- Evaluate any 5-, 10-, and 20-session forecasts maturing that day.
- Append immutable holding-level and forecast-level outcomes.
- Apply overlap-aware cumulative statistics.
- Load the current frozen model version.
- Build features without using the current decision's future outcome.
- Freeze primary, challenger, ablation, and placebo inputs.
- For every arm whose 5-, 10-, or 20-session clock is due, refit Ridge or XGBoost, create a new model version, and freeze its top-40 portfolio.
- Evaluate tactical sentiment gates, calculate
available_at, and route each qualifying order to verified same-close or next-open execution. - Write hashes, manifests, coverage checks, and failure status.
- Publish the dashboard with legacy one-session results visibly separated.
If any required input is incomplete, fail closed and create a diagnostic record rather than a forecast.
The redesigned dashboard should show:
- clear
20-session primarystatus; - forecast date, entry date, expected maturity date, and actual maturity date;
- pending versus matured cohorts;
- 20-, 10-, and 5-session results without mixing their return intervals;
- technical-plus-sentiment and technical-only comparison;
- eligible-universe and SPY benchmarks;
- gross and cost-sensitive portfolio views;
- number of matured forecasts and approximate non-overlapping block count;
- HAC confidence interval and paired 20-minus-10 result;
- sentiment coverage and missing-data flags;
- survival hard-rule exclusions, ML watchlist score, and eligible-name count;
- strategic-only, tactical-only, and combined results;
- tactical-event reason, confidence, position change, and expiry;
- tactical availability time, route, order time, and realized fill;
- model version and next scheduled refit; and
- a separate legacy link for the archived one-session experiment.
The dashboard must not present overlapping daily outcomes as independent sample size.
reports/live_forward_horizon_matched_v2/
experiment_manifest.json
latest_run.json
dashboard.html
strategy_daily_returns.csv
model_versions/
MODEL/HORIZON/REFIT-DATE/
model.joblib
manifest.json
forecasts/
YYYY-MM-DD/
run_summary.json
strategic_rebalance_target.csv
survival_and_universe_context.csv
survival_guardrail_validation.json
tactical_orders.csv
MODEL_HORIZON/
manifest.json
eligible_scores.parquet
selected_holdings.csv
strategy_rebalance_target.csv
outcome.json
holding_returns.csv
matured_forecasts.csv
outcome.json, holding_returns.csv, and the aggregate
matured_forecasts.csv appear only after at least one cohort matures.
Counterfactual, ablation, cost, and overlap-aware statistical reports remain
required before performance promotion; version 1 does not fabricate them from
pending forecasts.
The immutable forecast key should include:
(experiment_version, model_version, arm, decision_date,
entry_date, maturity_date, horizon_sessions, benchmark)
Two implementation inputs still require confirmation before the same-close route can be enabled:
- Which broker or paper-execution venue supplies the closing-order cutoff and realized fill?
- Is the promotion gate of 504 matured forecasts and 24 non-overlapping blocks appropriate for the project's time constraints?
Without a verified venue and timestamped intraday data, tactical signals still run every day but use the next-open route.
The implementation does not silently answer these governance questions.