How Trading Instruments Define Identity, Precision, and Venue Constraints
Summary
The document explains how a trading system represents instruments across spot assets, futures, options, swaps, CFDs, betting markets, and synthetic instruments. Each instrument has a unique symbol-and-venue identity, while its definition carries details such as price and size precision, increments, contract multipliers, currencies, limits, margin rates, timestamps, and venue metadata. It also describes how Rust and Python interfaces share this model and how instrument definitions are loaded and retrieved from a central cache.
For order handling, the stated precision controls the number of decimal places accepted, while increments describe valid step sizes. Factory methods can round values to the declared precision, but they do not guarantee that a value is a multiple of the venue’s increment; the risk engine also does not perform this rounding or increment validation. Definitions must match market data and order semantics to avoid invalid orders or incorrect notional and PnL calculations. Instruments do not contain maker or taker fees; commissions are handled separately through fee models or venue fills.
Key ideas
- An instrument definition links a venue-specific identity to contract and trading constraints.
- Price and size precision set decimal-place limits, while increments specify valid trading steps.
- Rounding to the declared precision does not ensure a value matches the venue’s increment.
- Instrument definitions must match market and order semantics to support valid orders and accounting.
- Commission rates are handled separately from instrument definitions.
Tags
Full text
# Instruments
# Instruments
An instrument represents the specification for a tradable asset, contract, or local
synthetic market. Market data, orders, positions, accounting, portfolio calculations,
and adapter symbology all refer back to an `InstrumentId` and its instrument definition.
NautilusTrader exposes the same instrument model to Rust and Python users. Rust
examples use `nautilus_model`; Python examples use `nautilus_trader.model`.
## Instrument types
| Instrument type | `InstrumentClass` | Description | Typical adapters |
| ------------------------------------------------- | ----------------- | ---------------------------------------------------- | ------------------------------- |
| [`Equity`](equity.md) | `SPOT` | Listed share or ETF traded on a cash market. | Databento, Interactive Brokers. |
| [`CurrencyPair`](currency_pair.md) | `SPOT` | Fiat FX or crypto spot pair in base/quote form. | Binance, Kraken, OKX, Tardis. |
| [`Commodity`](commodity.md) | `SPOT` | Spot commodity such as gold or oil. | Interactive Brokers. |
| [`Cfd`](cfd.md) | `CFD` | Contract for difference tracking an underlying. | Interactive Brokers. |
| [`IndexInstrument`](index_instrument.md) | `SPOT` | Reference index, not directly tradable. | Interactive Brokers. |
| [`TokenizedAsset`](tokenized_asset.md) | `SPOT` | Tokenized asset on a crypto venue. | Kraken. |
| [`FuturesContract`](futures_contract.md) | `FUTURE` | Dated futures contract. | Databento, Interactive Brokers. |
| [`FuturesSpread`](futures_spread.md) | `FUTURES_SPREAD` | Exchange defined futures strategy with several legs. | Databento, Interactive Brokers. |
| [`CryptoFuture`](crypto_future.md) | `FUTURE` | Dated crypto futures contract. | Bybit, Deribit, OKX. |
| [`CryptoFuturesSpread`](crypto_futures_spread.md) | `FUTURES_SPREAD` | Exchange defined crypto futures spread. | Deribit, OKX. |
| [`CryptoPerpetual`](crypto_perpetual.md) | `SWAP` | Crypto perpetual futures contract. | Binance, Bybit, dYdX. |
| [`PerpetualContract`](perpetual_contract.md) | `SWAP` | Perpetual futures contract across asset classes. | Architect AX, Binance. |
| [`OptionContract`](option_contract.md) | `OPTION` | Exchange traded put or call option. | Databento, Interactive Brokers. |
| [`OptionSpread`](option_spread.md) | `OPTION_SPREAD` | Exchange defined options strategy with several legs. | Databento, Interactive Brokers. |
| [`CryptoOption`](crypto_option.md) | `OPTION` | Option on a crypto underlying. | Bybit, Deribit, OKX, Tardis. |
| [`CryptoOptionSpread`](crypto_option_spread.md) | `OPTION_SPREAD` | Exchange defined crypto option spread. | Deribit, OKX. |
| [`BinaryOption`](binary_option.md) | `BINARY_OPTION` | Binary instrument that settles to 0 or 1. | Hyperliquid, OKX, Polymarket. |
| [`BettingInstrument`](betting_instrument.md) | `SPORTS_BETTING` | Sports or gaming market selection. | Betfair. |
| [`SyntheticInstrument`](synthetic_instrument.md) | n/a | Formula derived local instrument. | Local only. |
## Taxonomy
NautilusTrader groups instruments by the market structure they represent:
```mermaid
flowchart TD
I[Instrument Types]
I --> Spot
I --> Derivatives
I --> Other
Spot --> Equity
Spot --> CurrencyPair
Spot --> Commodity
Spot --> IndexInstrument
Spot --> TokenizedAsset
Derivatives --> Futures
Derivatives --> Options
Derivatives --> Swaps
Derivatives --> Cfd
Futures --> FuturesContract
Futures --> FuturesSpread
Futures --> CryptoFuture
Futures --> CryptoFuturesSpread
Options --> OptionContract
Options --> OptionSpread
Options --> CryptoOption
Options --> CryptoOptionSpread
Options --> BinaryOption
Swaps --> CryptoPerpetual
Swaps --> PerpetualContract
Other --> BettingInstrument
Other --> SyntheticInstrument
```
## Common fields
Most concrete instruments share the same core shape. Individual type pages list the
complete constructor and struct fields for that type.
| Field | Meaning |
| ----------------- | ------------------------------------------------------------------- |
| `id` | Nautilus `InstrumentId`, formed from a symbol and venue. |
| `raw_symbol` | Native venue symbol before Nautilus normalization. |
| `price_precision` | Configured number of decimal places for price values. |
| `size_precision` | Configured number of decimal places for quantity values. |
| `price_increment` | Smallest valid price step. |
| `size_increment` | Smallest valid quantity step. |
| `multiplier` | Contract multiplier used in notional and PnL calculations. |
| `lot_size` | Rounded lot or board size when the venue publishes one. |
| `margin_init` | Initial margin rate as a decimal fraction of notional value. |
| `margin_maint` | Maintenance margin rate as a decimal fraction of notional value. |
| `max_quantity` | Maximum order quantity when known. |
| `min_quantity` | Minimum order quantity when known. |
| `max_notional` | Maximum order notional value when known. |
| `min_notional` | Minimum order notional value when known. |
| `max_price` | Maximum valid quote or order price when known. |
| `min_price` | Minimum valid quote or order price when known. |
| `tick_scheme` | Registered tick scheme name used for price navigation. |
| `info` | Adapter metadata preserved from the venue or data source. |
| `ts_event` | UNIX nanosecond timestamp for when the definition event occurred. |
| `ts_init` | UNIX nanosecond timestamp for when Nautilus initialized the object. |
## Tick schemes
A named tick scheme defines the price grid used by an instrument's `next_bid_price`
and `next_ask_price` methods. Use `FixedTickScheme` for a constant increment or
`TieredTickScheme` when the increment changes across price ranges.
Register a scheme before passing its name as an instrument's `tick_scheme` constructor
argument. Register custom schemes before loading saved instrument definitions from a catalog:
saved definitions contain the scheme name, and loading validates that name against the current
process's registry.
When migrating from v1, `FixedTickScheme` no longer accepts `min_tick` or `max_tick` and has no
`min_price` or `max_price` attributes. Fixed schemes use the representable `Price` range, including
negative prices; the built-in Forex schemes no longer impose their v1 price bounds.
The `increment` argument requires a `Price`; replace float values with `Price.from_str("0.05")`.
Fixed tick increments use `Price` to preserve their exact value. Navigation returns `None` if the
instrument's price precision cannot represent the increment exactly.
```rust tab="Rust"
use nautilus_model::{
instruments::{FixedTickScheme, TickScheme, TickSchemeRule, get_tick_scheme, register_tick_scheme},
types::Price,
};
let fixed = FixedTickScheme::new(Price::from("0.05")).unwrap();
register_tick_scheme("FIVE_CENT", TickScheme::Fixed(fixed)).unwrap();
let scheme = get_tick_scheme("five_cent").unwrap();
assert_eq!(scheme.next_bid_price(1.13, 0, 2), Some(Price::from("1.10")));
assert_eq!(scheme.next_ask_price(1.13, 0, 2), Some(Price::from("1.15")));
```
```python tab="Python"
from nautilus_trader.model import FixedTickScheme, Price, get_tick_scheme, register_tick_scheme
register_tick_scheme(
FixedTickScheme("FIVE_CENT", price_precision=2, increment=Price.from_str("0.05"))
)
scheme = get_tick_scheme("five_cent")
assert str(scheme.next_bid_price(1.13)) == "1.10"
assert str(scheme.next_ask_price(1.13)) == "1.15"
```
Tier definitions use `(start, stop, step)` with an inclusive start and exclusive stop.
Each tier expands to at most `max_ticks_per_tier` prices, including tiers with an infinite stop.
The Python default is 100. A finite tier that exceeds the cap is truncated without an error;
choose a cap large enough to include every required price.
```rust tab="Rust"
use nautilus_model::instruments::{TickScheme, TieredTickScheme, register_tick_scheme};
let tiers = [(0.05, 10.00, 0.05), (10.00, f64::INFINITY, 0.25)];
let scheme = TieredTickScheme::new(&tiers, 2, 1000).unwrap();
register_tick_scheme("OPTION_GRID", TickScheme::Tiered(scheme)).unwrap();
```
```python tab="Python"
from nautilus_trader.model import TieredTickScheme, register_tick_scheme
register_tick_scheme(
TieredTickScheme(
"OPTION_GRID",
tiers=[(0.05, 10.00, 0.05), (10.00, float("inf"), 0.25)],
price_precision=2,
max_ticks_per_tier=1000,
),
)
```
Names are ASCII, trimmed, and case-insensitive. Registration lasts for the process lifetime;
a registered name cannot be replaced or removed. Built-in names such as `BETFAIR` and
`TOPIX100` are protected, as are aliases such as `FIXED_PRECISION_01` for `FIXED_PRECISION_1`.
`list_tick_schemes()` returns canonical built-in and registered names in sorted order.
Rust registration and construction return `TickSchemeError` on invalid input or duplicates;
lookup returns `None` for an unknown name. Python uses `ValueError` for these failures and
`TypeError` when registration receives an unsupported object.
Python callers that catch v1's `KeyError` for unknown or duplicate names must catch `ValueError` instead.
## Symbology
Every instrument has a unique `InstrumentId` made from a Nautilus symbol and venue,
separated by a period. The separate `raw_symbol` field preserves the venue's native
symbol. For example, Binance Futures represents the Ethereum perpetual contract as:
```text
ETHUSDT-PERP.BINANCE
```
Native symbols should be unique for a venue, but this is not guaranteed by every
exchange. The Nautilus `{symbol}.{venue}` pair must be unique inside a system.
:::warning
The instrument definition must match the market data and venue order semantics. An
incorrect instrument can truncate prices or quantities, calculate notional values with
the wrong currency, or make a backtest accept prices a live venue would reject.
:::
## Rust and Python surfaces
Rust users work with the `nautilus_model` instrument structs and `InstrumentAny`:
```rust
use nautilus_model::instruments::{CurrencyPair, InstrumentAny};
```
Python users normally work with instrument classes from `nautilus_trader.model`:
```python
from nautilus_trader.model import CurrencyPair
```
Both surfaces represent the same instrument contract: identity, precision, increments,
currencies, limits, margins, fees, metadata, and timestamps.
## Loading instruments
Generic test instruments can be instantiated through the `TestInstrumentProvider`:
```python
from nautilus_trader.testkit.providers import TestInstrumentProvider
audusd = TestInstrumentProvider.default_fx_ccy("AUD/USD")
```
Live integration adapters expose `InstrumentProvider` objects that cache instrument
definitions. Use `InstrumentProviderConfig(load_all=True)` where the integration
supports it, or `load_ids` to load a known set of instruments. Order submission requires
the matching instrument definition to exist in the central cache.
## Finding instruments
Strategies and actors retrieve instruments from the central cache:
```rust tab="Rust"
use nautilus_model::identifiers::InstrumentId;
let instrument_id = InstrumentId::from("ETHUSDT-PERP.BINANCE");
let instrument = cache.instrument(&instrument_id);
```
```python tab="Python"
from nautilus_trader.model import InstrumentId
instrument_id = InstrumentId.from_str("ETHUSDT-PERP.BINANCE")
instrument = self.cache.instrument(instrument_id)
```
It is also possible to subscribe to one instrument or all instruments for a venue:
```python
self.subscribe_instrument(instrument_id)
self.subscribe_instruments(venue)
```
When the `DataEngine` receives an instrument update, it passes the object to the
`on_instrument()` handler.
## Precision
For order validation, `price_precision` and `size_precision` set the maximum number of
decimal places that the `RiskEngine` accepts. `price_increment` and `size_increment`
record the corresponding minimum steps.
| Field | Constrains | Example |
| ----------------- | ------------------------------------ | ----------------- |
| `price_precision` | Order prices, trigger prices, fills. | `2` -> `50000.01` |
| `size_precision` | Order quantities and fill sizes. | `5` -> `1.00001` |
The price increment precision must match `price_precision`, and the size increment
precision must match `size_precision`. For example, `price_precision=2` pairs with
`price_increment=Price(0.01, 2)`.
Use the instrument factory methods to round values to the configured precision:
```python
instrument = self.cache.instrument(instrument_id)
price = instrument.make_price(0.90500)
quantity = instrument.make_qty(150)
```
These methods round to the corresponding increment precision, which instrument
construction requires to match the declared precision. They do not ensure that the
result is a multiple of an increment such as `0.25`.
:::warning
The `RiskEngine` does not round values automatically. If you create a `Price` with
5 decimal places for an instrument that supports 2, the order is denied. Use
`instrument.make_price()` and `instrument.make_qty()` to round explicitly. The
`RiskEngine` also does not validate increment multiples, so ensure that prices and
quantities match the venue steps before submission.
:::
## Limits, margins, and fees
Venue and adapter definitions can include optional limits:
- `max_quantity` and `min_quantity`.
- `max_notional` and `min_notional`.
- `max_price` and `min_price`.
Margin models use `margin_init` and `margin_maint` to calculate initial and maintenance
margin. Instruments do not carry maker or taker fee rates. Backtest and sandbox
commission uses a [fee model](../behavioral_models.md). Live commissions come from
venue fills. Fee models use one rate convention:
- Positive fee rates represent commissions.
- Negative fee rates represent rebates.
For deeper accounting behavior, see [Accounting](../accounting.md).
## Metadata
The `info` field preserves raw or adapter-specific metadata as a JSON-serializable
dictionary. Use it when the venue publishes useful details that do not belong in the
unified Nautilus instrument API.
## Related guides
- [Data](../data/) covers market data types that reference instruments.
- [Orders](../orders/) covers order fields that reference instruments.
- [Synthetics](../synthetics.md) covers local formula-derived instruments.
- [Python API Reference](/docs/python-api-latest/model/instruments.html) lists Python
constructors and members.Shown in full with attribution under the source's licence. Licence: LGPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.