C1 — Go-live leverage diff: cap gross exposure, not the scalar
Date: 2026-08-01
Status: PRE-REGISTERED, BINDING. Committed BEFORE any twin backtest runs
(the runner, scripts/run_c1_twins.sh, mechanically refuses to execute if this
memo is absent). No file under results/diagnostics/c1_twins/ exists at
registration time.
Source: quant panel 2026-08-01, item C1 (CRO; 3/3 survive) + finding 1
(research/2026-08-01_quant_panel_suggestions.md).
Scope: amends WHICH KNOB the pre-specified December go-live config diff
turns. It does NOT touch the go-live gate's criteria, dates, or thresholds
(go_live: in config/settings.yaml, keystone-pinned, unchanged), and no
production config VALUE changes at registration — the new engine mode defaults
to today's behavior byte-identically.
1. The category error, stated precisely
Plain language first: "gross exposure" is how many dollars of stock the book holds per dollar of equity (1.0x = fully invested, no borrowing). The "vol- target scalar" is a multiplier the engine applies to the book to steer its volatility to the 12% target.
risk.max_leverage does not cap gross exposure. It caps the scalar:
# portfolio/risk.py::vol_target_scalar
scalar = target_vol / realized_vol
return min(scalar, max_leverage) # <- max_leverage caps THIS
The final book, built in the shared construction path
(portfolio/construct.py::build_target_weights, used byte-identically by the
backtest engine and the live executor), is:
final_gross = kelly_composite_gross × min(target_vol / realized_vol, max_leverage)
where kelly_composite_gross is the Kelly-scaled, limits-applied book's gross
before vol-targeting. The panel's verified live decomposition (2026-08-01):
gross 0.419 = kelly_gross 0.140 × vol_scalar 2.996
The pre-specified go-live diff (settings.yaml go_live: comment block:
risk.max_leverage 3.0 -> 1.0) therefore clamps the SCALAR at 1.0, and the
live book becomes:
- exposure ≈ 0.14x (
0.140 × min(2.996, 1.0) = 0.140) — not the ~1.0x the diff's own rationale reasons about; - volatility ≈ 4% annualized (
0.140/0.419 × 12% ≈ 4.0%) — one third of the validated 12% target; - a configuration no backtest ever validated (every validated run — the
byte-identical guard, CPCV both modes, all walk-forwards — ran
max_leverage: 3.0); - zero margin interest saved: at 0.419x gross the account borrows nothing. There is no borrowed dollar for the cap to save interest on.
The literal diff's own written rationale is exposure-denominated ("Reg-T caps overnight gross at 2.0x", "Alpaca margin ~6.25%/yr makes borrowing edge-negative", "the economic ceiling is ~1.0x") — its intent is "never hold more than 1.0x of equity in stock", i.e. a bound on GROSS, applied to the wrong knob. Implementation contradicts its own registered intent — the ratified implementation-matches-intent amendment class (panel §2: amendments land only "where implementation demonstrably contradicts its own registered intent"; the rule has never consumed data — no go-live flip has occurred).
2. The amendment: "no borrowed dollar"
Cap FINAL GROSS at 1.0x. Effective scalar cap:
scalar_cap = min(max_leverage, target_gross_cap / kelly_gross)
with target_gross_cap = risk.max_gross_exposure = 1.0 and kelly_gross the
pre-scalar Kelly-composite gross (measured after position/sector limits — the
exact book the scalar multiplies, so the cap lands on the FINAL book exactly).
Equivalently: final_gross = min(kelly_gross × min(raw_scalar, max_leverage), max_gross_exposure). This is the panel's formula verbatim
("scalar cap = min(3.0, 1.0/Kelly-gross)"): the validated composite
everywhere the account lives, clipped only above 1.0x exposure.
max_leverage KEEPS its validated scalar-cap meaning (3.0) in both modes.
Config surface (registered values, keystone-pinned in the same change)
| key | value | meaning |
|---|---|---|
risk.leverage_cap_basis | "scalar" (default) | today's behavior, byte-identical — max_leverage caps the scalar only |
"gross" | additionally cap FINAL gross at max_gross_exposure | |
risk.max_gross_exposure | 1.0 | the gross cap used by "gross" basis (inert under "scalar") |
Guard details (implementation contract): the gross clamp divides by the live
pre-scalar gross; a zero/empty book skips the clamp (gross is 0 regardless),
and the cap value is floored at 0 (a mis-set negative cap must mean all-cash,
never a sign-flipped book). The clamp applies whenever "gross" basis is on —
including the vol-estimator warmup window where the scalar is implicitly 1.0 —
because "no borrowed dollar" is an account invariant, not a property of the
scalar formula.
The amended go-live diff (effective ONLY upon ADOPT, §4)
execution.paper -> false (unchanged from the pre-specified diff); risk.leverage_cap_basis "scalar" -> "gross"; NOTHING ELSE. (
risk.max_leveragestays 3.0 — the validated scalar cap;risk.max_gross_exposureis already 1.0 in config.)
This replaces risk.max_leverage 3.0 -> 1.0 in the pre-specified diff. If the
adoption criteria FAIL, the pre-specified literal diff stands unmodified and
the §6 disclosure publishes with the gate output.
Disclosed property: the clamp loosens in losing streaks
Visible from the formula alone: as kelly_gross FALLS (Kelly de-weights after
losses), target_gross_cap / kelly_gross RISES — the gross clamp loosens
exactly when the book is shrinking. Gross-basis mode clips prosperity above
1.0x and adds no drawdown protection; relative to the literal scalar cap it
holds MORE exposure in losing streaks (the literal diff would pin the scalar at
1.0 always; gross basis lets it run to 3.0 while the composite gross is below
1.0x). This is why the amendment is not gate-shopping: it is derivable from the
formula with no forward data, it precedes the gate and any real dollar, and the
direction of its one behavioral asymmetry is risk-INCREASING in the losing
tail — nobody shops for that. (Panel C1 red-team, ratified.)
3. The three twins (frozen window; NOT run at registration)
All on the frozen guard window from results/baseline_metrics.json
(2010-01-01 → 2026-03-17), on the frozen owned data/raw snapshot, in one
sitting, via scripts/run_c1_twins.sh on the main checkout:
| twin | config | command core |
|---|---|---|
| T0 baseline | current config, no overrides | thales backtest --start <s> --end <e> |
| T1 gross-cap go-live | the amended diff's risk basis | … -p risk.leverage_cap_basis=gross |
| T2 literal go-live | the pre-specified literal diff | … -p risk.max_leverage=1.0 |
T2 is measured for the record — it quantifies exactly what the literal diff would deploy (the §6 disclosure's numbers). Adoption compares T1 vs T0 only. Two additional pinned instruments run in the same batch:
- Walk-forward A/B (criterion 3):
thales evaluate -p risk.leverage_cap_basis=gross --name c1_gross_cap_twin --tag c1-calibrationat the harness defaults (n_windows 5, train 60%, bootstrap 1000, seed 42). - Survivorship-free CPCV pair (criterion 2):
thales cpcv --survivorship-free --goldentwice — once on the baseline config, once with-p risk.leverage_cap_basis=gross— the honest frozen instrument (golden store + iShares membership, the monthly-revalidation convention), CPCV params from config (n_groups 6, k_test 2, purge 252, embargo 5). Only the SAME-BATCH pair is comparable (PBO levels are snapshot-sensitive; historical PBO numbers are not the baseline here).
4. ADOPTION CRITERIA (pinned now, before any twin runs)
Verbatim from the panel report, which is binding:
"Kill: adopt only if the gross-cap twin differs on <10% of days, survivorship-free PBO <= baseline, no window regresses 0.05 Sharpe; else keep the diff and pre-write the honest disclosure." (item C1)
"adopt only if the corrected twin matches the validated baseline on >90% of days with no overfitting-score or per-window regression, else keep the diff as written and pre-write the honest under-vol disclosure." (§1, rank 1)
Pinned operationalization (evaluated mechanically by
scripts/c1_twins_verdict.py; boundary semantics are part of the pin):
- Day-difference < 10% (strict). Compare T1 vs T0
weights_history.parquetover the frozen window. A day differs if ANY weight differs by > 1e-9 (absolute; symbols absent from a day's book count as weight 0.0). A date present in exactly one history differs by definition; the denominator is the union of dates across both histories. ADOPT requiresdiffering_days / total_days < 0.10; exactly 0.10 FAILS. - Survivorship-free PBO(T1 config) ≤ PBO(T0 config) from the §3 same-batch CPCV pair. Equality PASSES (the mode is not required to improve the overfitting score, only not to worsen it).
- No walk-forward window regresses ≥ 0.05 Sharpe. From the §3 evaluate
run: FAIL iff any window has
(candidate − baseline) sharpe_ratio ≤ −0.05(a regression of exactly 0.05 fails — the SHIP-gate reading of "regresses by ≥ 0.05").
ALL THREE pass → ADOPT the amended diff (§2). Any failure → KEEP-LITERAL: the pre-specified diff stands as written and the §6 disclosure is published alongside every go-live gate reading. The verdict is printed by the evaluator with the numbers; no discretion at read time.
Upon ADOPT (deliberate follow-up commit, citing this memo): update the
go_live: diff comment in settings.yaml to the amended text, and note that at
the actual go-live flip the byte-identical baseline must be re-pinned (§5). The
risk.leverage_cap_basis keystone stays "scalar" until the real December
flip — adoption changes the PLANNED diff, not the running paper config.
5. Re-baseline rule and calibration logging
- The re-baseline rule applies (panel C1 red-team: "a NEW engine cap mode —
the re-baseline rule applies"): gross basis is a new engine mode with no
validated baseline. At registration the DEFAULT is byte-identical, so
results/baseline_metrics.jsonis untouched and the guard stays green. IF the amended diff is adopted AND the December flip occurs, the production config becomes a new engine configuration and the byte-identical baseline must be re-pinned at flip time — T1's frozen-window metrics (retained underresults/diagnostics/c1_twins/backtest_gross_cap/) are exactly that measurement. - These runs are CALIBRATION, not alpha trials. They measure an
implementation-matches-intent fix against pre-pinned equivalence criteria;
nothing ships from their levels. The evaluate run carries the
c1-calibrationtag; the runner writes a manifest (results/diagnostics/c1_twins/manifest.json) recording commands, window, data snapshot, and this memo's criteria; when the C3 append-only trial registry lands, these rows enter it labeledcalibration.
6. Pre-written honest under-vol disclosure (published only on KEEP-LITERAL)
DISCLOSURE (drafted 2026-08-01, before the twins ran; published because the C1 gross-cap amendment failed its pre-registered adoption criteria — [cite the failing criterion + numbers from c1_twins_verdict.json here].)
The go-live configuration caps the vol-target scalar at 1.0 (
risk.max_leverage: 1.0). This knob does not cap market exposure: with the live book's Kelly deployment fraction at its recent ~0.14x level, the deployed book is ~0.14x gross exposure and ~4% annualized volatility — roughly one third of the validated 12% volatility target. This exposure level was never validated by any backtest (the validated configuration runsmax_leverage: 3.0), and the cap saves no margin interest — the book borrows nothing at ~0.42x gross. Expected total return scales with the exposure gap: results under this deployment must be read as a deliberately under-volatized experiment, and every live-vs-paper or live-vs-validated comparison (including the December gate's Sharpe criterion and the placebo percentile) must account for the ~3x exposure mismatch before any conclusion about the strategy is drawn. We keep the literal diff because the corrected alternative failed its own pre-registered equivalence tests — not because the literal diff's exposure level was ever validated.
7. What this memo does NOT do
- No live pinned value changes now:
go_live:values,risk.max_leverage: 3.0, meanrev/vrp configs — all untouched. The new keys default to byte-identical behavior and are keystone-pinned at their registered values. - It does not move the December gate, its criteria, or its interpretation memo
(
research/2026-07-13_december_gate_interpretation_memo.md, still binding). - It does not run anything: computation is deferred to the runner, which requires this memo to exist and refuses to overwrite an existing twin batch.
VERDICT APPEND — 2026-08-01 (same day; the batch ran immediately post-merge)
The §4 twin battery ran on the frozen window (scripts/run_c1_twins.sh,
outputs under results/diagnostics/c1_twins/, verdict JSON
c1_twins_verdict.json). All three pinned criteria evaluated mechanically:
| criterion | pinned bar | measured | result |
|---|---|---|---|
| day difference (T1 vs T0) | < 10% of days, any |dw| > 1e-9 | 0.00% | PASS |
| survfree golden PBO | gross <= baseline | 0.5000 vs 0.5000 | PASS |
| per-window Sharpe regression | none <= -0.05 | deltas [0,0,0,0,0] | PASS |
VERDICT: ADOPT. The settings.yaml go-live diff comment now carries the
amended text (basis "scalar" -> "gross"; max_gross_exposure 1.0 is the
binding no-borrowed-dollar cap). The leverage_cap_basis keystone stays
"scalar" until the real December flip; at that flip the byte-identical
baseline is re-pinned per §5 (measurement retained under
backtest_gross_cap/). T2 (the literal diff) was measured for the record
and its under-vol numbers stand in §6 as the disclosure that is now moot
unless the amendment is ever reverted.