feat(validation): P3-4 robustness matrix (universes x windows OOS cells) - #14
Merged
Conversation
Re-runs the P3-3 OOS stability check on every (universe x window) cell to test whether the P3-2/P3-3 conclusions were an SSE50 small-sample accident. No new factor, no new alpha, no portfolio / execution / factor-math change. Mechanics: - qt/robustness.py + CLI run-phase3-robustness: every cell reuses the P3-3 cell core VERBATIM (_run_oos_cell, refactored out of run_phase3_oos with single-run behaviour unchanged) -- holding-window performance slicing, realization-date IC slicing, walk-forward weights, and the shared guards (real source, oos section, ic_weighted fake-comparison guard). - Per-cell configs derive from the base with ONLY the cell identity swapped (universe.index_code, window, split, per-cell output_name so panels never collide); the derived config re-runs full pydantic validation. - robustness.skip_cells removes named cells EXPLICITLY (validated against declared universes/windows; at least one cell must remain) and every skip is disclosed in the report -- coverage is never silently reduced. - Cross-cell aggregation (summarize_matrix) is strictly per cell: per-series #cells with positive test IC / train->test sign consistency + test IC by cell, and #cells where ic_weighted beats equal_weight on test annual. Real matrix (3 cells, 2h total: SSE50x2020-2022 960s, SSE50x2022-2024 934s, CSI300x2022-2024 5301s / 399 names; CSI300x2020-2022 skipped + disclosed): - P3-3 no-drift smoke PASSED: the SSE50|2022-2024 cell reproduces the P3-3 report number-for-number (incl. the 2023-06-30 boundary exclusion). - Raw-factor IC sign flips are PERVASIVE across cells (sign consistency: momentum 1/3, roe 0/3, netprofit_yoy 0/3) -- the P3-3 finding was not an SSE50 accident. - combo_ic_weighted is the only cross-cell-consistent series (test IC 3/3 positive, 3/3 sign-consistent) but at ~0.004-0.008 -- tiny. - KEY NEGATIVE FINDING: ic_weighted portfolio performance does NOT generalize -- it edges equal_weight on both SSE50 cells but COLLAPSES on CSI300 (train +11.07% -> test -17.74%, sharpe +0.59 -> -1.01; turnover 1.2-1.6 adds cost drag). The P3-2 one-year edge does not extrapolate to a wider universe; weak positive IC != nav win. NOT a return claim. Tests: 299 passed (was 285): +14 matrix (config validation incl. skip references and all-skipped, cell enumeration/derivation incl. unique output_name + shared preconditions, strictly-per-cell aggregation, runner guards, report render incl. skipped-cell + boundary disclosure, no secret). Secret scan: report + tree contain no token content. Demo unchanged (ic 0.96 / annual 0.84).
The train->test collapse shape appears in 2 of 3 cells (SSE50|2020-2022 +7.09% -> -7.85% as well as CSI300 +11.07% -> -17.74%), not only CSI300: whenever ic_weighted is strongly positive in-sample it flips negative out of sample -- the classic overfit signature. (Also fixes a wrong -4.78% figure that briefly appeared in the PR body; the report itself was always right.)
Review MEDIUM on PR #14: the matrix report's DOWNGRADES section copied only the FIRST cell's disclosures (an early break), so a 2-universe matrix read as a single-universe (000016.SH) run, and the cell-level caveat said 'one index, two years' -- inaccurate for a matrix report whose accuracy IS the product. Fixed: - The DOWNGRADES section now opens with a MATRIX SCOPE line (run cells, skipped cells, universes covered, windows covered) and then renders the UNION of ALL run cells' disclosures (order-preserving dedup) -- each universe's PIT-membership line appears once, never only the first cell's. - The cell-level small-sample caveat is reworded to be accurate in both contexts: 'each run/cell covers ONE index universe over ONE window'; the matrix caveat states the matrix spans multiple universes x windows. - New render test: the DOWNGRADES section must contain BOTH universes' membership disclosures + the scope line incl. the skipped cell, dedup the shared lines, and must not contain the old single-universe wording. The committed artifacts report is git-ignored and regenerable; the next matrix run emits the corrected disclosures (locked by the test). Tests: 300 passed (was 299).
StackOverFlow11
added a commit
that referenced
this pull request
Jun 10, 2026
docs: mark PR #14 (P3-4) merged in progress docs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
P3-4 — robustness matrix: longer history + wider universe. Re-runs the P3-3 OOS stability check on every (universe × window) cell to answer whether the P3-2/P3-3 conclusions were an SSE50 small-sample accident. No new factor, no new alpha, no portfolio / execution / factor-math change — a report-only validation layer on top of the P3-3 framework.
Implementation
qt/robustness.py+ CLIrun-phase3-robustness: every cell reuses the P3-3 cell core verbatim —_run_oos_cellwas refactored out ofrun_phase3_oos(single-run behaviour unchanged, its 16 tests untouched), so every cell inherits holding-window performance slicing, realization-date IC slicing, walk-forward weights, and the shared guards (real source /oossection / ic_weighted fake-comparison guard).output_nameso panels never collide); the derived config re-runs full pydantic validation.robustness.skip_cellsremoves named cells (validated against declared universes/windows; ≥1 cell must remain) and every skip is disclosed in the report — coverage is never silently reduced. This run skips CSI300×2020-2022 (runtime budget; a CSI300 2y cell alone is ~88 min through the rate-limited SDK).summarize_matrix): strictly per cell (no pooling/mixing — locked by tests): per-series #cells with positive test IC + train→test sign consistency + test IC by cell; #cells where ic_weighted beats equal_weight on test annual.Real matrix (3 cells, 2h: 960s + 934s + 5301s; CSI300 = 399 historical names)
P3-3 no-drift smoke ✓: the SSE50|2022-2024 cell reproduces the P3-3 report number-for-number (eq train −11.92% / test −5.27%, ic train −8.31% / test −2.70%, boundary 2023-06-30 excluded + disclosed).
Cross-cell stability summary:
momentum_20roenetprofit_yoycombo_equal_weightcombo_ic_weightedPer-cell test-period annual (eq / ic): SSE50|2020-2022 −8.09% / −7.85% · SSE50|2022-2024 −5.27% / −2.70% · CSI300|2022-2024 −5.14% / −17.74%. ic_weighted beats equal_weight in 2/3 cells — but see finding 3.
Findings (the honest read)
combo_ic_weightedis the only cross-cell-consistent series — but at IC ≈ 0.004–0.008, far below tradable strength.Quality gates
pytest -p no:cacheproviderruff check .validate-config×7 (all old + robustness_matrix)run-phase0demo.config.jsoncontentTests (network-free)
test_robustness_matrix.py(14): matrix config validation (split-inside-window, unknown-skip, all-skipped), cell enumeration excludes skips in order, cell derivation swaps only the cell identity + unique output_name + passes shared preconditions, strictly-per-cell aggregation (no mixing, no dilution), runner guards (demo / missing section / non-ic_weighted), report render (cell labels, skipped-cell disclosure, boundary/fallback/sign-consistency, summary, caveat, no secret). P3-3's 16 tests pass unchanged on the refactored core.Docs
RUNBOOK (P3-4 section), TEST_REPORT (299 + breakdown), CLAUDE.md / AGENTS.md (verbatim copy) progress incl. the negative finding and the roadmap implication (the current factor set lacks tradable signal strength — iterate the factor set inside this validation framework, or move to minute-level).