The Bid & Ask

Quotations, Dispatches, and the Daily Tape

Thursday, August 13, 2026

Rejected thesis

Per-layer weight calibration sweep

The result

Question: For each (layer, horizon) cell in judgment.DEFAULT_HORIZON_WEIGHTS_BACKTEST, what weight maximises portfolio Sharpe? Are the shipped defaults optimal?

Methodology: Three stages:

  1. Marginal sweepscripts/sweep_layer_weights.py runs 8 layers × 3 horizons × 7 weights = 168 backtests. For each cell, all other weights held at default. Reveals each cell's marginal Sharpe response curve. Misses interactions by construction.
  2. Joint refinement — stack the top marginal winners into candidate weight matrices, test, pick the best.
  3. Anti-overfit validation — winner re-tested on nasdaq100 and sp600 (in-sample + OOS 2020+).

Setup: weekly rebalance, n_top=10, full production settings. sp500 2014-2026 = primary universe.

Stage 1 — marginal sweep (top winners by ΔSharpe vs baseline)

Layer Horizon Default w Best w ΔSharpe
sentiment medium 0.15 0.35 +0.136
sentiment long 0.15 0.60 +0.133
sentiment short 0.15 0.60 +0.116
sector long 0.65 0.20 +0.100
sector medium 0.55 0.20 +0.092
sector short 0.45 0.20 +0.086
technical long 0.10 0.60 +0.079
turnaround short 0.00 0.60 +0.070
technical short 0.10 0.35 +0.064
... ... ... ... ...

Sentiment dominates; sector is meaningfully over-weighted at every horizon; news shows zero impact (as expected — news layer is zero in backtest config because yfinance can't backdate it).

Stage 2 — joint candidates DO NOT compose

Naive stacking of marginal argmaxes is catastrophic:

Candidate ΔSharpe
A: sentiment 0.35 all-horizons +0.098
B: sector 0.20 all -0.019
C: A + B (sentiment + sector cut) -0.338
D: C + technical 0.30 -0.570
E: D + turnaround/insider bumps -0.460
F: full argmax stack -0.746
G: conservative tilt -0.236

Only candidate A (sentiment bump alone) beats baseline. Every other change either is null or actively destroys performance. Cutting sector — which the marginal sweep said should lift Sharpe by +0.10 — actually loses -0.34 when combined with the sentiment bump. The sector and sentiment layers carry overlapping information once both are at higher weights.

Stage 3 — refinement found a better magnitude

Sweeping the sentiment bump magnitude:

Sentiment weight (all-horizons) Sharpe ΔSharpe
0.15 (default) 1.461
0.20 1.571 +0.109
0.25 1.582 +0.120
0.30 1.570 +0.109
0.35 1.560 +0.098
0.40 1.462 +0.000
0.50 1.522 +0.060
0.70 1.299 -0.162

Optimum is 0.25, not 0.35. Response is non-monotonic — 0.40 breaks the lift, 0.70 destroys it. Per-horizon variation (0.30/0.35/0.45 etc) does no better than the uniform 0.25 setting.

Anti-overfit validation (sentiment 0.25 vs baseline)

Cell ΔSharpe ΔCAGR ΔMDD
sp500 12yr (in-sample) +0.120 +3.70 % +4.65 %
sp500 OOS 2020+ +0.122 +4.60 % +4.65 %
nasdaq100 12yr +0.008 -0.83 % -5.23 %
nasdaq100 OOS 2020+ +0.084 +2.99 % -5.23 %
sp600 12yr +0.009 -0.08 % -1.70 %
sp600 OOS 2020+ -0.036 -2.61 % -1.70 %

5/6 cells positive. In-sample +0.120 ≈ OOS +0.122 on sp500 — exceptional consistency. Nasdaq100/sp600 marginal-to-flat; no catastrophic regression on any cell.

Decision

Bump DEFAULT_HORIZON_WEIGHTS_BACKTEST sentiment row from 0.15/0.15/0.15 → 0.25/0.25/0.25. Live DEFAULT_HORIZON_WEIGHTS unchanged — live includes fundamental/news (which work live but not in backtest) so the calibration doesn't transfer cleanly. Live recalibration is a separate workstream that needs a different evaluation framework.

Two important methodology notes

  1. Marginal sweeps lie about joint optima. Stacking the 24-cell argmaxes produced the WORST Sharpe (-0.746) of any candidate tested. Per-cell optima don't compose because layers carry overlapping information that re-weights perturb non-linearly. Coordinate descent (or full Bayesian opt) is the right tool; marginal scan is a first pass to identify candidates.
  2. Bootstrap CIs are wide. +0.12 Sharpe is comfortably outside the cross-section noise of single backtests but still within the ~±0.30 paired-bootstrap CI for sample-path variability per the harness docstring. Treat as "probable improvement, not certain". scripts/bootstrap_recent_abs.py could confirm.

Reports written: - research/layer_weight_sweep_sp500_12yr.json (168-cell raw output)

Meta-finding: The shipped backtest defaults had sentiment too low and sector too high in isolation — but cutting sector breaks the ensemble. The robust win is a single-layer tweak (sentiment ↑) with everything else left alone. Future weight recalibration should use coordinate descent or Bayesian optimization rather than naive argmax stacking.

At a glance
VerdictRejected

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

Other rejected theses