| name | nautilus-trader |
| description | NautilusTrader algorithmic trading platform. Use for building trading strategies, backtesting, live trading, data handling, and quantitative finance applications. |
Nautilus-Trader Skill
Comprehensive assistance with nautilus-trader development, generated from official documentation.
When to Use This Skill
This skill should be triggered when:
- Working with nautilus_trader
- Asking about nautilus_trader features or APIs
- Implementing nautilus_trader solutions
- Debugging nautilus_trader code
- Learning nautilus_trader best practices
Quick Reference
Common Patterns
Pattern 1: Strategies The heart of the NautilusTrader user experience is in writing and working with trading strategies. Defining a strategy involves inheriting the Strategy class and implementing the methods required by the strategy's logic. Key capabilities: All Actor capabilities. Order management. Relationship with actors: The Strategy class inherits from Actor, which means strategies have access to all actor functionality plus order management capabilities. tipWe recommend reviewing the Actors guide before diving into strategy development. Strategies can be added to Nautilus systems in any environment contexts and will start sending commands and receiving events based on their logic as soon as the system starts. Using the basic building blocks of data ingest, event handling, and order management (which we will discuss below), it's possible to implement any type of strategy including directional, momentum, re-balancing, pairs, market making etc. infoSee the Strategy API Reference for a complete description of all available methods. There are two main parts of a Nautilus trading strategy: The strategy implementation itself, defined by inheriting the Strategy class. The optional strategy configuration, defined by inheriting the StrategyConfig class. tipOnce a strategy is defined, the same source code can be used for backtesting and live trading. The main capabilities of a strategy include: Historical data requests. Live data feed subscriptions. Setting time alerts or timers. Cache access. Portfolio access. Creating and managing orders and positions. Strategy implementation Since a trading strategy is a class which inherits from Strategy, you must define a constructor where you can handle initialization. Minimally the base/super class needs to be initialized: from nautilus_trader.trading.strategy import Strategyclass MyStrategy(Strategy): def init(self) -> None: super().init() # <-- the superclass must be called to initialize the strategy From here, you can implement handlers as necessary to perform actions based on state transitions and events. warningDo not call components such as clock and logger in the __init__ constructor (which is prior to registration). This is because the systems clock and logging subsystem have not yet been initialized. Handlers Handlers are methods within the Strategy class which may perform actions based on different types of events or on state changes. These methods are named with the prefix on_*. You can choose to implement any or all of these handler methods depending on the specific goals and needs of your strategy. The purpose of having multiple handlers for similar types of events is to provide flexibility in handling granularity. This means that you can choose to respond to specific events with a dedicated handler, or use a more generic handler to react to a range of related events (using typical switch statement logic). The handlers are called in sequence from the most specific to the most general. Stateful actions These handlers are triggered by lifecycle state changes of the Strategy. It's recommended to: Use the on_start method to initialize your strategy (e.g., fetch instruments, subscribe to data). Use the on_stop method for cleanup tasks (e.g., cancel open orders, close open positions, unsubscribe from data). def on_start(self) -> None:def on_stop(self) -> None:def on_resume(self) -> None:def on_reset(self) -> None:def on_dispose(self) -> None:def on_degrade(self) -> None:def on_fault(self) -> None:def on_save(self) -> dict[str, bytes]: # Returns user-defined dictionary of state to be saveddef on_load(self, state: dict[str, bytes]) -> None: Data handling These handlers receive data updates, including built-in market data and custom user-defined data. You can use these handlers to define actions upon receiving data object instances. from nautilus_trader.core import Datafrom nautilus_trader.model import OrderBookfrom nautilus_trader.model import Barfrom nautilus_trader.model import QuoteTickfrom nautilus_trader.model import TradeTickfrom nautilus_trader.model import OrderBookDeltasfrom nautilus_trader.model import InstrumentClosefrom nautilus_trader.model import InstrumentStatusfrom nautilus_trader.model.instruments import Instrumentdef on_order_book_deltas(self, deltas: OrderBookDeltas) -> None:def on_order_book(self, order_book: OrderBook) -> None:def on_quote_tick(self, tick: QuoteTick) -> None:def on_trade_tick(self, tick: TradeTick) -> None:def on_bar(self, bar: Bar) -> None:def on_instrument(self, instrument: Instrument) -> None:def on_instrument_status(self, data: InstrumentStatus) -> None:def on_instrument_close(self, data: InstrumentClose) -> None:def on_historical_data(self, data: Data) -> None:def on_data(self, data: Data) -> None: # Custom data passed to this handlerdef on_signal(self, signal: Data) -> None: # Custom signals passed to this handler Order management These handlers receive events related to orders. OrderEvent type messages are passed to handlers in the following sequence: Specific handler (e.g., on_order_accepted, on_order_rejected, etc.) on_order_event(...) on_event(...) from nautilus_trader.model.events import OrderAcceptedfrom nautilus_trader.model.events import OrderCanceledfrom nautilus_trader.model.events import OrderCancelRejectedfrom nautilus_trader.model.events import OrderDeniedfrom nautilus_trader.model.events import OrderEmulatedfrom nautilus_trader.model.events import OrderEventfrom nautilus_trader.model.events import OrderExpiredfrom nautilus_trader.model.events import OrderFilledfrom nautilus_trader.model.events import OrderInitializedfrom nautilus_trader.model.events import OrderModifyRejectedfrom nautilus_trader.model.events import OrderPendingCancelfrom nautilus_trader.model.events import OrderPendingUpdatefrom nautilus_trader.model.events import OrderRejectedfrom nautilus_trader.model.events import OrderReleasedfrom nautilus_trader.model.events import OrderSubmittedfrom nautilus_trader.model.events import OrderTriggeredfrom nautilus_trader.model.events import OrderUpdateddef on_order_initialized(self, event: OrderInitialized) -> None:def on_order_denied(self, event: OrderDenied) -> None:def on_order_emulated(self, event: OrderEmulated) -> None:def on_order_released(self, event: OrderReleased) -> None:def on_order_submitted(self, event: OrderSubmitted) -> None:def on_order_rejected(self, event: OrderRejected) -> None:def on_order_accepted(self, event: OrderAccepted) -> None:def on_order_canceled(self, event: OrderCanceled) -> None:def on_order_expired(self, event: OrderExpired) -> None:def on_order_triggered(self, event: OrderTriggered) -> None:def on_order_pending_update(self, event: OrderPendingUpdate) -> None:def on_order_pending_cancel(self, event: OrderPendingCancel) -> None:def on_order_modify_rejected(self, event: OrderModifyRejected) -> None:def on_order_cancel_rejected(self, event: OrderCancelRejected) -> None:def on_order_updated(self, event: OrderUpdated) -> None:def on_order_filled(self, event: OrderFilled) -> None:def on_order_event(self, event: OrderEvent) -> None: # All order event messages are eventually passed to this handler Position management These handlers receive events related to positions. PositionEvent type messages are passed to handlers in the following sequence: Specific handler (e.g., on_position_opened, on_position_changed, etc.) on_position_event(...) on_event(...) from nautilus_trader.model.events import PositionChangedfrom nautilus_trader.model.events import PositionClosedfrom nautilus_trader.model.events import PositionEventfrom nautilus_trader.model.events import PositionOpeneddef on_position_opened(self, event: PositionOpened) -> None:def on_position_changed(self, event: PositionChanged) -> None:def on_position_closed(self, event: PositionClosed) -> None:def on_position_event(self, event: PositionEvent) -> None: # All position event messages are eventually passed to this handler Generic event handling This handler will eventually receive all event messages which arrive at the strategy, including those for which no other specific handler exists. from nautilus_trader.core.message import Eventdef on_event(self, event: Event) -> None: Handler example The following example shows a typical on_start handler method implementation (taken from the example EMA cross strategy). Here we can see the following: Indicators being registered to receive bar updates. Historical data being requested (to hydrate the indicators). Live data being subscribed to. def on_start(self) -> None: """ Actions to be performed on strategy start. """ self.instrument = self.cache.instrument(self.instrument_id) if self.instrument is None: self.log.error(f"Could not find instrument for {self.instrument_id}") self.stop() return # Register the indicators for updating self.register_indicator_for_bars(self.bar_type, self.fast_ema) self.register_indicator_for_bars(self.bar_type, self.slow_ema) # Get historical data self.request_bars(self.bar_type) # Subscribe to live data self.subscribe_bars(self.bar_type) self.subscribe_quote_ticks(self.instrument_id) Clock and timers Strategies have access to a Clock which provides a number of methods for creating different timestamps, as well as setting time alerts or timers to trigger TimeEvents. infoSee the Clock API reference for a complete list of available methods. Current timestamps While there are multiple ways to obtain current timestamps, here are two commonly used methods as examples: To get the current UTC timestamp as a tz-aware pd.Timestamp: import pandas as pdnow: pd.Timestamp = self.clock.utc_now() To get the current UTC timestamp as nanoseconds since the UNIX epoch: unix_nanos: int = self.clock.timestamp_ns() Time alerts Time alerts can be set which will result in a TimeEvent being dispatched to the on_event handler at the specified alert time. In a live context, this might be slightly delayed by a few microseconds. This example sets a time alert to trigger one minute from the current time: import pandas as pd# Fire a TimeEvent one minute from nowself.clock.set_time_alert( name="MyTimeAlert1", alert_time=self.clock.utc_now() + pd.Timedelta(minutes=1),) Timers Continuous timers can be set up which will generate a TimeEvent at regular intervals until the timer expires or is canceled. This example sets a timer to fire once per minute, starting immediately: import pandas as pd# Fire a TimeEvent every minuteself.clock.set_timer( name="MyTimer1", interval=pd.Timedelta(minutes=1),) Cache access The trader instances central Cache can be accessed to fetch data and execution objects (orders, positions etc). There are many methods available often with filtering functionality, here we go through some basic use cases. Fetching data The following example shows how data can be fetched from the cache (assuming some instrument ID attribute is assigned): last_quote = self.cache.quote_tick(self.instrument_id)last_trade = self.cache.trade_tick(self.instrument_id)last_bar = self.cache.bar(bar_type) Fetching execution objects The following example shows how individual order and position objects can be fetched from the cache: order = self.cache.order(client_order_id)position = self.cache.position(position_id) infoSee the Cache API Reference for a complete description of all available methods. Portfolio access The traders central Portfolio can be accessed to fetch account and positional information. The following shows a general outline of available methods. Account and positional information import decimalfrom nautilus_trader.accounting.accounts.base import Accountfrom nautilus_trader.model import Venuefrom nautilus_trader.model import Currencyfrom nautilus_trader.model import Moneyfrom nautilus_trader.model import InstrumentIddef account(self, venue: Venue) -> Accountdef balances_locked(self, venue: Venue) -> dict[Currency, Money]def margins_init(self, venue: Venue) -> dict[Currency, Money]def margins_maint(self, venue: Venue) -> dict[Currency, Money]def unrealized_pnls(self, venue: Venue) -> dict[Currency, Money]def realized_pnls(self, venue: Venue) -> dict[Currency, Money]def net_exposures(self, venue: Venue) -> dict[Currency, Money]def unrealized_pnl(self, instrument_id: InstrumentId) -> Moneydef realized_pnl(self, instrument_id: InstrumentId) -> Moneydef net_exposure(self, instrument_id: InstrumentId) -> Moneydef net_position(self, instrument_id: InstrumentId) -> decimal.Decimaldef is_net_long(self, instrument_id: InstrumentId) -> booldef is_net_short(self, instrument_id: InstrumentId) -> booldef is_flat(self, instrument_id: InstrumentId) -> booldef is_completely_flat(self) -> bool infoSee the Portfolio API Reference for a complete description of all available methods. Reports and analysis The Portfolio also makes a PortfolioAnalyzer available, which can be fed with a flexible amount of data (to accommodate different lookback windows). The analyzer can provide tracking for and generating of performance metrics and statistics. infoSee the PortfolioAnalyzer API Reference for a complete description of all available methods. infoSee the Portfolio statistics guide. Trading commands NautilusTrader offers a comprehensive suite of trading commands, enabling granular order management tailored for algorithmic trading. These commands are essential for executing strategies, managing risk, and ensuring seamless interaction with various trading venues. In the following sections, we will delve into the specifics of each command and its use cases. infoThe Execution guide explains the flow through the system, and can be helpful to read in conjunction with the below. Submitting orders An OrderFactory is provided on the base class for every Strategy as a convenience, reducing the amount of boilerplate required to create different Order objects (although these objects can still be initialized directly with the Order.init(...) constructor if the trader prefers). The component a SubmitOrder or SubmitOrderList command will flow to for execution depends on the following: If an emulation_trigger is specified, the command will firstly be sent to the OrderEmulator. If an exec_algorithm_id is specified (with no emulation_trigger), the command will firstly be sent to the relevant ExecAlgorithm. Otherwise, the command will firstly be sent to the RiskEngine. This example submits a LIMIT BUY order for emulation (see Emulated Orders): from nautilus_trader.model.enums import OrderSidefrom nautilus_trader.model.enums import TriggerTypefrom nautilus_trader.model.orders import LimitOrderdef buy(self) -> None: """ Users simple buy method (example). """ order: LimitOrder = self.order_factory.limit( instrument_id=self.instrument_id, order_side=OrderSide.BUY, quantity=self.instrument.make_qty(self.trade_size), price=self.instrument.make_price(5000.00), emulation_trigger=TriggerType.LAST_PRICE, ) self.submit_order(order) infoYou can specify both order emulation and an execution algorithm. This example submits a MARKET BUY order to a TWAP execution algorithm: from nautilus_trader.model.enums import OrderSidefrom nautilus_trader.model.enums import TimeInForcefrom nautilus_trader.model import ExecAlgorithmIddef buy(self) -> None: """ Users simple buy method (example). """ order: MarketOrder = self.order_factory.market( instrument_id=self.instrument_id, order_side=OrderSide.BUY, quantity=self.instrument.make_qty(self.trade_size), time_in_force=TimeInForce.FOK, exec_algorithm_id=ExecAlgorithmId("TWAP"), exec_algorithm_params={"horizon_secs": 20, "interval_secs": 2.5}, ) self.submit_order(order) Canceling orders Orders can be canceled individually, as a batch, or all orders for an instrument (with an optional side filter). If the order is already closed or already pending cancel, then a warning will be logged. If the order is currently open then the status will become PENDING_CANCEL. The component a CancelOrder, CancelAllOrders or BatchCancelOrders command will flow to for execution depends on the following: If the order is currently emulated, the command will firstly be sent to the OrderEmulator. If an exec_algorithm_id is specified (with no emulation_trigger), and the order is still active within the local system, the command will firstly be sent to the relevant ExecAlgorithm. Otherwise, the order will firstly be sent to the ExecutionEngine. infoAny managed GTD timer will also be canceled after the command has left the strategy. The following shows how to cancel an individual order: self.cancel_order(order) The following shows how to cancel a batch of orders: from nautilus_trader.model import Ordermy_order_list: list[Order] = [order1, order2, order3]self.cancel_orders(my_order_list) The following shows how to cancel all orders: self.cancel_all_orders() Modifying orders Orders can be modified individually when emulated, or open on a venue (if supported). If the order is already closed or already pending cancel, then a warning will be logged. If the order is currently open then the status will become PENDING_UPDATE. warningAt least one value must differ from the original order for the command to be valid. The component a ModifyOrder command will flow to for execution depends on the following: If the order is currently emulated, the command will firstly be sent to the OrderEmulator. Otherwise, the order will firstly be sent to the RiskEngine. infoOnce an order is under the control of an execution algorithm, it cannot be directly modified by a strategy (only canceled). The following shows how to modify the size of LIMIT BUY order currently open on a venue: from nautilus_trader.model import Quantitynew_quantity: Quantity = Quantity.from_int(5)self.modify_order(order, new_quantity) infoThe price and trigger price can also be modified (when emulated or supported by a venue). Strategy configuration The main purpose of a separate configuration class is to provide total flexibility over where and how a trading strategy can be instantiated. This includes being able to serialize strategies and their configurations over the wire, making distributed backtesting and firing up remote live trading possible. This configuration flexibility is actually opt-in, in that you can actually choose not to have any strategy configuration beyond the parameters you choose to pass into your strategies' constructor. If you would like to run distributed backtests or launch live trading servers remotely, then you will need to define a configuration. Here is an example configuration: from decimal import Decimalfrom nautilus_trader.config import StrategyConfigfrom nautilus_trader.model import Bar, BarTypefrom nautilus_trader.model import InstrumentIdfrom nautilus_trader.trading.strategy import Strategy# Configuration definitionclass MyStrategyConfig(StrategyConfig): instrument_id: InstrumentId # example value: "ETHUSDT-PERP.BINANCE" bar_type: BarType # example value: "ETHUSDT-PERP.BINANCE-15-MINUTE[LAST]-EXTERNAL" fast_ema_period: int = 10 slow_ema_period: int = 20 trade_size: Decimal order_id_tag: str# Strategy definitionclass MyStrategy(Strategy): def init(self, config: MyStrategyConfig) -> None: # Always initialize the parent Strategy class # After this, configuration is stored and available via self.config super().init(config) # Custom state variables self.time_started = None self.count_of_processed_bars: int = 0 def on_start(self) -> None: self.time_started = self.clock.utc_now() # Remember time, when strategy started self.subscribe_bars(self.config.bar_type) # See how configuration data are exposed via self.config def on_bar(self, bar: Bar): self.count_of_processed_bars += 1 # Update count of processed bars# Instantiate configuration with specific values. By setting:# - InstrumentId - we parameterize the instrument the strategy will trade.# - BarType - we parameterize bar-data, that strategy will trade.config = MyStrategyConfig( instrument_id=InstrumentId.from_str("ETHUSDT-PERP.BINANCE"), bar_type=BarType.from_str("ETHUSDT-PERP.BINANCE-15-MINUTE[LAST]-EXTERNAL"), trade_size=Decimal(1), order_id_tag="001",)# Pass configuration to our trading strategy.strategy = MyStrategy(config=config) When implementing strategies, it's recommended to access configuration values directly through self.config. This provides clear separation between: Configuration data (accessed via self.config): Contains initial settings, that define how the strategy works. Example: self.config.trade_size, self.config.instrument_id Strategy state variables (as direct attributes): Track any custom state of the strategy. Example: self.time_started, self.count_of_processed_bars This separation makes code easier to understand and maintain. noteEven though it often makes sense to define a strategy which will trade a single instrument. The number of instruments a single strategy can work with is only limited by machine resources. Managed GTD expiry It's possible for the strategy to manage expiry for orders with a time in force of GTD (Good 'till Date). This may be desirable if the exchange/broker does not support this time in force option, or for any reason you prefer the strategy to manage this. To use this option, pass manage_gtd_expiry=True to your StrategyConfig. When an order is submitted with a time in force of GTD, the strategy will automatically start an internal time alert. Once the internal GTD time alert is reached, the order will be canceled (if not already closed). Some venues (such as Binance Futures) support the GTD time in force, so to avoid conflicts when using managed_gtd_expiry you should set use_gtd=False for your execution client config. Multiple strategies If you intend running multiple instances of the same strategy, with different configurations (such as trading different instruments), then you will need to define a unique order_id_tag for each of these strategies (as shown above). noteThe platform has built-in safety measures in the event that two strategies share a duplicated strategy ID, then an exception will be raised that the strategy ID has already been registered. The reason for this is that the system must be able to identify which strategy various commands and events belong to. A strategy ID is made up of the strategy class name, and the strategies order_id_tag separated by a hyphen. For example the above config would result in a strategy ID of MyStrategy-001. noteSee the StrategyId API Reference for further details.
Strategy
Pattern 2: Positions This guide explains how positions work in NautilusTrader, including their lifecycle, aggregation from order fills, profit and loss calculations, and the important concept of position snapshotting for netting OMS configurations. Overview A position represents an open exposure to a particular instrument in the market. Positions are fundamental to tracking trading performance and risk, as they aggregate all fills for a particular instrument and continuously calculate metrics like unrealized PnL, average entry price, and total exposure. The system automatically creates positions when orders fill and tracks them from open to close. The platform supports both netting and hedging position management styles through its OMS (Order Management System) configuration. Position lifecycle Creation The system opens a position on the first fill: NETTING OMS: Opens on first fill for an instrument (one position per instrument). HEDGING OMS: Opens on first fill for a new position_id (multiple positions per instrument). A position tracks: Opening order and fill details. Entry side (LONG or SHORT). Initial quantity and average price. Timestamps for initialization and opening. tipYou can access positions through the Cache using self.cache.position(position_id) or self.cache.positions(instrument_id=instrument_id) from within your actors/strategies. Updates As additional fills occur, the position: Aggregates quantities from buy and sell fills. Recalculates average entry and exit prices. Updates peak quantity (maximum exposure reached). Tracks all associated order IDs and trade IDs. Accumulates commissions by currency. Closure A position closes when the net quantity becomes zero (FLAT). At closure: The closing order ID is recorded. Duration is calculated from open to close. Final realized PnL is computed. In NETTING OMS, the engine preserves closed position state through snapshots to maintain historical PnL (see Position snapshotting). Order fill aggregation Positions aggregate order fills to maintain an accurate view of market exposure. The aggregation process handles both sides of trading activity: Buy fills When a BUY order fills: Increases long exposure or reduces short exposure. Updates average entry price for opening trades. Updates average exit price for closing trades. Calculates realized PnL for any closed portion. Sell fills When a SELL order fills: Increases short exposure or reduces long exposure. Updates average entry price for opening trades. Updates average exit price for closing trades. Calculates realized PnL for any closed portion. Net position calculation The position maintains a signed_qty field representing the net exposure: Positive values indicate LONG positions. Negative values indicate SHORT positions. Zero indicates a FLAT (closed) position. # Example: Position aggregation# Initial BUY 100 units at $50signed_qty = +100 # LONG position# Subsequent SELL 150 units at $55signed_qty = -50 # Now SHORT position# Final BUY 50 units at $52signed_qty = 0 # Position FLAT (closed) OMS types and position management NautilusTrader supports two primary OMS types that fundamentally affect how positions are tracked and managed. An OmsType.UNSPECIFIED option also exists, which defaults to the component's context. For comprehensive details, see the Execution guide. NETTING In NETTING mode, all fills for an instrument are aggregated into a single position: One position per instrument ID. All fills contribute to the same position. Position flips from LONG to SHORT (or vice versa) as net quantity changes. Historical snapshots preserve closed position states. HEDGING In HEDGING mode, multiple positions can exist for the same instrument: Multiple simultaneous LONG and SHORT positions. Each position has a unique position ID. Positions are tracked independently. No automatic netting across positions. warningWhen using HEDGING mode, be aware of increased margin requirements as each position consumes margin independently. Some venues may not support true hedging mode and will net positions automatically. Strategy vs venue OMS The platform allows different OMS configurations for strategies and venues: Strategy OMSVenue OMSBehaviorNETTINGNETTINGSingle position per instrument at both strategy and venue.HEDGINGHEDGINGMultiple positions supported at both levels.NETTINGHEDGINGVenue tracks multiple, Nautilus maintains single position.HEDGINGNETTINGVenue tracks single, Nautilus maintains virtual positions. tipFor most trading scenarios, keeping strategy and venue OMS types aligned simplifies position management. Override configurations are primarily useful for prop trading desks or when interfacing with legacy systems. See the Live guide for venue-specific OMS configuration. Position snapshotting Position snapshotting is an important feature for NETTING OMS configurations that preserves the state of closed positions for accurate PnL tracking and reporting. Why snapshotting matters In a NETTING system, when a position closes (becomes FLAT) and then reopens with a new trade, the position object is reset to track the new exposure. Without snapshotting, the historical realized PnL from the previous position cycle would be lost. How it works When a NETTING position closes and then receives a new fill for the same instrument, the execution engine snapshots the closed position state before resetting it, preserving: Final quantities and prices. Realized PnL. All fill events. Commission totals. This snapshot is stored in the cache indexed by position ID. The position then resets for the new cycle while previous snapshots remain accessible. The Portfolio aggregates PnL across all snapshots for accurate totals. noteThis historical snapshot mechanism differs from optional position state snapshots (snapshot_positions), which periodically record open-position state for telemetry. See the Live guide for snapshot_positions and snapshot_positions_interval_secs settings. Example scenario # NETTING OMS Example# Cycle 1: Open LONG positionBUY 100 units at $50 # Position opensSELL 100 units at $55 # Position closes, PnL = $500# Snapshot taken preserving $500 realized PnL# Cycle 2: Open SHORT positionSELL 50 units at $54 # Position reopens (SHORT)BUY 50 units at $52 # Position closes, PnL = $100# Snapshot taken preserving $100 realized PnL# Total realized PnL = $500 + $100 = $600 (from snapshots) Without snapshotting, only the most recent cycle's PnL would be available, leading to incorrect reporting and analysis. PnL calculations NautilusTrader provides comprehensive PnL calculations that account for instrument specifications and market conventions. Realized PnL Calculated when positions are partially or fully closed: # For standard instrumentsrealized_pnl = (exit_price - entry_price) * closed_quantity * multiplier# For inverse instruments (side-aware)# LONG: realized_pnl = closed_quantity * multiplier * (1/entry_price - 1/exit_price)# SHORT: realized_pnl = closed_quantity * multiplier * (1/exit_price - 1/entry_price) The engine automatically applies the correct formula based on position side. Unrealized PnL Calculated using current market prices for open positions. The price parameter accepts any reference price (bid, ask, mid, last, or mark): position.unrealized_pnl(last_price) # Using last traded priceposition.unrealized_pnl(bid_price) # Conservative for LONG positionsposition.unrealized_pnl(ask_price) # Conservative for SHORT positions Total PnL Combines realized and unrealized components: total_pnl = position.total_pnl(current_price)# Returns realized_pnl + unrealized_pnl Currency considerations PnL is calculated in the instrument's settlement currency. For Forex, this is typically the quote currency. For inverse contracts, PnL may be in the base currency. Portfolio aggregates realized PnL per instrument in settlement currency. Multi-currency totals require conversion outside the Position class. Commissions and costs Positions track all trading costs: Commissions are accumulated by currency. Each fill's commission is added to the running total. Multiple commission currencies are supported. Realized PnL includes commissions only when denominated in the settlement currency. Other commissions are tracked separately and may require conversion. commissions = position.commissions()# Returns list[Money] with aggregated commission totals per currencynotional = position.notional_value(current_price)# Returns Money in quote currency (standard) or base currency (inverse) Limitations: Panics if inverse instrument has no base_currency set. Does not handle quanto contracts (returns quote currency instead of settlement currency). For quanto instruments, use instrument.calculate_notional_value() instead. Position properties and state Identifiers id: Unique position identifier. instrument_id: The traded instrument. account_id: Account where position is held. trader_id: The trader who owns the position. strategy_id: The strategy managing the position. opening_order_id: Client order ID that opened the position. closing_order_id: Client order ID that closed the position. Position state side: Current position side (LONG, SHORT, or FLAT). entry: Direction of the currently open position (Buy for LONG, Sell for SHORT). Updates when position flips direction. quantity: Current absolute position size. signed_qty: Signed position size (positive for LONG, negative for SHORT). peak_qty: Maximum quantity reached during position lifetime. is_open: Whether position is currently open. is_closed: Whether position is closed (FLAT). is_long: Whether position side is LONG. is_short: Whether position side is SHORT. Pricing and valuation avg_px_open: Average entry price. avg_px_close: Average exit price when closing. realized_pnl: Realized profit/loss. realized_return: Realized return as decimal (e.g., 0.05 for 5%). quote_currency: Quote currency of the instrument. base_currency: Base currency if applicable. settlement_currency: Currency for PnL settlement. Instrument specifications multiplier: Contract multiplier. price_precision: Decimal precision for prices. size_precision: Decimal precision for quantities. is_inverse: Whether instrument is inverse. Timestamps ts_init: When position was initialized. ts_opened: When position was opened. ts_last: Last update timestamp. ts_closed: When position was closed. duration_ns: Duration from open to close in nanoseconds. Associated data symbol: The instrument's ticker symbol. venue: The trading venue. client_order_ids: All client order IDs associated with position. venue_order_ids: All venue order IDs associated with position. trade_ids: All trade/fill IDs from venue. events: All order fill events applied to position. event_count: Total number of fill events applied. last_event: Most recent fill event. last_trade_id: Most recent trade ID. infoFor complete type information and detailed property documentation, see the Position API Reference. Events and tracking Positions maintain a complete history of events: All order fill events are stored chronologically. Associated client order IDs are tracked. Trade IDs from the venue are preserved. Event count indicates total fills applied. This historical data enables: Detailed position analysis. Trade reconciliation. Performance attribution. Audit trails. tipUse position.events to access the full history of fills for reconciliation. The position.trade_ids property helps match against broker statements. See the Execution guide for reconciliation best practices. Numerical precision Position calculations use 64-bit floating-point (f64) arithmetic for PnL and average price computations. While fixed-point types (Price, Quantity, Money) preserve exact precision at configured decimal places, internal calculations convert to f64 for performance and overflow safety. Design rationale The platform uses f64 for position calculations to balance performance and accuracy: Floating-point operations are significantly faster than arbitrary-precision arithmetic. Raw integer multiplication can overflow even with 128-bit integers. Each calculation starts from precise fixed-point values, avoiding cumulative error. IEEE-754 double precision provides 15 decimal digits of accuracy. Validated precision characteristics Testing confirms f64 arithmetic maintains accuracy for typical trading scenarios: Standard amounts: No precision loss for amounts ≥ 0.01 in standard currencies. High-precision instruments: 9-decimal crypto prices preserved within 1e-6 tolerance. Sequential fills: 100 fills show no drift (commission accuracy to 1e-10). Extreme prices: Handles range from 0.00001 to 99,999.99999 without overflow. Round-trip trades: Opening and closing at same price produces exact PnL (commissions only). For implementation details, see test_position_pnl_precision_* tests in crates/model/src/position.rs. noteFor regulatory compliance or audit trails requiring exact decimal arithmetic, consider using Decimal types from external libraries. Very small amounts below f64 epsilon (1e-15) may round to zero, though this does not affect realistic trading scenarios with standard currency precisions. Integration with other components Positions interact with several key components: Portfolio: Aggregates positions across instruments and strategies. ExecutionEngine: Creates and updates positions from fills. Cache: Stores position state and snapshots. RiskEngine: Monitors position limits and exposure. notePositions are not created for spread instruments. While contingent orders can still trigger for spreads, they operate without position linkage. The engine handles spread instruments separately from regular positions. Summary Positions are central to tracking trading activity and performance in NautilusTrader. Understanding how positions aggregate fills, calculate PnL, and handle different OMS configurations is essential for building robust trading strategies. The position snapshotting mechanism ensures accurate historical tracking in NETTING mode, while the comprehensive event history supports detailed analysis and reconciliation.
position_id
Pattern 3: # Example: Position aggregation# Initial BUY 100 units at $50signed_qty = +100 # LONG position# Subsequent SELL 150 units at $55signed_qty = -50 # Now SHORT position# Final BUY 50 units at $52signed_qty = 0 # Position FLAT (closed)
# Example: Position aggregation# Initial BUY 100 units at $50signed_qty = +100 # LONG position# Subsequent SELL 150 units at $55signed_qty = -50 # Now SHORT position# Final BUY 50 units at $52signed_qty = 0 # Position FLAT (closed)
Pattern 4: OKX Founded in 2017, OKX is a leading cryptocurrency exchange offering spot, perpetual swap, futures, and options trading. This integration supports live market data ingest and order execution on OKX. Overview This adapter is implemented in Rust, with optional Python bindings for ease of use in Python-based workflows. It does not require external OKX client libraries—the core components are compiled as a static library and linked automatically during the build. Examples You can find live example scripts here. Product support Product TypeData FeedTradingNotesSpot✓✓Use for index prices.Perpetual Swaps✓✓Linear and inverse contracts.Futures✓✓Specific expiration dates.Margin✓✓Spot trading with margin/leverage (spot margin).Options✓-Data feed supported, trading coming soon. noteOptions support: While you can subscribe to options market data and receive price updates, order execution for options is not yet implemented. You can use the symbology format shown above to subscribe to options data feeds. infoInstrument multipliers: For derivatives (SWAP, FUTURES, OPTIONS), instrument multipliers are calculated as the product of OKX's ctMult (contract multiplier) and ctVal (contract value) fields. This ensures position sizing accurately reflects both the contract size and value. The OKX adapter includes multiple components, which can be used separately or together depending on your use case. OKXHttpClient: Low-level HTTP API connectivity. OKXWebSocketClient: Low-level WebSocket API connectivity. OKXInstrumentProvider: Instrument parsing and loading functionality. OKXDataClient: Market data feed manager. OKXExecutionClient: Account management and trade execution gateway. OKXLiveDataClientFactory: Factory for OKX data clients (used by the trading node builder). OKXLiveExecClientFactory: Factory for OKX execution clients (used by the trading node builder). noteMost users will simply define a configuration for a live trading node (as shown below), and won’t need to work directly with these lower-level components. Symbology OKX uses specific symbol conventions for different instrument types. All instrument IDs should include the .OKX suffix when referencing them (e.g., BTC-USDT.OKX for spot Bitcoin). Symbol format by instrument type SPOT Format: {BaseCurrency}-{QuoteCurrency} Examples: BTC-USDT - Bitcoin against USDT (Tether) BTC-USDC - Bitcoin against USDC ETH-USDT - Ethereum against USDT SOL-USDT - Solana against USDT To subscribe to spot Bitcoin USD in your strategy: InstrumentId.from_str("BTC-USDT.OKX") # For USDT-quoted spotInstrumentId.from_str("BTC-USDC.OKX") # For USDC-quoted spot SWAP (Perpetual Futures) Format: {BaseCurrency}-{QuoteCurrency}-SWAP Examples: BTC-USDT-SWAP - Bitcoin perpetual swap (linear, USDT-margined) BTC-USD-SWAP - Bitcoin perpetual swap (inverse, coin-margined) ETH-USDT-SWAP - Ethereum perpetual swap (linear) ETH-USD-SWAP - Ethereum perpetual swap (inverse) Linear vs Inverse contracts: Linear (USDT-margined): Uses stablecoins like USDT as margin. Inverse (coin-margined): Uses the base cryptocurrency as margin. FUTURES (Dated Futures) Format: {BaseCurrency}-{QuoteCurrency}-{YYMMDD} Examples: BTC-USD-251226 - Bitcoin futures expiring December 26, 2025 ETH-USD-251226 - Ethereum futures expiring December 26, 2025 BTC-USD-250328 - Bitcoin futures expiring March 28, 2025 Note: Futures are typically inverse contracts (coin-margined). OPTIONS Format: {BaseCurrency}-{QuoteCurrency}-{YYMMDD}-{Strike}-{Type} Examples: BTC-USD-251226-100000-C - Bitcoin call option, $100,000 strike, expiring December 26, 2025 BTC-USD-251226-100000-P - Bitcoin put option, $100,000 strike, expiring December 26, 2025 ETH-USD-251226-4000-C - Ethereum call option, $4,000 strike, expiring December 26, 2025 Where: C = Call option P = Put option Common questions Q: How do I subscribe to spot Bitcoin USD? A: Use BTC-USDT.OKX for USDT-margined spot or BTC-USDC.OKX for USDC-margined spot. Q: What's the difference between BTC-USDT-SWAP and BTC-USD-SWAP? A: BTC-USDT-SWAP is a linear perpetual (USDT-margined), while BTC-USD-SWAP is an inverse perpetual (BTC-margined). Q: How do I know which contract type to use? A: Check the contract_types parameter in the configuration: For linear contracts: OKXContractType.LINEAR. For inverse contracts: OKXContractType.INVERSE. Orders capability Below are the order types, execution instructions, and time-in-force options supported for linear perpetual swap products on OKX. Client order ID requirements noteOKX has specific requirements for client order IDs: No hyphens allowed: OKX does not accept hyphens (-) in client order IDs. Maximum length: 32 characters. Allowed characters: alphanumeric characters and underscores only. When configuring your strategy, ensure you set:use_hyphens_in_client_order_ids=False Order types Order TypeLinear Perpetual SwapNotesMARKET✓Immediate execution at market price. Supports quote quantity.LIMIT✓Execution at specified price or better.STOP_MARKET✓Conditional market order (OKX algo order).STOP_LIMIT✓Conditional limit order (OKX algo order).MARKET_IF_TOUCHED✓Conditional market order (OKX algo order).LIMIT_IF_TOUCHED✓Conditional limit order (OKX algo order).TRAILING_STOP-Not yet supported. infoConditional orders: STOP_MARKET, STOP_LIMIT, MARKET_IF_TOUCHED, and LIMIT_IF_TOUCHED are implemented as OKX algo orders, providing advanced trigger capabilities with multiple price sources. Quantity semantics for spot margin trading When using spot margin trading (use_spot_margin=True), OKX interprets order quantities differently depending on the order side: Limit orders interpret quantity as the number of base currency units. Market SELL orders also use base-unit quantities. Market BUY orders interpret quantity as quote notional (e.g., USDT). warningWhen submitting spot margin market BUY orders, you must: Set quote_quantity=True on the order (or pre-compute the quote-denominated amount). Configure the execution engine with convert_quote_qty_to_base=False so the quote amount reaches the adapter unchanged. The OKX execution client will deny base-denominated market buy orders for spot margin to prevent unintended fills.On the first fill, the order quantity will be automatically updated from the quote quantity to the actual base quantity received, reflecting the executed trade. from nautilus_trader.execution.config import ExecEngineConfigfrom nautilus_trader.execution.engine import ExecutionEngine# Disable automatic conversion for quote quantitiesconfig = ExecEngineConfig(convert_quote_qty_to_base=False)engine = ExecutionEngine(msgbus=msgbus, cache=cache, clock=clock, config=config)# Correct: Spot margin market BUY with quote quantity (spend $100 USDT)order = strategy.order_factory.market( instrument_id=instrument_id, order_side=OrderSide.BUY, quantity=instrument.make_qty(100.0), quote_quantity=True, # Interpret as USDT notional)strategy.submit_order(order) Execution instructions InstructionLinear Perpetual SwapNotespost_only✓Only for LIMIT orders.reduce_only✓Only for derivatives. Time in force Time in forceLinear Perpetual SwapNotesGTC✓Good Till Canceled.FOK✓Fill or Kill.IOC✓Immediate or Cancel.GTD✗Not supported by OKX API. noteGTD (Good Till Date) time in force: OKX does not support native GTD functionality through their API.If you need GTD functionality, you must use Nautilus's strategy-managed GTD feature, which will handle the order expiration by canceling the order at the specified expiry time. Batch operations OperationLinear Perpetual SwapNotesBatch Submit✓Submit multiple orders in single request.Batch Modify✓Modify multiple orders in single request.Batch Cancel✓Cancel multiple orders in single request. Position management FeatureLinear Perpetual SwapNotesQuery positions✓Real-time position updates.Position mode✓Net vs Long/Short mode (see below).Leverage control✓Dynamic leverage adjustment per instrument.Margin mode✓Supports cash, isolated, cross, spot_isolated modes. Position modes OKX supports two position modes for derivatives trading: Net mode (Netting): Single position per instrument that can be positive (LONG) or negative (SHORT). Buy and sell orders net against each other. This is the default and recommended for most traders. Long/Short mode (Hedging): Separate long and short positions for the same instrument. Allows simultaneous long and short positions, useful for hedging strategies. notePosition mode must be configured via the OKX Web/App interface and applies account-wide. The adapter automatically detects the current position mode and handles position reporting accordingly. Trade modes and margin configuration OKX's unified account system supports different trade modes for spot and derivatives trading. The adapter automatically determines the correct trade mode based on your configuration and instrument type. noteImportant: Account modes must be initially configured via the OKX Web/App interface. The API cannot set the account mode for the first time. For more details on OKX's account modes and margin system, see the OKX Account Mode documentation. Trade modes overview OKX supports four trade modes, which the adapter selects automatically based on your configuration: ModeUsed ForLeverageBorrowingConfigurationcashSimple spot trading--use_spot_margin=False (default for SPOT)spot_isolatedSpot trading with margin/leverage✓✓use_spot_margin=TrueisolatedDerivatives trading (SWAP/FUTURES/OPTIONS)✓✓margin_mode=ISOLATED or unset (default for derivatives)crossDerivatives with shared margin pool✓✓margin_mode=CROSS Configuration-based trade mode selection The adapter automatically selects the correct trade mode based on: Instrument type (SPOT vs derivatives) Configuration settings (use_spot_margin for SPOT, margin_mode for derivatives) For SPOT trading # Simple SPOT trading without leverage (uses 'cash' mode)exec_clients={ OKX: OKXExecClientConfig( instrument_types=(OKXInstrumentType.SPOT,), use_spot_margin=False, # Default - simple SPOT # ... other config ),}# SPOT trading WITH margin/leverage (uses 'spot_isolated' mode)exec_clients={ OKX: OKXExecClientConfig( instrument_types=(OKXInstrumentType.SPOT,), use_spot_margin=True, # Enable margin trading for SPOT # ... other config ),} For derivatives trading (SWAP/FUTURES/OPTIONS) # Derivatives with isolated margin (default - uses 'isolated' mode)exec_clients={ OKX: OKXExecClientConfig( instrument_types=(OKXInstrumentType.SWAP,), margin_mode=OKXMarginMode.ISOLATED, # Or omit - ISOLATED is default # ... other config ),}# Derivatives with cross margin (uses 'cross' mode)exec_clients={ OKX: OKXExecClientConfig( instrument_types=(OKXInstrumentType.SWAP,), margin_mode=OKXMarginMode.CROSS, # Share margin across all positions # ... other config ),} For mixed SPOT and derivatives trading When trading both SPOT and derivatives instruments simultaneously, the adapter automatically determines the correct trade mode per-order based on the instrument being traded: # Mixed SPOT + SWAP configurationexec_clients={ OKX: OKXExecClientConfig( instrument_types=(OKXInstrumentType.SPOT, OKXInstrumentType.SWAP), use_spot_margin=True, # Applies to SPOT orders only margin_mode=OKXMarginMode.CROSS, # Applies to SWAP orders only # ... other config ),} How it works: SPOT orders → Uses spot_isolated mode (because use_spot_margin=True) SWAP orders → Uses cross mode (because margin_mode=CROSS) Each order automatically gets the correct tdMode based on its instrument type No manual intervention required This enables strategies that trade across multiple instrument types with different margin configurations, such as: Spot-futures arbitrage strategies Delta-neutral strategies combining spot and perpetual swaps Market making across spot and derivatives markets warningManual trade mode override: While you can still manually override the trade mode per order using params={"td_mode": "..."}, this is not recommended as it bypasses automatic mode selection and can lead to order rejection if the wrong mode is specified for the instrument type (e.g., using isolated for SPOT instruments).Only use manual override if you have specific requirements that cannot be met through configuration. Benefits of configuration-based approach Type-safe: Configuration is validated at startup before placing any orders. Automatic: System chooses correct mode based on instrument type and intent. Clear: Field names explain purpose (use_spot_margin vs obscure td_mode parameter). Safe: Impossible to use incompatible combinations (e.g., isolated mode for SPOT). Backwards compatible: Default values maintain existing behavior. Order querying FeatureLinear Perpetual SwapNotesQuery open orders✓List all active orders.Query order history✓Historical order data.Order status updates✓Real-time order state changes.Trade history✓Execution and fill reports. Contingent orders FeatureLinear Perpetual SwapNotesOrder lists-Not supported.OCO orders✓One-Cancels-Other orders.Bracket orders✓Stop loss + take profit combinations.Conditional orders✓Stop and limit-if-touched orders. Conditional order architecture Conditional orders (OKX algo orders) use a hybrid architecture for optimal performance and reliability: Submission: Via HTTP REST API (/api/v5/trade/order-algo) Status updates: Via WebSocket business endpoint (/ws/v5/business) on the orders-algo channel Cancellation: Via HTTP REST API using algo order ID tracking This design ensures: Immediate submission acknowledgment through HTTP. Real-time status updates through WebSocket. Proper order lifecycle management with algo order ID mapping. Supported conditional order types Order TypeTrigger TypesNotesSTOP_MARKETLast, Mark, IndexMarket execution when triggered.STOP_LIMITLast, Mark, IndexLimit order placement when triggered.MARKET_IF_TOUCHEDLast, Mark, IndexMarket execution when price touched.LIMIT_IF_TOUCHEDLast, Mark, IndexLimit order placement when price touched. Trigger price types Conditional orders support different trigger price sources: Last Price (TriggerType.LAST_PRICE): Uses the last traded price (default). Mark Price (TriggerType.MARK_PRICE): Uses the mark price (recommended for derivatives). Index Price (TriggerType.INDEX_PRICE): Uses the underlying index price. # Example: Stop loss using mark price triggerstop_order = order_factory.stop_market( instrument_id=instrument_id, order_side=OrderSide.SELL, quantity=Quantity.from_str("0.1"), trigger_price=Price.from_str("45000.0"), trigger_type=TriggerType.MARK_PRICE, # Use mark price for trigger)strategy.submit_order(stop_order) Risk management Liquidation and ADL event handling The OKX adapter automatically detects and handles exchange-initiated risk management events: Liquidation orders: When a position is liquidated by the exchange (full or partial), the adapter detects the liquidation category and logs warnings with order details. These orders are processed normally through the order and fill pipeline. Auto-Deleveraging (ADL): When your position is closed by the exchange to offset a counterparty's liquidation, the adapter detects and logs the ADL event with position details. infoLiquidation and ADL events are logged at WARNING level with details including order ID, instrument, and state. Monitor your logs for these events as part of your risk management process.The adapter handles these exchange-generated orders seamlessly, generating appropriate OrderFilled events and updating positions accordingly. No special handling is required in your strategy code. Authentication To use the OKX adapter, you'll need to create API credentials in your OKX account: Log into your OKX account and navigate to the API management page. Create a new API key with the required permissions for trading and data access. Note down your API key, secret key, and passphrase. You can provide these credentials through environment variables: export OKX_API_KEY="your_api_key"export OKX_API_SECRET="your_api_secret"export OKX_API_PASSPHRASE="your_passphrase" Or pass them directly in the configuration (not recommended for production). Demo trading OKX provides a demo trading environment for testing strategies without real funds. To use demo mode, set is_demo=True in your client configuration: config = TradingNodeConfig( data_clients={ OKX: OKXDataClientConfig( is_demo=True, # Enable demo mode # ... other config ), }, exec_clients={ OKX: OKXExecClientConfig( is_demo=True, # Enable demo mode # ... other config ), },) When demo mode is enabled: REST API requests include the x-simulated-trading: 1 header. WebSocket connections use demo endpoints (wspap.okx.com). The same API credentials are used as production. noteYou must use API keys created specifically for demo trading. Production API keys will not work in demo mode. Rate limiting The adapter enforces OKX’s per-endpoint quotas while keeping sensible defaults for both REST and WebSocket calls. REST limits Global cap: 250 requests per second (matches 500 requests / 2 seconds IP allowance). Endpoint-specific quotas appear in the table below and mirror OKX’s published limits where available. WebSocket limits Connection establishment: 3 requests per second (per IP). Subscription operations (subscribe/unsubscribe/login): 480 requests per hour per connection. Order actions (place/cancel/amend): 250 requests per second. warningOKX enforces per-endpoint and per-account quotas; exceeding them leads to HTTP 429 responses and temporary throttling on that key. Key / EndpointLimit (req/sec)Notesokx:global250Matches 500 req / 2 s IP allowance./api/v5/public/instruments10Matches OKX 20 req / 2 s docs./api/v5/market/candles50Higher allowance for streaming candles./api/v5/market/history-candles20Conservative quota for large historical pulls./api/v5/market/history-trades30Trade history pulls./api/v5/account/balance5OKX guidance: 10 req / 2 s./api/v5/trade/order3060 requests / 2 seconds per-instrument limit./api/v5/trade/orders-pending20Open order fetch./api/v5/trade/orders-history20Historical orders./api/v5/trade/fills30Execution reports./api/v5/trade/order-algo10Algo placements (conditional orders)./api/v5/trade/cancel-algos10Algo cancellation. All keys automatically include the okx:global bucket. URLs are normalised (query strings removed) before rate limiting, so requests with different filters share the same quota. infoFor more details on rate limiting, see the official documentation: https://www.okx.com/docs-v5/en/#rest-api-rate-limit. Configuration Configuration options The OKX data client provides the following configuration options: Data client OptionDefaultDescriptioninstrument_types(OKXInstrumentType.SPOT,)Controls which OKX instrument families are loaded (spot, swap, futures, options).contract_typesNoneRestricts loading to specific contract styles when combined with instrument_types.instrument_familiesNoneInstrument families to load (e.g., "BTC-USD", "ETH-USD"). Required for OPTIONS. Optional for FUTURES/SWAP. Not applicable for SPOT/MARGIN.base_url_httpNoneOverride for the OKX REST endpoint; defaults to the production URL resolved at runtime.base_url_wsNoneOverride for the market data WebSocket endpoint.api_keyNoneFalls back to OKX_API_KEY environment variable when unset.api_secretNoneFalls back to OKX_API_SECRET environment variable when unset.api_passphraseNoneFalls back to OKX_PASSPHRASE environment variable when unset.is_demoFalseConnects to the OKX demo environment when True.http_timeout_secs60Request timeout (seconds) for REST market data calls.max_retries3Maximum retry attempts for recoverable REST errors.retry_delay_initial_ms1,000Initial delay (milliseconds) before retrying a failed request.retry_delay_max_ms10,000Upper bound for exponential backoff delay between retries.update_instruments_interval_mins60Interval, in minutes, between background instrument refreshes.vip_levelNoneEnables higher-depth order book channels when set to the matching OKX VIP tier. The OKX execution client provides the following configuration options: Execution client OptionDefaultDescriptioninstrument_types(OKXInstrumentType.SPOT,)Instrument families that should be tradable for this client.contract_typesNoneRestricts tradable contracts (linear, inverse, options) when paired with instrument_types.instrument_familiesNoneInstrument families to load (e.g., "BTC-USD", "ETH-USD"). Required for OPTIONS. Optional for FUTURES/SWAP. Not applicable for SPOT/MARGIN.base_url_httpNoneOverride for the OKX trading REST endpoint.base_url_wsNoneOverride for the private WebSocket endpoint.api_keyNoneFalls back to OKX_API_KEY environment variable when unset.api_secretNoneFalls back to OKX_API_SECRET environment variable when unset.api_passphraseNoneFalls back to OKX_PASSPHRASE environment variable when unset.margin_modeNoneMargin mode for derivatives trading (ISOLATED or CROSS). Only applies to SWAP/FUTURES/OPTIONS. Defaults to ISOLATED if not specified.use_spot_marginFalseEnables margin/leverage for SPOT trading. When True, uses spot_isolated trade mode. When False, uses cash trade mode (no leverage). Only applies to SPOT instruments.is_demoFalseConnects to the OKX demo trading environment.http_timeout_secs60Request timeout (seconds) for REST trading calls.use_fills_channelFalseSubscribes to the dedicated fills channel (VIP5+ required) for lower-latency fill reports.use_mm_mass_cancelFalseUses the market-maker bulk cancel endpoint when available; otherwise falls back to per-order cancels.max_retries3Maximum retry attempts for recoverable REST errors.retry_delay_initial_ms1,000Initial delay (milliseconds) applied before retrying a failed request.retry_delay_max_ms10,000Upper bound for the exponential backoff delay between retries. Below is an example configuration for a live trading node using OKX data and execution clients: from nautilus_trader.adapters.okx import OKXfrom nautilus_trader.adapters.okx import OKXDataClientConfig, OKXExecClientConfigfrom nautilus_trader.adapters.okx.factories import OKXLiveDataClientFactory, OKXLiveExecClientFactoryfrom nautilus_trader.config import InstrumentProviderConfig, LiveExecEngineConfig, LoggingConfig, TradingNodeConfigfrom nautilus_trader.core.nautilus_pyo3 import OKXContractTypefrom nautilus_trader.core.nautilus_pyo3 import OKXInstrumentTypefrom nautilus_trader.core.nautilus_pyo3 import OKXMarginModefrom nautilus_trader.live.node import TradingNodeconfig = TradingNodeConfig( ..., data_clients={ OKX: OKXDataClientConfig( api_key=None, # Will use OKX_API_KEY env var api_secret=None, # Will use OKX_API_SECRET env var api_passphrase=None, # Will use OKX_API_PASSPHRASE env var base_url_http=None, instrument_provider=InstrumentProviderConfig(load_all=True), instrument_types=(OKXInstrumentType.SWAP,), contract_types=(OKXContractType.LINEAR,), is_demo=False, ), }, exec_clients={ OKX: OKXExecClientConfig( api_key=None, api_secret=None, api_passphrase=None, base_url_http=None, base_url_ws=None, instrument_provider=InstrumentProviderConfig(load_all=True), instrument_types=(OKXInstrumentType.SWAP,), contract_types=(OKXContractType.LINEAR,), is_demo=False, ), },)node = TradingNode(config=config)node.add_data_client_factory(OKX, OKXLiveDataClientFactory)node.add_exec_client_factory(OKX, OKXLiveExecClientFactory)node.build() infoFor additional features or to contribute to the OKX adapter, please see our contributing guide.
ctMult
Pattern 5: Tardis Tardis provides granular data for cryptocurrency markets including tick-by-tick order book snapshots & updates, trades, open interest, funding rates, options chains and liquidations data for leading crypto exchanges. NautilusTrader provides an integration with the Tardis API and data formats, enabling seamless access. The capabilities of this adapter include: TardisCSVDataLoader: Reads Tardis-format CSV files and converts them into Nautilus data, with support for both bulk loading and memory-efficient streaming. TardisMachineClient: Supports live streaming and historical replay of data from the Tardis Machine WebSocket server - converting messages into Nautilus data. TardisHttpClient: Requests instrument definition metadata from the Tardis HTTP API, parsing it into Nautilus instrument definitions. TardisDataClient: Provides a live data client for subscribing to data streams from a Tardis Machine WebSocket server. TardisInstrumentProvider: Provides instrument definitions from Tardis through the HTTP instrument metadata API. Data pipeline functions: Enables replay of historical data from Tardis Machine and writes it to the Nautilus Parquet format, including direct catalog integration for streamlined data management (see below). infoA Tardis API key is required for the adapter to operate correctly. See also environment variables. Overview This adapter is implemented in Rust, with optional Python bindings for ease of use in Python-based workflows. It does not require any external Tardis client library dependencies. infoThere is no need for additional installation steps for tardis. The core components of the adapter are compiled as static libraries and automatically linked during the build process. Tardis documentation Tardis provides extensive user documentation. We recommend also referring to the Tardis documentation in conjunction with this NautilusTrader integration guide. Supported formats Tardis provides normalized market data—a unified format consistent across all supported exchanges. This normalization is highly valuable because it allows a single parser to handle data from any Tardis-supported exchange, reducing development time and complexity. As a result, NautilusTrader will not support exchange-native market data formats, as it would be inefficient to implement separate parsers for each exchange at this stage. The following normalized Tardis formats are supported by NautilusTrader: Tardis formatNautilus data typebook_changeOrderBookDeltabook_snapshot_*OrderBookDepth10quoteQuoteTickquote_10sQuoteTicktradeTradetrade_bar_BarinstrumentCurrencyPair, CryptoFuture, CryptoPerpetual, OptionContractderivative_tickerNot yet supporteddisconnectNot applicable Notes: quote is an alias for book_snapshot_1_0ms. quote_10s is an alias for book_snapshot_1_10s. Both quote, quote_10s, and one-level snapshots are parsed as QuoteTick. infoSee also the Tardis normalized market data APIs. Bars The adapter will automatically convert Tardis trade bar interval and suffix to Nautilus BarTypes. This includes the following: Tardis suffixNautilus bar aggregationms - millisecondsMILLISECONDs - secondsSECONDm - minutesMINUTEticks - number of ticksTICKvol - volume sizeVOLUME Symbology and normalization The Tardis integration ensures seamless compatibility with NautilusTrader’s crypto exchange adapters by consistently normalizing symbols. Typically, NautilusTrader uses the native exchange naming conventions provided by Tardis. However, for certain exchanges, raw symbols are adjusted to adhere to the Nautilus symbology normalization, as outlined below: Common rules All symbols are converted to uppercase. Market type suffixes are appended with a hyphen for some exchanges (see exchange-specific normalizations). Original exchange symbols are preserved in the Nautilus instrument definitions raw_symbol field. Exchange-specific normalizations Binance: Nautilus appends the suffix -PERP to all perpetual symbols. Bybit: Nautilus uses specific product category suffixes, including -SPOT, -LINEAR, -INVERSE, -OPTION. dYdX: Nautilus appends the suffix -PERP to all perpetual symbols. Gate.io: Nautilus appends the suffix -PERP to all perpetual symbols. For detailed symbology documentation per exchange: Binance symbology Bybit symbology dYdX symbology Venues Some exchanges on Tardis are partitioned into multiple venues. The table below outlines the mappings between Nautilus venues and corresponding Tardis exchanges, as well as the exchanges that Tardis supports: Nautilus venueTardis exchange(s)ASCENDEXascendexBINANCEbinance, binance-dex, binance-european-options, binance-futures, binance-jersey, binance-optionsBINANCE_DELIVERYbinance-delivery (COIN-margined contracts)BINANCE_USbinance-usBITFINEXbitfinex, bitfinex-derivativesBITFLYERbitflyerBITGETbitget, bitget-futuresBITMEXbitmexBITNOMIALbitnomialBITSTAMPbitstampBLOCKCHAIN_COMblockchain-comBYBITbybit, bybit-options, bybit-spotCOINBASEcoinbaseCOINBASE_INTXcoinbase-internationalCOINFLEXcoinflex (for historical research)CRYPTO_COMcrypto-com, crypto-com-derivativesCRYPTOFACILITIEScryptofacilitiesDELTAdeltaDERIBITderibitDYDXdydxDYDX_V4dydx-v4FTXftx, ftx-us (historical research)GATE_IOgate-io, gate-io-futuresGEMINIgeminiHITBTChitbtcHUOBIhuobi, huobi-dm, huobi-dm-linear-swap, huobi-dm-optionsHUOBI_DELIVERYhuobi-dm-swapHYPERLIQUIDhyperliquidKRAKENkrakenKUCOINkucoin, kucoin-futuresMANGOmangoOKCOINokcoinOKEXokex, okex-futures, okex-options, okex-spreads, okex-swapPHEMEXphemexPOLONIEXpoloniexSERUMserum (historical research)STAR_ATLASstar-atlasUPBITupbitWOO_Xwoo-x Environment variables The following environment variables are used by Tardis and NautilusTrader. TM_API_KEY: API key for the Tardis Machine. TARDIS_API_KEY: API key for NautilusTrader Tardis clients. TARDIS_MACHINE_WS_URL (optional): WebSocket URL for the TardisMachineClient in NautilusTrader. TARDIS_BASE_URL (optional): Base URL for the TardisHttpClient in NautilusTrader. NAUTILUS_PATH (optional): Parent directory containing the catalog/ subdirectory for writing replay data in the Nautilus catalog format. Running Tardis Machine historical replays The Tardis Machine Server is a locally runnable server with built-in data caching, providing both tick-level historical and consolidated real-time cryptocurrency market data through HTTP and WebSocket APIs. You can perform complete Tardis Machine WebSocket replays of historical data and output the results in Nautilus Parquet format, using either Python or Rust. Since the function is implemented in Rust, performance is consistent whether run from Python or Rust, letting you choose based on your preferred workflow. The end-to-end run_tardis_machine_replay data pipeline function utilizes a specified configuration to execute the following steps: Connect to the Tardis Machine server. Request and parse all necessary instrument definitions from the Tardis instruments metadata HTTP API. Stream all requested instruments and data types for the specified time ranges from the Tardis Machine server. For each instrument, data type and date (UTC), generate a .parquet file in the catalog-compatible format. Disconnect from the Tardis Machine server, and terminate the program. File Naming Convention Files are written one per day, per instrument, using ISO 8601 timestamp ranges that clearly indicate the exact time span of data: Format: {start_timestamp}{end_timestamp}.parquet Example: 2023-10-01T00-00-00-000000000Z_2023-10-01T23-59-59-999999999Z.parquet Structure: data/{data_type}/{instrument_id}/{filename} This format is fully compatible with the Nautilus data catalog, enabling seamless querying, consolidation, and data management operations. noteYou can request data for the first day of each month without an API key. For all other dates, a Tardis Machine API key is required. This process is optimized for direct output to a Nautilus Parquet data catalog. Ensure that the NAUTILUS_PATH environment variable is set to the parent directory containing the catalog/ subdirectory. Parquet files will then be organized under
TardisCSVDataLoader
Pattern 6: Rust Style Guide The Rust programming language is an ideal fit for implementing the mission-critical core of the platform and systems. Its strong type system, ownership model, and compile-time checks eliminate memory errors and data races by construction, while zero-cost abstractions and the absence of a garbage collector deliver C-like performance—critical for high-frequency trading workloads. Cargo manifest conventions In [dependencies], list internal crates (nautilus-) first in alphabetical order, insert a blank line, then external required dependencies alphabetically, followed by another blank line and the optional dependencies (those with optional = true) in alphabetical order. Preserve inline comments with their dependency. Add "python" to every extension-module feature list that builds a Python artefact, keeping it adjacent to "pyo3/extension-module" so the full Python stack is obvious. When a manifest groups adapters separately (for example crates/pyo3), keep the # Adapters block immediately below the internal crate list so downstream consumers can scan adapter coverage quickly. Always include a blank line before [dev-dependencies] and [build-dependencies] sections. Apply the same layout across related manifests when the feature or dependency sets change to avoid drift between crates. Use snake_case filenames for bin/ sources (for example bin/ws_data.rs) and reflect those paths in each [[bin]] section. Keep [[bin]] name entries in kebab-case (for example name = "hyperliquid-ws-data") so the compiled binaries retain their intended CLI names. Versioning guidance Use workspace inheritance for shared dependencies (for example serde = { workspace = true }). Only pin versions directly for crate-specific dependencies that are not part of the workspace. Group workspace-provided dependencies before crate-only dependencies so the inheritance is easy to audit. Feature flag conventions Prefer additive feature flags—enabling a feature must not break existing functionality. Use descriptive flag names that explain what capability is enabled. Document every feature in the crate-level documentation so consumers know what they toggle. Common patterns: high-precision: switches the value-type backing (64-bit or 128-bit integers) to support domains that require extra precision. default = []: keep defaults minimal. python: enables Python bindings. extension-module: builds a Python extension module (always include python). ffi: enables C FFI bindings. stubs: exposes testing stubs. Module organization Keep modules focused on a single responsibility. Use mod.rs as the module root when defining submodules. Prefer relatively flat hierarchies over deep nesting to keep paths manageable. Re-export commonly used items from the crate root for convenience. Code style and conventions File header requirements All Rust files must include the standardized copyright header: // -------------------------------------------------------------------------------------------------// Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.// https://nautechsystems.io//// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");// You may not use this file except in compliance with the License.// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// ------------------------------------------------------------------------------------------------- Code formatting Import formatting is automatically handled by rustfmt when running make format. The tool organizes imports into groups (standard library, external crates, local imports) and sorts them alphabetically within each group. Within this section, follow these spacing rules: Leave one blank line between functions (including tests) – this improves readability and mirrors the default behavior of rustfmt. Leave one blank line above every doc comment (/// or //!) so that the comment is clearly detached from the previous code block. String formatting Prefer inline format strings over positional arguments: // Preferred - inline format with variable namesanyhow::bail!("Failed to subtract {n} months from {datetime}");// Instead of - positional argumentsanyhow::bail!("Failed to subtract {} months from {}", n, datetime); This makes messages more readable and self-documenting, especially when there are multiple variables. Type qualification Follow these conventions for qualifying types in code: anyhow: Always fully qualify anyhow macros (anyhow::bail!, anyhow::anyhow!) and the Result type (anyhow::ResultSymbol] instance with correctness checking.////// # Errors////// Returns an error if value is not a valid string.////// # Notes////// PyO3 requires a Result type for proper error handling and stacktrace printing in Python.pub fn new_checked<T: AsRefSymbol] instance.////// # Panics////// Panics if value is not a valid string.pub fn new<T: AsRefffi: Enables the C foreign function interface (FFI) from cbindgen.//! - python: Enables Python bindings from PyO3.//! - extension-module: Builds as a Python extension module (used with python).//! - stubs: Enables type stubs for use in testing scenarios. Field documentation All struct and enum fields must have documentation with terminating periods: pub struct Currency { /// The currency code as an alpha-3 string (e.g., "USD", "EUR"). pub code: Ustr, /// The currency decimal precision. pub precision: u8, /// The ISO 4217 currency code. pub iso4217: u16, /// The full name of the currency. pub name: Ustr, /// The currency type, indicating its category (e.g. Fiat, Crypto). pub currency_type: CurrencyType,} Function documentation Document all public functions with: Purpose and behavior Explanation of input argument usage Error conditions (if applicable) Panic conditions (if applicable) /// Returns a reference to the AccountBalance for the specified currency, or None if absent.////// # Panics////// Panics if currency is None and self.base_currency is None.pub fn base_balance(&self, currency: OptionAccountBalance for the specified currency, or None if absent.////// # Errors////// Returns an error if the currency conversion fails.////// # Panics////// Panics if currency is None and self.base_currency is None.pub fn base_balance(&self, currency: Option
[dependencies]
Pattern 7: Primary Pattern: Use anyhow::Result
anyhow::Result<T>
Pattern 8: Example of problematic pattern:
// AVOID: This creates reference cyclesstruct CallbackHolder { handler: Option<Arc<PyObject>>, // ❌ Arc wrapper causes cycles}
Example Code Patterns
Example 1 (bash):
pip install -U nautilus_trader
Example 2 (bash):
pip install -U "nautilus_trader[docker,ib]"
Example 3 (python):
InstrumentId.from_str("BTC-USDT.OKX") # For USDT-quoted spotInstrumentId.from_str("BTC-USDC.OKX") # For USDC-quoted spot
Example 4 (python):
use_hyphens_in_client_order_ids=False
Example 5 (bash):
docker run -p 8000:8000 -p 8001:8001 -e "TM_API_KEY=YOUR_API_KEY" -d tardisdev/tardis-machine
Reference Files
This skill includes comprehensive documentation in references/:
- api.md - Api documentation
- backtesting.md - Backtesting documentation
- concepts.md - Concepts documentation
- data.md - Data documentation
- getting_started.md - Getting Started documentation
- other.md - Other documentation
- strategies.md - Strategies documentation
Use view to read specific reference files when detailed information is needed.
Working with This Skill
For Beginners
Start with the getting_started or tutorials reference files for foundational concepts.
For Specific Features
Use the appropriate category reference file (api, guides, etc.) for detailed information.
For Code Examples
The quick reference section above contains common patterns extracted from the official docs.
Resources
references/
Organized documentation extracted from official sources. These files contain:
- Detailed explanations
- Code examples with language annotations
- Links to original documentation
- Table of contents for quick navigation
scripts/
Add helper scripts here for common automation tasks.
assets/
Add templates, boilerplate, or example projects here.
Notes
- This skill was automatically generated from official documentation
- Reference files preserve the structure and examples from source docs
- Code examples include language detection for better syntax highlighting
- Quick reference patterns are extracted from common usage examples in the docs
Updating
To refresh this skill with updated documentation:
- Re-run the scraper with the same configuration
- The skill will be rebuilt with the latest information