Skip to content

feat(factors): P3-5 candidate factor pack, validated through the P3-4 matrix - #16

Merged
StackOverFlow11 merged 4 commits into
mainfrom
p3-factor-candidate-pack
Jun 11, 2026
Merged

feat(factors): P3-5 candidate factor pack, validated through the P3-4 matrix#16
StackOverFlow11 merged 4 commits into
mainfrom
p3-factor-candidate-pack

Conversation

@StackOverFlow11

@StackOverFlow11 StackOverFlow11 commented Jun 11, 2026

Copy link
Copy Markdown
Owner

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)

Factor Definition PIT argument
reversal_5/20 −(close[t]/close[t−w] − 1) exact negative of momentum (reuses its machinery; ≤t bars)
volatility_20 trailing 20d return std (ddof=1, full window only) ≤t bars
liquidity_20 log(mean(amount, 20)); non-positive → NaN (never −inf) same-day bar amount
value_ep / value_bp 1/pe, 1/pb from one daily_basic fetch; ≤0 → NaN ratios published same-day
grossprofit_margin quality field → joins SUPPORTED_FIELDS existing ann_date as-of machinery, unchanged

Wiring: dispatch extended (reversal* / volatility* / liquidity* / value fields); a window-name/params mismatch is a readable config error (a spec named reversal_5 with window: 10 can never silently mislabel a column); _maybe_enrich_value wired 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, disclosed skip_cells entry). 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):

Series test IC > 0 sign consistent test IC range
value_ep 3/3 2/3 +0.037 … +0.052
value_bp 3/3 2/3 +0.044 … +0.056
volatility_20 0/3 (i.e. 3/3 negative) 2/3 −0.044 … −0.079
liquidity_20 0/3 (3/3 negative) 2/3 −0.011 … −0.036
overnight_mom_20 2/3 2/3 −0.010 … +0.016
reversal_5/20, grossprofit_margin sign-flip ≤1/3 no signal
legacy trio (P3-4 unchanged) ≤1/3 |IC| ≤ 0.015
combo_ic_weighted 3/3 3/3 +0.001 … +0.040

(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):

  1. The too-narrow hypothesis is SUPPORTED: conservative value (E/P, B/P) and low-volatility carry cross-cell-stable direction at an order of magnitude above the legacy trio.
  2. The walk-forward ic_weighted combo 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.
  3. Portfolio (11-factor run): ic test −2.21% / −5.02% / −2.76%, beats equal weight in 3/3 cells (eq: −4.30% / −6.83% / −25.16%); CSI300 train +23.11% → test −2.76% — train→test decay remains.
  4. Caveats disclosed in the report and docs: 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 analysis yet. Next step: an independent re-check of a value+lowvol subset.

Quality gates

Gate Result
pytest -p no:cacheprovider 317 passed (was 300: +17 candidates; 1 legacy test reworked for the new mismatch guard)
ruff check . clean
validate-config ×8 (all old + factor_candidates) OK
run-phase0 demo ic 0.96 / annual 0.84 unchanged
P3-3/P3-4 no-drift trio per-cell ICs identical to the P3-4 report
Real matrix SSE50×2 folds + CSI300×2022-2024 run; CSI300×2020-2022 explicit skip, disclosed
Secret scan report + tree contain no token / .config.json content

Tests (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.

… 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
StackOverFlow11 merged commit 747c208 into main Jun 11, 2026
StackOverFlow11 added a commit that referenced this pull request Jun 11, 2026
docs: mark PR #16 (P3-5) merged in progress docs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant