Backtest harness
Run the same Strategy class against historical tick data, get a full equity curve and summary stats. The Strategy doesn’t change — only the venues and context underneath. Paper-mode semantics, deterministic, fast.
Why this design
We don’t want a separate “backtest framework” with its own interface — that creates two codebases for one strategy. Instead we keep the Strategy / Venue / Context contracts and swap the implementations beneath them:
- Live:
Polymarket()→ real WS → livePaperContext/LiveContext - Backtest:
HistoricalVenue→ tick stream from aTickSource→BacktestContext
Same Strategy code, same risk envelope, same handlers. Only the plumbing differs.
Run a backtest
from decimal import Decimal
from banger.backtest import Backtester, InMemoryTickSource
from my_strategy import MyStrategy
# Load ticks from wherever — in-memory list, parquet, Polymarket export, ...
source = InMemoryTickSource(ticks=[...])
result = Backtester(
MyStrategy,
source,
starting_capital=Decimal("10000"),
).run()
print(result)
# → BacktestResult(return=$5.67 (0.06%), max_dd=$107.49, trades=118, sharpe=0.022)Tick sources
A TickSource is anything that yields (Market, Tick) pairs in chronological order:
InMemoryTickSource(ticks)— pass a list. Useful for unit tests and small smoke runs.ParquetTickSource(path)— reads a parquet file with our standard schema. (Coming soon — stub raises NotImplementedError today.)PolymarketHistoricalSource(asset_ids, start, end)— pulls from Polymarket’s historical data API. (Coming soon.)
Roll your own
from banger.backtest import TickSource
class MyCsvSource(TickSource):
def __init__(self, path):
self.path = path
async def universe(self):
# Return all Markets present in your data.
...
async def stream(self):
# Async generator yielding (Market, Tick) pairs in time order.
...BacktestResult
Returned by Backtester.run():
result.summary # dict with total_return_usd, max_drawdown_usd, sharpe, win_rate, trades, ...
result.equity_curve # list of (timestamp, equity_usd) pairs
result.fills # list of every paper fill
result.save_csv("equity.csv") # equity curve as CSVDemo
See packages/sdk-python/examples/backtest_demo.py in the repo for a working end-to-end backtest against a synthetic random-walk source.
Caveats
- Paper fills are at the last seen tick price. We don’t model slippage in v0. Real venue execution has slippage; the backtest is mildly optimistic.
- Position sizing via
ctx.kelly()assumeswin_prob = 0.5as a placeholder — pass your own edge calculation if precision matters. - Sharpe in the summary is a crude tick-bucketed approximation. For real analysis, dump the equity curve and compute properly downstream.