Kalshi Weather Agent v3

How the whole thing works

An automated weather prediction market trading bot. It watches 6 supercomputers, evaluates hundreds of contracts, and places bets when the math says we have an edge.

225 Ensemble Members
7 Cities
30 Lifetime Trades
+$3.71 Net P&L
01 — The Basics

What is Kalshi?

Kalshi is a prediction market where you bet on real-world events. One of the most popular categories is weather.

Example Bet

"Will the high temperature in Chicago be above 72°F tomorrow?"

Buy YES for 80¢. If it happens, you get $1.00 back — 20¢ profit.
If it doesn't happen, you lose your 80¢.

There are also bracket bets — narrow 2-degree ranges like "Will the high be 72–73°F?" These are harder to hit, which is why the bot treats them differently.

Think of it like a sports bet, except instead of "Will the Lakers win?" it's "Will it be hot in Miami?"
02 — The Strategy

Our Edge

Most people on Kalshi check their phone weather app and guess. We ask six different supercomputers to each run dozens of simulations — giving us 225 independent forecasts.

When 200 out of 225 simulations agree on something, and the Kalshi market price doesn't reflect that confidence, we have an edge. The market is wrong, and we know it.

Imagine asking 225 weather experts. If 200 of them agree it'll be hot, but the betting odds say it's only 50/50 — that's the edge we're looking for.
03 — Ensemble Forecasting

The Brain — 225 Guesses

The bot pulls data from Open-Meteo's ensemble API. Each model runs the same physics simulation dozens of times with slightly different starting conditions. This gives us a probability distribution — not just a single number.

🇪🇺 ECMWF IFS 51 members · 25% weight
🇪🇺 ECMWF AIFS 51 members · 20% weight
🇺🇸 GFS (NOAA) 31 members · 15% weight
🇺🇸 GFS AI 31 members · 15% weight
🇩🇪 ICON Global 40 members · 10% weight
🇨🇦 GEM Global 21 members · 15% weight

Weights aren't equal — the European model (ECMWF) gets the most trust because it has the best track record. AI-enhanced versions of both ECMWF and GFS are also included. If a model returns no data, its weight gets redistributed to the others.

How Probability Works

Question: "Will Chicago's high be above 72°F?"

The bot counts how many simulations from each model predict above 72, then blends them using the weights.

ModelAbove 72°Fof TotalModel Prob× Weight
ECMWF485194%23.5%
ECMWF AI475192%18.4%
GFS273187%13.1%
GFS AI283190%13.5%
ICON354088%8.8%
GEM182186%12.9%
04 — The Rhythm

The Daily Loop

The daemon runs 24/7 on Cheesegrater. It only bets during two windows, then sleeps until the next one.

Trading Windows
WindowLocal TimeBetting On
Morning6:00 AM – 9:00 AMToday's weather
Evening6:00 PM – 11:00 PMTomorrow's weather

Each city's windows use its own timezone. Outside these hours, the bot sleeps.

During each window, the bot runs this loop every 5 minutes:

Check temps
Update tracker
Check fills
Fetch forecasts
Filter contracts
Run 9 checks
Place bets
Notify Discord
Sleep 5m

It also sends a heartbeat when a window opens, runs a daily Claude AI advisor briefing, checks for unfilled orders, and deploys a dashboard update to Cloudflare Pages after each trade.

05 — Coverage

Cities We Watch

Seven US cities, tiered by a 171-day backtest. Coastal cities are more predictable and profitable. Tier 2 cities only bet on highs because their overnight lows are harder to forecast.

🌞
Miami KMIA · Highs + Lows
T1
🌞
Los Angeles KLAX · Highs + Lows
T1
🌥
Philadelphia KPHL · Highs + Lows
T1b
🌥
New York KNYC · Highs + Lows
T1b
🌥
Chicago KMDW · Highs + Lows
T1b
☀️
Austin KAUS · Highs only
T2
☀️
Denver KDEN · Highs only
T2
06 — The Core Logic

The 9-Step Checklist

Every available contract runs through these 9 checks. All 9 must pass or the bot skips it. This is the heart of the engine — rules.js.

Parse the contract

Can we decode the ticker? KXHIGHCHI-26MAR20-T72 = Chicago HIGH above 72°F on March 20. If it's garbled, skip.

Compute probability

Count across all 225 ensemble members. How many predict above/below the threshold? Blend using model weights. Output: a single probability like 89%.

Ensemble agreement check

Are the 225 guesses clustered together or all over the place? If the standard deviation is above 3°F, the bot says "the models disagree too much" and skips. This check is currently blocking everything.

MAX_ENSEMBLE_SD: 3.0 — actual SD: ~7°F
Bracket filter

Bracket bets are 2° ranges ("Will the high be 72–73°F?"). Only allow NO bets on brackets. If there's a >30% chance the bracket hits, skip entirely — not confident enough.

Get market price

Check Kalshi's orderbook. What's the cheapest YES? What's the cheapest NO? Compute depth (how many contracts are available at that price). No orders = skip.

Calculate edge

Compare our probability vs. the market implied probability. If we think it's 89% and the contract costs 80¢ (implying 80%), we might have edge. Accounts for 7% Kalshi taker fee. Need ≥10% edge.

MIN_EDGE: 0.10 (10%)
Win probability check

After picking YES or NO, verify our chosen side has at least 70% win probability. No coin flips. We only bet when we expect to win.

MIN_WIN_PROBABILITY: 0.70 (70%)
Price guardrails

Contract must cost between 30¢ and 75¢. Below 30¢ = longshot that rarely hits. Above 75¢ = too expensive for the profit.

Range: 30¢ – 75¢
Depth check

Are there at least 5 contracts available at the price we want? No point finding a great deal if we can barely buy any.

MIN_DEPTH: 5 contracts
If all 9 steps pass, the bot sizes the bet ($2 flat for now), sends the order to Kalshi, logs it to SQLite, and posts the trade to Discord. If any single step fails, the contract is skipped entirely.
07 — Risk Controls

Safety Guardrails

Even when the checklist says "bet," hard limits in trading.js cap how much can be lost. These are hardcoded — the bot can't override them.

$2 Bet size (flat) Conservative while proving strategy. Kelly sizing planned later.
$5 Max per order Hard ceiling on any single trade, can't be overridden.
5 Max contracts Caps position size per order.
$8/day Daily spend limit $4 per window × 2 windows. Survives restarts (checked in DB).
2 Max trades/city/day Prevents correlated exposure in one city.
7% Taker fee Kalshi's fee, baked into all edge calculations.
Even if the bot finds 50 great bets, it can only spend $8 per day. It's on a strict allowance.
08 — The Stack

Infrastructure

⚙️
Main daemon
Cheesegrater · systemd
💰
Settlement checker
Daily 9AM CT · systemd timer
💬
Discord bot
Kalshi Grok#6332 · inside daemon
🗃️
Trade database
SQLite · trades.db
🌐
Dashboard
Cloudflare Pages
🌡️
Forecasts
Open-Meteo API · 30m cache
✈️
Live temperatures
METAR via Iowa State
Key Files
src/
  daemon.js— main loop, scheduling
  config.js— all settings, station configs
  engine/
    rules.js— the 9-step pipeline
    advisor.js— daily Claude AI review
  weather/
    forecast.js— ensemble fetch + probability
    tracker.js— running high/low tracker
    metar.js— airport weather observations
  kalshi/
    auth.js— RSA-PSS API auth
    markets.js— market/contract search
    trading.js— order placement + guardrails
  storage/
    trades.js— SQLite logging + settlement
  discord/
    bot.js— Grok-powered Discord bot
  notifications/
    discord.js— webhook alerts
09 — Current Issue

The Problem Right Now

Zero trades for 8 days

Step 3 — the Ensemble Agreement Check — is blocking every single contract. The threshold is set to MAX_ENSEMBLE_SD: 3.0 degrees, but the actual standard deviation across stations is roughly 7°F.

3°F Threshold (config)
vs
~7°F Actual SD (reality)

A 5–8°F ensemble spread is completely normal for 24-hour forecasts. The bot made trades in late Feb because there were unusually stable high-pressure days. As spring weather got more active, the spread naturally widened and the filter started blocking everything.

Fix Options

5–6°F
Conservative
Filters truly uncertain days. Allows normal spring weather through.
8°F
Moderate
Only blocks extreme model disagreement (active storm fronts).
Remove
Let other checks handle it
Edge (10%) and win probability (70%) already filter bad bets. SD check may be redundant.
Per-city
Variable by location
Coastal cities (MIA, LAX) get tighter limits. Inland (DEN, AUS) get wider. Matches natural forecast spread.