How to Build a Polymarket Weather Trading Bot in Python (Free Code) part 1

How to Build a Polymarket Weather Trading Bot in Python (Free Code) part 1

How to Build a Polymarket Weather Trading Bot in Python (Free Code) part 1

The Idea: Why Weather Markets Have an Edge

Polymarket lets you bet on whether the temperature in Chicago tomorrow will land between 44–45°F. Or 48°F and above. Or below 40°F. Each outcome trades like a stock — you buy it for 8 cents, and if it resolves YES, you collect $1.

Here’s the thing: the crowd prices these markets on vibes. Most people aren’t checking the actual airport weather station data that Polymarket uses to resolve these markets. They guess. Or they don’t trade at all, leaving buckets sitting at 7 or 8 cents when the real probability is closer to 70%.

That’s where a bot has an advantage. It fetches the same data source the market uses for resolution, compares it to the current prices, and flags anything that looks obviously wrong.

The strategy isn’t complicated. What makes it work is using the right data — and being consistent about checking it every day.

What the Bot Actually Does

In plain English, the bot runs through this logic every time you launch it:

  1. Checks the temperature forecast for 6 US cities using free government weather data
  2. Searches Polymarket for active markets on those cities for the next 4 days
  3. Finds which temperature bucket the forecast lands in
  4. Checks what the market is currently charging for that bucket
  5. If the price is below 15 cents and the forecast clearly points there — it flags a trade signal
  6. In simulation mode, it records the trade against a virtual $1,000 balance so you can track performance before going live

That’s it. Simple logic, free data, no paid subscriptions.

Before You Start: 2-Minute Setup

You need two things installed:

Open VS Code, press Ctrl + ~ to open the integrated terminal, and install the only library you need:

pip install requests

Create a folder called weatherbot. Inside it, create a new file called bot_v1.py. Everything below goes into that file, in order.

Step 1 — Define Your Cities and Trading Rules

import re
import json
import requests
from datetime import datetime, timezone, timedelta

LOCATIONS = {
    "nyc":     {"lat": 40.7772, "lon": -73.8726, "name": "New York City"},  # KLGA
    "chicago": {"lat": 41.9742, "lon": -87.9073, "name": "Chicago"},        # KORD
    "miami":   {"lat": 25.7959, "lon": -80.2870, "name": "Miami"},          # KMIA
    "dallas":  {"lat": 32.8471, "lon": -96.8518, "name": "Dallas"},         # KDAL
    "seattle": {"lat": 47.4502, "lon": -122.3088, "name": "Seattle"},       # KSEA
    "atlanta": {"lat": 33.6407, "lon": -84.4277,  "name": "Atlanta"},       # KATL
}

MONTHS = ["january","february","march","april","may","june",
          "july","august","september","october","november","december"]

ENTRY_THRESHOLD = 0.15   # Only buy if the market prices the outcome below 15 cents
EXIT_THRESHOLD  = 0.45   # Take profit when it reaches 45 cents
POSITION_PCT    = 0.05   # Risk 5% of your balance per trade

One thing most people get wrong here: the coordinates need to be the airport, not the city center.

Polymarket resolves weather markets using data from Weather Underground, which pulls readings from specific airport stations. If you point the bot at downtown Chicago instead of O’Hare, your temperature will be off by 3–8°F. On markets where buckets are only 1–2°F wide, that’s enough to lose every single trade.

City Airport Station Code
New YorkLaGuardiaKLGA
ChicagoO’HareKORD
MiamiMiami IntlKMIA
DallasLove FieldKDAL
SeattleSea-TacKSEA
AtlantaHartsfield-JacksonKATL

On the thresholds: 15 cents entry means you only act when the market is pricing something at roughly 15% probability that the forecast says is much more likely. The 45-cent exit means you don’t wait for resolution — you sell when most of the value has been captured and move on to the next trade.

Step 2 — Pull Free Weather Data from the US Government

NWS_ENDPOINTS = {
    "nyc":     "https://api.weather.gov/gridpoints/OKX/37,39/forecast/hourly",
    "chicago": "https://api.weather.gov/gridpoints/LOT/66,77/forecast/hourly",
    "miami":   "https://api.weather.gov/gridpoints/MFL/106,51/forecast/hourly",
    "dallas":  "https://api.weather.gov/gridpoints/FWD/87,107/forecast/hourly",
    "seattle": "https://api.weather.gov/gridpoints/SEW/124,61/forecast/hourly",
    "atlanta": "https://api.weather.gov/gridpoints/FFC/50,82/forecast/hourly",
}

STATION_IDS = {
    "nyc": "KLGA", "chicago": "KORD", "miami": "KMIA",
    "dallas": "KDAL", "seattle": "KSEA", "atlanta": "KATL",
}

def get_forecast(city_slug: str) -> dict:
    forecast_url = NWS_ENDPOINTS.get(city_slug)
    station_id = STATION_IDS.get(city_slug)
    daily_max = {}
    headers = {"User-Agent": "weatherbot/1.0"}

    try:
        obs_url = f"https://api.weather.gov/stations/{station_id}/observations?limit=48"
        r = requests.get(obs_url, timeout=10, headers=headers)
        for obs in r.json().get("features", []):
            props = obs["properties"]
            time_str = props.get("timestamp", "")[:10]
            temp_c = props.get("temperature", {}).get("value")
            if temp_c is not None:
                temp_f = round(temp_c * 9/5 + 32)
                if time_str not in daily_max or temp_f > daily_max[time_str]:
                    daily_max[time_str] = temp_f
    except Exception as e:
        print(f"  Observations error: {e}")

    try:
        r = requests.get(forecast_url, timeout=10, headers=headers)
        periods = r.json()["properties"]["periods"]
        for p in periods:
            date = p["startTime"][:10]
            temp = p["temperature"]
            if p.get("temperatureUnit") == "C":
                temp = round(temp * 9/5 + 32)
            if date not in daily_max or temp > daily_max[date]:
                daily_max[date] = temp
    except Exception as e:
        print(f"  Forecast error: {e}")

    return daily_max

This function returns one number per day — the daily high temperature. Something like:

{"2026-03-11": 46, "2026-03-12": 52, "2026-03-13": 49}

Why the National Weather Service? It’s completely free, no API key needed, and it’s the original source that Weather Underground (Polymarket’s resolution provider) pulls from. You’re reading from the root, not a copy of a copy.

Why two requests instead of one? Forecast APIs only show the future. If it’s 4 PM and today’s high happened at noon, the forecast won’t include it anymore. The first request grabs what already happened today from the station’s observation history. The second grabs what’s still coming. Combine them, take the max — now you always have the full daily picture no matter what time you run the bot.

Step 3 — Find the Right Market on Polymarket

def get_polymarket_event(city_slug: str, month: str, day: int, year: int):
    slug = f"highest-temperature-in-{city_slug}-on-{month}-{day}-{year}"
    url = f"https://gamma-api.polymarket.com/events?slug={slug}"
    try:
        r = requests.get(url, timeout=10)
        data = r.json()
        if data and isinstance(data, list) and len(data) > 0:
            return data[0]
    except Exception as e:
        print(f"  Polymarket error: {e}")
    return None

Polymarket’s Gamma API is public. No account needed, no API key, no rate limits that will bite you at this usage level.

Every weather market has a predictable URL slug. For Dallas on March 11, 2026:

highest-temperature-in-dallas-on-march-11-2026

The API response includes a markets array with every temperature bucket and its current price. That’s the raw material the rest of the bot works with.

Step 4 — Figure Out Which Bucket the Forecast Hits

def parse_temp_range(question: str):
    if "or below" in question.lower():
        m = re.search(r'(\d+)°F or below', question, re.IGNORECASE)
        if m: return (-999, int(m.group(1)))
    if "or higher" in question.lower():
        m = re.search(r'(\d+)°F or higher', question, re.IGNORECASE)
        if m: return (int(m.group(1)), 999)
    m = re.search(r'between (\d+)-(\d+)°F', question, re.IGNORECASE)
    if m: return (int(m.group(1)), int(m.group(2)))
    return None

Each market on Polymarket is phrased as a question. The bot needs to turn that text into numbers it can compare. Here’s how the three question types get converted:

What Polymarket says What the bot stores
“between 44–45°F”(44, 45)
“48°F or higher”(48, 999)
“40°F or below”(-999, 40)

Once everything is a number, checking whether the forecast falls inside a bucket is a single line:

rng[0] <= forecast_temp <= rng[1]

Using 999 and -999 as stand-ins for “infinity” means you never have to write special cases for the edge buckets.

Step 5 — Simulate Trades Before Risking Real Money

SIM_FILE = "simulation.json"
SIM_BALANCE = 1000.0

def load_sim() -> dict:
    try:
        with open(SIM_FILE) as f:
            return json.load(f)
    except FileNotFoundError:
        return {
            "balance": SIM_BALANCE,
            "starting_balance": SIM_BALANCE,
            "positions": {},
            "trades": [],
            "total_trades": 0,
            "wins": 0,
            "losses": 0,
        }

def save_sim(sim: dict):
    with open(SIM_FILE, "w") as f:
        json.dump(sim, f, indent=2)

This creates a local simulation.json file that tracks a virtual $1,000 balance. Every signal the bot finds in simulation mode gets recorded there — entry price, number of shares, date, city, forecast temperature.

Run this for at least 5–7 days before touching real money. Look at what the bot trades, whether those trades would have won, and whether the signals actually make sense. If something looks broken, now is the time to find out.

Step 6 — Connect Everything Into One Loop

def run(dry_run: bool = True):
    sim = load_sim()
    balance = sim["balance"]
    positions = sim["positions"]
    trades_executed = 0

    for city_slug in LOCATIONS:
        forecast = get_forecast(city_slug)
        if not forecast:
            continue

        for i in range(0, 4):
            date = datetime.now(timezone.utc) + timedelta(days=i)
            date_str = date.strftime("%Y-%m-%d")
            month = MONTHS[date.month - 1]

            forecast_temp = forecast.get(date_str)
            if forecast_temp is None:
                continue

            event = get_polymarket_event(city_slug, month, date.day, date.year)
            if not event:
                continue

            print(f"📍 {LOCATIONS[city_slug]['name']} — {date_str} | Forecast: {forecast_temp}°F")

            for market in event.get("markets", []):
                question = market.get("question", "")
                rng = parse_temp_range(question)
                if not rng:
                    continue

                if rng[0] <= forecast_temp <= rng[1]:
                    try:
                        prices = json.loads(market.get("outcomePrices", "[0.5,0.5]"))
                        yes_price = float(prices[0])
                    except Exception:
                        continue

                    print(f"   Bucket: {question[:60]}")
                    print(f"   Price:  ${yes_price:.3f}")

                    if yes_price < ENTRY_THRESHOLD:
                        market_id = market.get("id", "")
                        position_size = round(balance * POSITION_PCT, 2)
                        shares = position_size / yes_price

                        print(f"   ✅ SIGNAL — {shares:.1f} shares @ ${yes_price:.3f} = ${position_size:.2f}")

                        if market_id not in positions and trades_executed < 5 and not dry_run:
                            balance -= position_size
                            positions[market_id] = {
                                "question": question,
                                "entry_price": yes_price,
                                "shares": shares,
                                "cost": position_size,
                                "date": date_str,
                                "location": city_slug,
                                "forecast_temp": forecast_temp,
                                "opened_at": datetime.now().isoformat(),
                            }
                            sim["total_trades"] += 1
                            trades_executed += 1
                    break

    sim["balance"] = round(balance, 2)
    sim["positions"] = positions
    save_sim(sim)
    print(f"\nDone. Balance: ${balance:.2f} | Trades: {trades_executed}")


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--live", action="store_true")
    args = parser.parse_args()
    run(dry_run=not args.live)

The main loop goes city by city, day by day. For each combination it asks: what does the government forecast say? What is the market charging for that outcome? Are those two things wildly different?

If the answer is yes — if NWS says Atlanta hits 52°F tomorrow and Polymarket is selling the 51–52°F bucket for 9 cents — the bot prints a signal. In simulation mode, it also records the trade and deducts the cost from your virtual balance.

One of them is mispricing reality. The whole strategy is betting that free government data is more accurate than a thin prediction market.

How to Run It Right Now

Save the complete file. Open your terminal inside the weatherbot folder and run:

# See signals without recording anything
python bot_v1.py

# Record simulated trades against the $1,000 virtual balance
python bot_v1.py --live

# Wipe the simulation and start fresh
python bot_v1.py --reset

Start with the first command. Watch what prints. You’ll see each city, the forecast temperature, which bucket it hits, and what the market is pricing that bucket at. If signals show up, check them manually on Polymarket to see if they make sense.

After a few days of watching, switch to --live and let the simulation build up a trade history. That history is what tells you whether the strategy actually has an edge before you put real money behind it.

When You’re Ready to Trade for Real

⚠️ Minimum one week of simulation. Seriously.

You’ll need to connect your Polymarket wallet to their CLOB API.

Get your API key: polymarket.com → Settings → API Keys → Create. Save it — it’s only shown once.

Store it safely in a .env file in your project folder:

POLY_API_KEY=your_key_here

Install the live trading dependencies:

pip install py-clob-client python-dotenv

Swap the simulation block for a real order. Find this section with Ctrl + F:

if market_id not in positions and trades_executed < 5 and not dry_run:
    balance -= position_size
    positions[market_id] = { ... }

Replace it with:

from dotenv import load_dotenv
import os
from py_clob_client.client import ClobClient

load_dotenv()
api_key = os.getenv("POLY_API_KEY")
client = ClobClient(host="https://clob.polymarket.com", key=api_key, chain_id=137)

order = client.create_market_order(
    token_id=market_id,
    side="BUY",
    amount=position_size,
)
result = client.post_order(order)

if result.get("success"):
    print(f"   ✅ Order placed — ${position_size:.2f}")

Start with $10–$20 per trade. Verify the orders show up in your Polymarket account. Run it for a week at small size before scaling anything.

Common Questions

Does this actually make money?
It depends entirely on how accurately the NWS forecast matches market resolution. The strategy has a real edge when the market is thin and the forecast is clear. It doesn’t work if the weather is genuinely unpredictable or if someone else has already arbitraged the price up.

What if I run it and there are no signals?
That’s fine — no signal means no edge at that moment. The bot is conservative by design. Some days every bucket is priced reasonably and you sit out. That’s the right behavior.

Can I add more cities?
Yes. Look up the NWS gridpoint URL for each new airport station using this helper endpoint: https://api.weather.gov/points/{lat},{lon} — it returns the correct forecast URL for any US coordinate.

How often should I run it?
Once or twice per day is enough. Running it more frequently won’t find different signals — the market doesn’t move that fast on most days.

What happens if the NWS API is slow or down?
Each request is wrapped in a try/except block, so the bot skips the affected city and keeps going. You’ll see an error message in the terminal but nothing will break.

Is this legal?
Polymarket is available to traders outside the US. US residents face restrictions — check Polymarket’s current terms and applicable local regulations before trading real money.


Leave a Reply

Your email address will not be published. Required fields are marked *