Repository navigation
1. Setup Guide
This guide provides end-to-end instructions for provisioning a DigitalOcean Droplet, configuring automated GitHub Actions deployments via Doppler, running day-to-day Droplet operations, and setting up Grafana Cloud telemetry.
- Log into the DigitalOcean Dashboard.
- Click Create > Droplets.
-
Region: Choose New York (
NYC1orNYC3). Kalshi's API infrastructure is hosted in AWSus-east-1(N. Virginia/NYC area); New York hosting minimizes network execution latency. - OS Image: Select Ubuntu (24.04 LTS or latest).
- Droplet Type: Basic.
- CPU Options: Regular ($6/month plan with 1GB RAM is sufficient).
- Authentication: Select SSH Key. The private key associated with this SSH key is required for GitHub Actions authentication.
- Click Create Droplet and record the public IPv4 address.
SSH into the server:
ssh root@<YOUR_DROPLET_IP>Install Docker using the official installation script:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.shVerify that the Docker service is running:
sudo systemctl status dockerCreate the application directory:
mkdir -p ~/Kalshi-Trading-BotIn GitHub, navigate to Settings > Secrets and variables > Actions > New repository secret, and add:
-
DROPLET_IP: The public IPv4 address of the Droplet. -
SSH_USERNAME: Set toroot. -
SSH_PRIVATE_KEY: The complete private SSH key corresponding to the public key registered on the Droplet. -
DOPPLER_TOKEN: The Doppler production service token (starts withdp.st.prd.) to inject runtime secrets. -
PAT_GHCR: A GitHub Personal Access Token withread:packagespermission to pull images from GHCR (fine-grained personal access token recommended for least privilege).
Trigger the deployment pipeline by pushing code to main:
git push origin mainThe GitHub Actions workflow builds the image, pushes it to GHCR, transfers docker-compose.yml to the Droplet, installs Doppler if missing, and launches the container stack via doppler run -- docker compose up -d.
All container management on the server should be done cleanly to avoid evaluating ${DB_PASSWORD} without Doppler context.
Use direct Docker commands referencing the container name (kalshi-bot) to query the Docker daemon directly without parsing docker-compose.yml:
-
Check the selected market and discovery logs:
docker logs kalshi-bot | grep -i "Selected Market"
-
Stream live execution, order placements, and fills:
docker logs -f kalshi-bot
-
Inspect the last 100 log lines:
docker logs --tail=100 kalshi-bot
-
Restart the trading bot container:
docker restart kalshi-bot
(Or via Doppler:
cd ~/Kalshi-Trading-Bot && doppler run -- docker compose restart bot) -
Stop the bot safely:
docker stop kalshi-bot
(Triggers the synchronous kill switch on SIGTERM before stopping).
-
Teardown the full stack (Bot + Database):
cd ~/Kalshi-Trading-Bot && doppler run -- docker compose down
The database container (kalshi-bot-db) stores order execution history on a persistent host volume (postgres_data).
To inspect database orders directly from the Droplet:
cd ~/Kalshi-Trading-Bot
doppler run -- docker compose exec db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;"'The bot exposes Prometheus metrics locally on loopback port 8000 via utils/metrics.py. Grafana Alloy runs as a system daemon on the Droplet, scraping port 8000 and streaming telemetry to your hosted Grafana Cloud account.
SSH into the Droplet and install Alloy:
sudo apt-get update
sudo apt-get install -y apt-transport-https software-properties-common wget
sudo mkdir -p /etc/apt/keyrings/
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install grafana-alloy-
Copy
alloy.config/config.alloyto/etc/alloy/config.alloy. -
Configure your Grafana Cloud Prometheus remote write credentials:
- In
/etc/alloy/config.alloy:- Replace
<your_grafana_cloud_prometheus_remote_write_url>with your stack's remote-write push URL (from Grafana Cloud under Prometheus > Details / Send Metrics, e.g.,https://prometheus-prod-XX-prod-us-east-X.grafana.net/api/prom/push). - Replace
<your_grafana_cloud_prometheus_username>with your numeric Prometheus instance ID.
- Replace
- In
-
Configure the API key in
/etc/default/alloy:echo 'GRAFANA_API_KEY="<your_grafana_cloud_api_key>"' | sudo tee -a /etc/default/alloy
-
Restart and enable Alloy:
sudo systemctl restart alloy sudo systemctl enable alloy -
Verify that Alloy is running and healthy:
sudo systemctl status alloy journalctl -u alloy.service -n 50 --no-pager
Create a dashboard in Grafana Cloud with the following PromQL queries:
| Panel Title | Metric Query | Visualization | Description |
|---|---|---|---|
| Total Orders Placed | sum(orders_placed_total) |
Stat | Total limit orders submitted and accepted by the exchange. |
| Buy Orders Placed | sum(orders_placed_total{action="buy"}) |
Stat | Total buy orders submitted and accepted. |
| Sell Orders Placed | sum(orders_placed_total{action="sell"}) |
Stat | Total sell orders submitted and accepted. |
| Realized PnL ($) | kalshi_realized_pnl_cents / 100 |
Stat / Time Series | Net profit/loss locked in from closed round trips minus fees. |
| Unrealized MTM PnL ($) | kalshi_unrealized_pnl_cents / 100 |
Stat / Time Series | Floating mark-to-market gain/loss on open lots vs orderbook mid. |
| Total Strategy PnL ($) | (kalshi_realized_pnl_cents + kalshi_unrealized_pnl_cents) / 100 |
Stat / Time Series | Total economic performance across open and closed inventory. |
| Total Exchange Fees ($) | kalshi_total_fees_cents / 100 |
Stat | Total trading transaction fees paid to Kalshi. |
| Round Trip Trade Outcomes | kalshi_round_trips_total |
Bar Chart / Pie Chart | Total completed round trips categorized by profit, loss, or scratch. |
| Cash Balance ($) | bot_pnl_cents / 100 |
Time Series | Real-time bot cash balance in USD from exchange REST hydration. |
| Net Inventory Position | bot_inventory_net_position |
Time Series | Net contract exposure on active market (q). |
| Kalshi API Latency | rate(kalshi_api_latency_seconds_sum[1m]) / rate(kalshi_api_latency_seconds_count[1m]) * 1000 |
Time Series | Rolling REST execution roundtrip latency (ms). |