QuantJourney Backtester

QuantJourney Backtester

Share product feedback

✓

Thank you.

Your note is now in the QuantJourney inbox.

docs/engine/order-lifecycle.mdx

Order Lifecycle

How submitted orders become fills, cash, positions, NAV and audit evidence.

Order mode is a bar-by-bar execution simulation. A strategy submits orders; the fill engine stores them as pending orders, tests them against later OHLCV bars, emits fills, updates cash and positions, and records evidence.

Sourcebacktester/core.py + backtester/execution/fill_engine.py + backtester/execution/order_types.py
LayerOrder execution state machine
ModeOrder mode
InputOrder objects, OHLCV bars, slippage model, commission model, volume cap
Outputfills, order history, fill history, blotter trades, cash, positions and NAV
Primary APIexecution_mode="orders" + _compute_orders(...)
Main caveatWith daily OHLCV, the engine knows touched levels but not exact intraday sequence.

State Transition

for each bar: build BarData(open, high, low, close, volume) process pending orders through FillEngine apply fills to cash and positions mark NAV at close expose order context to strategy helpers call _compute_orders(date, bars, current_positions, nav) store positions, weights and returns

Timing Rules

  • Pending orders are processed before _compute_orders(...) runs for the current bar.
  • Orders submitted by _compute_orders(...) are pending for future bar processing.
  • Market orders fill at bar.open by default, or bar.close when FillEngine(fill_at="close") is configured.
  • A submitted order does not change cash, position or NAV until a fill is emitted.
  • A bracket parent creates child exits only after the entry order fills.

Fill Priority

The same-bar priority is explicit:

text
STOP -> STOP_LIMIT -> STOP_TRAIL -> STOP_TRAIL_LIMIT -> LIMIT -> MARKET -> BRACKET -> OCO

This is conservative for protective exits on daily bars. If TP and SL are both touched inside one daily candle, this priority is the documented convention.

Order State And Queries

APIMeaning
self.fill_engine.pending_ordersactive orders still eligible to fill
self.fill_engine.order_historyall submitted orders, including inactive historical orders
self.fill_engine.fill_historyall emitted fills
self.fill_engine.last_fill(inst)last fill object for one instrument
self.fill_engine.last_fill_price(inst)last actual fill price after slippage
self.fill_engine.cancel(order_id)cancel one active order
self.fill_engine.cancel_all(instrument=inst)cancel active orders for one instrument

Sizing Helpers

Use helpers inside _compute_orders(...) when you want Zipline-like sizing without manual share arithmetic.

Order helpers inside _compute_orders
python
self.order_value("AAPL", 25_000)
self.order_percent("AAPL", 0.15)
self.order_target_percent("AAPL", 0.20)
self.close_position("AAPL")
self.bracket_percent("AAPL", weight=0.20, tp=0.10, sl=0.05)

Partial Fills And Capacity

FillEngine(max_volume_participation=...) caps per-bar fill quantity as bar volume times the configured participation rate. If the cap is lower than remaining quantity, the fill status is PARTIAL and the order remains active. This is volume participation, not exchange queue modeling.

Minimal Order Strategy

One market entry
python
from backtester import Backtester
from backtester.execution import OrderType

class OneEntry(Backtester):
    def _compute_orders(self, date, bars, current_positions, nav):
        if self.fill_engine.order_history:
            return

        self.order_percent("SPY", 0.10, order_type=OrderType.MARKET)

strategy = OneEntry(
    instruments=["SPY"],
    backtest_period={"start": "2020-01-01", "end": "2025-01-01"},
    execution_mode="orders",
)

await strategy.run_strategy()

Failure Modes

  • Submitting a duplicate entry while already long.
  • Treating a submitted order as a filled position.
  • Leaving stale stop, limit, bracket or OCO orders active after a signal exit.
  • Comparing daily-bar order results without checking same-bar priority.
  • Ignoring partial fills when a volume participation cap is configured.
  • Using daily bars for a strategy whose edge depends on intraday order sequence.

Audit Checklist

  • Is execution_mode="orders" set?
  • Are open, high, low, close and volume available, or did the engine fall back to close?
  • Which orders are pending after each bar?
  • Which fills changed cash and positions?
  • What slippage and commission model produced each fill price?
  • Did any order expire by TimeInForce, expire_at or expires_after_bars?