# How the Nautilus Trader ExecutionEngine Handles Order Submission, Fill Processing, and Venue Connectivity

> Discover how the Nautilus Trader ExecutionEngine uses a Rust-based pipeline for seamless order submission fill processing and venue connectivity. Ensure deterministic trading.

- Repository: [Nautech Systems/nautilus_trader](https://github.com/nautechsystems/nautilus_trader)
- Tags: internals
- Published: 2026-02-16

---

**The ExecutionEngine orchestrates order routing, validates venue matching, reconciles incoming fill reports, and manages execution client connectivity through a centralized Rust-based pipeline that ensures deterministic state transitions across multiple trading venues.**

The `ExecutionEngine` serves as the central nervous system for order execution in the Nautilus Trader open-source trading platform. Written in Rust and located in the `nautechsystems/nautilus_trader` repository, this component manages the complete lifecycle of orders—from initial submission through fill reconciliation—while maintaining robust connectivity to diverse trading venues. Understanding how the ExecutionEngine handles order submission, fill processing, and venue connectivity is essential for developers building reliable algorithmic trading systems.

## Order Submission Routing and Validation

When a `TradingCommand` arrives on the message bus, the ExecutionEngine first resolves which execution client should receive the command. This routing logic is implemented in [`crates/execution/src/engine/mod.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/execution/src/engine/mod.rs) at lines 927-945:

```rust
match command {
    TradingCommand::SubmitOrder(cmd)       => self.handle_submit_order(client, cmd),
    TradingCommand::SubmitOrderList(cmd)   => self.handle_submit_order_list(client, cmd),
    // … other commands
}

```

The engine selects the client from a three-tier hierarchy:

1. **Explicit `client_id`** specified on the command itself
2. **Venue-based routing map** (`self.routing_map`) that maps venues to client IDs
3. **Default client** as a fallback mechanism

If no client can be resolved, the engine logs an error and drops the command, preventing orders from being sent to invalid destinations.

## Processing Single Orders and Order Lists

### Single Order Submission

The `handle_submit_order` method in [`crates/execution/src/engine/mod.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/execution/src/engine/mod.rs) (lines 959-1033) implements the complete validation and submission pipeline:

```rust
fn handle_submit_order(&self, client: &dyn ExecutionClient, cmd: &SubmitOrder) {
    // 1️⃣ Pull the order from the cache
    let mut order = match self.cache.borrow().order(&cmd.client_order_id) {
        Some(o) => o.clone(),
        None => { log::error!("Cannot handle submit order: order not found in cache …"); return; }
    };

    // 2️⃣ Verify the order’s venue matches the client’s venue
    if order.instrument_id().venue != client.venue() {
        self.deny_order(&order, &format!("Order venue … does not match client venue …"));
        return;
    }

    // 3️⃣ (Optional) Snapshot the order state
    if self.config.snapshot_orders { self.create_order_state_snapshot(&order); }

    // 4️⃣ Retrieve the instrument (needed for quote‑quantity conversion)
    let instrument = match self.cache.borrow().instrument(&order.instrument_id()) {
        Some(i) => i.clone(),
        None => { log::error!("Cannot handle submit order: no instrument found …"); return; }
    };

    // 5️⃣ Quote‑quantity conversion (deprecated flag)
    if self.config.convert_quote_qty_to_base && !instrument.is_inverse() && order.is_quote_quantity() {
        // … conversion logic
    }

    // 6️⃣ Own‑order‑book handling (if enabled)
    if self.config.manage_own_order_books && should_handle_own_book_order(&order) {
        let mut own_book = self.get_or_init_own_order_book(&order.instrument_id());
        own_book.add(order.to_own_book_order());
    }

    // 7️⃣ Forward the command to the client
    if let Err(e) = client.submit_order(cmd) {
        self.deny_order(&order, &format!("failed‑to‑submit‑order‑to‑client: {e}"));
    }
}

```

**Key validation steps include:**

- **Cache lookup**: Validates the order exists in the shared `Cache` before processing
- **Venue matching**: Ensures `order.instrument_id().venue` equals `client.venue()` to prevent misrouting
- **State snapshots**: Optional persistence via `snapshot_orders` configuration for audit trails
- **Quote quantity conversion**: Legacy support for converting quote-denominated quantities to base units
- **Own order book management**: Internal order book tracking when `manage_own_order_books` is enabled

### Batch Order List Submission

The `handle_submit_order_list` method (lines 1035-1115 in the same file) follows an analogous pattern but operates on vectors of orders. It performs batch venue validation, optional snapshots for all orders, and bulk quote-quantity conversion before invoking `client.submit_order_list`.

## Fill Report Reconciliation and Position Management

### Receiving Fill Reports

When an `ExecutionReport::Fill` arrives from a venue, the engine forwards it to the reconciliation logic at lines 647-658 in [`crates/execution/src/engine/mod.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/execution/src/engine/mod.rs):

```rust
ExecutionReport::Fill(fill_report) => {
    self.reconcile_fill_report(fill_report);
}

```

### Reconciliation Logic

The `reconcile_fill_report` method (lines 702-735) implements the core reconciliation pipeline:

```rust
pub fn reconcile_fill_report(&mut self, report: &FillReport) {
    // 1️⃣ Find the associated order (by venue/client order IDs)
    // 2️⃣ Validate the fill (duplicate, over‑fill, missing instrument/account, etc.)
    // 3️⃣ If valid, generate an OrderFilled event for the order
    //    (handled by `apply_fill_to_order` → `apply_order_event`)
}

```

The engine delegates validation to the `reconcile_fill` helper from `nautilus_execution::reconciliation`, which respects the `allow_overfills` configuration flag to determine whether over-fills are tolerated or rejected.

### Applying Fills to Orders

The `apply_fill_to_order` method (lines 660-675) handles the state transition:

```rust
fn apply_fill_to_order(&self, order: &mut OrderAny, fill: OrderFilled) -> anyhow::Result<()> {
    if order.is_duplicate_fill(&fill) { … }
    self.check_overfill(order, &fill)?;
    let event = OrderEventAny::Filled(fill);
    self.apply_order_event(order, event)
}

```

**Duplicate fills** are detected early and ignored with a warning. **Over-fills** are either logged as warnings (if `allow_overfills` is true) or rejected with an error via `check_overfill` (lines 27-40).

### Position Updates and OMS Handling

Once the fill event is applied, `handle_order_fill` (lines 1457-1478) updates portfolio positions:

```rust
fn handle_order_fill(&mut self, order: &OrderAny, fill: OrderFilled, oms_type: OmsType) {
    // 1️⃣ Ensure instrument & account exist
    // 2️⃣ Determine the correct OMS (hedging vs. netting) → `determine_oms_type`
    // 3️⃣ Determine (or generate) a PositionId → `determine_position_id`
    // 4️⃣ Apply the fill to the position (open, update, flip, close)
    // 5️⃣ Publish `OrderFilled` and `Position*` events via the msgbus
}

```

**OMS Determination** (lines 723-746):

```rust
fn determine_oms_type(&self, fill: &OrderFilled) -> OmsType {
    // Strategy‑level override → self.oms_overrides
    // Else native client OMS (via routing_map lookup)
    // Else default client OMS
    // Fallback: Netting
}

```

**Position ID Generation**:
- **Hedging**: Attempts to reuse existing positions for spawned orders or generates new IDs via `self.pos_id_generator.generate`
- **Netting**: Deterministic ID derived from `instrument_id` and `strategy_id`

The resulting events are published on the message bus (`msgbus::publish_order_event`, `msgbus::publish_position_event`). If `snapshot_positions` is enabled, state snapshots are persisted for recovery.

## Venue Connectivity and Client Registration

### Registering Execution Clients

The engine maintains a registry of execution clients through the `register_client` method in [`crates/execution/src/engine/mod.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/execution/src/engine/mod.rs) (lines 722-735):

```rust
pub fn register_client(&mut self, client: Box<dyn ExecutionClient>) -> anyhow::Result<()> {
    let client_id = client.client_id();
    let venue = client.venue();
    // … duplicate‑client check …
    let adapter = ExecutionClientAdapter::new(client);
    self.routing_map.insert(venue, client_id);
    self.clients.insert(client_id, adapter);
    Ok(())
}

```

The `routing_map` creates a direct link between a **venue** and its **client ID**, enabling O(1) lookup during command routing.

### Default Client Fallback

For commands that lack explicit venue mappings, the engine supports a default client (lines 943-951):

```rust
pub fn register_default_client(&mut self, client: Box<dyn ExecutionClient>) {
    let client_id = client.client_id();
    self.default_client = Some(ExecutionClientAdapter::new(client));
    log::debug!("Registered default client {client_id}");
}

```

This fallback ensures that order submission commands without specific client assignments still reach an execution venue rather than being dropped.

### Connection Status Monitoring

The engine exposes several helper methods to monitor venue health:

| Method | Purpose | Code Location |
|--------|---------|---------------|
| `check_connected` | Returns **true** only if **all** registered clients *and* the default client are connected | Lines 11-19 |
| `check_disconnected` | Returns **true** only if **all** are *disconnected* | Lines 21-29 |
| `client_connection_status` | Returns vector of `(ClientId, bool)` tuples indicating individual client connectivity | Lines 32-45 |
| `get_external_client_ids` / `external_order_claims` | Support external execution streams not handled by the engine (e.g., separate order-matching services) | Lines 55-70 |

These helpers read the `is_connected()` state from each `ExecutionClientAdapter`, which delegates to the underlying `ExecutionClient` implementation. Higher-level components like the `Trader` or `Kernel` invoke `exec_engine.check_connected()` before starting live trading, ensuring no orders are lost or sent to unavailable venues.

## Practical Implementation Example

The following pattern demonstrates how to instantiate and configure the ExecutionEngine for live trading:

```rust
use nautilus_execution::engine::{ExecutionEngine, ExecutionEngineConfig};
use std::rc::Rc;
use std::cell::RefCell;
use nautilus_common::{clock::Clock, cache::Cache};

// 1️⃣ Build core services
let clock: Rc<RefCell<dyn Clock>> = // ... initialize clock
let cache: Rc<RefCell<Cache>> = // ... initialize cache

// 2️⃣ Create the engine with default configuration
let mut engine = ExecutionEngine::new(clock.clone(), cache.clone(), None);

// 3️⃣ Register a concrete execution client (e.g., a mock exchange)
let client: Box<dyn ExecutionClient> = Box::new(MyMockExchange::new());
engine.register_client(client)?;  // Maps venue to client_id

// 4️⃣ (Optional) Register a default fallback client
engine.register_default_client(Box::new(FallbackClient::new()));

// 5️⃣ Verify connectivity before live trading
assert!(engine.check_connected(), "One or more venues are offline");

// 6️⃣ Submit an order (order must already exist in cache)
let submit_cmd = SubmitOrder::new(/* ... */);
engine.execute(TradingCommand::SubmitOrder(submit_cmd));

```

This initialization pattern mirrors the system kernel's construction logic in [`crates/system/src/kernel.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/system/src/kernel.rs) (lines 139-150), ensuring the engine is fully wired into the message bus and ready for deterministic order execution.

## Summary

- **Order submission** requires cache validation, venue matching against the execution client, optional state snapshots, and forwarding to the correct `ExecutionClient` via the routing map—errors generate denied-order events rather than silent failures.
- **Fill processing** reconciles incoming `FillReport` events against cached orders, detects duplicates and over-fills (respecting the `allow_overfills` configuration), applies fills to orders, and updates positions using either hedging or netting OMS logic with deterministic `PositionId` generation.
- **Venue connectivity** relies on a registration system mapping venues to client IDs, with optional default client fallback and comprehensive connection status helpers (`check_connected`, `client_connection_status`) that prevent trading when venues are unavailable.
- **Configuration options** including `snapshot_orders`, `manage_own_order_books`, and `convert_quote_qty_to_base` provide fine-grained control over order state persistence and quantity handling.

## Frequently Asked Questions

### How does the ExecutionEngine prevent orders from being sent to the wrong venue?

The engine validates that `order.instrument_id().venue` matches `client.venue()` in the `handle_submit_order` method (lines 959-1033 of [`crates/execution/src/engine/mod.rs`](https://github.com/nautechsystems/nautilus_trader/blob/main/crates/execution/src/engine/mod.rs)). If the venues do not match, the engine immediately calls `deny_order` and returns without forwarding the command to the client, preventing misrouted orders from reaching external venues.

### What happens when a fill report arrives for an order that is already filled?

The `apply_fill_to_order` method (lines 660-675) checks for duplicate fills using `order.is_duplicate_fill(&fill)`. If a duplicate is detected, the engine logs a warning and ignores the fill. For over-fills (fills exceeding the order quantity), the engine checks the `allow_overfills` configuration flag—if false, it rejects the fill with an error; if true, it logs a warning and processes the fill.

### How does the ExecutionEngine support both hedging and netting position management?

The engine determines the Order Management System (OMS) type via `determine_oms_type` (lines 723-746), which checks strategy-level overrides, native client OMS settings from the routing map, or falls back to netting. For hedging, the engine attempts to reuse existing positions or generates new `PositionId`s via `self.pos_id_generator.generate`. For netting, it creates deterministic IDs derived from `instrument_id` and `strategy_id`, ensuring consistent position aggregation.

### Can the ExecutionEngine operate with multiple trading venues simultaneously?

Yes, the engine supports multi-venue trading through its client registration system. The `register_client` method (lines 722-735) inserts entries into `self.routing_map`, mapping each venue to its corresponding client ID. Commands are routed based on the order's venue or explicit client ID. Additionally, `register_default_client` (lines 943-951) provides a fallback for commands without specific venue mappings, enabling complex multi-venue strategies while maintaining clean separation between venue connections.