feat(factors): reproduce volume peak-minute-count factor (PR-F, weak-positive Reject) - #66
Merged
Merged
Conversation
Reproduce the Kaiyuan market-microstructure series #27 flagship volume-peak-minute-count factor as a daily PIT-safe column derived directly from the 1min cache. New data/clean/intraday_volume_prv.py::compute_volume_peak_count classifies every visible minute against its same-slot STRICTLY-PRIOR 20-day baseline (eruptive if vol > mu + sigma else mild) and counts the eruptive minutes whose both 1-minute same-session neighbours are mild (peaks) over the trailing 20 valid days; ridge / session-boundary / unclassifiable-neighbour minutes are not peaks. Vectorized per symbol via a day x slot volume pivot (rolling over the day axis + shift(1) for the strictly-prior baseline) and a within-day 60s-gap neighbour test, so no per-(day,slot) python loop and no cross-day or cross-symbol leakage. Additive VolumePeakCountFactor mirrors the three prior minute-derived factors (is_intraday=False, close_to_close, pre-registered sign +1).
Add run-eval-volume-peak-count: the fourth real factor evaluation. New qt/eval_volume_peak_count.py runs VolumePeakCountFactor through the frozen StandardFactorEvaluator on real cached CSI500 data (2021-07..2026-06, daily, OOS split 2024-01-01), cache-only (minute read is provably live-call-free), twice (no book / value_ep+value_bp+volatility_20 book). CLI subcommand + config mirror the PR-C/D/E cell. Real run (995/996 covered, stk_mins_live_calls=0, 279M raw 1min rows, ~7.3min): IC mean +0.0177 (sign matches pre-registered +1), NW-t 7.43, win-rate 0.591, Q1->Q5 monotonicity Spearman 0.90, aligned Q5-Q1 spread +1.79%/yr; but ICIR 0.21 (CI lower 0.151) is below the default 0.3 bar, so the Predictive axis FAILs and the deployment verdict is Reject. Incremental beyond the book is +0.0074 (ICIR 0.111, CI lower 0.054 < 0.15) -> also Reject. A weak-but-positive daily-CSI500 signal that does not clear the (uncalibrated default) deployment bar; the report's monthly full-market ICIR 4.36 is a loose reference only.
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
Reproduce the volume peak-minute-count factor — the flagship of Kaiyuan Securities market-microstructure series #27 (volume peak/ridge/valley, 2025-07-20) — as loop reproduction 3/10 (PR-F). Outcome: sign transferred (+1 as pre-registered) but strength fails the pre-registered ICIR gate → honest Reject on both runs. Not a documented-negative (unlike PR-E): the signal is real, monotone and statistically significant, just weak on our cell.
Factor
volume_peak_count_20: per-minute-slot eruptive/mild classification vs the STRICTLY-PRIOR 20 same-slot days' μ+1σ (rolling+shift(1), day excluded from its own baseline; <10 obs unclassifiable); peak = eruptive bar whose exact ±60s same-session neighbours both exist and are mild (lunch break / missing minutes / session boundaries auto-break adjacency); factor = count of peak minutes over trailing 20 VALID days (valid = ≥100 classifiable bars; <10 valid days → NaN). PIT:available_time <= d 14:50uniformly before everything. Pre-registered sign +1 (informed-trading participation; report: full-market RankIC +10.62%, CSI500-domain L/S +14.96%/yr).Result (CSI500 2021-07..2026-06, cache-only minutes 995/996, stk_mins_live_calls=0, 439.5s): prototype RankIC +0.0333 (sign PASS) → full run IC +0.0177, ICIR 0.210 (CI [0.151, 0.270]), NW-t 7.43, win rate 0.591, Q1→Q5 monotonicity 0.90, aligned spread +1.79%/yr; incremental ICIR 0.111 (< 0.15 gate). Predictive FAIL (ICIR lower CI 0.151 < 0.30) → Reject / Reject. The gate thresholds are the contract's pre-registered defaults (flagged UNVALIDATED-BY-DATA in the design doc); the honest weak-positive is on the record.
Components (all additive; frozen
analytics/evaluntouched):data/clean/intraday_volume_prv.py(vectorized day×slot pivot + rolling/shift baseline) ·VolumePeakCountFactor· runnerqt/eval_volume_peak_count.py+ CLI +config/phase_f_volume_peak_count.yaml· 26 new tests (hand-computed peak counts, strictly-prior-baseline invariance, cross-session non-adjacency, post-14:50 invariance, gates, cache-only loader, two-run + dashboards).Test plan