How to Create Custom Data Types for Proprietary Feeds in Nautilus Trader

You can create custom data types for proprietary feeds by defining a frozen dataclass that inherits from Data, wrapping instances in CustomData with a DataType metadata, and emitting them through a LiveMarketDataClient implementation where strategies can subscribe via the standard subscribe() method.

Nautilus Trader treats every market information object as a Data instance, but proprietary feeds often transmit non-standard fields that don't map to built-in types like QuoteTick or Bar. When you need to create custom data types for proprietary feeds, the framework provides a unified pipeline through the CustomData wrapper and DataType metadata system. This guide walks through the implementation using actual source paths from the nautechsystems/nautilus_trader repository.

Define a Custom Data Model for Proprietary Feeds

Create a frozen dataclass that inherits from nautilus_trader.model.data.Data. This class should model the exact fields coming from your proprietary feed using serializable primitive types.


# src/nautilus_trader/model/custom.py

from dataclasses import dataclass
from nautilus_trader.model.data import Data
from nautilus_trader.model.identifiers import InstrumentId

@dataclass(frozen=True, slots=True)
class MyFeedTick(Data):
    """A tick coming from a proprietary feed."""
    instrument_id: InstrumentId
    price: float
    size: float
    timestamp: int                # epoch‑ns

The Data base class is defined in nautilus_trader/model/data.py and provides the foundation for all data objects in the system.

Wrap Custom Data in the CustomData Container

The framework uses CustomData to associate a DataType (the class plus optional metadata) with your raw payload. This wrapper is required for the message bus and persistence layer to route your data correctly.

from nautilus_trader.model.data import CustomData, DataType
from .custom import MyFeedTick

def make_custom_tick(tick: MyFeedTick) -> CustomData:
    data_type = DataType(MyFeedTick, metadata={"source": "my_proprietary_feed"})
    return CustomData(data_type=data_type, data=tick)

Both CustomData and DataType are defined in nautilus_trader/model/data.py. The DataType metadata dictionary can include any contextual information your strategy needs to distinguish between different proprietary sources.

Emit Custom Data from a Live Market Data Client

Implement a LiveMarketDataClient that connects to your proprietary API, parses incoming messages, and publishes CustomData instances to the message bus.


# src/nautilus_trader/adapters/my_feed/data.py

from nautilus_trader.live.data_client import LiveMarketDataClient
from nautilus_trader.model.identifiers import Venue
from ..model.custom import MyFeedTick, make_custom_tick

class MyFeedDataClient(LiveMarketDataClient):
    def __init__(self, config):
        super().__init__(config)
        self._venue = Venue("MYFEED")

    async def _connect(self) -> None:
        # Open websocket or TCP connection to your feed

        pass

    async def _handle_message(self, raw_msg: dict) -> None:
        # Parse the proprietary message format

        tick = MyFeedTick(
            instrument_id=self._parse_instrument(raw_msg["symbol"]),
            price=raw_msg["p"],
            size=raw_msg["s"],
            timestamp=raw_msg["t"],
        )
        custom = make_custom_tick(tick)
        await self._msgbus.publish(custom)   # Same path as native data

The base class LiveMarketDataClient is defined in nautilus_trader/live/data_client.py. For a complete template, see adapters/_template/data.py in the repository. The Betfair adapter demonstrates a production implementation of this pattern in adapters/betfair/parsing/streaming.py.

Subscribe to Custom Data Types in Trading Strategies

Strategies consume custom data through the standard subscription mechanism and process it in the on_data callback.


# src/nautilus_trader/examples/strategies/my_feed_strategy.py

from nautilus_trader.trading.strategy import Strategy
from ..model.custom import MyFeedTick

class MyFeedStrategy(Strategy):
    def __init__(self, config):
        super().__init__(config)
        self.subscribe(MyFeedTick)               # Subscribe to custom type

    def on_data(self, data):
        if isinstance(data, MyFeedTick):
            self._process_tick(data)

    def _process_tick(self, tick: MyFeedTick) -> None:
        self.logger.info(f"Received custom tick: {tick.instrument_id} {tick.price}/{tick.size}")

A real-world example of subscribing to custom data appears in examples/strategies/orderbook_imbalance.py, where the strategy subscribes to Betfair-specific custom data types (lines 125-153).

Persist Custom Data to the Catalog

The persistence layer automatically handles CustomData without additional configuration. When you write custom data to the DataCatalog, the DataWriter in nautilus_trader/persistence/writer.py checks for isinstance(obj, CustomData) and serializes it using the same Parquet-based machinery as native data types. The DataType metadata ensures the data can be correctly queried and reconstructed during backtests.

Summary

  • Define a frozen dataclass inheriting from Data in nautilus_trader/model/data.py to model your proprietary feed's schema.
  • Wrap instances in CustomData with a DataType metadata object to enable routing through the message bus.
  • Emit custom data from a LiveMarketDataClient implementation by publishing to the message bus via _msgbus.publish().
  • Subscribe to your custom type in strategies using self.subscribe(MyFeedTick) and handle it in on_data().
  • Persist custom data automatically through the built-in DataWriter without additional configuration.

Frequently Asked Questions

How does Nautilus Trader serialize custom data types for storage?

The framework uses the DataWriter class in nautilus_trader/persistence/writer.py to serialize all data objects. When it encounters a CustomData instance (verified via isinstance(obj, CustomData)), it extracts the underlying data class and converts it to Parquet format using the same schema inference engine used for built-in types. The DataType metadata is stored alongside the payload to ensure correct deserialization during backtesting.

Can I use custom data types in backtesting, or are they only for live trading?

Custom data types work identically in both live trading and backtesting. When you write custom data to the DataCatalog, the persistence layer stores it with the same timestamp-indexed partitioning as native data. During backtests, the BacktestEngine replays CustomData objects through the same on_data callback in your strategy, allowing you to test proprietary signals alongside standard market data.

What performance considerations apply when creating high-frequency custom data types?

For high-frequency proprietary feeds, use slots=True in your dataclass definition to reduce memory overhead, and ensure all fields use primitive types (int, float, str) rather than nested objects. The CustomData wrapper adds minimal overhead because it simply references your data object and a lightweight DataType tuple. According to the implementation in nautilus_trader/model/data.py, the framework handles millions of custom data events per second when using the Cython-accelerated message bus.

How do I distinguish between multiple proprietary feeds using the same data class?

Use the metadata parameter of the DataType constructor to tag instances with source-specific identifiers. For example, when wrapping your data class, pass DataType(MyFeedTick, metadata={"venue": "PROP_FEED_A", "channel": "ticks"}). Your strategy can then filter incoming data in on_data by checking data.data_type.metadata, or subscribe to specific metadata filters if your data client supports selective publication. This pattern is demonstrated in the Betfair adapter where different market data streams carry distinct metadata tags.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →