Skip to content
aperiodic-ioPublic

About

Python SDK for Aperiodic: point-in-time crypto microstructure, liquidity and order-flow metrics as parquet.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

66 Commits

Folders and files

Repository files navigation

Aperiodic Python Client

Python client for Aperiodic: 220 point-in-time order flow, liquidity and market microstructure metrics in 19 datasets (flow, slippage, L1 and L2 order book imbalance and liquidity, funding, open interest, basis and more) for crypto and RWA perpetuals on Binance futures, OKX perps and Hyperliquid perps, from 1m to 1d, returned as Polars or pandas DataFrames. No tick infrastructure to build or maintain.

Access pre-computed derivative and microstructure metrics with parallel downloads for optimal performance.

Installation

Install it with the data frame library you use. The extra also brings the HTTP client that fetching data needs:

pip install "aperiodic[polars]"   # or "aperiodic[pandas]"

A plain pip install aperiodic is enough to sign in with aperiodic.login(), but not to fetch data.

Install from source:

git clone https://github.com/aperiodic-io/client.git
cd client
pip install -e .

Authentication

Sign in once per machine, from Python or a terminal:

import aperiodic

aperiodic.login()  # prints a link and a code; open it on any device and allow it
python -m aperiodic login   # or `aperiodic login` with the Aperiodic CLI

This works on a laptop, over SSH and in a remote notebook: the link opens on whatever device you have at hand. A free account works, and covers every metric for 2025 on BTC, ETH, SOL and 10 RWA perps. The machine gets a key of its own, saved in ~/.config/aperiodic/credentials (the Aperiodic CLI uses the same file), which you can revoke at aperiodic.io/home/api. aperiodic.whoami() shows which key is in use and what the plan covers; aperiodic.logout() revokes it.

Logging in again on a machine whose saved key still works changes nothing: it says which key is in use. aperiodic.login(force=True) (or python -m aperiodic login --force) makes a new key and revokes the one it replaces, so repeated logins never pile up keys on your account.

After that, calls find the key on their own, so api_key= can be left out, stream() and get_symbols() included. The order is the same in this client and the Aperiodic CLI:

  1. an api_key="..." you pass;
  2. the APERIODIC_API_KEY environment variable;
  3. for preview data (preview=True), the shared public demo key;
  4. the saved key.

A saved key never takes over a preview request, so a revoked one cannot break the free sample. If the saved key is rejected, the error names its file and says to run aperiodic.login() again. For scheduled jobs and CI, make a named key at aperiodic.io/home/api and set APERIODIC_API_KEY.

Use it from an AI agent (MCP)

Aperiodic also runs a remote MCP server at https://aperiodic.io/mcp, for Claude, ChatGPT, Cursor and other agents.

Add it to Claude · ChatGPT · VS Code, or see aperiodic.io/mcp for Cursor, Claude Code and other clients.

mcptry

Symbology

Symbols are expected in Atlas unified symbology — a standardised, exchange-agnostic naming scheme.

Quick Start

from datetime import date
from aperiodic import get_metrics

df = get_metrics(
    api_key="your-api-key",
    metric="flow",
    timestamp="true",
    interval="1h",
    exchange="binance-futures",
    symbol="perpetual-BTC-USDT:USDT", # See https://github.com/aperiodic-io/atlas
    start_date=date(2024, 1, 1),
    end_date=date(2024, 1, 31),
)

print(df.head())
print(df.columns)

Available Functions

Dataset Sync Async metric values
Order, L1, L2 metrics get_metrics get_metrics_async see below
OHLCV candles get_ohlcv get_ohlcv_async —
VWAP get_vwap get_vwap_async —
TWAP get_twap get_twap_async —
Derivative metrics get_derivative_metrics get_derivative_metrics_async see below
Exchange symbols get_symbols get_symbols_async —
Raw data into a DataFrame get_raw get_raw_async dataset (RawDataset)
Raw data to disk download_raw download_raw_async dataset (RawDataset)
Raw coverage get_raw_coverage get_raw_coverage_async —
Live rows over WebSocket stream — dataset, exchange, interval

get_metrics — Trade & order book metrics

Trade metrics (TradeMetric): "vtwap", "flow", "trade_size", "impact", "range", "updownticks", "run_structure", "returns", "slippage"

L1 order book (L1Metric): "l1_price", "l1_imbalance", "l1_liquidity"

L2 order book (L2Metric): "l2_imbalance", "l2_liquidity"

get_derivative_metrics — Derivative metrics

"basis", "funding", "open_interest", "derivative_price"

Core Parameters

All data endpoints share this shape:

  • api_key: Your Aperiodic.io API key. Optional when preview=True — the shared public demo key is used automatically.
  • timestamp: "exchange" or "true".
  • interval: "1m" | "5m" | "15m" | "30m" | "1h" | "4h" | "1d".
  • exchange: "binance-futures" | "okx-perps" | "hyperliquid-perps".
  • symbol: Atlas-formatted symbol string (e.g. "perpetual-BTC-USDT:USDT").
  • start_date / end_date: Inclusive date boundaries.
  • preview: bool = False. When True, routes to the free preview endpoint — no subscription required, but the request must match an exact whitelisted parameter combination (exchange, symbol, interval, timestamp, date range).
  • show_progress: show tqdm progress bar (default: True).
  • max_concurrent: max parallel file downloads (default: 10).

Examples

Trade metrics

from datetime import date
from aperiodic import get_metrics

flow_df = get_metrics(
    api_key="your-api-key",
    metric="flow",
    timestamp="exchange",
    interval="5m",
    exchange="binance-futures",
    symbol="perpetual-ETH-USDT:USDT", # See https://github.com/aperiodic-io/atlas
    start_date=date(2024, 2, 1),
    end_date=date(2024, 2, 29),
)

L1 / L2 order book metrics

from datetime import date
from aperiodic import get_metrics

l1_df = get_metrics(
    api_key="your-api-key",
    metric="l1_imbalance",
    timestamp="true",
    interval="1m",
    exchange="binance-futures",
    symbol="perpetual-BTC-USDT:USDT", # See https://github.com/aperiodic-io/atlas
    start_date=date(2024, 3, 1),
    end_date=date(2024, 3, 7),
)

l2_df = get_metrics(
    api_key="your-api-key",
    metric="l2_liquidity",
    timestamp="true",
    interval="1m",
    exchange="binance-futures",
    symbol="perpetual-BTC-USDT:USDT", # See https://github.com/aperiodic-io/atlas
    start_date=date(2024, 3, 1),
    end_date=date(2024, 3, 7),
)

Derivative metrics

from datetime import date
from aperiodic import get_derivative_metrics

funding_df = get_derivative_metrics(
    api_key="your-api-key",
    metric="funding",
    timestamp="exchange",
    interval="1h",
    exchange="binance-futures",
    symbol="perpetual-BTC-USDT:USDT", # See https://github.com/aperiodic-io/atlas
    start_date=date(2024, 1, 1),
    end_date=date(2024, 3, 31),
)

Symbol discovery

from aperiodic import get_symbols

symbols = get_symbols(api_key="your-api-key", exchange="binance-futures") # Returns Atlas symbols: https://github.com/aperiodic-io/atlas
perpetuals = [s for s in symbols if s.startswith("perpetual-")]
print(f"Found {len(perpetuals)} perpetual symbols")

Async usage

import asyncio
from datetime import date
from aperiodic import get_metrics_async, get_symbols_async

async def main() -> None:
    symbols = await get_symbols_async(
        api_key="your-api-key",
        exchange="binance-futures",
    )
    for symbol in symbols:
        df = await get_metrics_async(
            api_key="your-api-key",
            metric="l1_liquidity",
            timestamp="true",
            interval="1h",
            exchange="binance-futures",
            symbol=symbol, # See https://github.com/aperiodic-io/atlas
            start_date=date(2024, 1, 1),
            end_date=date(2026, 1, 1),
        )

asyncio.run(main())

Preview (no subscription required)

Anyone can access a curated slice of data via preview=True — no subscription and no API key required. Omit api_key and the client uses the shared public demo key automatically. The request must match the exact parameters (exchange, symbol, interval, timestamp, date range) for one of the whitelisted entries.

Available preview datasets: aperiodic.io/catalog#preview

from datetime import date
from aperiodic import get_ohlcv

# Use the exact parameters listed at https://aperiodic.io/catalog#preview
df = get_ohlcv(
    exchange="binance-futures",
    symbol="perpetual-BTC-USDT:USDT",
    interval="5m",
    timestamp="exchange",
    start_date=date(2025, 5, 1),
    end_date=date(2025, 5, 31),
    preview=True,
)

print(df.head())

Raw data (Prime + Raw plan)

Raw trades, top-of-book quotes and derivative ticks for Binance, OKX and Hyperliquid perpetuals, the data the metrics are built from. Same API key and symbols as the metrics. History is one Parquet file per calendar month; from 2026-08-01 there is one file per day.

Datasets (RawDataset): "trades", "quotes", "mark_price", "index_price" and "funding_rate", on every venue.

Every file starts with exchange_timestamp (the venue's time) and local_timestamp (when the event reached our capture machine). Timestamps are timezone-aware UTC.

Hyperliquid's derivative feed carries no exchange time, so in its mark_price, index_price and funding_rate files exchange_timestamp is modelled from the capture time, and an exchange_timestamp_kind column ("modelled") follows it.

from datetime import date
import aperiodic as ap

# Into one DataFrame, trimmed to the range on exchange_timestamp
trades = ap.get_raw(
    api_key="your-api-key",
    dataset="trades",
    exchange="binance-futures",
    symbol="perpetual-BTC-USDT:USDT",
    start_date=date(2025, 6, 1),
    end_date=date(2025, 6, 3),
)

# Large ranges: stream the files to disk instead (skips files you already have)
paths = ap.download_raw(
    api_key="your-api-key",
    dataset="quotes",
    exchange="okx-perps",
    symbol="perpetual-BTC-USDT:USDT",
    start_date=date(2024, 1, 1),
    end_date=date(2025, 12, 31),
    output_dir="raw",
)

download_raw writes the bucket's own layout, raw/{dataset}/exchange=…/symbol=…/year=YYYY/month=MM[/day=DD]/data.parquet (: becomes %3A on Windows). Monthly and daily files sit at different depths, so read a folder with a glob and filter on exchange_timestamp:

import polars as pl

lazy = pl.scan_parquet("raw/quotes/**/data.parquet")
  • Ranges over 366 days are split into several requests for you.
  • Download URLs are valid for one hour; one that has expired is re-requested automatically.
  • A plan without raw data raises APIError with status_code=403 and code="raw_not_in_plan".
  • get_raw(..., preview=True) returns the free June 2025 file of each venue's BTC perpetual without a key. get_raw_coverage() lists every symbol's first and last day, no key needed.
  • download_raw needs CPython (httpx and a filesystem); in Pyodide use get_raw.

Live streaming

Rows as they are published, over WebSocket, on plans with live data. Needs the stream extra:

pip install "aperiodic[stream]"
from aperiodic import stream

for message in stream(
    api_key="your-api-key",
    dataset="ohlcv",
    exchange="binance-futures",
    interval="1m",
    symbols=["perpetual-BTC-USDT:USDT"],  # omit for every symbol on your plan
):
    print(message.channel, message.snapshot, message.data["close"])

Catch up on the last 30 minutes before the live rows:

from datetime import timedelta

for message in stream(
    api_key="your-api-key",
    dataset="ohlcv",
    exchange="binance-futures",
    interval="1m",
    since=timedelta(minutes=30),
):
    print(message.replay, message.data["time"], message.data["close"])

Each StreamMessage has channel ("ohlcv.binance-futures.1m"), data (the row as published; time is microseconds since the epoch), snapshot and replay. Right after subscribing, Pro plans and above get the latest row per symbol, flagged snapshot=True; pass snapshot=False to skip those.

  • More channels: channels=["open_interest.okx-perps.1m", {"dataset": "ohlcv", "exchange": "okx-perps", "interval": "1m", "symbols": [...]}], alone or next to dataset/exchange/interval, up to 200 in all.
  • Rejections: if every channel is refused, StreamSubscriptionError is raised and .rejected says why (not_entitled, not_live, unknown_channel, limit_exceeded, invalid_message). If only some are, a StreamWarning is emitted and the rest stream; the granted and rejected channels are on the stream's .subscription.
  • Refused connections raise APIError: 401 for a bad key, 403 when the plan has no live data, 429 when all of the plan's connections are in use. 401, 403 and 426 are never retried. 429 is raised on the first connect, but retried with backoff on a reconnect, where the dropped socket may still be counted for a moment.
  • Reconnects: a dropped or silent connection (no frame for 75 s, the server sends a heartbeat every ~30 s), a server restart or a graceful server close (1000) is re-opened with exponential backoff and the same subscription (reconnect=False raises StreamClosedError instead). Close codes 4000-4999 (4001: plan lapsed or key rotated) and 1008 (rate limit) always raise StreamClosedError.
  • Replay: on 1m and longer intervals the server keeps recent bars (1 h of 1m to 30m, 3 h of 1h, the newest 4h bar, 48 h of 1d). A reconnect asks for each channel's bars since the newest one received, so rows published while disconnected arrive first, flagged replay=True. Pass since= (a UTC datetime, or a timedelta back from now) to replay from a time of your choosing on the first connect too, and replay=False to turn the reconnect replay off. A row can arrive both replayed and live with the same key: overwrite it. The stream's .replays lists a StreamReplay per replay (bars, rows, missing bars the server does not hold, truncated, unavailable); a replay cut short at the server's size cap is followed up from the next bar, an unavailable one is asked for once more, and a subscribe refused while the server is busy replaying is sent again after a short random pause. A drop while a replay is still catching up resumes it from where it got to.
  • Gaps: each reconnect emits a StreamWarning and is recorded in the stream's .gaps, a list of (disconnected_at, reconnected_at) UTC datetimes. Below 1m, or past what the server keeps, rows published while disconnected are lost; if a gap matters, fill it from the REST endpoints (get_ohlcv, ...).
  • Leaving the loop (break, Ctrl-C) closes the connection. To close it from elsewhere, keep the stream and call .close(), or use it as a context manager.
  • CPython only: a browser WebSocket (Pyodide, marimo) cannot send the API key header.

Performance Notes

  • Downloads are split into monthly parquet files server-side.
  • Files are fetched concurrently and concatenated locally.
  • Final output is sorted and filtered to your exact requested date range.
  • Tune max_concurrent based on your network and compute resources.
  • Transient failures — rate limits, upstream 5xx, dropped connections — are retried with exponential backoff before an APIError is raised.

Requirements

  • Python 3.11+
  • httpx
  • polars
  • tqdm
  • nest-asyncio
  • websockets (only for live streaming, via the stream extra)

License

MIT

About

Python SDK for Aperiodic: point-in-time crypto microstructure, liquidity and order-flow metrics as parquet.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages