feat(factors): P3-5 candidate factor pack, validated through the P3-4 matrix - #16
Merged
Conversation
… matrix EXPLORATORY: tests whether the legacy trio's weak signal (P3-3/P3-4) was just a too-narrow factor set. No alpha / portfolio / execution / OOS / robustness model change; nothing tuned; not a return claim. Candidates (factors/compute/candidates.py; math, leading-window NaN, future-bar invariance, per-symbol isolation and immutability all unit-locked): - reversal_5/20: exact negative of momentum (reuses the same machinery); - volatility_20: trailing daily-return std (ddof=1, full window only); - liquidity_20: log trailing mean turnover amount (non-positive -> NaN); - value_ep / value_bp: 1/pe, 1/pb from ONE daily_basic fetch (same-day published, PIT-safe; non-positive ratios -> NaN); demo -> readable error; - grossprofit_margin: joins SUPPORTED_FIELDS, rides the existing ann_date as-of machinery unchanged. Wiring: _build_factors dispatch extended (reversal*/volatility*/liquidity*/ value fields); a window-name/params mismatch is now a readable config error (never a silently mislabelled column); _maybe_enrich_value wired into phase0 / phase2 / OOS-cell paths. Legacy configs reproduce their numbers (demo ic 0.96 / annual 0.84 unchanged). config/phase3_real_factor_candidates.yaml: legacy trio + 7 candidates through the UNCHANGED P3-4 robustness matrix (same cells; CSI300x2020-2022 stays an explicit, disclosed skip). Old-vs-new reads off one run: raw-factor ICs are per-column, so the trio's per-cell ICs double as a no-drift cross-check. Real matrix (3 cells, 2.3h; trio ICs identical to the P3-4 report = no-drift smoke PASSED; secret scan clean): - The too-narrow hypothesis is SUPPORTED: value_ep/value_bp test IC 3/3 positive at 0.037-0.056 and volatility_20 3/3 negative at -0.044..-0.079 -- an order of magnitude above the trio (|IC| <= 0.015) with cross-cell-stable direction. liquidity weakly negative 3/3; reversal / grossprofit_margin sign-flip with no signal. - combo_ic_weighted test IC 3/3 positive + 3/3 sign-consistent (the walk-forward weights pick up the new signal); the 10-factor EQUAL-WEIGHT combo is diluted by the sign-flipping factors (0/3, CSI300 test -34.6%) -- equal weight is sensitive to factor-set quality. - Portfolio: ic test -3.62% / -4.88% / -2.64% (beats equal weight in 2/3 cells; CSI300 train +23.69% -> test -2.64%: train->test decay remains). - CAVEATS disclosed: value/low-vol were a known strong A-share regime in 2020-2024; the three cells overlap in time (not independent samples); no cost-sensitivity yet. Next: an independent re-check of a value+lowvol subset. Tests: 317 passed (was 300): +17 candidates; the P3-1 duplicate-name test now uses a true duplicate spec (the new mismatch guard catches the old construction earlier).
overnight_ret[t] = log(open[t] / close[t-1]); overnight_mom_20[t] = the trailing 20-day sum. PIT: open[t] is known at the t open and the factor is computed at the t close; prices are front-adjusted by one anchor so the ratio is ex-dividend-safe; the t-1 close never crosses symbols (grouped shift); non-positive prices -> NaN (never -inf); leading window NaN (w returns need w+1 bars). Wired into the dispatch (overnight_mom*) and into config/phase3_real_factor_candidates.yaml (now legacy trio + 8 candidates). +5 unit tests (manual-reference math, future-bar invariance, per-symbol isolation, non-positive guard, missing-column error); dispatch + config tests extended. 322 passed; demo unchanged. Real matrix rerun with the extended pack is in flight; numbers land in a follow-up commit + the PR body.
Real rerun with the extended pack (3 cells, 2h; one CSI300 network failure -> full clean rerun): all 10 prior series' per-cell ICs unchanged (per-column independence re-confirmed); overnight_mom_20 test IC 2/3 positive (+0.008/+0.016; negative in 2020-2022) -- a moderate-weak signal. ic_weighted now beats equal weight in 3/3 cells (test -2.21%/-5.02%/-2.76% vs eq -4.30%/-6.83%/-25.16%); CSI300 train +23.11% -> test -2.76% (decay remains). Caveats unchanged: exploratory, overlapping windows, no cost sensitivity.
Review findings on PR #16, doc-only: - TEST_REPORT top results line said 299 passed while the breakdown summed to 322 (two earlier count bumps never matched the stale line) -> 322. - RUNBOOK said 'legacy trio + 7 candidates' -> 8 (overnight_mom_20). - CLAUDE/AGENTS said '10-factor equal-weight combo' and carried the 10-factor run's combo_ic_weighted ICs (0.0255/0.0014/0.0402) -> 11-factor wording and the real report's 0.0253/0.0012/0.0395. Verified by grep against artifacts/reports/phase3_robustness_matrix.md; no matrix rerun needed (display-only).
StackOverFlow11
added a commit
that referenced
this pull request
Jun 11, 2026
docs: mark PR #16 (P3-5) 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-5 — factor candidate pack (EXPLORATORY). Adds a conservative, daily, PIT-safe candidate factor set and re-runs the unchanged P3-4 robustness matrix to test whether the legacy trio's weak signal (P3-3/P3-4 finding) was just a too-narrow factor set. No alpha / portfolio / execution / OOS / robustness model change. Not tuned. Not a return claim.
Candidates (all unit-locked: math vs manual reference, leading-window NaN, future-bar perturbation invariance, per-symbol isolation, immutability)
reversal_5/20volatility_20liquidity_20value_ep/value_bpdaily_basicfetch; ≤0 → NaNgrossprofit_marginSUPPORTED_FIELDSWiring: dispatch extended (
reversal*/volatility*/liquidity*/ value fields); a window-name/params mismatch is a readable config error (a spec namedreversal_5withwindow: 10can never silently mislabel a column);_maybe_enrich_valuewired into phase0 / phase2 / OOS-cell paths; demo + value/financial factors still fail readably. Legacy configs reproduce their numbers (demo ic 0.96 / annual 0.84, test-locked).Config:
config/phase3_real_factor_candidates.yaml= legacy trio + 7 candidates through the same matrix cells as P3-4 (CSI300×2020-2022 stays an explicit, disclosedskip_cellsentry). Old-vs-new reads off ONE run: raw-factor ICs are per-column, so the trio's per-cell ICs double as a no-drift cross-check.Real matrix (3 cells, 2.3h; secret scan clean)
No-drift smoke ✓: the legacy trio's per-cell ICs are identical to the P3-4 report (momentum 0.0017/0.0062/0.0151; roe −0.0181/0.0066/0.0015; np_yoy −0.0117/0.0046/0.0070).
Cross-cell stability (test IC > 0 / sign consistent):
value_epvalue_bpvolatility_20liquidity_20overnight_mom_20reversal_5/20,grossprofit_margincombo_ic_weighted(table = the 11-factor rerun; all 10 prior series' per-cell ICs are unchanged vs the 10-factor run — per-column independence re-confirmed.)
|
combo_equal_weight| 0/3 | 1/3 | −0.013 … −0.023 |Findings (honest read):
ic_weightedcombo picks the new signal up (3/3 positive + consistent); the all-factor equal-weight combo is diluted by the sign-flipping factors (0/3; CSI300 test annual −25.2% in the 11-factor run) — equal weight is sensitive to factor-set quality.Quality gates
pytest -p no:cacheproviderruff check .validate-config×8 (all old + factor_candidates)run-phase0demo.config.jsoncontentTests (network-free)
test_candidate_factors.py(17): per-factor math vs manual references, leading-window NaN, future-bar invariance, per-symbol isolation, non-positive guards, missing-column readable errors, ValueFactor contracts, grossprofit_margin in SUPPORTED_FIELDS, dispatch for every candidate + mismatch guard, value enrichment (one fetch, 1/pe / 1/pb math, non-positive → NaN, demo error, immutability), demo e2e with price candidates, candidates config validation.Docs
RUNBOOK (P3-5 section incl. the one-run old-vs-new design and drop_missing note), TEST_REPORT (317 + breakdown), CLAUDE.md / AGENTS.md (verbatim copy) progress incl. findings + caveats.
Addendum:
overnight_mom_20(added on review request)overnight_ret[t] = log(open[t]/close[t-1]),overnight_mom_20 = trailing 20-day sum. PIT: open[t] is known at the t open, factor computed at the t close; one front-adjust anchor keeps the ratio ex-dividend-safe; the t−1 close never crosses symbols; non-positive prices → NaN. +5 unit tests (manual-reference math, future-bar invariance, per-symbol isolation, guards) → 322 passed. Real 11-factor matrix rerun (2h; one CSI300 network failure → full clean rerun): test IC 2/3 positive (+0.008 / +0.016; negative in 2020-2022) — moderate-weak; all prior series' ICs unchanged.