Routing Primary Orders Through TWAP and Custom Execution Algorithms
Summary
This documentation explains how an execution algorithm receives a primary order and can break it into spawned orders. Its built-in TWAP implementation divides an order across a configured time horizon and interval, submitting the first slice immediately and later slices at the selected spacing. Both parameters must be positive, and the horizon must be no shorter than the interval; unsupported or invalid orders can be denied before submission.
The guide also describes implementing a custom Python algorithm, registering it for message-bus routing, validating string-valued parameters, and creating market, market-to-limit, or limit spawns. It details how spawned quantities affect the primary order, including restoration after certain spawn failures and special handling for fills, cancellations, and quote-quantity conversion. Queries can retrieve orders by algorithm or primary-order identifier. This is implementation guidance rather than evidence of execution quality: it reports no market-impact comparisons or performance results, and the exact behavior depends on order state and submission timing.
Key ideas
- TWAP splits a primary order into slices submitted over a configured horizon and interval.
- The first TWAP slice is submitted immediately, with later slices spaced by the configured interval.
- Custom algorithms can validate parameters, deny unsupported orders, and create several types of spawned orders.
- Spawned quantities usually reduce the primary order's remaining quantity, with documented restoration behavior for certain outcomes.
- The guide explains routing and order management but provides no evidence comparing execution quality.
Tags
Full text
# Execution Algorithms
# 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. Use this page to configure TWAP, write an algorithm, and manage spawned
orders.
For the component and routing model, see [Execution](index.md#execution-flow).
## 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`:
```python
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`:
| Key | Meaning |
| --------------- | ------------------------------------------------------- |
| `horizon_secs` | Horizon used with the interval to determine the slices. |
| `interval_secs` | Time 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.
## Custom execution algorithms
To define a Python execution algorithm, subclass `ExecutionAlgorithm` and implement
`on_order(...)`:
```python
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](index.md#order-denied-reasons), 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.
:::warning
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, rejected, canceled, expired, or refused before submission, its
unfilled proportion is restored in the primary order's quantity units while the primary remains
local. This also applies when a venue converts a quote-quantity spawn to base quantity. Once primary
submission is handed off, its quantity is committed and is not changed by a later spawn outcome. A
late fill on a canceled spawn re-deducts the corresponding restored quantity while the primary
remains locally mutable; if that quantity was already reused by a later spawn, the excess is netted
from that spawn's own restoration instead.
Converted quote-quantity spawns calculate the remaining unfilled quantity from cumulative fills and
round it down to the primary's quantity precision. The total deduction does not depend on how many
fill events report the filled quantity.
If a [fill is voided](../events/order_fill_voided.md) after any spawn's unfilled quantity was
restored, the correction returns the additional unfilled quantity
while the primary remains local. It first offsets any late-fill quantity that could not be deducted
from the primary.
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:
```text
{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`.
:::note
The primary and spawned terminology distinguishes execution slicing from parent and child
contingent-order relationships.
:::
## Execution algorithm order queries
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`.
## Related guides
- [Execution](index.md): Component routing, OMS behavior, risk checks, and command outcomes.
- [Execution policies](policies.md): Order-state and command-delivery boundaries.
- [Orders](../orders/): Order types, instructions, and state transitions.Shown in full with attribution under the source's licence. Licence: LGPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.