Strategy Guide

Replay strategies against historical L2 order book state with tick-by-tick execution simulation. Unlike price-series backtests, orders fill against actual book depth with real liquidity constraints.

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.
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).
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).
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.
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.
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()

OrderBook

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)

Multi-Market

Pass a list of IDs to replay multiple markets with shared capital. Use ctx.books to access all active order books.

See Examples for complete strategy implementations.

python
result = client.backtest( strategy=MyStrategy(), id=[ "eth-up-or-down-5m", "sol-up-or-down-5m", ], initial_cash=50_000, after="2026-04-15T01:45:00Z", before="2026-04-15T01:50:00Z", )

Summary

Access metrics directly as properties or as a dict via result.summary().

Summary Metrics
total_pnlfloat
Net profit/loss in USD.
total_returnfloat
Return as a fraction (0.034 = 3.4%).
win_ratefloat
Fraction of winning trades.
sharpe_ratiofloat | None
Annualized Sharpe ratio.
sortino_ratiofloat | None
Annualized Sortino ratio.
max_drawdownfloat
Maximum peak-to-trough drawdown as fraction of capital.
profit_factorfloat
Gross profit / gross loss.
expectancyfloat
Average profit per trade.
avg_holding_msint
Average holding time (ms).
capital_utilizationfloat
Average fraction of capital deployed.
fee_drag_bpsfloat
Total fees as bps of traded volume.
jsonResponse
{ "total_pnl": "342.5000", "total_return": "3.43%", "win_rate": "68.00%", "sharpe_ratio": "2.14", "sortino_ratio": "3.01", "max_drawdown": "1.80%", "profit_factor": "2.35", "expectancy": "7.2872", "avg_holding_ms": 720000, "capital_utilization": "42.00%", "total_trades": 47, "total_fees": "38.4200", "fee_drag_bps": "8.20" }

Saving

Save a result to disk and reload it later. Loaded results are read-only with no live portfolio.

Methods
save(path, overwrite=False)Path
Write to a new directory. Raises FileExistsError unless overwrite=True.
load(path)BacktestResult
Reconstruct a result from a saved directory.
Files
manifest.json
Run config, targets, and computed metrics.
trades.parquet
One row per fill.
orders.parquet
One row per order.
settlements.parquet
One row per settled market.
equity.parquet
Equity curve snapshots.
python
from marketlens.backtest import BacktestResult result: BacktestResult = client.backtest(...) result.save("runs/btc-fader") loaded = BacktestResult.load("runs/btc-fader") loaded.summary()

Dashboard

Open a local browser dashboard with equity curve, drawdown, PnL by market, PnL distribution, trade timeline, order analysis, settlements table, and run config. Pass additional results to overlay.

Methods
show(*others, labels, title, open_browser)None
Render the result. Pass other backtest results to overlay runs.
dashboard(*paths, labels, title, open_browser)None
Load saved runs from disk and render them.
Arguments
labelslist[str] | None= null
Display name per run.
titlestr | None= null
Header title.
open_browserbool= true
Open the default browser automatically.
python
from marketlens.backtest import BacktestResult result.show() # overlay a second run result.show(other, labels=["baseline", "tuned"]) # load saved runs from disk BacktestResult.dashboard("runs/baseline", "runs/tuned")