Weather Bot — Episode 9: Scaling to 15 Cities — Two Weeks of Live Polymarket Weather Trading

The bot was working. Resolution holds were profitable. The bugs from Episode 8 were fixed. So I did what felt natural: add more cities.

Going from 5 to 15 turned out to be more than just copy-pasting city names into a config file. The API couldn't handle the extra load, same-day markets turned out to be a trap, and one METAR trade in Toronto taught me a $2 lesson about safety margins.

What This Post Covers

The expansion from 5 to 15 cities, the API optimization that made it possible, two hard lessons about when not to trade, and where things actually stand after two weeks of live operation. This isn't a wrap-up — the strategy is still changing and there's a lot I'm still figuring out.

Finding New Cities

I wrote a quick script to check Polymarket's gamma API for temperature markets in every major city I could think of. Atlanta, Tokyo, Shanghai, Toronto — all active, all with real volume.

The numbers surprised me. Atlanta was doing $1.68 million in weekly volume. More than NYC. I'd been ignoring it because I assumed US cities were all the same high-competition mess I'd seen with Chicago and Seattle. Wrong — Atlanta had volume and less bot competition than the big coastal cities.

CityWeekly VolumeAdded?
Atlanta$1,681KYes
Shanghai$463KYes
Tokyo$310KYes
Toronto$149KYes

All four had enough liquidity for $1-2 bets. I added them along with several others, bringing the total to 15 active cities.

The API Problem

More cities meant more API calls. I did the math and didn't like what I saw:

Before: 11 cities × 3 calls = 33 calls/scan
20 scans/hour × 24 hours = 15,840 calls/day
Open-Meteo free limit: 10,000/day  ← exceeded

I was burning through the free tier in 15 hours. The fix was something I should've done from the start: Open-Meteo's combined endpoint.

# Before — 3 separate calls per city
gfs = requests.get(
    "https://api.open-meteo.com/v1/gfs",
    params=...
)
ecmwf = requests.get(
    "https://api.open-meteo.com/v1/ecmwf",
    params=...
)
icon = requests.get(
    "https://api.open-meteo.com/v1/dwd-icon",
    params=...
)

# After — 1 combined call
resp = requests.get(
    "https://api.open-meteo.com/v1/forecast",
    params={
        "latitude": lat, "longitude": lon,
        "hourly": "temperature_2m",
        "models": "gfs_seamless,ecmwf_ifs025,
            icon_seamless",
        "forecast_days": 3,
    }
)

15 cities × 1 call = 15 calls per scan. Daily total dropped to around 7,200. Comfortably under the limit, even with room to add more cities later.

Same-Day Trading: The Trap

My biggest mistake in the first week of expansion was buying same-day markets. The logic seemed sound: forecast says 30°C, market has it at $0.15, buy.

But by the time a market is "today," the price already reflects the latest GFS update, actual morning temperatures, and every other bot's position. When the market says $0.15 on game day, it means "15% chance" — and the market is usually right.

I checked when the last GFS update gets priced in for each city:

CityLast GFS ReflectedPeakGap
NYC~UTC 16:00UTC 19:003h
Seoul~UTC 22:00 (prev day)UTC 05:007h
London~UTC 10:00UTC 14:004h

By the time you can trade a same-day market, the forecast edge is gone. The code change was simple — if days_ahead == 0, skip. The only exception is METAR NO trades, which are based on actual real-time measurements and get more accurate as peak approaches.

The Toronto METAR Lesson

METAR NO is the one same-day strategy that still works — in theory. If the airport thermometer reads 4°C and peak is two hours away, betting "won't reach 10°C" is nearly certain.

Toronto, March 15. METAR read 4°C. My bot calculated a ceiling of 5°C (4°C + 1°C safety margin) and bought NO on the "≥5°C" bucket.

Temperature reached exactly 5°C. -$2 loss.

Two fixes came out of that. First, I doubled the safety margin from 1°C to 2°C. Second, I banned edge buckets entirely — the "≥X" and "≤X" ranges that have infinite range on one side. One degree of margin error on an edge bucket decides everything.

# Ban edge buckets for METAR NO
low, high = temp_range
if low == -999 or high == 999:  # "≤X" or "≥X"
    continue  # too risky

if low <= ceiling:
    continue  # bucket starts below safety ceiling

Two Weeks In: The Numbers

Here's what my actual Polymarket portfolio looked like on March 20 — some green, some red, one Wellington position at -99% that I'd rather not talk about.

MetricValue
Starting capital$198
Current portfolio~$188
Realized P&L+$3.24
Total trades20+
Win rate (realized)62.5% (5/8)
Biggest win+$5.89 (Ankara resolution)
Biggest loss-$2.00 (Toronto METAR NO)
Cities15

The portfolio is slightly down from the $198 starting point because of open positions that haven't resolved yet. The realized P&L is positive at +$3.24 — mostly carried by that one Ankara resolution win. It's not impressive, but it's not negative either. The system works; it just needs more volume and more time.

What I'd Change

Same-day YES should've been banned from the start. Every same-day trade I took lost money. The market is smarter than my forecast model on game day, and it took me a week of losses to accept that.

The 1°C METAR safety margin was too tight. Toronto proved it. I should've started with 2°C and loosened it if the data supported it, not the other way around.

I also should've checked city volume before dismissing US cities as a group. Atlanta's $1.68M volume with moderate competition was a better opportunity than several of the non-US cities I'd been focused on. Not every US city is Chicago.

Key Takeaways

  • Open-Meteo's combined endpoint cuts API calls 3:1. Should've used it from day one.
  • Same-day YES trades are a trap. By the time you can buy, the forecast edge is priced in. Only METAR NO works on game day.
  • METAR safety margins need to be wide. Toronto's 1°C margin wasn't enough. Doubled to 2°C and banned edge buckets.
  • Check volume per city, not per country. Atlanta at $1.68M weekly was hiding in plain sight.

What's Happening Now

The bot's running 15 cities from my MacBook. The resolution strategy is holding, but barely — one big win carries a lot of small losses, and I'm right at the edge of breakeven. The next step is getting off the laptop entirely (it dies every time I close the lid), and there are some stop-loss decisions I've been putting off that the data is starting to make obvious.

The strategy is still changing. I'm still learning. More coming.

← Previous: Episode 8: FOK Ghost Orders, Orderbook Lies       Next: Episode 10: Moving to Oracle Cloud and the Stop-Loss Surgery →


More updates on the way. If you're working on something similar or found a smarter way to do it, drop it in the comments — the more we share, the faster we all move.

Disclaimer: This blog documents my personal learning journey. Nothing here is financial advice.

Comments