A modular backtesting system for crypto trading strategies. Three-step pipeline: fetch data → reconstruct ticks → run backtest.
# First-time setup (creates venv + installs deps)
./backtest.sh setup
# Full pipeline in tmux: fetch April 1, reconstruct, backtest
./backtest.sh run lagbot 2026-04-01 2026-04-01
# Re-attach to see results
./backtest.sh attach
# With custom parameters
./backtest.sh run lagbot 2026-04-01 2026-04-01 --entry-edge-yes 0.06
# Grid search (243 parameter combinations)
./backtest.sh run lagbot 2026-04-01 2026-04-01 --grid
# Stop the session
./backtest.sh stopOr run directly (without tmux):
python -m backtesting run --strategy lagbot --from 2026-04-01 --to 2026-04-01backtesting/
├── cli.py # Entry point (python -m backtesting)
├── config.py # API endpoints, network config, SQLite tuning
├── db.py # SQLite schema + CRUD
├── report.py # Bloomberg-style terminal output (shared)
│
├── fetch/ # Step 1: Data acquisition (shared across strategies)
│ ├── markets.py # Polymarket Gamma API → market metadata
│ ├── fills.py # Goldsky GraphQL → CLOB trade fills
│ └── btc.py # Binance aggTrades → 1-second BTC OHLCV
│
├── strategies/ # Steps 2 + 3: Strategy-specific logic
│ ├── base.py # Trade dataclass + Reconstructor/Backtester protocols
│ └── lagbot/ # BTC/Polymarket lag exploitation strategy
│ ├── config.py # 25+ tunable parameters
│ ├── reconstruct.py # DB → per-second ticks CSV
│ └── backtest.py # Replay entry/exit logic over ticks
│
└── data/ # Generated databases + CSVs
├── 2026-04-01_2026-04-01.db
└── lagbot_2026-04-01_2026-04-01_ticks.csv
Downloads raw data from three sources into a SQLite database.
| Source | API | Data | Resolution |
|---|---|---|---|
| Markets | Polymarket Gamma API | 5-minute window metadata + token IDs | 288 per day |
| Fills | Goldsky GraphQL | CLOB trade fills (price, size, side, aggressor) | ~12K per window |
| BTC | Binance aggTrades daily zips | BTC/USDT OHLCV candles | 1-second |
python -m backtesting fetch --from 2026-04-01 --to 2026-04-01
python -m backtesting fetch --from 2026-03-01 --to 2026-04-01 # multi-day
python -m backtesting fetch --from 2026-04-01 --to 2026-04-01 --source btc # BTC onlyDB is auto-named: data/2026-04-01_2026-04-01.db. Override with --db path.db.
Check data coverage:
python -m backtesting status
python -m backtesting status --db data/2026-04-01_2026-04-01.dbConverts raw DB data into a strategy-specific CSV. Each strategy defines its own reconstruction logic — what features to compute, what resolution, how to handle gaps.
python -m backtesting reconstruct --strategy lagbot --db data/2026-04-01_2026-04-01.dbFor lagbot, this produces one row per second per 5-minute window (86,400 ticks/day):
| Column | Source | Description |
|---|---|---|
timestamp |
BTC candles | ISO 8601 UTC timestamp |
window_id |
Markets table | 5-minute window start (unix seconds) |
seconds_remaining |
Computed | window_end - current_second |
btc_price |
BTC candles | Close price, forward-filled |
window_open_price |
BTC candles | BTC at window start, forward-filled |
realized_vol_ann |
Computed | 10-min rolling stdev of log-returns, annualized |
fair_value |
Computed | Black-Scholes N(d) probability |
poly_yes_price |
Fills (side=up) | Last trade price, forward-filled |
gap |
Computed | fair_value - poly_yes_price |
Replays the strategy's entry/exit logic over the ticks CSV.
# Single run
python -m backtesting backtest --strategy lagbot --ticks data/lagbot_2026-04-01_2026-04-01_ticks.csv
# Grid search
python -m backtesting backtest --strategy lagbot --ticks data/lagbot_*.csv --grid
# Export trades to CSV
python -m backtesting backtest --strategy lagbot --ticks data/lagbot_*.csv --out trades.csvOutput is a Bloomberg-style terminal report: P&L, win rate, profit factor, Sharpe ratio, drawdown, equity curve, exit reason breakdown, signal split (YES/NO), and a trade table.
Tmux wrapper for running pipeline commands in a detached session.
./backtest.sh setup # create venv + install deps
./backtest.sh run lagbot 2026-04-01 2026-04-01 # full pipeline in tmux
./backtest.sh run lagbot 2026-04-01 2026-04-01 --grid # grid search in tmux
./backtest.sh run lagbot 2026-03-01 2026-04-01 --out t.csv # multi-day + export
./backtest.sh fetch 2026-04-01 2026-04-01 # fetch data only
./backtest.sh backtest lagbot data/lagbot_*_ticks.csv # replay only
./backtest.sh attach # re-attach to session
./backtest.sh stop # kill session
./backtest.sh status # show session + data summary
./backtest.sh logs # tail latest log
./backtest.sh clean # remove generated data
./backtest.sh help # show all commandspython -m backtesting <command> [options]
Commands:
run Full pipeline: fetch → reconstruct → backtest
fetch Fetch raw data from external APIs
reconstruct Build strategy-specific ticks CSV from DB
backtest Run strategy over ticks CSV
status Show database coverage report
Global:
-v, --verbose Debug logging
run:
--strategy NAME Strategy to use (required). Available: lagbot
--from DATE Start date YYYY-MM-DD (required)
--to DATE End date YYYY-MM-DD (required)
--db PATH Override DB path (default: auto-generated in data/)
--grid Run parameter grid search instead of single run
--out PATH Export trades CSV
fetch:
--from DATE Start date (required)
--to DATE End date (required)
--db PATH Override DB path
--source TYPE all | markets | fills | btc (default: all)
reconstruct:
--strategy NAME Strategy to use (required)
--db PATH DB path (required)
--out PATH Output CSV path
--windows N Limit to first N windows (for testing)
backtest:
--strategy NAME Strategy to use (required)
--ticks PATH Ticks CSV path (required)
--grid Run parameter grid search
--out PATH Export trades CSV
Strategy-specific parameters are passed as extra flags:
--entry-edge-yes 0.06 --confirm-ticks 3 --take-profit 0.20
(auto-mapped to config dataclass fields by converting kebab-case to snake_case)
Polymarket Gamma API ─┐
├─→ SQLite DB ─→ Strategy Reconstructor ─→ ticks.csv ─→ Strategy Backtester ─→ Report
Goldsky GraphQL ──────┤
│
Binance aggTrades ────┘
These are inherent to backtesting from historical fill data (not bugs):
| Limitation | Impact | Mitigation |
|---|---|---|
| No real orderbook (bid/ask) | Entry/exit prices use assumed_half_spread (1%) |
Conservative: real spread is often tighter |
| No spread filter | Can't reject wide-spread entries | Use assumed_half_spread as proxy |
| BTC at 1-second resolution | Live bot sees 100ms ticks | Backtester operates on 1-second ticks anyway |
| Poly price from fills (not orderbook) | ~54% of seconds have fills, rest forward-filled | 12K fills/window gives good coverage |
| Fair value uses Abramowitz & Stegun CDF | Differs from scipy by ~1e-7 | Negligible for 7%+ gap thresholds |
BTC/Polymarket lag exploitation. Detects when Binance BTC price moves but Polymarket hasn't repriced yet. Uses Black-Scholes fair value to quantify the mispricing and enters when the gap exceeds a threshold.
Entry gates (all must pass): gap size, price zone, velocity filter, BTC momentum, 5-tick confirmation, cooldown, window guard, trade cap, daily loss limit.
Exit conditions (priority order): window rotation, end of window, timeout (30s), structural stop loss, take profit (+25%), stop loss (-40%), convergence (gap closes to 1.5%).
See strategies/lagbot/config.py for all 25+ tunable parameters and GRID for search dimensions.