跳到主要内容

策略

NautilusTrader 用户体验的核心在于编写和使用交易策略。定义策略涉及继承 Strategy 类并实现策略逻辑所需的方法。

主要功能

  • 所有 Actor 功能
  • 订单管理

与参与者的关系Strategy 类继承自 Actor,这意味着策略可以访问所有参与者功能以及订单管理功能。

提示

我们建议在深入策略开发之前先阅读参与者指南。

策略可以添加到任何环境上下文中的 Nautilus 系统,并将在系统启动后立即根据其逻辑开始发送命令和接收事件。

使用数据摄取、事件处理和订单管理的基本构建块(我们将在下面讨论),可以实现任何类型的策略,包括方向性、动量、再平衡、配对、做市等。

信息

有关所有可用方法的完整描述,请参阅 Strategy API 参考

Nautilus 交易策略有两个主要部分:

  • 策略实现本身,通过继承 Strategy 类定义
  • 可选的策略配置,通过继承 StrategyConfig 类定义
提示

一旦定义了策略,相同的源代码可以用于回测和实时交易。

策略的主要功能包括:

  • 历史数据请求
  • 实时数据订阅
  • 设置时间警报或定时器
  • 缓存访问
  • 投资组合访问
  • 创建和管理订单和头寸

策略实现

基本策略结构

所有策略都必须继承自 Strategy 基类并实现必要的方法:

from nautilus_trader.trading.strategy import Strategy

class MyStrategy(Strategy):
"""
自定义交易策略示例
"""

def __init__(self, config=None):
"""初始化策略"""
super().__init__(config)

# 在这里初始化策略特定的属性
self.my_indicator = None

def on_start(self):
"""策略启动时调用"""
# 订阅数据、设置定时器等
pass

def on_stop(self):
"""策略停止时调用"""
# 清理资源
pass

def on_data(self, data):
"""接收到数据时调用"""
# 处理市场数据
pass

def on_event(self, event):
"""接收到事件时调用"""
# 处理系统事件
pass

生命周期方法

策略有几个重要的生命周期方法:

on_start()

  • 策略启动时调用
  • 用于初始化订阅、指标等
  • 只调用一次

on_stop()

  • 策略停止时调用
  • 用于清理资源
  • 只调用一次

on_reset()

  • 策略重置时调用
  • 用于重置状态和指标
  • 主要用于回测

on_save()

  • 保存策略状态时调用
  • 返回需要持久化的状态数据

on_load()

  • 加载策略状态时调用
  • 从持久化数据恢复状态

数据处理方法

策略可以处理各种类型的市场数据:

on_quote_tick(tick)

  • 处理报价 tick 数据
  • 包含买卖价格信息

on_trade_tick(tick)

  • 处理成交 tick 数据
  • 包含实际成交信息

on_bar(bar)

  • 处理 K 线数据
  • 包含开高低收量信息

on_order_book_deltas(deltas)

  • 处理订单簿增量更新
  • 用于高频策略

on_order_book(order_book)

  • 处理完整订单簿快照
  • 包含完整的买卖盘信息

事件处理方法

策略可以响应各种系统事件:

on_order_submitted(event)

  • 订单提交事件

on_order_accepted(event)

  • 订单接受事件

on_order_filled(event)

  • 订单成交事件

on_position_opened(event)

  • 头寸开仓事件

on_position_closed(event)

  • 头寸平仓事件

订单管理

策略可以创建和管理各种类型的订单:

# 市价单
order = self.order_factory.market(
instrument_id=self.instrument_id,
order_side=OrderSide.BUY,
quantity=Quantity.from_int(100),
)
self.submit_order(order)

# 限价单
order = self.order_factory.limit(
instrument_id=self.instrument_id,
order_side=OrderSide.SELL,
quantity=Quantity.from_int(100),
price=Price.from_str("1.2000"),
)
self.submit_order(order)

# 止损单
order = self.order_factory.stop_market(
instrument_id=self.instrument_id,
order_side=OrderSide.SELL,
quantity=Quantity.from_int(100),
trigger_price=Price.from_str("1.1900"),
)
self.submit_order(order)

技术指标

策略可以使用内置的技术指标:

from nautilus_trader.indicators.average.sma import SimpleMovingAverage
from nautilus_trader.indicators.average.ema import ExponentialMovingAverage
from nautilus_trader.indicators.momentum.rsi import RelativeStrengthIndex

class IndicatorStrategy(Strategy):
def __init__(self, config=None):
super().__init__(config)

# 创建指标
self.sma = SimpleMovingAverage(20)
self.ema = ExponentialMovingAverage(12)
self.rsi = RelativeStrengthIndex(14)

def on_bar(self, bar):
# 更新指标
self.sma.update_raw(bar.close.as_double())
self.ema.update_raw(bar.close.as_double())
self.rsi.update_raw(bar.close.as_double())

# 检查指标是否已初始化
if not self.rsi.initialized:
return

# 使用指标值进行交易决策
if self.rsi.value < 30: # 超卖
self.buy()
elif self.rsi.value > 70: # 超买
self.sell()

Since a trading strategy is a class which inherits from Strategy, you must define a constructor where you can handle initialization. Minimally the base/super class needs to be initialized:

from nautilus_trader.trading.strategy import Strategy

class MyStrategy(Strategy):
def __init__(self) -> None:
super().__init__() # <-- the superclass must be called to initialize the strategy

From here, you can implement handlers as necessary to perform actions based on state transitions and events.

注意

Do not call components such as clock and logger in the __init__ constructor (which is prior to registration). This is because the systems clock and 日志记录 subsystem have not yet been initialized.

Handlers

Handlers are methods within the Strategy class which may perform actions based on different types of events or on state changes. These methods are named with the prefix on_*. You can choose to implement any or all of these handler methods depending on the specific goals and needs of your strategy.

The purpose of having multiple handlers for similar types of events is to provide flexibility in handling granularity. This means that you can choose to respond to specific events with a dedicated handler, or use a more generic handler to react to a range of related events (using typical switch statement logic). The handlers are called in sequence from the most specific to the most general.

Stateful actions

These handlers are triggered by lifecycle state changes of the Strategy. It's recommended to:

  • Use the on_start method to initialize your strategy (e.g., fetch 金融工具, subscribe to 数据).
  • Use the on_stop method for cleanup tasks (e.g., cancel open 订单, close open 头寸, unsubscribe from 数据).
def on_start(self) -> None:
def on_stop(self) -> None:
def on_resume(self) -> None:
def on_reset(self) -> None:
def on_dispose(self) -> None:
def on_degrade(self) -> None:
def on_fault(self) -> None:
def on_save(self) -> dict[str, bytes]: # Returns user-defined dictionary of state to be saved
def on_load(self, state: dict[str, bytes]) -> None:

数据 handling

These handlers receive 数据 updates, including built-in market 数据 and custom user-defined 数据. You can use these handlers to define actions upon receiving 数据 object instances.

from nautilus_trader.core import Data
from nautilus_trader.model import OrderBook
from nautilus_trader.model import Bar
from nautilus_trader.model import QuoteTick
from nautilus_trader.model import TradeTick
from nautilus_trader.model import OrderBookDeltas
from nautilus_trader.model import InstrumentClose
from nautilus_trader.model import InstrumentStatus
from nautilus_trader.model.instruments import Instrument

def on_order_book_deltas(self, deltas: OrderBookDeltas) -> None:
def on_order_book(self, order_book: OrderBook) -> None:
def on_quote_tick(self, tick: QuoteTick) -> None:
def on_trade_tick(self, tick: TradeTick) -> None:
def on_bar(self, bar: Bar) -> None:
def on_instrument(self, instrument: Instrument) -> None:
def on_instrument_status(self, data: InstrumentStatus) -> None:
def on_instrument_close(self, data: InstrumentClose) -> None:
def on_historical_data(self, data: Data) -> None:
def on_data(self, data: Data) -> None: # Custom data passed to this handler
def on_signal(self, signal: Data) -> None: # Custom signals passed to this handler

订单管理

These handlers receive events related to 订单. OrderEvent type messages are passed to handlers in the following sequence:

  1. Specific handler (e.g., on_order_accepted, on_order_rejected, etc.)
  2. on_order_event(...)
  3. on_event(...)
from nautilus_trader.model.events import OrderAccepted
from nautilus_trader.model.events import OrderCanceled
from nautilus_trader.model.events import OrderCancelRejected
from nautilus_trader.model.events import OrderDenied
from nautilus_trader.model.events import OrderEmulated
from nautilus_trader.model.events import OrderEvent
from nautilus_trader.model.events import OrderExpired
from nautilus_trader.model.events import OrderFilled
from nautilus_trader.model.events import OrderInitialized
from nautilus_trader.model.events import OrderModifyRejected
from nautilus_trader.model.events import OrderPendingCancel
from nautilus_trader.model.events import OrderPendingUpdate
from nautilus_trader.model.events import OrderRejected
from nautilus_trader.model.events import OrderReleased
from nautilus_trader.model.events import OrderSubmitted
from nautilus_trader.model.events import OrderTriggered
from nautilus_trader.model.events import OrderUpdated

def on_order_initialized(self, event: OrderInitialized) -> None:
def on_order_denied(self, event: OrderDenied) -> None:
def on_order_emulated(self, event: OrderEmulated) -> None:
def on_order_released(self, event: OrderReleased) -> None:
def on_order_submitted(self, event: OrderSubmitted) -> None:
def on_order_rejected(self, event: OrderRejected) -> None:
def on_order_accepted(self, event: OrderAccepted) -> None:
def on_order_canceled(self, event: OrderCanceled) -> None:
def on_order_expired(self, event: OrderExpired) -> None:
def on_order_triggered(self, event: OrderTriggered) -> None:
def on_order_pending_update(self, event: OrderPendingUpdate) -> None:
def on_order_pending_cancel(self, event: OrderPendingCancel) -> None:
def on_order_modify_rejected(self, event: OrderModifyRejected) -> None:
def on_order_cancel_rejected(self, event: OrderCancelRejected) -> None:
def on_order_updated(self, event: OrderUpdated) -> None:
def on_order_filled(self, event: OrderFilled) -> None:
def on_order_event(self, event: OrderEvent) -> None: # All order event messages are eventually passed to this handler

Position management

These handlers receive events related to 头寸. PositionEvent type messages are passed to handlers in the following sequence:

  1. Specific handler (e.g., on_position_opened, on_position_changed, etc.)
  2. on_position_event(...)
  3. on_event(...)
from nautilus_trader.model.events import PositionChanged
from nautilus_trader.model.events import PositionClosed
from nautilus_trader.model.events import PositionEvent
from nautilus_trader.model.events import PositionOpened

def on_position_opened(self, event: PositionOpened) -> None:
def on_position_changed(self, event: PositionChanged) -> None:
def on_position_closed(self, event: PositionClosed) -> None:
def on_position_event(self, event: PositionEvent) -> None: # All position event messages are eventually passed to this handler

Generic event handling

This handler will eventually receive all event messages which arrive at the strategy, including those for which no other specific handler exists.

from nautilus_trader.core.message import Event

def on_event(self, event: Event) -> None:

Handler example

The following example shows a typical on_start handler method 实现 (taken from the example EMA cross strategy). Here we can see the following:

  • Indicators being registered to receive bar updates
  • Historical 数据 being requested (to hydrate the indicators)
  • Live 数据 being subscribed to
def on_start(self) -> None:
"""
Actions to be performed on strategy start.
"""
self.instrument = self.cache.instrument(self.instrument_id)
if self.instrument is None:
self.log.error(f"Could not find instrument for {self.instrument_id}")
self.stop()
return

# Register the indicators for updating
self.register_indicator_for_bars(self.bar_type, self.fast_ema)
self.register_indicator_for_bars(self.bar_type, self.slow_ema)

# Get historical data
self.request_bars(self.bar_type)

# Subscribe to live data
self.subscribe_bars(self.bar_type)
self.subscribe_quote_ticks(self.instrument_id)

Clock and timers

策略 have access to a Clock which provides a number of methods for creating different timestamps, as well as setting time alerts or timers to trigger TimeEvents.

信息

See the Clock API reference for a complete list of available methods.

Current timestamps

While there are multiple ways to obtain current timestamps, here are two commonly used methods as 示例:

To get the current UTC timestamp as a tz-aware pd.Timestamp:

import pandas as pd


now: pd.Timestamp = self.clock.utc_now()

To get the current UTC timestamp as nanoseconds since the UNIX epoch:

unix_nanos: int = self.clock.timestamp_ns()

Time alerts

Time alerts can be set which will result in a TimeEvent being dispatched to the on_event handler at the specified alert time. In a live context, this might be slightly delayed by a few microseconds.

This example sets a time alert to trigger one minute from the current time:

import pandas as pd

# Fire a TimeEvent one minute from now
self.clock.set_time_alert(
name="MyTimeAlert1",
alert_time=self.clock.utc_now() + pd.Timedelta(minutes=1),
)

Timers

Continuous timers can be set up which will generate a TimeEvent at regular intervals until the timer expires or is canceled.

This example sets a timer to fire once per minute, starting immediately:

import pandas as pd

# Fire a TimeEvent every minute
self.clock.set_timer(
name="MyTimer1",
interval=pd.Timedelta(minutes=1),
)

缓存 access

The trader instances central 缓存 can be accessed to fetch 数据 and 执行 objects (订单, 头寸 etc). There are many methods available often with filtering functionality, here we go through some basic use cases.

Fetching 数据

The following example shows how 数据 can be fetched from the 缓存 (assuming some instrument ID attribute is assigned):

last_quote = self.cache.quote_tick(self.instrument_id)
last_trade = self.cache.trade_tick(self.instrument_id)
last_bar = self.cache.bar(bar_type)

Fetching 执行 objects

The following example shows how individual order and position objects can be fetched from the 缓存:

order = self.cache.order(client_order_id)
position = self.cache.position(position_id)

信息

See the Cache API Reference for a complete description of all available methods.

投资组合 access

The traders central 投资组合 can be accessed to fetch account and positional information. The following shows a general outline of available methods.

Account and positional information

import decimal

from nautilus_trader.accounting.accounts.base import Account
from nautilus_trader.model import Venue
from nautilus_trader.model import Currency
from nautilus_trader.model import Money
from nautilus_trader.model import InstrumentId

def account(self, venue: Venue) -> Account

def balances_locked(self, venue: Venue) -> dict[Currency, Money]
def margins_init(self, venue: Venue) -> dict[Currency, Money]
def margins_maint(self, venue: Venue) -> dict[Currency, Money]
def unrealized_pnls(self, venue: Venue) -> dict[Currency, Money]
def realized_pnls(self, venue: Venue) -> dict[Currency, Money]
def net_exposures(self, venue: Venue) -> dict[Currency, Money]

def unrealized_pnl(self, instrument_id: InstrumentId) -> Money
def realized_pnl(self, instrument_id: InstrumentId) -> Money
def net_exposure(self, instrument_id: InstrumentId) -> Money
def net_position(self, instrument_id: InstrumentId) -> decimal.Decimal

def is_net_long(self, instrument_id: InstrumentId) -> bool
def is_net_short(self, instrument_id: InstrumentId) -> bool
def is_flat(self, instrument_id: InstrumentId) -> bool
def is_completely_flat(self) -> bool
信息

See the Portfolio API Reference for a complete description of all available methods.

报告 and analysis

The 投资组合 also makes a PortfolioAnalyzer available, which can be fed with a flexible amount of 数据 (to accommodate different lookback windows). The analyzer can provide tracking for and generating of 性能 metrics and statistics.

信息

See the PortfolioAnalyzer API Reference for a complete description of all available methods.

信息

See the Portfolio statistics guide.

Trading commands

NautilusTrader offers a comprehensive suite of trading commands, enabling granular 订单管理 tailored for 算法交易. These commands are essential for executing 策略, managing risk, and ensuring seamless interaction with various trading venues. In the following sections, we will delve into the specifics of each command and its use cases.

信息

The Execution guide explains the flow through the system, and can be helpful to read in conjunction with the below.

Submitting 订单

An OrderFactory is provided on the base class for every Strategy as a convenience, reducing the amount of boilerplate required to create different Order objects (although these objects can still be initialized directly with the Order.__init__(...) constructor if the trader prefers).

The 组件 a SubmitOrder or SubmitOrderList command will flow to for 执行 depends on the following:

  • If an emulation_trigger is specified, the command will firstly be sent to the OrderEmulator.
  • If an exec_algorithm_id is specified (with no emulation_trigger), the command will firstly be sent to the relevant ExecAlgorithm.
  • Otherwise, the command will firstly be sent to the RiskEngine.

This example submits a LIMIT BUY order for emulation (see OrderEmulator):

from nautilus_trader.model.enums import OrderSide
from nautilus_trader.model.enums import TriggerType
from nautilus_trader.model.orders import LimitOrder


def buy(self) -> None:
"""
Users simple buy method (example).
"""
order: LimitOrder = self.order_factory.limit(
instrument_id=self.instrument_id,
order_side=OrderSide.BUY,
quantity=self.instrument.make_qty(self.trade_size),
price=self.instrument.make_price(5000.00),
emulation_trigger=TriggerType.LAST_PRICE,
)

self.submit_order(order)
信息

You can specify both order emulation and an 执行 algorithm.

This example submits a MARKET BUY order to a TWAP 执行 algorithm:

from nautilus_trader.model.enums import OrderSide
from nautilus_trader.model.enums import TimeInForce
from nautilus_trader.model import ExecAlgorithmId


def buy(self) -> None:
"""
Users simple buy method (example).
"""
order: MarketOrder = self.order_factory.market(
instrument_id=self.instrument_id,
order_side=OrderSide.BUY,
quantity=self.instrument.make_qty(self.trade_size),
time_in_force=TimeInForce.FOK,
exec_algorithm_id=ExecAlgorithmId("TWAP"),
exec_algorithm_params={"horizon_secs": 20, "interval_secs": 2.5},
)

self.submit_order(order)

Canceling 订单

订单 can be canceled individually, as a batch, or all 订单 for an instrument (with an optional side filter).

If the order is already closed or already pending cancel, then a warning will be logged.

If the order is currently open then the status will become PENDING_CANCEL.

The 组件 a CancelOrder, CancelAllOrders or BatchCancelOrders command will flow to for 执行 depends on the following:

  • If the order is currently emulated, the command will firstly be sent to the OrderEmulator
  • If an exec_algorithm_id is specified (with no emulation_trigger), and the order is still active within the local system, the command will firstly be sent to the relevant ExecAlgorithm
  • Otherwise, the order will firstly be sent to the ExecutionEngine
信息

Any managed GTD timer will also be canceled after the command has left the strategy.

The following shows how to cancel an individual order:


self.cancel_order(order)

The following shows how to cancel a batch of 订单:

from nautilus_trader.model import Order


my_order_list: list[Order] = [order1, order2, order3]
self.cancel_orders(my_order_list)

The following shows how to cancel all 订单:


self.cancel_all_orders()

Modifying 订单

订单 can be modified individually when emulated, or open on a venue (if supported).

If the order is already closed or already pending cancel, then a warning will be logged. If the order is currently open then the status will become PENDING_UPDATE.

注意

At least one value must differ from the original order for the command to be valid.

The 组件 a ModifyOrder command will flow to for 执行 depends on the following:

  • If the order is currently emulated, the command will firstly be sent to the OrderEmulator
  • Otherwise, the order will firstly be sent to the RiskEngine
信息

Once an order is under the control of an 执行 algorithm, it cannot be directly modified by a strategy (only canceled).

The following shows how to modify the size of LIMIT BUY order currently open on a venue:

from nautilus_trader.model import Quantity


new_quantity: Quantity = Quantity.from_int(5)
self.modify_order(order, new_quantity)

信息

The price and trigger price can also be modified (when emulated or supported by a venue).

Strategy 配置

The main purpose of a separate 配置 class is to provide total flexibility over where and how a trading strategy can be instantiated. This includes being able to serialize 策略 and their configurations over the wire, making distributed 回测 and firing up remote 实时交易 possible.

This 配置 flexibility is actually opt-in, in that you can actually choose not to have any strategy 配置 beyond the parameters you choose to pass into your 策略' constructor. If you would like to run distributed backtests or launch 实时交易 servers remotely, then you will need to define a 配置.

Here is an example 配置:

from decimal import Decimal
from nautilus_trader.config import StrategyConfig
from nautilus_trader.model import Bar, BarType
from nautilus_trader.model import InstrumentId
from nautilus_trader.trading.strategy import Strategy


# Configuration definition
class MyStrategyConfig(StrategyConfig):
instrument_id: InstrumentId # example value: "ETHUSDT-PERP.BINANCE"
bar_type: BarType # example value: "ETHUSDT-PERP.BINANCE-15-MINUTE[LAST]-EXTERNAL"
fast_ema_period: int = 10
slow_ema_period: int = 20
trade_size: Decimal
order_id_tag: str


# Strategy definition
class MyStrategy(Strategy):
def __init__(self, config: MyStrategyConfig) -> None:
# Always initialize the parent Strategy class
# After this, configuration is stored and available via `self.config`
super().__init__(config)

# Custom state variables
self.time_started = None
self.count_of_processed_bars: int = 0

def on_start(self) -> None:
self.time_started = self.clock.utc_now() # Remember time, when strategy started
self.subscribe_bars(self.config.bar_type) # See how configuration data are exposed via `self.config`

def on_bar(self, bar: Bar):
self.count_of_processed_bars += 1 # Update count of processed bars


# Instantiate configuration with specific values. By setting:
# - InstrumentId - we parameterize the instrument the strategy will trade.
# - BarType - we parameterize bar-data, that strategy will trade.
config = MyStrategyConfig(
instrument_id=InstrumentId.from_str("ETHUSDT-PERP.BINANCE"),
bar_type=BarType.from_str("ETHUSDT-PERP.BINANCE-15-MINUTE[LAST]-EXTERNAL"),
trade_size=Decimal(1),
order_id_tag="001",
)

# Pass configuration to our trading strategy.
strategy = MyStrategy(config=config)

When implementing 策略, it's recommended to access 配置 values directly through self.config. This provides clear separation between:

  • 配置 数据 (accessed via self.config):

    • Contains initial settings, that define how the strategy works
    • Example: self.config.trade_size, self.config.instrument_id
  • Strategy state variables (as direct attributes):

    • Track any custom state of the strategy
    • Example: self.time_started, self.count_of_processed_bars

This separation makes code easier to understand and maintain.

备注

Even though it often makes sense to define a strategy which will trade a single instrument. The number of 金融工具 a single strategy can work with is only limited by machine resources.

Managed GTD expiry

It's possible for the strategy to manage expiry for 订单 with a time in force of GTD (Good 'till Date). This may be desirable if the exchange/broker does not 支持 this time in force option, or for any reason you prefer the strategy to manage this.

To use this option, pass manage_gtd_expiry=True to your StrategyConfig. When an order is submitted with a time in force of GTD, the strategy will automatically start an internal time alert. Once the internal GTD time alert is reached, the order will be canceled (if not already closed).

Some venues (such as Binance Futures) 支持 the GTD time in force, so to avoid conflicts when using managed_gtd_expiry you should set use_gtd=False for your 执行 客户端 config.

Multiple 策略

If you intend running multiple instances of the same strategy, with different configurations (such as trading different 金融工具), then you will need to define a unique order_id_tag for each of these 策略 (as shown above).

备注

The platform has built-in safety measures in the event that two 策略 share a duplicated strategy ID, then an 异常 will be raised that the strategy ID has already been registered.

The reason for this is that the system must be able to identify which strategy various commands and events belong to. A strategy ID is made up of the strategy class name, and the 策略 order_id_tag separated by a hyphen. For example the above config would result in a strategy ID of MyStrategy-001.

备注

See the StrategyId API Reference for further details.