How to Create a Custom Trading Strategy in Nautilus Trader
Create a custom trading strategy by subclassing Strategy from nautilus_trader.trading.strategy, defining a frozen StrategyConfig for parameters, overriding event handlers like on_start and on_bar, then registering the instance with a Trader via add_strategy and start_strategy to begin processing market data.
Nautilus Trader is a high-performance algorithmic trading platform that enables you to create a custom trading strategy by extending its core Python classes. Whether you are backtesting historical data or trading live markets, the framework provides a structured lifecycle for your algorithms through configuration models and event-driven handlers defined in the nautechsystems/nautilus_trader repository.
Understanding the Strategy Architecture
Strategy Configuration (StrategyConfig)
The configuration layer uses immutable data models defined in nautilus_trader/trading/config.py. Every custom strategy requires a frozen msgspec model that inherits from StrategyConfig. This class holds static parameters such as the target InstrumentId, order ID tags, and OMS (Order Management System) type. Because the config is frozen, it can be safely hashed and serialized for distributed deployments.
Strategy Implementation (Strategy Class)
The execution logic resides in a class that inherits from Strategy, implemented in nautilus_trader/trading/strategy.pyx. This Cython base class provides the order factory, portfolio access, and a suite of on_* event hooks. You only override the handlers relevant to your algorithm—such as on_start for initialization, on_bar for bar-driven signals, or on_order_filled for fill notifications. The base class supplies no-op defaults that emit warnings if called unexpectedly.
Step-by-Step Guide to Create a Custom Trading Strategy
- Define the Configuration Class
Create a frozen model inheriting from StrategyConfig to declare your strategy's parameters.
from nautilus_trader.trading.config import StrategyConfig
from nautilus_trader.model.identifiers import InstrumentId
class MyStrategyConfig(StrategyConfig, frozen=True):
"""
Configuration for MyStrategy.
"""
instrument_id: InstrumentId
order_id_tag: str = "001"
quantity: int = 10
- Implement the Strategy Class
Subclass Strategy and override the event handlers required for your logic.
from nautilus_trader.trading.strategy import Strategy
from nautilus_trader.model.data import Bar
from nautilus_trader.model.enums import OrderSide
from nautilus_trader.model.objects import Quantity
class MyStrategy(Strategy):
"""
Example strategy that submits a market order on every bar close.
"""
def __init__(self, config: MyStrategyConfig) -> None:
super().__init__(config)
def on_start(self) -> None:
"""Called when the strategy starts."""
self.log.info("Strategy starting")
self.subscribe_bars(self.config.instrument_id)
def on_bar(self, bar: Bar) -> None:
"""Called on every new bar."""
order = self.order_factory.market(
instrument_id=self.config.instrument_id,
order_side=OrderSide.BUY,
quantity=Quantity(self.config.quantity),
)
self.submit_order(order)
def on_order_filled(self, event) -> None:
"""Called when an order fill is received."""
self.log.info(f"Fill received: {event.order_id}")
- Register with the Trader
Instantiate your strategy and add it to a Trader instance defined in nautilus_trader/trading/trader.py.
from nautilus_trader.trading.trader import Trader
# Assuming trader is already instantiated
strategy = MyStrategy(config)
trader.add_strategy(strategy)
- Start the Strategy
Begin processing market data by starting the strategy lifecycle.
trader.start_strategy(strategy.id)
Complete Working Example
The repository provides a canonical blank template at nautilus_trader/examples/strategies/blank.py that demonstrates the minimal required structure.
from nautilus_trader.trading.config import StrategyConfig
from nautilus_trader.model.data import Bar
from nautilus_trader.model.identifiers import InstrumentId
from nautilus_trader.trading.strategy import Strategy
class MyStrategyConfig(StrategyConfig, frozen=True):
instrument_id: InstrumentId
class MyStrategy(Strategy):
def __init__(self, config: MyStrategyConfig) -> None:
super().__init__(config)
def on_start(self) -> None:
self.log.info("MyStrategy started")
def on_bar(self, bar: Bar) -> None:
self.log.info(f"Received bar {bar}")
To run this example:
from nautilus_trader.examples.strategies.blank import MyStrategy, MyStrategyConfig
from nautilus_trader.model.identifiers import InstrumentId
cfg = MyStrategyConfig(instrument_id=InstrumentId.from_str("ETHUSDT-PERP.BINANCE"))
strategy = MyStrategy(cfg)
trader.add_strategy(strategy)
trader.start_strategy(strategy.id)
Key Source Files and References
nautilus_trader/trading/config.py– DefinesStrategyConfig,ImportableStrategyConfig, andStrategyFactoryfor serializable configuration.nautilus_trader/trading/strategy.pyx– Cython base class providing theStrategyinterface, order factory, and event hooks.nautilus_trader/trading/trader.py– Manages strategy lifecycle viaadd_strategy,start_strategy, andstop_strategy.nautilus_trader/examples/strategies/blank.py– Minimal template for new strategies.nautilus_trader/trading/messages.py– Command definitions for internal strategy control.
Summary
- Create a custom trading strategy by defining a frozen
StrategyConfigsubclass for static parameters and aStrategysubclass for logic. - Place configuration in
nautilus_trader/trading/config.pyand implementation innautilus_trader/trading/strategy.pyx. - Override lifecycle hooks (
on_start,on_stop) and data handlers (on_bar,on_quote_tick) as needed. - Use the built-in
order_factoryto create orders andsubmit_orderto execute them. - Register the strategy with a
Traderusingadd_strategyand activate it withstart_strategyto begin processing events.
Frequently Asked Questions
What is the difference between StrategyConfig and Strategy?
StrategyConfig is an immutable data model defined in nautilus_trader/trading/config.py that holds static parameters like instrument IDs and order tags. Strategy is the executable class defined in nautilus_trader/trading/strategy.pyx that contains the trading logic and event handlers. The configuration instance is passed to the strategy's constructor during instantiation.
How do I subscribe to market data in my custom strategy?
Override the on_start method and call subscription methods such as self.subscribe_bars(instrument_id), self.subscribe_quote_ticks(instrument_id), or self.subscribe_trade_ticks(instrument_id). These methods register your strategy with the data engine, ensuring that the corresponding on_bar, on_quote_tick, or on_trade_tick handlers receive events automatically.
Can I load a custom strategy from a YAML configuration file?
Yes, use ImportableStrategyConfig from nautilus_trader/trading/config.py. Define strategy_path as the dot-path to your strategy class and config_path as the dot-path to your config class, along with a config dictionary of parameters. The StrategyFactory.create method resolves these strings, instantiates the configuration, and builds the strategy for registration with the trader.
What lifecycle methods should I implement for proper resource management?
At minimum, implement on_start to subscribe to market data and initialize internal state, and on_stop to cancel pending orders and release resources. For graceful shutdowns in live trading environments, you may also override on_market_exit to flatten positions. The base class in nautilus_trader/trading/strategy.pyx provides these hooks; only override the ones your algorithm requires.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →