Brings the durable artifacts of research/portfolio-capacity-rebalancing onto main so the rationale for raising the count cap lives with the code that cites it. The matrix runner, the research simulator hooks and the study's unit tests are deliberately left behind; they remain at tag research/portfolio-capacity-final. Corrects conclusions that were reached on EV per trade and are now superseded: the findings doc's decisions 1 (keep cap 10) and 4 (run the risk-floor A/B) are struck through and answered in a new correction section, and the research README and phase-A matrix entries are updated to match. The frozen specification itself is untouched -- its recorded SHA-256 f1e37783 still verifies. effective-risk-floor-ab.md is retained but marked CLOSED/NEGATIVE: the study it proposes is already answered by cap15 vs cash_unbounded (-0.753pp CAGR while EV/trade rises), and its EV-based pass rule would have shipped it. scripts/research_rankings.py replaces a fourth copy of the historical rank-map helper; run_research_matrix, run_execution_recovery_matrix and run_daily_reentry_matrix now share it. The shared version adds a duplicate observation guard and a deterministic symbol tie-break the copies lacked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.2 KiB
Effective initial-risk floor A/B - frozen specification
⛔ CLOSED 2026-08-05 — NEGATIVE. DO NOT RUN.
This A/B was never executed because the capacity-bracket run already contains it.
cap15_incumbent(peak 12, zero blocked, no floor) andcash_unbounded(peak 12, floor) have the same effective capacity and differ essentially only bymin_initial_risk_fraction. Paired over 175 paths, the 0.5% floor gives EV/trade +0.032 and profit factor +0.073, but CAGR −0.753pp, total return −0.765pp, Sharpe −0.047, Calmar −0.051, and it removes 11.4 trades per path while never adding one (174 worse / 0 better).The pass rule below is unsafe. It promotes on paired EV, and the floor raises EV per trade precisely by deleting trades that were net positive contributors — so this specification would have shipped a change costing 0.75pp of CAGR. Any successor study must decide on CAGR/total return and treat EV per trade as a diagnostic.
See portfolio-capacity-bracket-findings.md. Retained as a record of what was specified and why it was withdrawn.
Date frozen: 2026-08-05
Branch: research/portfolio-capacity-rebalancing (deleted; tag research/portfolio-capacity-final)
Runner: scripts/run_portfolio_construction_matrix.py (not on main; see tag)
Study ID: risk-floor-ab
Question
Does rejecting an otherwise qualified cap-10 entry when its actual initial stop-risk after cash and notional sizing is below 0.5% of marked equity improve trade selection?
The completed capacity bracket cannot answer this. Its cash_unbounded arm removed the count cap and applied the 0.5% floor simultaneously. In the 70 paths where the control cap never bound, that arm still raised mean EV from 0.328 to 0.399 R and profit factor from 1.60 to 1.75 while trades fell about 8% and exposure stayed nearly flat. Capacity was a no-op in those paths, so the floor is the plausible cause, but the prior arm remains confounded.
This A/B changes only the floor. It has no formal promotion gate and does not automatically change production.
Frozen arms
- cap10_incumbent: current production-style cap-10 control, with no minimum effective-risk floor.
- cap10_min_risk_005: the same cap-10 strategy, rejecting an entry only when actual initial stop-risk after cash/notional sizing is below 0.5% of marked equity.
Both arms have max_positions=10, weekly replacement disabled, 1% target risk per trade, and identical admission ordering. The only differing simulator argument is min_initial_risk_fraction: None versus 0.005.
All other settings remain the frozen daily Phase A control: current production construction universe, full-universe residual-momentum/low-volatility 80/20 rank, threshold 80, normal gate-reset re-entry, close fills, 3x ATR trail, 30-session maximum hold, 20% per-position notional ceiling, no leverage, and costs of 0.10% and 0.20% per fill.
Every priced symbol contributes to the daily cross-sectional rank. Rank-only symbols cannot submit trades. Validation retains the 450-600-symbol production construction guardrail and the legacy-snapshot column-scoped loader.
Frozen cohorts
Reuse the completed bracket's point-in-time daily candidate/rank cache and cohort manifest:
- Empty book: first eligible session of each month in 2019-2025, with 504 prior scoring sessions and 252 measurement sessions. This is the primary start-date evidence.
- Warm book: weekly seeds 63-126 sessions before each 2019-2025 annual anchor, with state carried into the same 252-session measurement window. This is a state-carrying replication, not independent evidence.
The expected realization is 78 empty-book paths, 97 warm paths, seven annual clusters in each protocol, two costs, two arms, and 700 cells.
Do not use warm-seed IQR as evidence. Six of seven completed-bracket anchors were structurally degenerate because fractional sizing is scale invariant and the 30-session maximum hold washed out books before anchors. The 2023 exception shows that state carrying itself works.
Reporting and interpretation
For every protocol and cost, pair identical paths. Report:
- mean, median, P25, and P75 paired net-EV changes in R;
- positive-path and bit-identical-path fractions;
- the median paired delta within each year and the median across seven years;
- simple 90% cluster-bootstrap context for EV and Calmar, with no CI gate;
- mean paired PF, Gain-to-Pain, Sortino, Calmar/MAR, CAGR, maximum drawdown, total return, and Sharpe changes;
- trades, floor rejections, holding time, cash, gross exposure, average/peak positions, turnover, and costs.
Means and identical-path fractions must appear beside medians so inert cohorts cannot turn a left- or right-skewed treatment into a misleading zero headline. For these 252-session windows, the implementation's full-window Calmar is CAGR divided by maximum drawdown, the same numeric definition commonly called MAR; do not present the duplicate label as a second independent metric.
Today's production membership is projected backward. Use paired differences for the treatment conclusion; absolute profitability remains descriptive and survivorship-biased. Empty and warm protocols cover the same seven market years and must not be interpreted as independent replications.
Interpretation is deliberately simple:
- a positive result means the isolated floor improves the paired EV distribution without an economically important loss of total-return or drawdown quality;
- a negative result closes the floor;
- mixed EV/portfolio-quality results are reported as a trade-off, not forced through a composite score.
Reproducibility and macOS execution
The authoritative run refuses a dirty worktree. Its fingerprint includes the implementation commit, this specification hash, snapshot hash, candidate-cache key, construction view, cohort manifest, arm definitions, costs, and study version. Cells checkpoint atomically and --resume verifies the fingerprint.
From the repository root on macOS:
python3 -m venv .venv
./.venv/bin/python -m pip install -e '.[dev]'
Preflight, reusing the completed bracket's candidate/rank cache:
./.venv/bin/python scripts/run_portfolio_construction_matrix.py + backtest_snapshots/research.sqlite + --study risk-floor-ab + --run-id prod505-effective-risk-floor-ab-daily-v1 + --candidate-cache reports/.cache/prod505-capacity-bracket-daily-v1-candidates.pkl + --workers 8 + --resume + --validate-only
Authoritative run:
./.venv/bin/python scripts/run_portfolio_construction_matrix.py + backtest_snapshots/research.sqlite + --study risk-floor-ab + --run-id prod505-effective-risk-floor-ab-daily-v1 + --candidate-cache reports/.cache/prod505-capacity-bracket-daily-v1-candidates.pkl + --workers 8 + --resume
On an M2 Pro, eight workers is the explicit high-utilization setting. Use six instead on a memory-constrained machine; auto intentionally caps itself at six. Changing worker count does not change the fingerprint or results.
Commit only the compact final JSON and Markdown reports. Candidate caches, checkpoints, raw curves, and trade ledgers remain ignored.