Strategies

The heart of the NautilusTrader user experience is in writing and working with trading strategies. Defining a trading strategy is achieved by inheriting the Strategy class, and implementing the methods required by the strategy.

Using the basic building blocks of data ingest, event handling, and order management (which we will discuss below), it’s possible to implement any type of trading strategy including directional, momentum, re-balancing, pairs, market making etc.

Refer to the Strategy in the API Reference for a complete description of all the possible functionality.

There are two main parts of a Nautilus trading strategy:

  • The strategy implementation itself, defined by inheriting the Strategy class

  • The optional strategy configuration, defined by inheriting the StrategyConfig class

Note

Once a strategy is defined, the same source can be used for backtesting and live trading.

Implementation

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:

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

Configuration

The main purpose of a separate configuration class is to provide total flexibility over where and how a trading strategy can be instantiated. This includes being able to serialize strategies and their configurations over the wire, making distributed backtesting and firing up remote live trading possible.

This configuration flexibility is actually opt-in, in that you can actually choose not to have any strategy configuration beyond the parameters you choose to pass into your strategies’ constructor. However, if you would like to run distributed backtests or launch live trading servers remotely, then you will need to define a configuration.

Here is an example configuration:

from decimal import Decimal
from nautilus_trader.config import StrategyConfig
from nautilus_trader.model.identifiers import InstrumentId
from nautilus_trader.trading.strategy import Strategy


class MyStrategyConfig(StrategyConfig):
    instrument_id: str
    bar_type: str
    fast_ema_period: int = 10
    slow_ema_period: int = 20
    trade_size: Decimal
    order_id_tag: str

# Here we simply add an instrument ID as a string, to 
# parameterize the instrument the strategy will trade.

class MyStrategy(Strategy):
    def __init__(self, config: MyStrategyConfig) -> None:
        super().__init__(config)

        # Configuration
        self.instrument_id = InstrumentId.from_str(config.instrument_id)


# Once a configuration is defined and instantiated, we can pass this to our 
# trading strategy to initialize.

config = MyStrategyConfig(
    instrument_id="ETHUSDT-PERP.BINANCE",
    bar_type="ETHUSDT-PERP.BINANCE-1000-TICK[LAST]-INTERNAL",
    trade_size=Decimal(1),
    order_id_tag="001",
)

strategy = MyStrategy(config=config)

Note

Even though it often makes sense to define a strategy which will trade a single instrument. There is actually no limit to the number of instruments a single strategy can work with.

Multiple strategies

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

Note

The platform has built-in safety measures in the event that two strategies share a duplicated strategy ID, then an exception will be thrown 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 strategies order_id_tag separated by a hyphen. For example the above config would result in a strategy ID of MyStrategy-001 .

Tip

See the StrategyId documentation for further details.

Managed GTD expiry

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

Simply set the manage_gtd_expiry boolean flag on the submit_order() or submit_order_list() methods to True . This will then start a timer, when the timer expires the order will be canceled (if not already closed).

strategy.submit_order(order, manage_gtd_expiry=True)