feat(factors): bind valley_price_quantile + fold binding.py into code_hash (D5 C4b) - #107
Merged
Merged
Conversation
…+ fold binding.py into code_hash (D5 C4) vpq was the only unbound minute factor: its residualize is a per-date cross-sectional OLS on a T-1 20-day reversal, so its value depends on the loaded universe AND on the daily close panel. * binding: new vpq stream entry (per_symbol = compute_valley_price_quantile_stats -> raw_qbar intermediate, stored per D4c) with a DECLARED combine_daily input (DailyCombineInput on MinuteStreamBinding — a declaration the engine consults, never isinstance dispatch, red line #5); vpq joins CROSS_SECTIONAL_MINUTE_FACTORS; the _DEFERRED machinery is retired. Existing table entries are byte-untouched. * engine: materialize.combine_daily_panel loads the declared close column, applies the decision lag (row d carries close_{d-1}) and trims to the declared warmup (reversal_days + 2 trailing trading days — rev20's 21 closes + the lag's leading NaN row, section-6.18 nested-lookback trap); service._assemble routes it to the combine at read-assembly over the requested universe. The pooled saturation loop reads the value's output dates off the intermediate for daily-bound factors (their combine contract preserves the intermediate's rows exactly). * third path: reversal_20_shifted (internal T-1 OFF on the shifted panel) is algebraically identical to the legacy reversal_20 on the un-lagged panel — pinned bit-for-bit on BOTH sides (binding unit test with a sabotage control at max|diff| 4.6e-2 proving non-vacuity; service read-through vs the legacy per-symbol-stats + one residualization reference at max|diff| 0.0, plus a hard zero-NaN-divergence assertion over the left-edge 22-trading-day window). * code_hash: factors/compute/minute/binding.py joins the enumerated shared set — it carries load-bearing value semantics while the D4c shape validation covers only column names. ONE-TIME EFFECT, disclosed: every factor's store key changes (measured 18/18 registered classes), so a factor store filled before this commit is wholesale invalid and must be recomputed once (dev-only artifact). * decision-cutoff visibility: vpq is now measured through its per-symbol qbar stage (routed by the binding tables); the measured-clean set is 11 again.
vpq is the eleventh and last subject the unified exec-only runner could not serve; the C4b binding (declared daily combine input) made the engine path ready, so the readable deferral is retired and vpq rides the SAME service.panel read-through as every other factor. * mechanism B disclosure (catalogue §三): NeutralizationCoverage has no diagnostics sink — the runner reduces it AFTER the panel read from the stored raw_qbar intermediate + reversal_20 on the bundle's close panel (pinned bit-for-bit equal to the combine's shifted-panel path) + the served residual, and ships it as the add-Section "neutralization_coverage". The legacy runner only LOGGED it, so the frozen artifact carries no such section. * factors.service.stored_payload: the disclosure's read-back of the STORED payload (value for most factors, per-symbol intermediate for a cross-sectional one) through the same key/fingerprint/coverage engine as panel(), sliced to exactly the request (never a superset left by a wider earlier fill); registered as a probed symbol entry point. * reconcile registration (catalogue §七之四, pre-registered): the new section's JSON subtree is matched by section NAME (never bare index) and its MD lines by dataclass-derived prefixes; any other added section stays unregistered and fails. Blank separator lines register only alongside a registered section. * the runner log/report state vpq's real cold-fill cost in the same terms as the other factors: pooled + cross-sectional + daily-bound means the saturation load (declared 2015-01-05 floor) before the store is warm. Gates: pytest 2518 passed; ruff clean; node-ID diff vs afb697c is +12/-1 (the one removal is the replaced readable-deferral test; zero existing tests squeezed out).
…chors mode (D5 C4) Measured on valley_price_quantile's first real anchors reconcile: the anchors harness requested only the anchor rows' symbols, but a cross-sectional factor's served value is a function of the REQUESTED universe (D4c) — 5 names < the combine's min_cross_section floor served all-NaN and 4/5 rows false-FAILED. stores_intermediate factors are now served over the full config universe and the anchor cells are looked up inside it (the hand side was computed over that universe); per-symbol factors keep the cheap anchor-only request. intraday_amp_cut shares the property, so C5 would have hit the same wall. After the fix, vpq anchors: 5/5 — 4 rows reconcile (2 at rel=0.0, 2 at ~1e-15) + the 2021-07-30 row lands in the registered warmup_left_extension class. Also registers the vpq real-reconcile decomposition in the catalogue (§七之五): reports 4/4 OK; the panels-leg differences fully attributed to three blocks — the registered warmup_left_extension (24,516 cells, with a structural partial-first-month that defeats the pooled monthly monotonicity rule), the 600623.SH classification-flip block (19,143 cells, same symbol/window as threshold_flip_tail, one directly affected name + OLS-coefficient contamination), and float dust on near-zero residuals that the rel-based float tail misjudges. Class-boundary revisions are left to the lead; the facts are registered, nothing rounded away. Gates: pytest 2518 passed; ruff clean.
…ross-sectional factors (D5 C4)
…uctural partial month (D5 C4 review)
StackOverFlow11
added a commit
that referenced
this pull request
Jul 28, 2026
…5-c4b docs: progress for refactor D5 C4b (PR #107)
This was referenced Jul 30, 2026
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.
D5 C4 (part b) — bind valley_price_quantile + fold binding.py into code_hash
Second half of D5 C4: binds the last unbound minute factor into the service/materializer machinery, closes the "binding.py is load-bearing but outside code_hash's reach" follow-up (lead-adjudicated), and enables the factor in the unified runner with a full three-mode reconciliation.
What changed (5 commits)
valley_price_quantile_20residualizes via per-day cross-sectional OLS ⇒ its value depends on the universe, so per D4c the store holds the per-symbol intermediate (raw_qbar) and the cross-sectional combine runs at read-assembly. Its combine needs dailyclose— the one engine extension: a declarativeDailyCombineInput/combine_dailyslot onMinuteStreamBinding, fed bymaterialize.combine_daily_panel(decision-lagged close, 22 lagged days of left-end warmup so the shifted panel's first row is not a spurious NaN; under-warm clamping now logs one disclosure line). Existing 11 binding entries: zero diff (pinned).reversal_20'sshift(1)) would be double-lagged by the decision view to close_{d−2}; the landed path passes a shifted panel and disables the internal T-1 — measured bitwise identical (Series.equals) to the legacy path, with break controls (~8.5e-02) proving the tests can fail, re-verified by the reviewer with different fixtures and novel perturbations (negative/zero closes, NaN injection).binding.pyfolded intocode_hash(lead ruling Q2): the file carrying load-bearing combine semantics now moves every registered factor's store key (mutation-verified: touch binding.py → 18/18 hashes change; touch an unrelated module → 0/18). One-time full invalidation of the dev-onlyartifacts/factor_store/disclosed; over-invalidation direction is safe and documented.run-factor-eval;NeutralizationCoveragewired via mechanism B (raw intermediate re-read through the newservice.stored_payload— same key/fingerprint/coverage engine, not a second data path); anchors-mode fixed to serve cross-sectional factors over the full universe (5 anchor symbols <min_cross_section=10structurally yields all-NaN — would have bittenintraday_amp_cutin C5 too).threshold_flip_contamination(window [2023-06-01, 2023-07-14]; direct symbol 600623.SH ≤ 2e-04; others ≤ 1e-06; ≤ 20,000 cells) for the PRV critical-bar flip × per-day OLS contamination family — input-side change excluded (ledgers zero writes since before the freeze; qbar bitwise across geometries, max 1.1e-16, independently recomputed by the reviewer); ③ float tail gains an abs floor (≤ 1e-12 is dust regardless of rel, fixing near-zero-residual rel misfire) and a per-class cap (bars-only 101, cross-sectional 1,000).Real-run reconciliation (cache-only,
stk_mins_live_calls=0)vpq three modes, independently rerun by the reviewer with matching counts: panels rc=0 — warmup=24,568 (monthly 902 → 19,980 → 3,667 → 19, exempt 2021-07 structurally, then monotone), float_tail=883 (≤1,000), flip_contamination=19,177 (≤20,000), unclassified=0; reports 4/4 OK (registered classes only); anchors 5/5 (ok=4 incl. two rel=0.0 and two ~1e-15; warmup=1). Fresh artifacts
factor_eval_valley_price_quantile_20_exec_*: verdicts Watch/Watch (exec no-book predictive=PASS, ic=0.0270/icir=0.426; with-book incremental=PASS) — same verdicts as the frozen baseline, numeric shifts all inside the registered warmup-aggregate effect.Review
Two rounds, separate executor: APPROVE-WITH-NITS (0 CRIT/HIGH/MEDIUM; LOW-1 positional-exemption bypass + NIT-1 silent clamp) → fixes → delta re-review: both CLOSED, PR approved, including the reviewer re-running their own bypass probe against the new code (FAILs), reverting the fix themselves to prove the tests bite, and independently recomputing the qbar double-geometry evidence (1,241/1,241 cells, max|diff|=1.11e-16).
Gates (independently rerun by the lead)
pytest2533 passed (node-ID diff: 0 pre-existing displaced),ruffclean,run-phase0anchor ic 0.9600 / annual 0.8408,validate-config32/32, secret-value scan 0,exec_baseline_freeze --verify77/77 @45c14aa.NOT in this PR
Remaining 7 factors' real-run reconciliation + full four-leg audit (C5), old-runner deletion (C6),
mmp_ew(D6c), run-registry (D7). Known non-blocking registrations: LOW-1's residual semantic edge (a factor whose first finite value genuinely starts mid-window exempts that month — self-consistent with the ruling), the C4a LOW-1/LOW-2 items (C5 hardening list).