Synthetic Instruments

The platform supports the definition of customized synthetic instruments. These instruments can generate synthetic quote and trade ticks, which are beneficial for:

  • Allowing Actor (and Strategy ) components to subscribe to quote or trade feeds (for any purpose)

  • Facilitating the triggering of emulated orders

  • Constructing bars from synthetic quotes or trades

Synthetic instruments cannot be traded directly, as they are constructs that only exist locally within the platform. However, the synthetic instrument serves as an analytical tool providing useful metrics based on its component instruments.

In the future, we plan to support order management for synthetic instruments, which would involve trading their component instruments based on the behavior of the synthetic instrument.

Note

Note that the venue for a synthetic instrument is always designated as 'SYNTH' .

Formula

A synthetic instrument is composed of a combination of two or more component instruments (which can include instruments from multiple venues), as well as a “derivation formula”. Utilizing the dynamic expression engine powered by the evalexpr Rust crate, the platform can evaluate the formula to calculate the latest synthetic price tick from the incoming component instrument prices.

See the evalexpr documentation for a full description of available features, operators and precedence.

Warning

Before defining a new synthetic instrument, ensure that all component instruments are already defined and exist in the cache.

Subscribing

The following example demonstrates the creation of a new synthetic instrument with an actor/strategy. This synthetic instrument will represent a simple spread between Bitcoin and Ethereum spot prices on Binance. For this example, it is assumed that spot instruments for BTCUSDT.BINANCE and ETHUSDT.BINANCE are already present 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")

# Define the synthetic instrument
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(),
)

# Recommended to store the synthetic instruments ID somewhere
self._synthetic_id = synthetic.id

# Add the synthetic instrument for use by other components
self.add_synthetic(synthetic)

# Subscribe to quote ticks for the synthetic instrument
self.subscribe_quote_ticks(self._synthetic_id)

Note

The instrument_id for the synthetic instrument in the above example will be structured as {symbol}.{SYNTH} , resulting in 'BTC-ETH:BINANCE.SYNTH' .

Updating formulas

It’s also possible to update a synthetic instrument formulas at any time. The following example shows how to achieve this with an actor/strategy.

# Recover the synthetic instrument from the cache (assuming `synthetic_id` was assigned)
synthetic = self.cache.synthetic(self._synthetic_id)

# Update the formula, here is a simple example of just taking the average
new_formula = "(BTCUSDT.BINANCE + ETHUSDT.BINANCE) / 2"
synthetic.change_formula(new_formula)

# Now update the synthetic instrument
self.update_synthetic(synthetic)

Trigger instrument IDs

The platform allows for emulated orders to be triggered based on synthetic instrument prices. In the following example, we build upon the previous one to submit a new emulated order. This order will be retained in the emulator until a trigger from synthetic quote ticks releases it. It will then be submitted to Binance as a MARKET order:

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"),  # <-- Synthetic instrument price
    emulation_trigger=TriggerType.DEFAULT,
    trigger_instrument_id=self._synthetic_id,  # <-- Synthetic instrument identifier
)

self.strategy.submit_order(order)

Error handling

Considerable effort has been made to validate inputs, including the derivation formula for synthetic instruments. Despite this, caution is advised as invalid or erroneous inputs may lead to undefined behavior.

Refer to the SyntheticInstrument API reference for a detailed understanding of input requirements and potential exceptions.