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

> Learn how to extend Nautilus Trader with custom adapters. Implement custom live market data or execution clients by inheriting from base classes and overriding key methods.

- Repository: [Nautech Systems/nautilus_trader](https://github.com/nautechsystems/nautilus_trader)
- Tags: how-to-guide
- Published: 2026-02-16

---

**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`](https://github.com/nautechsystems/nautilus_trader/blob/main/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:

```python
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:

```bash
cp -r nautilus_trader/adapters/_template my_exchange

```

The template contains three key files:

- [`providers.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/providers.py) – Instrument provider implementation
- [`data.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/data.py) – Live market data client implementation
- [`execution.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/execution.py) – Live execution client implementation

### 2. Implement the Instrument Provider

In [`my_exchange/providers.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/my_exchange/providers.py), inherit from `InstrumentProvider` and implement the three async loading methods:

```python
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`](https://github.com/nautechsystems/nautilus_trader/blob/main/my_exchange/data.py), create a class inheriting from `LiveMarketDataClient` and implement the connection and subscription handlers:

```python
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`](https://github.com/nautechsystems/nautilus_trader/blob/main/my_exchange/execution.py), inherit from `LiveExecutionClient` to handle order management:

```python
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:

```yaml
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.

```python

# 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`](https://github.com/nautechsystems/nautilus_trader/blob/main/config.yaml)):

```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`](https://github.com/nautechsystems/nautilus_trader/blob/main/nautilus_trader/adapters/_template/providers.py) | Skeleton for instrument discovery |
| **Template – Data Client** | [`nautilus_trader/adapters/_template/data.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/nautilus_trader/adapters/_template/data.py) | Skeleton for market data connections |
| **Template – Execution Client** | [`nautilus_trader/adapters/_template/execution.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/nautilus_trader/adapters/_template/execution.py) | Skeleton for order management |
| **Cache Adapter Example** | [`nautilus_trader/cache/adapter.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/nautilus_trader/cache/adapter.py) | PostgreSQL cache backend implementation |
| **Kernel Wiring** | [`nautilus_trader/system/kernel.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/nautilus_trader/system/kernel.py) | Component instantiation and adapter lifecycle |
| **Live Data Client Base** | [`nautilus_trader/live/data_client.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/nautilus_trader/live/data_client.py) | Abstract base for data adapters |
| **Live Execution Client Base** | [`nautilus_trader/live/execution_client.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/nautilus_trader/live/execution_client.py) | Abstract base for execution adapters |
| **Instrument Provider Base** | [`nautilus_trader/common/providers.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/nautilus_trader/common/providers.py) | Abstract base for instrument providers |
| **Cache Facade Base** | [`nautilus_trader/cache/facade.py`](https://github.com/nautechsystems/nautilus_trader/blob/main/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`](https://github.com/nautechsystems/nautilus_trader/blob/main/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`](https://github.com/nautechsystems/nautilus_trader/blob/main/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.