Backtesting
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.
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.
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.
# 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,
)# 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.
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.
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().
{
"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.
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.
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")