API

Processing Polymarket data with Polars

Book exports get wide fast: many snapshots, many outcomes. Polars' columnar, multithreaded engine turns the per-snapshot loop into expressions, and a lazy frame lets you push filters down before anything is materialized.

What this guide covers

  • Polars expressions replace row-wise Python loops over snapshots
  • A LazyFrame defers execution so filters run before materialization
  • scan_parquet reads exported book data without loading it whole
  • Column operations parallelize across cores automatically

Build a frame from snapshots

The flattening is the same as with pandas - one row per outcome per timestamp - but the arithmetic is expressed as column operations. Polars runs them across all rows at once, so the cost stops scaling with the Python loop overhead.

Create the frame from a list of dicts or, better, from an Arrow table you already built. Polars uses Arrow memory, so Arrow-native input avoids a copy.

Lazy frames and predicate pushdown

Wrap the scan in lazy() and Polars will push filters and projections down to the scan. On a parquet export that means only the columns and row groups you need are read, which matters when you are scanning days of snapshots to answer a question about one market.

Call collect() only at the end. Building the whole plan first lets Polars choose the physical plan, including parallel scans and predicate pushdown, rather than executing each step eagerly.

Grouping and rolling windows

Polars makes per-outcome aggregations and rolling metrics concise. Group by outcome, then compute mean spread, max depth or a rolling average with window functions, all as expressions.

For time-series work, sort by timestamp and use over() or rolling expressions rather than manual loops. This keeps the analysis declarative and lets the engine parallelize across groups.

Downsampling with group_by_dynamic

Polars has a native windowing expression for time buckets. After sorting by timestamp, group_by_dynamic collapses fine-grained rows into coarser intervals and lets you aggregate spread and depth per interval without a manual resample.

This is where the expression API pays off: the same code scales from a single market to all eight assets, because the aggregation is declarative and the engine parallelizes across groups.

Keep the finest data you fetched on disk as Parquet and downsample on read. That way a coarser analysis never forces you to re-fetch, and a finer one is always available if the question changes.

Code examples

import os
import polars as pl

from polyorderbooks import PolyOrderbooksClient

rows = []
with PolyOrderbooksClient(api_key=os.environ["POLYORDERBOOKS_API_KEY"]) as c:
    books = c.get_market_books(
        "sol-updown-5m-1787486400",
        start_ts="2026-10-17T07:00:00Z",
        end_ts="2026-10-17T08:00:00Z",
        resolution="1s",
    )
for outcome, snaps in books["data"].items():
    for s in snaps:
        bids, asks = s.get("bids") or [], s.get("asks") or []
        rows.append({
            "t": s["t"], "outcome": outcome,
            "best_bid": bids[0][0] if bids else None,
            "best_ask": asks[0][0] if asks else None,
        })

lf = pl.DataFrame(rows).lazy().with_columns(
    (pl.col("best_ask") - pl.col("best_bid")).alias("spread"),
)
out = lf.group_by("outcome").agg(
    pl.col("spread").mean().alias("avg_spread"),
    pl.col("spread").max().alias("max_spread"),
).collect()
print(out)

Free tier

The Starter plan is free, includes order books, prices, and metrics at 250ms resolution, with 3 days of history, 60 requests/min, 1,000/day, 1 free AI backtest, and 3 strategy backtests. No credit card required.

Paid data windows from $19/mo extend history to 30–120 days at higher throughput. Backtest AI add-on is +$19/mo or standalone at $29/mo.

FAQ

When should I use Polars over pandas?

For wide exports with many timestamps, Polars' columnar engine and expression API are faster and use less memory than row-wise loops.

What is a LazyFrame for?

It defers execution so filters and projections are pushed down before data is materialized, letting you scan exported files and read only what you need.

How do I compute rolling metrics?

Sort by timestamp and use window expressions with over() rather than manual loops, so the engine can parallelize across outcome groups.

Does Polars read Parquet?

Yes, via scan_parquet and read_parquet, which pair well with the export workload shown in the Parquet recipe.