feat: I5c MMP minute-factor study (exploratory) - #37
Merged
Conversation
Add the exploratory Minute Microstructure Pressure (MMP) factor to the I3 minute -> daily aggregation. Per 1min bar t (single symbol/day session, sorted by bar_end): mid=(high+low)/2; S=(close-mid)/mid; V=sqrt(volume/median(vol[t-20:t])); B=|close-open|/(high-low+eps); R=(high-low)/(mean(hl[t-20:t])+eps); MMP_t=S*V*B*R, eps=1e-6. - compute_minute_mmp: rolling baselines use ONLY the prior 20 bars t-20..t-1 (rolling(20).shift(1)) — never bar t, never a later bar, never the prior day's tail; the first 20 bars of each session are NaN. Invalid denominators (mid<=0, median vol<=0, NaN baseline) yield NaN, never inf. - new feature key 'mmp_ew' -> column intraday_mmp20_ew_0930_1450 = the EQUAL -WEIGHT mean of valid MMP_t visible at the cutoff (the volume term is already inside MMP_t; the daily aggregation is NOT additionally volume-weighted). No valid minute -> NaN. - mmp_ew is SELECTABLE-ONLY: DEFAULT_FEATURE_KEYS keeps the original four, so a no-args asof_daily_features call has the same columns/cost as before. - mmp_valid_minute_counts: report-only per-(date,symbol) valid-minute count under the same PIT cutoff, reusing compute_minute_mmp (one MMP source of truth). PIT unchanged: available_time <= trade_date + decision_time filters per-bar timestamps BEFORE daily grouping. Data-layer only: no returns / Tushare / cache / portfolio / execution.
Add IntradayCfg.score_feature (Literal mirroring INTRADAY_FEATURE_KEYS; default 'ret' reproduces the I5a/I5b smoke score, 'mmp_ew' selects the MMP factor) and OutputCfg.intraday_report_title so a reused intraday runner can name the actual study instead of a stale phase label (same precedent as subset_report_title). All existing configs validate unchanged and keep the 'ret' default; an unknown score_feature fails readably at validation.
Make the intraday tail runner consume intraday.score_feature: _score_panel requests ONLY the selected feature from asof_daily_features and uses its returned column EXACTLY (no hardcoded prefix matching), so default 'ret' reproduces I5b and 'mmp_ew' selects intraday_mmp20_ew_0930_1450. _report_heading is study-aware (I5c for mmp_ew / I5b when price-limit on / else I5a) with an intraday_report_title override. Add report-only factor diagnostics: MMP formula + PIT disclosure, daily score coverage, valid-MMP-minute distribution, and per-settled-rebalance cross-sectional score stats + Spearman IC of the decision-date score vs the same period's exec-to-exec return (read in the report layer only, never fed back to factors/alpha). I5b raw stk_limit feasibility stays ON and disclosed. Add config/phase_i5c_mmp_minute_factor.yaml: same SSE50 SH/SZ window as I5b with score_feature=mmp_ew, price_limit_check=true, its own report name + title.
16 I5c tests: exact MMP_t formula on a controlled 22-bar day; rolling baselines exclude bar t (t-20..t-1); first 20 bars NaN; daily score is equal-weight (asserts != volume-weighted); NaN-not-inf denominators; daily NaN when no valid minute; PIT (post-cutoff bars + delayed availability don't move the score); future bars don't change earlier MMP_t; multi-day no prior-tail carryover; multi-symbol isolation; old configs default to 'ret'; I5c config selects mmp_ew + titles the study; invalid score_feature fails; score_feature Literal mirrors feature keys; _score_panel selects MMP without prefix matching; heading names I5c. Update the I5b heading test for the new _report_heading signature.
…ime] window The MMP daily score promised aggregation over bars with bar_end in [session_open, decision_time] but only applied the upper bound (available_time <= cutoff); pre-session bars (before session_open) leaked into the rolling baseline, so the first in-session bar wrongly got a non-NaN MMP and the report's [session_open, decision_time] claim did not hold. Add the lower bound bar_end >= trade_date + session_open on the MMP path only (_mmp_ew_daily + mmp_valid_minute_counts) so pre-session bars enter neither the rolling baseline nor the daily mean; the first 20 IN-SESSION bars are NaN. The existing four features (ret/realized_vol/vwap/last30m_ret) are untouched — only mmp_ew gains the lower bound — so I5a/I5b behavior cannot drift. Regression: 20 pre-session bars (09:00..09:19) + one 09:31 bar with session_open 09:30 -> daily MMP NaN / 0 valid minutes (was a spurious value); prepending pre-session bars leaves a full in-session day's MMP unchanged. Real SSE50 stk_mins has no pre-09:30 continuous bars, so the smoke is unaffected.
Owner
Author
|
Addressed a review blocker (real I5c semantic bug): the MMP daily aggregation promised Fix (
|
This was referenced Jun 15, 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.
Summary
EXPLORATORY MMP minute-factor study — NOT a performance claim and NOT a broad
factor search. Implements ONE user-proposed minute factor (Minute Microstructure
Pressure, MMP) from cached 1min OHLCV bars, equal-weight aggregates the PIT-visible
per-minute values (session open → 14:50) into one daily score
intraday_mmp20_ew_0930_1450, and analyzes it through the I5a intraday tail eventmodel with I5b raw
stk_limitexecution feasibility ON. No tuning, no robustnessmatrix, no learned/IC-weighted alpha.
Per 1min bar
t:mid=(high+low)/2;S=(close-mid)/mid;V=sqrt(volume/median(vol[t-20:t]));B=|close-open|/(high-low+eps);R=(high-low)/(mean(hl[t-20:t])+eps);MMP_t=S*V*B*R,eps=1e-6. Daily scoreis the equal-weight mean of valid
MMP_t(the volume term is already insideMMP_t— not additionally volume-weighted).PIT / correctness guarantees
t-20..t-1(rolling(20).shift(1))— never bar
t, never a later bar, never the prior day's tail; first 20 bars ofeach session → NaN.
available_time <= trade_date + decision_timefilters per-bartimestamps BEFORE daily grouping; post-cutoff / late-available bars can't move the
score.
intraday.score_feature; defaultretreproduces the I5a/I5b smoke exactly (
mmp_ewis selectable-only, not in thedefault feature set).
read-only — never fed back to factors/alpha. Daily close-to-close,
BacktestEngine,SimExecution, and the achieved-book ledger are unchanged.stk_limitexecution feasibility stays ON and disclosed.Changes
data/clean/intraday_aggregate.py—compute_minute_mmp,mmp_ewkey + column,mmp_valid_minute_counts,DEFAULT_FEATURE_KEYS.qt/config.py—IntradayCfg.score_feature(defaultret),OutputCfg.intraday_report_title.qt/intraday_tail_framework.py— configurable score, study-aware heading, MMP +IC report diagnostics.
config/phase_i5c_mmp_minute_factor.yaml,tests/test_i5c_mmp_minute_factor.py.Test plan
pytest tests→ 555 passed (538 baseline + 16 I5c + 1 config-validation param)ruff check .→ cleanvalidate-configon all 16 configs → OKic_mean=0.9600, annual_return=0.8408(unchanged)covered=58/58,stk_mins_live_calls=0,limit coverage 174/174, 1 up-limit blocked buy, MMP score coverage 4054/4060
valid, per-rebalance Spearman IC −0.1154/−0.0608/+0.0427 (mean −0.0445,
report-only), final NAV 0.957143 — not a performance claim.
.config.json.Acceptance:
tmp/context/session_handoff_20260615/stage_i5c_acceptance.md.