feat(factors): reproduce volume-peak interval-kurtosis factor (PR-H, null result → Reject) - #68
Merged
Merged
Conversation
Reproduces the SECOND factor of the Kaiyuan market-microstructure series #27 (reportId 4957417, section 6): the kurtosis of the gaps between consecutive same-day volume peaks, pooled over the trailing 20 valid trading days. Same MACHINE as PR-F, different STATISTIC. The volume-peak identification is REUSED from data/clean/intraday_volume_prv.py rather than re-implemented, so the two factors can never drift apart. That required a behaviour-preserving additive extraction of two helpers from the existing code: - prepare_visible_minute_bars: the PIT truncation + volume guard + slot column - peak_mask_for_symbol: the same-slot strictly-prior baseline, eruptive/mild classification and the mild-neighbour peak rule compute_volume_peak_count now calls both; nothing else about it changed. The extraction is provably inert: the pre-refactor and post-refactor functions were run against the same real cached minute bars for 12 symbols (7906 factor rows, 2022-2024) and compared with assert_series_equal(check_exact=True) -- identical. The 26 existing PR-F tests pass unchanged. Two interpretations the report leaves open are PINNED and disclosed on the spec: 1. an interval is measured in TRADING MINUTES (the tradable-slot difference inside the day's visible bar sequence), so the lunch break costs nothing -- peaks at 11:29 and 13:02 are 3 apart, not 93. Measuring wall clock would inject a fixed ~90-minute spike into every straddling distribution and dominate the kurtosis. 2. kurtosis = Fisher excess, bias-corrected (the pandas .kurt() / scipy fisher=True bias=False convention; a normal sample sits at 0, not 3). A day with fewer than 2 peaks contributes 0 intervals but remains a valid day. Two honest-missing gates: fewer than 20 pooled intervals, or a zero-variance pool, yield NaN rather than a fabricated number. Pre-registered sign +1. Tests cover two hand-computed kurtosis values (intervals [2,2,2,8] -> exactly 4.0; [2,3,4,5,6,2] -> exactly -1.48125), estimator alignment with pandas .kurt() on random samples, the lunch-break trading-minute semantics, the single-peak and zero-peak day cases, both NaN gates, PIT invariance to post-14:50 and future bars, per-symbol isolation, and an anti-drift lock asserting the intervals come from exactly the peaks PR-F counts.
…PR-H) Runs the PR-H factor through the FROZEN StandardFactorEvaluator on real cached CSI500 data via a new run-eval-peak-interval-kurtosis subcommand. The eval cell is IDENTICAL to PR-C..PR-G (CSI500 PIT membership, 2021-07-01..2026-06-30, daily rebalance, OOS split 2024-01-01, book value_ep/value_bp/volatility_20, fee 0.001), so this run is directly comparable to its PR-F sibling: same peaks, different statistic. Cache-only: the per-symbol minute read goes through IntradayParquetStore, which has no fetch closure, so stk_mins live calls are provably zero; a symbol with no cached minute is disclosed as empty rather than fetched. Memory-bounded -- one symbol's minute history is aggregated to its daily series and discarded before the next. The evaluator runs twice (no book -> Incremental NOT_ASSESSED; with book -> Incremental measured). Real run: 995/996 symbols covered, stk_mins_live_calls=0, 1139746 factor rows, 462.7s. RESULT IS NEGATIVE and reported as such. Rejected on both axes: rank IC mean +0.000217, ICIR +0.006 (95% CI -0.051..+0.063), Newey-West t 0.232, win rate 52.2%, N_eff 1190; the expected sign is not consistent across the two holdout subperiods (+0.000683 / -0.000179). With the book, incremental ICIR +0.046 -- redundant. Quintile monotonicity is +0.6 (test +0.7) but the aligned Q5-Q1 spread is -11.0%/yr, i.e. the ordering is not worth anything net. The report's full-market monthly figures (RankIC +7.19%, RankICIR 4.63) do NOT reproduce on this daily CSI500 cell; the report gives no CSI500 sub-domain number for this factor and none is invented.
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 interval-kurtosis factor — the second factor of Kaiyuan Securities microstructure #27 (loop reproduction 5/10). Same peak taxonomy as the merged PR-F peak-COUNT factor, different statistic: the kurtosis of the distribution of gaps between consecutive same-day volume peaks.
Result: an honest NULL. IC mean +0.000217, ICIR 0.0058, NW-t 0.232, win rate 0.522 (coin flip) → Predictive FAIL (OOS sign not consistent across both holdout subperiods) → Reject / Reject. The report claims RankIC +7.19% / RankICIR 4.63 with 13 consecutive positive years — this is the largest reproduction gap in the loop so far, and it does not survive on our daily CSI500 cell.
Independently verified as a real null, not a defect (review): an independent from-scratch reproduction on a different 35-symbol sample got mean IC +0.0095 — same near-zero order, sign-consistent with the pre-registered +1. Factor coverage is healthy (NaN rate 2.86%, mean cross-section 929/995) and the value distribution is non-degenerate (std 5.93, 8857 distinct values), so this is a genuine 'does not rank-correlate with next-day returns' finding rather than a data-starvation or constant-factor artifact.
Factor
peak_interval_kurtosis_20: reuses PR-F's peak identification unchanged; per valid day, gaps between consecutive peaks measured in trading minutes (11:30→13:01 is 1 apart, not 91); pooled over the trailing 20 valid days; factor = Fisher excess, bias-corrected kurtosis (pandas .kurt() convention); NaN if pooled intervals < 20 or zero variance; a day with <2 peaks contributes 0 intervals but stays valid. Both pins are report-silent and disclosed on the spec. Pre-registered sign +1.Reuse refactor — verified bit-identical.
data/clean/intraday_volume_prv.pywas refactored (+97/−32) to extractprepare_visible_minute_barsandpeak_mask_for_symbolfor reuse. Review ran two independent numerical equivalence checks (synthetic incl. bad-data and empty paths; 10 real cached symbols / 4,740 rows) comparing main vs branchcompute_volume_peak_count: 0 mismatches, bit-identical; all 26 PR-F tests pass unchanged.Test plan