Skip to content

Business Logic Order Routing and Execution

tien.nguyen edited this page Jul 30, 2026 · 1 revision

Business Logic: Order Routing & Smart Order Routing (SOR)

This document explains the execution routing strategies, Smart Order Routing (SOR) NBBO matching algorithms, and VWAP algorithmic time-slicing implemented in Emporia Trading Platform's execution-service.


🎯 Routing Strategy Overview

When execution-service consumes an OrderCreated event from emporia.orders.v1, it selects an execution strategy based on order characteristics:

flowchart TD
    OrderEvent["OrderCreated Event"] --> StrategySelection{"Strategy Selected"}
    StrategySelection -->|DIRECT| DirectRoute["Direct Execution Route"]
    StrategySelection -->|SMART| SorRoute["Smart Order Router (SOR)"]
    StrategySelection -->|VWAP| VwapRoute["VWAP Algorithmic Execution"]

    DirectRoute --> ExchangeCore["Exchange Core Matching Engine"]
    SorRoute --> NbboEval["Evaluate NBBO Across Exchange Venues"]
    NbboEval --> VenueSlice["Route Child Slice to Best Price Venue"]
    VwapRoute --> TimeSlice["Decompose Parent Order into Time Slices"]
    TimeSlice --> ChildOrders["Issue Child Orders via Kafka (order.commands.v1)"]
Loading

🧠 Smart Order Routing (SOR) & NBBO Best Execution

The Smart Order Router (SOR) enforces regulatory Best Execution by analyzing the National Best Bid and Offer (NBBO) across connected liquidity venues.

1. NBBO Best Execution Rules

  • For BUY Orders: Route to the venue displaying the lowest Ask price ($\min P_{\text{ask}}$).
  • For SELL Orders: Route to the venue displaying the highest Bid price ($\max P_{\text{bid}}$).
  • Tie-Breaking Rule: If multiple venues offer identical prices, priority is given to the venue displaying the largest available depth / size ($\max Q_{\text{available}}$).

2. Multi-Venue Order Splitting Algorithm

If a single venue does not possess sufficient quantity to satisfy the incoming order size $Q_{\text{order}}$, the SOR splits the order across venues:

$$\text{ChildQty}_i = \min\left(Q_{\text{available}, i}, Q_{\text{remaining}}\right)$$

$$Q_{\text{remaining}} \leftarrow Q_{\text{remaining}} - \text{ChildQty}_i$$

Example:

  • Parent Order: BUY 500 AAPL @ MARKET
  • Venue A Depth: 100 AAPL @ $150.00
  • Venue B Depth: 300 AAPL @ $150.00
  • Venue C Depth: 200 AAPL @ $150.05
  • Routing Decision:
    • Slice 1: BUY 300 AAPL to Venue B @ $150.00
    • Slice 2: BUY 100 AAPL to Venue A @ $150.00
    • Slice 3: BUY 100 AAPL to Venue C @ $150.05

⏱️ VWAP Algorithmic Time-Slicing Execution

The Volume-Weighted Average Price (VWAP) strategy minimizes market impact for large institutional parent orders by slicing them into child orders distributed across a specified trading time horizon $[T_{\text{start}}, T_{\text{end}}]$.

1. Historical Volume Curve Profiling

The algorithm divides the trading day into $N$ discrete time intervals $t_1, t_2, \dots, t_N$ (e.g., 5-minute intervals). Each interval has a historical volume percentage $w_i$:

$$\sum_{i=1}^{N} w_i = 1.0$$

2. Target Slice Quantity Formula

For a parent order of size $Q_{\text{parent}}$, the child order size $q_i$ to execute in interval $i$ is calculated as:

$$q_i = \text{round to lot size}\left( Q_{\text{parent}} \times w_i \right)$$

sequenceDiagram
    autonumber
    participant ParentOrder as Parent Order (10,000 shares)
    participant VWAP as VWAP Execution Engine
    participant Kafka as Kafka (emporia.order.commands.v1)
    participant Venue as Execution Venue

    Note over VWAP: Load 5-min Intraday Volume Profile
    loop Every 5-Minute Time Interval
        VWAP->>VWAP: Calculate target slice q_i = Q_parent * w_i
        VWAP->>Kafka: Issue Child Order (CREATE 500 shares)
        Kafka->>Venue: Execute Child Order
        Venue-->>VWAP: Child Order Filled
    end
Loading

🔗 Parent-Child Order Tracking & Correlation

  • Parent Order ID: Unique UUID generated upon initial order placement.
  • Child Order ID: Each algorithmic slice receives a unique childOrderId with a reference to parentOrderId.
  • Completion Criteria: Parent order transitions to FILLED only when the cumulative filled quantity of all child orders equals the parent quantity.

Clone this wiki locally