feat(factors): D1 — factor registry, PanelField requires, FactorSpec contract v1.0 - #86
Merged
Merged
Conversation
Factor-refactor D1, first slice (design v3.2 §3.1; D0 taxonomy in docs/factors/refactor_d0_contract.md). EXPLICITLY AUTHORIZED change to the previously frozen FactorSpec contract (stated outright per the #74 precedent): three declaration dimensions become MANDATORY on every spec — * requires: endpoint-level PanelField inputs (R25 endpoint-only; R8 endpoint closure + R7 market_daily field-level validation fire at declaration time inside PanelField); * adjustment / overnight_boundary: value domains are the data.availability_policy enums (single source, no second list). A missing declaration is a readable definition-/construction-time error, same as the pre-existing mandatory fields; the metaclass double-check and expected_ic_sign pre-registration are untouched. The D0 §1.1 static check (adjustment=none must not require a price-channel field) fires at construction; the converse is deliberately unchecked (VWAP-ratio factors carry price via sum(amount)/sum(volume) with no OHLC field). All 18 shipped Factor classes declare per the D0 pre-assignment table (evidence cited in each spec docstring): the finishing-line 14 land row-for-row, including minute_ideal_amplitude's CROSSED_DISCLOSED candidate whose missing PR-L-style deviation measurement is pinned in the docstring as a D2 obligation; valley_price_quantile additionally requires market_daily close (the T-1 rev20 leg reads the qfq daily panel — a real second endpoint beyond its minute fields). Factor math untouched: zero compute() changes.
…ecks Factor-refactor D1, second slice. factors/registry is THE one name -> class mapping (red line #4): register() re-runs the metaclass mandatory- spec validation (red line #1 — reused, not reimplemented), demands a concrete Factor, checks a static class-attribute spec against the class name at definition time, and rejects duplicate keys and mutually- prefixing prefixes readably. build() reproduces the retired _build_factors params coercion exactly (same int()/str() calls, same defaults) and enforces config name == instance name == spec.factor_id — property specs are checked on the built INSTANCE, never forced back into class attributes (pit #2). requirements() aggregates deduplicated PanelField needs for the D4 materializer; require_legal_pairing() forwards the availability-policy view x basis legality check (deep wiring is D4 — D1 only guarantees it is callable from the factor layer). builtin.py is an explicit import + register list (pit #3: no pkgutil auto-discovery) covering the FULL 18-class surface in the retired chain's dispatch order, including the 11 minute-derived factors the chain never dispatched (eval runners construct them directly today). Collision tests run on fresh FactorRegistry instances so the default registry is never polluted.
Factor-refactor D1, final slice — the dispatch replacement that IS D1's identity. _build_factors' if/elif chain (qt/pipeline.py:769) is retired for registry.build(): same classes, same params coercion, same config order; the no-enabled-factor and duplicate-name guards stay verbatim in _build_factors; unknown names and name/params mismatches keep readable errors (now single-sourced in the registry). The now-unused factor-class imports are dropped; FinancialFactor / ValueFactor / SUPPORTED_FIELDS stay — the _maybe_enrich_* isinstance dispatch is D4/D6 scope, untouched. qt.config.FactorCfg gains the third naming timepoint: every ENABLED factor entry resolves its name in the registry at parse time, so validate-config / load_config reject an unknown name before any run (pydantic ValidationError subclasses ValueError and carries the registry's message). Disabled entries stay unchecked, mirroring _build_factors. test_unknown_factor_name_raises moves to the parse-time boundary accordingly; the direct registry.build unknown-name path is locked in tests/test_factor_registry.py. Behavior anchors: run-phase0 demo unchanged (ic 0.9600 / annual 0.8408), all 31 configs validate, full suite green — factor math untouched by D1.
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.
Second step (D1) of the factor-layer refactor per v3.2 design (§3.1 / §九 D1). Identity of this step: dispatch swap + declarations — zero factor-math changes.
What (+1751/−69, 19 files, 3 commits)
factors/requires.py—PanelField(field, source), endpoint-only (R25: no factor-on-factor refs); construction-timerequire_known_source(R8) + field-level market_daily validation (R7) via the D0 policy module.factors/spec.py→ contract v1.0 (explicit authorized change to the frozen contract): three new mandatory declaration dims (requires/adjustment/overnight_boundary), values coerced to thedata.availability_policyenums (single source); D0 §1.1 static check (adjustment=noneforbids OHLC requires, one direction only, converse documented legal). Existing enforcement untouched: metaclass double-check,expected_ic_signpre-registration, property-spec deferred validation (factors/base.pydiff empty).factors/registry/—register/build/requirements+ explicitbuiltin.pylist (no pkgutil); three-timepoint naming checks (define / build / config-parse); exact-before-prefix dispatch preserving the old chain's semantics, coercions and error messages;require_legal_pairingforwarding (deep wiring lands D4). All 18 shipped Factor classes registered (incl. the 11 minute surfaces the old chain never dispatched — registry now covers the full D4 surface).minute_ideal_amplitudelands CROSSED_DISCLOSED-candidate with the missing bias-measurement duty pinned as a tested "MISSING…D2" docstring debt.qt/pipeline.py::_build_factors— body becomes a pureregistry.build()loop (if/elif chain retired, −55);_maybe_enrich_*, all 11 eval runners, intraday framework: zero changes (verified per-file empty diffs).qt/config.py— enabled factor names resolve against the registry at parse time (fail-fast enhancement; disabled entries unchecked, mirroring historical behavior).Zero-math evidence
grep '^[+-].*def compute'over compute diffs = zero hits (review, hunk-by-hunk).Disclosed behavior deltas (intentional / accidental)
int(params.get("window",20))for every factor; a boguswindowon a financial/value factor used to raise accidentally — no longer reproduced. Verified: no config or test depends on it.Acceptance
703bd3ebefore docs PR docs: record factor-layer refactor kickoff (v3.2 + review + D0) #85 (which re-synced AGENTS.md) merged; two-dotgit diff mainshowed docs: record factor-layer refactor kickoff (v3.2 + review + D0) #85 inverted. Merge-base diff (main...HEAD) confirms 19 files, AGENTS.md/CLAUDE.md untouched. 1 LOW recorded as a future-facing note (_PRICE_CHANNEL_FIELDSmatches field names regardless of source — no conflict among current 18 factors; revisit if a non-market endpoint ever names a column "close").validate-config31/31, phase0 anchor verbatim, secret scan on diff 0 hits.Next (still step D1 per v3.2 §九): end-of-D1 panel freeze (R11) — dump the 14 closing-set factor value panels + manifest into git, before any D2 math migration.