Skip to main content

Python SDK for Polymarket order book data and backtesting. Tick-level L2 snapshots, billions of deltas, full book reconstruction, and a strategy backtesting engine with realistic execution.

Project description

marketlens

Backtest prediction market strategies on tick-level L2 order book data from Polymarket.

pip install marketlens

Backtest

Define a strategy, run it against any market or series — the engine replays full L2 book state tick-by-tick with realistic execution.

from marketlens import MarketLens
from marketlens.backtest import Strategy

class SpreadTimer(Strategy):
    def on_market_start(self, ctx, market, book):
        self._entered = False

    def on_book(self, ctx, market, book):
        if self._entered:
            return
        if book.spread_bps() and book.spread_bps() < 300:
            ctx.buy_yes(size="200")
            self._entered = True

client = MarketLens()  # uses MARKETLENS_API_KEY env var
result = client.backtest(
    SpreadTimer(), "sol-up-or-down-hourly",
    initial_cash="10000",
    after="2026-03-05T10:00Z", before="2026-03-05T14:00Z",
)
print(result)

Pass a market ID, series slug, or a list of series for multi-asset portfolios:

# Single market
result = client.backtest(strategy, market_id, initial_cash="10000")

# Rolling series — walks every market in the series chronologically
result = client.backtest(strategy, "btc-up-or-down-5m", initial_cash="10000",
                         after="2026-03-05", before="2026-03-06")

# Multi-asset portfolio — shared capital across series
result = client.backtest(strategy,
    ["btc-up-or-down-5m", "eth-up-or-down-5m", "sol-up-or-down-5m"],
    initial_cash="10000", after="2026-03-05", before="2026-03-06")

# Structured product — parallel replay of all strike markets in the series
result = client.backtest(strategy, "btc-multi-strikes-weekly", initial_cash="10000")

Execution realism

Parameter Default Description
latency_ms 50 Order-to-fill delay in milliseconds
queue_position False CLOB queue modeling — fills only when queue-ahead is drained by trades
limit_fill_rate 0.1 Fraction of trade size filling your limit (ignored when queue_position=True)
slippage_bps 0 Extra price penalty on market order fills
fees "polymarket" Auto-detects crypto vs sports fee schedule; 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 fills and on_trade)
settlement_delay_ms 5000 Delay before filled tokens become sellable (on-chain settlement)

The portfolio automatically handles CTF merge (opposite-side netting): buying NO while holding YES nets matched pairs at $1 per share. No explicit merge call needed in backtests.

Strategy hooks

Hook Called when
on_book(ctx, market, book) Every book state change (snapshot or delta)
on_trade(ctx, market, book, trade) Every executed trade
on_fill(ctx, market, fill) Your order is filled
on_market_start(ctx, market, book) A new market begins
on_market_end(ctx, market) A market ends, before settlement

ctx provides: buy_yes(), sell_yes(), buy_no(), sell_no(), cancel_order(), cancel_all_orders(), position(), open_orders, books (all active order books), and reference_price() (Binance spot for crypto underlyings).

Results

result.total_pnl            # net P&L
result.total_return         # as decimal (0.12 = 12%)
result.win_rate             # fraction of profitable settlements
result.sharpe_ratio         # per-settlement Sharpe
result.sortino_ratio        # downside-adjusted
result.max_drawdown         # peak-to-trough as fraction
result.profit_factor        # gross wins / gross losses
result.expectancy           # avg net P&L per settlement

result.trades_df()          # per-fill DataFrame
result.orders_df()          # per-order DataFrame
result.settlements_df()     # per-market settlement P&L
result.equity_df()          # equity curve time series
result.by_series()          # per-series P&L attribution

Data

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

Order book replay

walk() replays full L2 book state for any market or series. Pass a market ID, series slug, or condition ID — the same interface for everything.

for market, book in client.orderbook.walk("btc-up-or-down-5m", status="resolved"):
    print(market.question, book.midpoint, book.spread_bps())

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

Candles, trades, markets

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

Bulk export

Download full history as Parquet — snapshots, deltas, and trades in a single file.

# Single market
path = client.exports.download(market_id)

# All markets in a series
data_dir = client.exports.download_series(
    "btc-up-or-down-5m", after="2026-03-01", before="2026-03-08")

# Reference trades for a crypto underlying
client.exports.download_reference("BTC", after="2026-03-01", before="2026-03-08", path=data_dir)

Offline backtesting

Download once, run many backtests without API calls:

data_dir = client.exports.download_series(
    "btc-up-or-down-5m", after="2026-03-01", before="2026-03-08")
client.exports.download_reference("BTC", after="2026-03-01", before="2026-03-08", path=data_dir)

result = client.backtest(
    strategy, "btc-up-or-down-5m",
    data_dir=data_dir,
    after="2026-03-01", before="2026-03-08",
    initial_cash="10000",
)

Structured Products & Surfaces

For multi-strike series (survival, density, barrier), all sibling markets replay in parallel. walk.books holds the latest book for every strike, and walk.surface() fits the implied probability distribution at each tick.

walk = client.orderbook.walk("btc-multi-strikes-weekly")
for market, book in walk:
    surface = walk.surface()
    if surface:
        for s in surface.survival_strikes():
            print(f"${s.strike:,.0f} P(above)={s.fitted_prob:.3f}")
        print(f"implied_mean=${float(surface.implied_mean):,.0f}")
Type Source Stats
survival "above $X" multi-strike markets implied_mean, implied_cv, implied_skew
density Neg-risk range + tail markets implied_mean, implied_cv, implied_skew
barrier Hit-price reach/dip markets implied_peak, implied_trough

Pre-computed surfaces updated every 5 minutes are also available via client.signals.surfaces().

OrderBook

Every OrderBook instance — live or replayed — carries analytical methods:

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(levels=3)       # bid/ask imbalance [-1, 1]
book.impact("BUY", "1000")     # VWAP for $1k market buy
book.slippage("BUY", "1000")   # slippage from mid
book.depth_within("0.02")      # (bid, ask) depth within 2c of mid

Reference Prices

Binance spot at 1-second resolution for crypto underlyings (BTC, ETH, SOL, XRP, etc.). Available directly or inside backtests via ctx.reference_price().

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

API Reference

Resource 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() trades()
client.exports download() download_series() download_reference()

Async: use AsyncMarketLens — every method has an async counterpart.

Examples

Example Description
backtest_basic.py Spread-timing strategy on a rolling series
backtest_limit_orders.py Market-making with CLOB queue position simulation
backtest_surface.py Surface mispricing with spot-distance filtering
backtest_portfolio.py Multi-series portfolio with shared capital
execution_cost.py Book depth, spread, impact and slippage
microstructure.py Feature matrix — does imbalance predict outcome?
implied_surfaces.py Survival, density, and barrier surfaces
event_strikes.py Structured product walk with live surface fitting

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-1.0.6.tar.gz (63.8 kB view details)

Uploaded Source

Built Distribution

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

marketlens-1.0.6-py3-none-any.whl (58.8 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for marketlens-1.0.6.tar.gz
Algorithm Hash digest
SHA256 07a3cf8156b31a2abe68d17cc01c2ea01203f37d007e7f844fbeb5325be6b507
MD5 fd64ad732ca14ddb99dd6762741fd2fb
BLAKE2b-256 f959b313fbfa891164a6f9fdbe091d6bb23c8132ef2b0c9a2ada768524bcb577

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for marketlens-1.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 87478b795c9dfe8f100c8dfba4d9216c71ba7d11592996324e8c205efa1a599b
MD5 aec64ac2c2701408bd4efbe0c3fc7edf
BLAKE2b-256 f88d0d29da7019b9b647aad5c17fd9f90cd215362e36a414e225fdb4efbf5710

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