NautilusTrader
Concepts
These docs track the unreleased nightly build and may change without notice. Switch to the latest stable docs.

Execution

NautilusTrader coordinates order submission, risk checks, venue execution, reconciliation, and position updates across multiple strategies and venues. This page explains the components and message flows that support execution.

The main execution-related components include:

  • Strategy
  • ExecutionAlgorithm
  • OrderEmulator
  • RiskEngine
  • ExecutionEngine
  • ExecutionClient

Execution flow

A Strategy builds on data actor capabilities and adds methods for managing orders and execution:

  • submit_order(...)
  • submit_order_list(...)
  • modify_order(...)
  • cancel_order(...)
  • cancel_orders(...)
  • cancel_all_orders(...)
  • close_position(...)
  • close_all_positions(...)
  • query_account(...)
  • query_order(...)

These methods send point-to-point execution commands over the message bus. Order creation also publishes events such as OrderInitialized.

Commands follow different routes:

  • submit_order(...) routes to OrderEmulator for emulated orders, to an ExecutionAlgorithm when exec_algorithm_id is set, and to the RiskEngine otherwise.
  • submit_order_list(...) follows the same branching behavior based on emulation and exec_algorithm_id.
  • modify_order(...) routes to the OrderEmulator for emulated orders and to the RiskEngine otherwise.
  • Cancel and query commands can route directly to the OrderEmulator, ExecutionAlgorithm, or ExecutionEngine, depending on the command and order state.

New orders typically enter one of these paths:

Strategy -> OrderEmulator or ExecutionAlgorithm or RiskEngine

The downstream flow is:

OrderEmulator -> ExecutionAlgorithm or ExecutionEngine

ExecutionAlgorithm -> RiskEngine -> ExecutionEngine -> ExecutionClient

Execution paths branch by emulation and algorithm routing before reaching the execution engine and client.

Command outcomes

Execution commands resolve according to the evidence available:

EvidenceMeaningResult
Definitive local failureValidation proves that the command was not sent.Denies a submit or rejects a modify or cancel when the failure is attributable to that command.
Definitive resultThe matching engine or venue explicitly confirms the outcome.Applies the corresponding accepted, updated, canceled, or rejected event.
Unknown live outcomeThe command may have reached the venue, but no result is known.Keeps the command in flight without inventing a rejection.

The failure event depends on the command and when the failure becomes definitive:

CommandEventMeaning
Submit or submit order listOrderDeniedLocal checks prevent submission; no OrderSubmitted event is emitted.
Submit or submit order listOrderRejectedThe submit entered execution and was later proven unsuccessful.
ModifyOrderModifyRejectedThe requested modification was proven unsuccessful.
Cancel, cancel‑all, or batch cancelOrderCancelRejectedThe requested cancellation was proven unsuccessful.

For modify or cancel preparation, Nautilus emits the matching rejection only when the failure is attributable to that command and proves it was not sent. Otherwise, it logs the failure without inventing an outcome.

A successful batch response can still contain definitive per‑order failures. A whole‑request failure without per‑order evidence does not prove that every child command failed.

Unknown live outcomes

Transport errors, timeouts, disconnects, task cancellation, exhausted adapter request retries, missing acknowledgements, and parse failures after transmission usually leave the venue outcome unknown. HTTP status codes and rate limits are definitive only when venue‑specific semantics prove that the command was not accepted.

The live engine initially keeps an unknown outcome in flight while stream updates, polling, queries, or reconciliation determine the venue state. A later in‑flight check can apply a terminal reconciliation event after the configured retry limit.

An in‑flight order is awaiting resolution:

  • SUBMITTED: initial submission awaiting acceptance or rejection.
  • PENDING_UPDATE: modification awaiting confirmation.
  • PENDING_CANCEL: cancellation awaiting confirmation.

See Runtime checks for how live reconciliation monitors and resolves these states.

Order denied reasons

A local denial (OrderDenied) carries a standardized CATEGORY_CONDITION reason code and may include a diagnostic suffix. Only the leading code is canonical. Messages use these forms:

  • CODE when the denial needs no diagnostic suffix.
  • CODE: value for one typed value or a free‑text diagnostic.
  • CODE: key=value, key=value when multiple typed values need disambiguation.
  • CODE: value; free text when one typed value precedes a free‑text diagnostic.

The table covers local denials emitted by execution algorithms and clients as well as the risk and execution engines. These codes are the source of truth for locally denied orders; venue rejections (OrderRejected) instead carry the venue‑provided meaning. Adapters remove protocol wrappers and bound untrusted venue text before emission without replacing it with a standardized local denial code.

Price and quantity checks can also emit these code‑led reasons on OrderModifyRejected:

  • PRICE_PRECISION_EXCEEDS_MAXIMUM
  • PRICE_NOT_POSITIVE
  • QUANTITY_PRECISION_EXCEEDS_MAXIMUM
  • QUANTITY_EXCEEDS_MAXIMUM
  • QUANTITY_BELOW_MINIMUM

For price reasons, field is PRICE or TRIGGER_PRICE and names the rejected command field. Other modification rejection reasons remain free‑form; OrderDeniedCode does not classify them.

OrderRejected.due_post_only is true only when venue evidence proves that a post‑only order would cross or immediately match. Other venue rejections leave it false.

CodeDescription
PRICE_PRECISION_EXCEEDS_MAXIMUMThe price precision exceeds the instrument maximum.
PRICE_NOT_POSITIVEThe price is not positive.
QUANTITY_PRECISION_EXCEEDS_MAXIMUMThe quantity precision exceeds the instrument maximum.
QUANTITY_CONVERSION_FAILEDThe order quantity could not be converted for risk checks.
QUANTITY_EXCEEDS_MAXIMUMThe effective order quantity exceeds the instrument maximum.
QUANTITY_BELOW_MINIMUMThe effective order quantity is below the instrument minimum.
INVALID_MAX_NOTIONAL_PER_ORDERThe configured maximum notional per order is invalid.
INVALID_ORDER_SIDEThe order side is invalid for this operation.
MISSING_EXPIRE_TIMEA GTD order is missing its expire time.
EXPIRE_TIME_IN_PASTThe order's expire time is in the past.
MISSING_TRAILING_OFFSET_TYPEThe order is missing a required trailing offset type.
UNSUPPORTED_TRAILING_OFFSET_TYPEThe order's trailing offset type is not supported.
MISSING_TRIGGER_TYPEThe order is missing a required trigger type.
MISSING_TRAILING_OFFSETThe order is missing a required trailing offset.
INSTRUMENT_NOT_FOUNDThe instrument was not found in the cache.
POSITION_NOT_FOUNDThe position for a reduce‑only order was not found.
MARKET_PRICE_UNAVAILABLENo market price is available for the order risk check.
TRAILING_STOP_CALCULATION_FAILEDThe trailing stop trigger price could not be calculated.
NOTIONAL_CALCULATION_FAILEDThe order notional value could not be calculated.
NOTIONAL_BELOW_MINIMUMThe order notional is below the instrument minimum.
NOTIONAL_EXCEEDS_MAXIMUMThe order notional exceeds the instrument maximum.
NOTIONAL_EXCEEDS_MAX_PER_ORDERThe order notional exceeds the configured maximum per order.
NOTIONAL_EXCEEDS_FREE_BALANCEThe order notional exceeds the account free balance.
INITIAL_MARGIN_CALCULATION_FAILEDThe order initial margin could not be calculated.
INITIAL_MARGIN_EXCEEDS_FREE_BALANCEThe order initial margin exceeds the account free balance.
BETTING_BALANCE_LOCKED_CALCULATION_FAILEDThe balance to lock for the betting order could not be calculated.
CUMULATIVE_NOTIONAL_EXCEEDS_FREE_BALANCEThe cumulative order notional exceeds the account free balance.
CUMULATIVE_INITIAL_MARGIN_CALCULATION_FAILEDThe cumulative initial margin could not be calculated.
CUMULATIVE_INITIAL_MARGIN_EXCEEDS_FREE_BALANCEThe cumulative initial margin exceeds the account free balance.
REDUCE_ONLY_WOULD_INCREASE_POSITIONA reduce‑only order would increase the position.
ORDER_LIST_INCOMPLETEThe order list is missing orders in the cache.
ORDER_LIST_DENIEDThe order was denied because its order list failed risk checks.
TRADING_HALTEDTrading is halted; new orders are denied.
TRADING_STATE_REDUCINGTrading is reducing; the order would increase exposure.
RATE_LIMIT_EXCEEDEDThe order submission rate limit was exceeded.
STREAM_RECONCILINGThe execution stream is unavailable or recovering; retry after recovery.
NO_EXECUTION_CLIENTNo execution client was found for the routed command.
CLIENT_VENUE_MISMATCHThe execution client does not handle the order venue.
SUBMIT_FAILEDSubmitting the order to the execution client failed.
INVALID_CLIENT_ORDER_IDThe client order ID is invalid for the venue.
INVALID_POSITION_IDThe supplied position ID is invalid for the order submission.
UNSUPPORTED_ORDER_LISTThe venue does not support the requested order list.
UNSUPPORTED_ORDER_TYPEThe order type is not supported.
UNSUPPORTED_TIME_IN_FORCEThe order's time in force is not supported.
UNSUPPORTED_TP_SLThe venue does not support the requested take‑profit/stop‑loss parameters.
VALIDATION_FAILEDThe order failed validation before submission.

Order management system (OMS)

An order management system (OMS) type determines how orders map to positions for an instrument. Strategies and venues, whether simulated or live, each use an OMS type defined by the OmsType enum.

The OmsType enum has three variants:

  • UNSPECIFIED: The strategy uses the venue's OMS type.
  • NETTING: Positions combine into one position per instrument and strategy.
  • HEDGING: Multiple positions per instrument and strategy can remain open.

When the strategy and venue OMS types differ, the ExecutionEngine assigns or overrides position_id values on OrderFilled events. A virtual position exists in NautilusTrader but not as a separate venue position.

Strategy OMSVenue OMSResult
NETTINGNETTINGOne position per instrument and strategy.
HEDGINGHEDGINGMultiple positions per instrument and strategy.
NETTINGHEDGINGOne virtual position across the venue positions.
HEDGINGNETTINGMultiple virtual positions against the venue's single net position.

OMS configuration

When a strategy omits oms_type or uses UNSPECIFIED, the ExecutionEngine follows the venue's OMS type without overriding venue position_id values. Configure a backtest venue with the OMS type used by the venue being modeled.

Venue position modes may require adapter-specific configuration. For example, see Binance Futures hedge mode.

Custom position IDs and NETTING

Custom position IDs are only valid under HEDGING OMS. NETTING has one position per instrument and strategy, with a deterministic ID of the form {instrument_id}-{strategy_id}.

The ExecutionEngine enforces this at submit time. If the effective OMS resolves to NETTING and submit_order (or submit_order_list) is called with a position_id that does not match {instrument_id}-{strategy_id}, the order is denied with an OrderDenied event explaining the mismatch.

This rule still permits the common closing idiom: Strategy.close_position(position) forwards position.id, which under NETTING is exactly the deterministic ID, so it is accepted. To label or partition positions with arbitrary IDs, configure the strategy with oms_type=HEDGING.

For submit_order_list, the engine additionally denies any mixed-instrument list when a position_id is supplied, regardless of OMS. A position belongs to a single instrument, so the combination is rejected with an explicit OrderDenied reason. See Order lists for the broader set of mixed-instrument caveats.

Position replay across NETTING cycles

Under NETTING the engine reuses one position ID across close and reopen cycles, so a position's replay log can accumulate every fill ever applied to that ID. The ExecutionEngineConfig.carry_replay_events_on_reopen option controls whether that log survives a reopen:

carry_replay_events_on_reopenBehavior
False (default)Keeps only current‑cycle state, bounding the per‑fill cost.
TrueKeeps earlier fills correctable while position state can grow.

Live trading pins the option True: LiveExecEngineConfig always carries the replay log, so a venue OrderFillVoided referencing an earlier cycle still resolves. The simulated venue never emits fill voids, so backtests take the bounded default. Enable it explicitly for a custom or external execution client that can correct a fill from a prior cycle; without the carried log the engine finds no matching position fragment and rejects the correction.

Realized-PnL snapshots follow the correction. A fill void that reaches an earlier cycle rebuilds the position across the cycle boundary, moving the boundaries its archived snapshots describe, so the engine settles those snapshots into the corrected history's own closed cycles and realized PnL counts each cycle once. A void confined to the current cycle leaves the archive intact. See Position snapshotting.

Risk engine

The RiskEngine is a component of every Nautilus system, including backtest, sandbox, and live environments. It sits on the submit and modify path, and it also receives order events such as OrderReleased from the OrderEmulator. Cancel and query commands route directly to other execution components and do not pass through the RiskEngine.

Unless bypassed in RiskEngineConfig, the engine validates:

  • Price and trigger-price precision for the instrument.
  • Positive prices, unless the instrument allows negative prices (options, futures spreads, option spreads, and spot commodities).
  • Quantity precision and base-quantity minimum and maximum bounds.
  • GTD orders have not already expired.
  • reduce_only orders do not increase the referenced position.
  • Engine-level max_notional_per_order limits and instrument max_notional limits.
  • Cash-account balance impact for non-margin accounts.
  • Submit and modify rate limits.
  • Trading-state restrictions (ACTIVE, HALTED, REDUCING).

If a submit-time risk check fails, the system generates an OrderDenied event with a standardized reason code. If a modify-time risk check fails, it generates an OrderModifyRejected event.

Trading state

The TradingState enum has three variants:

  • ACTIVE: Submit and modify commands operate normally.
  • HALTED: New submit and modify commands are denied. Cancels still pass through.
  • REDUCING: Cancels are allowed, and only submit or modify commands that do not increase exposure are accepted.

See the RiskEngineConfig API reference for configuration details.

Execution algorithms

An ExecutionAlgorithm receives primary orders selected by exec_algorithm_id and can split them into smaller spawned orders. NautilusTrader supports custom algorithms and includes a native Rust TWAP implementation.

TWAP (Time-Weighted Average Price)

TWAP spreads a primary order across regular intervals to reduce the market impact of submitting the full quantity at once. To register the native algorithm with an initialized BacktestEngine:

from nautilus_trader.model import ExecAlgorithmId
from nautilus_trader.config import ExecutionAlgorithmConfig

engine.add_native_exec_algorithm(
    "TwapAlgorithm",
    ExecutionAlgorithmConfig(exec_algorithm_id=ExecAlgorithmId("TWAP")),
)

Orders routed to TWAP require these string-valued exec_algorithm_params:

KeyMeaning
horizon_secsHorizon used with the interval to determine the slices.
interval_secsTime between slices.

Both values must parse as positive numbers, and horizon_secs must be at least interval_secs. The algorithm submits the first slice immediately and the remaining slices at the configured interval. TWAP denies the primary order before submission when the order type, instrument, or schedule is unsupported or invalid.

Writing execution algorithms

To define a Python execution algorithm, subclass ExecutionAlgorithm and implement on_order(...):

from nautilus_trader.model import ExecAlgorithmId
from nautilus_trader.trading import ExecutionAlgorithm
from nautilus_trader.config import ExecutionAlgorithmConfig


class MyExecutionAlgorithm(ExecutionAlgorithm):
    def __init__(self) -> None:
        super().__init__(
            ExecutionAlgorithmConfig(exec_algorithm_id=ExecAlgorithmId("MY-ALGO")),
        )

    def on_order(self, order) -> None: ...

Python execution algorithms provide cache and portfolio access, a clock for timers, signals, and methods for spawning orders.

After registration, the message bus routes an order to the algorithm whose ExecAlgorithmId matches the order's exec_algorithm_id. The optional exec_algorithm_params field is a Mapping[str, str]. Override on_order_list(...) to handle a list as a unit; its default implementation passes each order to on_order(...).

Validate required exec_algorithm_params keys and parse their string values before executing an order. Call deny_order(...) with a standardized reason code, such as VALIDATION_FAILED: horizon_secs not found in exec_algorithm_params, when the order cannot be executed.

An order received by an execution algorithm is the primary order. Use these methods to create spawned orders:

  • spawn_market(...): Creates a MARKET order.
  • spawn_market_to_limit(...): Creates a MARKET_TO_LIMIT order.
  • spawn_limit(...): Creates a LIMIT order.

Each method takes the primary order as its first argument. By default, the method reduces the primary order quantity by the spawned quantity. Pass reduce_primary=False to keep the primary quantity unchanged.

When reduce_primary=True, the spawned quantity must not exceed the primary order's leaves_qty (remaining unfilled quantity).

If a spawned order is denied or rejected before acceptance, the deducted quantity is automatically restored to the primary order. Once accepted by the venue, the reduction is considered committed.

An execution algorithm can keep spawning orders, submit the remaining primary order, or do both. The built-in TWAP algorithm submits the remaining primary order on the final interval.

Spawned orders

Every spawned order sets exec_spawn_id to the primary order's client_order_id. Its own client_order_id follows this pattern:

{exec_spawn_id}-E{spawn_sequence}

For example, the first order spawned from O-20230404-001-000 has the ID O-20230404-001-000-E1.

The primary and spawned terminology distinguishes execution slicing from parent and child contingent-order relationships.

Managing execution algorithm orders

The Cache provides two primary queries:

  • orders_for_exec_algorithm(...): Returns orders for an algorithm, with optional venue, instrument, strategy, account, and side filters.
  • orders_for_exec_spawn(...): Returns the primary order and its spawned orders for a primary ClientOrderId.

Own order books

When manage_own_order_books is enabled, the ExecutionEngine maintains a market-by-order (MBO/L3) view of your working orders for each instrument. Strategies can subtract these orders from the public book to estimate net available liquidity. See Own order book for lifecycle, queries, filtering, and auditing.

Safe cancellation queries

When querying an own order book for cancellation candidates, exclude PENDING_CANCEL from the status filter.

Including PENDING_CANCEL can issue duplicate cancel requests and repeatedly select orders that already await confirmation.

Overfills

An overfill occurs when an order's cumulative filled quantity exceeds its original quantity. For example, fills totaling 110 units overfill a 100-unit order by 10 units.

How overfills occur

The engine observes an overfill when reported quantities exceed the order quantity. This can represent a genuine venue result, duplicate delivery under different trade IDs, or inconsistent venue reporting. Quantity alone does not identify the cause.

Live fills can arrive through two channels:

  • Real-time fill events arriving via WebSocket.
  • Periodic reconciliation polling the venue for fill history and position status.

Stable trade_id values let the engine deduplicate the same fill across both channels. If the logical fill arrives with different IDs, the engine treats the reports as distinct. See Continuous reconciliation for configuration details.

System behavior

The ExecutionEngine checks for potential overfills before applying each fill event by comparing the order's current filled_qty plus the incoming last_qty against the original quantity.

The allow_overfills configuration option (default: False) controls how overfills are handled:

allow_overfillsBehavior
FalseLogs and rejects the fill, preserving the order's current state.
TrueLogs a warning, applies the fill, and tracks the excess in overfill_qty.

When overfills are allowed, the order's overfill_qty field tracks the excess quantity. The order transitions to FILLED status and leaves_qty is clamped to zero.

Duplicate fill detection

The Order model enforces one applied fill per trade_id. Order.apply() returns an error when the same ID already exists on the order.

Core engine path

Before applying a fill, the ExecutionEngine calls Order.is_duplicate_fill(), which compares:

  • trade_id
  • order_side
  • last_px
  • last_qty

An exact match is skipped with a warning. If the trade_id matches but another field differs, the four-field check does not classify the fill as an exact duplicate. Order.apply() then rejects the reused ID, and the engine logs and drops the fill.

Reconciliation path

The reconciliation path checks trade_id before generating an OrderFilled event. It drops a report when that ID already exists on the order, regardless of its price or quantity.

Synthetic and inferred reconciliation fills use deterministic IDs. Replaying the same inputs after a restart therefore produces the same trade_id and is deduplicated.

Configuration

For live trading, enable overfill tolerance in the LiveExecEngineConfig:

from nautilus_trader.config import LiveExecEngineConfig

config = LiveExecEngineConfig(
    allow_overfills=True,
)

Choose this setting from the venue's execution contract. The default False protects local state but can leave a discrepancy after a legitimate venue overfill. True applies the excess quantity and is not a substitute for duplicate-fill detection. Use execution reconciliation to detect discrepancies.

Fill corrections

Some venues can later reduce or invalidate a fill. Nautilus records this as an OrderFillVoided event, never as an opposite-side fill. The event identifies the original trade and carries the cumulative voided quantity and fee correction.

The execution engine rebuilds the affected order and positions and refreshes portfolio position and PnL caches before publishing the correction to strategies and execution algorithms. Adapters that support fill corrections request an authoritative account refresh after a void.

Adapters must publish the referenced fill before a reopened correction or a partial correction that leaves the order executable. Without a local fill, a non-reopened correction makes the whole order terminal, even when voided_qty is less than the order quantity. A later working status report does not reopen VOIDED. See the complete OrderFillVoided contract.

How voided fills occur

A void is a venue action on a trade it already reported. The causes recur across asset classes:

  • Erroneous execution review: the venue nullifies a print that is substantially inconsistent with the market at the time of execution, or one caused by an exchange system fault.
  • Settlement failure: a matched trade fails to settle, so the fill never takes economic effect.
  • Event invalidation: the underlying event is abandoned or a competitor is withdrawn, so matched positions carry no exposure.
  • Post-trade restatement: the venue restates the quantity or fees of a trade during clearing.

The event does not restate the fill price, so a venue price adjustment is not expressible as a single correction.

A break reaches the client differently by venue. FIX venues signal one through ExecType <150> values H (trade cancel) and G (trade correct). Venues that notify out of band leave the break to surface through execution reconciliation.

Venue references

Each venue publishes the conditions under which it acts:

VenueMechanismReference
NasdaqClearly erroneous transactions (Rule 11890).Clearly erroneous transactions policy.
NYSEClearly erroneous executions (Rule 7.10).Clearly erroneous execution review.
Cboe US equitiesClearly erroneous executions (BZX Rule 11.17).Clearly erroneous execution form.
CME GroupTrade cancellations and price adjustments (Rule 588).CME rulebook chapter 5.
BetfairVoided bets, reported as cumulative size voided (sv).Void bets on the Stream API.
PolymarketFAILED trade status after an on‑chain revert or reorg.User channel.

Nautilus adapters emit OrderFillVoided where the venue publishes the void on a stream the adapter consumes: Betfair from the order change message sv field, and Polymarket from the user channel trade status.

Reconciliation reports

The execution engine consumes four reconciliation report variants from live adapters. Each variant has a different normal role when its matching order is absent from the cache. Explicitly bounded history can instead use order‑only fill projection.

VariantPurposeMissing‑order action
OrderStatusReportOrder state update.Creates an order and infers any reported fill.
FillReportStandalone fill.Creates a market order, then applies fill metadata.
OrderWithFillsOrder state plus fills.Creates an order, applies fills, and infers residue.
PositionStatusReportVenue position snapshot.Logs the report; positions remain fill‑derived.

When to use each variant

Adapters choose the variant that matches the venue event:

  • Use OrderStatusReport for order lifecycle updates when fill details arrive on a separate stream.
  • Use FillReport for a venue-initiated closure that has a fill but no user-level order. Hyperliquid liquidations follow this pattern.
  • Use OrderWithFills when one venue event contains both an order status and its fills. Binance Futures uses this for exchange-generated ADL, liquidation, and settlement orders.

Order-only fill projection

During startup reconciliation, a bounded historical report can prove an order's status and filled quantity without proving that its fill belongs in the current position lifecycle. The engine then projects the OrderFilled event onto the order only. The order reaches the exact reported state, while the fill does not create or change a position and does not update portfolio economics.

This projection applies only to reconciliation recovery. Raw reports remain available, and an authoritative position report can reconcile the current venue position separately. See Bounded history safety for the required evidence.

External order creation

When a report references an order that is absent from the cache, the engine creates an external order. This covers venue-initiated ADL, liquidation, or settlement, orders placed by another process, and orders not yet observed locally. The engine assigns ownership to:

  • The strategy that claimed the instrument through register_external_order_claims.
  • The EXTERNAL strategy as a default fallback.

The external order uses the report's client_order_id when present and otherwise derives one from the venue_order_id. The engine adds the order to the cache, registers its venue order ID, and emits the applicable OrderAccepted, OrderFilled, OrderCanceled, or OrderExpired events. Positions then update through the normal event pipeline.

On this page