Forecasting events with several language models: calibration and Brier scoring
My own system for forecasting the outcomes of events on prediction markets (Polymarket). Five independent AI agents search open sources on the web, a supervising agent reconciles their answers, and a calibration step adjusts the result. Every forecast is saved to a database before the outcome is known and scored afterwards with the Brier score, a standard measure of accuracy for probability forecasts. This case is about measurement: how the quality bar was set, what it showed on 203 resolved markets, and why trading with real money stayed switched off.
A model's confident answer can't be checked on its own
A language model with web search produces a coherent argument and a number: "probability 0.82". You can't judge that number by eye. A single forecast has no right answer until the event happens, and afterwards one case still says nothing about calibration: a single miss can be bad luck, and a single hit can be luck too. You need a series of recorded forecasts and a measure that penalises both overconfidence and timid 50% answers.
The backstory is also about measurement. Before this system, I ran short-term order-book strategies on the same platforms: reacting to changes in price quotes and copying large accounts. On 12 May 2026 that line of work was closed after a review. After 2.5 weeks of paper trading (simulated, with no real money) across seven strategies, none of them passed validation. Three causes turned out to be shared. Counting by order-book snapshots inflated the win rate: 51.9% per snapshot against 30.4% per market. A signal that passed a historical test with a t-statistic of 4.79 decayed to 1.73 within two weeks, and by 12 May it had stopped firing altogether: zero triggers in 11 hours across 27 thousand scans. One lucky day hid seven losing days out of eight.
Those lessons became the rules for the new system: results are counted per market, after the outcome, on a sample of a size set in advance, and with allowance for any advantage fading over time. The new system also targets a different kind of question: slow events in geopolitics and politics, where the price holds for days, the platform charges no fee in that category, and the model has time to read the resolution rules. The hypothesis: prices go wrong where traders read the headlines and skip the resolution rules.
The forecast is saved before the outcome and scored after it
The process has five steps. The model only does the reasoning. Finding markets, the database, calibration, tracking outcomes and the Brier score are handled in Python without the model. The recipe for combining the agents comes from the AIA Forecaster paper, and from day one everything was recorded so that each step could be recalculated later.
The platform's public API: yes/no markets on geopolitics and politics, 1–45 days to resolution, price between 0.02 and 0.98, a minimum 24-hour volume. A market is re-forecast no more than once every 48 hours
Five independent Claude Sonnet agents, up to five searches each. An agent sees the question, the resolution rules and the time left; its answer ends with a PROBABILITY line
A Claude Opus agent reads all five lines of reasoning, searches for the disputed facts and returns one agreed probability
The agents' average, Platt extremization with α = 1.73, a weighted blend with the market price (weight 0.33), a 0.03 threshold on the gap. DuckDB stores the raw, calibrated and blended probabilities, the price, how far the agents agreed, and the reasoning
A daily timer checks closed markets by the explicit winner flag, records the system's and the market's Brier scores and a paper result at the ask price with a 0.015 half-spread, and posts a summary to a messenger
The quality bar was set before the first forecast
The Brier score is the squared difference between the stated probability and the outcome (1 or 0). Zero is a perfect forecast, always saying 50% scores exactly 0.25, and a confident miss costs close to 1. The same number is calculated for the market price at the time of the forecast, which gives a natural benchmark: the market is a forecaster too. The bar was written into the report code before the first forecast was saved:
- at least 30 resolved markets, and the system's Brier score lower than the market's
- until both conditions are met, trading with real money is forbidden, and the system runs in paper mode
- the paper result is calculated at the ask price with a 0.015 half-spread, so the paper run can't flatter itself
- the geopolitics category was chosen partly because the platform charges no fee on it, so the spread is the only trading cost in the model
Two ways to make a forecast: through the API, or with the model as an operator
The full Python pipeline calls the model through the API: five agents in parallel, then the supervisor. The server had no API key, so the second way became the working one. A Claude Code session with web search does the research, following a recipe written up as a Claude Code skill. A recording script then takes its raw probability, fetches the live market price itself, applies the calibration, calculates the gap and saves the row. A daily batch looks like this: a queue of 13–14 markets, 8–14 Claude Sonnet sub-agents run through a workflow, then the operator's review before anything is saved.
The split of roles is deliberate. The model does the reasoning and the search. Python handles everything that has to survive a change of model or prompt: finding markets, the database, calibration, tracking outcomes and the report.
The outcome counter stayed silent for seven weeks
The first real failure happened in the most important place. The platform's public API stopped returning closed markets at the address the system used to check outcomes: the response came back empty, with no error. The outcomes table stayed empty from late May until 16 July, the report honestly printed "no resolved forecasts yet", and it all looked like slow markets. It's the same kind of failure as in the case on reliability of background services: the process is alive, data stops arriving, and nobody complains.
The fix: outcome checks moved to the exchange's core API, where each market is looked up by its condition ID and every outcome has an explicit winner flag. The lookup by market address stayed as a backup. The very first run recorded 4 outcomes: the system's Brier score was 0.499 against the market's 0.235. The market led from the first row, and from that day the measurement ran daily.
Every row keeps what's needed to recalculate it
Each forecast stores the agents' raw probability, the calibrated one, the blend with the price, the price itself, the number of agents, how far they agreed, the direction of the signal, the reasoning and the model version. That later decided the project's course: when the question came up whether Platt extremization helps, the answer was calculated from the rows already saved, with no re-runs. The rules for keeping the queue clean came from incidents:
- a market enters the queue only if the market search saw it within the last 48 hours: May markets with a frozen "1.6 days to resolve" were still in the queue in July
- the gap is calculated only against the live price: a price stored in the queue was once ten times off the real one (0.04 against 0.389)
- the search window was cut from 120 to 45 days: short-dated markets produce resolved pairs faster
The hard part is the measurement
Clients for the platform's public APIs, which need no login: market search, live prices, outcome checks
Three tables: markets, forecasts, outcomes. Raw and calibrated probabilities sit side by side, so any step can be recalculated later
Five independent agents with web search on the provider's side, plus a supervisor that settles their disagreements
The operator path: 8–14 sub-agents per batch, a sceptic pass and an arbiter pass, saving through a script
Probabilities stretched toward the edges (log-odds × 1.73) and blended with the price (weight 0.33), as in the paper; both steps measured separately
Outcome checks and the report once a day, with no model involved. The API forecast batch has its own timer ready (08:45 UTC, up to 12 markets per run) and starts with one command once there is a key on the server
Markets, forecasts and outcomes counted, the Brier score against the market, the forecast queue: a daily summary in Discord
Losses broken down by price range, and base rates for families of similar questions
The system never passed the bar, and the numbers show why
first check on 31 July, the thirteenth on 5 September 2026; over 600 forecasts saved
the bar wasn't reached at any of the 13 checks; the first showed 0.119 against 0.080
164 of 203; the overall gap comes from rare confident misses and from overrating unlikely events
Where exactly the accuracy is lost
The pattern holds from one check to the next: the system beats the price on most markets and loses on the total. At n = 66, four misses made up 84% of the whole gap (3.646 of 4.322); without them the score was 0.068 against 0.057 on 62 pairs, close to even. Three of the four were confident disagreements with the price by more than 75 points, and in all four the market was right. Two misses came from the same question template about warships passing through a strait: raw 0.98 and 0.82 against prices of 0.010 and 0.037, and both resolved "no". Another mixed up one conflict with a different one: 0.03 against 0.895, resolved "yes". A breakdown by price range at n = 203 showed where the losses are concentrated:
- price below 0.10: n = 132, the system's Brier 0.042 against 0.022, with an actual rate of 2.3%: the system overrated unlikely events
- the middle, 0.25–0.75: n = 20, 0.297 against 0.220: the one range where disagreeing with the price clearly costs accuracy
- price above 0.90: n = 21, 0.048 against 0.047: level with the market, nothing to fix
The paper's correction made the score worse
Platt extremization pushes probabilities toward the edges: the log-odds are multiplied by 1.73. In the paper it improved the Brier score because models tend to cluster around 50%. On this series it worked the other way. The same rows show it, since the raw probability is stored separately: at n = 53 the raw Brier score was 0.108, with the correction 0.115, and the market's 0.048. At the extremes the correction flips the direction of the gap. A raw 0.13 at a price of 0.07 means "the forecast is above the market". After stretching it becomes 0.036, a signal in the opposite direction. In one batch of 13 markets the direction flipped on 9.
The practical result: the signal field in the database can't be trusted for very cheap and very expensive markets, and the gap is read only as "raw probability minus price". The general lesson for any AI evaluation: check a coefficient from a paper on your own outcomes, and store the value before correction.
What changed after the checks
Every change to the recipe was dated and then measured at the next checks, on the same table:
- The market price is hidden from the agent. Despite an explicit ban in the prompt, all five forecasts on one series of dates landed within 1.5 points of the price, and the reasoning said so openly. Since 23 August an agent gets only the question, the rules and the time left
- One family of markets, one agent. Independent agents gave logically impossible pairs on series of dates: 0.25 for "by 31 August" and 0.15 for "by 15 September". A series is now built from a single survival model, and an arbiter checks that the probability never falls for a later date
- A sceptic pass and an arbiter pass. When the gap from the price reaches 15 points, a second agent tries to disprove the key facts: on 29 August it rewrote 5 of 14 forecasts. From 20 points, a third agent plays "the person who will resolve this market" and goes through the rules clause by clause: on 1 September it pulled 3 of 5 extreme forecasts back toward the middle (0.94 → 0.33; 0.93 → 0.32; 0.78 → 0.55) and confirmed two
- Base rates by question family, from the outcomes table. "Ceasefire holds until a date": 14 of 14 "yes" against the system's average of 0.847. "Town captured by a date": 0 of 4 against the system's 0.268 and a price of 0.095. "Blockade lifted by a date": 10 of 10 "no", with the system at 0.011 against a price of 0.101, and a Brier score of 0.0003 against 0.0161. That is the one family where the system is clearly more accurate than the market
The effect shows in the same price ranges. The cheap end stopped getting worse: from n = 183 to n = 203 it gained 12 outcomes, and its total gap moved from 2.605 to 2.579; the overrating of unlikely events fell from 2.3× to 1.7× the actual rate. The 0.25–0.75 middle is still losing: two new outcomes added 0.371 to the gap. That's where the recording rules now point: when the market price is in the middle, the forecast is recorded more cautiously than the agents' conclusion.
A related experiment: weather markets and numerical forecasts
In parallel, the same discipline was applied to daily temperature markets, where the probability came from a set of numerical weather forecasts (30–50 runs). The first lesson there: a win rate on its own says little. A bot that won 91.6% of 1,089 markets still lost money: it bought favourites at a high price, so one loss wiped out many wins. The most expensive range showed it clearly: at an average price of 0.995 you need to win 99.5% to break even, and the actual 97.54% over 285 markets fell short. The second lesson: the bot picked favourites by price and never looked at the forecast. The forecast filter was connected on 31 May, first in a watch-only mode where it just logged its decision. Minimum-temperature markets were dropped after a 61% win rate, and the platform's fee, once discovered, was added to the paper accounting. On 28 July the experiment was closed by its own A/B test: the tested versions lost money on average.
In short, the system never earned the right to trade real money, and the outcomes table says the same thing at all 13 checks. The value is in the measurement loop: every forecast carries a date, a raw and a calibrated probability, a price and an outcome, so any argument about quality is settled with a database query.
Any AI that answers with a number or a decision needs the same check
The underlying problem is common: the model answers confidently, and the only way to check it is against outcomes collected over time. That's the core of my AI Evals service: record the answer before the result is known, compare it with a reference and find the range where the model goes wrong. The same process carries over almost unchanged:
- → AI chats and document search where a second model grades the answers: its grades are checked against human grades with the same measure used here to check forecasts against outcomes
- → Scoring and risk assessment, where the model gives a probability: a breakdown by range shows where it overrates itself
- → Sorting documents and support tickets, where a confident miss on a rare category is especially costly
- → Forecasting demand and lead times: base rates from your own history are the first thing to check a model against
- → Agents with web search that build a convincing story from the latest news: they need a sceptic pass and a clause-by-clause check of the rules before the answer goes any further
- → Any method from a paper (calibration coefficients, weights, thresholds) should be recalculated on your own outcomes before you rely on it
- The storage layout: the answer is saved before the outcome, values before and after correction sit side by side, and any step can be recalculated later
- A quality bar set before the first run: sample size, measure, benchmark, and no next step until the bar is met
- Breakdowns by range: the score split by answer range, plus a query for base rates by type of question
- The sceptic and arbiter passes for answers far from the reference: trying to disprove the key facts and going through the rules clause by clause
- Protection against anchoring: the model never sees the reference answer during research, and a separate agent checks that related answers are consistent
- Outcome tracking as a separate process with a check for silent failures: it notices when the source returns an empty response although outcomes exist
If a model answers confidently, the question is whether anyone records the outcomes
Start with two things: a table where the answer is saved before the result is known, and one measure that penalises a confident miss more heavily than a cautious one. The first 30–50 pairs usually show the range where the model overrates itself, and from there you tune it by the numbers.
Related cases
Language models analyze trading signals right on a laptop: Ollama and RAG on an M3
qwen2.5:14b is the main model, llama3.1:8b the backup, and LLaVA:7b reads chart screenshots; ChromaDB finds…
A sales-figures bot: six metrics, with the arithmetic checked by AI agents
A Telegram bot reports sales by country, year, product and US state through one shared metrics core. All the…
An AI assistant for company knowledge in Telegram
The bot answers from work chats, email, voice notes and documents, and quotes its sources in every answer…
Start with the 5,000 ₽ audit: a written report and a payback estimate
I'll tell you what to automate first, when it will pay for itself and whether your task needs AI at all. Sometimes it doesn't.
Or just ask a question. During business hours I reply within 2 hours