How to Use Built-In Indicators in NautilusTrader Trading Strategies

NautilusTrader provides a high-performance library of technical indicators implemented as C-extension classes that you instantiate, register with your strategy's actor, and read from within on_bar or on_tick callbacks to generate trading signals.

The nautechsystems/nautilus_trader repository ships with a comprehensive suite of technical indicators—including EMA, SMA, MACD, Bollinger Bands, and RSI—implemented as cdef classes for C-speed calculations. Understanding how to use built-in indicators in trading strategies allows you to leverage these optimized tools without writing custom indicator logic from scratch.

Creating Built-In Indicators

You can instantiate indicators either through the factory pattern for moving averages or by directly importing concrete classes for other indicator types.

Using the MovingAverageFactory

The MovingAverageFactory.create method defined in nautilus_trader/indicators/averages.pyx (lines 898-907) provides a unified interface for creating various moving average types:

from nautilus_trader.indicators import MovingAverageFactory, MovingAverageType

# Create a 10-period Exponential Moving Average

ema10 = MovingAverageFactory.create(10, MovingAverageType.EXPONENTIAL)

MovingAverageType is an Enum exposing supported families including EXPONENTIAL, SIMPLE, WILDERS, and WEIGHTED.

Direct Class Instantiation

For specific implementations or non-moving-average indicators, import directly from the indicators package:

from nautilus_trader.indicators import ExponentialMovingAverage

# Instantiate EMA directly (implementation in averages.pyx lines 182-207)

ema10 = ExponentialMovingAverage(10)  # Defaults to PriceType.LAST

The ExponentialMovingAverage class and other indicators are implemented as cdef classes in .pyx files, ensuring the value attribute updates with minimal latency.

Registering Indicators for Automatic Updates

Indicators must be registered with the strategy's actor to receive market data updates automatically. The registration API is defined in nautilus_trader/common/actor.pyx (lines 444-471).

For bar-based strategies, call register_indicator_for_bars inside on_start:

def on_start(self):
    # Subscribe to the bar stream

    self.subscribe_bars(self.bar_type)
    # Register indicator to receive bar updates automatically

    self.register_indicator_for_bars(self.bar_type, self.ema10)

When registered, the engine automatically invokes the indicator's handle_bar method (or handle_quote_tick/handle_trade_tick for tick data) before executing your strategy's on_bar callback. This ensures indicator.value reflects the current bar's calculation when you read it.

For tick-based indicators, use register_indicator_for_quotes or register_indicator_for_trade_ticks instead.

Accessing Indicator Values in Strategy Logic

Inside your strategy callbacks (on_bar, on_tick, etc.), read the indicator's value property to access the latest calculated result. Always check the initialized property first to ensure the indicator has received sufficient data (at least period data points):

def on_bar(self, bar: Bar):
    self.bars_processed += 1
    
    if self.ema10.initialized:
        # Indicator is ready - use the value for signal generation

        ema_value = self.ema10.value
        self.log.info(f"EMA(10) value: {ema_value:.5f}")
        
        # Example signal logic

        if bar.close > ema_value:
            # Bullish condition

            pass
    else:
        # Indicator warming up

        self.log.info(f"Waiting for EMA initialization... ({self.bars_processed} bars processed)")

The value attribute updates automatically via the indicator's internal handle_bar method, which the engine invoked during the registration phase.

Complete Working Example

The repository provides a full implementation in examples/backtest/example_07_using_indicators/strategy.py (lines 27-73). Below is a condensed version demonstrating the complete workflow:

import datetime as dt
from collections import deque
from nautilus_trader.common.enums import LogColor
from nautilus_trader.indicators import MovingAverageFactory, MovingAverageType
from nautilus_trader.model.data import Bar, BarType
from nautilus_trader.trading.strategy import Strategy

class EmaDemoStrategy(Strategy):
    """
    Minimal demo showing how to use built-in indicators in trading strategies
    by calculating a 10-period EMA and logging its values.
    """
    
    def __init__(self, bar_type: BarType):
        super().__init__()
        self.bar_type = bar_type
        self.bars_processed = 0
        
        # Step 1: Create the indicator

        self.ema10 = MovingAverageFactory.create(10, MovingAverageType.EXPONENTIAL)
        self.ema_history: deque[float] = deque(maxlen=100)
    
    def on_start(self):
        # Step 2: Subscribe and register

        self.subscribe_bars(self.bar_type)
        self.register_indicator_for_bars(self.bar_type, self.ema10)
    
    def on_bar(self, bar: Bar):
        self.bars_processed += 1
        
        # Step 3: Use the indicator value

        if self.ema10.initialized:
            self.ema_history.appendleft(self.ema10.value)
            self.log.info(
                f"Bar #{self.bars_processed} | Close={bar.close} | EMA(10)={self.ema10.value:.5f}",
                color=LogColor.YELLOW,
            )
        else:
            self.log.info(
                f"Bar #{self.bars_processed} | Close={bar.close} | Waiting for EMA init...",
                color=LogColor.RED,
            )

When executed through the NautilusTrader backtest engine, this strategy automatically receives bar updates, calculates the EMA in C-speed via the cdef class implementation, and logs values once the 10-period warm-up completes.

Summary

  • Instantiation: Create indicators using MovingAverageFactory.create() for moving averages or direct class imports from nautilus_trader.indicators for other types like MACD or RSI.
  • Registration: Call register_indicator_for_bars() (or tick variants) in on_start() to wire indicators to data streams, as implemented in nautilus_trader/common/actor.pyx.
  • Access: Read indicator.value inside on_bar() or on_tick() after checking indicator.initialized to ensure warm-up completion.
  • Performance: Built-in indicators use cdef C-extension classes defined in .pyx files (e.g., averages.pyx) for high-speed calculations without Python overhead.

Frequently Asked Questions

What indicator types are available in NautilusTrader?

NautilusTrader provides a comprehensive library including moving averages (EMA, SMA, WMA, Wilders), momentum indicators (RSI, MACD), volatility measures (Bollinger Bands, ATR), and volume-based tools. All are implemented as cdef classes in the nautilus_trader.indicators package for C-speed performance.

How do I know when an indicator is ready to use?

Check the initialized boolean property before accessing value. An indicator becomes initialized once it has processed at least period data points. Accessing value before initialization may return None or incomplete calculations depending on the specific indicator implementation.

Can I use indicators with tick data instead of bars?

Yes. Use register_indicator_for_quotes() or register_indicator_for_trade_ticks() instead of register_indicator_for_bars(). The indicator will then receive QuoteTick or TradeTick objects via its handle_quote_tick or handle_trade_tick methods, updating its value property accordingly.

Where can I find example strategies using built-in indicators?

The repository includes a complete working example at examples/backtest/example_07_using_indicators/strategy.py. This file demonstrates creating an EMA indicator via MovingAverageFactory, registering it for bar updates, and accessing values within the on_bar callback to generate trading signals.

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 →