QuantJourney Backtester

QuantJourney Backtester

Share product feedback

✓

Thank you.

Your note is now in the QuantJourney inbox.

docs/engine/market-orders.mdx

Market Orders

How to submit market orders in QuantJourney order mode and how they fill on bars.

Market orders are the simplest order-mode primitive. They express execution certainty, not price control.

Sourcebacktester/execution/order_types.py + backtester/execution/fill_engine.py
LayerOrder execution
ModeOrder mode
InputOrderType.MARKET, side, quantity, current bar
Outputfill at bar open or close, after slippage and commission
Primary APIOrder(..., order_type=OrderType.MARKET)
Main caveatMarket orders always fill in the model; this can overstate realism for illiquid assets.

Level 1: Market Entry

Buy 15 percent of NAV
python
qty = int(nav * 0.15 / bars["AAPL"].close)

self.fill_engine.submit(Order(
    instrument="AAPL",
    side=OrderSide.BUY,
    quantity=qty,
    order_type=OrderType.MARKET,
))

Level 2: Market Exit

Cancel resting exits before flattening, so stale stops or limits do not remain after the strategy leaves the position.

Cancel exits and sell current position
python
pos = current_positions.get("AAPL", 0.0)

if pos > 0 and sell_signal:
    self.fill_engine.cancel_all(instrument="AAPL")
    self.fill_engine.submit(Order(
        instrument="AAPL",
        side=OrderSide.SELL,
        quantity=pos,
        order_type=OrderType.MARKET,
    ))

Level 3: Crossover Strategy Skeleton

Market orders from SMA signals
python
class MarketCrossover(Backtester):
    def __init__(self, **kwargs):
        super().__init__(execution_mode="orders", **kwargs)
        self._prev_signal = {}

    def _compute_orders(self, date, bars, current_positions, nav):
        fast = self.instruments_data.get_feature("SMA_20_close")
        slow = self.instruments_data.get_feature("SMA_50_close")

        for inst in self.instruments:
            if date not in fast.index or inst not in fast.columns:
                continue

            signal = 1 if fast.loc[date, inst] > slow.loc[date, inst] else 0
            prev = self._prev_signal.get(inst, 0)
            pos = current_positions.get(inst, 0.0)

            if signal == 1 and prev == 0 and pos == 0:
                qty = int(nav * 0.15 / bars[inst].close)
                self.fill_engine.submit(Order(inst, OrderSide.BUY, qty, OrderType.MARKET))

            elif signal == 0 and prev == 1 and pos > 0:
                self.fill_engine.cancel_all(instrument=inst)
                self.fill_engine.submit(Order(inst, OrderSide.SELL, pos, OrderType.MARKET))

            self._prev_signal[inst] = signal

Fill Rule

For OrderType.MARKET, FillEngine uses

bar.open by default. If configured with fill_at="close", it uses bar.close. Slippage and commission are applied after the theoretical fill price is selected.

Failure Modes

  • Forgetting execution_mode="orders".
  • Submitting a new market entry on every bar while already long.
  • Failing to cancel existing stop/limit exits before a manual market exit.
  • Assuming market fill price equals the signal bar close.
  • Using market orders for illiquid assets without a realistic slippage model.