Automated trading bot for Kalshi's BTC 15-minute prediction markets. Combines Order Book Imbalance (OBI) and Rate of Change (ROC) momentum signals with ATR-based volatility regime filtering, automated risk management, and a real-time dashboard.
Real-time dashboard: equity curve, BTC price with EMA overlays, OBI / ATR regime indicators, and live trade attribution. The screenshot above shows a paper-trading session — 315 trades, 49.5% win rate, +$10,648 cumulative PnL.
Coinbase Spot WS ──┐
├─→ Coordinator ──→ Strategies ──→ Resolver ──→ Execution
Kalshi Order Book ──┘ │ │ │
│ ATR Regime Paper / Live
│ Filter Trader
▼
┌─── Dashboard ───┐
│ Equity Chart │
│ BTC Price │
│ Signals/OBI │
│ Trade History │
│ Attribution │
│ Backtest Viz │
│ System Health │
└─────────────────┘
| Directory | Purpose |
|---|---|
backend/strategies/ |
OBI and ROC signal generators, signal conflict resolver |
backend/filters/ |
ATR volatility regime filter (gates entries in HIGH regimes) |
backend/risk/ |
Position sizer (fixed fractional) and circuit breaker (daily/weekly/drawdown limits) |
backend/execution/ |
Paper trader (simulated fills) and live trader (Kalshi REST API) |
backend/data/ |
Kalshi WebSocket, Coinbase spot feed, candle aggregator |
backend/backtesting/ |
Contract-price simulation engine, walk-forward optimizer, auto-tuner, attribution, reports |
backend/monitoring/ |
Signal health/decay monitoring (IC, win rate drift, Sharpe drift) |
backend/api/ |
FastAPI REST endpoints and WebSocket feed for the dashboard |
frontend/ |
React + TypeScript + Tailwind CSS dashboard with TradingView charts |
- Data ingestion -- Coinbase spot price + Kalshi order book stream into the coordinator
- ATR regime check -- If volatility is HIGH, block all new entries
- OBI evaluation -- Order book imbalance above 0.65 = bullish, below 0.35 = bearish
- ROC evaluation -- 15-minute price rate of change confirms momentum direction
- Resolver -- OBI + ROC must agree or at least not conflict; conviction level set (HIGH/NORMAL/LOW)
- Position sizing -- Fixed fractional sizing scaled by conviction and drawdown state
- Execution -- Paper trader records simulated fill; live trader places real Kalshi orders
- Docker and Docker Compose
- Node.js 18+ (for frontend development)
- Kalshi API key with RSA private key
- Python 3.11+ (for local backtesting)
# Clone and configure
cp .env.example .env
# Edit .env with your Kalshi API credentials and Discord webhooks
# Start database and bot
docker compose up -d
# Frontend development server (hot reload)
cd frontend && npm install && npm run devThe dashboard is available at http://localhost:5173 (dev) or http://localhost:8001 (served from FastAPI).
# Deploy to DigitalOcean droplet
./scripts/deploy.sh botuser@your-server-ipThe deploy script rsyncs the project, adjusts ports for production, and rebuilds the container on the remote host. The frontend is pre-built and served as static files from backend/static/.
State-mutating endpoints (/api/trading-mode, /api/emergency-stop, /api/close-all-exchange-positions, /api/reset-drawdown, /api/trade-limit, /api/trading-pause, DELETE /api/param-overrides) require a bearer token via the Authorization: Bearer <token> header. GET endpoints are unauthenticated.
Set DASHBOARD_API_TOKEN in the production .env (generate with python3 -c "import secrets; print(secrets.token_urlsafe(32))"). The dashboard prompts for the token on the first protected action and persists it in localStorage. To rotate, update the env var and restart; users will be re-prompted on their next mutating action. To "log out" a browser, clear localStorage from devtools.
When DASHBOARD_API_TOKEN is empty (default in dev), auth is disabled and all endpoints are open. The bot logs a warning at startup if BOT_ENV=production but the token is unset.
curl example:
curl -X POST http://your-host:8000/api/emergency-stop \
-H "Authorization: Bearer $DASHBOARD_API_TOKEN"Daily pg_dump backups run at 03:30 UTC via cron on the droplet, push to a DigitalOcean Spaces bucket (~$5/mo), and post a status line to the #kbtc-errors Discord channel. Local copies are kept 7 days; remote copies 30 days via Spaces lifecycle policy. See docs/runbooks/database-backups.md for one-time setup, the restore drill, and full disaster recovery procedure.
The bot supports two modes, switchable from the dashboard sidebar:
- Paper (default) -- Simulated fills, no real money. Uses its own bankroll, position sizer, and circuit breaker instance.
- Live -- Real orders via Kalshi REST API. Separate bankroll tracking. Requires confirmation and checks for open positions before switching.
A trading pause button on the dashboard halts new entries while still allowing open positions to exit normally.
The bot's backtesting and tuning capabilities mature as more live data accumulates:
| Milestone | Data Required | What Unlocks |
|---|---|---|
| Now | candles + ob_snapshots + kalshi_trades from DB |
Backtest strategy logic on Kalshi contract prices (1-99c) with production guard parity |
| ~3 weeks | 2,000+ live candles | Auto-tuner activates (runs every 6h); walk-forward with 1 window |
| ~2 months | 8,000+ live candles | Walk-forward with multiple windows; statistically meaningful parameter optimization |
| ~3 months | 12,000+ live candles + daily attribution history | Signal drift detection; session/regime profitability trends; full attribution time series |
cd backend
# Run the contract-price backtester from DB data (default path)
python -m backtesting run --from-db --symbol BTC --series KXBTC --source live_spot,binance
# Optional: CSV-only candle replay (no contract timeline unless --ob-csv is supplied)
python -m backtesting run --csv ../data/candles_btc_15m.csv
# Walk-forward optimization
python -m backtesting walk-forward --from-db --symbol BTC --series KXBTC
# Manual tuning cycle
python -m backtesting tune --from-db
# Generate HTML report from existing JSON
python -m backtesting report --input backtest_reports/latest.jsonResults land in backend/backtest_reports/ and are visible in the dashboard's Backtest Results panel. Full interactive HTML reports are accessible via the "Open full HTML report" button.
Legacy spot-price simulation is retained only for reference at backend/backtesting/spot_backtester.py and is marked deprecated.
# Replay paper trades and score parity
python ../scripts/backtest_parity_check.py \
--start 2026-05-05 --end 2026-05-07 \
--output ../backtest_reports/parity_check_latest.json
# HARD_STOP sweep + counterfactual analysis
python ../scripts/backtest_hard_stop_sweep_v2.py \
--start 2026-04-16 --end 2026-05-06 \
--counterfactual-start 2026-05-05 --counterfactual-end 2026-05-07 \
--output ../backtest_reports/hard_stop_sweep_v2.json
# Joint walk-forward optimization for hard stop / cooldown / health threshold
python ../scripts/walk_forward_contract_optimizer.py \
--start 2026-04-16 --end 2026-05-06 \
--output ../backtest_reports/walk_forward_contract_joint.jsonThe coordinator runs a tuning cycle every 6 hours:
- Loads live candle and order book data from the database
- Builds a parameter search grid around current settings
- Runs walk-forward optimization with train/test splits
- Evaluates whether the recommendation passes safety thresholds
- Posts results to Discord; does not auto-apply by default
Parameter overrides can be viewed and cleared from the dashboard or via the API (GET/DELETE /api/param-overrides).
- Daily (00:05 UTC) -- Queries previous day's trades, runs full PnL attribution, persists to
daily_attributiontable, posts to Discord#kbtc-attribution - Weekly (Sunday 00:10 UTC) -- Aggregates the week's daily attribution, detects session/regime drift (profitable-to-unprofitable flips), posts digest to Discord
Attribution breaks down PnL by conviction level, trading session (Asia/London/US), ATR regime, exit reason, and fee drag. Visible in the dashboard's PnL Attribution panel.
The bot passively collects 14 entry-time features at trade entry, labels outcomes at exit, and enriches each trade with MFE (max favorable excursion) and MAE (max adverse excursion) during the trade. This data feeds an XGBoost entry filter that learns to gate bad entries and size good ones more aggressively.
| Phase | Status | Data Required | Deliverables |
|---|---|---|---|
| Phase 0 | Active | None (runs immediately) | MFE/MAE columns added to trade_features; tracked on every tick while a position is open; flushed to DB at exit |
| Phase 1 | Accumulating | 500+ labeled paper trades | Paper bot runs 24/7 with circuit breakers disabled; Discord alert fires at 500 trades; checkpoint analysis at 300 |
| Phase 2 | Pending | Phase 1 complete | Export trade_features, train XGBoost with 5-fold stratified CV, tune threshold for OOS precision >= 0.58, serialize to ml/models/xgb_entry_v1.pkl |
| Phase 3 | Pending | Phase 2 complete | ml/inference.py loads model at startup; ml_gate() called after resolver fires; p_win feeds conviction override in position sizer |
| Phase 4 | Pending | Phase 3 complete | Shadow mode on paper for 1 week; compare gate vs baseline win rate; promote to live if >= 5pp improvement confirmed |
| Category | Features |
|---|---|
| Signal | obi, roc_3, roc_5, roc_10 |
| Volatility / Microstructure | atr_pct, spread_pct, bid_depth, ask_depth |
| Candle Context | green_candles_3, candle_body_pct, volume_ratio |
| Time Context | time_remaining_sec, hour_of_day, day_of_week |
| Trade Quality (exit-time) | max_favorable_excursion, max_adverse_excursion |
| Label | label (-1/0/+1), pnl |
- Fail-open: If the model file is missing or inference throws,
ml_gate()returns(True, 0.5)-- the trade proceeds as if the gate was not present - No feature leakage: Only entry-time features are passed to
ml_gate()during live inference; MFE/MAE improve label quality during training but are not available at entry - Paper-first: The ML gate is validated on paper trading before promotion to live
# Export labeled data from the remote DB (set KBTC_DEPLOY_HOST=user@host)
ssh "$KBTC_DEPLOY_HOST" "docker exec kbtc-db psql -U kalshi -d kbtc \
-c \"COPY (SELECT * FROM trade_features WHERE label IS NOT NULL) TO STDOUT CSV HEADER\"" \
> trade_features_export.csv
# Train the model
python scripts/train_xgb.py --csv trade_features_export.csvThe dashboard is a single-page React app at the server's root URL.
| Section | What It Shows |
|---|---|
| Sidebar | Bankroll, drawdown, daily/weekly loss, trade count, paper/live toggle, trading pause |
| Equity / BTC Price | Equity curve (PnL or account value) and BTC candlestick chart with time range filtering |
| Signal Panel | Live OBI %, bid/ask volumes, spread, spot price, ATR regime |
| System Health | WebSocket connection status, tick/candle counts, reconnect attempts |
| Position Table | Open position, closed trade history (paginated), errored/quarantined trades |
| Additional Stats | Best/worst/avg trade, daily PnL bar chart, win rate by regime |
| PnL Attribution | Conviction/session/regime breakdown tables, fee drag summary |
| Backtest Results | Latest backtest metrics, overfitting warnings, walk-forward recommendation, HTML report link |
Five webhook channels, each independently configurable:
| Channel | Events |
|---|---|
#kbtc-trades |
Trade opened, trade closed |
#kbtc-risk |
Circuit breaker tripped/cleared, ATR regime change, sizing failure |
#kbtc-heartbeat |
Periodic heartbeat, 4h/24h performance summaries |
#kbtc-errors |
Bot start/stop, WebSocket disconnect, DB errors, quarantined trades |
#kbtc-attribution |
Daily attribution report, weekly attribution digest |
- Position sizing: Fixed fractional (2% risk per trade), scaled by conviction (HIGH 1.3x, NORMAL 1.0x, LOW 0.65x) and reduced 50% during drawdowns
- Circuit breaker: Halts trading when daily loss exceeds 6%, weekly loss exceeds 15%, or drawdown exceeds 20%
- ATR regime filter: Blocks all new entries during HIGH volatility periods
- Stop loss: Hard 2% stop on every position
- Rapid-fire detection: Quarantines trades if 3+ exits occur within 60 seconds (prevents feedback loops)
PostgreSQL with TimescaleDB. Key tables:
| Table | Purpose |
|---|---|
candles |
15-minute OHLCV from live feeds and Binance |
ob_snapshots |
Order book depth snapshots (every 30s) |
trades |
All completed trades with full metadata |
bankroll_history |
Equity curve snapshots |
signal_log |
Every signal evaluation (OBI/ROC/regime/decision) |
daily_attribution |
Daily PnL attribution snapshots |
param_recommendations |
Auto-tuner recommendations |
trade_features |
ML feature snapshots at entry, labeled at exit with MFE/MAE |
bot_state |
Key-value store for runtime state (bankroll, param overrides) |
The bot includes a two-layer validation system to ensure orphan/phantom position handling works correctly before live trading resumes after any position management changes.
Deterministic tests that replay exact Kalshi API response sequences from real production incidents. Each test asserts that the current code handles the scenario correctly.
cd backend && python3 -m pytest tests/replay/ -vCovers:
- Settlement verify failure (Trade 451/454) — no orphan created when verify exhausts retries
- Phantom accumulation (BUG-015) — 70+ reconciliation cycles do not inflate contract count
- Restart persistence —
_settled_tickerssurvives snapshot/restore cycle - Exit cooldown race — recently-exited ticker skipped for 90s during reconciliation
- Orphan-to-trade dedup — duplicate trade within 5min window is skipped
- Full lifecycle — enter → settle → restart → reconcile = 0 orphans
An isolated canary stack running the exact same live code path against Kalshi's demo API with a small bankroll. Validates that no orphans, desyncs, or duplicates occur under real market conditions.
# Launch canary stack on the droplet
bash scripts/canary_up.sh
# Check health
bash scripts/canary_status.sh
# After 72h, run the validation report
bash scripts/canary_report.sh
# Tear down
bash scripts/canary_down.sh # preserve data
bash scripts/canary_down.sh --wipe # full resetThe canary runs on ports 8100 (API) and 5434 (DB), fully isolated from production.
- Run replay suite → all tests must pass
- Deploy canary →
bash scripts/canary_up.sh - Wait 72 hours
- Run canary report →
bash scripts/canary_report.sh - If all gates pass → safe to unpause live trading
- Tear down canary →
bash scripts/canary_down.sh
See scripts/PROMOTION_GATES.md for the full gate definitions.
cd backend && python -m pytest tests/ -vUnit tests cover: position sizer, circuit breaker, OBI strategy, ROC strategy, signal resolver, candle aggregator, ATR regime filter, paper trader, orphan incident replay, price guard, and trend guard.
See .env.example for the full list. Key variables:
| Variable | Description |
|---|---|
KALSHI_API_KEY_ID |
Kalshi API key |
KALSHI_PRIVATE_KEY_PATH |
Path to RSA private key for request signing |
KALSHI_ENV |
demo or prod |
TRADING_MODE |
paper or live |
INITIAL_BANKROLL |
Starting bankroll in dollars |
DISCORD_*_WEBHOOK |
Webhook URLs for trades, risk, heartbeat, errors, attribution channels |
TUNING_INTERVAL_HOURS |
How often the auto-tuner runs (default: 6) |
