Skip to content

perf(engine): expanding-mean ADV fallback during intraday warm-up (follow-up to #269/#272) #275

Description

@shardul0701

Follow-up to #269/#272. Raised as an open question in #272's review: min_periods was deliberately left at 1 because raising it to a full session would leave the first session of every intraday backtest with no liquidity cap and no market impact at all (both _daily_adv consumers guard with if pd.notna(adv_20) and adv_20 > 0, so NaN skips the cap block rather than applying a cautious one).

Problem

During warm-up (fewer than _adv_bars_per_day * 20 bars of history), _daily_adv() extrapolates from whatever partial window it has, weighted by min_periods=1 — in the worst case, a single bar's volume × bars-per-day. That's a fair, unbiased but high-variance guess, and it's strictly better than the pre-#265 behaviour, but it's not the best available estimate.

Proposed fix

Use an expanding mean during warm-up instead of a fixed-window rolling mean with min_periods=1:

  • For at_date where fewer than _adv_window_bars are available, average all bars seen so far (rescaled to daily by _adv_bars_per_day) instead of relying on min_periods=1's partial-window behaviour.
  • Once _adv_window_bars of history exists, behaviour is identical to today — this only changes the warm-up period.
  • Must compose with perf(engine): memoize the per-symbol ADV series #272's per-symbol memoization (the cached series should already reflect the expanding-then-fixed window; do not add a second cache layer).
  • Does not change trade admission logic — no trade that currently fires should stop firing, and no trade that's currently blocked should start firing. Only the cap/impact magnitude during warm-up changes.

No-regression requirement

  • D/W/M timeframes: bpd == 1, so the "warm-up" period is the first ~20 calendar days of any run — same as today's behaviour for daily backtests, since 20 daily bars is a short warm-up already covered by existing tests. Confirm no numeric drift there.
  • Any backtest whose full history already exceeds 20 days behaves byte-identically (the expanding-mean path is only reachable during the literal first _adv_window_bars bars of a series).
  • Golden master (tests/test_engine_characterization.py) green.

Acceptance criteria

  • Expanding-mean fallback implemented for the warm-up period, composed with perf(engine): memoize the per-symbol ADV series #272's per-symbol cache
  • Test proving warm-up-period cap/impact values match a hand-computed expanding mean, not a single-bar extrapolation
  • Test proving trade admission (which trades fire) is unchanged versus the pre-fix behaviour on a fixture with a real warm-up gap
  • Test proving post-warm-up behaviour is byte-identical to today
  • Golden master green; full suite green

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions