Skip to content

fix(engine): trading_hours_per_day is global while instruments resolve per symbol (mixed books get wrong bars-per-day) #270

Description

@zachisit

Follow-up to #264 / #265, raised as a minor in @shardul0701's round-2 review of #265.

Problem

trading_hours_per_day is a single process-wide number read from CONFIG, while instrument metadata already resolves per symbolresolve_instrument(sym, CONFIG) branches per symbol on margin mode, integer units, borrow, and calendar (see helpers/instruments.py, added in #229).

Session length is the one instrument property that stayed global. A mixed book of equities (6.5h RTH) and 24h futures (CME_ETH) therefore gets the wrong bars-per-day for one side of the book no matter which value is configured.

Consumers of the assumption:

Concretely, on 1-hour bars with trading_hours_per_day = 6.5, a 24h futures symbol gets bpd = 6 when its true session is 24 bars — a ~4× ADV understatement on that symbol, on top of #268's truncation error.

Severity

Pre-existing, out of scope for #265, and currently latent: it only bites once a single portfolio genuinely mixes asset classes with different session lengths. Filing it so it is tracked rather than rediscovered.

Design note / open question

This is the largest of the three #265 follow-ups because it changes a function signature that the annualization path also depends on. Proposed conservative shape:

  • Resolve session hours per symbol via the existing instrument layer (the Instrument already carries calendar), falling back to CONFIG["trading_hours_per_day"] when the instrument doesn't specify one.
  • Keep the existing global-config behaviour as the exact default so no current configuration changes behaviour.
  • Thread the per-symbol value through the ADV path first (where correctness impact is concrete and fix(engine): scale ADV liquidity-cap window to timeframe, not a fixed 20 bars #265 already isolated the call sites); treat the annualization path as a separate decision, since changing it retroactively alters reported Sharpe/CAGR on existing futures runs.

Open question for review: whether the annualization consumer (get_bars_per_year) should move per-symbol in the same change or stay global pending an explicit decision — it has reporting-comparability consequences that the ADV path does not.

Dependency

Overlaps get_bars_per_day() with #268. #268 should land first; this builds on the exact-bars-per-day helper it introduces.

Acceptance criteria

  • Per-symbol session length resolved via the instrument layer, global config as fallback
  • ADV path uses the per-symbol value
  • Existing single-asset-class configs byte-for-byte unchanged; golden master green
  • Test covering a mixed equity + 24h-futures book
  • Explicit decision recorded on the annualization path

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