qpf_anchor
qpf_anchor is a master switch wired to how much rain is about to fall on the wettest of sixteen big American cities. Every three hours we ask one free weather API for the expected precipitation over the next three days in each metro, add up each city's three days, and keep two numbers: the single largest city total, and the population-weighted national average. While your level holds, the bot trades. While it doesn't, the bot places no trades at all that cycle, in any market. QPF stands for quantitative precipitation forecast — how much water, not whether it rains — and it is measured in inches of liquid, melted snow included.
- Bots using it
- 0 (none live, none out-of-sample, none ever)
- Data source
- Open-Meteo /v1/forecast — the registry says “undocumented”
- Refresh
- Every 3 hours (29 */3 * * *)
- Rows stored
- 241 readings 30 days — the rest is deleted
- Backtest-replayable
- No — see catch #6 for what it would take
- Anchor module
- quants/qpf_anchor.py
This page has a different shape from the rest of the dictionary. On most of these signals the punchline is that the market doesn't exist. Here it does. Kalshi runs a daily rain market called KXRAIN — twenty cities, a fresh contract every day, settled on official station data — and as I write this there are 26 open contracts carrying $275,121 of volume and $124,026 of open interest, with $5.47 million traded across the 460 that have already settled in the past four weeks. That is a real, working book about rain.
Our database contains none of it: zero of 539,344 ingested Kalshi rows mention rain. And even if it did, the gate could not speak to it. The market asks whether any measurable rain falls at one station on one day. The gate answers with a three-day total for whichever of sixteen cities happens to be wettest — and it answers for the whole bot at once, so a soaking forecast for Miami licenses a bet on whether it rains in Phoenix. Tested against those settled contracts, the national number the gate uses scores an AUC of 0.534, with a confidence interval that comfortably contains a coin flip. The per-city number the poller computes on every run and then throws away scores 0.77.
The sharpest finding, though, is about the gate's own sentence. It is sold as detecting whether “any big city is due a real soaking”. Over the month we can check, some metro did go on to record more than an inch on 84.7% of the polls when the gate was open — and on 88.5% of the polls when it was shut. The event it claims to detect happens either way. That is what this page is about: not a broken feed or a dead threshold, but a well-built pipeline whose final three lines throw away the useful number and keep one that is almost always on.
Where the data actually comes from
One free, keyless HTTP request. predictive/qpf_poller.py builds a single batched call to api.open-meteo.com/v1/forecast carrying all sixteen latitude/longitude pairs at once, asking for daily=precipitation_sum with precipitation_unit=inch, timezone=auto and forecast_days=3. Cron fires it at 29 */3 * * * — eight times a day, at 29 minutes past midnight, 3am, 6am and so on in UTC.
The sixteen metros are New York, Los Angeles, Chicago, Dallas-Ft Worth, Houston, Washington DC, Philadelphia, Atlanta, Miami, Phoenix, Boston, San Francisco, Detroit, Seattle, Minneapolis and Denver — 111.9 million people. That list is byte-for-byte identical to the one in heat_index_anchor, wind_poller and aqi_poller; I checked rather than assumed. Each metro's three daily figures are summed into a three-day total. Then peak_precip is the largest of the sixteen, mean_precip is the population-weighted average, and wettest_metro records which city won. The row goes into qpf_forecast in predictive.db, which holds 241 rows covering 14 July to 13 August 2026 and is pruned to thirty days.
Three notes on the record itself. The registry reports this signal's source as “undocumented”, which is wrong — the anchor's own docstring says “a free, keyless Open-Meteo daily forecast” in plain English. The root cause is the one found on the heat_index_anchor page and still unfixed: SOURCE_HINTS in quants/signal_registry.py has no Open-Meteo entry at all, so the vendor behind six of our weather signals is undetectable by construction. Everything else the registry says — poller, cron, table, replayable: false, live_bots: 0 — is correct, and here zero really does mean zero. Second, the backlog that queues these pages flagged this signal “NO DOCSTRING”; it has a 25-line one. That annotation has now been wrong on every signal it was applied to. Third, the as_of column is documented in the schema as the forecast issue time and holds the fetch time in all 241 rows — so the freshness check proves the cron fired, not that the model ran.
What it means for your bot
It is a whole-bot switch, not a filter on rainy markets. When the level fails, the engine returns an empty candidate list and the bot places no trades that cycle in any category — the same shape as sentiment_anchor and degree_days_anchor. The builder offers two metrics, “wettest metro” and “average”, a direction, and a number in inches. Its default is {metric:'peak', above:1}; so is the one-click recipe “Heavy-rain regime”; so is the starter bot “Heavy-Rain Watch”, which pairs it with categories:['weather'] and a 30¢–60¢ price band.
Read plainly, that setting says: trade only when a real soaking is coming. The rest of this page is about the distance between that sentence and what the code does.
The catches
1 · The rain market is real, liquid and open right now — and we hold none of it
Kalshi's KXRAIN series is titled “Where will it rain daily”, files under Climate and Weather, and settles on The Weather Company's station data. The contract language is precise: “If the total precipitation at CLISFO in San Francisco in Aug 13, 2026 is strictly greater than 0 inches, then the market resolves to Yes.” Twenty cities, a new contract every day. 26 open as I write, $275,121 of volume and $124,026 of open interest; 460 settled in the last four weeks carrying $5,469,138, an average of $11,889 each. Not a gold rush, and not a ghost town — a working book. Fifteen of our sixteen metros have a daily contract on it. Detroit does not; and Austin, Las Vegas, New Orleans, Oklahoma City and San Antonio have contracts we never poll.
Our database contains none of them. Zero of 539,344 ingested Kalshi rows mention rain, precipitation, flood or snow. All 26,852 of our weather-category rows are KXHIGHT* / KXLOWT* — brackets on the daily high and low air temperature. So the starter bot's categories:['weather'] would point a rain gate at temperature contracts.
Where the blame actually sits. Our Kalshi ingest is a hand-curated list of 40 series across 20 stations, and the comment above it records an audit date of 28 May 2026, checked against Kalshi's own Climate-and-Weather event list. The daily KXRAIN series did not exist then — its earliest contract opened at 19:09 UTC on 14 July 2026, and its NYC-only predecessor KXRAINNYC ran 5 June to 14 July, trading $1,536,096. (I have not audited Kalshi's full historical catalogue, so I can't claim no rain market existed before June.) But the audit date explains only how the gap opened. It does not explain why the gap is still there four weeks later. Nothing re-reads the catalogue — a hand audit silently became the permanent state of the world.
2 · The gate answers a different question than the market asks — and I measured how badly
The market asks whether any measurable rain falls at one station on one day. The gate answers whether the three-day total at whichever of sixteen cities happens to be wettest exceeds a level — for the whole bot at once.
Against the settled contracts, on the 300–345 city-days that map to a metro we poll: the per-city three-day total scores AUC 0.768, the per-city same-day forecast 0.765, and the national peak the gate actually uses scores 0.534 — 95% confidence interval 0.456 to 0.612, which contains a coin flip. As a plain threshold the per-city same-day number is genuinely useful: when it forecast more than a hundredth of an inch, rain followed on 76.2% of city-days (48 of 63); when it didn't, on 12.4% (35 of 282).
Two honest limits on those numbers. All three come from Open-Meteo's near-analysis archive rather than from what the forecast said at the time, so 0.77 is a ceiling, not live forecast skill — the comparison between the three is sound because they share a source and a lead time, but the absolute level is optimistic. And the three-day total contains the same-day value, so 0.768 and 0.765 are not independent evidence of anything.
With the live gate on the real days: labelling a day OPEN when the majority of its eight polls had peak > 1, rain fell on 20.0% of the 380 open city-days and 26.2% of the 80 shut ones, against a base rate of 21.1%. A two-proportion test gives z = −1.25, p = 0.21, with a 95% interval on the difference running from −16.7 to +4.2 percentage points. And because the gate is a single national switch shared by every city on a given day, the effective sample is days, not city-days: those 80 shut city-days come from four days. So: we have no evidence the shipped gate sorts rain from dry, and no evidence it sorts the wrong way either. The point estimate is inverted; four days cannot tell you whether that means anything.
The constructive half: the poller already computes all sixteen city totals on every run and then discards fifteen of them. And the quantity the market is literally priced on — precipitation_probability_max — comes back in the same request; I added it to the live URL from prod and it returned HTTP 200 alongside the numbers we already fetch. The gap between this signal and a useful one is storage, not a vendor.
3 · An inch is a city-sized bar. It is pointed at a national “any of sixteen”.
For a single city, more than an inch of rain in three days is a real event: it happens on 7.7% of windows on average across our sixteen metros. But the gate does not ask about a city. It asks whether any of sixteen clears the bar, and across 2,046 windows that is true 66.4% of the time. If the sixteen were statistically independent it would be 73.1% — the observed figure sits just below that, so the cities are close to independent. The high duty cycle is the union of sixteen chances, not sixteen cities moving together, and not a wet country.
The shape of the open windows confirms it. On 49.9% of them, exactly one of the sixteen cities is over an inch. The median is two. Three or more happens on 22.5%. Half the time, “the heavy-rain regime” is one thunderstorm in one city somewhere in America.
Two things that stop this being a defence of the number 1. First, that 7.7% is an average over climates that are not comparable: an inch in three days is a 21.6% event in Miami and a 0.3% event in Phoenix, so a single national inch was never going to mean the same thing twice. Second, as actually applied, 1.00 inch sits at the 33rd percentile of the very series it thresholds, whose median is 1.51 — it is a bar below the middle of the distribution. If you want a duty cycle that matches the words “heavy rain”, the same series says peak > 3 is open 20.8% of the time and peak > 4 11.8%.
There is an official yardstick for exactly this, and we used half of it. NOAA's Climate Prediction Center defines heavy precipitation, in its own published rules, as “a 3-day total precipitation amount exceeding the 85th percentile and greater than 1 inch for the location and time of year.” The inch is a floor attached to a local percentile, on an 8–14 day hazards outlook. We kept the floor, dropped “locally rare”, and applied what was left to a national maximum at nought-to-three days. Same words, opposite estimator.
And the starter bot's promise — “when the country is dry it simply stands down” — does not hold. On windows when the population-weighted national average is at or below its own median, the gate is still open 37.5% of the time; in the driest quarter of all windows, 15.8%. Though sixteen metros are not the country: the Plains could be drowning and this number would not know.
4 · The gate's own promise comes true just as often when it is shut
The sentence being sold is “is any big city due a real soaking?” That is checkable. Taking the 111 polls whose three-day window can be pinned to local calendar dates without ambiguity, and asking the ERA5 reanalysis whether any of the sixteen metros went on to record more than an inch: 84.7% of the open polls — and 88.5% of the shut ones.
The event the gate exists to detect happens either way. On three-day windows across sixteen cities, somebody nearly always gets an inch. That comparison is the honest one, because both sides are selected identically, and it is the finding I would keep if I could keep only one.
The raw accuracy numbers, and why I am not leading with them. On those same 111 polls, the metro each row names as wettest was forecast to get 2.24 inches on average and ERA5 records 1.19 — a ratio of means of 1.88, a median per-row ratio of 1.63, mean error +1.04 inches, with the forecast higher on 71.2% of rows. When the gate promised more than an inch at that named metro, ERA5 agrees 51.8% of the time. The largest reading we have ever stored is 12.49 inches for New York on 20 July, against 0.84 inches observed — twelve and a half inches on New York in three days would have been a historic flood, and it did not happen.
That gap is not a calibration constant, and three separate effects inflate it. The biggest is selection: we always verify the metro that won the maximum, and even sixteen perfectly unbiased forecasts would show a positive gap at the argmax, purely by construction — I cannot separate that from a genuine wet bias, because the poller discards the other fifteen forecasts and nothing unselected survives to check. Second, ERA5 is a reanalysis on a grid cell, not a rain gauge, and it under-reads exactly the small convective storms that make up most summer rain, so it is a dry reference. Third, the ratio of means is levered by a handful of large busts, and 223 raw rows are really about thirty independent days. Read the 12.49 as an anecdote about what point forecasts of convective rain can do, not as a multiplier.
5 · The number churns all day, because the city underneath it keeps changing
A one-way variance split on our 241 live readings: hour of day explains 0.9% of the variation in peak_precip and calendar day 31.0%. The first number rules out the defect found on ensemble_anchor, where 70% of the signal turned out to be the clock — there is no stable time-of-day pattern here. But it does not mean the number is stable, and the second figure says it isn't: roughly two-thirds of the variation happens within a single day.
In practice the gate changed state at least once on 20 of our 31 days, 46 flips in all, and the within-day range of peak_precip has a median of 3.40 inches — more than twice the median level of the series itself. There is no hysteresis and no smoothing; whichever of the eight daily polls your bot happens to hit decides whether it trades.
And the cause is not one city's forecast bouncing around. The wettest metro changes identity on 30 of our 31 days, averaging 3.65 different cities per day. On the one day it stayed the same city, the within-day range was 0.27 inches; on the days it switched, 3.42. So the churn is the argmax hopping between sixteen different climates — Miami's number replaced by Denver's — which is what taking a maximum over a heterogeneous set does when the members are close together.
6 · Not backtest-replayable — and the archive does not fix it
qpf appears nowhere in quants/backtest.py — not in _SUPPORTED_ENTRY, not even in the documentary _UNSUPPORTED_ENTRY. It is caught only by the catch-all (k not in _SUPPORTED_ENTRY), which is the designed behaviour. The consequence is not: a backtest of a bot using this signal runs without the gate, returns a curve anyway, and flags unsupported_rules. You get a result for a strategy you did not test.
The poller's docstring explains why: “Open-Meteo's free forecast is forward-only (no archive here), so the signal is live-only.” That is wrong in letter — Open-Meteo's historical-forecast archive served precipitation_sum for all sixteen metros back to 1 January 2021 in a single 44-second request, free and keyless, and ERA5 goes back to 1940. But it is closer to right in substance than I first thought, and this is a correction to my own working. That archive is stitched from the earliest hours of successive model runs — it is built to track what happened, not to record what the forecast said. Summing three of its days gives you a near-analysis three-day total, not the 0-to-3-day-ahead forecast the poller actually stores. My own cross-check shows it: over the 26 overlapping days the reconstruction reads 0.61 inches drier than our live rows, exactly the direction you would expect.
So everywhere this page uses the 2,046-window series — the per-city rates, the 66.4%, the monthly duty cycle — it is a climatology of what the weather did, not a replay of what our gate would have said. That is still the right tool for base rates, and it makes the case against the shipped threshold stronger: even in a product that tracks reality more closely than a three-day forecast, somebody clears an inch two days in three. What it is not is a backfill. A true as-of replay needs Open-Meteo's previous-runs endpoint, which returned HTTP 400 for precipitation_sum on the parameter names I tried from prod, so I could not build one and am not going to claim otherwise.
7 · Thirty days of memory — and for once it hides less than you'd expect
Our window says the shipped threshold is open 74.7% of the time. The 5.6-year climatology says 66.4%. Month by month it runs from 52.0% in November to 87.6% in July; year by year, 62.5% to 73.2%. So unlike heat_index_anchor, where thirty summer days made a well-calibrated threshold look permanently stuck, here the short memory is not covering for the setting. Even in the driest month of the year the gate is open more than half the time. It is loose all year round. (One caveat on the winter figures: this field is liquid equivalent and includes melted snow, so a January duty cycle is not a heavy-rain duty cycle.)
What thirty days does hide is who gets the rain. In our window Los Angeles, San Francisco and Seattle have never once been the wettest metro — a fact that reads like a bug and is just July. Across 5.6 years Seattle is the fourth most frequent winner at 8.5%, behind Miami (19.2%), Houston (11.6%) and Atlanta (9.2%), and every one of the sixteen has taken the title at least once, down to Phoenix's 14 days. Any conclusion drawn from the retained window about where rain happens in America is a conclusion about summer.
One more retention note, for the eighth poller running: the prune is fetched_at < datetime('now','-30 days'), which compares an ISO timestamp (2026-07-14T00:29:02+00:00) against SQLite's space-separated one (2026-07-14 05:38:43) as text. Because 'T' > ' ', the cut-off day never prunes. Real retention is 723 hours, not 720. Harmless here, and not fixed — editing a poller is outside what this loop is allowed to touch.
8 · Settings that validate and cannot work
{metric:'mean', below:0} validates cleanly and renders as “only when average 3-day precipitation < 0 in”. Precipitation is never negative and the comparison is strict, so that gate can never open, and nothing warns you — the validator explicitly rejects a negative threshold but happily accepts zero. {above:2, below:0.5} validates and renders as both conditions at once. {above:100} validates.
In fairness this validator is one of the stricter ones in the library: it rejects booleans, negative levels, an unknown metric and a config with no condition at all, each with a readable message. And one genuine footgun is absent here — unlike finance_anchor, the units are unambiguous, because the builder field is labelled “inches” and the stored number is inches.
9 · Two smaller things worth knowing
The instrument is not pinned. The request carries no models= parameter, so Open-Meteo selects the model blend for each point and can change that selection without any change to our code. Strictly, the 241 stored rows are not guaranteed to be one instrument measured consistently — a real obstacle to treating the series as a single time series, and a reason to be careful about the multi-year climatology too.
“Incoming” is the wrong word for part of the window. forecast_days=3 with timezone=auto means the window starts at 00:00 local today. By the 21:29 UTC poll, most of day one has already happened in the eastern cities, so part of what the gate treats as a forecast is the model's reconstruction of rain already on the ground. It is not a large effect here — the three-day sum dilutes it — but it would be a genuine lookahead leak in any replay built on these rows, and it is why the verification in catch #4 uses only the 111 polls whose local window is unambiguous.
The case for the defence
Eight things this signal gets right
- The pipeline is in the healthiest tier of this dictionary. 459 scheduled runs since mid-June are fully accounted for: 458 successes and one failure, a disk I/O error raised inside CREATE TABLE IF NOT EXISTS — before the insert, so nothing was written and nothing corrupted. Zero database is locked failures, against seven other pollers wrecked by that epidemic; the :29 cron slot sits clear of ticker_index's 1,11,21,31,41,51. The vendor call has never once failed: ok: False has never been logged.
- The freshness check is written correctly — a real timezone-aware ISO parse into a Python datetime, failing closed on anything unparseable, rather than the string comparison that silently defeats the same check in finance_anchor and tropical_anchor. It has never had to fire: the largest gap between rows is 21,600 seconds — exactly 360.00 minutes against a 360-minute threshold, passing by zero seconds because the comparison is strict.
- It really does refresh. 7.71 distinct readings per day out of eight polls, and only one consecutive-identical pair in 241 rows (0.4%) — against ensemble_anchor's five distinct forecasts per eight polls and 37% exact repeats.
- It records what it chose. wettest_metro and n_metros are on every row, 16 of 16 every time. That single column is the only reason catches 4 and 5 could be measured at all — without it, nothing on this page about what actually fell would have been checkable.
- The premise is real. Precipitation is a genuinely independent dimension from temperature, demand, wind and air quality, and there is a liquid market on it. The problem is the shape of the number, not the idea of watching rain.
- The expensive part is already built. Sixteen metro totals, one batched free call, every three hours, from a vendor that has not failed us. What stands between this and something that scores 0.77 against a live market is where the numbers are written down.
- It never invents a value. compute() returns None rather than a guess on any failure, and _MIN_METROS = 8 refuses to publish a national aggregate from a partial response.
- Nobody was hurt. No bot has ever used this signal, so none of the above has cost anyone anything.
The real record
There isn't one, and this page is not going to manufacture one. 0 of 76 strategy configurations (21 live, 15 out-of-sample, 36 archived, 4 draft) and 0 of 88 strategy versions mention qpf_anchor — none live, none out-of-sample, none archived, none ever. No trades, no fills, no evaluations, no P&L. Both files have been unmodified since 16 June 2026, 58 days.
For context, that is not unusual company: all seven of our three-hourly weather signals have zero bots. The only weather signal anything trades is wx_anchor, in 10 configurations — and it reads the air temperature.
What this page does not tell you
Whether any of this would have made money. AUC measures whether a number separates rainy days from dry ones. It says nothing about whether that separation beats the price — and Kalshi's rain contracts trade across the full range from a cent to ninety-nine, which is what a market that already knows the forecast looks like. Nobody here has run that test, and a 0.77 is not an edge until somebody does.
Whether the signal is any good at the markets it was actually sold for. The copy names “rainfall-total and flood markets”. I tested it against a daily binary rain market, because that is the one that exists and settles. A national three-day maximum was never likely to predict whether Phoenix got a trace today, and failing that test is not proof it would fail as a filter on monthly rainfall totals or flood contracts. That remains untested in both directions.
Whether mean_precip is any better. Everything measured here indicts peak. The population-weighted average is a different variable with a median of 0.23 inches, and a rare bar on it might be a genuine large-scale wet-pattern filter. Nobody has tested it, and this page is not going to guess.
Whether Open-Meteo's grid point agrees with the station Kalshi settles on. Displacement — the model putting a storm twenty kilometres from where it lands — is the classic failure mode for point-forecast rainfall, and our coordinates are city centres while the contracts settle on CLI station records via The Weather Company. I did not measure that gap.
Sources for every figure on this page: quants/qpf_anchor.py and predictive/qpf_poller.py on production; the qpf_forecast table in predictive.db (241 rows, 14 Jul – 13 Aug 2026); quants.db for the bot counts; Kalshi's public API for the KXRAIN market and settlement figures, read 13 August 2026; Open-Meteo's historical-forecast and ERA5 archive APIs for the 2,046-window climatology and the verification; and NOAA CPC's published outlook rules on weather.gov for the heavy-precipitation definition. Nothing here is estimated or remembered. All TinyCorp bots trade simulated money.