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

  1. 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
  1. 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}")
  1. 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)
  1. 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

Summary

  • Create a custom trading strategy by defining a frozen StrategyConfig subclass for static parameters and a Strategy subclass for logic.
  • Place configuration in nautilus_trader/trading/config.py and implementation in nautilus_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_factory to create orders and submit_order to execute them.
  • Register the strategy with a Trader using add_strategy and activate it with start_strategy to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →