Backtesting
Execution
Replay historical L2 order books tick by tick. Orders fill against real book depth with queue priority, latency, and slippage. To research whether a signal is profitable over long periods, use Alpha.
Quick Start
Orders fill against real historical liquidity with configurable latency, slippage, and platform-accurate fees.
Parameters
strategyStrategyrequired
Your Strategy subclass instance.
idstr | list[str]required
Market UUID, series slug, condition ID, or list thereof.
initial_cashfloatrequired
Starting capital in USD.
afterstr
Start of replay window (ISO 8601 or ms).
beforestr
End of replay window.
subtypestr | None= null
Backtest a single bet type of a series that bundles several, e.g. "moneyline". See Runs.
feesstr | None= "polymarket"
Trading fee model. Polymarket-accurate by default.
include_tradesbool= true
Include trade events (enables on_trade hook).
latency_msint= 50
Simulated order submission latency.
slippage_bpsint= 0
Additional slippage in basis points.
limit_fill_ratefloat= 0.1
Volume fraction that fills limit orders when queued.
queue_positionbool= false
CLOB queue position simulation for limit orders.
settlement_delay_msint= 5000
Delay before realized settlement cash becomes available (on-chain availability).
auto_mergebool= true
Net matched YES and NO shares back to cash; disable to keep them separate.
data_dirstr | PathLike | None= null
Local Parquet directory for offline replay. Downloads on first run when missing or empty.
progressbool= true
Show progress bars for fetching and backtesting. Auto-disables in non-TTY.
coalescebool | None= null
Trade-aligned compact data path (auto-detected when only on_trade is implemented).
Subtypes, results, saving, and the dashboard are shared across modes; see Runs.
python
from marketlens import MarketLens, Strategy
class ValueBuyer(Strategy):
def on_market_start(self, ctx, market, book):
self._entered = False
def on_trade(self, ctx, market, book, trade):
if self._entered:
return
if book.midpoint < 0.35:
ctx.buy_yes(size=100)
self._entered = True
client = MarketLens()
result = client.backtest(
strategy=ValueBuyer(),
id="btc-up-or-down-5m",
initial_cash=10_000,
after="2026-04-15T01:45:00Z",
before="2026-04-15T01:50:00Z",
)
print(result.summary())Strategies
Subclass Strategy and implement one or more hooks. The engine calls them on each replay tick.
Hooks
on_trade(ctx, market, book, trade)
Called when a trade executes. Primary hook for most strategies.
on_book(ctx, market, book)
Called on every order book update. Higher overhead, finer book granularity.
on_fill(ctx, market, fill)
Called when one of your orders fills.
on_reject(ctx, market, order)
Called when one of your orders is rejected or fails to submit.
on_market_start(ctx, market, book)
Called once when a market begins.
on_market_end(ctx, market)
Called when a market resolves or data ends.
python
class MyStrategy(Strategy):
def on_trade(self, ctx, market, book, trade):
if trade.size > 1000:
ctx.buy_yes(size=100)
def on_book(self, ctx, market, book):
if book.imbalance(5) > 0.4:
ctx.buy_yes(size=200)
def on_fill(self, ctx, market, fill):
ctx.cancel_all()Context
Passed to every hook. Place orders, read positions, and query market state.
Orders
buy_yes(size, limit_price, cancel_after)Order
Buy YES outcome. Market order if no limit_price. cancel_after is absolute timestamp (ms).
buy_no(size, limit_price, cancel_after)Order
Buy NO outcome.
sell_yes(size, limit_price, cancel_after)Order
Sell YES position.
sell_no(size, limit_price, cancel_after)Order
Sell NO position.
split(size)None
Mint equal YES and NO shares for $1 per pair.
merge(size)None
Redeem matched YES and NO shares for $1 per pair.
cancel(order)None
Cancel a specific open order.
cancel_all()None
Cancel all open orders.
State
position()Position
Current position: side, shares, avg_entry_price, unrealized_pnl, realized_pnl.
yes_position()Position
YES shares only. position() returns the net of YES and NO.
no_position()Position
NO shares only.
cashfloat
Available cash balance.
equityfloat
Total equity (cash + positions marked to market).
open_orderslist[Order]
All open orders.
marketMarket
Current market object.
bookOrderBook
Current order book state.
timeint
Current simulation timestamp (ms).
booksdict
All active books by market_id (multi-market).
reference_price()float | None
Spot price of the underlying at current time.
python
# entry: market order or patient limit
if book.spread < 0.03:
ctx.buy_yes(size=500)
else:
ctx.buy_yes(
size=500,
limit_price=book.best_ask,
cancel_after=ctx.time + 30000,
)python
# exit: take profit and clean up
pos = ctx.position()
if pos.shares > 0 and pos.unrealized_pnl > 100:
ctx.sell_yes(size=pos.shares)
ctx.cancel_all()Order Book
L2 book state at the current replay tick. Properties: best_bid, best_ask, spread, midpoint, bid_depth, ask_depth.
Methods
impact(side, size)float | None
VWAP for a hypothetical order of the given size.
slippage(side, size)float | None
Price slippage from midpoint for the given size.
depth_within(spread)tuple[float, float]
Bid and ask liquidity within spread of mid.
microprice()float | None
Size-weighted midpoint using top-of-book.
weighted_midpoint(n)float | None
Size-weighted mid using top n levels.
spread_bps()float | None
Bid-ask spread in basis points.
imbalance(levels)float | None
Volume imbalance [-1, 1]. Positive = bid-heavy.
python
cost = book.impact("BUY", 500)
if book.imbalance(5) > 0.3 and cost and cost < 0.65:
ctx.buy_yes(size=500)