docs/concepts/positions.md
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.
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.
The system opens a position on the first fill:
position_id (multiple positions per instrument).A position tracks:
LONG or SHORT).:::tip
You 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.
:::
As additional fills occur, the position:
A position closes when the net quantity becomes zero (FLAT). At closure:
NETTING OMS, when the position later reopens, the engine snapshots the closed state to preserve historical PnL (see Position snapshotting).Positions aggregate order fills to maintain an accurate view of market exposure. The aggregation process handles both sides of trading activity:
When a BUY order fills:
When a SELL order fills:
The position maintains a signed_qty field representing the net exposure:
LONG positions.SHORT positions.FLAT (closed) position.# Example: Position aggregation
# Initial BUY 100 units at $50
signed_qty = +100 # LONG position
# Subsequent SELL 150 units at $55
signed_qty = -50 # Now SHORT position
# Final BUY 50 units at $52
signed_qty = 0 # Position FLAT (closed)
Position adjustments track quantity or PnL changes that occur outside of normal order fills,
ensuring the position quantity accurately reflects the true net asset position. The system
generates PositionAdjusted events for these scenarios.
When trading spot currency pairs (e.g., BTC/USDT) or FX spot, commissions paid in the base currency directly affect the net quantity received or delivered:
signed_qty because it affects actual inventory.
Selling a 0.999 BTC LONG position with 0.000999 BTC commission leaves you SHORT 0.000999 BTC,
not FLAT, because you gave up 0.999999 BTC total.:::note
Base currency commissions only apply to spot currency pairs and FX spot instruments where the
commission currency matches instrument.base_currency. For other instruments, commissions are
tracked separately and do not affect position quantity.
:::
Funding adjustments track periodic payments for perpetual futures without affecting position
quantity. These are logged with quantity_change = None and can include PnL impacts.
All adjustments are preserved in the position event history:
position.adjustments returns the list of all PositionAdjusted events.COMMISSION or FUNDING), quantity change, and timestamps.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 full details, see the Execution guide.
NETTINGIn NETTING mode, all fills for an instrument are aggregated into a single position:
LONG to SHORT (or vice versa) as net quantity changes.HEDGINGIn HEDGING mode, multiple positions can exist for the same instrument:
LONG and SHORT positions.:::warning
When 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.
:::
The platform allows different OMS configurations for strategies and venues:
| Strategy OMS | Venue OMS | Behavior |
|---|---|---|
NETTING | NETTING | Single position per instrument at both strategy and venue. |
HEDGING | HEDGING | Multiple positions supported at both levels. |
NETTING | HEDGING | Venue tracks multiple, Nautilus maintains single position. |
HEDGING | NETTING | Venue tracks single, Nautilus maintains virtual positions. |
:::tip For 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 is an important feature for NETTING OMS configurations that preserves
the state of closed positions for accurate PnL tracking and reporting.
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.
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:
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.
:::note
This 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.
:::
# NETTING OMS Example
# Cycle 1: Open LONG position
BUY 100 units at $50 # Position opens
SELL 100 units at $55 # Position closes, PnL = $500
# Snapshot taken preserving $500 realized PnL
# Cycle 2: Open SHORT position
SELL 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.
NautilusTrader provides PnL calculations that account for instrument specifications and market conventions.
Calculated when positions are partially or fully closed:
# For standard instruments
realized_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.
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 price
position.unrealized_pnl(bid_price) # Conservative for LONG positions
position.unrealized_pnl(ask_price) # Conservative for SHORT positions
Returns Money(0, settlement_currency) for FLAT positions regardless of the price provided.
Combines realized and unrealized components:
total_pnl = position.total_pnl(current_price)
# Returns realized_pnl + unrealized_pnl
Positions track all trading costs:
commissions = position.commissions()
# Returns list[Money] with aggregated commission totals per currency
notional = position.notional_value(current_price)
# Returns Money in quote currency (standard) or base currency (inverse)
Limitations:
base_currency set.instrument.calculate_notional_value() instead.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.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.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.multiplier: Contract multiplier.price_precision: Decimal precision for prices.size_precision: Decimal precision for quantities.is_inverse: Whether instrument is inverse.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.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.:::info For complete type information and detailed property documentation, see the Position API Reference. :::
Positions maintain a complete history of events:
This historical data enables:
:::tip
Use 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.
:::
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.
The platform uses f64 for position calculations to balance performance and accuracy:
Testing confirms f64 arithmetic maintains accuracy for typical trading scenarios:
For implementation details, see test_position_pnl_precision_* tests in crates/model/src/position.rs.
:::note
For 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.
This does not affect realistic trading scenarios with standard currency precisions (typically 2-9 decimals).
:::
Positions interact with several key components:
:::note Positions 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. :::
Positions are central to tracking trading activity and performance. Understanding how positions
aggregate fills, calculate PnL, and handle different OMS configurations matters when building
trading strategies. Position snapshotting provides accurate historical tracking in NETTING
mode, and the event history supports detailed analysis and reconciliation.