research: volatility-standardized residual momentum screen, failed

Strategy review (2026-09-08) and a frozen, paired signal screen of
residual 12-1 momentum divided by its formation-window residual
volatility. Across 73 non-overlapping windows the normalized signal's IC
is 0.0128 against 0.0145 for the matched control, below the frozen 0.03
floor, so the conditional portfolio replay was not run and nothing in
production changes.

Code-review fixes to the runner, before first commit: a missing OHLC
price stops the run instead of reading as 0; the snapshot connection is
closed explicitly (on Windows it otherwise kept the file locked); the
paired-coverage check raises instead of asserting, so python -O cannot
strip it; and source files are recorded as git blob ids with line
endings normalized, so the report can be checked against history. The
report was regenerated with the fixed runner; its screen section is
byte-identical to the original run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
dennisthiessenandClaude Opus 5.5 committed 2026-10-10 18:45:24 +02:00
1 parent 70d17c1491
commit 7a9b3ad38b
9 files changed
+2659

No files matched your search

+8
View File
@@ -5,6 +5,13 @@ stop re-litigating settled questions: **if a row says "rejected", the experiment
was run and the data said no.** Detail lives in the linked docs and in
`reports/*.json` (all committed).
[Strategy research review (2026-09-08)](strategy-review-2026-09-08.md) compares
the existing evidence with published research and specifies a proposed
volatility-standardized residual-momentum experiment. The subsequent
[normalization screen failed](residual-normalization-findings.md): IC 0.0128
versus 0.0145 for matched control, below the frozen 0.03 floor. No portfolio
replay or production change followed.
**The one-line summary of the whole platform:** it is a **long-only
cross-sectional momentum book** — buy the top quintile by beta-adjusted 12-1
momentum, tilt toward higher volatility, hold ≤ 10 names, cut at 1.5× ATR, then
@@ -48,6 +55,7 @@ score, Structural S/R, the Gate Target Ladder, sentiment, fundamentals) is
| 11 | **FIP path-smoothness** as tie-breaker/filter | Non-monotonic within the qualified set; thinning the entry stream costs more compounding than the tilt returns | **Rejected as a filter** — but see §4, it's the strongest raw signal we've measured | — |
| 12 | **Fixed take-profit sweep** (R-multiples) | No interior optimum ever found — the best TP is "no TP" | **Rejected.** Momentum's edge lives in the right tail | `backtest_service.py:450` |
| 13 | **Sector-residual 12-1** (`mom_12_1_sector_resid` / sector demean) as replacement for market residual | Short-window IC/A/B looked knife-edge green; deep repaired + **liquid-1500** retest: weeks 83, mild +IC **0.027** / t 1.69, **below iron bar 0.03** (FAIL). Demean already weaker | **Rejected / closed.** Keep production market residual. Do not resurrect without a new pre-registered protocol | [sector-residual-momentum.md](sector-residual-momentum.md) · `sector-resid-deep-20260719-113319.json` · history-depth supersession note |
| 14 | **Volatility-standardized market-residual 12-1** | 73 non-overlapping windows: IC **0.01284** vs matched control **0.01446**, below frozen 0.03 floor. Paired delta -0.00162, 95% interval [-0.00496, +0.00156] | **Closed negative at signal screen.** No demonstrated improvement; difference is inconclusive. Conditional portfolio stage not run | [Findings](residual-normalization-findings.md) · [Protocol](residual-normalization.md) · `residual-normalization-20260908.json` |
| 15 | **Breakeven stop** (stop to entry once the trade is +1R / +2R, on top of the 3× ATR trail) | `be_1r` fails the frozen rule: train worse on Sharpe, CAGR and drawdown, test better on return but worse on drawdown. `be_2r` changed 2 of 480 trades, because the trail already reaches entry near +2R. A post-hoc arm that gates re-entry after breakeven exits does better but still fails on drawdown | **Rejected / closed for this snapshot.** Keep the production exit. Reopen only with a new pre-registered test on unseen data | [breakeven-stop.md](breakeven-stop.md) · `backtest-20261009-breakeven-stop.json` |
---
@@ -0,0 +1,76 @@
# Residual normalization — findings
**2026-09-08: closed negative at the signal screen. Keep production unchanged.**
The proposed normalization did not meet the rule frozen in
[the experiment protocol](residual-normalization.md), and its point estimates
did not improve on the current momentum signal. No portfolio replay was run:
the protocol requires a passing signal screen before that stage.
The candidate used the production SPY-adjusted 12-1 return sum divided by sample
standard deviation of the same 231 daily adjusted returns. All comparisons used
identical as-of dates and 30-SPY-session forward-return endpoints. No new factor,
lookback, volatility weight, sizing rule, or exit was tested.
| Era | Non-overlapping windows | Current signal, matched coverage | Normalized signal | Difference |
|---|---:|---:|---:|---:|
| Full period | 73 | 0.01446 | 0.01284 | -0.00162 |
| Before 2021 | 30 | 0.00776 | 0.00496 | -0.00280 |
| From 2021 | 42 | 0.02377 | 0.02259 | -0.00118 |
Values are mean **Spearman information coefficients (IC)**: how well signal
ordering agrees with subsequent stock returns. They are neither Sharpe ratios
nor trading returns. Higher is better for this positive-momentum hypothesis.
The candidate needed full-period IC >= 0.03, at least 12 windows, and positive
mean IC in both eras. It passed the count and era-sign conditions but failed the
0.03 threshold. The rule was evaluated before rounding.
The paired normalized-minus-current IC difference has a 95% moving-block interval
of **[-0.00496, +0.00156]**. This spans zero: the study does not establish that
normalization is worse, and it supplies no convincing improvement evidence.
The mean top-minus-bottom-quintile 30-session return spread also fell, from
**0.967% to 0.751%**. That is an unconditional diagnostic, not the P&L of the
long-only, gated, capital-constrained production book.
**Coverage and checks**
- The snapshot completion manifest matches 4,650 registry tickers, 5,081,073
OHLCV rows, and 4,144 research-only names. The experiment used the remaining
506 production-universe registry names, averaging 485.4 paired stocks per
retained window.
- Actual retained as-of dates run from **2017-01-06 to 2026-05-01**; the final
forward endpoint is **2026-06-15**. Later dates cannot necessarily add another
complete non-overlapping 30-session window. SPY history starts in January
2016, so a full one-year signal cannot start in July 2016.
- One retained window crosses the 2021 boundary and belongs to the full-period
result only, explaining why the era counts sum to 72 rather than 73.
- The unrestricted production control had **35,441** retained stock/date cells;
paired control and treatment each had **35,433**. Its mean IC was 0.01447,
essentially the same as paired control. Coverage restriction did not account
for the observed result.
- Numerator parity was checked against the production implementation on each
eligible evaluated cell. The snapshot and frozen source hashes were unchanged
across the run. SQLite was opened read-only.
- Twelve targeted tests passed, including numerical invariance, production
parity, lookahead exclusion, zero-variance handling, missing-data pairing,
calendar overlap, and an end-to-end screen check. Ruff passed for the runner
and its tests. The existing Python runtime was usable outside the filesystem
sandbox; no environment replacement was needed.
**Interpretation and decision**
My proposed mechanism was plausible but unsupported by this experiment. Do not
add normalization to production or search denominator variants on this history.
This ends this specific experiment, not all possible residual-momentum research.
The current signal also falls below 0.03 on this particular long-history screen.
That cautions against treating its predictive evidence as universally strong;
it does not by itself measure or invalidate the complete production book. The
unconditional universe, true session spacing, dates, and return objective differ
from earlier setup/portfolio studies. Historical constituents are still projected
backward, this history has already been inspected, and the bootstrap intervals
remain exploratory. No new CAGR, drawdown, cost-stress, or live result is claimed.
**Artifacts:** [machine-readable results](../../reports/residual-normalization-20260908.json),
[generated tables](../../reports/residual-normalization-20260908.md),
[runner](../../scripts/run_residual_normalization.py), and
[tests](../../tests/unit/test_residual_normalization.py).
+87
View File
@@ -0,0 +1,87 @@
# Residual normalization experiment
Protocol frozen 2026-09-08 before computing either signal's results. Implements
the first candidate in [the strategy review](strategy-review-2026-09-08.md).
This is offline research; it does not change production selection or settings.
**Question:** does dividing the current SPY-adjusted 12-1 score by its own daily
residual standard deviation improve predictive ordering and portfolio economics?
**Signal:** use the existing 252-bar lookback, skip 21 bars, estimate SPY beta
with centered covariance/variance, and retain the fitted intercept's contribution
exactly as production does. The treatment divides the production sum by sample
standard deviation (`ddof=1`) over the same 231 adjusted daily returns. No
denominator clipping, alternate window, factor addition, or weight sweep.
An exactly market-tracking series can leave machine-rounding residuals; sample
standard deviation <= 64 machine epsilons times `max(1, max(abs(stock_returns)))`
is treated as numerical zero, not divided into the signal.
**Data:** the completed `backtest_snapshots/research.sqlite`, restricted to
tickers absent from `research_rank_only` (506 production-universe registry names).
Nominal as-of start 2016-07-01; end bounded by actual SPY history and availability
of a 30-SPY-session forward endpoint. This snapshot starts SPY in January 2016,
so a fully aligned one-year signal cannot start in July 2016. Report actual
signal and forward-return dates. All history is previously inspected and
survivorship-biased; none is labeled fresh out-of-sample.
Require exactly 231 return pairs over consecutive benchmark sessions in the
formation window, positive finite prices, and nonzero residual variance for
both paired arms. A separate unrestricted production control retains production's
minimum-100-pair behavior. Report the removed population. Forward returns use
the same exact stock endpoint dates 30 SPY sessions apart, without filling absent
prices, without subtracting SPY, and without changing the stored return basis.
**Stage 1 — signal screen:** consider the last SPY session of each ISO week.
Keep a date with at least 20 common eligible stocks only if its as-of session is
at or after the preceding retained forward endpoint. Thus successive intervals
share at most an endpoint, not a return, including around holidays and year ends.
Use those dates for all three arms. Report per-window Spearman IC, top-minus-bottom
quintile forward-return spread, cross-sectional coverage, and paired IC deltas.
Era summaries use windows ending before 2021-01-01 and windows starting on/after
2021-01-01; boundary-crossing windows remain in full-period results only.
The screen passes only if the treatment has full-period mean IC >= 0.03, at
least 12 non-overlapping full-period windows, and strictly positive mean IC in
both eras. Mean IC and signs are checked before rounding. No additional paired
delta threshold is silently added. Report uncertainty whether the screen passes
or fails. For IC intervals use paired moving blocks of four retained windows,
5,000 replicates, NumPy PCG64 seed 20260908. These are exploratory intervals;
non-overlap alone does not establish statistical independence.
**Stage 2 — conditional portfolio comparison:** run only if stage 1 passes.
Replay daily, with unrestricted control, common-coverage control, and normalized
treatment; rank the full available production universe once per ticker/date.
Use unchanged 80/20 momentum/6-month-volatility ordering, top-20% momentum gate,
GTL qualification, capacity 15, 1% fixed-fractional risk, 20% notional cap,
cash constraints, 1.5 ATR initial stop, 3 ATR trail, 30-session hold, no
take-profit, and independent gate-reset histories. Keep the volatility rank
universe and its values identical between the paired arms. Use the snapshot's
non-momentum configuration and assert it agrees with the documented production
floors before running. Keep all non-qualified scans for gate-reset observations.
Use close fills as the historical execution scenario and costs 10 and 20 bps per
side. On a common calendar report paired returns, trade overlap, exposure,
turnover, risk per fill, and winning/losing contributions. Advancement requires
positive paired Sharpe improvement, no lower point-estimate CAGR, no deeper
point-estimate max drawdown, and a positive lower 95% bootstrap bound for Sharpe
improvement at both costs and both 21-/63-session block lengths. Use 5,000
paired replicates and seed 20260908. Bootstrap the realized return streams;
this does not model counterfactual changes in decisions along resampled prices.
A failed screen ends this experiment. A portfolio pass would justify forward
research, not automatic promotion. No historical variation or rule relaxation
will be chosen after seeing this result. Point-in-time membership and actual
execution evidence remain separate requirements for deployment.
**Reproduce stage 1:**
```powershell
.venv\Scripts\python.exe scripts/run_residual_normalization.py
.venv\Scripts\python.exe -m pytest tests/unit/test_residual_normalization.py -q
```
The runner reads SQLite in read-only mode, validates the manifest, records
snapshot/protocol/code hashes and package versions, checks production-numerator
parity on the evaluated cells, and verifies the snapshot hash again before
writing completed results. Errors must not become skipped tickers or partial
successful reports.
@@ -0,0 +1,80 @@
# Strategy research review — 2026-09-08
**Subsequent experiment:** the first candidate below was implemented and
[failed the frozen signal screen](residual-normalization-findings.md) on
2026-09-08. Its IC was 0.0128 versus 0.0145 for matched control, below the 0.03
floor. No portfolio replay or production change followed. The original research
rationale below is preserved as the proposal made before seeing that result.
**Recommendation:** make volatility-standardized market-residual momentum the next small research experiment. Keep the current strategy as the control. Before treating any winner as deployable, establish its result on historical membership and a forward record with observable execution. A separate cross-asset trend strategy is the more substantial diversification candidate.
**Status:** research synthesis and proposed experiment, not a new backtest or a promotion. Objective assumed: better net returns relative to drawdown. Historical figures below were checked against the saved JSON reports; production code and settings were not changed. No replacement has been demonstrated superior by this review.
**What the existing evidence actually supports**
The current code defaults are a long-only, top-quintile market-residual 12-1 momentum book, ordered by 80% momentum percentile and 20% six-month volatility percentile. Capacity is 15, with 1% account risk per entry subject to cash and notional limits, a 1.5 ATR initial stop, a 3 ATR trail, a 30-session maximum hold, and gate-reset re-entry. Capacity and near-close scheduling have already shipped; some introductory research text still describes capacity 10 or scheduling as open work. These are code defaults and documented implementation, not an inspection of live Admin settings. See [momentum implementation](../../app/services/momentum_service.py), [capacity findings](portfolio-capacity-bracket-findings.md), and [execution decision](execution-recovery.md).
| Existing experiment | Recorded result | Implication |
|---|---|---|
| Production universe, entries from July 2022 | Sharpe 1.32; CAGR 31.8%; max drawdown 18.9% | Positive on that particular replay. |
| Same package, production plus liquid breadth | Sharpe 0.14; CAGR -1.8%; max drawdown 55.3% | Expanding the tradable universe under the existing rules already failed. |
| Production universe, entries from July 2016 | Sharpe 0.88; CAGR 16.7%; max drawdown 24.4% | Longer-history evidence is appreciably weaker. |
| Deep history plus liquid breadth | Sharpe -0.06; CAGR -7.0%; max drawdown 73.9% | More history did not rescue breadth. |
| Restore 85 removed constituents, September sensitivity study | Sharpe 2.09 → 1.57; CAGR 51.6% → 38.1% | Cross-sectional universe composition materially changes results. |
| Close versus next-open execution, July study | Sharpe 1.77 → 1.20; CAGR 48.3% → 30.0% | Execution assumptions can dominate a signal improvement. |
The first four rows come from one [universe/horizon matrix](../../reports/prod-book-universe-horizon-20260719-140737.json), using the then-current capacity of 10. They are not new measurements of today's capacity-15 book. The last two rows come from separate [survivorship](survivorship-audit.md) and [execution](../../reports/execution_recovery_matrix.json) studies. Do not pool these as one baseline: cadence, history, universe, configuration, and end dates differ. Today's constituents projected backward remain a limitation of the deep-history study. Restoring removals is also not point-in-time membership, and the 13.5 percentage-point CAGR difference is not an estimate of survivorship bias. The sensitivity study attributes the portfolio change to universe recomposition, not positions in the failed banks.
**First candidate: standardize the existing residual signal**
The code estimates a stock's beta to SPY over the 12-1 formation window and sums daily stock returns minus beta times SPY returns. It deliberately does not subtract a fitted intercept. It also does not divide this score by residual volatility. That is a legitimate practical approximation; the name alone does not make it a replication of the academic strategy.
Blitz, Huij and Martens estimate market, size, and value exposures using 36 monthly observations, then standardize the 12-1 residual return by its volatility. Their main portfolios are monthly long-short decile portfolios. They explicitly omit the estimated alpha from the momentum calculation. Their reported improvement therefore does not establish an improvement for this concentrated long-only ATR strategy. [Residual Momentum, accepted manuscript, pp. 10–12](https://repub.eur.nl/pub/22252/ResidualMomentum-2011.pdf)
My proposed first experiment isolates standardization using the existing SPY model:
```text
u[d] = stock_return[d] - beta * SPY_return[d]
control_score = sum(u[d])
candidate_score = control_score / sample_standard_deviation(u[d])
```
Use the same 231 daily return pairs from the existing 252-session lookback with the final 21 sessions skipped. Require a complete, aligned window and positive residual standard deviation in both arms for the paired signal comparison. Retain a separate unrestricted control to quantify any coverage effect. Do not add a fitted-intercept subtraction, new lookback, or new volatility weight.
**Hypothesis, not finding:** measuring a stock-specific rise relative to its own noise may improve ordering and reduce sensitivity to unusually volatile names. This changes the signal itself; it is distinct from the previously rejected inverse-volatility position sizing or standalone low-volatility ranking. Nevertheless, those negative results and the strategy's dependence on a few large winners lower confidence: standardization could also remove valuable winners. The research search found no recorded direct test of this exact normalization in the current scripts and research documents.
Keep the existing 80/20 blend in the first portfolio comparison, replacing only its momentum input and the momentum gate input. Removing the high-volatility tilt simultaneously would obscure the mechanism. If this narrow test fails, record the failure without searching over denominator windows, powers, or clipping rules on the same history.
**Second candidate: a fuller factor-residual model**
A later, separately specified candidate could remove market, size, and value exposures with a longer estimation window. This addresses factor contamination that single-market beta adjustment leaves behind. It is distinct from the rejected sector-residual experiment, but that rejection still argues for modest expectations. Model estimation, monthly versus daily updates, coverage, and the existing daily gate-reset behavior would all need explicit treatment.
The factor data are available from the [Kenneth French Data Library](https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html), which states that historical returns are reconstructed during updates and can change. Freeze the source vintage and record when factors were actually available. A factor observation dated month-end is not automatically usable for a trade at that same close. Current downloadable history alone does not establish historical publication availability. A monthly paper replication and a daily deployable adaptation must be labeled separately.
Prioritize the simpler normalization experiment because it isolates one mechanism without adding a factor-data dependency. Neither candidate fixes historical universe membership or corporate-action errors. The existing [total-return A/B](tr-total-return-ab.md) remains unresolved; silently changing dividend treatment in the same comparison would introduce another cause for any difference.
**Larger alternative: an independent cross-asset trend allocation**
Research a separate portfolio of liquid equity-index, bond, commodity, and currency exposures, taking positions according to each market's own past return. The purpose is to diversify the source of portfolio returns beyond stock selection. This would be a new strategy and data pipeline, with its own capital allocation and execution model.
Hurst, Ooi and Pedersen study time-series momentum across futures and forwards and extend the historical examination back to 1880. Their results motivate investigating the strategy family; they do not supply an expected return or optimal allocation for this platform. [A Century of Evidence on Trend-Following Investing](https://www.aqr.com/insights/research/journal-article/a-century-of-evidence-on-trend-following-investing)
A long-only ETF/cash version may be easier to operate, but cannot reproduce the short positions, leverage, or historical instrument coverage of the published strategy. Start with one fixed specification and evaluate both its standalone economics and its contribution to a fixed combined portfolio after costs. Diversification must be measured, especially during equity losses; shared use of the word momentum does not determine correlation. This is a larger effort than the first candidate and remains untested here.
**What to leave closed**
The local record does not support another round of take-profit exits, SPY moving-average entry bans, gap-up caps, high-volatility breadth expansion, earnings-surprise gates, fundamental score weights, or ATR/hold-period sweeps on the same snapshots. See the [experiment index](README.md), [earnings findings](earnings-gap-and-sue.md), and [fundamental-weight findings](fundamentals-weight-backtest.md). Repeating them would add selection opportunities rather than independent evidence.
Crash-management papers also need careful translation. Daniel, Jagannathan and Kim identify a short-side mechanism in long-short momentum crashes. That is not direct evidence for adding an equity-regime veto to this long-only strategy. [Tail Risk in Momentum Strategy Returns](https://www.nber.org/papers/w18169)
**Proposed evaluation contract for the first candidate**
1. **Freeze the inputs and trial first.** Record snapshot and code hashes, symbol/date eligibility, cash treatment, costs, signal formula, and report boundaries. Verify the research snapshot completion manifest against actual counts. Treat the previously inspected 2016–2026 history as exploratory evidence, including periods historically labeled validation or test.
2. **Screen the signal on identical cells.** Compare the candidate and current score against the same 30-session forward returns. Select actual non-overlapping session intervals; report window count, mean Spearman IC, uncertainty, quintile spreads, and pre-/post-2021 signs. The existing IC >= 0.03, consistent-sign, >= 12-window rule is a screening requirement. The harness's `reliable` flag counts windows; it is not a statistical significance result. Measure paired IC differences as well as each signal's absolute IC.
3. **Replay the whole policy if the screen passes.** Use daily scans, capacity 15, unchanged cash/notional limits, stops, trail, hold, costs, and 80/20 weights. Recompute qualifications and gate-reset histories separately for each arm. Reusing the incumbent's qualified candidates or lock histories would miss decisions caused by the treatment.
4. **Grade portfolio economics.** For the default objective, propose a conservative advancement rule: positive paired Sharpe improvement, no lower point-estimate CAGR, and no deeper point-estimate maximum drawdown, with a paired 95% interval for Sharpe improvement above zero. Report 21- and 63-session block-bootstrap intervals, with seed and replicate count frozen before execution. Require the conclusion to survive both block lengths and a doubled-cost stress from 10 to 20 bps per side. These are proposed research criteria, not previously passed tests. Failure or inadequate precision means no promotion; do not relax the rule after seeing results.
5. **Check why the book changed.** Report turnover, exposure, entry overlap, risk per fill, best/worst contributors, and foregone winners. Pair daily portfolio returns on a common calendar. Overlapping start-date paths are sensitivity checks, not independent observations. Resampling realized returns does not simulate how different price histories would change future decisions.
6. **Establish deployability separately.** Use dated constituent membership, symbol identity and delisting treatment, and a documented price/return basis. For execution, retain the actual scan inputs, order timestamps, decision prices, and fills. The current shadow book records near-close setup prices; those are simulated entries, not broker execution evidence. Daily bars cannot reconstruct the exact partial bar that a near-close scan saw. Historical close/stale-close results are scenarios, not a guaranteed bracket for future live Sharpe.
7. **Reserve prospective evidence.** Choose a fixed forward evaluation schedule before collecting the comparison, with separate paper books. Require enough calendar history to assess uncertainty; a small trade count or an early favorable month is not a promotion criterion. Maintain the full experiment ledger. Deflated Sharpe can address multiple selection and non-normality when the relevant trial information is available, but cannot repair lookahead or invent untouched data. [Bailey and López de Prado, The Deflated Sharpe Ratio](https://www.davidhbailey.com/dhbpapers/deflated-sharpe.pdf)
**Deliverable and limits:** this review supplies a prioritized candidate and a concrete evaluation design. It verifies existing evidence rather than reporting a newly discovered edge. No new replay was executed; the checked-in virtual environment points to an unavailable Python interpreter in this session. The research recommendation does not depend on treating that missing run as a result.