Skip to content

feat: I5d MMP quintile 5Y group backtest (exploratory, stacked on I5c)#38

Open
StackOverFlow11 wants to merge 3 commits into
feat/i5c-mmp-minute-factorfrom
feat/i5d-mmp-quintile-5y
Open

feat: I5d MMP quintile 5Y group backtest (exploratory, stacked on I5c)#38
StackOverFlow11 wants to merge 3 commits into
feat/i5c-mmp-minute-factorfrom
feat/i5d-mmp-quintile-5y

Conversation

@StackOverFlow11

Copy link
Copy Markdown
Owner

I5d — MMP Quintile 5Y Group Backtest (EXPLORATORY)

This is an EXPLORATORY grouped factor analysis of the I5c MMP minute-derived
daily score — NOT a performance claim and NOT parameter tuning.

Stacked on I5c (PR #37). This PR is based on feat/i5c-mmp-minute-factor
(PR #37, still open) and its diff shows ONLY the I5d commits. Merge/rebase after
I5c lands; the base will be retargeted to main once #37 merges. Dependency is
disclosed, not hidden.

What it does

On each monthly rebalance date the cross-section is ranked by the PIT-safe daily
score intraday_mmp20_ew_0930_1450 (the I5c Minute Microstructure Pressure
factor, visible at the 14:50 cutoff) and split into analytics.quantiles=5
EQUAL-COUNT rank buckets (Q1 = lowest score, Q5 = highest). Each group runs as
its own long-only equal-weight portfolio through the SAME event-driven machinery —
a FRESH BacktestEngine + IntradayTailEventModel + SimExecution(fee_rate=0.001)
per group — with I5b raw stk_limit execution feasibility ON (14:50 decision /
14:51 execution, exec-to-exec returns, NEVER close-to-close).

Design highlights

  • Memory-conscious minute load: the monthly model only needs minute bars on
    the ANCHOR dates (rebalance ∪ exit), so the runner reads ONLY those days from
    the persistent intraday cache, one trading day at a time. Cache-only, read-only:
    stk_mins_live_calls=0 (a miss never warms/backfills). 12.9M raw rows over a
    5-year CSI500 window without materialising the full minute history.
  • One shared exec-price matrix (immutable) across the 5 fresh per-group models
    via a new backward-compatible precomputed_prices param on
    IntradayTailEventModel (default None = byte-identical I5a/I5b build).
  • Equal-count RANK buckets (not value cuts): deterministic on ties / degenerate
    scores and robust to the MMP factor's fat tails.

Result (one window — exploratory)

CSI500 (000905.SH), 2021-06-01 → 2026-05-31, 59 monthly rebalances, 892/995
constituents minute-covered (103 uncovered dropped + disclosed; window NOT
shortened
).

group final NAV annual Sharpe maxDD
Q1 (low) 0.9822 -0.36% 0.085 -39.1%
Q2 1.0423 0.85% 0.142 -36.6%
Q3 1.0495 0.99% 0.149 -38.3%
Q4 1.1275 2.47% 0.217 -34.4%
Q5 (high) 1.1577 3.02% 0.240 -37.2%

Final NAV and annual return increase monotonically Q1→Q5 (group monotonicity
Spearman = 1.0000); synthetic Q5−Q1 spread +0.29%/period, +17.15% cumulative.
Higher MMP (more intraday buying pressure) → higher realized next-month return
over this window. ⚠️ One overlapping window, one universe, no robustness, no
tuning — far too little to infer factor quality. The Q5−Q1 spread is a synthetic
long-only leg difference, NOT a separately executed dollar-neutral book.

Invariants preserved

  • Daily close-to-close, BacktestEngine, SimExecution, the MMP factor math and
    the I5b execution feasibility are UNCHANGED — only grouping + per-group
    orchestration is new.
  • raw stk_limit feasibility ON (strict coverage 52584/52584); per-group blocked
    counts do not double-count across groups; missing bar blocks (never daily-close
    fallback).

Tests & gates

  • pytest 577 passed (+19 I5d); ruff clean; all 17 configs validate;
    run-phase0 ic_mean=0.9600 / annual_return=0.8408 unchanged; I5c short smoke
    final_nav=0.957143 unchanged (zero drift from the precomputed_prices change).
  • Report + logs + figures secret-scanned: 0 token leaks.

…Model

The execution-price matrix + fills are a pure function of
(bars, anchor_dates, symbols, cfg). When several fresh models share the same
bars/cfg (one per quantile group in I5d), pass the precomputed (prices, fills)
so the heavy matrix is built once and reused while each model keeps its own
fresh mutable feasibility diagnostics. Default None reproduces the byte-identical
I5a/I5b in-place build (regression tests unchanged).
Rank the I5c MMP daily score (intraday_mmp20_ew_0930_1450) on each monthly
rebalance date into analytics.quantiles equal-count rank buckets (Q1 = lowest,
QN = highest) and run each group as its own long-only equal-weight portfolio
through the SAME BacktestEngine + IntradayTailEventModel + SimExecution
(fee_rate) with I5b raw stk_limit execution feasibility ON.

- intraday_groups.py: assign_quantile_buckets (equal-count rank buckets,
  deterministic ties, too-few-names leaves high groups empty), GroupScores,
  EqualWeightAll (whole-bucket equal weight, ignores top_n).
- intraday_group_backtest.py: run-phase-i5d-intraday-groups runner. Cache-only,
  anchor-date-sliced minute load (only rebalance/exit days -> memory-conscious,
  zero stk_mins live calls), one shared immutable exec-price matrix across N
  fresh per-group models, per-group metrics, QN-Q1 synthetic spread + group
  monotonicity (report-only).
- intraday_group_figures.py / intraday_group_report.py: Agg PNG figures (NAV
  curves / Q5-Q1 spread / grouped metric bars) + markdown report.
- cli.py: run-phase-i5d-intraday-groups subcommand.
- config/phase_i5d_mmp_quintile_5y.yaml: CSI500, 2021-06-01..2026-05-31, mmp_ew,
  price_limit_check on, quantiles=5, fee_rate=0.001. require_cache_coverage=false
  drops minute-uncovered constituents (disclosed; window not shortened).

Factor math, daily close-to-close, the event model and execution feasibility are
unchanged; only the grouping + per-group orchestration is new. EXPLORATORY, not a
performance claim, no tuning.
19 tests over the goal's five groups: equal-count bucket assignment (Q1 low / QN
high, NaN/inf excluded, deterministic ties, too-few-names), EqualWeightAll
ignores top_n, GroupScores selects one group, fee_rate reaches SimExecution; PIT
invariants (grouping uses only the <=14:50 MMP score, post-cutoff and exit bars
cannot change the assignment); grouped execution feasibility (raw stk_limit
blocks, missing bar blocks without daily-close fallback, per-group counts do not
double-count); report H1 names I5d (not stale) and mentions fee_rate=0.001 /
analytics.quantiles=5; figures written and non-empty.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant