Dynamic market-making bot for Kalshi with explicit safety controls and operational tooling.
The runtime is a two-step loop:
-
Market selection
- Pull open markets from Kalshi.
- Exclude multivariate/combo markets (
mve_filter=exclude). - Enforce local hard filter to binary-only markets.
- Filter using
min_volume_24handmin_spread_cents. - Score candidates with weighted normalized volume and spread.
- Keep the top
top_nmarkets.
-
Market making
- Run one Avellaneda-Stoikov worker per selected market.
- Each worker computes reservation price, asymmetric quotes, and order sizes.
- Inventory risk aversion increases as inventory approaches limits.
- New risk accumulation is blocked when global portfolio cap is reached.
- Worker manages resting orders on each loop interval.
- Dynamic-only runtime: this project runs from
dynamicconfig. - Deselect cleanup invariant:
- stop worker,
- wait up to
worker_shutdown_timeout_seconds, - cancel all resting orders for that ticker,
- verify cleanup before removing worker state.
- Selector and API requests use retry/backoff handling for transient and rate-limit errors.
- Portfolio controls enforce global and per-market contract caps across all active workers.
-
Install dependencies
uv pip install -e . -
Set environment variables in
.envKALSHI_API_KEY_ID=your_api_key_id KALSHI_PRIVATE_KEY_PATH=/absolute/path/to/your/private-key.key KALSHI_BASE_URL=https://demo-api.kalshi.co/trade-api/v2
Use
https://api.elections.kalshi.com/trade-api/v2for production. -
Run bot
kalshi-mm --config config.yaml
dynamic:
log_level: INFO
risk:
max_global_contracts: 20
max_contracts_per_market: 3
reserve_contracts_buffer: 2
market_selector:
top_n: 6
refresh_seconds: 45
worker_shutdown_timeout_seconds: 15
min_volume_24h: 500
min_spread_cents: 2
volume_weight: 0.35
spread_weight: 0.65
mve_filter: exclude
page_limit: 250
max_pages: 5
max_markets: 1250
# series_ticker: "FED"
market_maker:
max_position: 3
order_expiration: 3600
gamma: 0.2
k: 1.5
sigma: 0.001
T: 28800
min_spread: 0.02
position_limit_buffer: 0.05
inventory_skew_factor: 0.001
trade_side: "yes"
dt: 5.0Realtime account dashboard (money, positions, resting orders):
kalshi-dashboardDashboard options:
kalshi-dashboard --refresh-seconds 1.5 --balance-every-n 10Controls:
- Press
qto quit.
Cancel resting orders across all markets:
kalshi-cancel-allDry-run preview:
kalshi-cancel-all --dry-runScoped cancellation examples:
kalshi-cancel-all --ticker FEDDECISION-24NOV-H0
kalshi-cancel-all --side yes --action buy
kalshi-cancel-all --max-cancels 10Liquidate entire book (cancel resting orders first, then submit flattening orders):
kalshi-cancel-all --liquidate-allDry-run liquidation preview:
kalshi-cancel-all --liquidate-all --dry-runLiquidation controls:
kalshi-cancel-all --liquidate-all --max-liquidations 25 --liquidation-expiration-seconds 120Aggressive multi-round liquidation (recommended when you need out fast):
kalshi-cancel-all --liquidate-all \
--liquidation-rounds 12 \
--liquidation-round-sleep-seconds 1 \
--liquidation-price-offset-cents 2 \
--liquidation-expiration-seconds 20Note: liquidation currently uses signed-yes position convention:
position > 0->sell yesat currentyes_bidposition < 0->buy yesat currentyes_ask
In aggressive liquidation mode, each round:
- cancels remaining resting orders,
- refreshes live positions,
- re-prices flattening orders with configurable aggressiveness,
- repeats until flat or max rounds reached.
Deploy with Fly:
flyctl deploySet secrets:
flyctl secrets set KALSHI_API_KEY_ID=your_api_key_id
flyctl secrets set KALSHI_PRIVATE_KEY_PATH=/app/keys/kalshi-private.key
flyctl secrets set KALSHI_BASE_URL=https://demo-api.kalshi.co/trade-api/v2Logs are written to stdout.
flyctl logs