Skip to content

feat(factors): P3-1 multi-factor pipeline + first real price+financial baseline - #9

Merged
StackOverFlow11 merged 1 commit into
mainfrom
p3-multifactor-financial-baseline
Jun 10, 2026
Merged

feat(factors): P3-1 multi-factor pipeline + first real price+financial baseline#9
StackOverFlow11 merged 1 commit into
mainfrom
p3-multifactor-financial-baseline

Conversation

@StackOverFlow11

Copy link
Copy Markdown
Owner

Summary

P3-1 — first REAL multi-factor baseline (price + PIT financials). The pipeline previously consumed only the FIRST enabled factor; this PR makes it consume every enabled factor and adds the first real multi-factor baseline: momentum_20 + roe + netprofit_yoy on SSE50, still combined by the same equal-weight alpha. No parameter search, no learned weights, not a performance claim — this validates the multi-factor plumbing against real data.

What changed

  • _build_factors (was _build_factor): instantiates every enabled factor in config order; duplicate factor names are a readable config error. Single-factor configs are byte-stable (primary = the only factor).
  • Batched financials: _maybe_enrich_financials collects ALL financial-factor fields and does one fina_indicator fetch + one asof_financials pass — each field independently honours ann_date <= trade_date; no per-factor refetch. Demo + financial factor still raises a readable error (no fabricated financials).
  • Multi-column factor panel: one column per factor; processing (drop_missing → neutralize → zscore) was already per-column. drop_missing requires all enabled factors per name/date — disclosed in DOWNGRADES, never scored on partial data.
  • Analytics: _factor_analytics now returns per-factor {ic_mean, ic_ir, quantile_returns, coverage} plus combo-score analytics (forward returns computed once). Top-level metrics remain the PRIMARY (first-enabled) factor's → identical for all existing configs. The alphalens cross-check stays on the primary raw factor (report-only, P2-4 contract intact).
  • Report: active factor list; per-factor coverage/IC/quantile tables + combo row; financial ann_date coverage per field, labelled TRADED financial factor vs diagnostic only. output.baseline_report_name keeps the phase3 report file separate from phase2's.
  • Config: config/phase3_real_multifactor.yaml (SSE50, 2023-07 ~ 2024-06 — same window as phase2 for comparability; neutralize PIT SW-L1; top_n 20). Runs via the shared run-phase2-baseline real-baseline runner.

Real-data results (manual, not in CI)

Regression — phase2 single-factor rerun (13.6 min): UNCHANGED
annual −10.19% · IC 0.0083 · PIT SW-L1 coverage 98.53% — identical to the P2-3 baseline. Demo also unchanged (ic 0.96 / annual 0.84).

Phase3 multi-factor (14.3 min):

Factor coverage IC mean IC IR
momentum_20 91.70% 0.0083 0.0272
roe 100.00% 0.0006 0.0033
netprofit_yoy 100.00% 0.0001 0.0006
combo score (equal-weight) −0.0038 −0.0279
  • Portfolio: annual −9.05% (single-factor was −10.19%) · maxDD −16.17% · sharpe −0.59 · avg turnover 0.85
  • Financial ann_date coverage 100% on both fields (reported per field, labelled TRADED)
  • momentum_20 IC identical across the two runs (0.0083) — cross-run consistency evidence for the per-factor path
  • Financial-factor ICs ≈ 0 on this 50-name / 1-year window — disclosed honestly; this is plumbing validation, not a signal claim
  • Standard analytics both ran (quantstats CAGR −14.38% / alphalens IC 0.0083 == simple IC)

Quality gates

Gate Result
pytest -p no:cacheprovider 249 passed (was 234: +10 multifactor, +1 multi-field as-of, +4 report surface)
ruff check . clean
validate-config ×4 (demo / example_tushare / phase2 / phase3) OK
run-phase0 demo ic 0.96 / annual 0.84 unchanged
run-phase2-baseline (phase2 config) −10.19% / 0.0083 unchanged
run-phase2-baseline (phase3 config) new baseline above
Hygiene no token, artifacts/parquet git-ignored, no conflict markers

Tests (network-free)

test_multifactor_pipeline.py (10): factor list build (order / disabled / duplicate / none), single-fetch batched financials + as-of correctness + input immutability, demo+financial readable error, e2e demo multi-factor panel + per-factor/combo analytics + primary==first, report factor list + combo + no-secret, single-factor legacy shape. test_pit_financials.py (+1): multiple fields as-of aligned in one call. test_phase2_baseline.py (+4): active-factor list, per-field role labels, per-factor table, configurable baseline report name.

Docs

RUNBOOK (Phase 3-1 section), TEST_REPORT (counts + P3-1 notes), BIAS_AUDIT (multi-factor ann_date / combination disclosure, regenerated from renderer), CLAUDE.md / AGENTS.md progress.

…l baseline

The pipeline previously consumed only the FIRST enabled factor. P3-1 makes it
consume every enabled factor and adds the first real multi-factor baseline
(momentum_20 + roe + netprofit_yoy on SSE50), still combined by the
equal-weight alpha. No parameter search, no learned weights, not a
performance claim.

Pipeline:
- _build_factors instantiates EVERY enabled factor (config order; duplicate
  names are a readable config error). Single-factor configs unchanged.
- _maybe_enrich_financials batches ALL financial fields into ONE
  fina_indicator fetch + ONE asof_financials pass (each field independently
  honours ann_date <= trade_date). Demo + financial factor still raises a
  readable error (no fabricated financials).
- _compute_factor_panel writes one column per factor; processing
  (drop_missing / neutralize / zscore) is already per-column. drop_missing
  requires ALL enabled factors per name/date (disclosed).
- _factor_analytics now returns per-factor {ic, ic_ir, quantiles, coverage}
  plus combo-score analytics (forward returns computed once). Top-level
  metrics stay the PRIMARY (first-enabled) factor's -- byte-identical for
  single-factor configs; the std (alphalens) cross-check stays on the primary
  raw factor.

Baseline + report:
- config/phase3_real_multifactor.yaml (SSE50, 2023-07~2024-06, same window as
  phase2 for comparability); output.baseline_report_name keeps the phase3
  report file separate from the phase2 one.
- Report gains: active factor list, per-factor coverage/IC/quantile tables,
  combo-score diagnostics, and per-field ann_date coverage labelled TRADED
  factor vs diagnostic-only.

Real runs (not in CI):
- Phase2 single-factor regression rerun: annual -10.19% / IC 0.0083 / PIT
  SW-L1 98.53% -- unchanged (no regression).
- Phase3 multi-factor: annual -9.05%, per-factor IC momentum_20 0.0083
  (identical across runs) / roe 0.0006 / netprofit_yoy 0.0001, combo -0.0038,
  financial ann_date coverage 100% on both fields. Financial ICs ~0 on this
  small cross-section / short window -- disclosed as plumbing validation.

Tests: 249 passed (was 234): +10 multifactor pipeline, +1 multi-field as-of,
+4 baseline report surface. Demo numbers unchanged (ic 0.96 / annual 0.84).
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