An open API service indexing awesome lists of open source software.

https://github.com/atoceano/exchange-router-service

Drop-in container that normalizes public crypto exchange market data into a unified interface.
https://github.com/atoceano/exchange-router-service

docker exchange-api market-data-api microservice python

Last synced: about 2 months ago
JSON representation

Drop-in container that normalizes public crypto exchange market data into a unified interface.

Awesome Lists containing this project

README

          

OCEΛNO exchange-router-service


Python 3.8+
FastAPI
License: MIT


Introduction  • 
API Reference  • 
Python SDK  • 
Exchange Notes  • 
System Architecture  • 
Validator Guide  • 
Contributor Guide








## Introduction

The `exchange-router-service` is an async API gateway for public cryptocurrency market data. It sits in front of multiple exchanges and normalizes their REST and WebSocket APIs into a single consistent schema. You write one client instead of many.

The service handles the parts that every exchange integration ends up needing: pagination for large historical pulls, request-weight throttling to prevent IP bans, and persistent connection management for WebSocket streams. Adding a new exchange is a matter of dropping in a new adapter, with no changes to the routing core.

**Stateless and keyless.** The service handles public market data only. It places no orders, holds no API keys, manages no accounts, and persists nothing to disk. If you need authentication or private endpoints, this is not it.



Exchange Router Architecture

Architecture: Client -> Router -> Multiple Exchanges



Clients talk to one endpoint, the router routes requests to the right exchange adapter, and the adapter normalizes the response into a schema that is identical across exchanges. REST and WebSocket both sit on the same port, and the same adapter instance serves both, so a client written against Binance spot works against Bybit linear with a single path change. The schema is uniform; some unit semantics are not. See [Exchange Notes](.Documentation/Exchange_Notes.md) before comparing values across markets; each exchange's section calls out the fields whose units differ.




## Supported Exchanges



Exchange
Market
OHLCV
Ticker
DOM
Trades
OI / FR
L/S Ratio
WebSocket Streams








Spot
[x]
[x]
[x]
[x]
[ ]
[ ]
Ticker, Book Ticker, Trades, Agg Trades, Orderbook


Linear
[x]
[x]
[x]
[x]
[x]
[x]
Ticker, Book Ticker, Trades, Agg Trades, Orderbook, Mark Price, Liquidations


Inverse
[x]
[x]
[x]
[x]
[x]
[x]
Ticker, Book Ticker, Trades, Agg Trades, Orderbook, Mark Price, Liquidations






Spot
[x]
[x]
[x]
[x]
[ ]
[ ]
Ticker, Book Ticker, Trades, Orderbook


Linear
[x]
[x]
[x]
[x]
[x]
[x]
Ticker, Book Ticker, Trades, Orderbook, Liquidations


Inverse
[x]
[x]
[x]
[x]
[x]
[x]
Ticker, Book Ticker, Trades, Orderbook, Liquidations






Spot
[x]
[x]
[x]
[x]
[ ]
[ ]
Ticker, Book Ticker, Trades, Orderbook


Linear
[x]
[x]
[x]
[x]
[x]
[x]
Ticker, Book Ticker, Trades, Orderbook, Mark Price


Inverse
[x]
[x]
[x]
[x]
[x]
[x]
Ticker, Book Ticker, Trades, Orderbook, Mark Price






Spot
[x]
[x]
[x]
[x]
[ ]
[ ]
Ticker, Book Ticker, Trades, Orderbook


Linear
[x]
[x]
[x]
[x]
[x]
[x]
Ticker, Book Ticker, Trades, Orderbook, Mark Price, Liquidations


Inverse
[x]
[x]
[x]
[x]
[x]
[x]
Ticker, Book Ticker, Trades, Orderbook, Mark Price, Liquidations






Spot
[x]
[x]
[x]
[x]
[ ]
[ ]
Ticker, Book Ticker, Trades, Orderbook


Linear
[x]
[x]
[x]
[x]
[x]
[ ]
Ticker, Book Ticker, Trades, Orderbook, Mark Price


Inverse
[x]
[x]
[x]
[x]
[x]
[ ]
Ticker, Book Ticker, Trades, Orderbook, Mark Price

*WebSocket streams provide real-time updates for the listed channels. See the API Reference for full channel specs and subscription payloads.




## Quick Start

The service runs as a stateless Docker container.

```bash
# Clone and enter the directory
git clone https://github.com/atOCEANO/exchange-router-service.git
cd exchange-router-service

# Configure environment
cp .env.example .env

# Launch the service
docker-compose up -d --build

# Verify the service is running (if this fails, check logs with: docker-compose logs -f)
curl http://localhost:8040/status

# Fetch Binance Spot Ticker
curl http://localhost:8040/binance/spot/ticker/BTCUSDT
```


The only knob exposed at deploy time is the host port:

| Variable | Required | Default | Purpose |
| :--- | :--- | :--- | :--- |
| `EXCHANGE_ROUTER_SERVICE_PORT` | Yes | `8040` | Host port that Docker publishes. The container always binds `8040` internally. Change this if `8040` is already taken on the host, or if you run multiple router instances on the same machine. |


This variable lives in `.env` and is consumed by `docker-compose.yml` in the `ports` mapping. There is no configuration file for adapters, upstream URLs, timeouts, or rate-limit thresholds. Those are defined in code, per adapter, and changing them means editing the adapter and rebuilding the container. This is intentional, the router ships as a single immutable image and should behave identically across deployments.




## Python SDK

A thin async client over the router's REST and WebSocket interfaces. Historical and time-series methods return `pandas.DataFrame` objects indexed by datetime; point-in-time snapshots (ticker, book ticker, orderbook, mark price) return plain dicts. See [Exchange Notes](.Documentation/Exchange_Notes.md) for fields whose units vary across exchanges.

```bash
pip install git+https://github.com/atOCEANO/exchange-router-service.git#subdirectory=client
```

```python
from exchange_router_client import ExchangeRouterClient

client = ExchangeRouterClient("http://localhost:8040")

df = await client.get_candles("binance", "spot", "BTCUSDT", interval="1h", limit=500)
print(df.tail())

await client.close()
```

Fetch candles for every symbol on an exchange in one call:

```python
from exchange_router_client import ExchangeRouterClient

client = ExchangeRouterClient("http://localhost:8040")

# Discover every active Binance spot market
all_markets = await client.get_markets(exchange="binance", market_type="spot")

all_markets[:5]
# ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT']

f"Total spot markets: {len(all_markets)}"
# 'Total spot markets: 517'

# Fetch 1000 daily candles for all of them, 5 requests at a time
data_map = await client.fetch_multi_candles(
exchange="binance",
market_type="spot",
symbols=all_markets,
interval="1d",
limit=1000,
max_concurrent=5,
)

f"Fetched {len(data_map)} markets"
# 'Fetched 517 markets'

data_map["BTCUSDT"].tail()
# open high low close volume
# datetime
# 2025-04-21 84389.97 85300.00 83500.00 84500.01 21345.82
# ...

await client.close()
```