执行
NautilusTrader can handle trade 执行 and 订单管理 for multiple 策略 and venues simultaneously (per instance). Several interacting components are involved in 执行, making it crucial to understand the possible flows of 执行 messages (commands and events).
The main 执行-related components include:
Strategy
ExecAlgorithm
(执行 algorithms)OrderEmulator
RiskEngine
ExecutionEngine
orLiveExecutionEngine
ExecutionClient
orLiveExecutionClient
执行 flow
The Strategy
base class inherits from Actor
and so contains all of the common 数据 related
methods. It also provides methods for managing 订单 and trade 执行:
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 create the necessary 执行 commands under the hood and send them on the message
bus to the relevant components (point-to-point), as well as publishing any events (such as the
initialization of new 订单 i.e. OrderInitialized
events).
The general 执行 flow looks like the following (each arrow indicates movement across the 消息总线):
Strategy
-> OrderEmulator
-> ExecAlgorithm
-> RiskEngine
-> ExecutionEngine
-> ExecutionClient
The OrderEmulator
and ExecAlgorithm
(s) components are optional in the flow, depending on
individual order parameters (as explained below).
This diagram illustrates message flow (commands and events) across the Nautilus 执行 components.
┌───────────────────┐
│ │
│ │
│ │
┌───────► OrderEmulator ├────────────┐
│ │ │ │
│ │ │ │
│ │ │ │
┌─────────┴──┐ └─────▲──────┬──────┘ │
│ │ │ │ ┌───────▼────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ │ │ │ │ │ │ │ │ │
│ ├──────────┼──────┼───────────► ├───► ├───► │
│ Strategy │ │ │ │ │ │ │ │ │
│ │ │ │ │ RiskEngine │ │ ExecutionEngine │ │ ExecutionClient │
│ ◄──────────┼──────┼───────────┤ ◄───┤ ◄───┤ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
└─────────┬──┘ ┌─────┴──────▼──────┐ └───────▲────────┘ └─────────────────────┘ └─────────────────────┘
│ │ │ │
│ │ │ │
│ │ │ │
└───────► ExecAlgorithm ├────────────┘
│ │
│ │
│ │
└───────────────────┘
订单管理 System (OMS)
An 订单管理 system (OMS) type refers to the method used for assigning 订单 to 头寸 and tracking those 头寸 for an instrument.
OMS types apply to both 策略 and venues (simulated and real). Even if a venue doesn't explicitly
state the method in use, an OMS type is always in effect. The OMS type for a 组件 can be specified
using the OmsType
enum.
The OmsType
enum has three variants:
UNSPECIFIED
: The OMS type defaults based on where it is applied (details below)NETTING
: Positions are combined into a single position per instrument IDHEDGING
: Multiple positions per instrument ID are supported (both long and short)
The table below describes different 配置 combinations and their applicable scenarios.
When the strategy and venue OMS types differ, the ExecutionEngine
handles this by overriding or assigning position_id
values for received OrderFilled
events.
A "virtual position" refers to a position ID that exists within the Nautilus system but not on the venue in
reality.
Strategy OMS | Venue OMS | Description |
---|---|---|
NETTING | NETTING | The strategy uses the venues native OMS type, with a single position ID per instrument ID. |
HEDGING | HEDGING | The strategy uses the venues native OMS type, with multiple position IDs per instrument ID (both LONG and SHORT ). |
NETTING | HEDGING | The strategy overrides the venues native OMS type. The venue tracks multiple 头寸 per instrument ID, but Nautilus maintains a single position ID. |
HEDGING | NETTING | The strategy overrides the venues native OMS type. The venue tracks a single position per instrument ID, but Nautilus maintains multiple position IDs. |
Configuring OMS types separately for 策略 and venues increases platform complexity but allows for a wide range of trading styles and preferences (see below).
OMS config 示例:
- Most cryptocurrency exchanges use a
NETTING
OMS type, representing a single position per market. It may be desirable for a trader to track multiple "virtual" 头寸 for a strategy. - Some FX ECNs or brokers use a
HEDGING
OMS type, tracking multiple 头寸 bothLONG
andSHORT
. The trader may only care about the NET position per currency pair.
Nautilus does not yet 支持 venue-side hedging modes such as Binance BOTH
vs. LONG/SHORT
where the venue nets per direction.
It is advised to keep Binance account configurations as BOTH
so that a single position is netted.
OMS 配置
If a strategy OMS type is not explicitly set using the oms_type
配置 option,
it will default to UNSPECIFIED
. This means the ExecutionEngine
will not override any venue position_id
s,
and the OMS type will follow the venue's OMS type.
When configuring a backtest, you can specify the oms_type
for the venue. To enhance backtest
accuracy, it is recommended to match this with the actual OMS type used by the venue in practice.
Risk engine
The RiskEngine
is a core 组件 of every Nautilus system, including backtest, sandbox, and live environments.
Every order command and event passes through the RiskEngine
unless specifically bypassed in the RiskEngineConfig
.
The RiskEngine
includes several built-in pre-trade risk checks, including:
- Price precisions correct for the instrument
- Prices are positive (unless an option type instrument)
- Quantity precisions correct for the instrument
- Below maximum notional for the instrument
- Within maximum or minimum quantity for the instrument
- Only reducing position when a
reduce_only
执行 instruction is specified for the order
If any risk check fails, an OrderDenied
event is generated, effectively closing the order and
preventing it from progressing further. This event includes a human-readable reason for the denial.
Trading state
Additionally, the current trading state of a Nautilus system affects order flow.
The TradingState
enum has three variants:
ACTIVE
: The system operates normallyHALTED
: The system will not process further order commands until the state changesREDUCING
: The system will only process cancels or commands that reduce open positions
See the RiskEngineConfig
API Reference for further details.
执行 algorithms
The platform supports customized 执行 algorithm components and provides some built-in algorithms, such as the Time-Weighted Average Price (TWAP) algorithm.
TWAP (Time-Weighted Average Price)
The TWAP 执行 algorithm aims to execute 订单 by evenly spreading them over a specified time horizon. The algorithm receives a primary order representing the total size and direction then splits this by spawning smaller child 订单, which are then executed at regular intervals throughout the time horizon.
This helps to reduce the impact of the full size of the primary order on the market, by minimizing the concentration of trade size at any given time.
The algorithm will immediately submit the first order, with the final order submitted being the primary order at the end of the horizon period.
Using the TWAP algorithm as an example (found in /示例/algorithms/twap.py
), this example
demonstrates how to initialize and register a TWAP 执行 algorithm directly with a
BacktestEngine
(assuming an engine is already initialized):
from nautilus_trader.examples.algorithms.twap import TWAPExecAlgorithm
# `engine` is an initialized BacktestEngine instance
exec_algorithm = TWAPExecAlgorithm()
engine.add_exec_algorithm(exec_algorithm)
For this particular algorithm, two parameters must be specified:
horizon_secs
interval_secs
The horizon_secs
parameter determines the time period over which the algorithm will execute, while
the interval_secs
parameter sets the time between individual order executions. These parameters
determine how a primary order is split into a series of spawned 订单.
from decimal import Decimal
from nautilus_trader.model.data import BarType
from nautilus_trader.test_kit.providers import TestInstrumentProvider
from nautilus_trader.examples.strategies.ema_cross_twap import EMACrossTWAP, EMACrossTWAPConfig
# Configure your strategy
config = EMACrossTWAPConfig(
instrument_id=TestInstrumentProvider.ethusdt_binance().id,
bar_type=BarType.from_str("ETHUSDT.BINANCE-250-TICK-LAST-INTERNAL"),
trade_size=Decimal("0.05"),
fast_ema_period=10,
slow_ema_period=20,
twap_horizon_secs=10.0, # execution algorithm parameter (total horizon in seconds)
twap_interval_secs=2.5, # execution algorithm parameter (seconds between orders)
)
# Instantiate your strategy
strategy = EMACrossTWAP(config=config)
Alternatively, you can specify these parameters dynamically per order, determining them based on actual market conditions. In this case, the strategy 配置 parameters could be provided to an 执行 model which determines the horizon and interval.
There is no limit to the number of 执行 algorithm parameters you can create. The parameters just need to be a dictionary with string keys and primitive values (values that can be serialized over the wire, such as ints, floats, and strings).
Writing 执行 algorithms
To implement a custom 执行 algorithm you must define a class which inherits from ExecAlgorithm
.
An 执行 algorithm is a type of Actor
, so it's capable of the following:
- Request and subscribe to 数据
- Access the
缓存
- Set time alerts and/or timers using a
Clock
Additionally it can:
- Access the central
投资组合
- Spawn secondary 订单 from a received primary (original) order
Once an 执行 algorithm is registered, and the system is running, it will receive 订单 off the
messages bus which are addressed to its ExecAlgorithmId
via the exec_algorithm_id
order parameter.
The order may also carry the exec_algorithm_params
being a dict[str, Any]
.
Because of the flexibility of the exec_algorithm_params
dictionary. It's important to thoroughly
validate all of the key value pairs for correct operation of the algorithm (for starters that the
dictionary is not None
and all necessary parameters actually exist).
Received 订单 will arrive via the following on_order(...)
method. These received 订单 are
know as "primary" (original) 订单 when being handled by an 执行 algorithm.
from nautilus_trader.model.orders.base import Order
def on_order(self, order: Order) -> None:
# Handle the order here
When the algorithm is ready to spawn a secondary order, it can use one of the following methods:
spawn_market(...)
(spawns aMARKET
order)spawn_market_to_limit(...)
(spawns aMARKET_TO_LIMIT
order)spawn_limit(...)
(spawns aLIMIT
order)
Additional order types will be implemented in future versions, as the need arises.
Each of these methods takes the primary (original) Order
as the first argument. The primary order
quantity will be reduced by the quantity
passed in (becoming the spawned 订单 quantity).
There must be enough primary order quantity remaining (this is validated).
Once the desired number of secondary 订单 have been spawned, and the 执行 routine is over, the intention is that the algorithm will then finally send the primary (original) order.
Spawned 订单
All secondary 订单 spawned from an 执行 algorithm will carry a exec_spawn_id
which is
simply the ClientOrderId
of the primary (original) order, and whose client_order_id
derives from this original identifier with the following convention:
exec_spawn_id
(primary orderclient_order_id
value)spawn_sequence
(the sequence number for the spawned order)
{exec_spawn_id}-E{spawn_sequence}
e.g. O-20230404-001-000-E1
(for the first spawned order)
The "primary" and "secondary" / "spawn" terminology was specifically chosen to avoid conflict or confusion with the "parent" and "child" contingent 订单 terminology (an 执行 algorithm may also deal with contingent 订单).
Managing 执行 algorithm 订单
The 缓存
provides several methods to aid in managing (keeping track of) the activity of
an 执行 algorithm. Calling the below method will return all 执行 algorithm 订单
for the given query filters.
def orders_for_exec_algorithm(
self,
exec_algorithm_id: ExecAlgorithmId,
venue: Venue | None = None,
instrument_id: InstrumentId | None = None,
strategy_id: StrategyId | None = None,
side: OrderSide = OrderSide.NO_ORDER_SIDE,
) -> list[Order]:
As well as more specifically querying the 订单 for a certain 执行 series/spawn.
Calling the below method will return all 订单 for the given exec_spawn_id
(if found).
def orders_for_exec_spawn(self, exec_spawn_id: ClientOrderId) -> list[Order]:
This also includes the primary (original) order.