-
Notifications
You must be signed in to change notification settings - Fork 0
Quickstart
Tutorial for NautilusTrader a high-performance algorithmic trading platform and event-driven backtester.
NautilusTrader 高性能算法交易平台和事件驱动回测器的教程。
View source on GitHub.
在 GitHub 上查看源代码。
This quickstart tutorial steps through how to get up and running with NautilusTrader backtesting using FX data. To support this, some pre-loaded test data is available using the standard Nautilus persistence format (Parquet).
本快速入门教程将逐步介绍如何使用外汇数据启动并运行 NautilusTrader 回测。为了支持这一点,使用标准的 Nautilus 持久化格式(Parquet)提供了一些预加载的测试数据。
- Python 3.10+ installed
- Python 3.10+ 已安装
- NautilusTrader latest release installed (pip install -U nautilus_trader)
- NautilusTrader 最新版本已安装(pip install -U nautilus_trader)
- JupyterLab or similar installed (pip install -U jupyterlab)
- JupyterLab 或类似软件已安装(pip install -U jupyterlab)
To save time, we have prepared a script to load sample data into the Nautilus format for use with this example. First, download and load the data by running the next cell (this should take ~ 1-2 mins):
为了节省时间,我们准备了一个脚本,将示例数据加载到 Nautilus 格式中,以便在本示例中使用。首先,通过运行下一个单元格来下载和加载数据(这应该需要大约 1-2 分钟):
!apt-get update && apt-get install curl -y
!curl https://raw.githubusercontent.com/nautechsystems/nautilus_data/main/nautilus_data/hist_data_to_catalog.py | python -
For further details on how to load data into Nautilus, see Loading External Data guide.
有关如何将数据加载到 Nautilus 的更多详细信息,请参阅加载外部数据指南。
from nautilus_trader.backtest.node import BacktestDataConfig
from nautilus_trader.backtest.node import BacktestEngineConfig
from nautilus_trader.backtest.node import BacktestNode
from nautilus_trader.backtest.node import BacktestRunConfig
from nautilus_trader.backtest.node import BacktestVenueConfig
from nautilus_trader.config import ImportableStrategyConfig
from nautilus_trader.config import LoggingConfig
from nautilus_trader.model.data import QuoteTick
from nautilus_trader.model.objects import Quantity
from nautilus_trader.persistence.catalog import ParquetDataCatalog
If everything worked correctly, you should be able to see a single EUR/USD instrument in the catalog.
如果一切正常,您应该能够在目录中看到一个 EUR/USD 金融工具。
# You can also use a relative path such as `ParquetDataCatalog("./catalog")`,
# for example if you're running this notebook after the data setup from the docs.
# catalog = ParquetDataCatalog("./catalog")
catalog = ParquetDataCatalog.from_env()
catalog.instruments()
NautilusTrader includes many indicators built-in, in this example we will use the MACD indicator to build a simple trading strategy.
NautilusTrader 包含许多内置指标,在本例中,我们将使用 MACD 指标构建一个简单的交易策略。
You can read more about MACD here, this indicator merely serves as an example without any expected alpha. There is also a way of registering indicators to receive certain data types, however in this example we manually pass the received QuoteTick to the indicator in the on_quote_tick method.
您可以在这里阅读更多关于 MACD 的信息,此指标仅作为示例,没有任何预期阿尔法。还有一种方法可以注册指标以接收某些数据类型,但是,在本例中,我们在 on_quote_tick 方法中手动将接收到的 QuoteTick 传递给指标。
from nautilus_trader.core.message import Event
from nautilus_trader.indicators.macd import MovingAverageConvergenceDivergence
from nautilus_trader.model.enums import OrderSide
from nautilus_trader.model.enums import PositionSide
from nautilus_trader.model.enums import PriceType
from nautilus_trader.model.events import PositionOpened
from nautilus_trader.model.identifiers import InstrumentId
from nautilus_trader.model.position import Position
from nautilus_trader.trading.strategy import Strategy
from nautilus_trader.trading.strategy import StrategyConfig
class MACDConfig(StrategyConfig):
instrument_id: InstrumentId
fast_period: int = 12
slow_period: int = 26
trade_size: int = 1_000_000
entry_threshold: float = 0.00010
class MACDStrategy(Strategy):
def __init__(self, config: MACDConfig):
super().__init__(config=config)
# Our "trading signal"
self.macd = MovingAverageConvergenceDivergence(
fast_period=config.fast_period, slow_period=config.slow_period, price_type=PriceType.MID
)
# We copy some config values onto the class to make them easier to reference later on
self.entry_threshold = config.entry_threshold
self.instrument_id = config.instrument_id
self.trade_size = Quantity.from_int(config.trade_size)
self.entry_threshold = config.entry_threshold
# Convenience
self.position: Position | None = None
def on_start(self):
self.subscribe_quote_ticks(instrument_id=self.instrument_id)
def on_stop(self):
self.close_all_positions(self.instrument_id)
self.unsubscribe_quote_ticks(instrument_id=self.instrument_id)
def on_quote_tick(self, tick: QuoteTick):
# You can register indicators to receive quote tick updates automatically,
# here we manually update the indicator to demonstrate the flexibility available.
self.macd.handle_quote_tick(tick)
if not self.macd.initialized:
return # Wait for indicator to warm up
# self._log.info(f"{self.macd.value=}:%5d")
self.check_for_entry()
self.check_for_exit()
def on_event(self, event: Event):
if isinstance(event, PositionOpened):
self.position = self.cache.position(event.position_id)
def check_for_entry(self):
# If MACD line is above our entry threshold, we should be LONG
if self.macd.value > self.entry_threshold:
if self.position and self.position.side == PositionSide.LONG:
return # Already LONG
order = self.order_factory.market(
instrument_id=self.instrument_id,
order_side=OrderSide.BUY,
quantity=self.trade_size,
)
self.submit_order(order)
# If MACD line is below our entry threshold, we should be SHORT
elif self.macd.value < -self.entry_threshold:
if self.position and self.position.side == PositionSide.SHORT:
return # Already SHORT
order = self.order_factory.market(
instrument_id=self.instrument_id,
order_side=OrderSide.SELL,
quantity=self.trade_size,
)
self.submit_order(order)
def check_for_exit(self):
# If MACD line is above zero then exit if we are SHORT
if self.macd.value >= 0.0:
if self.position and self.position.side == PositionSide.SHORT:
self.close_position(self.position)
# If MACD line is below zero then exit if we are LONG
else:
if self.position and self.position.side == PositionSide.LONG:
self.close_position(self.position)
def on_dispose(self):
pass # Do nothing else
Now that we have a trading strategy and data, we can begin to configure a backtest run. Nautilus uses a BacktestNode to orchestrate backtest runs, which requires some setup. This may seem a little complex at first, however, this is necessary for the capabilities that Nautilus strives for.
现在我们有了交易策略和数据,就可以开始配置回测运行了。Nautilus 使用 BacktestNode 来编排回测运行,这需要一些设置。这乍一看可能有点复杂,但这对于 Nautilus 努力实现的功能来说是必要的。
To configure a BacktestNode, we first need to create an instance of a BacktestRunConfig, configuring the following (minimal) aspects of the backtest:
要配置 BacktestNode,我们首先需要创建一个 BacktestRunConfig 的实例,配置回测的以下(最小)方面:
- engine: The engine for the backtest representing our core system, which will also contain our strategies
- engine: 回测的引擎,代表我们的核心系统,它也将包含我们的策略
- venues: The simulated venues (exchanges or brokers) available in the backtest
- venues: 回测中可用的模拟场所(交易所或经纪商)
- data: The input data we would like to perform the backtest on
- data: 我们想要进行回测的输入数据
There are many more configurable features which will be described later in the docs, for now, this will get us up and running.
还有更多可配置的功能将在文档的后面部分进行描述,现在这将使我们能够启动并运行。
First, we create a venue configuration. For this example, we will create a simulated FX ECN. A venue needs a name which acts as an ID (in this case SIM), as well as some basic configuration, e.g. the account type (CASH vs MARGIN), an optional base currency, and starting balance(s).
首先,我们创建一个场所配置。在本例中,我们将创建一个模拟的外汇 ECN。场所需要一个用作 ID 的名称(在本例中为 SIM),以及一些基本配置,例如账户类型(现金与保证金)、可选的基础货币和初始余额。
note
FX trading is typically done on margin with Non-Deliverable Forward, Swap, or CFD type instruments.
外汇交易通常以保证金方式进行,使用非 deliverable forward、掉期或差价合约类型的金融工具。
venue = BacktestVenueConfig(
name="SIM",
oms_type="NETTING",
account_type="MARGIN",
base_currency="USD",
starting_balances=["1_000_000 USD"]
)
We need to know about the instruments that we would like to load data for, we can use the ParquetDataCatalog for this.
我们需要了解要加载数据的金融工具,我们可以使用 ParquetDataCatalog 来实现这一点。
instruments = catalog.instruments()
instruments
Next, we need to configure the data for the backtest. Nautilus is built to be very flexible when it comes to loading data for backtests, however this also means some configuration is required.
接下来,我们需要为回测配置数据。在加载回测数据方面,Nautilus 的构建非常灵活,但这同时也意味着需要进行一些配置。
For each tick type (and instrument), we add a BacktestDataConfig. In this instance, we are simply adding the QuoteTick(s) for our EUR/USD instrument:
对于每个 tick 类型(和金融工具),我们添加一个 BacktestDataConfig。在本例中,我们只是为我们的 EUR/USD 金融工具添加 QuoteTick:
from nautilus_trader.model.data import QuoteTick
data = BacktestDataConfig(
catalog_path=str(catalog.path),
data_cls=QuoteTick,
instrument_id=instruments[0].id,
end_time="2020-01-10",
)
Then, we need a BacktestEngineConfig which represents the configuration of our core trading system. Here we need to pass our trading strategies, we can also adjust the log level and configure many other components (however, it's also fine to use the defaults):
然后,我们需要一个 BacktestEngineConfig,它代表我们核心交易系统的配置。在这里,我们需要传递我们的交易策略,我们也可以调整日志级别并配置许多其他组件(但是,使用默认值也可以):
Strategies are added via the ImportableStrategyConfig, which enables importing strategies from arbitrary files or user packages. In this instance, our MACDStrategy is defined in the current module, which python refers to as main.
策略是通过 ImportableStrategyConfig 添加的,它允许从任意文件或用户包导入策略。在本例中,我们的 MACDStrategy 在当前模块中定义,Python 将其称为 main。
# NautilusTrader currently exceeds the rate limit for Jupyter notebook logging (stdout output),
# this is why the `log_level` is set to "ERROR". If you lower this level to see
# more logging then the notebook will hang during cell execution. A fix is currently
# being investigated which involves either raising the configured rate limits for
# Jupyter, or throttling the log flushing from Nautilus.
# https://github.com/jupyterlab/jupyterlab/issues/12845
# https://github.com/deshaw/jupyterlab-limit-output
engine = BacktestEngineConfig(
strategies=[
ImportableStrategyConfig(
strategy_path="__main__:MACDStrategy",
config_path="__main__:MACDConfig",
config={
"instrument_id": instruments[0].id,
"fast_period": 12,
"slow_period": 26,
},
)
],
logging=LoggingConfig(log_level="ERROR"),
)
We can now pass our various config pieces to the BacktestRunConfig. This object now contains the full configuration for our backtest.
现在我们可以将我们的各种配置部分传递给 BacktestRunConfig。此对象现在包含我们回测的完整配置。
config = BacktestRunConfig(
engine=engine,
venues=[venue],
data=[data],
)
The BacktestNode class will orchestrate the backtest run. The reason for this separation between configuration and execution is the BacktestNode, which enables running multiple configurations (different parameters or batches of data). We are now ready to run some backtests.
BacktestNode 类将编排回测运行。配置和执行之间之所以要进行这种分离,是因为 BacktestNode 能够运行多个配置(不同的参数或批次的数据)。我们现在准备运行一些回测。
from nautilus_trader.backtest.results import BacktestResult
node = BacktestNode(configs=[config])
# Runs one or many configs synchronously
results: list[BacktestResult] = node.run()
Now that the run is complete, we can also directly query for the BacktestEngine(s) used internally by the BacktestNode by using the run configs ID.
现在运行已完成,我们还可以使用运行配置 ID 直接查询 BacktestNode 内部使用的 BacktestEngine。
The engine(s) can provide additional reports and information.
引擎可以提供额外的报告和信息。
from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.model.identifiers import Venue
engine: BacktestEngine = node.get_engine(config.id)
engine.trader.generate_order_fills_report()
engine.trader.generate_positions_report()
engine.trader.generate_account_report(Venue("SIM"))