AymarData

When to use L2 snapshots vs order-book deltas in AymarData — reconstruction, replay, bandwidth, and working Python examples.

Order book deltas vs snapshots

Market-data systems almost always combine snapshots (full depth at a time) with deltas (incremental level updates). AymarData follows that model so you can either ask for a reconstructed book at at= or download deltas and apply them yourself. Choosing the right mode saves bandwidth and avoids subtle bugs in backtests.

Glossary: snapshot, order book delta, L2 order book. Endpoints: book, deltas.

Definitions

Snapshot — Complete set of bid and ask levels (optionally truncated by depth) valid at a timestamp. In AymarData, GET /v1/book returns a snapshot. With at=, the service reconstructs from stored checkpoints plus deltas.

Delta — A single level update: side, price, new size (or zero to delete), sequence number, timestamps. GET /v1/book/deltas returns a time-ordered list between start and end.

Checkpoint — An internally stored full book used as a restore point. You do not address checkpoints directly; at= finds the right one for you.

Interpreting delta fields

FieldMeaning
sidebid or ask ladder
priceLevel price key
sizeNew absolute size; 0 deletes
seqMonotonic apply order
ts_exchange / ts_recvVenue vs receive clocks

Treat size as absolute replacement, not as a signed increment, unless a schema note says otherwise for a specific source. Absolute sizes match how most L2 feeds publish level updates.

When to use snapshots

  • Dashboards and alerting (latest book)
  • Sparse sampling (every 1–60 seconds) for signals
  • Simple notebooks that do not want apply logic
  • Bootstrapping a replay before applying deltas
  • Spot-checking reconstruction after a long delta apply
export AYMARDATA_API_KEY=ad_live_YOUR_KEY

curl -s "https://api.aymardata.com/v1/book" \
  -H "Authorization: Bearer $AYMARDATA_API_KEY" \
  -G \
  --data-urlencode "source=polymarket" \
  --data-urlencode "market=btc-updown-15m" \
  --data-urlencode "outcome=up" \
  --data-urlencode "at=2026-10-23T10:00:00Z" \
  --data-urlencode "depth=10"

When to use deltas

  • Continuous replay for queue or impact models
  • High-frequency feature generation
  • Verifying reconstruction locally
  • Reducing payload size vs polling full depth every 100ms
curl -s "https://api.aymardata.com/v1/book/deltas" \
  -H "Authorization: Bearer $AYMARDATA_API_KEY" \
  -G \
  --data-urlencode "source=binance-spot" \
  --data-urlencode "market=BTCUSDT" \
  --data-urlencode "start=2026-10-23T10:00:00Z" \
  --data-urlencode "end=2026-10-23T10:00:30Z"

Decision matrix by venue style

Venue styleTypical densityPrefer
Binance spot/perps (hub)HighSnapshot seed + deltas for paths
Polymarket CLOB (hub)High on liquid familiesSame; always pass outcome
Kalshi snapshots (hub)Often coarserSnapshot grids first; deltas if dense
Hyperliquid (hub)Varies by coinValidate continuity on thin coins

Correct apply algorithm

import os
import requests

API = "https://api.aymardata.com/v1"
H = {"Authorization": f"Bearer {os.environ['AYMARDATA_API_KEY']}"}

def snapshot(source, market, at, outcome=None, depth=500):
    params = {"source": source, "market": market, "at": at, "depth": depth}
    if outcome:
        params["outcome"] = outcome
    r = requests.get(f"{API}/book", headers=H, params=params, timeout=30)
    r.raise_for_status()
    return r.json()

def deltas(source, market, start, end, outcome=None):
    params = {"source": source, "market": market, "start": start, "end": end}
    if outcome:
        params["outcome"] = outcome
    r = requests.get(f"{API}/book/deltas", headers=H, params=params, timeout=120)
    r.raise_for_status()
    return r.json()["data"]

def to_maps(book):
    return (
        {x["price"]: x["size"] for x in book["bids"]},
        {x["price"]: x["size"] for x in book["asks"]},
    )

def apply_one(bids, asks, d):
    side = bids if d["side"] == "bid" else asks
    if d["size"] == 0:
        side.pop(d["price"], None)
    else:
        side[d["price"]] = d["size"]

def reconstruct(source, market, t0, t1, outcome=None, depth=500):
    bids, asks = to_maps(snapshot(source, market, t0, outcome=outcome, depth=depth))
    last_seq = None
    for d in sorted(deltas(source, market, t0, t1, outcome=outcome), key=lambda x: x["seq"]):
        if last_seq is not None and d["seq"] <= last_seq:
            raise RuntimeError(f"non-increasing seq: {last_seq} -> {d['seq']}")
        apply_one(bids, asks, d)
        last_seq = d["seq"]
    return bids, asks

def top(bids, asks):
    return (max(bids) if bids else None, min(asks) if asks else None)

Rules of thumb:

  1. Apply in seq order only
  2. Treat size == 0 as delete
  3. Resync from a fresh snapshot if sequences gap
  4. Pass outcome for Polymarket two-sided markets
  5. Compare your top-of-book to a second GET /v1/book?at=t1 as a unit test
  6. Seed depth ≥ the deepest level your model cares about

Chunked replay for long windows

Do not pull hours of deltas in one HTTP call when you can avoid it. Chunk by 1–5 minutes, persist maps, and optionally checkpoint to disk:

from datetime import datetime, timedelta, timezone

def chunked_replay(source, market, t0, t1, outcome=None, minutes=2):
    start = datetime.fromisoformat(t0.replace("Z", "+00:00"))
    end = datetime.fromisoformat(t1.replace("Z", "+00:00"))
    bids, asks = to_maps(snapshot(source, market, t0, outcome=outcome))
    cur = start
    while cur < end:
        nxt = min(cur + timedelta(minutes=minutes), end)
        a = cur.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
        b = nxt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
        last = None
        for d in sorted(deltas(source, market, a, b, outcome=outcome), key=lambda x: x["seq"]):
            if last is not None and d["seq"] <= last:
                raise RuntimeError("seq regression — resync")
            apply_one(bids, asks, d)
            last = d["seq"]
        cur = nxt
    return bids, asks

Bandwidth and latency tradeoffs

ApproachPayloadCPUTypical use
Poll snapshot every 1sMedium–largeLowSignals, UI
Snapshot + deltasSmall after seedMediumReplay, HFT research
Bulk ParquetLarge offlineBatchMulti-day training

For multi-day Binance work, prefer bulk files then local delta apply. Guide: Binance download and replay. Bulk listing is on Pro/Enterprise via /v1/files.

How AymarData uses both internally

Collectors write every normalized delta and periodic checkpoints. The book endpoint’s at= parameter finds a checkpoint at or before the target time and applies deltas forward — the same algorithm you can run client-side. That keeps interactive queries fast without forcing every client to store full tick history.

Understanding that dual storage explains why sparse at= polling is fine for research signals, while production microstructure work still wants deltas: you are choosing whether the server or your process owns the apply loop.

Pitfalls

  • Starting deltas without a snapshot — undefined book
  • Ignoring deletes — ghost liquidity
  • Sorting by timestamp only — ties need seq
  • Truncated seed depth — levels outside depth will not update correctly; seed with enough depth for your model
  • Crossing the book — treat as quality event; resync
  • Mixing outcomes — Polymarket Up/Down deltas are separate streams
  • Assuming Kalshi density equals Binance — pilot first (Kalshi guide)
  • Clock mixing — feature joins should prefer ts_exchange alignment across venues

Why teams still call at= constantly

Even if you understand deltas, point-in-time snapshots remain the right default for many jobs. Dashboards should not run a client-side apply loop. Sparse alpha features rarely need every level update. Agents exploring via MCP are safer fetching snapshots than applying thousands of tool-returned deltas interactively. The server-side reconstruction behind GET /v1/book?at= exists so interactive and agent workflows stay correct without reimplementing collector logic.

Deltas become mandatory when the research question is pathwise: queue position, impact of sweeping N levels, or features sampled every few milliseconds. In those regimes, paying CPU for apply logic is cheaper than paying bandwidth for full-depth polls. Bulk Parquet sits at the far end of the spectrum for multi-day training sets — list files via /v1/files when your plan allows, then apply locally with the same algorithm shown above. Moving a notebook from REST snapshots to files should not change labels if you kept asof timestamps explicit.

Venue differences matter more than textbook definitions. Binance and liquid Polymarket families usually reward delta replay. Kalshi during snapshot-heavy collection may not. Hyperliquid major coins often behave well; thin coins may not. Always pilot density on the exact source and market you will train on, using the hubs at Binance, Polymarket, Kalshi, and Hyperliquid as coverage maps rather than as guarantees of tick density.

Testing strategy for apply code

Treat reconstruction like any other pure function. Property tests help: applying no deltas returns the seed; applying a delete removes a level; applying a size replaces absolutely; sorting by seq is required for determinism. Integration tests should pull a real minute from AymarData in CI (with a secret key) or freeze a fixture captured from a known good pull. When integration asserts fail, dump the first mismatched price level — that single clue usually reveals shallow depth or a missed outcome parameter on Polymarket.

Also test operational behavior: chunk boundaries should not duplicate or drop seq values; empty delta ranges should leave the book unchanged; and a forced shallow seed should fail the full-map equality test on purpose so you know the assertion has teeth. Those tests document intent for the next engineer who will otherwise “optimize” depth downward to speed up CI and reintroduce ghost-liquidity bugs.

When you are unsure whether a venue warrants deltas at all, time a pilot: count unique book hashes on a one-second snapshot grid versus the number of delta events in the same minute. If hashes barely change while deltas are empty or tiny, stay on snapshots. If deltas dominate, invest in apply code and fixtures. That empirical fork beats dogma and keeps quota available for the markets that actually need path reconstruction.

Worked Polymarket check

bids, asks = reconstruct(
    "polymarket",
    "btc-updown-15m",
    "2026-10-23T10:00:00Z",
    "2026-10-23T10:01:00Z",
    outcome="up",
)
truth = snapshot(
    "polymarket",
    "btc-updown-15m",
    "2026-10-23T10:01:00Z",
    outcome="up",
    depth=500,
)
tb, ta = to_maps(truth)
assert bids == tb and asks == ta, "resync required or depth too shallow"
print("ok", max(bids), min(asks))

Use real market ids from /v1/markets in production; the family slug works when the API resolves it to an active window — prefer concrete window ids for historical precision (Polymarket books explained). Related historical download patterns: How to get Polymarket historical data.

Worked Binance top-of-book check

bids, asks = reconstruct(
    "binance-spot",
    "BTCUSDT",
    "2026-10-23T10:00:00Z",
    "2026-10-23T10:00:30Z",
)
truth = snapshot("binance-spot", "BTCUSDT", "2026-10-23T10:00:30Z", depth=500)
assert top(bids, asks) == top(*to_maps(truth))

If only tops match but full maps differ, your seed depth was likely too shallow.

FAQ

Can I rely only on deltas from t=0?
Only if you have a true genesis snapshot. Otherwise always seed.

Do snapshots include trade prints?
No — use /v1/trades separately.

Should agents use deltas via MCP?
For exploration, snapshots are safer. For path models, have the agent emit a REST replay script (MCP backtesting guide) rather than applying thousands of tool calls interactively.

Next
Docs · Pricing · Polymarket historical data

Educational content, not trading or investment advice. Past data doesn't predict future results.