Skill: tujinsama/nautilus-trader-pro (version 1.0.0)

--- name: nautilus-trader version: 1.0.0 description: How to use the NautilusTrader algorithmic trading platform for data conversion, strategy development, backtesting, paper trading (sandbox), and live trading. Use this skill whenever the user wants to build or run a quantitative trading strategy with nautilus_trader, convert market data for backtesting, set up a backtest engine, run a sandbox/paper-trading session, connect to a live exchange or broker, or generate performance reports and tearsheets. Also use it when the user mentions NautilusTrader, trading strategies, backtesting frameworks, or connecting to exchanges like Binance, Bybit, Interactive Brokers, etc. --- # NautilusTrader Skill This skill teaches you how to use the **NautilusTrader** open-source algorithmic trading platform. NautilusTrader is a Rust-native, high-performance trading system with Python bindings (via PyO3). It works as both a backtesting engine and a live trading system — the same strategy code runs in both environments with zero changes. ## Prerequisites — check before ANY task Before doing any work, you MUST verify the environment is ready. Follow these steps in order: ### Step 1: Check if nautilus_trader exists in the workspace Look for a `nautilus_trader/` directory in the user's workspace that contains `pyproject.toml` and a `nautilus_trader/` Python package subdirectory. Run: ```bash ls pyproject.toml nautilus_trader/__init__.py 2>/dev/null ``` If both files exist, the project is present — skip to Step 3. ### Step 2: Clone the repository (only if Step 1 failed) If the project is not found in the workspace, clone it: ```bash git clone https://github.com/nautechsystems/nautilus_trader.git cd nautilus_trader ``` ### Step 3: Check if the Python environment is set up Check if a virtual environment exists and nautilus_trader is installed: ```bash python -c "import nautilus_trader; print(nautilus_trader.__version__)" 2>/dev/null ``` If this succeeds, the environment is ready — proceed to the user's task. ### Step 4: Set up the environment (only if Step 3 failed) NautilusTrader requires Python 3.12+ and the Rust toolchain. Install in this order: #### 4a. Install system prerequisites ```bash # macOS (Rust + clang are typically available, just ensure Rust is installed) curl https://sh.rustup.rs -sSf | sh -s -- -y source $HOME/.cargo/env # Linux (Ubuntu) curl https://sh.rustup.rs -sSf | sh -s -- -y source $HOME/.cargo/env sudo apt-get install -y clang ``` Verify: `rustc --version` and `clang --version` #### 4b. Install uv (if not already available) ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` #### 4c. Install nautilus_trader There are two installation approaches: **Option A: Install from PyPI (recommended for users who just want to USE the framework)** ```bash uv venv --python 3.12 source .venv/bin/activate # Linux/macOS uv pip install nautilus_trader uv pip install "nautilus_trader[visualization]" # Optional: for tearsheet reports ``` **Option B: Install from source (recommended when working inside the cloned repository)** ```bash cd nautilus_trader uv sync --all-extras ``` This creates a virtual environment, installs all dependencies, and builds the Cython/Rust extensions. It takes several minutes on first build. For faster development iteration, use: ```bash make build-debug # Debug build (faster compilation, slower runtime) make install # Release build (slower compilation, faster runtime) ``` #### 4d. Set environment variables (source builds only, Linux/macOS) ```bash export PYO3_PYTHON=$(pwd)/.venv/bin/python # Linux only: export LD_LIBRARY_PATH="$(python -c 'import sys; print(sys.base_prefix)')/lib:$LD_LIBRARY_PATH" ``` #### 4e. Verify installation ```bash python -c "import nautilus_trader; print(f'NautilusTrader {nautilus_trader.__version__} installed successfully')" ``` ### Optional dependencies | Extra | Install command | Purpose | |---|---|---| | Visualization | `uv pip install "nautilus_trader[visualization]"` | Plotly tearsheets and charts | | Interactive Brokers | `uv pip install "nautilus_trader[ib]"` | IB adapter dependencies | | Docker (for IB Gateway) | `uv pip install "nautilus_trader[docker]"` | Dockerized IB Gateway | | Betfair | `uv pip install "nautilus_trader[betfair]"` | Betfair adapter | | Polymarket | `uv pip install "nautilus_trader[polymarket]"` | Polymarket adapter | ## When to read which reference Based on what the user needs, read the appropriate reference file from the `references/` directory next to this SKILL.md. Each reference file is self-contained with code templates and explanations. | User's goal | Reference file to read | |---|---| | Convert CSV/external data into Nautilus format | `references/data_conversion.md` | | Write a trading strategy | `references/strategy_development.md` | | Run a backtest and generate reports | `references/backtesting.md` | | Run paper/simulated trading with real market data | `references/paper_trading.md` | | Connect to a live exchange/broker and trade | `references/live_trading.md` | Read **only** the reference file(s) relevant to the current task. If a task spans multiple areas (e.g., "convert data, write a strategy, and backtest it"), read them in the order listed above. ## Project layout ``` nautilus_trader/ # Python package (v1, production) ├── adapters/ # Venue adapters (Binance, Bybit, IB, etc.) ├── backtest/ # BacktestEngine, BacktestNode ├── examples/ # Example strategies and scripts │ ├── strategies/ # EMACross, MarketMaker, etc. │ └── algorithms/ # TWAP execution algorithm ├── indicators/ # Technical indicators (EMA, SMA, RSI, ATR, etc.) ├── live/ # TradingNode for live/sandbox trading ├── model/ # Domain types: instruments, orders, events, data ├── persistence/ # Data catalog (Parquet), wranglers ├── analysis/ # Reports, tearsheets, visualization └── trading/ # Strategy base class examples/ # Runnable example scripts ├── backtest/ # Backtest examples (FX, crypto, equities) ├── data_conversion/ # Data conversion examples ├── live/ # Live trading examples per exchange └── sandbox/ # Sandbox (paper trading) examples docs/concepts/ # Concept guides (data, strategies, backtesting, etc.) docs/integrations/ # Per-exchange integration guides ``` ## Core concepts quick reference ### Data types NautilusTrader uses these built-in market data types (in descending order of granularity): | Type | Description | |---|---| | `OrderBookDelta` | Individual order book updates (L1/L2/L3) | | `OrderBookDepth10` | Aggregated order book snapshot (up to 10 levels per side) | | `QuoteTick` | Best bid/ask prices with sizes (top-of-book) | | `TradeTick` | A single executed trade | | `Bar` | OHLCV candle aggregated by time, tick, volume, etc. | ### Instrument types | Type | Use case | |---|---| | `CurrencyPair` | Forex, crypto spot (e.g., EUR/USD, BTC/USDT) | | `CryptoPerpetual` | Crypto perpetual futures (e.g., BTCUSDT-PERP) | | `Equity` | Stocks (e.g., AAPL, TSLA) | | `FuturesContract` | Traditional futures (e.g., ES, IF) | | `OptionContract` | Options | | `Cfd` | CFDs | ### BarType string syntax Standard format: `{instrument_id}-{step}-{aggregation}-{price_type}-{source}` Examples: ``` EUR/USD.SIM-1-MINUTE-BID-INTERNAL # 1-min bars from bid quotes, aggregated internally BTCUSDT-PERP.BINANCE-5-MINUTE-LAST-EXTERNAL # 5-min bars from exchange AAPL.XNAS-1-HOUR-LAST-INTERNAL # 1-hour bars aggregated internally from trades ``` - Price types: `BID`, `ASK`, `MID` (from QuoteTick), `LAST` (from TradeTick) - Sources: `INTERNAL` (Nautilus aggregates), `EXTERNAL` (exchange/provider provides) ### Key identifiers - `InstrumentId`: e.g., `"EUR/USD.SIM"`, `"ETHUSDT-PERP.BINANCE"`, `"AAPL.XNAS"` - `Venue`: e.g., `"SIM"`, `"BINANCE"`, `"INTERACTIVE_BROKERS"` - `TraderId`: e.g., `"TRADER-001"` - `StrategyId`: auto-generated as `{ClassName}-{order_id_tag}` ### Environment contexts NautilusTrader has three operating modes: | Mode | Data source | Execution | Use case | |---|---|---|---| | **Backtest** | Historical data files | Simulated exchange | Strategy research | | **Sandbox** | Live market feeds | Simulated locally | Paper trading | | **Live** | Live market feeds | Real exchange | Production trading | The **same strategy code** runs in all three modes. ### Installation ```bash pip install nautilus_trader # With visualization support (Plotly tearsheets) pip install "nautilus_trader[visualization]" ``` For development in this repository: ```bash make install # Release build make build-debug # Debug build (faster compilation) ``` ## Important conventions - **Timestamps** are UNIX nanoseconds (`ts_event`, `ts_init`) - **Prices** use fixed-point arithmetic (`Price`, `Quantity` types) — never raw floats - **All imports** come from the `nautilus_trader` package - **Strategy code is identical** across backtest, sandbox, and live — only the engine config changes - **Do not block the event loop** — strategy callbacks must return quickly - **One TradingNode per process** — use separate processes for parallel live nodes ## Key source files for reference When writing code, these files in the repository are the most useful references: | Purpose | Path | |---|---| | EMA Cross strategy (simplest example) | `nautilus_trader/examples/strategies/ema_cross.py` | | Quickstart backtest | `docs/getting_started/quickstart.py` | | Loading external CSV data | `docs/how_to/loading_external_data.py` | | Backtest with ticks | `examples/backtest/fx_ema_cross_audusd_ticks.py` | | Bar-based backtest | `examples/backtest/fx_ema_cross_bracket_gbpusd_bars_external.py` | | Sandbox example | `examples/sandbox/binance_futures_testnet_sandbox.py` | | Live trading example | `examples/live/binance/binance_spot_ema_cross_bracket_algo.py` | | Data conversion (CN futures) | `examples/data_conversion/if_data_converter.py` | | Strategy concepts doc | `docs/concepts/strategies.md` | | Data concepts doc | `docs/concepts/data.md` | | Backtesting concepts doc | `docs/concepts/backtesting.md` | | Live trading concepts doc | `docs/concepts/live.md` | | Reports & analysis doc | `docs/concepts/reports.md` | | Visualization doc | `docs/concepts/visualization.md` | | Integration guides (per exchange) | `docs/integrations/*.md` | ## Typical end-to-end workflow 1. **Prepare data** → Read `references/data_conversion.md` - Load raw CSV/API data → DataFrame → DataWrangler → Nautilus objects - Optionally persist to ParquetDataCatalog 2. **Write strategy** → Read `references/strategy_development.md` - Create `StrategyConfig` + `Strategy` subclass - Implement `on_start`, `on_bar`/`on_quote_tick`, `on_stop` 3. **Backtest** → Read `references/backtesting.md` - Configure `BacktestEngine` or `BacktestNode` - Run and generate reports/tearsheets 4. **Paper trade** → Read `references/paper_trading.md` - Configure `TradingNode` with Sandbox execution client - Use live data feeds with simulated order execution 5. **Go live** → Read `references/live_trading.md` - Switch execution client from Sandbox to real exchange adapter - Configure reconciliation, risk management

path: references/backtesting.md
content: # Backtesting Guide

This guide explains how to run backtests and generate performance reports with NautilusTrader.

## Two API levels

| API | Entry point | Best for |
|---|---|---|
| **Low-level** | `BacktestEngine` | Data fits in RAM, want fine control |
| **High-level** | `BacktestNode` + config objects | Streaming large datasets, multiple runs |

## Low-level API: BacktestEngine

### Complete example (bar data)

```python
from decimal import Decimal

import pandas as pd

from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.config import BacktestEngineConfig, LoggingConfig
from nautilus_trader.model.currencies import USD
from nautilus_trader.model.data import BarType
from nautilus_trader.model.enums import AccountType, OmsType
from nautilus_trader.model.identifiers import Venue
from nautilus_trader.model.objects import Money
from nautilus_trader.persistence.wranglers import BarDataWrangler
from nautilus_trader.test_kit.providers import TestInstrumentProvider

# -- Your strategy import --
# from my_strategy import MyStrategy, MyStrategyConfig

# 1. Configure engine
engine = BacktestEngine(
    config=BacktestEngineConfig(
        logging=LoggingConfig(log_level="INFO"),
    ),
)

# 2. Add venue
SIM = Venue("SIM")
engine.add_venue(
    venue=SIM,
    oms_type=OmsType.NETTING,       # NETTING (one position per instrument) or HEDGING
    account_type=AccountType.MARGIN, # MARGIN or CASH
    base_currency=USD,
    starting_balances=[Money(1_000_000, USD)],
    default_leverage=Decimal(1),
)

# 3. Add instrument
instrument = TestInstrumentProvider.default_fx_ccy("EUR/USD", SIM)
engine.add_instrument(instrument)

# 4. Add data (bars, ticks, or order book data)
# Assuming you have prepared `bars` via a DataWrangler (see data_conversion.md)
engine.add_data(bars)

# 5. Add strategy
strategy = MyStrategy(
    MyStrategyConfig(
        instrument_id=instrument.id,
        bar_type=BarType.from_str("EUR/USD.SIM-1-MINUTE-LAST-EXTERNAL"),
        trade_size=Decimal(100_000),
    ),
)
engine.add_strategy(strategy)

# 6. Run
engine.run()

# 7. Generate reports (see Reports section below)
print(engine.trader.generate_account_report(SIM))
print(engine.trader.generate_positions_report())
print(engine.trader.generate_order_fills_report())

# 8. Clean up
engine.reset()   # Reset for another run (data + instruments persist)
engine.dispose()  # Final cleanup
```

### Complete example (tick data)

```python
from nautilus_trader.persistence.wranglers import QuoteTickDataWrangler
from nautilus_trader.test_kit.providers import TestDataProvider, TestInstrumentProvider

# Load test tick data
provider = TestDataProvider()
instrument = TestInstrumentProvider.default_fx_ccy("AUD/USD", SIM)

wrangler = QuoteTickDataWrangler(instrument=instrument)
ticks = wrangler.process(provider.read_csv_ticks("truefx/audusd-ticks.csv"))

engine.add_instrument(instrument)
engine.add_data(ticks)

# Bars will be aggregated INTERNALLY from ticks
strategy_config = MyStrategyConfig(
    instrument_id=instrument.id,
    bar_type=BarType.from_str("AUD/USD.SIM-100-TICK-MID-INTERNAL"),
    trade_size=Decimal(1_000_000),
)
```

### Performance optimization for large datasets

When loading data for multiple instruments:

```python
# BAD: sorts on each call (gets slower as data grows)
engine.add_data(instrument1_bars)
engine.add_data(instrument2_bars)

# GOOD: defer sorting until the end
engine.add_data(instrument1_bars, sort=False)
engine.add_data(instrument2_bars, sort=False)
engine.sort_data()  # Sort once — much faster
```

## High-level API: BacktestNode

Uses `BacktestNode` with configuration objects and data from a `ParquetDataCatalog`.

```python
from decimal import Decimal
from pathlib import Path

from nautilus_trader.backtest.node import (
    BacktestDataConfig,
    BacktestEngineConfig,
    BacktestNode,
    BacktestRunConfig,
    BacktestVenueConfig,
)
from nautilus_trader.config import ImportableStrategyConfig
from nautilus_trader.model import BarType, QuoteTick
from nautilus_trader.persistence.catalog import ParquetDataCatalog

# 1. Set up catalog (must have data written already — see data_conversion.md)
catalog = ParquetDataCatalog(Path("./catalog"))
instrument = catalog.instruments()[0]

# 2. Configure venues
venue_configs = [
    BacktestVenueConfig(
        name="SIM",
        oms_type="NETTING",
        account_type="MARGIN",
        base_currency="USD",
        starting_balances=["1_000_000 USD"],
    ),
]

# 3. Configure data sources
data_configs = [
    BacktestDataConfig(
        catalog_path=str(catalog.path),
        data_cls=QuoteTick,
        instrument_id=instrument.id,
    ),
]

# 4. Configure strategies (using importable config for serialization)
strategies = [
    ImportableStrategyConfig(
        strategy_path="nautilus_trader.examples.strategies.ema_cross:EMACross",
        config_path="nautilus_trader.examples.strategies.ema_cross:EMACrossConfig",
        config={
            "instrument_id": instrument.id,
            "bar_type": BarType.from_str(f"{instrument.id}-15-MINUTE-BID-INTERNAL"),
            "fast_ema_period": 10,
            "slow_ema_period": 20,
            "trade_size": Decimal(1_000_000),
        },
    ),
]

# 5. Create run config
config = BacktestRunConfig(
    engine=BacktestEngineConfig(strategies=strategies),
    data=data_configs,
    venues=venue_configs,
)

# 6. Run
node = BacktestNode(configs=[config])
results = node.run()

# results is a list of BacktestResult objects
print(results[0])
```

## Venue configuration

### OMS types

| Type | Behavior | Use for |
|---|---|---|
| `NETTING` | One position per instrument, fills net against each other | Forex, crypto futures |
| `HEDGING` | Separate position per order, allows simultaneous long/short | Some brokers, CFDs |

### Account types

| Type | Description |
|---|---|
| `CASH` | Cannot go negative, no leverage |
| `MARGIN` | Supports leverage and margin requirements |

### Book types (controls execution simulation fidelity)

| Type | Description | Data required |
|---|---|---|
| `L1_MBP` (default) | Top-of-book only | Quotes, trades, or bars |
| `L2_MBP` | Full price-level depth | OrderBookDelta data |
| `L3_MBO` | Individual order tracking | OrderBookDelta data |

### Fill models

Customize order fill simulation:

```python
from nautilus_trader.backtest.models import FillModel

fill_model = FillModel(
    prob_fill_on_limit=0.2,   # 20% chance resting limit fills
    prob_slippage=0.5,        # 50% chance of 1-tick slippage
    random_seed=42,           # For reproducibility
)

engine.add_venue(
    venue=SIM,
    oms_type=OmsType.NETTING,
    account_type=AccountType.MARGIN,
    starting_balances=[Money(1_000_000, USD)],
    fill_model=fill_model,
)
```

## Reports and analysis

### Built-in reports

After `engine.run()`, generate pandas DataFrame reports:

```python
# Account balance over time
account_report = engine.trader.generate_account_report(SIM)

# All orders with status, fill info
orders_report = engine.trader.generate_orders_report()

# Filled orders only
order_fills = engine.trader.generate_order_fills_report()

# Positions with PnL
positions_report = engine.trader.generate_positions_report()

# Individual fill events
fills_report = engine.trader.generate_fills_report()
```

### Performance statistics

```python
portfolio = engine.portfolio

# PnL stats (per currency)
stats_pnls = portfolio.analyzer.get_performance_stats_pnls()

# Returns stats (Sharpe, Sortino, max drawdown, etc.)
stats_returns = portfolio.analyzer.get_performance_stats_returns()

# General stats (win rate, profit factor, avg trade, etc.)
stats_general = portfolio.analyzer.get_performance_stats_general()
```

### Tearsheet visualization

Generate an interactive HTML performance report:

```python
from nautilus_trader.analysis import create_tearsheet

# After engine.run()
create_tearsheet(
    engine=engine,
    output_path="tearsheet.html",
)
```

This creates an HTML file with:
- Equity curve
- Drawdown chart
- Monthly returns heatmap
- Returns distribution
- Rolling Sharpe ratio
- Performance statistics tables

Requires: `pip install "nautilus_trader[visualization]"`

### Custom tearsheet configuration

```python
from nautilus_trader.analysis import (
    TearsheetConfig,
    TearsheetEquityChart,
    TearsheetDrawdownChart,
    TearsheetStatsTableChart,
    TearsheetRunInfoChart,
)

config = TearsheetConfig(
    charts=[
        TearsheetRunInfoChart(),
        TearsheetStatsTableChart(),
        TearsheetEquityChart(),
        TearsheetDrawdownChart(),
    ],
    theme="nautilus_dark",  # or "plotly_white", "nautilus", "plotly_dark"
    height=2000,
)

create_tearsheet(engine=engine, output_path="custom.html", config=config)
```

### Bars with fills chart (standalone)

```python
from nautilus_trader.analysis import create_bars_with_fills

fig = create_bars_with_fills(
    engine=engine,
    bar_type=BarType.from_str("EUR/USD.SIM-1-MINUTE-LAST-EXTERNAL"),
    title="EUR/USD - Entry/Exit Analysis",
)
fig.write_html("bars_with_fills.html")
```

## Multiple backtest runs

### With BacktestEngine (parameter optimization)

```python
# Data and instruments persist across resets
engine.add_venue(...)
engine.add_instrument(instrument)
engine.add_data(data)

# Run with parameter set 1
engine.add_strategy(strategy_v1)
engine.run()
results_v1 = engine.trader.generate_positions_report()

# Reset (clears strategy, orders, positions — keeps data)
engine.reset()

# Run with parameter set 2
engine.add_strategy(strategy_v2)
engine.run()
results_v2 = engine.trader.generate_positions_report()
```

### With BacktestNode (multiple configs)

```python
configs = [
    BacktestRunConfig(engine=engine_config1, data=data_configs, venues=venue_configs),
    BacktestRunConfig(engine=engine_config2, data=data_configs, venues=venue_configs),
]
node = BacktestNode(configs=configs)
results = node.run()  # Returns list of results
```

## Key tips

- **Register indicators before requesting data** — otherwise they miss historical bars
- **Use `EXTERNAL` for pre-aggregated bars** (e.g., from CSV), `INTERNAL` when Nautilus
  should aggregate from ticks
- **Start with bar data** for faster iteration, then move to tick/orderbook data for
  higher fidelity when the strategy looks promising
- **Refer to `examples/backtest/`** in the repository for many working examples
  covering FX, crypto, equities, and order book strategies


path: references/data_conversion.md
content: # Data Conversion Guide

This guide explains how to convert external data sources into formats that NautilusTrader can use
for backtesting and live trading.

## Conversion pipeline overview

```
Raw data (CSV, API, database)
    ↓
DataLoader → pd.DataFrame (with specific column schema)
    ↓
DataWrangler → list[NautilusObject]  (QuoteTick, TradeTick, Bar, etc.)
    ↓
Option A: Add directly to BacktestEngine  →  engine.add_data(data)
Option B: Write to ParquetDataCatalog     →  catalog.write_data(data)
```

## Available data wranglers

Each wrangler expects a specific DataFrame schema. All wranglers are in
`nautilus_trader.persistence.wranglers`.

### QuoteTickDataWrangler

Converts bid/ask quote data. DataFrame must have a **DatetimeIndex** (UTC) and these columns:

| Column | Type | Description |
|---|---|---|
| `bid_price` | float | Best bid price |
| `ask_price` | float | Best ask price |
| `bid_size` | float | Best bid size (optional, defaults to 0) |
| `ask_size` | float | Best ask size (optional, defaults to 0) |

```python
from nautilus_trader.persistence.wranglers import QuoteTickDataWrangler

wrangler = QuoteTickDataWrangler(instrument=instrument)
ticks = wrangler.process(df)  # Returns list[QuoteTick]
```

### TradeTickDataWrangler

Converts trade/execution data. DataFrame must have a **DatetimeIndex** (UTC) and these columns:

| Column | Type | Description |
|---|---|---|
| `price` | float | Trade price |
| `quantity` | float | Trade size |
| `aggressor_side` | str | "BUY" or "SELL" (trade initiator) |
| `trade_id` | str | Unique trade identifier |

```python
from nautilus_trader.persistence.wranglers import TradeTickDataWrangler

wrangler = TradeTickDataWrangler(instrument=instrument)
ticks = wrangler.process(df)  # Returns list[TradeTick]
```

### BarDataWrangler

Converts OHLCV bar/candle data. DataFrame must have a **DatetimeIndex** (UTC) and these columns:

| Column | Type | Description |
|---|---|---|
| `open` | float | Opening price |
| `high` | float | Highest price |
| `low` | float | Lowest price |
| `close` | float | Closing price |
| `volume` | float | Traded volume (optional, defaults to 0) |

```python
from nautilus_trader.persistence.wranglers import BarDataWrangler
from nautilus_trader.model.data import BarType

bar_type = BarType.from_str("EUR/USD.SIM-1-MINUTE-LAST-EXTERNAL")
wrangler = BarDataWrangler(bar_type=bar_type, instrument=instrument)
bars = wrangler.process(df)  # Returns list[Bar]
```

### OrderBookDeltaDataWrangler

Converts order book update data. DataFrame must have specific columns — see
`docs/concepts/data.md` in the repository for the full schema.

```python
from nautilus_trader.persistence.wranglers import OrderBookDeltaDataWrangler

wrangler = OrderBookDeltaDataWrangler(instrument=instrument)
deltas = wrangler.process(df)  # Returns list[OrderBookDelta]
```

## Creating instruments

Before wrangling data, you need an instrument definition. You can create instruments in two ways:

### Using TestInstrumentProvider (for common instruments)

```python
from nautilus_trader.test_kit.providers import TestInstrumentProvider
from nautilus_trader.model.identifiers import Venue

SIM = Venue("SIM")

# Forex
EURUSD = TestInstrumentProvider.default_fx_ccy("EUR/USD", SIM)
AUDUSD = TestInstrumentProvider.default_fx_ccy("AUD/USD", SIM)

# Crypto
BTCUSDT = TestInstrumentProvider.btcusdt_binance()
ETHUSDT = TestInstrumentProvider.ethusdt_binance()

# Equity
AAPL = TestInstrumentProvider.aapl_equity()
```

### Creating instruments manually

For instruments not in the test provider, create them manually:

```python
from nautilus_trader.model.instruments import CurrencyPair, Equity, CryptoPerpetual
from nautilus_trader.model.identifiers import InstrumentId, Symbol, Venue
from nautilus_trader.model.objects import Price, Quantity, Money, Currency
from nautilus_trader.model.currencies import USD, CNY

# Example: Custom equity
instrument = Equity(
    instrument_id=InstrumentId(Symbol("CUSTOM"), Venue("SIM")),
    raw_symbol=Symbol("CUSTOM"),
    currency=USD,
    price_precision=2,
    price_increment=Price.from_str("0.01"),
    lot_size=Quantity.from_int(1),
    ts_event=0,
    ts_init=0,
)
```

For exchange-specific instruments (crypto, futures), see `docs/concepts/instruments.md`.

## Complete example: CSV bar data to backtest

```python
import pandas as pd
from pathlib import Path
from nautilus_trader.model.data import BarType
from nautilus_trader.persistence.wranglers import BarDataWrangler
from nautilus_trader.test_kit.providers import TestInstrumentProvider
from nautilus_trader.model.identifiers import Venue

# 1. Load raw CSV
df = pd.read_csv("my_data.csv")

# 2. Ensure proper datetime index (UTC)
df["timestamp"] = pd.to_datetime(df["timestamp"], utc=True)
df = df.set_index("timestamp")

# 3. Rename columns to match schema
df = df.rename(columns={
    "Open": "open",
    "High": "high",
    "Low": "low",
    "Close": "close",
    "Volume": "volume",
})

# 4. Create instrument
SIM = Venue("SIM")
instrument = TestInstrumentProvider.default_fx_ccy("EUR/USD", SIM)

# 5. Create bar type and wrangle
bar_type = BarType.from_str("EUR/USD.SIM-1-MINUTE-LAST-EXTERNAL")
wrangler = BarDataWrangler(bar_type=bar_type, instrument=instrument)
bars = wrangler.process(df)

# 6. Now use bars:
#    - Add to BacktestEngine: engine.add_data(bars)
#    - Or write to catalog: catalog.write_data(bars)
```

## Complete example: CSV tick data to backtest

```python
import pandas as pd
from nautilus_trader.persistence.wranglers import QuoteTickDataWrangler
from nautilus_trader.test_kit.providers import TestInstrumentProvider
from nautilus_trader.model.identifiers import Venue

# 1. Load and prepare DataFrame
df = pd.read_csv("tick_data.csv")
df["timestamp"] = pd.to_datetime(df["timestamp"], utc=True)
df = df.set_index("timestamp")

# Ensure columns: bid_price, ask_price, bid_size, ask_size
df = df.rename(columns={
    "Bid": "bid_price",
    "Ask": "ask_price",
    "BidSize": "bid_size",
    "AskSize": "ask_size",
})

# 2. Create instrument and wrangle
SIM = Venue("SIM")
instrument = TestInstrumentProvider.default_fx_ccy("EUR/USD", SIM)
wrangler = QuoteTickDataWrangler(instrument=instrument)
ticks = wrangler.process(df)

# 3. Use: engine.add_data(ticks)
```

## Using the ParquetDataCatalog

The data catalog persists Nautilus data in Parquet format for efficient reuse:

```python
from pathlib import Path
from nautilus_trader.persistence.catalog import ParquetDataCatalog
from nautilus_trader.model import QuoteTick

# Write data to catalog
catalog = ParquetDataCatalog(Path("./catalog"))
catalog.write_data([instrument])  # Write instrument first
catalog.write_data(ticks)          # Then write data

# Read data back
instruments = catalog.instruments()
ticks = catalog.quote_ticks(instrument_ids=["EUR/USD.SIM"])
bars = catalog.bars(bar_types=["EUR/USD.SIM-1-MINUTE-LAST-EXTERNAL"])
```

The catalog is required when using the high-level `BacktestNode` API (see backtesting guide).

## Crypto exchange data (Binance example)

Binance provides specific loaders for its data formats:

```python
from nautilus_trader.adapters.binance.loaders import BinanceOrderBookDeltaDataLoader
from nautilus_trader.persistence.wranglers import OrderBookDeltaDataWrangler
from nautilus_trader.test_kit.providers import TestInstrumentProvider

# Load raw Binance CSV
df = BinanceOrderBookDeltaDataLoader.load("btcusdt-depth-snap.csv")

# Wrangle
instrument = TestInstrumentProvider.btcusdt_binance()
wrangler = OrderBookDeltaDataWrangler(instrument)
deltas = wrangler.process(df)
```

## Traditional finance data (Databento example)

For US equities/futures data via Databento:

```python
# See docs/how_to/data_catalog_databento.py for the complete workflow
# Key steps:
# 1. Download DBN data via Databento API
# 2. Use DatabentoDataLoader to parse into Nautilus objects
# 3. Write to ParquetDataCatalog
```

See `docs/how_to/data_catalog_databento.py` and `docs/integrations/databento.md` for details.

## Key tips

- **DateTime index must be UTC** — always set `utc=True` when parsing timestamps
- **Column names are case-sensitive** — use lowercase: `open`, `high`, `low`, `close`, `volume`
- **For EXTERNAL bars** — use the `EXTERNAL` aggregation source, meaning the data provider
  already aggregated the bars (most common for downloaded historical data)
- **For INTERNAL bars** — Nautilus will aggregate bars from ticks at runtime
- **Instrument precision matters** — the instrument defines how many decimal places prices
  and quantities can have. Mismatched precision will cause errors
- **Refer to `examples/data_conversion/`** in the repository for real-world conversion examples
  including Chinese futures (IF) data from CSV and ZIP files


path: references/live_trading.md
content: # Live Trading Guide

This guide explains how to connect NautilusTrader to real exchanges and brokers for live trading.

## IMPORTANT: Risk warning

**Live trading involves real financial risk.** Before deploying:

1. **Thoroughly backtest** your strategy with realistic fill models
2. **Paper trade** in Sandbox mode with real market data
3. **Start with small position sizes** when going live
4. **Never run live trading in Jupyter notebooks** — use standalone Python scripts
5. **One TradingNode per process** — do not run multiple nodes in the same process

## Supported exchanges and brokers

### Crypto exchanges (CEX)

| Exchange | Venue ID | Status | Required credentials | Extra features |
|---|---|---|---|---|
| **Binance** | `BINANCE` | stable | `BINANCE_API_KEY`, `BINANCE_API_SECRET` | Spot, USDT Futures, Coin Futures; testnet available |
| **Bybit** | `BYBIT` | stable | `BYBIT_API_KEY`, `BYBIT_API_SECRET` | Spot, Linear, Inverse; testnet available |
| **OKX** | `OKX` | stable | `OKX_API_KEY`, `OKX_API_SECRET`, `OKX_PASSPHRASE` | Spot, Futures, Options; demo mode available |
| **Kraken** | `KRAKEN` | stable | `KRAKEN_API_KEY`, `KRAKEN_API_SECRET` | Spot, Futures |
| **BitMEX** | `BITMEX` | stable | `BITMEX_API_KEY`, `BITMEX_API_SECRET` | Derivatives; testnet available |
| **Deribit** | `DERIBIT` | stable | `DERIBIT_CLIENT_ID`, `DERIBIT_CLIENT_SECRET` | Options, Futures; testnet available |

### Crypto exchanges (DEX)

| Exchange | Venue ID | Status | Required credentials |
|---|---|---|---|
| **dYdX** | `DYDX` | stable | Mnemonic or private key |
| **Hyperliquid** | `HYPERLIQUID` | stable | Private key; testnet available |
| **Polymarket** | `POLYMARKET` | stable | Private key |

### Traditional finance

| Broker/Provider | Venue ID | Status | Required setup |
|---|---|---|---|
| **Interactive Brokers** | `INTERACTIVE_BROKERS` | stable | TWS or IB Gateway running locally, account credentials |
| **AX Exchange** | `AX` | beta | API Key + Secret |

### Data providers (no execution)

| Provider | Venue ID | Status | Usage |
|---|---|---|---|
| **Databento** | `DATABENTO` | stable | US equities, futures, options data (CME, XNAS, etc.) |
| **Tardis** | `TARDIS` | stable | Historical and live crypto data |

## Complete live trading example (Binance Spot)

```python
from decimal import Decimal

from nautilus_trader.adapters.binance import BINANCE
from nautilus_trader.adapters.binance import BinanceAccountType
from nautilus_trader.adapters.binance import BinanceDataClientConfig
from nautilus_trader.adapters.binance import BinanceExecClientConfig
from nautilus_trader.adapters.binance import BinanceLiveDataClientFactory
from nautilus_trader.adapters.binance import BinanceLiveExecClientFactory
from nautilus_trader.config import (
    InstrumentProviderConfig,
    LiveExecEngineConfig,
    LoggingConfig,
    TradingNodeConfig,
)
from nautilus_trader.live.node import TradingNode
from nautilus_trader.model.data import BarType
from nautilus_trader.model.identifiers import InstrumentId, TraderId

# -- Your strategy import --
# from my_strategy import MyStrategy, MyStrategyConfig


# 1. Configure the TradingNode
config_node = TradingNodeConfig(
    trader_id=TraderId("LIVE-001"),
    logging=LoggingConfig(
        log_level="INFO",
        log_level_file="DEBUG",   # Detailed logs to file
    ),
    exec_engine=LiveExecEngineConfig(
        reconciliation=True,                # Always enable for live
        reconciliation_lookback_mins=1440,  # 24 hours
    ),
    data_clients={
        BINANCE: BinanceDataClientConfig(
            api_key=None,       # Uses BINANCE_API_KEY env var
            api_secret=None,    # Uses BINANCE_API_SECRET env var
            account_type=BinanceAccountType.SPOT,
            testnet=False,      # PRODUCTION
            instrument_provider=InstrumentProviderConfig(load_all=True),
        ),
    },
    exec_clients={
        BINANCE: BinanceExecClientConfig(
            api_key=None,       # Uses BINANCE_API_KEY env var
            api_secret=None,    # Uses BINANCE_API_SECRET env var
            account_type=BinanceAccountType.SPOT,
            testnet=False,      # PRODUCTION
            instrument_provider=InstrumentProviderConfig(load_all=True),
            max_retries=3,
        ),
    },
    timeout_connection=30.0,
    timeout_reconciliation=10.0,
    timeout_portfolio=10.0,
    timeout_disconnection=10.0,
    timeout_post_stop=5.0,
)

# 2. Create TradingNode
node = TradingNode(config=config_node)

# 3. Configure and add strategy
strategy = MyStrategy(
    MyStrategyConfig(
        instrument_id=InstrumentId.from_str("ETHUSDT.BINANCE"),
        bar_type=BarType.from_str("ETHUSDT.BINANCE-1-MINUTE-LAST-EXTERNAL"),
        trade_size=Decimal("0.05"),
    ),
)
node.trader.add_strategy(strategy)

# 4. Register client factories
node.add_data_client_factory(BINANCE, BinanceLiveDataClientFactory)
node.add_exec_client_factory(BINANCE, BinanceLiveExecClientFactory)
node.build()

# 5. Run (CTRL+C to stop gracefully)
if __name__ == "__main__":
    try:
        node.run()
    finally:
        node.dispose()
```

## Exchange-specific configuration

### Binance Futures

```python
from nautilus_trader.adapters.binance import BinanceAccountType

# USDT-margined futures
data_config = BinanceDataClientConfig(
    account_type=BinanceAccountType.USDT_FUTURES,
    testnet=False,
    instrument_provider=InstrumentProviderConfig(load_all=True),
)

exec_config = BinanceExecClientConfig(
    account_type=BinanceAccountType.USDT_FUTURES,
    testnet=False,
    instrument_provider=InstrumentProviderConfig(load_all=True),
)

# Instrument ID format: "BTCUSDT-PERP.BINANCE"
```

### Bybit

```python
from nautilus_trader.adapters.bybit import BYBIT
from nautilus_trader.adapters.bybit import BybitDataClientConfig
from nautilus_trader.adapters.bybit import BybitExecClientConfig
from nautilus_trader.adapters.bybit import BybitLiveDataClientFactory
from nautilus_trader.adapters.bybit import BybitLiveExecClientFactory
from nautilus_trader.adapters.bybit import BybitProductType

data_config = BybitDataClientConfig(
    product_types=[BybitProductType.LINEAR],
    testnet=False,
    instrument_provider=InstrumentProviderConfig(load_all=True),
)

exec_config = BybitExecClientConfig(
    product_types=[BybitProductType.LINEAR],
    testnet=False,
    instrument_provider=InstrumentProviderConfig(load_all=True),
)
```

### Interactive Brokers

Requires TWS or IB Gateway running locally:

```python
from nautilus_trader.adapters.interactive_brokers.common import IB
from nautilus_trader.adapters.interactive_brokers.config import (
    InteractiveBrokersDataClientConfig,
    InteractiveBrokersExecClientConfig,
)
from nautilus_trader.adapters.interactive_brokers.factories import (
    InteractiveBrokersLiveDataClientFactory,
    InteractiveBrokersLiveExecClientFactory,
)

data_config = InteractiveBrokersDataClientConfig(
    ibg_host="127.0.0.1",
    ibg_port=7497,       # TWS live: 7496, paper: 7497; Gateway live: 4001, paper: 4002
    ibg_client_id=1,
    instrument_provider=InstrumentProviderConfig(load_all=False),
)

exec_config = InteractiveBrokersExecClientConfig(
    ibg_host="127.0.0.1",
    ibg_port=7497,
    ibg_client_id=1,
    instrument_provider=InstrumentProviderConfig(load_all=False),
    account_id="YOUR_ACCOUNT_ID",  # e.g., "DU12345" for paper
)
```

**IB Prerequisites:**
- Install TWS (Trader Workstation) or IB Gateway
- Enable API access in TWS/Gateway settings
- Configure allowed IP addresses
- For paper trading: use port 7497 (TWS) or 4002 (Gateway)
- For live trading: use port 7496 (TWS) or 4001 (Gateway)

See `docs/integrations/ib.md` for the complete IB integration guide.

## Multi-venue configuration

Connect to multiple exchanges simultaneously:

```python
config_node = TradingNodeConfig(
    data_clients={
        "BINANCE_SPOT": BinanceDataClientConfig(
            account_type=BinanceAccountType.SPOT,
        ),
        "BINANCE_FUTURES": BinanceDataClientConfig(
            account_type=BinanceAccountType.USDT_FUTURES,
        ),
    },
    exec_clients={
        "BINANCE_SPOT": BinanceExecClientConfig(
            account_type=BinanceAccountType.SPOT,
        ),
        "BINANCE_FUTURES": BinanceExecClientConfig(
            account_type=BinanceAccountType.USDT_FUTURES,
        ),
    },
)
```

## Execution reconciliation

Reconciliation aligns the engine's internal state with the exchange's actual state at startup.
It recovers missed order events, fills, and position changes.

**Always enable reconciliation for live trading:**

```python
LiveExecEngineConfig(
    reconciliation=True,
    reconciliation_lookback_mins=1440,  # Look back 24 hours
)
```

Key settings:

| Setting | Default | Description |
|---|---|---|
| `reconciliation` | True | Enable startup reconciliation |
| `reconciliation_lookback_mins` | None (max) | How far back to check |
| `inflight_check_interval_ms` | 2000 | Monitor unconfirmed orders |
| `open_check_interval_secs` | None | Periodic open order verification |

## Environment variables

Most adapters read credentials from environment variables. Set them before running:

```bash
# Binance
export BINANCE_API_KEY="your_key"
export BINANCE_API_SECRET="your_secret"

# Binance testnet
export BINANCE_FUTURES_TESTNET_API_KEY="your_testnet_key"
export BINANCE_FUTURES_TESTNET_API_SECRET="your_testnet_secret"

# Bybit
export BYBIT_API_KEY="your_key"
export BYBIT_API_SECRET="your_secret"

# OKX
export OKX_API_KEY="your_key"
export OKX_API_SECRET="your_secret"
export OKX_PASSPHRASE="your_passphrase"
```

## Strategy considerations for live trading

### Strategy must not block the event loop

All strategy callbacks (`on_bar`, `on_quote_tick`, etc.) run on the main event loop thread.
They must return quickly. Move heavy computation to a background thread:

```python
import asyncio
from concurrent.futures import ThreadPoolExecutor

class MyStrategy(Strategy):
    def __init__(self, config):
        super().__init__(config)
        self._executor = ThreadPoolExecutor(max_workers=1)

    def on_bar(self, bar: Bar) -> None:
        # Quick calculations are OK here
        if self.should_compute_signal():
            # Offload heavy work
            loop = asyncio.get_event_loop()
            loop.run_in_executor(self._executor, self._heavy_computation, bar)

    def _heavy_computation(self, bar):
        # ML inference, complex calculations, etc.
        pass
```

### Graceful shutdown

```python
if __name__ == "__main__":
    try:
        node.run()
    except KeyboardInterrupt:
        pass
    finally:
        try:
            node.stop()
        finally:
            node.dispose()
```

### Managing stop behavior

Configure automatic position closing on stop:

```python
config = MyStrategyConfig(
    manage_stop=True,    # Performs market_exit() before stopping
    close_positions_on_stop=True,
)
```

## Testing progression

Recommended path from development to production:

1. **Backtest** with historical data → validate strategy logic
2. **Backtest** with realistic fill model → assess execution sensitivity
3. **Sandbox** with testnet data → verify live data handling
4. **Sandbox** with production data → confirm real market behavior
5. **Live** with testnet/paper account → test exchange connectivity
6. **Live** with small real capital → gradual scale-up

## Detailed integration guides

For exchange-specific details (instrument formats, API limits, supported features),
read the per-exchange guide in `docs/integrations/`:

| Exchange | Guide file |
|---|---|
| Binance | `docs/integrations/binance.md` |
| Bybit | `docs/integrations/bybit.md` |
| OKX | `docs/integrations/okx.md` |
| Kraken | `docs/integrations/kraken.md` |
| BitMEX | `docs/integrations/bitmex.md` |
| Deribit | `docs/integrations/deribit.md` |
| dYdX | `docs/integrations/dydx.md` |
| Hyperliquid | `docs/integrations/hyperliquid.md` |
| Interactive Brokers | `docs/integrations/ib.md` |
| Polymarket | `docs/integrations/polymarket.md` |
| Databento | `docs/integrations/databento.md` |
| Tardis | `docs/integrations/tardis.md` |

Also see `examples/live/` in the repository for per-exchange runnable examples.


path: references/paper_trading.md
content: # Paper Trading (Sandbox) Guide

This guide explains how to run paper trading using NautilusTrader's Sandbox mode, which
uses **real-time market data** from a live exchange but executes orders through a **local
simulated exchange**.

## What is Sandbox mode?

Sandbox mode combines:
- **Live data client** → real market data from an exchange (prices, order books, bars)
- **Sandbox execution client** → local simulated exchange (no real orders sent)

This lets you test strategies against real market conditions without risking capital.

## How Sandbox differs from Backtest and Live

| Aspect | Backtest | Sandbox | Live |
|---|---|---|---|
| Data source | Historical files | Live exchange feeds | Live exchange feeds |
| Order execution | Simulated (BacktestEngine) | Simulated (SandboxExecClient) | Real exchange |
| Market impact | None | None | Real |
| Account balance | Simulated | Simulated (configurable) | Real |
| Running mode | Finite (data exhaustion) | Continuous | Continuous |

## Complete Sandbox example (Binance Futures)

```python
import asyncio
from decimal import Decimal

from nautilus_trader.adapters.binance import BINANCE, BINANCE_VENUE
from nautilus_trader.adapters.binance import BinanceAccountType
from nautilus_trader.adapters.binance import BinanceDataClientConfig
from nautilus_trader.adapters.binance.factories import BinanceLiveDataClientFactory
from nautilus_trader.adapters.sandbox.config import SandboxExecutionClientConfig
from nautilus_trader.adapters.sandbox.factory import SandboxLiveExecClientFactory
from nautilus_trader.config import (
    CacheConfig,
    InstrumentProviderConfig,
    LiveExecEngineConfig,
    LoggingConfig,
    TradingNodeConfig,
)
from nautilus_trader.live.node import TradingNode
from nautilus_trader.model.data import BarType
from nautilus_trader.model.identifiers import InstrumentId, TraderId

# -- Your strategy import --
# from my_strategy import MyStrategy, MyStrategyConfig


async def main():
    # 1. Configure the TradingNode
    config_node = TradingNodeConfig(
        trader_id=TraderId("SANDBOX-001"),
        logging=LoggingConfig(
            log_level="INFO",
            log_colors=True,
            use_pyo3=True,
        ),
        exec_engine=LiveExecEngineConfig(
            reconciliation=True,
            reconciliation_lookback_mins=1440,
            filter_position_reports=True,
        ),
        cache=CacheConfig(
            timestamps_as_iso8601=True,
            flush_on_start=False,
        ),
        # DATA: Real market data from Binance testnet
        data_clients={
            BINANCE: BinanceDataClientConfig(
                api_key=None,       # Uses BINANCE_API_KEY env var
                api_secret=None,    # Uses BINANCE_API_SECRET env var
                account_type=BinanceAccountType.USDT_FUTURES,
                testnet=True,       # Use testnet for safety
                instrument_provider=InstrumentProviderConfig(load_all=True),
            ),
        },
        # EXECUTION: Local sandbox (no real orders)
        exec_clients={
            BINANCE: SandboxExecutionClientConfig(
                venue=BINANCE_VENUE,
                starting_balances=["10_000 USDT", "10 ETH"],
            ),
        },
        timeout_connection=30.0,
        timeout_reconciliation=10.0,
        timeout_portfolio=10.0,
        timeout_disconnection=10.0,
        timeout_post_stop=5.0,
    )

    # 2. Create TradingNode
    node = TradingNode(config=config_node)

    # 3. Configure and add strategy
    strategy = MyStrategy(
        MyStrategyConfig(
            instrument_id=InstrumentId.from_str("ETHUSDT-PERP.BINANCE"),
            bar_type=BarType.from_str("ETHUSDT-PERP.BINANCE-1-MINUTE-LAST-EXTERNAL"),
            trade_size=Decimal("0.010"),
        ),
    )
    node.trader.add_strategy(strategy)

    # 4. Register client factories
    node.add_data_client_factory(BINANCE, BinanceLiveDataClientFactory)
    node.add_exec_client_factory(BINANCE, SandboxLiveExecClientFactory)
    node.build()

    # 5. Run (CTRL+C to stop)
    try:
        await node.run_async()
    finally:
        await node.stop_async()
        await asyncio.sleep(1)
        node.dispose()


if __name__ == "__main__":
    asyncio.run(main())
```

## Key configuration details

### SandboxExecutionClientConfig

```python
from nautilus_trader.adapters.sandbox.config import SandboxExecutionClientConfig

SandboxExecutionClientConfig(
    venue=BINANCE_VENUE,                        # Venue to simulate
    starting_balances=["10_000 USDT", "10 ETH"],  # Initial balances
)
```

- `venue`: The venue identifier (must match data client venue)
- `starting_balances`: List of starting balances as `"{amount} {currency}"` strings

### Using testnet data

For Binance, set `testnet=True` in the data client config to use testnet data feeds.
This avoids needing production API credentials for paper trading.

Required environment variables (for Binance testnet):
```bash
export BINANCE_FUTURES_TESTNET_API_KEY="your_testnet_key"
export BINANCE_FUTURES_TESTNET_API_SECRET="your_testnet_secret"
```

Get testnet keys from: https://testnet.binancefuture.com

### Client factory pairing

The key to Sandbox mode is using **mismatched** factories:

```python
# Data: real exchange data factory
node.add_data_client_factory(BINANCE, BinanceLiveDataClientFactory)

# Execution: sandbox simulated factory (NOT BinanceLiveExecClientFactory)
node.add_exec_client_factory(BINANCE, SandboxLiveExecClientFactory)
```

## Sandbox with other exchanges

### Bybit

```python
from nautilus_trader.adapters.bybit import BYBIT, BYBIT_VENUE
from nautilus_trader.adapters.bybit import BybitDataClientConfig
from nautilus_trader.adapters.bybit.factories import BybitLiveDataClientFactory

config_node = TradingNodeConfig(
    data_clients={
        BYBIT: BybitDataClientConfig(
            testnet=True,
            instrument_provider=InstrumentProviderConfig(load_all=True),
        ),
    },
    exec_clients={
        BYBIT: SandboxExecutionClientConfig(
            venue=BYBIT_VENUE,
            starting_balances=["10_000 USDT"],
        ),
    },
    ...
)

node.add_data_client_factory(BYBIT, BybitLiveDataClientFactory)
node.add_exec_client_factory(BYBIT, SandboxLiveExecClientFactory)
```

### Interactive Brokers

```python
from nautilus_trader.adapters.interactive_brokers.common import IB
from nautilus_trader.adapters.interactive_brokers.config import InteractiveBrokersDataClientConfig
from nautilus_trader.adapters.interactive_brokers.factories import (
    InteractiveBrokersLiveDataClientFactory,
)

config_node = TradingNodeConfig(
    data_clients={
        IB: InteractiveBrokersDataClientConfig(
            ibg_host="127.0.0.1",
            ibg_port=4002,  # Paper trading port
            ibg_client_id=1,
            instrument_provider=InstrumentProviderConfig(load_all=False),
        ),
    },
    exec_clients={
        IB: SandboxExecutionClientConfig(
            venue=IB,
            starting_balances=["100_000 USD"],
        ),
    },
    ...
)

node.add_data_client_factory("IB", InteractiveBrokersLiveDataClientFactory)
node.add_exec_client_factory("IB", SandboxLiveExecClientFactory)
```

## Monitoring and results

During a sandbox session, you can monitor:

1. **Logs**: Strategy logs, order events, fill events appear in console
2. **Cache**: Access orders, positions, and account data via `node.trader.cache`

After stopping the node, you can generate reports the same way as backtesting:

```python
# These work the same as in backtesting
orders_report = node.trader.generate_orders_report()
positions_report = node.trader.generate_positions_report()
```

## Key tips

- **Always test with testnet first** before connecting to production data feeds
- **The same strategy file** used in backtesting works in Sandbox — only the config changes
- **Look at `examples/sandbox/`** in the repository for per-exchange sandbox examples
- **Sandbox mode has the same matching engine** as backtesting (L1 by default), so fills
  are simulated locally based on market data
- **Network errors in data feeds** will affect sandbox just like live trading — this is
  actually useful for testing strategy resilience


path: references/strategy_development.md
content: # Strategy Development Guide

This guide explains how to write trading strategies with NautilusTrader.

## Strategy architecture

A strategy consists of two parts:

1. **StrategyConfig** — Configuration class (optional but recommended). Defines parameters.
2. **Strategy** — The strategy class. Inherits from `Strategy` and implements event handlers.

The same strategy code runs identically in backtest, sandbox, and live modes.

## Minimal strategy template

```python
from decimal import Decimal

from nautilus_trader.config import StrategyConfig
from nautilus_trader.indicators import ExponentialMovingAverage
from nautilus_trader.model.data import Bar, BarType
from nautilus_trader.model.enums import OrderSide
from nautilus_trader.model.identifiers import InstrumentId
from nautilus_trader.trading.strategy import Strategy


class MyStrategyConfig(StrategyConfig, frozen=True):
    """
    Configuration for MyStrategy.

    Parameters
    ----------
    instrument_id : InstrumentId
        The instrument to trade.
    bar_type : BarType
        The bar type to use for signals.
    trade_size : Decimal
        The position size per trade.
    fast_ema_period : int
        Fast EMA period.
    slow_ema_period : int
        Slow EMA period.

    """
    instrument_id: InstrumentId
    bar_type: BarType
    trade_size: Decimal
    fast_ema_period: int = 10
    slow_ema_period: int = 20


class MyStrategy(Strategy):
    def __init__(self, config: MyStrategyConfig) -> None:
        super().__init__(config)
        # Create indicators
        self.fast_ema = ExponentialMovingAverage(config.fast_ema_period)
        self.slow_ema = ExponentialMovingAverage(config.slow_ema_period)

    def on_start(self) -> None:
        """Called when the strategy starts. Set up subscriptions and state."""
        # Validate instrument exists
        self.instrument = self.cache.instrument(self.config.instrument_id)
        if self.instrument is None:
            self.log.error(f"Could not find instrument {self.config.instrument_id}")
            self.stop()
            return

        # Register indicators for automatic updates
        self.register_indicator_for_bars(self.config.bar_type, self.fast_ema)
        self.register_indicator_for_bars(self.config.bar_type, self.slow_ema)

        # Request historical data (to warm up indicators)
        self.request_bars(self.config.bar_type)

        # Subscribe to live data
        self.subscribe_bars(self.config.bar_type)

    def on_bar(self, bar: Bar) -> None:
        """Called on each new bar. Implement trading logic here."""
        # Wait for indicators to warm up
        if not self.indicators_initialized():
            self.log.info(
                f"Waiting for indicators to warm up "
                f"[{self.cache.bar_count(self.config.bar_type)}]",
            )
            return

        # === YOUR TRADING LOGIC HERE ===
        # BUY signal: fast EMA crosses above slow EMA
        if self.fast_ema.value >= self.slow_ema.value:
            if self.portfolio.is_flat(self.config.instrument_id):
                self.buy()
            elif self.portfolio.is_net_short(self.config.instrument_id):
                self.close_all_positions(self.config.instrument_id)
                self.buy()

        # SELL signal: fast EMA crosses below slow EMA
        elif self.fast_ema.value < self.slow_ema.value:
            if self.portfolio.is_flat(self.config.instrument_id):
                self.sell()
            elif self.portfolio.is_net_long(self.config.instrument_id):
                self.close_all_positions(self.config.instrument_id)
                self.sell()

    def buy(self) -> None:
        order = self.order_factory.market(
            instrument_id=self.config.instrument_id,
            order_side=OrderSide.BUY,
            quantity=self.instrument.make_qty(self.config.trade_size),
        )
        self.submit_order(order)

    def sell(self) -> None:
        order = self.order_factory.market(
            instrument_id=self.config.instrument_id,
            order_side=OrderSide.SELL,
            quantity=self.instrument.make_qty(self.config.trade_size),
        )
        self.submit_order(order)

    def on_stop(self) -> None:
        """Called when the strategy stops. Clean up."""
        self.cancel_all_orders(self.config.instrument_id)
        self.close_all_positions(self.config.instrument_id)
        self.unsubscribe_bars(self.config.bar_type)

    def on_reset(self) -> None:
        """Called on strategy reset. Reset indicators."""
        self.fast_ema.reset()
        self.slow_ema.reset()
```

## Strategy lifecycle handlers

These are called by the Nautilus engine at specific lifecycle events:

| Handler | When it's called | Typical use |
|---|---|---|
| `on_start()` | Strategy starts | Subscribe to data, initialize state |
| `on_stop()` | Strategy stops | Cancel orders, close positions |
| `on_resume()` | Strategy resumes | Re-subscribe after a pause |
| `on_reset()` | Strategy resets | Reset indicators and custom state |
| `on_dispose()` | Strategy is destroyed | Release resources |

## Data handlers

These are called when market data arrives:

| Handler | Data type | Use case |
|---|---|---|
| `on_bar(bar: Bar)` | Bar/candle data | Most common — time-based strategies |
| `on_quote_tick(tick: QuoteTick)` | Bid/ask quotes | Spread-based strategies, HFT |
| `on_trade_tick(tick: TradeTick)` | Individual trades | Volume-based strategies |
| `on_order_book(book: OrderBook)` | Full order book | Market-making, order flow |
| `on_order_book_deltas(deltas)` | Book updates | Incremental book processing |
| `on_data(data: Data)` | Custom data types | User-defined signals |
| `on_historical_data(data: Data)` | Historical response | Processing requested history |

## Order management

### Order types

Create orders via `self.order_factory`:

```python
from nautilus_trader.model.enums import OrderSide, TimeInForce
from nautilus_trader.model.objects import Price, Quantity

# Market order
order = self.order_factory.market(
    instrument_id=self.config.instrument_id,
    order_side=OrderSide.BUY,
    quantity=self.instrument.make_qty(Decimal("1.0")),
)

# Limit order
order = self.order_factory.limit(
    instrument_id=self.config.instrument_id,
    order_side=OrderSide.BUY,
    quantity=self.instrument.make_qty(Decimal("1.0")),
    price=self.instrument.make_price(50000.00),
    time_in_force=TimeInForce.GTC,
)

# Stop-market order
order = self.order_factory.stop_market(
    instrument_id=self.config.instrument_id,
    order_side=OrderSide.SELL,
    quantity=self.instrument.make_qty(Decimal("1.0")),
    trigger_price=self.instrument.make_price(49000.00),
)

# Submit any order
self.submit_order(order)
```

### Order event handlers

Monitor order lifecycle events:

```python
def on_order_accepted(self, event) -> None: ...
def on_order_rejected(self, event) -> None: ...
def on_order_filled(self, event) -> None: ...
def on_order_canceled(self, event) -> None: ...
def on_order_expired(self, event) -> None: ...
def on_order_event(self, event) -> None: ...  # Catches all order events
```

### Position event handlers

```python
def on_position_opened(self, event) -> None: ...
def on_position_changed(self, event) -> None: ...
def on_position_closed(self, event) -> None: ...
```

### Position management

```python
# Check position state
self.portfolio.is_flat(instrument_id)       # No position
self.portfolio.is_net_long(instrument_id)   # Net long
self.portfolio.is_net_short(instrument_id)  # Net short

# Close positions
self.close_all_positions(instrument_id)

# Cancel orders
self.cancel_all_orders(instrument_id)
self.cancel_order(order)

# Modify existing order
self.modify_order(order, new_quantity)

# Graceful exit (cancel orders + close positions)
self.market_exit()
```

## Indicators

NautilusTrader includes many built-in indicators in `nautilus_trader.indicators`:

| Indicator | Class |
|---|---|
| EMA | `ExponentialMovingAverage(period)` |
| SMA | `SimpleMovingAverage(period)` |
| RSI | `RelativeStrengthIndex(period)` |
| ATR | `AverageTrueRange(period)` |
| Bollinger Bands | `BollingerBands(period, k)` |
| MACD | `MovingAverageConvergenceDivergence(fast, slow, signal)` |

### Registering indicators

```python
# Register for automatic updates with bar data
self.register_indicator_for_bars(bar_type, self.ema)

# Register for automatic updates with quote ticks
self.register_indicator_for_quote_ticks(instrument_id, self.ema)

# Register for automatic updates with trade ticks
self.register_indicator_for_trade_ticks(instrument_id, self.ema)

# Check if all registered indicators are warmed up
if self.indicators_initialized():
    # Safe to use indicator values
    value = self.ema.value
```

## Timers and scheduled actions

```python
import pandas as pd

# Set a one-time alert
self.clock.set_time_alert(
    name="MyAlert",
    alert_time=self.clock.utc_now() + pd.Timedelta(minutes=5),
)

# Set a recurring timer
self.clock.set_timer(
    name="MyTimer",
    interval=pd.Timedelta(minutes=1),
)

# Handle timer events in on_event
def on_event(self, event) -> None:
    if isinstance(event, TimeEvent) and event.name == "MyTimer":
        # Do periodic work
        pass
```

## Cache access

```python
# Get latest data
last_quote = self.cache.quote_tick(instrument_id)
last_trade = self.cache.trade_tick(instrument_id)
last_bar = self.cache.bar(bar_type)

# Get orders and positions
order = self.cache.order(client_order_id)
position = self.cache.position(position_id)

# Get instrument
instrument = self.cache.instrument(instrument_id)
```

## Multiple strategy instances

When running multiple instances of the same strategy (different instruments or parameters),
give each a unique `order_id_tag`:

```python
config1 = MyStrategyConfig(
    instrument_id="EUR/USD.SIM",
    order_id_tag="001",  # Unique tag
    ...
)
config2 = MyStrategyConfig(
    instrument_id="GBP/USD.SIM",
    order_id_tag="002",  # Different tag
    ...
)
```

## Writing more complex strategies

For advanced strategy patterns beyond the simple EMA cross template above, refer to:

| Pattern | Example file |
|---|---|
| Bracket orders (entry + SL + TP) | `nautilus_trader/examples/strategies/ema_cross_bracket.py` |
| Bracket with exec algorithm (TWAP) | `nautilus_trader/examples/strategies/ema_cross_bracket_algo.py` |
| Stop entry orders | `nautilus_trader/examples/strategies/ema_cross_stop_entry.py` |
| Trailing stop loss | `nautilus_trader/examples/strategies/ema_cross_trailing_stop.py` |
| Market making | `nautilus_trader/examples/strategies/volatility_market_maker.py` |
| Order book imbalance | `nautilus_trader/examples/strategies/orderbook_imbalance.py` |
| Hedge mode (dual positions) | `nautilus_trader/examples/strategies/ema_cross_hedge_mode.py` |

For more complex concepts also see:

- **Execution algorithms**: `docs/concepts/execution.md` — TWAP, custom algos
- **Custom data types**: `docs/concepts/custom_data.md` — integrate non-market signals
- **Actors**: `docs/concepts/actors.md` — for non-trading components (data processing, signals)
- **Order types and advanced orders**: `docs/concepts/orders.md`
- **Options and greeks**: `docs/concepts/options.md` and `docs/concepts/greeks.md`


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)