Skip to main content

Historical tick-by-tick L2 orderbook reconstruction and backtesting primitives for Polymarket. Spread, impact, imbalance, and every trade, delivered as a DataFrame.

Project description

MarketLens Python SDK

Historical and real-time prediction market data — full L2 orderbook reconstruction, microstructure analytics, and backtesting primitives for Polymarket.

pip install marketlens
from marketlens import MarketLens

client = MarketLens(api_key="mk_...")  # or set MARKETLENS_API_KEY env var

Order Book Replay

Replay full L2 book state for any market. Each tick yields (Market, OrderBook) — one line to go from market ID to book-level analysis.

for market, book in client.orderbook.walk(market_id, after=start, before=end):
    print(f"mid={book.midpoint}  spread={book.spread_bps():.0f}bps")

# Or as a DataFrame
df = client.orderbook.walk(market_id, after=start, before=end).to_dataframe()

Rolling Series

Walk every market in a rolling series chronologically — same orderbook.walk() interface, just pass a series slug instead of a market ID.

for market, book in client.orderbook.walk("btc-up-or-down-5m", status="resolved"):
    if (spread := book.spread_bps()) and spread < 200:
        entry = book.impact("BUY", "100")

Structured Products

Walk a structured product series (multi-strikes, neg-risk, barrier). All sibling strike markets are replayed in parallel — walk.books always holds the latest book for every strike, and walk.surface() fits the implied probability distribution at every tick.

walk = client.orderbook.walk("btc-multi-strikes-weekly")
for market, book in walk:
    surface = walk.surface()
    if not surface:
        continue
    strikes = surface.survival_strikes()
    curve = "  ".join(f"${s.strike:,.0f}={s.fitted_prob:.3f}" for s in strikes)
    print(f"[{walk.event.title}] mean=${float(surface.implied_mean):,.0f}  {curve}")

Walk properties available during iteration:

Property Description
walk.books {market_id: OrderBook} — latest book for every sibling strike
walk.markets {market_id: Market} — all strike markets in the current event
walk.event Current Event (transitions automatically between events)
walk.series Resolved Series
walk.surface() Surface fitted from current book midpoints (same format as API)

Backtesting

Define a strategy by subclassing Strategy and implementing event hooks. Run it against any market, rolling series, structured product, or multiple series at once — the engine replays L2 book data tick-by-tick with realistic execution simulation.

from marketlens.backtest import Strategy

class BuyOnTightSpread(Strategy):
    def on_book(self, ctx, market, book):
        if ctx.position().side == "FLAT" and book.spread_bps() and book.spread_bps() < 200:
            ctx.buy_yes(size="100")

# Single series
result = client.backtest(BuyOnTightSpread(), "btc-up-or-down-5m",
                         initial_cash="10000.0000",
                         after="2026-03-05T10:00Z", before="2026-03-05T10:05Z")

# Multi-series portfolio — shared capital across assets
result = client.backtest(BuyOnTightSpread(),
                         ["btc-up-or-down-5m", "eth-up-or-down-5m", "sol-up-or-down-5m"],
                         initial_cash="10000.0000",
                         after="2026-03-05T10:00Z", before="2026-03-05T10:30Z")
result.trades_df()       # per-fill DataFrame
result.settlements_df()  # per-market settlement P&L
result.by_series()       # per-series PnL attribution
result.equity_df()       # equity curve over time

Strategy hooks

Hook Called when
on_book(ctx, market, book) Every book state change (snapshot or delta)
on_trade(ctx, market, book, trade) Every historical trade
on_fill(ctx, market, fill) Your order is filled
on_market_start(ctx, market, book) A new market begins in the walk
on_market_end(ctx, market) A market's data is exhausted, before settlement

For structured products and multi-market backtests, ctx.books gives the latest book for every active market — the same cross-market view as walk.books.

For markets with a crypto underlying, ctx.reference_price() returns the Binance spot price at the current tick — useful for computing moneyness, basis, or filtering by distance-to-strike.

Execution realism

Parameter Default Description
initial_cash required Starting capital (e.g. "10000.0000") — buy orders exceeding cash are cancelled
latency_ms 50 Order-to-fill delay — orders fill against the book state N ms after submission
queue_position False CLOB-realistic queue modeling — tracks each order's position in the book and fills only when queue-ahead is fully drained by trades and cancellations
limit_fill_rate 0.1 Flat fraction of trade size that fills your limit order. Ignored when queue_position=True
slippage_bps 0 Extra price penalty on market order fills (on top of L2 book walk)
fees "polymarket" Fee model — auto-detects per category (crypto vs sports). Set to None for zero fees
max_fill_fraction 1.0 Max fraction of each book level consumed per order
include_trades True Fetch trade data (required for limit order fills and on_trade)

For full control, use BacktestEngine with BacktestConfig directly.

Implied Probability Surfaces

Implied distributions extracted from multi-strike prediction markets — survival curves, density functions, and barrier probabilities, fitted via isotonic regression. Updated every 5 minutes via the API, or recomputed at every tick via walk.surface().

for surface in client.signals.surfaces(underlying="BTC"):
    if surface.surface_type == "survival":
        for s in surface.survival_strikes():
            print(f"  K={s.strike:>10,.0f}  P(above)={s.fitted_prob:.3f}")
    elif surface.surface_type == "density":
        for b in surface.density_buckets():
            print(f"  ${b.lower:,.0f}-${b.upper:,.0f}  p={b.normalized_prob:.3f}")
    elif surface.surface_type == "barrier":
        for b in surface.barrier_strikes():
            print(f"  {b.direction} ${b.strike:,.0f}  P={b.fitted_prob:.3f}")
Type Source Fitting Stats
survival Multi-strike "above $X" markets PAVA monotone decreasing implied_mean, implied_cv, implied_skew
density Neg-risk range + tail markets Normalized probabilities implied_mean, implied_cv, implied_skew
barrier Hit-price reach/dip markets PAVA per direction implied_peak, implied_peak_cv, implied_trough, implied_trough_cv

Reference Prices

Binance spot prices for crypto underlyings (BTC, ETH, SOL, XRP, BNB, DOGE, LINK, HYPE, ENA) at 1-second resolution. Markets with a recognized underlying expose it via market.underlying.

# Direct access
for candle in client.reference.candles("BTC", after=start, before=end):
    print(candle.timestamp, candle.close)

# In a backtest — spot price at the current tick
class MyStrategy(Strategy):
    def on_book(self, ctx, market, book):
        spot = ctx.reference_price()  # Binance close for market's underlying

OrderBook Analytics

Every OrderBook — live snapshot or replayed — carries the same analytical methods:

book = client.orderbook.get(market_id)

book.microprice()              # size-weighted mid from best level
book.weighted_midpoint(n=3)    # n-level weighted mid
book.spread_bps()              # spread in basis points
book.imbalance()               # full-book bid/ask imbalance [-1, 1]
book.imbalance(levels=3)       # top-of-book imbalance
book.impact("BUY", "1000")     # VWAP execution price for $1k market buy
book.slippage("BUY", "1000")   # slippage from mid for $1k order
book.depth_within("0.02")      # (bid_depth, ask_depth) within 2c of mid

Resources

Namespace Methods
client.markets list() get() trades() candles()
client.events list() get() markets()
client.series list() get() markets() walk() events()
client.orderbook get() history() metrics() walk()
client.signals surfaces() surface() history()
client.reference candles()
client.exports download() download_range()

All list methods return auto-paginating iterators with .to_list() and .to_dataframe().

df = client.markets.candles(market_id, resolution="1h").to_dataframe()
trades = client.markets.trades(market_id, after=start, before=end).to_list()
top = client.markets.list(status="active", sort="-liquidity", limit=5).first_page()

Bulk Data Export

Download full-day Parquet exports of order book snapshots and deltas — one file per market per day, no pagination required.

# Single day
path = client.exports.download(market_id, table="deltas", date="2026-03-07")

# Date range
paths = client.exports.download_range(
    market_id, table="snapshots", after="2026-03-01", before="2026-03-08",
)

Files are saved to the current directory by default. Pass path="./data" to choose a destination. Returns Path objects pointing to the downloaded .parquet files.

Parameter Description
table "snapshots" or "deltas"
date YYYY-MM-DD (must be before today)
after / before Date range — inclusive start, exclusive end
path Output directory (default: .)

Async

Every resource, iterator, and replay helper has an async counterpart.

from marketlens import AsyncMarketLens

async with AsyncMarketLens() as client:
    async for market, book in await client.orderbook.walk(market_id, after=start, before=end):
        print(book.microprice(), book.imbalance(levels=3))

Examples

Example Description
execution_cost.py Live book depth, spread, impact and slippage across order sizes
microstructure.py Rolling series feature matrix — does imbalance predict outcome?
implied_surfaces.py Implied probability surfaces — survival, density, and barrier
event_strikes.py Structured product walk — parallel books with live surface fitting
backtest_basic.py Single-series backtest — spread-timing strategy with settlement
backtest_limit_orders.py Market-making with limit orders and on_fill exit
backtest_surface.py Surface mispricing — PAVA regression with spot-distance filter via reference prices
backtest_portfolio.py Multi-series portfolio — imbalance strategy across three assets

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

marketlens-0.5.0.tar.gz (63.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

marketlens-0.5.0-py3-none-any.whl (54.7 kB view details)

Uploaded Python 3

File details

Details for the file marketlens-0.5.0.tar.gz.

File metadata

  • Download URL: marketlens-0.5.0.tar.gz
  • Upload date:
  • Size: 63.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for marketlens-0.5.0.tar.gz
Algorithm Hash digest
SHA256 01b721efbbc8292b380c1f958bc00aff37d8ed8fe4b52eb3a45ebe8ef63e00aa
MD5 2a1a769b3b4924a782f73b82404e93df
BLAKE2b-256 31ececcd2cdc9c92abc7db18dde6fc4095186947d6216c7c1e225400ead0d6d1

See more details on using hashes here.

File details

Details for the file marketlens-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: marketlens-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 54.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for marketlens-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b3091c4d5d3d16b724f9ec2ad6abc4624d6964f66a09d2491185c7352d9fcdb9
MD5 2dc07c9633ce3bffabf98dd35c719c5e
BLAKE2b-256 e9615717b361352f6561f22d509bd5b07e37ecf99c2a4081f690276953710d06

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page