feat(factors): reproduce valley-relative-VWAP factor (PR-I, both assessed axes PASS → Watch) - #69
Merged
Merged
Conversation
Third reproduction from the Kaiyuan market-microstructure series #27 (reportId 4957417, §4) — 量谷相对加权价格 — and the first from a different FAMILY: PR-F counted volume peaks and PR-H measured their timing (both null); this factor prices the VALLEYS. The minute classification is REUSED verbatim from data/clean/intraday_volume_prv (prepare_visible_minute_bars + peak_mask_for_symbol), so the three factors can never drift apart and a different verdict here cannot be an artefact of a differently-implemented taxonomy. Two additive exposures make the reuse possible: - prepare_visible_minute_bars gains extra_columns (default () -> byte-identical output for PR-F / PR-H); PR-I passes ("amount",) so a volume-weighted price can be formed. - peak_mask_for_symbol exposes `valley` (== the module's internal `mild`, i.e. classifiable & ~eruptive) as a first-class boolean. Both are behaviour-preserving: volume_peak_count_20 and peak_interval_kurtosis_20 are bit-identical before/after (verified numerically against a `main` worktree on 26,440 real cached symbol-days — identical SHA-256 of the raw float64 buffers for both factors), and all 59 existing PR-F / PR-H tests pass unchanged. Factor: valley_relative_vwap_20 = trailing-20-VALID-day mean of the daily ratio (valley VWAP) / (whole visible day VWAP). PINNED choices, disclosed on the spec: 1. each VWAP uses the aggregation identity sum(p*v)/sum(v) == sum(amount)/ sum(volume) — the day's real volume-weighted price, not a close proxy; 2. bars with non-finite or non-positive volume OR amount are dropped from both sums; the guard runs at the SUMMATION step only, never before classification, which is what keeps PR-F's same-slot baseline bit-identical; 3. RAW unadjusted prices are correct here — the adjustment factor is constant within a day and cancels in the ratio (as in PR-D amplitude / I5b limits); 4. DEVIATION FROM THE REPORT, disclosed: our day VWAP spans the PIT-visible window 09:31-14:50 only, while the report uses the full day. Reading the close would be lookahead at our 14:50 decision time; 5. a day is VALID iff >= 100 classifiable bars AND >= 20 TRADABLE valley bars (counted after the guard) AND positive volume in both denominators; NaN below 10 valid days (honest missing). Pre-registered sign +1 (report full-market RankIC +8.69% / RankICIR 4.44; CSI500 sub-domain long-short 9.94% / IR 1.26 — a LOOSE reference only, never an expected value). Prototype sanity on 30 random CSI500 members with minute cache, 2022-2024: mean daily cross-sectional RankIC +0.0388 over 706 days, positive as pre-registered (tmp/design/pr_i_prototype_sanity.txt). Tests cover two hand-computed ratio cases in closed form (one below 1, one above, so an inversion cannot pass both), the aggregation identity recomputed the long way from per-bar prices, the positive-trade guard (degenerate bars are classified as valleys yet excluded from the VWAP, and do not count toward the valley floor), the all-mild ratio==1 identity, the three validity gates, the trailing-mean window, PIT non-influence, per-symbol isolation, and the reuse non-drift locks.
Seventh real factor evaluation: runs ValleyRelativeVwapFactor through the FROZEN StandardFactorEvaluator on real cached A-share data, mirroring the PR-F / PR-H runners so the cell is directly comparable to its siblings. New run mode `run-eval-valley-relative-vwap` + config/phase_i_valley_relative_vwap .yaml. The eval CELL is IDENTICAL to PR-C..PR-H (CSI500 000905.SH 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); the only substantive difference is the subject factor, which is minute-derived and computed by the runner from the intraday cache rather than listed as a daily factor. CACHE-ONLY: the per-symbol minute read goes through IntradayParquetStore.read_range, which has no fetch closure, so stk_mins live calls are provably zero — an uncovered symbol yields no rows and is disclosed as empty rather than fetched. The read is memory-bounded (one symbol's history is aggregated to its daily series and discarded before the next, so the multi-year all-symbol minute panel is never materialized). Forward returns are formed only at the evaluator/analytics boundary from ctx.price_panel; the factor computation never sees one. The evaluator runs TWICE: no book (Incremental NOT_ASSESSED) and with the confirmed book. The with-book run carries extra weight for this factor — a relative PRICE LEVEL is a plausible cousin of a value signal, so it is what says whether this is a genuinely new bet rather than value in disguise. Tests are network-free: the cache-only loader (live_calls==0, empty symbols disclosed, hand-computed ratio flows through, and the no-eruption day yields exactly 1.0), the blocked-when-nothing-cached guard, and the two-run evaluation core over synthetic panels (all 8 mandatory sections, Incremental NOT_ASSESSED vs assessed, md/json written, both dashboard PNGs > 20KB, no-book never reaching Adopt).
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 valley-relative-VWAP factor — the third factor of Kaiyuan Securities microstructure #27 (loop reproduction 6/10), built in the spirit of their smart-money factor. This is the strongest reproduction in the loop so far: both assessed axes PASS.
Factor
valley_relative_vwap_20: reuses PR-F's peak/ridge/valley classification unchanged (valley = classifiable & ~eruptive); per valid day, ratio = valley VWAP / all-visible VWAP computed via the amount/volume aggregation identity (Σamount/Σvolume on each leg, bars with volume<=0 or amount<=0 dropped); factor = mean daily ratio over the trailing 20 valid days (day valid: ≥100 classifiable bars, ≥20 tradable valley bars, positive denominators; NaN below 10 valid days). Raw prices are fine — the ratio cancels the adjustment factor within a day. Pre-registered sign +1 (report: RankIC +8.69%, the highest in that report).Disclosed deviation: our 'day VWAP' covers only the visible 09:31–14:50 PIT window; the report used the full day.
Result (CSI500 2021-07..2026-06, cache-only minutes 995/996, stk_mins_live_calls=0): IC +0.0327, ICIR 0.506 (CI low 0.436 > the 0.30 bar), NW-t 17.28, win rate 0.70, quantile monotonicity 1.0 (perfect, Q1→Q5 NAV 1.20→1.95), OOS sign holds in BOTH holdout subperiods (+0.0296 / +0.0358). Incremental axis PASS (orthogonalized ICIR CI low +0.279 > 0.15, point +0.343) — real signal beyond the value/low-vol book. Deployment Watch (structural cap: exploratory + Tradable NOT_ASSESSED). Prototype sanity, run and persisted BEFORE the full run (
tmp/design/pr_i_prototype_sanity.txt): 30 names, 706 days, mean RankIC +0.0388, PASS.Honest caveat — strong IC does not mean tradable here. The net long-short spread is −2.35%/yr at every cost scenario: gross Q5−Q1 is ~0.000387/period while round-trip cost at 1× fee is ~0.000473/period. This is arithmetically correct (sign is +1, so no aligned-spread sign issue applies) — a genuine 'positive IC, but turnover eats the gross spread' result. The Incremental PASS is an IC-orthogonality statement, not a profitability claim.
Scientific value of the three-factor set from this report: peak-COUNT (PR-F) came out weakly positive, peak-INTERVAL-KURTOSIS (PR-H) came out null, and this price-level statistic passes cleanly — so the peak/ridge/valley taxonomy does carry signal in our cell; the count/timing statistics built on it are what fail.
Test plan
volume_peak_count_20andpeak_interval_kurtosis_20both bit-exact (not merely 'tests pass').