What Is This?
This is an automated scanner that identifies potentially mispriced daily high temperature contracts on Kalshi, a CFTC-regulated prediction market exchange. It compares probability estimates derived from National Weather Service forecasts against live Kalshi market prices to find contracts where the market may disagree with the NWS forecast.
How Kalshi Weather Markets Work
Kalshi offers daily high temperature markets for major US cities. Each market asks a question like "Highest temperature in NYC today?" and offers multiple brackets (e.g., ≤48°F, 49-50°F, 51-52°F, ≥57°F). Each bracket is a YES/NO contract that settles at $1.00 if the actual NWS-reported high falls in that range, or $0.00 if it does not.
Prices range from 1¢ to 99¢ and represent the market's implied probability. Settlement is based on the official NWS Daily Climate Report from that city's designated weather station.
Cities Tracked
The scanner monitors all active Kalshi weather markets. Each city settles on a specific NWS weather station:
| City | NWS Station | Kalshi Series |
|---|
The Probability Model
Core Concept
We model the actual daily high temperature as a normally distributed random variable centered on the NWS forecast high. The standard deviation is calibrated using historical forecast error data for each station. We then calculate the probability that the temperature falls within each Kalshi bracket by integrating the normal CDF across the bracket bounds.
Step 1: Forecast Mean
- NWS forecast high/low — from the official NWS point forecast. For HIGH markets, the daytime period high. For LOW markets, the minimum of the preceding night and following night periods (matching Kalshi's midnight-to-midnight settlement window).
- Auto-calibrated bias — an exponential moving average of recent forecast errors per city. Updated automatically after each settlement:
new_bias = 0.3 × latest_error + 0.7 × old_bias. Activates after 3 settlements. Before that, a static default is used. - Trend adjustment — if current observations show the day running hotter or colder than forecast, the mean is nudged. For HIGH markets, this applies during 8am-4pm ET weighted by time of day. For LOW markets, if the observed minimum is already below the forecast low, the mean shifts down aggressively (70-90% weight depending on whether temps are still falling or rising).
Step 2: Standard Deviation (Sigma)
Sigma represents how uncertain the model is. A larger sigma means wider probability spreads and fewer confident signals. The final sigma is computed from four stacked multipliers:
Base Sigma (Auto-Calibrated)
Instead of hardcoded values, the base sigma is learned from data:
- 90-day backtest — on startup, we pull 90 days of historical daily highs/lows from NOAA's RCC-ACIS climate database for each city. The standard deviation of day-to-day temperature changes gives us the baseline sigma.
- Rolling calibration — after each settlement, the rolling sigma is updated from the last 14 days of actual forecast errors. This adapts automatically — if a city has been hard to forecast recently, sigma widens. Activates after 5 settlements.
- Static fallback — until enough data accumulates, a per-city default from the original spec is used.
The CLI and dashboard show [cal] when calibrated values are active, or [static] when using defaults.
Confidence Multiplier
| Confidence | Multiplier | When Used |
|---|---|---|
| High | ×0.45 | Day-of forecast, stable weather pattern |
| Medium | ×0.70 | 2-3 days out, some uncertainty |
| Low | ×1.0 | 4+ days out or unstable pattern |
Weather Pattern Stability (×0.70 to ×1.50)
The scanner analyzes three real-time data sources to assess how stable the weather pattern is for each city:
| Source | What It Measures | Range |
|---|---|---|
| Hourly Forecast Volatility | 24-hour temperature range and max hour-to-hour swings from the NWS hourly forecast | ×0.85 to ×1.30 |
| Active Weather Alerts | NWS watches, warnings, and advisories for the city's coordinates, scored by severity | ×1.0 to ×1.50 |
| Forecast Discussion (AFD) | NWS meteorologist narrative analyzed for instability keywords ("cold front", "storm", "uncertainty") vs stability keywords ("high pressure", "ridge", "calm") | ×0.85 to ×1.25 |
These three factors multiply together and are clamped to a 0.70–1.50 range. The dashboard shows: 🟢 very stable, 🟡 normal, or 🔴 volatile for each city.
Time-of-Day Decay (×0.40 to ×1.0)
As the day progresses and more observations come in, the model becomes more certain about the outcome. Sigma shrinks accordingly:
| HIGH Markets | Decay | Reasoning |
|---|---|---|
| Before 8am ET | ×1.0 | Full uncertainty — high hasn't been reached |
| 8-11am ET | ×0.92 | Morning — temps climbing |
| 11am-2pm ET, near forecast | ×0.70 | Observed max approaching forecast |
| After 2pm, max ≈ forecast | ×0.50 | Nearly certain — high is set |
| After 2pm, max > forecast | ×0.40 | Already exceeded — outcome clear |
| LOW Markets | Decay | Reasoning |
|---|---|---|
| Temps falling, low ahead | ×1.0 | Full uncertainty — overnight low hasn't occurred |
| Rising, min ≈ forecast | ×0.45 | Morning low locked in, matches forecast |
| Rising, min < forecast | ×0.50 | Already went below forecast — outcome locked |
| Midday, stable | ×0.65 | Morning low is probably set |
This means the later scans (11am, 1pm ET) produce tighter, more confident signals than the early morning scan. This is by design — later scans have more data.
Step 3: Bracket Probabilities
For each Kalshi bracket [low, high], the probability is calculated using the normal CDF:
The ±0.5 adjustment accounts for integer temperature reporting. For open-ended brackets (≤X or ≥X), only one CDF bound is used.
Step 4: Edge Detection + Liquidity Filter
expected_value = model_prob × (1 − market_price) − (1 − model_prob) × market_price
A signal is generated when all of the following are met:
- Edge > 15% AND expected value > 3¢ per contract
- Bid/ask spread ≤ 10¢ (rejects thin, illiquid markets)
- Volume ≥ 100 contracts (rejects untradeable markets)
- Price between 3¢ and 97¢ (rejects near-certain outcomes)
The liquidity filter is critical — without it, the model would generate phantom signals on contracts that look mispriced but have no real market to trade against.
Step 5: Position Sizing (Quarter-Kelly)
position_size = min(kelly_fraction × daily_budget, max_per_trade)
Where p = model probability, b = payout odds = (1 − price) / price. The 0.25 multiplier is a conservative quarter-Kelly to reduce volatility. Positions are sorted by expected value and allocated until the $100 daily budget is exhausted.
Trading Parameters
| Parameter | Value |
|---|---|
| Daily Budget | $100 |
| Max Per Trade | $20 |
| Min Edge Threshold | 15% |
| Min Expected Value | 3¢ per contract |
| Kelly Fraction | Quarter-Kelly (0.25) |
| YES Price Floor | ≥15¢ (NO stays at ≥3¢) |
| Min Bid Required | 2¢ (ensures exit liquidity) |
| Max Bid/Ask Spread | 10¢ |
| Min Volume | 100 contracts |
| Portfolio Split | 70% NO / 30% YES |
| MAE Suppression | Signals disabled when MAE > 5°F |
Understanding MAE (Mean Absolute Error)
MAE measures the average of how far off our forecast is from what actually happened, ignoring direction. If we forecast 52°F and the actual was 55°F, the error is 3°F. If the next day we forecast 60°F and actual was 57°F, the error is also 3°F. The MAE across both days is 3°F.
Why MAE Matters for Bracket Markets
Kalshi brackets are typically 2°F wide (e.g., 52-53°F). The model needs to estimate which bracket the temperature will land in. If our MAE is 2°F, we're usually within one bracket of being right — the probability estimates are trustworthy. If our MAE is 10°F, we're off by 5 brackets on average — the model's probabilities are essentially guesses.
| MAE Range | Quality | Action |
|---|---|---|
| < 3°F | Strong | Trade with confidence — model is within one bracket |
| 3-5°F | Moderate | Trade selectively — signals have some uncertainty |
| > 5°F | Too inaccurate | Signals automatically suppressed |
Automatic Signal Suppression
The scanner tracks a rolling MAE for each city and market type (HIGH/LOW) based on the last 14 days of settlement data. When a city's MAE exceeds 5°F with at least 5 settlement samples, signal generation for that market is automatically suppressed.
What this means in practice:
- The scanner still scans the suppressed market — it fetches forecasts, observations, and market prices as usual
- The model still runs and calculates probabilities — you can see the bracket analysis on the dashboard
- Settlement data still collects and the calibration continues learning from errors
- But no signals are generated — the market won't appear in the portfolio allocation
- Once the rolling MAE drops below 5°F, signals are automatically re-enabled — no manual intervention needed
This prevents the model from recommending trades in markets where it doesn't yet have enough accuracy to be profitable. As the calibration system learns from more settlements, the MAE naturally decreases and markets re-enable themselves.
Current suppression status is visible on the Roadmap page or via the CLI: node cli.js --calibration. The /api/calibration endpoint also returns the suppressed flag and rollingMAE for each city.
Auto-Calibration
The model improves itself automatically over time through three mechanisms:
- Bias EMA — after each settlement, the forecast error is blended into a running exponential moving average per city. If NYC consistently forecasts 2° too low, the bias adjusts to +2°. Uses α=0.3 so it adapts quickly but doesn't overreact to one bad day. Activates after 3 settlements.
- Rolling Sigma — the standard deviation is recomputed from the last 14 days of actual forecast errors. A city that's been hard to forecast recently automatically gets a wider sigma. Activates after 5 settlements.
- MAE Suppression — the rolling Mean Absolute Error is tracked per city/market. When it exceeds 5°F (with at least 5 samples), signals for that market are automatically suppressed. When accuracy improves below the threshold, signals re-enable. This prevents trading on markets where the model isn't accurate enough.
On first setup, a 90-day historical backtest seeds the calibration using data from NOAA's RCC-ACIS climate database. This gives the model a strong baseline immediately rather than waiting weeks for settlements to accumulate. Run node cli.js --backtest to re-run the backtest at any time.
View the current calibration state with node cli.js --calibration or via the API at /api/calibration.
Data Sources
National Weather Service API
The NWS API is the sole authoritative source for settlement data on Kalshi weather markets. We use five endpoints per city:
- Point forecast — 7-day forecast with daily high/low temperatures
- Hourly forecast — 156-hour temperature forecast, used for stability analysis (24h range, hourly swings)
- Station observations (latest) — current live readings from the exact NWS station Kalshi settles on
- Station observations (today) — all readings today, used to compute observed min/max/trend for the model
- Active alerts — watches, warnings, advisories affecting the city, used in stability scoring
Additionally, the Area Forecast Discussion (AFD) from each NWS office is analyzed for weather pattern keywords.
The NWS API is free, requires no API key, and is rate-limited. We add 300ms delays between requests and implement retry logic with exponential backoff.
NOAA RCC-ACIS (Historical Data)
For the 90-day backtest, we use the Regional Climate Centers' Applied Climate Information System. This provides daily max/min temperature records for each station going back years. No API key required.
Kalshi Public API
Market prices, bracket definitions, and order book data are fetched from Kalshi's public API. No authentication is required for reading market data. Prices are parsed from live bid/ask spreads and last-traded prices.
Polymarket Cross-Reference
In addition to Kalshi, Polymarket offers daily high temperature markets for 36+ cities worldwide, including all 17 US cities we track on Kalshi. The scanner pulls Polymarket prices on every scan and computes a consensus score for each city.
How Consensus Works
For each signal the model generates, we check whether Polymarket's pricing agrees or disagrees:
- BUY NO signal: Our model says Kalshi is overpricing a bracket. If Polymarket prices the same bracket lower than Kalshi, Polymarket agrees the bracket is overpriced → consensus.
- BUY YES signal: Our model says Kalshi is underpricing a bracket. If Polymarket prices it higher than Kalshi, Polymarket agrees it's underpriced → consensus.
The consensus score ranges from -1 (strong disagreement) to +1 (strong consensus):
| Score | Badge | Meaning |
|---|---|---|
| > 0.5 | PM ✓ | Polymarket agrees with our signals — higher confidence |
| 0 to 0.5 | PM ~ | Mixed agreement — some signals confirmed, some not |
| < 0 | PM ✗ | Polymarket disagrees — the mispricing may be on our end, not Kalshi's |
Why This Matters
A single market can be wrong. But when two independent markets (Kalshi and Polymarket) agree with our model that a bracket is mispriced, the probability of real edge increases significantly. Conversely, if Polymarket's price is close to Kalshi's and far from our model, it suggests our model may be the one that's wrong — two markets independently arrived at a similar price.
Polymarket also offers 11 brackets per city (vs Kalshi's 6), providing finer resolution on market expectations. The scanner matches brackets with overlapping temperature ranges for comparison.
Quick-Reference Trading Guide
Reading the Dashboard Indicators
| Indicator | What It Means | Trade with confidence when... |
|---|---|---|
| Signal (BUY NO / BUY YES) | Model thinks Kalshi mispriced this bracket | Edge > 20% and EV > 5¢ |
| Bid/Ask | What you'd actually pay. Bid = sell price, Ask = buy price | Spread ≤ 5¢ (tight = liquid market) |
| PM ✓ | Polymarket prices confirm the mispricing | Two independent markets agree against Kalshi |
| PM ✗ | Polymarket disagrees — maybe Kalshi is right | Be cautious, consider skipping |
| σ [cal] | Sigma computed from real settlement data, not static guesses | Green [cal] = more trustworthy model |
| Ensemble spread | How much the 6 weather models disagree | Low spread (< 5°) = models agree = more certain forecast |
| decay | How settled the temperature is based on time of day and observations | Strong decay (×0.4-0.5) = outcome nearly locked in |
| MAE (via calibration) | How accurate our forecast has been for this city historically | Below 3° = strong accuracy |
Strongest Trades
Your highest-confidence trades are when multiple indicators align:
- BUY NO signal with decent edge (> 20%)
- PM ✓ — Polymarket agrees with the signal
- Low ensemble spread — weather models agree on the forecast
- Strong time decay — afternoon local time, temperature near or past forecast
- City with low MAE (< 3°F) — accurate historical track record
When to Skip
- PM ✗ on the signal — Polymarket disagrees, the mispricing may be on our end
- High ensemble spread (> 10°) — weather models can't agree, forecast is uncertain
- City has high MAE (> 4°F) — our forecasts for this city aren't accurate enough
- Wide bid/ask spread (> 8¢) — thin market, hard to enter/exit
- Early morning scan with minimal time decay — too much uncertainty remains
Recommended Workflow
- 6:45 AM PT — Review the landscape. Note which cities have signals. Don't trade yet.
- 8:00 AM PT — Primary trading window. Updated forecast, moderate sigma decay, decent liquidity. Place your best trades here.
- 10:00 AM PT — Rebalance. If existing signals strengthened and PM ✓, add. If they disappeared, the market corrected.
- 12:00-2:00 PM PT — East coast cities are nearly settled. West coast settling. Good for late conviction trades on cities with strong decay.
How Settlement Works
Each morning, the scanner fetches the previous day's actual high temperature from NWS observation data for each station. It then:
- Compares the actual high to the forecast to calculate forecast error
- Identifies which bracket the actual temperature landed in (the winning bracket)
- Scores each signal as a WIN or LOSS — a BUY YES wins if the temp lands in that bracket; a BUY NO wins if it doesn't
- Calculates actual P&L (payout minus cost) for each position
Historical performance, win rates, and cumulative P&L are tracked on the History page.
Real Example: Reading the Dashboard
Let's walk through an actual scan to show how to interpret everything the scanner produces. We'll look at Miami HIGH from a recent run, then explain the portfolio allocation.
Step-by-Step: Miami High Temperature
Here's what the scanner showed for Miami's daily high temperature market:
Forecast High: 83°F | Model Mean: 83.0°F | Sigma: ±1.7°F
| Bracket | Model % | Market Bid/Ask | Signal |
|---|---|---|---|
| ≥86° | 7.2% | -/10¢ | - |
| 84-85° | 31.3% | -/47¢ | BUY NO |
| 82-83° | 42.5% | -/39¢ | - |
| 80-81° | 17.0% | -/3¢ | BUY YES |
| 78-79° | 2.0% | -/5¢ | - |
| ≤77° | 0.1% | -/1¢ | - |
How to Read Each Column
- Bracket — The temperature range. "84-85°" means this contract pays $1 if Miami's official high is 84°F or 85°F. "≥86°" means 86° or above. "≤77°" means 77° or below.
- Model % — Our model's estimate of the probability that the actual high falls in this range. The NWS forecasts 83°F, so the model thinks 82-83° is most likely (42.5%), with 84-85° next (31.3%).
- Bid/Ask — The current Kalshi market prices in cents. The bid is what you can sell at; the ask is what you'd pay to buy. A bid/ask of "-/47¢" means nobody is bidding, but you can buy YES for 47¢. These prices represent what the market thinks the probability is — 47¢ implies a 47% chance.
- Signal — The scanner's recommendation based on comparing our model probability to the market price.
Why BUY NO on 84-85°?
The market is asking 47¢ for a YES contract on 84-85°, implying a 47% chance. But our model estimates only a 31.3% chance. The market is overpricing this bracket — it thinks 84-85° is more likely than our forecast supports.
When a bracket is overpriced, we buy the NO side. A NO contract costs 100¢ − 47¢ = 53¢ (approximately). If the high does NOT land in 84-85° (which our model says is a ~69% chance), we collect $1.00, profiting ~47¢ per contract.
Why BUY YES on 80-81°?
The market is asking only 3¢ for a YES contract on 80-81°, implying just a 3% chance. But our model estimates a 17% chance — more than 5x what the market thinks. This bracket is significantly underpriced.
At 3¢ per contract, you risk 3¢ to potentially win $1.00. With a 17% model probability, the expected value is strongly positive. Even though this bracket is less likely than 82-83°, the price is so cheap relative to the probability that it's a good bet.
Why No Signal on 82-83°?
The model says 82-83° has a 42.5% chance, and the market is asking 39¢. That's close enough — the market and our model roughly agree. The edge (42.5% vs 39%) doesn't clear our 15% minimum threshold, so no signal is generated. Not every bracket needs a trade — we only act when the market is significantly mispriced.
Reading the Portfolio Allocation
After scanning all cities, the scanner combines every signal into a single portfolio. Here's how to read it:
SIGNALS FOUND
31
TOTAL COST
$90.82
EXPECTED RETURN
$632.21
POSITIONS
10
| Ticker | Bracket | Side | Contracts | Cost | Exp. Profit |
|---|---|---|---|---|---|
| KXHIGHNY-26MAR24-B48.5 | High 48-49 | NO | 22 | $12.76 | $4.80 |
| KXHIGHMIA-26MAR24-B84.5 | High 84-85 | NO | 14 | $7.56 | $2.06 |
| KXHIGHMIA-26MAR24-B80.5 | High 80-81 | YES | 120 | $3.60 | $16.80 |
| KXLOWTMIA-26MAR24-T63 | Low ≥64 | YES | 131 | $14.41 | $67.47 |
| KXLOWTPHIL-26MAR24-T33 | Low ≥34 | YES | 83 | $19.09 | $49.22 |
(Showing 5 of 10 positions for brevity)
Understanding Each Column
- Ticker — The Kalshi contract identifier. You'll search for this in the Kalshi app to place the trade. Example:
KXHIGHMIA-26MAR24-B80.5is Miami's high temp, March 24, bracket centered on 80.5°F. - Bracket — A human-readable description. "High 84-85" means this is a daily high temperature market for the 84-85°F range. "Low ≥64" means a daily low temperature market asking if the low will be 64° or above.
- Side — YES means you're betting the temperature WILL land in this bracket. NO means you're betting it WON'T.
- Contracts — How many contracts the model recommends buying, based on Quarter-Kelly position sizing. More contracts = higher confidence in the edge.
- Cost — Total dollars you'd spend to buy this position. Each contract costs between 1¢ and 99¢. For example, 120 contracts at 3¢ each = $3.60.
- Exp. Profit — The model's estimate of how much profit this position will generate on average. This is not guaranteed — it's a statistical expectation based on the model probability.
Why So Many Contracts for Miami 80-81° (120) vs 84-85° (14)?
Position size is determined by two things: the edge (how mispriced the contract is) and the price (how cheap each contract is).
- 80-81° YES at 3¢: Very cheap per contract, and the model sees a big edge (17% vs 3%). The Kelly formula says to bet more when the edge is large and the price is low. 120 contracts × 3¢ = only $3.60 at risk.
- 84-85° NO at ~53¢: More expensive per contract. Even though the edge is decent, each contract costs more, so fewer fit within the $20 max-per-trade limit. 14 contracts × 53¢ = $7.56.
What Does "$90.82 Total Cost" Mean?
This is the total amount of money you'd need to place all 10 positions. The scanner targets a $100 daily budget and allocates by expected value — the best opportunities get funded first, and it stops when the budget is used up. You don't have to take every position — you can cherry-pick the ones you're most confident in.
About Expected Profit
The expected profit numbers assume the model's probability estimates are correct. In reality:
- Some trades will win (payout $1.00 per contract) and some will lose (payout $0.00)
- Over many trades, the average profit should approach the expected value if the model is well-calibrated
- Individual days can be profitable or unprofitable — that's normal variance
- The History page tracks actual results vs predictions over time
Tips for Placing Trades on Kalshi
- Search for the ticker or city name in the Kalshi app
- Match the bracket and side (YES or NO) from the scanner
- Use limit orders at or near the bid/ask shown in the scanner, not market orders
- You don't need to trade the exact number of contracts shown — start smaller while you build confidence
- Check the order book depth on Kalshi — if there are only a few contracts available at the ask price, you may not be able to fill the full position
- Later scans (11am, 1pm ET) often have better liquidity and updated forecasts
Schedule
| Time (PT / ET) | Action |
|---|---|
| 6:45 AM PT / 9:45 AM ET | First scan — early morning forecast, initial market prices |
| 8:00 AM PT / 11:00 AM ET | Second scan — updated forecast, more market liquidity |
| 10:00 AM PT / 1:00 PM ET | Third scan — midday update, strongest observation data |
| 7:00 AM PT / 10:00 AM ET | Settlement check — score yesterday's signals as wins/losses |
You can also trigger a scan at any time using the "Run Scan" button on the dashboard. Each run is saved separately so you can compare how prices changed throughout the day.
FAQ
No. This is an experimental quantitative model for educational and research purposes. The signals generated are based on statistical analysis of NWS forecasts and should not be considered financial advice. All prediction market trading involves risk of loss. Past performance does not guarantee future results.
Large apparent edges can occur when: (1) markets are thinly traded with wide bid/ask spreads, (2) the scan runs during off-hours when prices are stale, or (3) the model's normal distribution assumption doesn't capture weather pattern nuances like frontal passages or storm systems. The portfolio builder filters out contracts priced at ≤2¢ or ≥98¢ to avoid illusory edges on near-certain outcomes.
Kalshi settles exclusively on NWS Daily Climate Reports. While AccuWeather, Weather.com, or other sources may sometimes be more accurate, the NWS report is the only source that determines contract outcomes. Using the same data source for prediction and settlement eliminates basis risk.
The Kelly Criterion is a formula for optimal bet sizing that maximizes long-term growth rate. Full Kelly is mathematically optimal but extremely volatile. Quarter-Kelly (betting 25% of the Kelly-recommended amount) sacrifices some growth rate for significantly lower variance and drawdown risk. It's a common approach in professional trading and gambling.
NWS forecast errors are approximately normally distributed for day-of and next-day forecasts, which is the primary use case. The model is less reliable for multi-day forecasts or during extreme weather events (cold fronts, heat waves) where the error distribution may be skewed. The historical sigma values are calibrated per-city to account for local climate variability.
The current model does not account for: (1) sudden forecast revisions or weather advisories, (2) microclimate effects at specific stations, (3) instrument measurement biases, (4) order book depth and liquidity constraints, or (5) transaction costs and fees. These are areas for future improvement.
The scanner runs at 9:45 AM ET, shortly before Kalshi weather markets typically see the most trading activity. Day-of forecasts have the highest confidence and lowest sigma, making edge detection most reliable. Signals generated from overnight or multi-day forecasts carry more uncertainty.
Visit the History page to see forecast accuracy (mean absolute error, bias), signal win rate, and cumulative P&L over time. Click any settlement row with signals to see the per-signal WIN/LOSS breakdown. You can also filter by city to analyze performance for specific markets.