NautilusTrader
Concepts

Synthetics

Synthetic instruments are locally defined instruments whose prices derive from other instruments. They can combine components from one venue or many venues and expose the result as a standard Nautilus instrument with the synthetic venue code SYNTH.

Synthetic instruments are useful for:

  • Enabling Actor and Strategy components to subscribe to quote or trade feeds.
  • Triggering emulated orders from derived prices.
  • Constructing bars from synthetic quotes or trades.

Synthetic instruments cannot be traded directly. They exist locally within the platform and serve as analytical tools. In the future, Nautilus may support trading component instruments based on synthetic instrument behavior.

Formula language

Each synthetic instrument defines a derivation formula. Nautilus evaluates this formula with its built-in numeric expression engine and converts the final numeric result to the synthetic Price.

Supported syntax

Formulas can reference component InstrumentId values directly, including IDs that contain / and -.

ConstructExampleNotes
Component referenceBTCUSDT.BINANCEUse the raw InstrumentId text.
Component referenceAUD/USD.SIMIDs containing / are valid.
Component referenceETH-USDT-SWAP.OKXIDs containing - are valid.
Numeric literal1, 0.5, 1.2e-3Evaluated with f64 semantics.
Boolean literaltrue, falseUsed in conditions and logical expressions.
Parentheses(a + b) / 2Use parentheses to override precedence.
Unary operators-x, !flagUnary - negates numbers. Unary ! negates booleans.
Binary operators+ - * / % ^, == !=, < <= > >=, && ||Arithmetic is numeric. Logical operators are boolean.
Local assignmentspread = a - b; spread / 2Statements run from left to right. The formula must end with a value.
Comments// line, /* block */Comments are ignored.

New formulas should use raw InstrumentId values. For backward compatibility, formulas that replace - with _ in component IDs remain accepted.

Operator precedence

The expression engine evaluates operators in the following order, from highest precedence to lowest precedence:

LevelOperatorsNotes
Highest^Exponentiation. Right associative.
Unary -, unary !-2 ^ 2 evaluates as -(2 ^ 2).
*, /, %Multiplication, division, and modulo.
+, -Addition and subtraction.
<, <=, >, >=Numeric comparisons.
==, !=Equality and inequality. Both sides must have the same type.
Lowest&&, ||Boolean operators.

Assignments are not expression operators. Separate statements with ;, and make the last statement the value you want the synthetic to produce.

Built-in functions

FunctionSignatureNotes
absabs(x)Absolute value.
ceilceil(x)Ceiling.
floorfloor(x)Floor.
roundround(x)Round to the nearest integer using Rust f64 rules.
minmin(x1, x2, ...)Accepts one or more numeric arguments.
maxmax(x1, x2, ...)Accepts one or more numeric arguments.
ifif(condition, when_true, when_false)The condition must be boolean. Both branches match. Only the selected branch evaluates.

Type rules

  • Component inputs are numeric.
  • Arithmetic operators require numeric operands and return numeric results.
  • <, <=, >, >= require numeric operands and return boolean results.
  • == and != accept any matching type (both numeric or both boolean) and return boolean results.
  • &&, ||, and unary ! require boolean operands.
  • && and || short-circuit. The right-hand side evaluates only when needed.
  • Local variables must be assigned before use.
  • Local variable names must start with a letter or _ and then use letters, digits, or _.
  • The final formula result must be numeric. A formula that ends with an assignment or produces a boolean result is invalid for a synthetic instrument.

Limits

The expression engine enforces the following compile-time limits. Formulas that exceed them produce a clear error at construction time.

LimitValueDescription
Stack depth32Maximum number of intermediate values on the evaluation stack.
Local variables16Maximum number of distinct local variable names.

These limits are generous for any realistic pricing formula. A weighted sum of 8 components uses a peak stack depth of 3 and zero locals.

Examples

# Simple spread
formula = "BTCUSDT.BINANCE - ETHUSDT.BINANCE"

# Average of two FX pairs
formula = "(AUD/USD.SIM + NZD/USD.SIM) / 2"

# Reuse an intermediate value
formula = "spread = BTCUSDT.BINANCE - ETHUSDT.BINANCE; spread / 2"

# Conditional output
formula = "if(BTCUSDT.BINANCE > ETHUSDT.BINANCE, BTCUSDT.BINANCE, ETHUSDT.BINANCE)"

Creating a synthetic instrument

Before defining a new synthetic instrument, make sure all component instruments already exist in the cache.

The following example creates a synthetic instrument with an actor or strategy. This synthetic represents a simple spread between Bitcoin and Ethereum spot prices on Binance. It assumes that BTCUSDT.BINANCE and ETHUSDT.BINANCE already exist in the cache.

from nautilus_trader.model.instruments import SyntheticInstrument

btcusdt_binance_id = InstrumentId.from_str("BTCUSDT.BINANCE")
ethusdt_binance_id = InstrumentId.from_str("ETHUSDT.BINANCE")

synthetic = SyntheticInstrument(
    symbol=Symbol("BTC-ETH:BINANCE"),
    price_precision=8,
    components=[
        btcusdt_binance_id,
        ethusdt_binance_id,
    ],
    formula=f"{btcusdt_binance_id} - {ethusdt_binance_id}",
    ts_event=self.clock.timestamp_ns(),
    ts_init=self.clock.timestamp_ns(),
)

self._synthetic_id = synthetic.id
self.add_synthetic(synthetic)
self.subscribe_quote_ticks(self._synthetic_id)

The synthetic instrument_id in the example above is {symbol}.SYNTH, which produces BTC-ETH:BINANCE.SYNTH.

Updating formulas

You can update a synthetic formula at any time.

synthetic = self.cache.synthetic(self._synthetic_id)

new_formula = "(BTCUSDT.BINANCE + ETHUSDT.BINANCE) / 2"
synthetic.change_formula(new_formula)

self.update_synthetic(synthetic)

Trigger instrument IDs

You can trigger emulated orders from synthetic prices. In the following example, a synthetic instrument releases an emulated order once the synthetic price reaches the trigger condition.

order = self.strategy.order_factory.limit(
    instrument_id=ETHUSDT_BINANCE.id,
    order_side=OrderSide.BUY,
    quantity=Quantity.from_str("1.5"),
    price=Price.from_str("30000.00000000"),
    emulation_trigger=TriggerType.DEFAULT,
    trigger_instrument_id=self._synthetic_id,
)

self.strategy.submit_order(order)

Performance

Formulas compile once at construction time and evaluate on every incoming component price tick. The expression engine uses a compile-once/eval-many architecture with a zero-allocation f64 stack, so evaluation adds negligible overhead to the tick-processing path.

Measured on Apple M4 Pro, rustc 1.94.1, release profile (opt-level 3):

Evaluation (hot path)

Formula patternTime
(A + B) / 2.012 ns
A * 0.4 + B * 0.3 + C * 0.2 + D * 0.118 ns
if(A > B, A - B, B - A)12 ns
spread = A - B; mid = ...; mid + ...19 ns
max(min(A, B * 20), abs(A - B))15 ns

Evaluation scaling (weighted sum)

ComponentsTime
214 ns
418 ns
828 ns

Compilation (cold path)

Formula patternTime
Simple average675 ns
4-input weighted1.4 us
Conditional1.0 us
With locals1.3 us
Hyphenated IDs755 ns

Error handling

Nautilus validates synthetic instruments at every boundary. Formula compilation rejects unknown symbols, type errors, and capacity overflows. Evaluation rejects wrong input counts and non-finite prices (NaN, Infinity) before they reach the formula.

See the SyntheticInstrument API Reference for input requirements and exceptions.

  • Instruments - Instrument definitions and venue-specific instrument types.
  • Data - Market data types that reference instruments.
  • Orders - Orders can use synthetic instrument IDs for emulation triggers.

On this page