# Kalshi Weather Edge: NWS Forecast vs Market Odds (`precious_bathmat/kalshi-weather-edge`) Actor

Every open Kalshi daily high and low temperature market priced against the official National Weather Service forecast and today's station readings: forecast probability for each range, the market's price, and the edge after Kalshi's fee. Official APIs only, no key.

- **URL**: https://apify.com/precious\_bathmat/kalshi-weather-edge.md
- **Developed by:** [Mariam Ahmed](https://apify.com/precious_bathmat) (community)
- **Categories:** Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 temperature ranges

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Kalshi Weather Edge: NWS Forecast vs Market Odds

Every open **Kalshi daily high and low temperature market**, in every city Kalshi lists, priced against the **official National Weather Service forecast** and **today's station readings**. For each temperature range you get the forecast probability, the market's price, the difference, and the edge on Yes and No **after Kalshi's fee**.

Other Kalshi weather Actors return bracket prices. A price only tells you what traders think. This one puts what the forecast says next to it, range by range, for 24 cities at once.

### What does this do?

For each city and day with an open market, it:

1. reads the market's six ranges and their prices from Kalshi's public API
2. finds the **exact NWS climate station** the market settles on (Central Park for New York, Midway for Chicago…)
3. reads that station's NWS hourly forecast and every reading so far today
4. works out the chance the day's high (or low) lands in each range, and compares it with the price

```
Chicago daily high, 24 Sep 2026 · station KMDW (Midway) · 62°F so far
NWS forecast peak 70°F in ~6 h (includes a +2.6°F correction from the latest reading)

 range          forecast   market   yes ask   edge after fee
 65° or below      3.9%     2.0%      0.03      Yes  +0.7
 66° to 67°       12.9%     7.5%      0.09      Yes  +3.4
 68° to 69°       26.9%    35.5%      0.36      No   +6.5
 70° to 71°       30.4%    52.5%      0.54      No  +18.9
 72° to 73°       18.6%     4.5%      0.05      Yes +13.2
 74° or above      7.3%     0.5%      0.01      Yes  +6.3
```

The forecast and the market agree on the most likely range, 70° to 71°. They disagree on how sure to be: the market puts 52.5% on it and the forecast 30.4%. That disagreement is where every "edge" in this table comes from; see *Read this before trading on it*.

### Who is it for?

- **Kalshi weather traders**: the forecast comparison you'd otherwise do by hand, for every city, in about a minute
- **Bot and model builders**: clean, scheduled data with prices, forecast, observations and fees in one row
- **Forecasters and researchers**: a daily record of how the market's view compares with the NWS

### What data do you get?

One row per temperature range:

| Field | What it tells you |
|---|---|
| `city`, `marketType`, `day`, `range` | Which market: "New York City, high, Sep 24, 66° to 67°" |
| `forecastProbabilityPercent` | The model's chance of the day ending in this range |
| `marketProbabilityPercent` | The market's view: bid/ask midpoint, or last trade |
| `differencePercent` | Forecast minus market, in percentage points |
| `yesBid`, `yesAsk`, `noBid`, `noAsk`, `lastPrice` | Live prices in dollars |
| `yesEdgeAfterFeePercent`, `noEdgeAfterFeePercent`, `bestSide` | Forecast probability minus the price you'd pay, minus Kalshi's fee |
| `expectedF`, `forecastRemainingF`, `observedSoFarF` | The expected final value, the forecast for the rest of the day, and what the readings already show |
| `sigmaF`, `hoursToForecastPeak`, `observedGapF`, `forecastCorrectionF` | The uncertainty and correction the model used, so every number can be checked |
| `volume`, `volume24h`, `openInterest` | Contracts traded and open |
| `station`, `climateSite`, `climateDayStart`, `climateDayEnd` | Exactly what the market settles on, and when |

The run summary lists each city-day with the forecast's favourite range next to the market's.

### How the probability is worked out

**The day's high is the higher of two things: the highest reading so far, and the highest temperature still to come.** The first is already known. The second is the NWS hourly forecast for the rest of the climate day, which is uncertain. The model treats it as normally distributed around the forecast. The low works the same way in reverse.

Four details decide whether the numbers mean anything, and all four are handled:

- **The right day.** NWS climate days run midnight to midnight *local standard time*, so in summer the day ends at 1 a.m. by the clock. Readings are counted in that window, not the calendar day.
- **The right readings.** Many airports report every five minutes but in **whole °C**. A reading of 24°C could mean anything from 74.3°F to 76.1°F, which spans two Kalshi ranges. Each reading is treated as the range it could mean, and only what the readings *prove* counts as observed.
- **A forecast that has drifted.** A forecast issued at 4 a.m. can already be 3°F off by 7 a.m. The model compares the latest reading with the forecast for that hour and carries the difference forward, fading over the next few hours (`forecastCorrectionF` shows it). Without this, a Phoenix low at 85°F would be "forecast" to fall to 79°F within the hour.
- **The gap to the official number.** The settled high comes from the station's continuous record, which can peak between readings. The model allows for that gap: small for five-minute data, about 1°F for hourly-only stations. It uses the six-hour extremes in METAR reports where they cover the day.

### Read this before trading on it

**The edge depends on one assumption: how far NWS forecasts miss.** The default is 3.5°F for a peak a day away, narrowing as the peak gets closer (about 2.5°F six hours out). This is a rule of thumb, not a calibration.

On a typical day the market is **more confident than this model**. It prices one range at 50–60%, while a 2.5°F uncertainty puts at most about 30% on any 2°F range. So most edges this Actor shows are of one kind: **cheap tail ranges look underpriced and the favourite looks overpriced**. Maybe the market is overconfident. Maybe traders simply know more, from high-resolution models the NWS point forecast doesn't reflect. **This Actor cannot tell you which.**

Before risking money:

- run it on a schedule and **compare with how markets actually settle** over a few weeks
- try the **forecast error setting** at 2°F and at 5°F, and see which edges survive both
- treat an edge as a question to check, not an answer

**No picks, tips or trading advice.** The probabilities are a model estimate from public forecasts.

### Other limits

- **Settlement source.** Kalshi settles these markets on **The Weather Company's** reported value for the named NWS climate station. This Actor uses NWS data for the same station, so rounding and late corrections can differ.
- **US cities only.** Kalshi also lists some international cities, but the NWS covers only the US, so those are skipped.
- **Markets appear the day before.** Kalshi opens each day's markets roughly a day ahead, so a run usually covers today, and tomorrow once listed.
- **Thin markets.** Some ranges trade a few contracts a day, with wide spreads. The midpoint of a 5¢/87¢ spread is not a real price; check `yesBid`, `yesAsk` and `volume`.

### Data sources and terms

Both sources publish this data for exactly this kind of use:

- **Kalshi public API**: market data, no key or account needed
- **US National Weather Service API** (api.weather.gov): US government data, in the public domain

Not affiliated with Kalshi or the National Weather Service.

### Example run

| Measured on 24 September 2026, from Apify | Result |
|---|---|
| City-day markets | **48** (24 highs, 24 lows) |
| Ranges priced | **288**, every one with a forecast probability |
| NWS stations read | 24 |
| Run time, memory | 96 s, 66 MB |
| Failures | none |
| Forecast and market pick the same favourite range | 30 of 48 city-days |

### Pricing

**$0.002 per temperature range.** A full run of every city, highs and lows (about 290 ranges), costs about **$0.58**. One city's high is **$0.012**. With "only ranges where the forecast beats the price" switched on, you pay only for the ranges returned.

### Input

| Field | Meaning |
|---|---|
| **Markets** | Highs, lows or both |
| **Cities** | Only these cities; NYC, LA, DC and SF work. Empty means all |
| **Forecast error one day out** | The uncertainty assumption; see above |
| **Only ranges where the forecast beats the price** | Keep only positive edges after fees, largest first |
| **Minimum edge** | How large that edge must be, in percentage points |

### Integrations

Results export to JSON, CSV, Excel and Google Sheets, or go straight into a sheet, bot or dashboard through the Apify API, webhooks, Make, Zapier and n8n. Schedule it hourly through the day: the forecast updates, the readings build up, and the probabilities tighten as the peak passes.

# Actor input Schema

## `markets` (type: `string`):

Daily high temperature markets, daily low markets, or both.

## `cities` (type: `array`):

Only these cities, e.g. "New York", "Chicago", "Miami". Short forms like NYC, LA, DC and SF work. Leave empty for every city Kalshi has open.

## `forecastErrorF` (type: `string`):

How far an NWS temperature forecast typically misses when the peak is a day away. The model narrows it as the peak approaches. Larger is more cautious.

## `onlyWithEdge` (type: `boolean`):

Keep only ranges where buying Yes or No costs less than the forecast probability, after Kalshi's fee. Sorted largest gap first.

## `minEdgePercent` (type: `integer`):

With the option above, how large the gap must be after the fee. 5 means the forecast rates that side at least 5 points above its price.

## Actor input object example

```json
{
  "markets": "both",
  "cities": [],
  "forecastErrorF": "3.5",
  "onlyWithEdge": false,
  "minEdgePercent": 0
}
```

# Actor output Schema

## `ranges` (type: `string`):

Every open temperature range, with forecast probability, market price and edge after fees.

## `summary` (type: `string`):

Each city-day's expected temperature, the forecast's and the market's favourite range, the method and its limits.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "cities": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("precious_bathmat/kalshi-weather-edge").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "cities": [] }

# Run the Actor and wait for it to finish
run = client.actor("precious_bathmat/kalshi-weather-edge").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "cities": []
}' |
apify call precious_bathmat/kalshi-weather-edge --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,precious_bathmat/kalshi-weather-edge"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/CbtpR4aQzhgNDbd6M/builds/7ufQnMTg7wCMHDAW1/openapi.json
