feat(factors): reproduce ridge minute-return factor (PR-K, sign transfers but Reject on monotonicity + redundancy) - #71
Merged
Conversation
Reproduce the FIFTH factor of the Kaiyuan market-microstructure series #27 (量岭分钟收益) and run it through the frozen StandardFactorEvaluator on the same CSI500 cell as PR-C..PR-J. Opens a FOURTH statistic family (a RETURN, after a count / a timing moment / two price levels) and is the report's only NEGATIVE peak/ridge/valley factor, so it also tests whether the SIGN transfers along with the family. The minute taxonomy is REUSED verbatim from data/clean/intraday_volume_prv.py (unmodified by this change): a ridge is an eruptive minute that is not an isolated peak. The four already-merged factors stay bit-identical, locked by hand-value tests here. Pinned choices, none of which the report specifies (all disclosed on the spec and in the config header): - minute return close_t/close_{t-1} - 1 with a WITHIN-DAY lag, so each day's first visible bar carries no return and no return crosses a day boundary; exact 60s adjacency is deliberately not required, so the post-lunch bar returns against the last bar before the gap; - RAW unadjusted closes (the adjustment factor is constant within a day and cancels in the ratio; the within-day lag excludes the one bar that could straddle an ex-date); - the daily aggregate is a SIMPLE SUM, not a compounded product: ridge minutes are non-contiguous, so a holding-period reading does not apply; - a day is valid iff >=100 classifiable bars and >=10 ridge bars carrying a valid return (counted after the guard), NaN below 10 valid days. The runner surfaces net_long_short_by_cost explicitly: this factor's sign of -1 triggers a known frozen-layer defect in aligned_spread_*, which computes sign * (gross - cost) and so adds costs back. The frozen layer is not patched. Adds: data/clean/intraday_ridge_return.py, RidgeMinuteReturnFactor, qt/eval_ridge_minute_return.py, config/phase_k_ridge_minute_return.yaml, a run-eval-ridge-minute-return subcommand, and two test files (+54 tests).
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 ridge minute-return factor — the fifth factor of Kaiyuan Securities microstructure #27 (loop reproduction 8/10) and the only negative-sign factor in that report. This completes a fourth statistic family (RETURN) on top of the count / timing / price-level families already reproduced from the same report.
Result: Reject / Reject — but the sign transferred. IC −0.0312, ICIR −0.381 (aligned CI lower bound 0.320 > the 0.30 bar), NW-t −13.31, win rate 0.652, OOS sign-consistent (train −0.0278 / test −0.0346). Prototype (persisted pre-run, same seed-20260720 30 names as PR-I/PR-J, symbol list byte-identical): mean RankIC −0.0314, PASS. So the report's behavioural claim — ridge minutes represent retail follow-on trading whose accumulated return reflects over-reaction and therefore predicts negatively — reproduces at the IC level.
It fails on exactly two things:
A clean experimental control. PR-K sits almost exactly on PR-J's machine profile — turnover 2.108 vs 2.101, half-life 8.06 vs 7.99, cross-section 472.8 vs 475.2, ridge day-validity 51.2% vs 51.5% (same mask, same scarcity gate) — which isolates the difference to the statistic, not the pipeline.
Tradability, third data point: net long-short is negative at every cost scenario (−0.001983 / −0.004092 / −0.008309), the worst of the three. Gross long-short is only +0.000125/period against turnover 2.108 — costs swamp the edge roughly 16:1 at base fee (cumulative −90.9%).
aligned_spread_*is unreliable here because this factor is sign=−1 and triggers the frozen layer's cost-sign defect (sign*(gross−cost)=−gross+cost); the frozen layer was correctly NOT patched, and the warning is carried in the module docstring, config header, CLI output and run log — every place the implementer controls.Factor
ridge_minute_return_20: reuses the merged ridge mask with zero changes to the shared taxonomy (git diff main -- data/clean/intraday_volume_prv.pyis empty); minute return close_t/close_{t−1}−1 with within-day lag on raw closes; daily aggregate = simple sum of selected ridge returns (pinned and test-discriminated against Π(1+r)−1, which differs >4× on the hand cases); day valid = ≥100 classifiable AND ≥10 ridge-with-return; factor = sum (not mean) of daily sums over the trailing 20 valid days; NaN below 10 valid days. Pre-registered sign −1.Disclosed interpretation edge (task-card gap, surfaced by the implementer): the within-day lag was pinned without a 60s-adjacency requirement, so the 13:01 bar carries a ~91-minute across-lunch return summed alongside 1-minute returns. Disclosed on the spec and locked in a test; the same-slot baseline mitigates systematic selection (13:01 is compared against its own history). First parameter to vary if this factor is ever revisited.
Test plan