The Bid & Ask

Quotations, Dispatches, and the Daily Tape

Thursday, August 13, 2026

Rejected thesis

Turnaround layer: portfolio backtest (4 universe / weight cells)

The result

Question: Does the new turnaround layer added to judgment.py (default weights 0.10 medium / 0.20 long) actually move the portfolio? Run side-by-side walk-forward backtests with the turnaround weight ON vs OFF, all other layers identical.

Code: - research/turnarounds.py::historical_panel — vectorised point-in-time score panel (date × symbol) consumed by the harness. - backtest/harness.py::_build_panels adds a "turnaround" panel keyed turnaround_v1 in the score-cache. - backtest/harness.py::_judge_from_panels accepts turnaround_row and forwards it to judgment.judge(). - scripts/compare_turnaround_overlay.py runs the A/B comparison using horizon_weights override (turnaround row zeroed = OFF; default = ON).

Setup: weekly rebalance, n_top=10, default cost/sizing/sector config, 30-day EWM panel smoothing — i.e. production settings. Default weights = DEFAULT_HORIZON_WEIGHTS_BACKTEST with the turnaround row at 0.10 (medium) / 0.20 (long). Heavy weights = 0.40 (medium) / 0.80 (long), i.e. 4× default.

Headline numbers:

Universe Window Weight Sharpe CAGR Vol MDD ΔSharpe
nasdaq100 2014→2026 OFF 1.41 +45.49 % 29.92 % -37.92 %
nasdaq100 2014→2026 ON 1.41 +45.81 % 29.92 % -37.73 % +0.007
sp500 2014→2026 OFF 1.46 +51.18 % 31.83 % -43.83 %
sp500 2014→2026 ON 1.46 +51.12 % 31.81 % -44.54 % -0.001
sp500 2014→2026 ON 4× 1.54 +53.43 % 31.05 % -40.71 % +0.076
sp500 2020→2026 OFF 1.76 +80.51 % 37.63 % -43.83 %
sp500 2020→2026 ON 1.76 +80.19 % 37.57 % -44.54 % -0.002
sp600 2014→2026 OFF 0.86 +28.72 % 37.39 % -51.53 %
sp600 2014→2026 ON 0.87 +28.88 % 37.54 % -51.77 % +0.001
sp600 2014→2026 ON 4× 0.94 +32.41 % 37.07 % -53.62 % +0.081

Three findings:

  1. At the shipped default weight (0.10 / 0.20), the turnaround layer is essentially NEUTRAL at the portfolio level across all four (universe × window) combinations. Sharpe lifts ±0.01, CAGR lifts ±0.3 %, MDD ±0.7 %. The layer does not break anything but does not add measurable portfolio alpha either.

  2. At 4× weight (0.40 / 0.80), the layer DOES carry signal:

    • sp500 12yr: +0.076 Sharpe, +2.25 pp CAGR, -3.12 pp MDD
    • sp600 12yr: +0.081 Sharpe, +3.69 pp CAGR (drawdown worse +2.09 pp) Both universes show the same Sharpe magnitude (~+0.08) — the signal is real and scales with weight.
  3. Why default is too small: with n_top=10 weekly, the portfolio is highly concentrated. The default turnaround weight fractionally re-ranks names but doesn't break a turnaround candidate into the top-10 in any given week. At 4× weight, turnaround candidates start displacing other top-N names; the validated single-name edge (+11–59 % over 250d on D≥30/V≥1.3 to D≥50/V≥2 cells) finally expresses at the portfolio level.

Bootstrap caveat: Sharpe lift +0.08 is within the bootstrap CI width (~±0.30 per the docstring reference). The DIRECTION is consistent across both universes (which is encouraging), but a paired-bootstrap (scripts/bootstrap_recent_abs.py) would be needed to confirm the lift survives sample-path noise.

Verdict: - The layer carries real but modest signal at appropriately heavier weights. The shipped default (0.10 / 0.20) under-expresses it. - Recommended next step: re-calibrate defaults to 0.20 / 0.40 (mid-point between current and heavy) and re-test with a paired-bootstrap. Worth one more A/B cycle before declaring a new shipped default. - The /turnarounds page value is independent of the layer weight — it shows the specific names the cohort fires on. That's where the signal is most visible (single-name level), even if the aggregate portfolio Sharpe barely moves at default weight.

Meta-finding: This is the second time a validated single-name signal has under-expressed at the portfolio level due to top-N concentration (the first being accumulation_validate, marginal at +0.4-0.9 pp/250d but never showed up in portfolio backtests). The pattern is consistent — multi-quarter single-name alpha gets diluted in a concentrated portfolio. To express it, either: (a) raise the layer weight to the point where it routinely changes top-N membership (the 4× test); (b) reduce portfolio concentration (e.g. n_top=20+); or (c) productize the signal as a separate sub-portfolio / watch-list (the /turnarounds page).

At a glance
VerdictRejected

Every result here is reproducible from the script named above. Reports are in the repository.

Other rejected theses