diff --git a/README.md b/README.md
index edbcb9b..1f42fc7 100644
--- a/README.md
+++ b/README.md
@@ -6,35 +6,37 @@
[](https://black.readthedocs.io/en/stable/)
[](https://opensource.org/licenses/MIT)
-Official Python SDK for the [DataMaxi+ API](https://docs.datamaxiplus.com/).
-Fetch both historical and latest market data across centralized exchanges (OHLCV
-candles, tickers, trading fees, wallet status, announcements), perpetual funding
-rates, cross-exchange price premiums for arbitrage, forex rates, Telegram channel
-data, and Naver search trends.
+Official Python SDK for the [DataMaxi+ API](https://docs.datamaxiplus.com/) —
+one library for both **historical** and **real-time** crypto market data.
-This package is compatible with Python v3.10+.
+- **REST** — OHLCV candles, tickers, trading fees, wallet status, announcements
+ and token updates across centralized exchanges; perpetual funding rates,
+ liquidations, open interest, margin-borrow rates; cross-exchange price
+ premiums for arbitrage; index prices and forex rates; Telegram channel data
+ and Naver search trends.
+- **WebSocket** — stream tickers, forex, premiums, funding rates, open interest,
+ liquidations and listing announcements as they happen.
+- **Sync or async** — a synchronous client, a coroutine-based async twin, and an
+ async WebSocket client, all sharing the same resource tree and arguments.
+
+Compatible with Python v3.10+.
## Table of Contents
- [Installation](#installation)
-- [Configuration](#configuration)
+- [Authentication](#authentication)
- [Quickstart](#quickstart)
-- [Async Client](#async-client)
-- [API Reference](#api-reference)
- - [CEX Candle Data](#cex-candle-data)
- - [CEX Ticker Data](#cex-ticker-data)
- - [CEX Trading Fees](#cex-trading-fees)
- - [CEX Wallet Status](#cex-wallet-status)
- - [CEX Announcements](#cex-announcements)
- - [CEX Token Updates](#cex-token-updates)
- - [Funding Rate](#funding-rate)
- - [Premium](#premium)
- - [Forex](#forex)
- - [Telegram](#telegram)
- - [Naver Trend](#naver-trend)
+- [Clients](#clients)
+- [REST API Reference](#rest-api-reference)
+ - [CEX](#cex) — [Candle](#cex-candle-data), [Ticker](#cex-ticker-data), [Fees](#cex-trading-fees), [Wallet Status](#cex-wallet-status), [Announcements](#cex-announcements), [Token Updates](#cex-token-updates), [Symbol](#cex-symbol)
+ - [Derivatives and Leverage](#derivatives-and-leverage) — [Funding Rate](#funding-rate), [Liquidation](#liquidation), [Open Interest](#open-interest), [Margin Borrow](#margin-borrow)
+ - [Pricing and Cross-Exchange](#pricing-and-cross-exchange) — [Premium](#premium), [Index Price](#index-price), [Forex](#forex)
+ - [Alternative Data](#alternative-data) — [Telegram](#telegram), [Naver Trend](#naver-trend)
+- [WebSockets](#websockets)
- [Response Types](#response-types)
- [Pagination](#pagination)
- [Error Handling](#error-handling)
+- [Async Client](#async-client)
- [Local Development](#local-development)
- [Tests](#tests)
- [Links](#links)
@@ -47,44 +49,53 @@ This package is compatible with Python v3.10+.
pip install datamaxi
```
-## Configuration
+The SDK is lightweight by default (`requests` + `pandas`). Two features live
+behind optional extras so you only install what you use:
+
+| Extra | Installs | Enables |
+| ------------------------------- | ------------ | ------------------------------------------------ |
+| `pip install "datamaxi[async]"` | `httpx` | The async client, [`AsyncDatamaxi`](#async-client). |
+| `pip install "datamaxi[ws]"` | `websockets` | The async WebSocket client, [`AsyncDatamaxiWS`](#websockets). |
+
+Combine them in one shot: `pip install "datamaxi[async,ws]"`.
+
+## Authentication
+
+DataMaxi+ endpoints are protected by an API key. Get one by registering at
+https://datamaxiplus.com/auth.
+
+Set it once via the `DATAMAXI_API_KEY` environment variable (recommended, so the
+key stays out of source code) and every client picks it up automatically:
+
+```shell
+export DATAMAXI_API_KEY="your_api_key"
+```
-Private API endpoints are protected by an API key.
-You can get the API key upon registering at https://datamaxiplus.com/auth.
+Or pass it explicitly to any client: `Datamaxi(api_key="your_api_key")`.
+
+Every client accepts the following options:
| Option | Explanation |
|--------------------|---------------------------------------------------------------------------------------|
-| `api_key` | Your API key |
-| `base_url` | If `base_url` is not provided, it defaults to `https://api.datamaxiplus.com`. |
-| `timeout` | Number of seconds to wait for a server response. By default requests do not time out. |
-| `proxies` | Proxy through which the request is queried |
-| `show_limit_usage` | Return response as dictionary including `"limit_usage"` and `"data"` keys |
-| `show_header` | Return response as dictionary including `"header"` and `"data"` keys |
+| `api_key` | Your API key. Falls back to `DATAMAXI_API_KEY` when omitted. |
+| `base_url` | API base URL. Defaults to `https://api.datamaxiplus.com`. |
+| `timeout` | Seconds to wait for a server response. By default requests do not time out. |
+| `proxies` | Proxy through which the request is routed. |
+| `show_limit_usage` | *(Deprecated)* Return a dict with `"limit_usage"` and `"data"` keys. See [Response Types](#response-types). |
+| `show_header` | *(Deprecated)* Return a dict with `"header"` and `"data"` keys. See [Response Types](#response-types). |
### Environment Variables
-You may use environment variables to configure the SDK to avoid any inline boilerplate.
-
| Env | Description |
| ------------------ | -------------------------------------------- |
| `DATAMAXI_API_KEY` | Used instead of `api_key` if none is passed. |
## Quickstart
-DataMaxi+ Python package includes the following clients:
-
-- `Datamaxi` - Main (synchronous) client for crypto trading data (CEX, funding rates, premium, forex)
-- `Telegram` - Client for Telegram channel data
-- `Naver` - Client for Naver trend data
-
-An asynchronous variant, `AsyncDatamaxi`, is covered in [Async Client](#async-client).
-
-Set your API key via the `DATAMAXI_API_KEY` environment variable (recommended, so
-the key stays out of source code):
-
-```shell
-export DATAMAXI_API_KEY="your_api_key"
-```
+Set `DATAMAXI_API_KEY` (see [Authentication](#authentication)), then pick the
+style that fits your app — synchronous, `asyncio`, or streaming over WebSocket.
+All three share the same resource tree, so an endpoint you learn in one works in
+the others.
Sync
@@ -102,16 +113,12 @@ df = maxi.cex.candle(
exchange="binance",
symbol="BTC-USDT",
interval="1d",
- market="spot"
+ market="spot",
)
print(df.head())
# Fetch ticker data
-ticker = maxi.cex.ticker.get(
- exchange="binance",
- symbol="BTC-USDT",
- market="spot"
-)
+ticker = maxi.cex.ticker.get(exchange="binance", symbol="BTC-USDT", market="spot")
print(ticker)
# Fetch premium data
@@ -132,24 +139,16 @@ async def main():
# Reads DATAMAXI_API_KEY from the environment automatically.
# Alternatively, pass api_key="your_api_key" explicitly.
async with AsyncDatamaxi() as client:
- # Fetch CEX candle data (returns pandas DataFrame)
df = await client.cex.candle(
- exchange="binance",
- symbol="BTC-USDT",
- interval="1d",
- market="spot",
+ exchange="binance", symbol="BTC-USDT", interval="1d", market="spot"
)
print(df.head())
- # Fetch ticker data
ticker = await client.cex.ticker.get(
- exchange="binance",
- symbol="BTC-USDT",
- market="spot",
+ exchange="binance", symbol="BTC-USDT", market="spot"
)
print(ticker)
- # Fetch premium data
premium = await client.premium(asset="BTC")
print(premium.head())
@@ -159,52 +158,62 @@ asyncio.run(main())
-## Async Client
+WebSocket
-`AsyncDatamaxi` is the asynchronous counterpart to `Datamaxi` (built on
-[httpx](https://www.python-httpx.org/)). It mirrors the same resource tree and
-arguments, with one rule: every method is a coroutine and must be `await`ed.
-Install the async extra:
-
-```shell
-pip install "datamaxi[async]"
-```
+Requires the `ws` extra (`pip install "datamaxi[ws]"`). Streaming is
+async-only — see [WebSockets](#websockets) for the full channel list.
```python
import asyncio
-from datamaxi.aio import AsyncDatamaxi
+from datamaxi.aio.ws import AsyncDatamaxiWS
async def main():
- # Reads DATAMAXI_API_KEY from the environment, or pass api_key=... explicitly.
- async with AsyncDatamaxi() as client:
- df = await client.cex.candle(
- exchange="binance", symbol="BTC-USDT", interval="1d", market="spot"
- )
- print(df.head())
+ # Reads DATAMAXI_API_KEY from the environment automatically.
+ async with AsyncDatamaxiWS() as ws:
+ # subscribe() returns an async iterator over live messages
+ async for msg in await ws.ticker.subscribe("BTC-USDT@binance", market="spot"):
+ print(msg["s"], msg.get("p")) # symbol, price
asyncio.run(main())
```
-Use `AsyncDatamaxi` as an async context manager (shown above) or call
-`await client.aclose()` yourself. Paginated endpoints return an async
-`next_request` — `await` it too
-(`data, next_request = await client.cex.announcement(...)`). Telegram and Naver
-have standalone `AsyncTelegram` / `AsyncNaver` clients.
+
+
+## Clients
+
+The package ships these clients, all configured the same way (see
+[Authentication](#authentication)):
-Every endpoint in the [API Reference](#api-reference) works the same under the
-async client — see the [docs](https://datamaxi.readthedocs.io/) where each
-example has a Sync/Async tab.
+| Client | Import | Purpose |
+| ----------------- | --------------------------------------------- | ---------------------------------------------------------- |
+| `Datamaxi` | `from datamaxi import Datamaxi` | Synchronous client for all crypto REST data. |
+| `Telegram` | `from datamaxi import Telegram` | Telegram channel messages and metadata. |
+| `Naver` | `from datamaxi import Naver` | Naver search-trend data (South Korea). |
+| `AsyncDatamaxi` | `from datamaxi.aio import AsyncDatamaxi` | Async twin of `Datamaxi` (needs the `[async]` extra). |
+| `AsyncDatamaxiWS` | `from datamaxi.aio.ws import AsyncDatamaxiWS` | Async WebSocket streaming (needs the `[ws]` extra). |
-## API Reference
+`AsyncTelegram` and `AsyncNaver` are the async twins of `Telegram` / `Naver`,
+also imported from `datamaxi.aio`.
+
+## REST API Reference
> **Discovery helpers.** Most endpoints expose helpers to list valid argument
> values before you fetch — commonly `.exchanges()`, `.symbols(exchange=...)`, and
> (for candles) `.intervals()`. Use them to discover supported exchanges, trading
> pairs, and intervals. The examples below show them per endpoint.
-### CEX Candle Data
+All examples use the sync `Datamaxi` client. Every endpoint works identically on
+the [async client](#async-client) — just `await` the call. Each endpoint also has
+a dedicated page with a Sync/Async tab in the
+[docs](https://datamaxi.readthedocs.io/).
+
+### CEX
+
+Data from centralized exchanges: prices, fees, wallet status, and listings.
+
+#### CEX Candle Data
Fetch historical candlestick (OHLCV) data from centralized exchanges.
@@ -231,7 +240,7 @@ df = maxi.cex.candle(
)
```
-### CEX Ticker Data
+#### CEX Ticker Data
Fetch real-time ticker data from centralized exchanges.
@@ -253,7 +262,7 @@ ticker = maxi.cex.ticker.get(
)
```
-### CEX Trading Fees
+#### CEX Trading Fees
Fetch trading fee information from centralized exchanges.
@@ -271,7 +280,7 @@ fees = maxi.cex.fee(
)
```
-### CEX Wallet Status
+#### CEX Wallet Status
Fetch deposit/withdrawal status for assets on centralized exchanges.
@@ -290,7 +299,7 @@ status = maxi.cex.wallet_status(
)
```
-### CEX Announcements
+#### CEX Announcements
Fetch exchange announcements (listings, delistings, etc.).
@@ -309,7 +318,7 @@ data, next_request = maxi.cex.announcement(
data2, next_request2 = next_request()
```
-### CEX Token Updates
+#### CEX Token Updates
Fetch token listing/delisting updates.
@@ -322,7 +331,27 @@ data, next_request = maxi.cex.token.updates(
)
```
-### Funding Rate
+#### CEX Symbol
+
+Per-base / per-symbol CEX metadata and aggregates: trading status, tags,
+cautions, delistings, volume, open interest, and liquidation.
+
+```python
+metadata = maxi.cex.symbol.metadata(exchange="binance", base="BTC")
+tags = maxi.cex.symbol.tags(exchange="binance", base="BTC")
+cautions = maxi.cex.symbol.cautions(exchange="binance")
+delistings = maxi.cex.symbol.delistings(exchange="binance")
+volume = maxi.cex.symbol.volume(base="BTC")
+oi = maxi.cex.symbol.oi(base="BTC", exchange="binance")
+oi_stats = maxi.cex.symbol.oi_stats(base="BTC", exchange="binance", currency="USD")
+liquidation = maxi.cex.symbol.liquidation(base="BTC", window="24h")
+```
+
+### Derivatives and Leverage
+
+Perpetual funding, liquidations, open interest, and margin-borrow rates.
+
+#### Funding Rate
Fetch funding rate data for perpetual futures.
@@ -355,7 +384,48 @@ df = maxi.funding_rate.latest(
)
```
-### Premium
+#### Liquidation
+
+CEX futures liquidation data: recent events, a firehose feed, heatmaps, maps,
+and bucketed history.
+
+```python
+events = maxi.liquidation(exchange="binance", symbol="BTC-USDT", limit=100)
+feed = maxi.liquidation.feed(limit=100) # most recent across all symbols
+heatmap = maxi.liquidation.heatmap(window="1h", topN=10) # window: 1h/4h/24h, topN 1-30
+stats = maxi.liquidation.stats(window="1h")
+liq_map = maxi.liquidation.map(base="BTC", exchange="binance", quote="USDT")
+history = maxi.liquidation.symbol_history(
+ symbol="BTC", quote="USDT", exchange="binance", interval="5m", window="24h"
+)
+```
+
+#### Open Interest
+
+CEX futures open interest: latest snapshots, reporting pairs, the token ×
+exchange matrix, top-line aggregates, and aggregated history.
+
+```python
+snapshot = maxi.open_interest(exchange="binance", symbol="BTC-USDT")
+pairs = maxi.open_interest.list(exchange="binance")
+overview = maxi.open_interest.overview(page=1, limit=20, key="binance", sort="desc")
+summary = maxi.open_interest.summary(topN=10)
+history = maxi.open_interest.history_aggregated(token_id="bitcoin", interval="1h")
+```
+
+#### Margin Borrow
+
+Margin-borrow data for a single asset.
+
+```python
+data = maxi.margin_borrow(asset="BTC")
+```
+
+### Pricing and Cross-Exchange
+
+Cross-exchange premiums, index prices, and forex rates.
+
+#### Premium
Fetch cross-exchange price premium data for arbitrage analysis.
@@ -388,7 +458,20 @@ volume bounds (`min/max_sv`, `min/max_tv`), funding-rate bounds, `only_transfera
`network`, and more. See the [premium endpoint docs](https://docs.datamaxiplus.com/)
for the full list.
-### Forex
+#### Index Price
+
+Historical index-price time series for a single asset.
+
+```python
+data = maxi.index_price(
+ asset="BTC",
+ from_="now - 1 month", # from_ has a trailing underscore (from is a keyword);
+ to="now", # the wire-level query param is still "from"
+ interval="5m",
+)
+```
+
+#### Forex
Fetch forex exchange rate data.
@@ -403,7 +486,11 @@ df = maxi.forex(
)
```
-### Telegram
+### Alternative Data
+
+Off-exchange signals: Telegram channels and Naver search trends.
+
+#### Telegram
Fetch Telegram channel messages and metadata.
@@ -432,7 +519,7 @@ data, next_request = telegram.messages(
)
```
-### Naver Trend
+#### Naver Trend
Fetch Naver search trend data (South Korea).
@@ -451,6 +538,131 @@ data = naver.trend(
)
```
+## WebSockets
+
+Stream real-time market data over the DataMaxi+ WebSocket API. The WebSocket
+client is **async-only** and lives behind the `ws` extra:
+
+```shell
+pip install "datamaxi[ws]"
+```
+
+```python
+import asyncio
+from datamaxi.aio.ws import AsyncDatamaxiWS
+
+
+async def main():
+ # Reads DATAMAXI_API_KEY from the environment, or pass api_key=... explicitly.
+ async with AsyncDatamaxiWS() as ws:
+ stream = await ws.ticker.subscribe("BTC-USDT@binance", market="spot")
+ async for msg in stream:
+ print(msg["s"], msg.get("p")) # symbol, price
+
+
+asyncio.run(main())
+```
+
+### Channels
+
+Each accessor on `AsyncDatamaxiWS` maps to one channel. `subscribe(*params)` is a
+coroutine returning an **async iterator** over live messages; `stream()` (for the
+param-less firehose feeds) does the same. Pass the raw param strings shown below —
+you can also read the expected shape at runtime via `ws..param_format`.
+
+| Accessor | Call | Param format | Plan |
+| --------------------------- | ------------------------------------------- | --------------------------------------------------- | ----- |
+| `ws.ticker` | `subscribe(*p, market="spot"\|"futures")` | `SYMBOL@exchange[@currency@conversionBase]` | Basic |
+| `ws.forex` | `subscribe(*p)` | `SYMBOL` | Basic |
+| `ws.premium` | `subscribe(*p)` | `src:tgt:tokenId:srcQuote:tgtQuote:srcMkt:tgtMkt` | Basic |
+| `ws.funding_rate` | `subscribe(*p)` | `SYMBOL@exchange` | Basic |
+| `ws.open_interest` | `subscribe(*p)` | `SYMBOL@exchange` | Basic |
+| `ws.liquidation` | `subscribe(*p)` | `SYMBOL@exchange` | Basic |
+| `ws.liquidation_feed` | `stream()` | — (firehose, no params) | Basic |
+| `ws.announcement` | `subscribe()` | — (no params) | Pro+ |
+| `ws.announcement_internal` | `subscribe()` | — (no params) | Pro+ |
+
+### Multiplexing and filtering
+
+One connection is opened per channel and multiplexes every param you subscribe
+to. Because the protocol tags messages by payload fields (not by a subscription
+id), `subscribe()` yields **every** message on the channel — filter client-side
+by symbol (`msg["s"]`) when you subscribe to more than one:
+
+```python
+stream = await ws.ticker.subscribe(
+ "BTC-USDT@binance", "ETH-USDT@binance", market="spot"
+)
+async for msg in stream:
+ if msg["s"] == "BTC-USDT":
+ handle_btc(msg)
+```
+
+Add or drop params on the fly:
+
+```python
+await ws.ticker.subscribe("SOL-USDT@binance", market="spot") # add
+await ws.ticker.unsubscribe("SOL-USDT@binance", market="spot") # remove
+```
+
+> Not every channel supports removing an individual param server-side —
+> `liquidation` and `open_interest` are subscribe-only. Closing the client (see
+> [Lifecycle](#lifecycle)) always stops all streams.
+
+### Firehose feeds
+
+`ws.liquidation_feed` needs no subscription — call `stream()` and consume:
+
+```python
+async for evt in await ws.liquidation_feed.stream():
+ print(evt["s"], evt.get("sd"), evt.get("p")) # symbol, side, price
+```
+
+### Reconnect and keepalive
+
+The client is resilient by default:
+
+- **Auto-reconnect** — if the connection drops it reconnects and replays your
+ active subscriptions, so your `async for` loop resumes without extra code.
+ Disable with `AsyncDatamaxiWS(reconnect=False)`.
+- **Keepalive** — an app-level `PING` is sent every 30s to stay under the
+ server's idle timeout. Tune with the `keepalive=` argument (`0`
+ disables it).
+
+### Lifecycle
+
+Use `AsyncDatamaxiWS` as an async context manager (shown above) so all open
+connections close cleanly, or manage it yourself:
+
+```python
+ws = AsyncDatamaxiWS()
+try:
+ async for msg in await ws.forex.subscribe("USD-KRW"):
+ ...
+finally:
+ await ws.aclose()
+```
+
+Constructor options: `api_key`, `base_url` (derives the `wss://` URL) or an
+explicit `ws_url`, `keepalive`, `reconnect`, and `connect_kwargs` (passed through
+to the underlying `websockets.connect`).
+
+### Message shapes
+
+Each message is a plain `dict`. Compact channels use short wire keys (`s` =
+symbol, plus per-channel fields like `p`, `e`, `r`, `oi`, …), while others
+(`premium`, announcements) use descriptive keys. The exact typed shape of every
+channel is generated into
+[`datamaxi._ws_models`](https://github.com/bisonai/datamaxi-python/blob/main/datamaxi/_ws_models.py)
+as `TypedDict`s, and field meanings are documented in the
+[API docs](https://docs.datamaxiplus.com/):
+
+```python
+from datamaxi._ws_models import TickerMessage, PremiumMessage
+```
+
+> Orderbook streaming is intentionally not exposed.
+
## Response Types
Most methods return pandas DataFrames by default. Set `pandas=False` to get raw dict/list responses.
@@ -465,6 +677,11 @@ data = maxi.cex.candle(exchange="binance", symbol="BTC-USDT", interval="1d", mar
print(type(data)) #
```
+Response metadata (rate-limit headers, etc.) is available on the client after a
+call via `maxi..last_response`. The older `show_limit_usage` /
+`show_header` options that folded metadata into the return value are deprecated
+and will be removed in a future major release.
+
## Pagination
Many endpoints support pagination and return a `next_request` function:
@@ -480,6 +697,9 @@ data2, next_request2 = next_request()
data3, next_request3 = next_request2()
```
+On the [async client](#async-client), `next_request` is itself a coroutine —
+`await` it: `data2, _ = await next_request()`.
+
## Error Handling
All SDK exceptions subclass `datamaxi.error.Error`:
@@ -505,6 +725,45 @@ except ServerError as e:
print(f"Server error {e.status_code}: {e.message}")
```
+## Async Client
+
+`AsyncDatamaxi` is the asynchronous counterpart to `Datamaxi` (built on
+[httpx](https://www.python-httpx.org/)). It mirrors the same resource tree and
+arguments, with one rule: every method is a coroutine and must be `await`ed.
+Install the async extra:
+
+```shell
+pip install "datamaxi[async]"
+```
+
+```python
+import asyncio
+from datamaxi.aio import AsyncDatamaxi
+
+
+async def main():
+ # Reads DATAMAXI_API_KEY from the environment, or pass api_key=... explicitly.
+ async with AsyncDatamaxi() as client:
+ df = await client.cex.candle(
+ exchange="binance", symbol="BTC-USDT", interval="1d", market="spot"
+ )
+ print(df.head())
+
+
+asyncio.run(main())
+```
+
+Use `AsyncDatamaxi` as an async context manager (shown above) or call
+`await client.aclose()` yourself. Paginated endpoints return an async
+`next_request` — `await` it too
+(`data, next_request = await client.cex.announcement(...)`). Telegram and Naver
+have standalone `AsyncTelegram` / `AsyncNaver` clients.
+
+Every endpoint in the [REST API Reference](#rest-api-reference) works the same
+under the async client — see the [docs](https://datamaxi.readthedocs.io/) where
+each example has a Sync/Async tab. For real-time streaming, see
+[WebSockets](#websockets).
+
## Local Development
This project uses [uv](https://docs.astral.sh/uv/) for fast dev setup. Install
@@ -532,6 +791,9 @@ Dependency files under `requirements/`:
| `requirements-test.txt` | `common.txt` + test/lint tooling (pytest, responses, black, flake8). |
| `requirements-dev.txt` | `requirements-test.txt` + docs tooling (mkdocs). |
+The `[async]` and `[ws]` extras add `httpx` and `websockets` respectively — the
+test stack installs them so the async and WebSocket suites can run.
+
## Tests
```shell
@@ -571,3 +833,4 @@ If you discover a bug in this project, please feel free to open an issue to disc
## License
[MIT License](LICENSE)
+
diff --git a/docs/websocket.md b/docs/websocket.md
new file mode 100644
index 0000000..cad477c
--- /dev/null
+++ b/docs/websocket.md
@@ -0,0 +1,146 @@
+# WebSocket
+
+Stream real-time market data over the DataMaxi+ WebSocket API. Unlike the REST
+resources, the WebSocket client is **async-only** — there is no synchronous
+variant.
+
+## Installation
+
+The WebSocket client requires the `ws` extra, which pulls in
+[`websockets`](https://websockets.readthedocs.io/):
+
+```shell
+pip install "datamaxi[ws]"
+```
+
+## Quickstart
+
+`AsyncDatamaxiWS` reads the same `DATAMAXI_API_KEY` environment variable as the
+REST clients (or pass `api_key=...`). Use it as an async context manager so open
+connections close cleanly.
+
+```python
+import asyncio
+from datamaxi.aio.ws import AsyncDatamaxiWS
+
+
+async def main():
+ async with AsyncDatamaxiWS() as ws:
+ stream = await ws.ticker.subscribe("BTC-USDT@binance", market="spot")
+ async for msg in stream:
+ print(msg["s"], msg.get("p")) # symbol, price
+
+
+asyncio.run(main())
+```
+
+`subscribe(*params)` is a coroutine that returns an **async iterator** over live
+messages. Iterate it with `async for`.
+
+## Channels
+
+Each accessor on `AsyncDatamaxiWS` maps to one channel. Pass the raw param
+strings shown below; you can also inspect the expected format at runtime via
+`ws..param_format`.
+
+| Accessor | Call | Param format | Plan |
+| --------------------------- | ------------------------------------------- | --------------------------------------------------- | ----- |
+| `ws.ticker` | `subscribe(*p, market="spot"\|"futures")` | `SYMBOL@exchange[@currency@conversionBase]` | Basic |
+| `ws.forex` | `subscribe(*p)` | `SYMBOL` | Basic |
+| `ws.premium` | `subscribe(*p)` | `src:tgt:tokenId:srcQuote:tgtQuote:srcMkt:tgtMkt` | Basic |
+| `ws.funding_rate` | `subscribe(*p)` | `SYMBOL@exchange` | Basic |
+| `ws.open_interest` | `subscribe(*p)` | `SYMBOL@exchange` | Basic |
+| `ws.liquidation` | `subscribe(*p)` | `SYMBOL@exchange` | Basic |
+| `ws.liquidation_feed` | `stream()` | — (firehose, no params) | Basic |
+| `ws.announcement` | `subscribe()` | — (no params) | Pro+ |
+| `ws.announcement_internal` | `subscribe()` | — (no params) | Pro+ |
+
+`ticker` is market-keyed — pass `market="spot"` (default) or `market="futures"`.
+The announcement channels require a **Pro+** plan.
+
+## Multiplexing and filtering
+
+One connection is opened per channel and multiplexes every param you subscribe
+to. Because the protocol tags messages by payload fields (not by a subscription
+id), `subscribe()` yields **every** message on the channel — filter client-side
+by symbol (`msg["s"]`) when subscribing to more than one:
+
+```python
+stream = await ws.ticker.subscribe(
+ "BTC-USDT@binance", "ETH-USDT@binance", market="spot"
+)
+async for msg in stream:
+ if msg["s"] == "BTC-USDT":
+ handle_btc(msg)
+```
+
+Add or drop params on the fly:
+
+```python
+await ws.ticker.subscribe("SOL-USDT@binance", market="spot") # add
+await ws.ticker.unsubscribe("SOL-USDT@binance", market="spot") # remove
+```
+
+> Not every channel supports removing an individual param server-side —
+> `liquidation` and `open_interest` are subscribe-only. Closing the client
+> (`await ws.aclose()`, or exiting the `async with` block) always stops all
+> streams.
+
+## Firehose feeds
+
+`ws.liquidation_feed` needs no subscription — call `stream()` and consume:
+
+```python
+async for evt in await ws.liquidation_feed.stream():
+ print(evt["s"], evt.get("sd"), evt.get("p")) # symbol, side, price
+```
+
+## Reconnect and keepalive
+
+The client is resilient by default:
+
+- **Auto-reconnect** — if the connection drops it reconnects and replays your
+ active subscriptions, so your `async for` loop resumes without extra code.
+ Disable with `AsyncDatamaxiWS(reconnect=False)`.
+- **Keepalive** — an app-level `PING` is sent every 30 seconds to stay under the
+ server's idle timeout. Tune it with the `keepalive=` argument (`0`
+ disables it).
+
+## Lifecycle
+
+Use `AsyncDatamaxiWS` as an async context manager, or manage it yourself:
+
+```python
+ws = AsyncDatamaxiWS()
+try:
+ async for msg in await ws.forex.subscribe("USD-KRW"):
+ ...
+finally:
+ await ws.aclose()
+```
+
+Constructor options: `api_key`, `base_url` (derives the `wss://` URL) or an
+explicit `ws_url`, `keepalive`, `reconnect`, and `connect_kwargs` (passed through
+to the underlying `websockets.connect`).
+
+## Message shapes
+
+Each message is a plain `dict`. Compact channels use short wire keys (`s` =
+symbol, plus per-channel fields like `p`, `e`, `r`, `oi`, …), while others
+(`premium`, announcements) use descriptive keys. The typed shape of every channel
+is generated into `datamaxi._ws_models` as `TypedDict`s, and field meanings are
+documented in the [API docs](https://docs.datamaxiplus.com/):
+
+```python
+from datamaxi._ws_models import TickerMessage, PremiumMessage
+```
+
+Orderbook streaming is intentionally not exposed.
+
+## Reference
+
+::: datamaxi.aio.ws.AsyncDatamaxiWS
+ options:
+ show_submodules: false
+ show_source: false
+
diff --git a/mkdocs.yml b/mkdocs.yml
index aaab41a..9dfc248 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -35,6 +35,7 @@ nav:
- Main: index.md
- API: api.md
- Async Client: async.md
+ - WebSocket: websocket.md
- CEX:
- Candle: cex-candle.md
- Ticker: cex-ticker.md