Backtest (low-level API)
Tutorial for NautilusTrader a high-performance algorithmic trading platform and event driven backtester.
Overview
This tutorial walks through how to use a BacktestEngine
to backtest a simple EMA cross strategy
with a TWAP execution algorithm on a simulated Binance Spot exchange using historical trade tick data.
The following points will be covered:
- How to load raw data (external to Nautilus) using data loaders and wranglers
- How to add this data to a
BacktestEngine
- How to add venues, strategies and execution algorithms to a
BacktestEngine
- How to run backtests with a
BacktestEngine
- Post-run analysis and options for repeated runs
Prerequisites
- Python 3.11+ installed
- JupyterLab or similar installed (
pip install -U jupyterlab
) - NautilusTrader latest release installed (
pip install -U nautilus_trader
)
Imports
We'll start with all of our imports for the remainder of this tutorial.
from decimal import Decimal
from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.backtest.engine import BacktestEngineConfig
from nautilus_trader.examples.algorithms.twap import TWAPExecAlgorithm
from nautilus_trader.examples.strategies.ema_cross_twap import EMACrossTWAP
from nautilus_trader.examples.strategies.ema_cross_twap import EMACrossTWAPConfig
from nautilus_trader.model import BarType
from nautilus_trader.model import Money
from nautilus_trader.model import TraderId
from nautilus_trader.model import Venue
from nautilus_trader.model.currencies import ETH
from nautilus_trader.model.currencies import USDT
from nautilus_trader.model.enums import AccountType
from nautilus_trader.model.enums import OmsType
from nautilus_trader.persistence.wranglers import TradeTickDataWrangler
from nautilus_trader.test_kit.providers import TestDataProvider
from nautilus_trader.test_kit.providers import TestInstrumentProvider
Loading data
For this tutorial we'll use some stub test data which exists in the NautilusTrader repository (this data is also used by the automated test suite to test the correctness of the platform).
Firstly, instantiate a data provider which we can use to read raw CSV trade tick data into memory as a pd.DataFrame
.
We then need to initialize the instrument which matches the data, in this case the ETHUSDT
spot cryptocurrency pair for Binance.
We'll use this instrument for the remainder of this backtest run.
Next, we need to wrangle this data into a list of Nautilus TradeTick
objects, which can we later add to the BacktestEngine
.
# Load stub test data
provider = TestDataProvider()
trades_df = provider.read_csv_ticks("binance/ethusdt-trades.csv")
# Initialize the instrument which matches the data
ETHUSDT_BINANCE = TestInstrumentProvider.ethusdt_binance()
# Process into Nautilus objects
wrangler = TradeTickDataWrangler(instrument=ETHUSDT_BINANCE)
ticks = wrangler.process(trades_df)
See the Loading External Data guide for a more detailed explanation of the typical data processing components and pipeline.
Initialize a backtest engine
Now we'll need a backtest engine, minimally you could just call BacktestEngine()
which will instantiate
an engine with a default configuration.
Here we also show initializing a BacktestEngineConfig
(will only a custom trader_id
specified)
to show the general configuration pattern.
See the Configuration API reference for details of all configuration options available.
# Configure backtest engine
config = BacktestEngineConfig(trader_id=TraderId("BACKTESTER-001"))
# Build the backtest engine
engine = BacktestEngine(config=config)
Add venues
We'll need a venue to trade on, which should match the market data being added to the engine.
In this case we'll set up a simulated Binance Spot exchange.
# Add a trading venue (multiple venues possible)
BINANCE = Venue("BINANCE")
engine.add_venue(
venue=BINANCE,
oms_type=OmsType.NETTING,
account_type=AccountType.CASH, # Spot CASH account (not for perpetuals or futures)
base_currency=None, # Multi-currency account
starting_balances=[Money(1_000_000.0, USDT), Money(10.0, ETH)],
)
Add data
Now we can add data to the backtest engine. First add the Instrument
object we previously initialized, which matches our data.
Then we can add the trades we wrangled earlier.
# Add instrument(s)
engine.add_instrument(ETHUSDT_BINANCE)
# Add data
engine.add_data(ticks)
The amount of and variety of data types is only limited by machine resources and your imagination (custom types are possible). Also, multiple venues can be used for backtesting, again only limited by machine resources.