Trade Bot Dictionary · Weather

sst_anchor

sst_anchor is a master switch wired to how warm the tropical Atlantic is. Every three hours we ask one free marine API for the sea-surface temperature at sixteen fixed points of open ocean — eight strung across the stretch of Atlantic where hurricanes are born, four in the Caribbean, four in the Gulf of Mexico — and average them into two numbers. While the ocean is above your level, the bot trades. While it isn't, the bot places no trades at all that cycle, in any market. The idea is honest and old: warm water is what hurricanes run on, so a warm ocean is the hurricane regime.

Bots using it
0 (none live, none out-of-sample, none ever)
Data source
Open-Meteo Marine /v1/marine — the registry says “undocumented”
Refresh
Every 3 hours (17 */3 * * *)
Rows stored
244 readings 30 days — the rest is deleted
Backtest-replayable
No — and here the excuse doesn’t hold (catch #6)
Anchor module
quants/sst_anchor.py

This entry is unusual, so let me put the shape of it up front. Every other signal in this dictionary that we have checked against reality has come back wrong in some way. This one didn't. We moored the number against actual thermometers floating in the Atlantic Ocean and it agreed with them to about a fifth of a degree. The pipe that carries it has not dropped a single poll in two months. The history we said we couldn't get turns out to be free, and it matches what we recorded to three hundredths of a degree.

And it is aimed at nothing. The level we ship has been true on 100% of days in August, September and October for four consecutive years — so during hurricane season, the only season anyone would switch this on, the switch cannot be off. The one bot we ship using it is pointed at a market category that does not exist. And of the forty-nine hurricane contracts we have ever ingested, every single one is about the Pacific.

So: a superb instrument, wired to nothing. That's a better problem to have than most of the signals on this shelf, and it is worth being precise about which part is good.

Where the number actually comes from

Every three hours, at 17 minutes past — 17 */3 * * *predictive/sst_poller.py makes one HTTP request to marine-api.open-meteo.com/v1/marine, free and with no API key, asking for sea_surface_temperature_max for today at sixteen latitude/longitude pairs at once. I ran the exact URL the poller builds from the production box while writing this: HTTP 200 in 0.68 seconds, sixteen series returned. Recomputing the average by hand from that response gave 27.93 °C and 29.01 °C — byte for byte the two numbers sitting in our newest stored row. The pipe is connected.

Open-Meteo is a front door, not the source. Their own documentation names what is behind it: “MeteoFrance Sea Surface Temperature”, a global model at 0.08° (~8 km) resolution, 6-hourly, available from January 2022 with a 10-day forecast, updated every 24 hours. That last phrase matters twice over, and both times it is load-bearing — it explains why three quarters of our polls are wasted (catch #7), and it is the reason the four-year history in this page exists at all.

The sixteen points are hard-coded in the poller, and they are three different oceans:

Both land in the atlantic_sst table in predictive.db as one row per run, with a count of how many points returned usable data. We hold 244 rows covering 14 July to 13 August 2026. All 244 have all sixteen points. The gate reads the newest row, picks mdr or basin, and compares it to your number.

What it means for your bot

It is a whole-bot kill switch, not a filter. It doesn't pick better tropical markets — it decides whether your bot wakes up at all this cycle. If you set {"metric": "mdr", "above": 26.5} and the storm nursery is at 27.9 °C, everything else in your strategy runs as normal. If it were at 26.2, your bot would place no trades anywhere — not in weather, not in sports, not in crypto. One ocean reading, all markets.

It also fails closed, and correctly. No row, a null value, a timestamp it cannot parse, or a newest row older than six hours, and the answer is no-trade rather than a guess. That is the right default and it is properly implemented here — which, as the rest of this dictionary shows, is not something to take for granted.

We checked it against real thermometers

A forecast of ocean temperature is a claim about the world, so it can be audited. The trap is auditing it against a cousin: Open-Meteo also publishes an archive, but that archive comes out of the same model family, so agreement between them would prove nothing. What we needed was an instrument.

NOAA's National Data Buoy Center moors weather buoys across the Atlantic, the Caribbean and the Gulf, and publishes their readings as plain text at ndbc.noaa.gov/data/realtime2/<station>.txt — free, no key, and the WTMP column is a thermometer in the water. Thirteen of them returned usable water temperature for our window. For each buoy we pulled Open-Meteo's forecast at that buoy's exact coordinates, and applied the identical rule to both sides: daily maximum against daily maximum, same days, no cherry-picking.

Across 380 buoy-days the model ran 0.114 °C cool, with a mean absolute error of 0.214 °C and a worst single day of 0.70 °C. At the two buoys that actually sit inside the Main Development Region box — 41041 at 14.26°N 46.05°W and 41040 at 14.57°N 53.04°W — it was better still: bias +0.05 and −0.03 °C, mean absolute error 0.23 and 0.14.

That is a genuinely accurate field. For context, this is the first signal in this dictionary to survive an independent check: wind_anchor was 10 mph below the gusts it was named after and sorted windy days from calm ones no better than a coin. sst_anchor is right to a fifth of a degree.

Two caveats I am not going to bury

A buoy thermometer sits roughly a metre and a half down; the model's “sea surface” is nearer the skin, and on calm sunny days a thin warm layer can form on top. Some part of that 0.2 °C is two instruments measuring slightly different water, not error. And only two NDBC buoys sit inside the classic MDR box — NOAA does not moor a grid across the mid-Atlantic — so eleven of the thirteen are Caribbean, Gulf, or subtropical Atlantic. Two of the buoys I tried were themselves broken: 42057 returned no water temperature at all, and 42059 stopped partway through the window. That is normal for ocean instruments and it is why the comparison is pooled rather than resting on any one mooring.

The catch

1. 🔴 The gate cannot be false during the only season you would use it

Every setting this site ships or documents is the same number, 26.5 °C, and on our 244 stored readings it is 100.0% open. So is the docstring's second example, {"metric": "basin", "above": 28}. The third, {"metric": "mdr", "below": 26}, is 0.0% — it has never once been true. Four settings, four constants.

That alone would only prove our memory is short, so I went and got the history. Over 1,346 complete days from January 2022 to August 2026, mdr above 26.5 is true 63.7% of the time — a real gate with a real duty cycle. But it is a seasonal switch, not a weather switch. Broken out: February 13.6%, March 16.9%, April 26.7% — and then July, August, September, October and November all 100.0%. Across the official Atlantic hurricane season, June to November, it is open 93.8% of days. Across the peak, August to October, it is open on every single day of four years.

Read that as a trader. The switch is capable of turning off. It turns off in February and March — when there are no Atlantic hurricanes to trade, no tropical contracts open, and nothing for the gate to gate. During the months when a hurricane bot would be running, it is welded on. The signal's discriminating power is precisely anti-correlated with its usefulness.

In fairness: 26.5 is not a silly number, and our window is the problem

Unlike some settings in this dictionary, this one was not picked out of the air, and it is not dead everywhere. A 63.7% duty cycle over four years is respectable. What makes it useless is where our thirty-day memory happens to sit — mid-July to mid-August, climbing straight into the annual peak. The docstring's {"mdr", "below": 26} looks broken at 0% in our data and is in fact a winter setting: it is true on 25.3% of all days in the full record. That's the same lesson wind_anchor taught from the opposite end of the year — there we were parked at the annual floor, here at the annual ceiling. Thirty days of memory cannot represent a variable whose whole behaviour is a twelve-month cycle.

2. 🔴 The bot we ship is pointed at a market category that does not exist

The starter bot “Hurricane-Fuel Watch” is configured with categories: ['tropical']. There is no such category. Kalshi rows are labelled by categorize() in predictive/kalshi_poller.py, whose prefix table can only ever emit weather, sports, crypto, economics, politics, entertainment, commodities, stocks, world-events or other. I checked the ingest rather than trusting the code: 0 of 540,808 Kalshi rows and 0 of 136,812 Polymarket rows have ever carried the category tropical.

This is the identical defect that kills tropical_anchor, and finding it twice makes it a pattern rather than a typo: the builder offers “tropical”, two shipped starter bots use it, and no market can ever match. A bot with this config would evaluate forever and never see a single candidate — which, with an always-open gate above it, would look exactly like a working bot waiting patiently for its moment.

3. 🔴 Every hurricane contract we hold is about the wrong ocean

Repointing the category wouldn't save it. Kalshi's catalogue really does carry the market this signal was built for — I pulled all 12,940 series and found roughly fifty hurricane and tropical-storm series in Climate and Weather, including KXHURCTOT (number of hurricanes), KXHURCMAJ (major hurricanes), KXNAMEDSTORM, KXTROPSTORM, KXFIRSTHURRICANE and about twenty city-landfall series from Miami to New York. Seasonal storm counts are the one thing tropical-Atlantic ocean heat is genuinely a recognised predictor of.

But look at what we actually ingest. We hold 49 markets across KXHURRICANE and KXNAMEDSTORM — and sorted by title, 26 are Central Pacific and 23 are Eastern Pacific. Zero are Atlantic. Our signal measures the Atlantic. The busiest of them traded 67 contracts in 24 hours. Every one is filed other, so even categories: ['weather'] would miss them.

A footnote with a certain poetry: the one surface-water-temperature contract that exists anywhere on Kalshi is KXMICHTEMP — “Lake Michigan surface water temperature.” We ingest none of it. Across both books there is no sea-surface-temperature market at all: 0 of 21,294 distinct Kalshi titles and 0 of 124,915 Polymarket titles mention sea surface temperature, ocean temperature or water temperature.

4. 🔴 NOAA's own threshold is about depth, and we measure the skin at its daily peak

Our copy calls 26.5 °C “the textbook minimum to sustain a tropical cyclone”, and that is fair as far as it goes — it is in NOAA's own FAQ. But I read the primary source rather than the shorthand. The Hurricane Research Division's genesis entry (aoml.noaa.gov/hrd/tcfaq/A15.html, contributed by Chris Landsea of the National Hurricane Center) lists six conditions, and the first one reads in full:

“Warm ocean waters (of at least 26.5°C [80°F]) throughout a sufficient depth (unknown how deep, but at least on the order of 50 m [150 ft]).”

We measure sea_surface_temperature_max — the warmest the surface gets in a day. That is the top of the ocean at its daily peak, which is the least representative moment of the shallowest possible layer, standing in for a condition about fifty metres of water column. A thin warm film over cool water satisfies our gate and would not fuel anything.

The other five conditions are an unstable atmosphere, a moist mid-troposphere, at least 500 km from the equator, a pre-existing disturbance with spin, and vertical wind shear below about 20 knots. NOAA then adds the sentence that should govern how anyone reads this signal: “Having these conditions met is necessary, but not sufficient as many disturbances that appear to have favorable conditions do not develop.” Warm water is permission, not a trigger. We gate on one of six conditions, measured at the wrong depth, at its most flattering hour.

5. 🟠 The average hides the only spatial contrast that matters

Sixteen points collapse to one number, and the collapse throws away real information. On 20 of our 30 days (66.7%) the MDR average sat above 26.5 °C while at least one of the eight MDR points was below it. The spread across those eight points averages 2.87 °C and reaches 3.40 °C. On the live pull for this page the nursery ran from 26.4 °C at 18°N 35°W to 29.1 °C at 12°N 55°W — the eastern end, where storms actually get their start, was the cold end and was below the threshold, while the average comfortably cleared it.

basin is worse, because it isn't a basin. Four of its sixteen points sit in the Gulf of Mexico, which over our window ran 3.10 °C hotter than the MDR. That one choice lifts basin_sst a systematic +1.07 °C above mdr_sst, which is most of why the docstring needs a different threshold (28) for it. A quarter of the weight of a number labelled “the whole warm-water Atlantic” is a small, shallow, much hotter sea, and it is doing the work.

6. 🟠 Not backtest-replayable — and this time the standard excuse is simply false

The string sst appears nowhere in quants/backtest.py. It isn't in _SUPPORTED_ENTRY, and it isn't even in the documentary _UNSUPPORTED_ENTRY list — it is caught only by the catch-all. So a backtest of a bot using this gate runs the strategy without the gate, returns a perfectly normal-looking equity curve, and flags unsupported_rules. Whatever that curve is testing, it is not this signal.

The poller's docstring justifies this: “Open-Meteo's free marine forecast is forward-only (no archive here), so the signal is live-only.” That is not true. The same endpoint accepts start_date and end_date; I pulled 2022-01-01 to 2026-08-12 in a single 1.4-second request, HTTP 200, and got 1,346 complete sixteen-point days — the exact series this page's seasonal figures are built from.

And it is the same series. Comparing the backfill against the 30 days we captured live, the mean difference is −0.013 °C with a mean absolute error of 0.031 °C and a worst day of 0.12 °C. That distinction matters, because wind_anchor's archive existed too but produced a materially different signal, which made its live-only status defensible. Here it isn't: four and a half years of replayable history is one URL parameter away, and it would reproduce what we recorded to three hundredths of a degree.

7. 🟡 Three quarters of our polls fetch a number we already have

We poll eight times a day. Open-Meteo's documentation says the sea-surface-temperature product is updated every 24 hours. The consequence is exactly what you'd expect and is visible in the table: of 244 stored readings, only 59 (24.2%) differ from the one before them. 184 polls — 75.4% — recorded a number we already had. Most days show one or two distinct values across all eight polls.

Nothing breaks; it is a free API and the row is small. But it means the “every 3 hours” on the fact card above overstates this signal's freshness by a factor of eight, and any intuition that this is a fast-moving reading is wrong. Sea-surface temperature is a slow variable sampled far faster than it moves.

8. 🟡 Configs that validate and cannot mean anything

{"metric": "mdr", "above": 26.5, "below": 26} passes validation and renders as “only when sea-surface temp > 26.5°C and sea-surface temp < 26°C” — two opposite conditions, permanently unsatisfiable, no warning. {"above": 100} (a boiling ocean) and {"below": -5} (frozen seawater) both validate too. This is the same class of gap found in finance_anchor and btc_dominance_anchor: the validator checks types and spelling but never asks whether the range it just accepted can occur.

Smaller, and consistent with nine other pollers: the prune says fetched_at < datetime('now', '-30 days'), but fetched_at is ISO-8601 with a T in it while SQLite's datetime() uses a space, and 'T' sorts after ' '. So the cut-off day never prunes and we keep 31 calendar days, not 30. Harmless here. Not fixed — this loop doesn't touch pollers.

The case for the defence

Five things this signal gets right

  • It is accurate. 0.214 °C mean absolute error against thirteen moored thermometers, and 0.14–0.23 °C at the two buoys inside the region it is named after. Nothing else in this dictionary has passed an independent check.
  • The healthiest pipeline on the shelf. 460 runs, 460 successes — zero failures, zero database is locked, zero tracebacks in the entire log. All 244 rows carry all sixteen points. The :17 cron slot sits clear of the ticker-index write lock that has mauled seven other pollers. This is a better record than wind_anchor's 464-of-466, which was the previous best.
  • The freshness check is correctly written. It parses the timestamp into a real timezone-aware datetime rather than comparing strings — the bug that silently defeats the same check in finance_anchor and tropical_anchor — and it fails closed on anything unparseable. It has never had to fire: across 243 intervals the largest gap was 10,802 seconds against a 21,600-second threshold, and the six-hour window correctly tolerates exactly one missed three-hourly poll.
  • The validator rejects the things that matter most. {"metric": "gulf"} is refused with a helpful message, a missing condition is refused, and a boolean where a number belongs is refused. The gaps in catch #8 are about ranges, not types.
  • The physics is real and correctly explained. Warm water genuinely is the fuel, the MDR genuinely is the nursery, and the module's English is accurate throughout — to_english says what the gate does. This is a signal built on a true idea and a good feed. Only the wiring and the threshold are wrong.

The picture

How sst_anchor works, and why its threshold cannot turn off in hurricane season Four panels. First, the data path from sixteen fixed ocean points through the Open-Meteo marine API, the poller, the atlantic_sst table and the gate to a trade decision. Second, a bar chart of the percentage of days per month on which the shipped 26.5 degree setting is open, computed from 1,346 days of history: it is 13.6 percent in February but 100 percent in July through November, with hurricane season and our thirty-day memory both marked. Third, the verification result: the difference between the model and thirteen NOAA buoy thermometers, all within 0.4 degrees of zero. Fourth, today's sixteen individual ocean points, showing the eastern nursery below the 26.5 threshold while the average clears it and the Gulf runs three degrees hotter.
Panel 2 is the finding. The shipped 26.5 °C setting is a genuine gate in February and March and a constant from July to November — and hurricane season is shaded so you can see that the months it can discriminate in are the months with nothing to trade. The green strip marks all thirty days our database keeps. Panel 3 is the verification: every one of thirteen NOAA buoys agrees with the model to within 0.4 °C, and the two moored inside the storm nursery agree to within 0.05. Panel 4 shows why one average is a lossy summary — the eastern end of the nursery was below the threshold on the day this was written, while the average cleared it comfortably.

Its record

There isn't one, and this page is not going to invent one. Of our 76 strategy configurations — 21 live, 15 out-of-sample, 36 archived, 4 draft — and all 88 stored strategy versions, and all 1,064 configurations in the genome bank, zero mention sst_anchor. No bot has ever been armed with it. There are no trades, no fills, no evaluations and no profit or loss to report.

Meanwhile the poller has made 460 successful requests. This signal sits in the same category as the other three-hourly weather anchors: the data is collected faithfully every day and nothing reads it. Of the eight weather signals on this shelf, only wx_anchor has ever had a bot attached, and it reads air temperature.

What this page does not tell you

Notes for the record

The signal registry resolves this entry's source as “undocumented”, which is wrong: the anchor's docstring names Open-Meteo in plain English, and the poller's docstring names it twice. The cause is a lookup table, SOURCE_HINTS, that still has no entry for Open-Meteo — first noticed writing up heat_index_anchor, again on qpf_anchor, again on wind_anchor, and still unfixed. This is the fourth page to carry the correction.

The as_of column is documented in the schema as “forecast issue time” and holds the fetch time in 244 of 244 rows — the fifth signal with this same mismatch, after ensemble, heat_index, qpf and wind. Everything else the registry reports for this signal — poller, cron, table, replayable: false, live_bots: 0 — is correct, and here zero really does mean zero.

One more, because this dictionary has kept score: the backlog entry for this signal was annotated “⚠️ NO DOCSTRING — read the code”. It has a 25-line docstring and the registry's own documented field says true. That annotation has now been wrong on every single signal it was applied to.

Every number on this page was read from production on 13 August 2026 — the registry, the module source, the atlantic_sst table, the poller log, the strategy database, Kalshi's series catalogue, and live requests to Open-Meteo and to NOAA's buoy network. The genesis conditions are quoted from NOAA AOML's Hurricane Research Division FAQ. Nothing here is estimated or remembered. Where a check could not be run, the page says so rather than guessing.