How to Extend Nautilus Trader with Custom Adapters: A Complete Implementation Guide

Extend Nautilus Trader by implementing a custom adapter that inherits from abstract base classes like LiveMarketDataClient or LiveExecutionClient, override the required async methods such as _connect and _subscribe, and register the class in your system configuration to wire it into the kernel.

Nautilus Trader is built around a plug-in architecture that separates core trading components from external integrations. To connect a new exchange, data source, or cache backend, you need to extend Nautilus Trader with custom adapters that implement well-defined abstract interfaces. This guide walks through the architecture, implementation steps, and concrete code examples based on the official template package.

Understanding the Adapter Architecture in Nautilus Trader

Core Abstract Base Classes

All custom adapters must inherit from specific abstract base classes defined in the nautilus_trader package:

  • Instrument Provider: nautilus_trader.common.providers.InstrumentProvider requires implementing load_all_async, load_ids_async, and load_async to fetch instrument definitions.
  • Live Market Data Client: nautilus_trader.live.data_client.LiveMarketDataClient requires _connect, _disconnect, _subscribe, _unsubscribe, and _request_* methods to handle real-time data feeds.
  • Live Execution Client: nautilus_trader.live.execution_client.LiveExecutionClient requires _connect, _disconnect, _submit_order, _cancel_order, and generate_*_report methods for order management.
  • Cache Database: nautilus_trader.cache.facade.CacheDatabaseFacade requires load, add, and update_* methods for custom persistence backends.

How the Kernel Wires Adapters

The kernel (nautilus_trader/system/kernel.py) instantiates adapters based on configuration entries. For example, when configuring a cache database, the kernel checks the configuration type and instantiates the appropriate adapter class:

if config.cache and config.cache.database.type == "redis":
    cache_db = CacheDatabaseAdapter(
        trader_id=self._trader_id,
        instance_id=self._instance_id,
        serializer=MsgSpecSerializer(...),
        config=config.cache,
    )

To use a custom cache adapter, you would replace CacheDatabaseAdapter with your own subclass of CacheDatabaseFacade and reference it in the configuration.

Step-by-Step Guide to Building a Custom Adapter

1. Copy the Template Package

Start by copying the official template package to create your adapter skeleton:

cp -r nautilus_trader/adapters/_template my_exchange

The template contains three key files:

  • providers.py – Instrument provider implementation
  • data.py – Live market data client implementation
  • execution.py – Live execution client implementation

2. Implement the Instrument Provider

In my_exchange/providers.py, inherit from InstrumentProvider and implement the three async loading methods:

from nautilus_trader.common.providers import InstrumentProvider
from nautilus_trader.model.instruments import Instrument
from nautilus_trader.model.identifiers import InstrumentId

class MyExchangeInstrumentProvider(InstrumentProvider):
    async def load_all_async(self, filters: dict | None = None) -> list[Instrument]:
        # Fetch all tradable instruments from your exchange API

        pass

    async def load_ids_async(
        self,
        instrument_ids: list[InstrumentId],
        filters: dict | None = None,
    ) -> list[Instrument]:
        # Fetch specific instruments by ID

        pass

    async def load_async(
        self,
        instrument_id: InstrumentId,
        filters: dict | None = None,
    ) -> Instrument | None:
        # Fetch a single instrument

        pass

3. Build the Live Data Client

In my_exchange/data.py, create a class inheriting from LiveMarketDataClient and implement the connection and subscription handlers:

from nautilus_trader.live.data_client import LiveMarketDataClient
from nautilus_trader.data.messages import SubscribeBars, UnsubscribeBars

class MyExchangeLiveDataClient(LiveMarketDataClient):
    async def _connect(self) -> None:
        # Initialize WebSocket or HTTP connection

        self._log.info("Connecting to MyExchange...")
        
    async def _disconnect(self) -> None:
        # Clean up connections

        self._log.info("Disconnecting from MyExchange...")
        
    async def _subscribe(self, command: SubscribeBars) -> None:
        # Map Nautilus SubscribeBars to exchange subscription

        instrument_id = command.data_type.metadata["instrument_id"]
        # Subscribe via WebSocket...

        
    async def _unsubscribe(self, command: UnsubscribeBars) -> None:
        # Handle unsubscription

        pass

4. Build the Live Execution Client

In my_exchange/execution.py, inherit from LiveExecutionClient to handle order management:

from nautilus_trader.live.execution_client import LiveExecutionClient
from nautilus_trader.execution.messages import SubmitOrder, CancelOrder

class MyExchangeLiveExecutionClient(LiveExecutionClient):
    async def _connect(self) -> None:
        # Connect to execution gateway

        pass
        
    async def _disconnect(self) -> None:
        # Disconnect from gateway

        pass
        
    async def _submit_order(self, command: SubmitOrder) -> None:
        # Convert Nautilus order to exchange format and submit

        order = command.order
        # API call to submit order...

        
    async def _cancel_order(self, command: CancelOrder) -> None:
        # Cancel order on exchange

        pass
        
    async def generate_order_status_report(self, order_id):
        # Fetch order status from exchange

        pass
        
    async def generate_fill_report(self, order_id):
        # Fetch fill/trade report

        pass

5. Register Your Adapter in Configuration

Add your adapter to the system configuration so the kernel can instantiate it:

data:
  client:
    type: my_exchange
    class: my_exchange.data.MyExchangeLiveDataClient
    config:
      venue: MYEX
      api_key: ${MYEX_API_KEY}
      api_secret: ${MYEX_API_SECRET}

exec:
  client:
    type: my_exchange
    class: my_exchange.execution.MyExchangeLiveExecutionClient
    config:
      venue: MYEX

Complete Custom Adapter Example: Echo Data Client

Below is a minimal runnable example of a custom data adapter that echoes subscription requests back as synthetic data. This demonstrates the required method signatures without external dependencies.


# my_exchange/data.py

from nautilus_trader.live.data_client import LiveMarketDataClient
from nautilus_trader.common.providers import InstrumentProvider
from nautilus_trader.common.component import LiveClock, MessageBus
from nautilus_trader.cache.cache import Cache
from nautilus_trader.model.identifiers import Venue
from nautilus_trader.model.data import Bar, BarSpecification, BarType, BarAggregation, PriceType
from nautilus_trader.data.messages import SubscribeBars, UnsubscribeBars
from nautilus_trader.core.datetime import secs_to_nanos

class EchoLiveDataClient(LiveMarketDataClient):
    """
    A trivial live data client that returns synthetic 1‑minute bars for any
    subscribed instrument. Useful as a starting point for a custom adapter.
    """

    async def _connect(self) -> None:
        self._log.info("EchoLiveDataClient connected")

    async def _disconnect(self) -> None:
        self._log.info("EchoLiveDataClient disconnected")

    async def _subscribe(self, command: SubscribeBars) -> None:
        instrument_id = command.data_type.metadata["instrument_id"]
        bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.LAST)
        bar_type = BarType(instrument_id, bar_spec, aggregation_source="EXTERNAL")
        synthetic_bar = Bar(
            ts_event=secs_to_nanos(0),
            ts_init=secs_to_nanos(0),
            open=100,
            high=101,
            low=99,
            close=100.5,
            volume=10,
            bar_type=bar_type,
        )
        self._handle_data(synthetic_bar)

    async def _unsubscribe(self, command: UnsubscribeBars) -> None:
        pass

    async def _request_bars(self, request):
        instrument_id = request.instrument_id
        bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.LAST)
        bar_type = BarType(instrument_id, bar_spec, aggregation_source="EXTERNAL")
        bar = Bar(
            ts_event=secs_to_nanos(0),
            ts_init=secs_to_nanos(0),
            open=100,
            high=101,
            low=99,
            close=100.5,
            volume=10,
            bar_type=bar_type,
        )
        self._handle_data_response(
            data_type=request.bar_type,
            data=[bar],
            correlation_id=request.id,
            start=request.start,
            end=request.end,
            params=request.params,
        )

Configuration snippet (config.yaml):

data:
  client:
    type: echo
    class: my_exchange.data.EchoLiveDataClient
    config:
      venue: ECHO

When the kernel loads this configuration, the EchoLiveDataClient will be instantiated and the system will treat it like any other exchange data client.

Key Source Files for Custom Adapter Development

Role File Description
Template – Provider nautilus_trader/adapters/_template/providers.py Skeleton for instrument discovery
Template – Data Client nautilus_trader/adapters/_template/data.py Skeleton for market data connections
Template – Execution Client nautilus_trader/adapters/_template/execution.py Skeleton for order management
Cache Adapter Example nautilus_trader/cache/adapter.py PostgreSQL cache backend implementation
Kernel Wiring nautilus_trader/system/kernel.py Component instantiation and adapter lifecycle
Live Data Client Base nautilus_trader/live/data_client.py Abstract base for data adapters
Live Execution Client Base nautilus_trader/live/execution_client.py Abstract base for execution adapters
Instrument Provider Base nautilus_trader/common/providers.py Abstract base for instrument providers
Cache Facade Base nautilus_trader/cache/facade.py Abstract base for cache backends

Summary

  • Adapters are the primary extension mechanism in Nautilus Trader, enabling integration with any exchange, data vendor, or database backend.
  • Four adapter types exist: Instrument Provider, Live Market Data Client, Live Execution Client, and Cache Database.
  • Template package at nautilus_trader/adapters/_template/ provides a type-checked skeleton with pragma: no cover markers for abstract methods.
  • Kernel instantiation automatically wires adapters based on configuration entries under data.client, exec.client, or cache.database.
  • Implementation requires async methods for connection management, subscription handling, and protocol-specific translations between Nautilus message types and exchange APIs.

Frequently Asked Questions

What is the difference between a data client and an execution client in Nautilus Trader?

A data client (LiveMarketDataClient) handles market data subscriptions, historical data requests, and real-time feed connections via WebSocket or REST. An execution client (LiveExecutionClient) manages the order lifecycle, including submitting orders, canceling orders, and generating fill reports and account status. While both inherit from base client classes and implement _connect and _disconnect, they handle distinct message types: data clients process SubscribeBars and RequestQuoteTicks, while execution clients process SubmitOrder and CancelOrder commands.

Do I need to implement all abstract methods for a custom adapter?

Yes, you must implement all abstract methods defined in the base class you are extending. The template files in nautilus_trader/adapters/_template/ include these methods with # pragma: no cover comments to indicate they are intentionally unimplemented in the skeleton. For a minimal working adapter, you need at minimum: for data clients, implement _connect, _disconnect, _subscribe, and _unsubscribe; for execution clients, implement _connect, _disconnect, _submit_order, and _cancel_order. Additional methods like _request_bars or generate_order_status_report enable full functionality but can raise NotImplementedError initially if not required for your use case.

How do I test a custom adapter before connecting to a live exchange?

Use the template package structure to create a mock implementation that returns synthetic data, similar to the EchoLiveDataClient example provided above. You can implement _subscribe to generate synthetic Bar or QuoteTick objects without network calls, allowing you to test the integration with the Nautilus data engine and message bus. Additionally, the repository contains integration tests for built-in adapters (such as Binance) in the tests/ directory; you can create analogous tests using pytest and pytest-asyncio to verify your adapter's behavior against recorded API responses or mock servers before deploying to production.

Can I extend the cache backend with a custom adapter?

Yes, the cache system supports custom backends through the CacheDatabaseFacade abstract base class defined in nautilus_trader/cache/facade.py. You can implement a custom cache adapter by subclassing this facade and implementing methods such as load, add, update_order, and update_account. The kernel instantiates the cache adapter based on the cache.database configuration, similar to data and execution clients. The nautilus_trader/cache/adapter.py file provides a reference implementation using PostgreSQL, demonstrating how to serialize objects with MsgSpecSerializer and handle database transactions within the async facade methods.

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 →