Files
signal-platform/docs/research/portfolio-capacity-bracket-findings.md
T
dennisthiessenandClaude Opus 5 f5d4b516ab
Deploy / lint (push) Successful in 10s
Deploy / test (push) Successful in 1m21s
Deploy / deploy (push) Successful in 37s
docs: land capacity-study evidence and share the rank-map helper
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>
2026-08-05 23:08:29 +02:00

10 KiB
Raw Blame History

Portfolio-capacity bracket — findings

Date interpreted: 2026-08-05

Status: SUPERSEDED IN PART — see Correction at the foot of this document before acting on anything here. Weekly replacement is closed as a negative result and that still holds. The capacity decision below ("keep cap 10") and the recommendation to run the effective-risk-floor A/B were both reached on EV per trade and are reversed by the correction: the count cap was raised so it no longer binds, and the floor A/B is closed as negative.

The runner (scripts/run_portfolio_construction_matrix.py), the research simulator hooks, and the study's unit tests were deliberately not merged to main. They live at tag research/portfolio-capacity-final.

This document interprets the frozen v2 run without modifying its generated outputs:

  • result commit: 24482c6;
  • simulation source commit: 6fc82ae8574de9104c83273e018391e75a5f8ac6;
  • frozen specification SHA-256: f1e37783cf6d157ecc827d48211fa45da16f0a0ac19cd23686b3902d347a1898;
  • JSON SHA-256: 2435875667097db7416a0d96f412db81d2f2d09ba053748c9f2cfb8a0cba4417;
  • Markdown SHA-256: dc3f5de25eb0a156ce51d0025c90e04ac0977e9502dec47bcf1b25bdcf609c81.

The run completed 78 empty-book paths, 97 warm-seed paths, seven annual clusters under both protocols, two cost levels, four arms, and 1,400 cells with no validation errors. The construction universe was 505 priced tradable symbols plus 4,149 priced rank-only symbols.

Capacity is economically free

The clean capacity treatment is cap15_incumbent: it changes no sizing or admission rule. Its cap never bound in any cell (maximum observed position count 12; zero full-book skips), so it absorbed every opportunity blocked by cap 10.

At 0.10% per fill, split the 175 paths by whether the paired control recorded any skipped_book_full. Values below are mean paired changes in net EV per trade, in R:

Arm Cap never bound (n=70) Cap did bind (n=105)
cap15_incumbent +0.0000 +0.0018
cash_unbounded +0.0714 +0.0077
cap10_weekly_top10 -0.0246 -0.0426

The exact zero for cap15 in the never-bound stratum is also a harness validity check: when the treatment cannot act, results are identical. Where it does act, giving the strategy every slot it requested adds only 0.0018 R/trade. The old 519-blocked-versus-472-admitted count was true, but it did not imply that the blocked opportunities were economically valuable.

Decision: keep the production cap at 10. Do not remove it or raise it in the expectation of additional edge.

The positive arm measured the risk floor

cash_unbounded combined two treatments: no count cap and a 0.5% minimum effective initial-risk fraction. Its EV effect is roughly nine times larger in the 70 paths where the control cap never bound, so capacity cannot explain the improvement.

Within that never-bound stratum:

Measure Control cash_unbounded
Mean trades 75.7 69.9
Mean cash 27.8% 28.2%
Mean gross exposure 72.2% 71.8%
Mean hold 15.4 sessions 15.6 sessions
Mean EV +0.328 R +0.399 R
Mean profit factor 1.60 1.75

The floor removes about 8% of fills while leaving exposure and holding time nearly unchanged. This is selection, not general de-risking: candidates that available sizing compresses below half the intended risk are worse on average. The report records repeated reject attempts, not the rejected candidates' ranks, so whether the effect is rank-mediated remains unknown.

Next research: one single-variable A/B, cap10_incumbent versus cap 10 with min_initial_risk_fraction=0.005, with every other rule unchanged. Do not call the current cash_unbounded result causal evidence for that floor until this confound-free comparison is run.

Weekly replacement hurts

Median paired deltas read zero because enough cohorts are inert. The distribution is not neutral:

Protocol Mean ΔEV P25 ΔEV Identical paths
Empty book -0.0360 R -0.0817 R 27/78 (34.6%)
Warm book -0.0348 R -0.1582 R 14/97 (14.4%)

The arm made 2,170 replacements and 529 same-symbol re-entries within ten sessions, so 24% of replacements were associated with short-horizon churn.

Decision: reject weekly top-10 replacement. Future reports should show mean paired effects and identical-path fractions beside medians whenever treatments are inert in a material share of cohorts.

Warm dispersion was mostly structurally degenerate

For six of seven anchors, control EV IQR is numerical zero (approximately 1e-16) and Calmar IQR is exactly zero. The displayed ratio 1.000 is therefore mostly the implementation's zero-over-zero convention, not evidence of equal nonzero dispersion.

Two mechanics cause convergence: sizing and notional limits are fractions of equity, making R and ratio metrics scale-invariant; and the 30-session maximum hold is shorter than the 63-session minimum seed offset, allowing initial books to wash out before the anchor.

The exception is 2023. Control measurement-start positions vary from 6 to 9, EV IQR is 0.0274 R, and Calmar IQR is 0.2675. The protocol therefore carries state correctly, but its chosen offsets usually erase the initialization effect it was intended to measure.

Future initialization studies should use seed offsets shorter than maximum hold, approximately 525 sessions. The current empty-book cohorts remain the primary start-date evidence, but they necessarily mix initialization with market regime.

Final decisions

  1. Keep cap 10; its measured opportunity cost is negligible. REVERSED — see the correction below.
  2. Reject weekly rank replacement. (Stands.)
  3. Do not interpret the cash_unbounded improvement as a capacity effect. (Stands — and it is not a floor effect worth having either; see below.)
  4. Run only the focused cap-10 effective-risk-floor A/B next. REVERSED — that A/B is answered and negative; do not run it.
  5. Report means, inert fractions, and absolute dispersion beside medians and ratios in future sparse-treatment studies. (Stands, and see below — the metric itself matters as much as the summary statistic.)

Correction 2026-08-05: EV per trade was the wrong lens

Everything above judged the arms on mean paired net EV per trade. That is the wrong metric for any treatment that changes how many trades the book takes. Capacity does not change trade quality; it changes trade count. A flat EV/trade delta therefore does not mean "no benefit" — it means the blocked entries were just as good as the taken ones, so refusing them cost their entire contribution to return. Re-running the same paired comparison on CAGR inverts two conclusions.

Capacity: raise the cap (reverses decision 1)

cap15_incumbent versus cap10_incumbent, paired, all 175 paths, 0.10% per fill:

Metric Mean Δ Worse / better
Trades +1.00 0 / 76 (never fewer)
CAGR pp +1.075 2 / 51
Total return pp +1.079 1 / 51
Max drawdown pp +0.007 1 / 2
Calmar +0.062 1 / 51
Sharpe +0.022 10 / 28
Net EV R/trade +0.001 47 / 29

Restricted to the 105 paths where the cap actually bound: +1.791pp CAGR.

The honest tail: exactly one path was materially hurt — empty-2023-04, CAGR 87.2 → 81.2 (6.0pp), drawdown 13.0 → 14.3, from two extra trades. Second-worst was 0.1pp. The best paths (+6.6/+6.7/+6.9pp) came with identical drawdown. Best and worst magnitudes are symmetric at roughly ±6pp, but the frequency is 51:1.

Blocked count is not lost value in either direction: empty-2021-05 had 244 blocked entries under cap 10, and relieving every one of them moved CAGR by 0.1pp.

Shipped: SIM_MAX_POSITIONS and shadow_book_service.DEFAULT_CAPACITY raised 10 → 15. Fifteen is headroom, not a target — cap15 peaked at 12 with zero full-book skips, so cash plus the 20% notional cap is the real ceiling and 15/20/None are the same experiment.

Effective-risk floor: closed negative (reverses decision 4)

The floor A/B does not need running — this study already contains it. cap15_incumbent (peak 12, zero blocked, no floor) and cash_unbounded (peak 12, floor) have the same effective capacity and differ essentially only by min_initial_risk_fraction. Paired, n=175, 0.10% per fill, floor minus no-floor:

Metric Mean Δ Worse / better
Net EV R/trade +0.032 53 / 121
Profit factor +0.073 46 / 128
Trades 11.4 174 / 0 (never adds one)
CAGR pp 0.753 105 / 68
Total return pp 0.765 105 / 68
Sharpe 0.047 108 / 65
Calmar 0.051 103 / 71
Max drawdown pp +0.333 (worse)

The same trap, mirrored: the floor raises per-trade quality precisely by deleting trades, and the deleted trades were net positive contributors. The frozen specification in effective-risk-floor-ab.md would have passed it on paired EV and shipped a change costing 0.75pp of CAGR.

Genuinely open, low priority: 0.005 clearly over-cuts, but the sizing code's real floor is a $1 minimum, which is no floor at all. Whether something near 0.001 strips true dust without cutting real trades is untested, and only worth revisiting if live broker order minimums force it.

Start-date sensitivity is real but not a capacity artifact

Within-year spread of EV across monthly start dates is ~0.672 R and is identical for cap10 (0.672), cap15 (0.672) and cash_unbounded (0.677). It is small-sample noise — roughly 84 trades per 252-session window drawn from a fat-tailed R distribution gives an EV standard error near 0.150.25 R — not a queueing artifact. No construction policy reduces it.

Rule for future studies

Choose the metric from the treatment's mechanism before reading any table. If a treatment changes trade count, CAGR and total return are the decision metrics and EV per trade is a diagnostic. The generated report's headline tables lead with ΔEV net R, which is what made this error easy to make twice.