NautilusTrader 完整快速入门指南

NautilusTrader 是一个高性能的开源交易平台,本文将提供一个包含数据获取的完整入门指南。

前置要求

  • Python 3.11 或更高版本
  • 稳定的网络连接(用于下载数据)

安装步骤

1. 安装 NautilusTrader

bash
# 创建虚拟环境(推荐)
python -m venv nautilus_env
source nautilus_env/bin/activate  # Linux/Mac
# nautilus_env\Scripts\activate  # Windows

# 安装 NautilusTrader
pip install -U nautilus_trader

# 安装 JupyterLab(可选,用于交互式开发)
pip install -U jupyterlab

2. 获取示例数据(重要!)

官方提供了一个便捷的脚本来下载示例数据:

python
# 在 Jupyter notebook 或 Python 脚本中运行
import subprocess
import sys

# 如果在 Colab 或需要安装 curl
# !apt-get update && apt-get install curl -y

# 下载并执行数据准备脚本
subprocess.run([
    sys.executable, 
    "-c", 
    "import urllib.request; exec(urllib.request.urlopen('https://raw.githubusercontent.com/nautechsystems/nautilus_data/main/nautilus_data/hist_data_to_catalog.py').read())"
])

或者使用命令行:

bash
curl https://raw.githubusercontent.com/nautechsystems/nautilus_data/main/nautilus_data/hist_data_to_catalog.py | python -

这个脚本会:

  • 下载 EUR/USD 的历史数据
  • 将数据转换为 Parquet 格式
  • 创建数据目录结构

完整的回测示例

1. 导入必要的模块

python
from decimal import Decimal
from pathlib import Path

import pandas as pd
from nautilus_trader.backtest.node import BacktestNode
from nautilus_trader.config import BacktestRunConfig, BacktestVenueConfig, BacktestDataConfig, BacktestEngineConfig
from nautilus_trader.config import ImportableStrategyConfig
from nautilus_trader.config import LoggingConfig
from nautilus_trader.core.datetime import dt_to_unix_nanos
from nautilus_trader.model.data import QuoteTick
from nautilus_trader.model.identifiers import InstrumentId, Symbol, Venue
from nautilus_trader.model.objects import Price, Quantity
from nautilus_trader.persistence.catalog.parquet import ParquetDataCatalog
from nautilus_trader.trading.strategy import Strategy

2. 设置数据目录

python
# 设置数据目录路径
from nautilus_trader.persistence.catalog import ParquetDataCatalog

# 从环境变量或默认路径创建数据目录
catalog = ParquetDataCatalog.from_env()

# 查看可用的交易工具
instruments = catalog.instruments()
print(f"可用交易工具: {[str(i.id) for i in instruments]}")

# 查看数据时间范围
start = catalog.min_timestamp("quote_tick", instrument_id="EUR/USD.SIM")
end = catalog.max_timestamp("quote_tick", instrument_id="EUR/USD.SIM")
print(f"数据时间范围: {start} 到 {end}")

3. 创建简单的 MACD 策略

python
from nautilus_trader.indicators.macd import MovingAverageConvergenceDivergence
from nautilus_trader.trading.strategy import Strategy

class MACDStrategy(Strategy):
    def __init__(self, config: dict):
        super().__init__(config)
        self.instrument_id = InstrumentId.from_str(config["instrument_id"])
        
        # MACD 参数
        self.fast_period = config.get("fast_period", 12)
        self.slow_period = config.get("slow_period", 26)
        self.signal_period = config.get("signal_period", 9)
        
        # 交易参数
        self.trade_size = Decimal(config.get("trade_size", "1.0"))
        self.entry_threshold = config.get("entry_threshold", 0.0)
        
        # 指标
        self.macd = None
        
    def on_start(self):
        """策略启动时调用"""
        self.macd = MovingAverageConvergenceDivergence(
            fast_period=self.fast_period,
            slow_period=self.slow_period,
            signal_period=self.signal_period,
        )
        
        # 订阅报价数据
        self.subscribe_quote_ticks(self.instrument_id)
        
    def on_quote_tick(self, tick: QuoteTick):
        """接收到报价时调用"""
        # 更新 MACD
        price = float(tick.bid_price)
        self.macd.update_raw(price)
        
        if not self.macd.initialized:
            return
            
        # 获取 MACD 值
        macd_line = self.macd.line
        signal_line = self.macd.signal
        
        # 检查持仓
        position = self.cache.position(self.instrument_id)
        
        # 交易逻辑
        if position is None:
            # 无持仓时的入场逻辑
            if macd_line > self.entry_threshold and macd_line > signal_line:
                # MACD 在阈值之上且高于信号线,做多
                self.buy(size=self.trade_size)
            elif macd_line < -self.entry_threshold and macd_line < signal_line:
                # MACD 在阈值之下且低于信号线,做空
                self.sell(size=self.trade_size)
        else:
            # 有持仓时的出场逻辑
            if position.is_long and macd_line <= 0:
                # 多仓且 MACD 跌破零线,平仓
                self.close_position(position)
            elif position.is_short and macd_line >= 0:
                # 空仓且 MACD 升破零线,平仓
                self.close_position(position)
                
    def buy(self, size: Decimal):
        """发送买入订单"""
        order = self.order_factory.market(
            instrument_id=self.instrument_id,
            order_side="BUY",
            quantity=Quantity.from_str(str(size)),
        )
        self.submit_order(order)
        
    def sell(self, size: Decimal):
        """发送卖出订单"""
        order = self.order_factory.market(
            instrument_id=self.instrument_id,
            order_side="SELL",
            quantity=Quantity.from_str(str(size)),
        )
        self.submit_order(order)
        
    def close_position(self, position):
        """平仓"""
        order = self.order_factory.market(
            instrument_id=self.instrument_id,
            order_side="SELL" if position.is_long else "BUY",
            quantity=position.quantity,
        )
        self.submit_order(order)

4. 配置并运行回测

python
# 配置回测
config = BacktestRunConfig(
    engine=BacktestEngineConfig(
        strategies=[
            ImportableStrategyConfig(
                strategy_path="__main__:MACDStrategy",
                config={
                    "instrument_id": "EUR/USD.SIM",
                    "fast_period": 12,
                    "slow_period": 26,
                    "signal_period": 9,
                    "trade_size": "100000",  # 1标准手
                    "entry_threshold": 0.0001,
                },
            ),
        ],
        logging=LoggingConfig(log_level="INFO"),
    ),
    venues=[
        BacktestVenueConfig(
            name="SIM",
            oms_type="NETTING",
            account_type="MARGIN",
            base_currency="USD",
            starting_balances=["100000 USD"],
        ),
    ],
    data=[
        BacktestDataConfig(
            catalog_path=str(catalog.path),
            data_cls="nautilus_trader.model.data:QuoteTick",
            instrument_id="EUR/USD.SIM",
            start_time="2020-01-01T00:00:00Z",
            end_time="2020-01-31T23:59:59Z",
        ),
    ],
)

# 创建并运行回测节点
node = BacktestNode(configs=[config])
results = node.run()

5. 分析回测结果

python
# 获取引擎实例
engine = node.engine

# 生成报告
print("=== 订单成交报告 ===")
engine.trader.generate_order_fills_report()

print("\n=== 持仓报告 ===")
engine.trader.generate_positions_report()

print("\n=== 账户报告 ===")
engine.trader.generate_account_report(Venue("SIM"))

# 获取账户统计
account = engine.trader.accounts()[0]
print(f"\n初始余额: {account.starting_balances()}")
print(f"最终余额: {account.balances()}")

# 计算收益
starting_balance = 100000
final_balance = float(str(account.balance(USD).split()[0]))
pnl = final_balance - starting_balance
return_pct = (pnl / starting_balance) * 100

print(f"盈亏: ${pnl:.2f}")
print(f"收益率: {return_pct:.2f}%")

数据获取的其他方式

1. 使用自己的数据

如果你有自己的数据,可以将其转换为 NautilusTrader 格式:

python
# 从 CSV 导入数据
from nautilus_trader.persistence.loaders import CSVTickDataLoader

loader = CSVTickDataLoader(
    instrument_id="EUR/USD.SIM",
    price_precision=5,
    size_precision=0,
)

# 加载数据
ticks = loader.load("path/to/your/data.csv")

# 保存到 catalog
catalog.write_data(ticks)

2. 从交易所获取实时数据

python
# 以 Binance 为例
from nautilus_trader.adapters.binance.config import BinanceDataClientConfig
from nautilus_trader.adapters.binance.factories import BinanceLiveDataClientFactory

# 配置数据客户端
config = BinanceDataClientConfig(
    api_key="your_api_key",  # 可选
    api_secret="your_api_secret",  # 可选
    testnet=False,
)

# 创建数据客户端
data_client = BinanceLiveDataClientFactory.create(
    loop=asyncio.get_event_loop(),
    name="BINANCE",
    config=config,
)

注意事项

  1. 数据质量:回测结果的准确性依赖于数据质量
  2. 滑点和手续费:记得在回测配置中设置现实的滑点和手续费
  3. 过度拟合:避免过度优化策略参数
  4. 样本外测试:使用未参与优化的数据进行验证

下一步

  1. 尝试修改 MACD 策略参数
  2. 实现其他技术指标策略
  3. 添加风险管理(止损、止盈)
  4. 尝试多品种交易
  5. 连接实盘交易

资源链接

现在你已经掌握了 NautilusTrader 的完整入门流程,包括最重要的数据获取步骤!