Skip to content

Repository files navigation

weather-edge

Prices Polymarket temperature contracts off a calibrated forecast error model, sizes positions with fractional Kelly, and refuses to quote until sigma has been fitted against real data.

A market like "Will the high temperature in London exceed 25C on Aug 8?" is a binary option on a continuous underlying. The fair value is a tail integral. Everything that decides whether an edge is real lives in two places: which daily statistic settles the contract, and how wide the distribution is at that forecast horizon.

$ weather-edge calibrate --history data/forecast_history_synthetic.csv
samples            1200
mean signed error  -0.10 F
sigma(h) = 2.231 * sqrt(1 + h / 45.8)
fit R^2            0.962

  sigma at   12h  2.51 F
  sigma at   24h  2.75 F
  sigma at  168h  4.82 F

What is actually validated

The engine is covered by 77 tests. Calibration recovers a known error process: fitting against 1,200 samples drawn from sigma(h) = 2.40 * sqrt(1 + h/60) returns 2.23 * sqrt(1 + h/45.8) with an R^2 of 0.962.

On the 500 synthetic markets in data/, the model scores a Brier of 0.161 against a base rate benchmark of 0.249, and reliability holds across the range:

predicted realised n
0.048 0.047 43
0.253 0.309 55
0.547 0.604 53
0.850 0.852 54
0.952 1.000 58

Those numbers come from synthetic data and are not evidence of edge. scripts/make_synthetic_data.py draws outcomes from the same distribution family the model assumes and injects a deliberate mispricing into every quoted price, so the model finds what was planted. The backtest reports a large profit on that file. It means the machinery is wired correctly end to end. It means nothing about real markets.

To find out whether there is real edge, point calibrate at your own Tomorrow.io history and backtest at markets you actually recorded. The tool prints skill against the base rate on every run and says so in plain text when the model fails to beat it.

Quickstart

pip install -r requirements-dev.txt
python scripts/make_synthetic_data.py
PYTHONPATH=src python -m weather_edge.cli backtest \
  --markets data/markets_synthetic.csv --sigma-base 2.2314 --sigma-scale 45.7882

Run the tests with PYTHONPATH=src python -m pytest.

For live pricing, copy .env.example to .env, add a Tomorrow.io key and your fitted sigma, then:

PYTHONPATH=src python -m weather_edge.cli price \
  "Will the high temperature in Seoul exceed 30C?" --date 2026-08-08

How it works

question text ──> markets.parse_market ──> MarketTerms
                                           city, threshold, statistic, side, local tz
                                                    │
Tomorrow.io ──> forecast.summarise ──────────────> Forecast
                (local day boundaries)             high / low / mean, lead time
                                                    │
forecast history ──> calibration.fit_horizon_sigma ──> sigma(h)
                                                    │
                                                    v
                                         pricing.fair_probability
                                                    │
                                    ┌───────────────┴───────────────┐
                                    v                               v
                        sizing.stake_usd                 liquidity.walk_book
                        fractional Kelly                 fill at your limit
                                    └───────────────┬───────────────┘
                                                    v
                                          strategy.evaluate
                                       trade, or a logged reason

Sigma has no default. Settings.from_env raises if it is unset and tells you to run the calibrator. A fixed sigma prices a seven day forecast with the same confidence as a twelve hour one, which is the fastest way to talk yourself into a position that does not exist.

Edge is measured in probability points, not percent of price. Relative edge explodes on cheap contracts: a 1c market quoted at a 2c fair value shows a 100% edge while offering one point of real advantage. Sizing off that number pushes the entire book into longshots.

What this does not do

It does not place orders. Live execution needs a funded Polygon wallet and a Polymarket CLOB client, and untested code that moves real money is worse than no code. The pricing, sizing, and fill simulation are complete and tested; wiring them to an exchange is deliberately left out.

Cities are limited to the nine in markets.CITIES, each with the timezone its local calendar day is defined in.

Fixes over the earlier version

This replaces a bot I wrote on top of polymarket/agents. Rewriting it surfaced seven bugs, each of which now has a regression test:

Bug Effect
Priced daily-high markets off the daily mean Understated P(exceed) on every market touched. An 86F high with a 75F mean prices an 80F threshold at 0.96 or at 0.08 depending on which you use
UTC day boundaries for local settlement days Seoul's day was off by nine hours, mixing two days into one high
Fixed sigma of 3.5F at every horizon A seven day forecast priced as confidently as a twelve hour one
(\d+)\s*°?\s*[Cc] matched years "2024 Championship" parsed as a 2024 degree Celsius threshold
Liquidity summed every ask at any price One share at your limit behind a wall at 40c looked like a deep market
YES only When fair value sat below the market, the trade is to buy NO. The bot skipped it
Sizing was min(edge * 0.1, 0.05) Ad hoc. Replaced with fractional Kelly, f* = (q - p) / (1 - p)

Writing the fill simulator turned up an eighth: the minimum stake was checked against the intended size but never against what actually filled, so a thin book could open a $0.05 position under a $1.00 minimum.

Licence

MIT. See LICENSE.

About

Fair-value pricing and fractional Kelly sizing for Polymarket temperature markets. Sigma is calibrated against realised forecast error, not assumed.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages