Trade Bot Dictionary · Tropical

tropical_anchor

tropical_anchor is a master switch wired to the National Hurricane Center. Four times a day the NHC publishes two things: a list of storms currently spinning, and a list of areas of interest — blobs of disorganised weather it is watching — each with an official percentage chance of turning into a tropical cyclone within 48 hours and within 7 days. You pick a condition off that list: "only when a hurricane is active in the Atlantic", or "only when NHC gives an Atlantic disturbance at least 70% odds of forming inside a week". While your condition holds, the bot goes on and trades. While it doesn't, the bot places no trades at all that cycle, in any market. It is a switch for the whole bot, not a filter on storm-related markets.

Bots using it
1 (out-of-sample; none live)
Data source
NOAA · National Hurricane Center
Refresh
Every 3 hours (0 */3 * * *)
History stored
458 snapshots 3 Jun – 9 Aug 2026
Backtest-replayable
No — see catch #10
Anchor module
quants/tropical_anchor.py

That is the tidy version. The rest of this page is what we found when we followed the wires, and the headline is not flattering: the one bot built on this signal has run 17,390 times and never placed a single trade — not because the weather never cooperated, but because it asks for a category of market that does not exist in our data, and the markets it means are almost untraded anyway. Both of those are true at the same time, and either one alone would be enough.

How tropical_anchor gets from a National Hurricane Center bulletin to a trade decision, and the two independent reasons its only bot has never traded The top half traces the data path. The source is NOAA's National Hurricane Center, which is free and needs no key. The poller reads two kinds of feed from it. The first is CurrentStorms.json, a machine-readable list of active named storms giving each storm's name, a classification code such as TD for tropical depression, TS for tropical storm or HU for hurricane, and an intensity in knots. The second is the Tropical Weather Outlook, a plain-text bulletin scraped for each area of interest and its official National Hurricane Center formation-chance percentages at 48 hours and 7 days. Three outlook products are requested. The Atlantic product MIATWOAT returns HTTP 200 and the East Pacific product MIATWOEP returns HTTP 200, but the Central Pacific product the code asks for, MIATWOCP, returns HTTP 404 and always has; the correct Honolulu product identifier is HFOTWOCP. The failure is swallowed silently, so the central Pacific basin has no rows at all. The poller is predictive slash tropical underscore poller dot py on a cron of zero star slash three, meaning every three hours. It is currently dead: its last ten consecutive runs have died with a SQLite database-is-locked error, and the newest successful write is from 21:00 UTC on the ninth of August 2026. It writes to the tropical underscore systems table in predictive.db, which holds one thousand one hundred and seventy-one rows across four hundred and fifty-eight snapshots from the third of June to the ninth of August 2026. The gate function in quants slash tropical underscore anchor dot py takes the most recent snapshot, provided that snapshot is less than twelve hours old, and asks whether any row in it matches your condition. Pass means the bot scans markets as normal. Fail means the bot places no trades at all that cycle, in any market. Missing or stale data also means fail, so the gate fails closed. The bottom half shows why the only bot using this signal has never traded. Two conditions must both be true before it can buy anything, and they are independent. The first track is the weather gate: of four hundred and fifty-eight stored snapshots, the bot's condition — an Atlantic disturbance at seventy percent or higher seven-day formation odds — was satisfied in five of them, which is one point one percent, spread over three calendar days. That is a low duty cycle but it is not zero. The second track is the market filter, and this one is zero. The bot asks for Kalshi markets whose category is tropical. The Kalshi poller's categoriser can only ever return one of nine labels, and tropical is not among them; the labels present in our table are crypto, weather, sports, other and economics. Hurricane markets do exist — forty-nine of them — but they are filed under other. So the category filter matches zero markets and always has. A third panel shows that fixing the category would still not be enough: of those forty-nine hurricane markets, twelve fall inside the bot's twenty to sixty cent price band, but zero of them meet its minimum twenty-four-hour volume of one thousand contracts, because the highest twenty-four-hour volume recorded on any of them is twenty-seven. SOURCE POLLER TABLE GATE DECISION NOAA · NHC nhc.noaa.gov free · no key · no account CurrentStorms.json active storms · class + knots Tropical Weather Outlook areas of interest · 48 h / 7 d odds ✓ 200MIATWOAT · atlantic ✓ 200MIATWOEP · east pac ✕ 404MIATWOCP · cen pac correct id is HFOTWOCP tropical_poller.py predictive/ 0 */3 * * * 8×/day appends one snapshot per run · never edits prunes past 120 days ✕ DEAD — db locked 10 runs in a row failed last write 9 Aug 21:00 UTC tropical_systems predictive.db 1,171 rows · 458 snapshots 3 Jun – 9 Aug 2026 atlantic205 east pacific966 central pacific0 forecast stored · outcome never recorded (catch #8) tropical_anchor quants/ · runs every 5 min 1. newest snapshot only 2. must be under 12 h old …but see catch #5 3. does ANY row in it match your condition? no rows · stale · error → fails closed (one exception: catch #2) PASS bot scans markets as normal, then applies its filters FAIL no trades at all this cycle — in any market Why the only bot using this signal has never placed a trade “Tropical Formation Lag” · out-of-sample · 17,390 engine runs since 11 Jun 2026 · 0 candidates · 0 fills · 0 errors Both tracks below must be true at the same moment. They are independent — fixing either one alone changes nothing. TRACK 1 · THE WEATHER GATE basin atlantic, formation_7d_above: 70 5 of 458 stored snapshots satisfied it 1.1% Open on 3 calendar days: 16 Jun, 17 Jun, 19 Jul. Low, but not zero. Context: NHC listed an Atlantic area of interest at all in only 128 of those 458 snapshots — the other 330 are quiet skies, not a broken gate. TRACK 2 · THE MARKET FILTER categories: ["tropical"] on Kalshi Every label the Kalshi categoriser can produce, with rows in our table: crypto 488,383 weather 26,372 sports 1,866 other 1,567 economics 70 politics, stocks, … 0 tropical — not a label the categoriser can ever emit 0 AND AND EVEN IF THE CATEGORY WERE FIXED Hurricane markets do exist on Kalshi — they are filed under other. Point the bot at them and it still buys nothing: 49 KXHURRICANE / KXNAMEDSTORM markets in our table, all filed “other” 12 inside the 20¢–60¢ band the bot’s configured price window 0 clear vol_24h_min: 1000 highest 24 h volume on any of the 49 is 27 contracts The 49 are season-count contracts — “more than 7 hurricanes of category 3 or above by 1 Dec”. A 7-day formation probability for one Atlantic blob is not an answer to that question, which is the deeper problem (catch #7). Counts read from production on 11 Aug 2026. The category defect is ours; the thin order books are the venue’s.
The pipeline, and the two independent dead ends underneath it. Track 1 is the signal doing its job — rarely, but correctly. Track 2 is a filter that has matched nothing since the day the bot was armed, and the bottom panel shows that repairing it would not be enough on its own.

Where the data comes from

One organisation, no vendor, no key, no account: NOAA's National Hurricane Center in Miami. It is the US government's official forecaster for the Atlantic and eastern Pacific, and both feeds we use are published free to the public. The poller reads two very different things from it.

Feed 1 — the storms that already exist

https://www.nhc.noaa.gov/CurrentStorms.json is a machine-readable list of every active named system. For each one we keep the basin, the name, a classification code (TD tropical depression, TS tropical storm, HU hurricane, PTC post-tropical cyclone, and so on) and the intensity in knots. In our 67 days of history that feed has given us 17 named systems — 13 in the east Pacific, 4 in the Atlantic. The strongest was Hurricane Genevieve in the east Pacific at 140 knots on 27 July, which is a Category 5. The strongest Atlantic system was Tropical Storm Bertha at 50 knots; we have not recorded an Atlantic hurricane at all in this window.

Feed 2 — the storms that don't exist yet

This is the interesting one, and it is the reason the signal is worth having. The Tropical Weather Outlook is a plain-text bulletin NHC issues roughly four times a day. It describes each area of interest — a patch of disorganised weather worth watching — and attaches NHC's official odds that it becomes a tropical cyclone within 48 hours and within 7 days. The text looks exactly like this, pulled live from the Atlantic product while this page was being written:

Central Tropical Atlantic: Shower and thunderstorm activity is currently limited in association with an area of low pressure located about 500 miles southwest of the Cabo Verde Islands... * Formation chance through 48 hours...low...30 percent. * Formation chance through 7 days...high...70 percent.

There is no JSON version of that. Our poller fetches the HTML page, strips the tags out of the <pre> block and runs a regular expression over the prose to lift the two percentages, then labels each one with the nearest heading above it. That is genuinely the best available route to the number, and it is worth stressing what the number is: an official government probability, published on a schedule, about an event that has not happened yet. It is not something a trader can read off the order book. That is the whole argument for this signal existing.

The poller

predictive/tropical_poller.py, on cron 0 */3 * * * — every three hours, eight times a day, against an outlook that NHC reissues about four times a day. Each run appends one complete snapshot stamped with a single fetched_at; nothing is ever edited in place, and rows older than 120 days are deleted. It requests three outlook products, one per basin.

Note that the signal registry reports this signal's poller as db with no schedule. That is a bug in the registry, not a missing poller: its regex hunts for the pattern predictive.<word> in the module docstring and matches the words "predictive.db" instead of a filename. The same artefact appears on the energy_anchor page. The registry also lists this signal's source as "undocumented" — it is documented, in the docstring, by name.

The table

tropical_systems in predictive.db: 1,171 rows across 458 snapshots, 3 June to 9 August 2026. Each row is either an active storm (352 rows) or a disturbance with formation odds (819 rows). By basin it is 966 east Pacific, 205 Atlantic, and zero central Pacific — see catch #6.

What it means for your bot

It is a global switch, not a filter. This matters more than people expect. When the gate is shut your bot does not trade fewer markets or trade more carefully — it does nothing at all, in every market it was configured for, including markets that have nothing to do with the weather. When the gate is open, the bot simply proceeds to its ordinary entry rules; the anchor never tells it which market to buy or which side to take.

The conditions available are: a named storm is active; an active storm of at least a given class; more than N active systems; a disturbance at or above a given 7-day formation percentage; the same at 48 hours. You can add a basin, or leave it off to mean any basin. Multiple conditions in one config are ANDed together.

Two behaviours worth knowing before you write a config. First, every condition is satisfied by any single row in the newest snapshot — formation_7d_above: 40 means "at least one area of interest in this basin is at 40% or more", not the strongest one, not the sum, and it does not care which area or whether it is the same area as last time. Second, the gate looks only at the most recent snapshot, so there is no notion of an odds trend; a disturbance climbing 10 → 30 → 60 and one collapsing 90 → 60 look identical to it.

The one bot that has used it

Tropical Formation Lag (id 38), a house bot, armed out-of-sample on 11 June 2026 and never promoted to live. Its rule: Kalshi only, buy YES between 20¢ and 60¢, at least 1,000 contracts of 24-hour volume, at least 24 hours to close, in category tropical, gated on {"basin": "atlantic", "formation_7d_above": 70}.

Tropical Formation Lag · 11 Jun – 11 Aug 2026Count
Engine evaluations17,390
Candidate markets found0
Fills0
Errors0
Realised P&L— (no trades)

There is no track record to report and this page will not manufacture one. Zero errors is worth noticing: nothing is crashing. The bot is running perfectly and finding nothing, which is the quietest kind of failure there is.

The catch

Ten of them, worst first. Nine make the gate refuse to open; one — catch #2 — does the opposite, and silently holds it open forever. If you read only one, read that one.

1. 🔴 The category it filters on does not exist, so no bot built this way can ever trade

The bot asks for Kalshi markets in category tropical. The Kalshi poller assigns every market a category by matching its ticker against a list of prefixes, and that list has no tropical entry — the only labels it can produce are weather, sports, crypto, economics, politics, entertainment, commodities, stocks, world-events and other. The string "tropical" is not among them, and there are zero such rows in our market table. Polymarket's categories don't include it either. The filter has matched nothing since the day the bot was armed, and it always will.

This is not one bot's typo. The builder offers "tropical" in its category list, and we ship a starter bot called "Formation Fade" that uses it. Anyone who picks tropical in the builder gets a bot that cannot trade, with no warning anywhere.

Hurricane markets genuinely do exist — 49 of them in our table, tickers like KXHURRICANE-26DEC01EPACMAJ-5 — and they are filed under other. But repointing the bot at them would not resuscitate it either: 12 of the 49 sit in its 20¢–60¢ band, and none of them clear its vol_24h_min: 1000, because the highest 24-hour volume ever recorded on any of the 49 is 27 contracts. Two independent locks. To be fair about whose fault each one is: the missing category is ours, and the empty order books are the venue's.

2. 🔴 active: false does not mean "no storms" — it jams the gate open

The code asks if anchor.get("active") and len(active) < 1: return False. If you set active to false, the first half of that is falsy, so the check is skipped entirely — and because no other condition was given, the function falls through and returns True. We verified it: with a config of {"basin": "atlantic", "active": false} against a snapshot containing one disturbance at 0% odds and no storms whatsoever, the gate returns True.

So the config that reads most naturally as "only trade when the Atlantic is quiet" actually means "trade whenever we have any Atlantic row at all". Validation accepts it without complaint, and the plain-English preview the builder shows you degrades to the meaningless "tropical activity gate" rather than warning you. This is the only defect on this page that makes a bot trade more than you intended, which is why it is this high. There is no "no storms" condition in this anchor. If that is what you want, it does not exist — don't try to spell it with active.

3. 🔴 The poller is dead right now — and it went down just before the signal fired

The last ten consecutive runs have died with sqlite3.OperationalError: database is locked, taking the whole batch down with them (the insert fails before the commit, so nothing is written). Over the poller's life that is 67 failures in 548 runs, but the failures are not evenly spread — the most recent ten are consecutive, and the newest successful write is from 21:00 UTC on 9 August, roughly two days stale.

Here is why that is not an abstract complaint. At 05:04 UTC on 11 August, NHC's live Atlantic outlook gave the Central Tropical Atlantic 70% odds of formation within 7 days. That is exactly the condition Tropical Formation Lag is armed on. The newest row in our table says 50%, from two days ago, so the gate is shut at the one moment in three weeks it should be open. Only the fourth time in 67 days the condition has been met, and we cannot see it.

This is the fourth job we have found dying on the same shared-predictive.db write contention, after the nightly resolution-bucket rebuild, the sentiment poller and the EIA poller. There is no retry, no alert and no staleness check anywhere that would surface it. Fixing pollers is out of scope for the loop that writes these pages; it has been reported.

4. 🔴 "Major hurricane" can never match, not even during a Category 5

min_classification accepts MH, and the builder renders it as "only when a major hurricane is active". It ranks the storm's classification string against a table in which MH outranks HU. But NHC does not publish "MH" as a classification. A major hurricane is reported as HU with a high intensity; "major" is a strength threshold (96 knots and up), not a category code. In 352 active-storm rows the only classifications we have ever received are HU, TS, TD, PTC and PC.

Hurricane Genevieve proves it. On 27 July she was carrying 140 knots — Category 5 — and NHC filed her as HU. All 25 of our rows at major-hurricane strength are classified HU. We ran the gate against that exact row: min_classification: "MH" returns False through a Category 5. The intensity in knots is sitting in the very same row, stored and never read. A config that looks like the most selective setting on the menu is in fact a permanently shut gate.

5. The 12-hour freshness check is not 12 hours

The gate refuses to use a snapshot older than 12 hours — the docstring says so and the SQL looks right: fetched_at >= datetime('now','-12 hours'). But the poller writes fetched_at as a Python ISO-8601 string, 2026-08-10T00:00:01+00:00, while SQLite's datetime() returns 2026-08-10 17:27:43. SQLite compares them as text, and at character 11 it is comparing 'T' against a space. 'T' sorts higher, so any row bearing that date wins the comparison.

Verified on production: a row stamped 2026-08-10T00:00:01+00:00, which was 29.5 hours old at the time of the test, passed the 12-hour check. The real cutoff is midnight UTC of whatever date it was 12 hours ago, so the effective window breathes between 12 and nearly 36 hours depending on the time of day you ask. It does still fail closed eventually — which is why the two-day-old data in catch #3 is correctly rejected — just up to a day later than advertised. This is the same class of bug documented on the finance_anchor page, in a different module.

6. One of the three basins has never returned a single row

central_pacific passes config validation, renders in plain English and is completely empty: 0 rows in 458 snapshots. The reason is a wrong URL. The code requests the Central Pacific outlook from nhc.noaa.gov/text/MIATWOCP.shtml, and we confirmed from production that it returns HTTP 404 — and always has. "MIA" is Miami; the Central Pacific outlook is issued by the Central Pacific Hurricane Center in Honolulu, under HFOTWOCP. We fetched that one too: HTTP 200, and the poller's existing regex parses two areas of interest out of it unchanged. The Atlantic and east Pacific products both return 200 and work correctly.

The failure is invisible because the fetch is wrapped in a bare except that returns an empty list. No log line, no counter, no alert — for 67 days, a third of the feed has been quietly missing and the poller has reported success on every one of those runs.

7. NHC is answering a different question than the market is asking

This one is not a bug, and it would survive every fix above. A formation probability is about one specific blob of weather becoming a named cyclone within a window. The tropical contracts in our universe are almost all season counts: "will there be more than 7 hurricanes of category 3 or above by 1 December". Knowing that a particular Atlantic disturbance has a 70% chance of forming this week barely moves that number, and the 49 hurricane markets we hold are mostly east and central Pacific season counts, which an Atlantic formation probability says nothing about at all.

Genesis is also not landfall, not intensity and not damage. If you gate a bot on formation odds, be honest with yourself about whether the contract you are buying actually resolves on the thing NHC is forecasting. Mostly, in our market universe today, it does not.

8. We store the forecast and never the outcome, so nobody can score it

The table keeps 819 disturbance readings with NHC's odds on them, and no record of whether any of those areas ever became a storm. There is no stable identifier to join on: the area is labelled with free text scraped from the bulletin heading, like "Eastern Tropical Atlantic" or "Bay of Campeche", occasionally carrying an invest number in brackets — "Northern Gulf of America and near Florida (AL91)" — but usually not. The names drift as NHC rewrites the geography from one bulletin to the next.

The consequence: we cannot tell you whether NHC's 70% means 70%, and neither can you, from anything we store. Not because the forecasts are bad — NHC's calibration is a serious question with a real literature behind it — but because we have not captured the ground truth needed to check it. This page is not going to quote a calibration figure it did not measure.

The label heuristic is also imperfect in a visible way. It takes the nearest heading above each set of percentages, and seven of our Atlantic rows are labelled "For the North Atlantic...Caribbean Sea and the Gulf of America" — which is the bulletin's own title line, not an area of interest. The percentages on those rows are still real NHC numbers; you just cannot tell what they were about.

9. Two settings end in _above and mean different things — and it decided most of the gate's history

formation_7d_above: 70 matches a reading of exactly 70. But active_count_above: 2 does not match exactly 2 — it is strictly greater, so it needs 3. Same suffix, same menu, opposite convention, no warning.

That is not pedantry here. Of the five snapshots in which the bot's gate has ever been satisfied, three read exactly 70 — the 16–17 June system. Had formation_7d_above followed the other key's convention, the gate would have opened on one day in 67 instead of three. A single character of comparison operator accounts for 60% of this signal's entire open history.

10. The backtester cannot replay it

tropical_anchor is not in the backtester's supported-rules set, so a backtest runs without the gate and still returns a curve — it flags the key under unsupported_rules, but it does not refuse. Whatever that curve shows, it is the performance of the price and volume filters alone, with the weather switch permanently on.

The stated reason is that these anchors "need a forward-captured series we don't have retroactively", which is fair here in a way it was not for energy_anchor: NHC does not serve a convenient archive of past Tropical Weather Outlooks in this format, so 458 forward-captured snapshots really are all we have. Note also that 67 days of an unusually quiet Atlantic June and July is a thin tape to replay a hurricane strategy against even if the plumbing existed.

How often is it actually open?

Worth separating from the catches, because a rare signal is not a broken one and it would be sloppy to imply otherwise. Across all 458 stored snapshots, the bot's condition — an Atlantic area of interest at 70% or better — held in 5, or 1.1%, spread over three calendar days.

Most of that is simply weather. NHC listed any Atlantic area of interest in only 128 of the 458 snapshots; the other 330 are quiet skies, and on those the gate is correctly shut because there is nothing to be shut about. Of the 154 Atlantic disturbance readings we do hold, here is the whole distribution:

NHC 7-day formation odds (Atlantic)0%10%20%30%40%50%60%70%80%
Readings8554816661032

The tail is thin by nature: NHC only reaches high confidence shortly before a system actually forms, so a 70% threshold is close to asking "is a storm about to exist". Dropping to 40% would have opened the gate on 27 of 154 readings instead of 5 — a real strategy choice, not a bug fix. And the window itself was quiet: the Atlantic produced four systems in these two months, none of them a hurricane. A busier season would move all of these numbers.

How to actually use it

Do not use the tropical category. It matches nothing on either platform. Until the categoriser learns the KXHURRICANE prefix, the only way to reach those markets is categories: ["other"] with title_contains: ["HURRICANE"], and you must drop vol_24h_min or nothing will clear it. That configuration currently finds nine markets — and every one of them is a season-count contract closing on 1 December, which brings you straight back to catch #7. We are not going to dress that up: there is no market in our universe today that this signal cleanly speaks to.

Never write active: false. It does the opposite of what it reads like. If you want "only when things are calm", this anchor cannot express it.

Use a threshold that can fire. 70% and above has happened five times in 67 days. If you want the gate to open often enough to learn anything, 40–50% is the honest range — accepting that you are then trading a maybe, not an almost-certainty.

Match the basin to the market. Nothing enforces it. Gating a Pacific season-count contract on Atlantic formation odds is a legal config and a meaningless one, and leaving the basin off means "any basin", which is broader than most people intend — two-thirds of our storm rows are east Pacific.

Ask for a strength, not a label. Because of catch #4, use min_classification: "HU" if you want a hurricane. There is no working way to ask for a major hurricane, and MH will silently give you nothing.

You can watch the same two feeds, without a bot, on the Tropical Outlook tool — which fetches active storms live from NHC rather than from the table, so during an outage like the current one it will show you storms the gate cannot see.

Where to go next

sentiment_anchor is the closest relative: another global mood switch whose only armed bot has never had its gate open, and another victim of the same locked database. finance_anchor is the one to read if catch #5 caught your eye — its freshness check fails the same way, in a module that a bot actually depends on. energy_anchor is the other signal whose registry entry mislabels its poller as db.

There is a second tropical signal in the library that no bot uses: sst_anchor, which reads Atlantic sea-surface temperature — the ocean heat that has to be in place before formation odds ever light up — and refreshes every three hours. It has not been written up yet.

Written 11 August 2026. Every number on this page was read from production that day — the live host at /var/www/tinycorp.ai, not a local mirror: the output of quants/signal_registry.py, the source of quants/tropical_anchor.py, predictive/tropical_poller.py and predictive/kalshi_poller.py, the live crontab and poller log, read-only queries against tropical_systems and kalshi_markets in predictive.db and the bot's run records in quants.db, and NHC's four outlook URLs fetched directly from that same host. The gate behaviours in catches #2, #4 and #5 were confirmed by running the real anchor function against synthetic in-memory snapshots — no production data was written. Two things the registry reports about this signal are wrong and are corrected above: the poller resolves as db with no schedule, and the source resolves as "undocumented" when the docstring names NHC outright. Its "live bots" column counts status oos and live together, so the 1 it reports is one out-of-sample bot and no live ones — the fifth signal running where that count has not meant "live". Nothing on this page is a projection.