Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ml4t/backtest
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.1.3
Choose a base ref
...
head repository: ml4t/backtest
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.1.4
Choose a head ref
  • 1 commit
  • 11 files changed
  • 1 contributor

Commits on Sep 3, 2026

  1. fix: ExecutionPrice.VWAP fills at the feed's VWAP, or refuses (#82)

    * fix: ExecutionPrice.VWAP fills at the feed's VWAP, or refuses
    
    ExecutionPrice.VWAP has been declared since the enum was written and never read
    a VWAP. broker.py resolved it to `self._current_prices.get(asset,
    self._current_closes.get(asset))` - character-for-character what
    ExecutionPrice.PRICE returns - and there was no VWAP anywhere in the feed
    contract for it to read.
    
    The branch was also unreachable in practice. The use_open short-circuit excluded
    only BID, ASK, QUOTE_MID and QUOTE_SIDE, so under ExecutionMode.NEXT_BAR a VWAP
    request returned the next bar's *open* whatever that branch said. Fixing the
    lookup alone would have changed nothing observable while making every artifact a
    reviewer opens - the enum, the config, the data, the new store - read correctly.
    Both halves are here for that reason.
    
    - FeedSpec.vwap_col declares the column; DataFeed carries it into
      _AssetsData._vwaps and MarketState.vwaps beside the existing OHLCV caches.
    - ExecutionPrice.VWAP joins the use_open exclusion set: a VWAP is already a
      whole-bar price and the open must not override it.
    - A missing VWAP raises instead of falling back. A VWAP fill and a close fill are
      different assumptions, and substituting one for the other is indistinguishable
      from a correct run - which is how this survived since the enum was written.
    - _update_time keeps its existing 2- and 8-positional forms working and gains a
      9-positional and a vwaps= keyword form.
    
    The compatibility snapshot is regenerated: both surface changes are additive - a
    nullable field and a keyword-only parameter with a default - so no existing
    caller breaks.
    
    Verified: 1966 passed. tests/test_vwap_execution_price.py uses an open, close and
    VWAP that are mutually distinct, and 6 of its 9 cases fail against the previous
    broker; a fixture where any two coincide could not.
    
    Depends on ml4t/specs#13, pinned here through a temporary [tool.uv.sources] git
    source that must be removed before merge.
    Closes ml4t/agent-workspace#1017.
    
    * fix: a bar with no VWAP is a non-fill, not a refusal
    
    Amends the previous commit, which conflated two different situations behind one
    raise. The nasdaq100_microstructure lane asked whether the raise fires upstream
    of the skip that implements a non-fill, and it does: the fill path is
    FillEngine.get_fill_price_for_order -> broker.get_price_for_source(
    broker.execution_price, ...), and every caller in execution_engine.py guards it
    with `if price is None: continue`.
    
    That `continue` is already the behaviour we want - the order does not fill, the
    prior position stands, no new machinery - and it needs a None. Raising there
    turned an ordinary no-trade bar into a stopped run.
    
    The two cases are now separated by where they are caught:
    
    - A feed that declares no vwap_col at all, under execution_price=VWAP, is a
      configuration error. Engine rejects it once, before the first bar.
    - An individual asset-bar with no VWAP is an ordinary market state: nothing
      traded, so there is no volume-weighted price. get_price_for_source returns
      None and the order goes unfilled.
    
    Neither falls back to the close, which is the original defect: on a no-trade bar
    the close is a stale carried print, so substituting it would invent a fill at a
    price nothing transacted at.
    
    Measured, because the rate decides whether this matters: on the NASDAQ-100
    production universe (102 symbols, March 2021) 0.2384% of symbol-minutes inside
    09:30-16:00 carry no print, worst name CTAS at 2.95%. Across all hours it is
    5.02%, but that population is pre- and post-market and this case study trades the
    exchange session.
    
    Verified: 1970 passed. The refusal tests become non-fill tests, and three new
    cases cover the configuration guard: it raises on an undeclared column, accepts a
    declared one, and stays out of the way under any other execution price.
    
    * Depend on released ml4t-specs 0.1.4 instead of the branch pin
    
    FeedSpec.vwap_col shipped in ml4t-specs 0.1.4 (ml4t/specs#13, b17105b), so
    the temporary [tool.uv.sources] git pin that let the `ty` hook resolve the
    field is no longer needed. Delete it and raise the floor to >=0.1.4,<0.2.
    
    The pin was also what made every Compatibility job red: those jobs run with
    --locked, and a git-sourced dependency cannot satisfy a lockfile check
    against the registry.
    
    * test: update the ml4t-specs minimum version contract
    stefan-jansen authored Sep 3, 2026
    Configuration menu
    Copy the full SHA
    da7bf2e View commit details
    Browse the repository at this point in the history
Loading