<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: myfirstcodeo</title>
    <description>The latest articles on DEV Community by myfirstcodeo (@myfirstcodeo).</description>
    <link>https://dev.to/myfirstcodeo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4139736%2Fe8ef6391-0f52-4070-ba80-98a291f7ab87.png</url>
      <title>DEV Community: myfirstcodeo</title>
      <link>https://dev.to/myfirstcodeo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/myfirstcodeo"/>
    <language>en</language>
    <item>
      <title>I backtested a forecast-based model against Kalshi's temperature markets. The market won. Here's the data.</title>
      <dc:creator>myfirstcodeo</dc:creator>
      <pubDate>Wed, 23 Sep 2026 16:33:39 +0000</pubDate>
      <link>https://dev.to/myfirstcodeo/i-backtested-a-forecast-based-model-against-kalshis-temperature-markets-the-market-won-heres-442c</link>
      <guid>https://dev.to/myfirstcodeo/i-backtested-a-forecast-based-model-against-kalshis-temperature-markets-the-market-won-heres-442c</guid>
      <description>&lt;p&gt;I built a tool that takes the NWS forecast high for a city, wraps it in that city's measured forecast-error distribution, prices every Kalshi daily high-temperature bracket from it, and compares to the Kalshi price at the close of the day before. Then I backtested it on every settled US high-temp market from Jul 25 to Sep 22, 2026: 24 cities, 1,317 city-days, 7,902 markets.&lt;/p&gt;

&lt;p&gt;The short version: &lt;strong&gt;the market beat the model.&lt;/strong&gt; Here are the numbers, and the one thing in the data that was genuinely useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Markets and ground truth:&lt;/strong&gt; Kalshi's public API. Settlement value is Kalshi's own &lt;code&gt;expiration_value&lt;/code&gt;, which is The Weather Company's reading at the station named in each market's rules (for NYC, "New York City (CLINYC)", the Central Park climate station). I cross-checked it against the NWS climate report: it matched on 1,316 of 1,317 city-days. The one miss was Miami on Aug 29, 90 vs 85.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forecast:&lt;/strong&gt; Open-Meteo's previous-runs archive, the hourly &lt;code&gt;temperature_2m_previous_day1&lt;/code&gt; series, max over the local day. I tried GFS, ECMWF, and their average; the blend had the lowest pooled sigma so it won.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market price:&lt;/strong&gt; the close of Kalshi's daily candle for the last full day before the target date. Mid for scoring, ask for the P&amp;amp;L.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; actual high ~ Normal(forecast + city bias, city sigma), bracket probability is the mass inside the bracket with a half-degree continuity correction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Forecast error by city (actual minus day-1 forecast, F)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;city&lt;/th&gt;
&lt;th&gt;n days&lt;/th&gt;
&lt;th&gt;bias&lt;/th&gt;
&lt;th&gt;sigma&lt;/th&gt;
&lt;th&gt;MAE&lt;/th&gt;
&lt;th&gt;skew&lt;/th&gt;
&lt;th&gt;ex. kurt&lt;/th&gt;
&lt;th&gt;within 1sd&lt;/th&gt;
&lt;th&gt;within 2sd&lt;/th&gt;
&lt;th&gt;normal ok?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NYC&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;-1.074&lt;/td&gt;
&lt;td&gt;2.078&lt;/td&gt;
&lt;td&gt;1.861&lt;/td&gt;
&lt;td&gt;-0.35&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;0.983&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MIA&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;3.657&lt;/td&gt;
&lt;td&gt;1.916&lt;/td&gt;
&lt;td&gt;3.667&lt;/td&gt;
&lt;td&gt;0.11&lt;/td&gt;
&lt;td&gt;-1.18&lt;/td&gt;
&lt;td&gt;0.583&lt;/td&gt;
&lt;td&gt;0.983&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CHI&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.26&lt;/td&gt;
&lt;td&gt;2.407&lt;/td&gt;
&lt;td&gt;1.837&lt;/td&gt;
&lt;td&gt;-0.74&lt;/td&gt;
&lt;td&gt;1.57&lt;/td&gt;
&lt;td&gt;0.733&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AUS&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;2.458&lt;/td&gt;
&lt;td&gt;1.455&lt;/td&gt;
&lt;td&gt;2.622&lt;/td&gt;
&lt;td&gt;-1.29&lt;/td&gt;
&lt;td&gt;4.69&lt;/td&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HOU&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;-0.025&lt;/td&gt;
&lt;td&gt;2.267&lt;/td&gt;
&lt;td&gt;1.75&lt;/td&gt;
&lt;td&gt;0.64&lt;/td&gt;
&lt;td&gt;1.44&lt;/td&gt;
&lt;td&gt;0.717&lt;/td&gt;
&lt;td&gt;0.967&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;-0.593&lt;/td&gt;
&lt;td&gt;2.546&lt;/td&gt;
&lt;td&gt;2.113&lt;/td&gt;
&lt;td&gt;-0.02&lt;/td&gt;
&lt;td&gt;-0.49&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAN&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;-3.354&lt;/td&gt;
&lt;td&gt;2.543&lt;/td&gt;
&lt;td&gt;3.669&lt;/td&gt;
&lt;td&gt;0.51&lt;/td&gt;
&lt;td&gt;-0.18&lt;/td&gt;
&lt;td&gt;0.657&lt;/td&gt;
&lt;td&gt;0.971&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NOLA&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;1.159&lt;/td&gt;
&lt;td&gt;2.201&lt;/td&gt;
&lt;td&gt;1.926&lt;/td&gt;
&lt;td&gt;-0.64&lt;/td&gt;
&lt;td&gt;2.02&lt;/td&gt;
&lt;td&gt;0.717&lt;/td&gt;
&lt;td&gt;0.967&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MIN&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;-0.116&lt;/td&gt;
&lt;td&gt;2.976&lt;/td&gt;
&lt;td&gt;2.366&lt;/td&gt;
&lt;td&gt;-0.38&lt;/td&gt;
&lt;td&gt;-0.09&lt;/td&gt;
&lt;td&gt;0.617&lt;/td&gt;
&lt;td&gt;0.967&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SATX&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.526&lt;/td&gt;
&lt;td&gt;1.592&lt;/td&gt;
&lt;td&gt;1.204&lt;/td&gt;
&lt;td&gt;-0.39&lt;/td&gt;
&lt;td&gt;2.06&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;0.933&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DEN&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.624&lt;/td&gt;
&lt;td&gt;1.946&lt;/td&gt;
&lt;td&gt;1.641&lt;/td&gt;
&lt;td&gt;-0.52&lt;/td&gt;
&lt;td&gt;0.32&lt;/td&gt;
&lt;td&gt;0.733&lt;/td&gt;
&lt;td&gt;0.917&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LAX&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;-3.961&lt;/td&gt;
&lt;td&gt;3.064&lt;/td&gt;
&lt;td&gt;4.346&lt;/td&gt;
&lt;td&gt;0.19&lt;/td&gt;
&lt;td&gt;-0.15&lt;/td&gt;
&lt;td&gt;0.717&lt;/td&gt;
&lt;td&gt;0.917&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BOS&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.309&lt;/td&gt;
&lt;td&gt;2.446&lt;/td&gt;
&lt;td&gt;1.901&lt;/td&gt;
&lt;td&gt;-0.71&lt;/td&gt;
&lt;td&gt;0.79&lt;/td&gt;
&lt;td&gt;0.683&lt;/td&gt;
&lt;td&gt;0.983&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ATL&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.326&lt;/td&gt;
&lt;td&gt;2.128&lt;/td&gt;
&lt;td&gt;1.647&lt;/td&gt;
&lt;td&gt;0.4&lt;/td&gt;
&lt;td&gt;-0.5&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;0.967&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHL&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;1.901&lt;/td&gt;
&lt;td&gt;1.884&lt;/td&gt;
&lt;td&gt;2.224&lt;/td&gt;
&lt;td&gt;-0.08&lt;/td&gt;
&lt;td&gt;-0.56&lt;/td&gt;
&lt;td&gt;0.6&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OKC&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;1.263&lt;/td&gt;
&lt;td&gt;2.603&lt;/td&gt;
&lt;td&gt;2.26&lt;/td&gt;
&lt;td&gt;-0.39&lt;/td&gt;
&lt;td&gt;2.47&lt;/td&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;td&gt;0.933&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHX&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.972&lt;/td&gt;
&lt;td&gt;1.832&lt;/td&gt;
&lt;td&gt;1.608&lt;/td&gt;
&lt;td&gt;0.33&lt;/td&gt;
&lt;td&gt;0.46&lt;/td&gt;
&lt;td&gt;0.717&lt;/td&gt;
&lt;td&gt;0.933&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LAS&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.616&lt;/td&gt;
&lt;td&gt;2.232&lt;/td&gt;
&lt;td&gt;1.587&lt;/td&gt;
&lt;td&gt;-1.4&lt;/td&gt;
&lt;td&gt;6.02&lt;/td&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;td&gt;0.933&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTN&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;0.546&lt;/td&gt;
&lt;td&gt;1.651&lt;/td&gt;
&lt;td&gt;1.425&lt;/td&gt;
&lt;td&gt;-0.72&lt;/td&gt;
&lt;td&gt;-0.18&lt;/td&gt;
&lt;td&gt;0.679&lt;/td&gt;
&lt;td&gt;0.964&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EWR&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;0.873&lt;/td&gt;
&lt;td&gt;2.236&lt;/td&gt;
&lt;td&gt;1.87&lt;/td&gt;
&lt;td&gt;0.17&lt;/td&gt;
&lt;td&gt;-0.53&lt;/td&gt;
&lt;td&gt;0.643&lt;/td&gt;
&lt;td&gt;0.929&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SFO&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;-1.095&lt;/td&gt;
&lt;td&gt;3.434&lt;/td&gt;
&lt;td&gt;2.527&lt;/td&gt;
&lt;td&gt;-1.42&lt;/td&gt;
&lt;td&gt;2.8&lt;/td&gt;
&lt;td&gt;0.767&lt;/td&gt;
&lt;td&gt;0.967&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEA&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.809&lt;/td&gt;
&lt;td&gt;2.151&lt;/td&gt;
&lt;td&gt;1.912&lt;/td&gt;
&lt;td&gt;-0.76&lt;/td&gt;
&lt;td&gt;0.76&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DFW&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.45&lt;/td&gt;
&lt;td&gt;1.926&lt;/td&gt;
&lt;td&gt;1.527&lt;/td&gt;
&lt;td&gt;-1.49&lt;/td&gt;
&lt;td&gt;4.11&lt;/td&gt;
&lt;td&gt;0.733&lt;/td&gt;
&lt;td&gt;0.967&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDF&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;-0.285&lt;/td&gt;
&lt;td&gt;2.465&lt;/td&gt;
&lt;td&gt;1.573&lt;/td&gt;
&lt;td&gt;-0.76&lt;/td&gt;
&lt;td&gt;3.81&lt;/td&gt;
&lt;td&gt;0.846&lt;/td&gt;
&lt;td&gt;0.923&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pooled&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1317&lt;/td&gt;
&lt;td&gt;0.321&lt;/td&gt;
&lt;td&gt;2.755&lt;/td&gt;
&lt;td&gt;2.136&lt;/td&gt;
&lt;td&gt;-0.72&lt;/td&gt;
&lt;td&gt;1.57&lt;/td&gt;
&lt;td&gt;0.733&lt;/td&gt;
&lt;td&gt;0.948&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pooled: bias +0.3F, sigma 2.76F, MAE 2.1F. The station biases are the interesting part. Miami runs 3.7F warmer than forecast; LAX runs 4.0F colder. Those are large, stable, and exactly the kind of thing a normal-distribution model should be able to exploit if the market weren't already pricing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Out of sample: fit on the first 30 days, test on the last 30
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Brier (lower is better)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model (quoted markets)&lt;/td&gt;
&lt;td&gt;0.1466&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Market mid (quoted markets)&lt;/td&gt;
&lt;td&gt;0.1195&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base rate (constant)&lt;/td&gt;
&lt;td&gt;0.1541&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model (all markets incl. unquoted)&lt;/td&gt;
&lt;td&gt;0.1285&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The market wins by 0.027 Brier. It won in every city and in every price band.&lt;/p&gt;

&lt;p&gt;Calibration in 10% bins:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bin&lt;/th&gt;
&lt;th&gt;model n&lt;/th&gt;
&lt;th&gt;model mean p&lt;/th&gt;
&lt;th&gt;observed&lt;/th&gt;
&lt;th&gt;market n&lt;/th&gt;
&lt;th&gt;market mean p&lt;/th&gt;
&lt;th&gt;observed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.0-0.1&lt;/td&gt;
&lt;td&gt;1240&lt;/td&gt;
&lt;td&gt;0.034&lt;/td&gt;
&lt;td&gt;0.079&lt;/td&gt;
&lt;td&gt;1407&lt;/td&gt;
&lt;td&gt;0.042&lt;/td&gt;
&lt;td&gt;0.026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.1-0.2&lt;/td&gt;
&lt;td&gt;614&lt;/td&gt;
&lt;td&gt;0.147&lt;/td&gt;
&lt;td&gt;0.179&lt;/td&gt;
&lt;td&gt;498&lt;/td&gt;
&lt;td&gt;0.143&lt;/td&gt;
&lt;td&gt;0.131&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.2-0.3&lt;/td&gt;
&lt;td&gt;681&lt;/td&gt;
&lt;td&gt;0.249&lt;/td&gt;
&lt;td&gt;0.266&lt;/td&gt;
&lt;td&gt;411&lt;/td&gt;
&lt;td&gt;0.244&lt;/td&gt;
&lt;td&gt;0.238&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.3-0.4&lt;/td&gt;
&lt;td&gt;491&lt;/td&gt;
&lt;td&gt;0.345&lt;/td&gt;
&lt;td&gt;0.263&lt;/td&gt;
&lt;td&gt;347&lt;/td&gt;
&lt;td&gt;0.348&lt;/td&gt;
&lt;td&gt;0.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.4-0.5&lt;/td&gt;
&lt;td&gt;142&lt;/td&gt;
&lt;td&gt;0.444&lt;/td&gt;
&lt;td&gt;0.423&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;td&gt;0.447&lt;/td&gt;
&lt;td&gt;0.427&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5-0.6&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;0.535&lt;/td&gt;
&lt;td&gt;0.358&lt;/td&gt;
&lt;td&gt;162&lt;/td&gt;
&lt;td&gt;0.545&lt;/td&gt;
&lt;td&gt;0.556&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.6-0.7&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;0.641&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;0.634&lt;/td&gt;
&lt;td&gt;0.683&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.7-0.8&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0.734&lt;/td&gt;
&lt;td&gt;0.556&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0.736&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.8-0.9&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;0.84&lt;/td&gt;
&lt;td&gt;0.533&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0.847&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.9-1.0&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;0.945&lt;/td&gt;
&lt;td&gt;0.571&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Where the model said 50 to 70 percent and disagreed with the market, the event happened 25 to 36 percent of the time. The model's lowest bin (0 to 10 percent) resolved yes 7.9 percent of the time against the market's 2.6 percent: fat tails that a normal distribution under-prices.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens if you trade it
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;strategy&lt;/th&gt;
&lt;th&gt;trades&lt;/th&gt;
&lt;th&gt;wins&lt;/th&gt;
&lt;th&gt;staked $&lt;/th&gt;
&lt;th&gt;fees $&lt;/th&gt;
&lt;th&gt;P&amp;amp;L $&lt;/th&gt;
&lt;th&gt;ROI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Buy YES (as specified)&lt;/td&gt;
&lt;td&gt;689&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;6850.81&lt;/td&gt;
&lt;td&gt;420.8&lt;/td&gt;
&lt;td&gt;-2583.61&lt;/td&gt;
&lt;td&gt;-0.3771&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buy NO (mirror: model NO prob &amp;gt;= NO ask + 0.08)&lt;/td&gt;
&lt;td&gt;787&lt;/td&gt;
&lt;td&gt;454&lt;/td&gt;
&lt;td&gt;7630.54&lt;/td&gt;
&lt;td&gt;215.34&lt;/td&gt;
&lt;td&gt;-550.88&lt;/td&gt;
&lt;td&gt;-0.0722&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every price band loses. The worst are the sub-10-cent tails, which is where an 8-cent "edge" shows up most often and where the normal assumption is most wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I take from this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A public forecast plus a normal error model does not beat the day-before Kalshi price. The people trading these markets are already doing at least that, with fresher model runs than a previous-day archive.&lt;/li&gt;
&lt;li&gt;The per-station bias table is real information. If you trade weather markets and are not adjusting for the station, that is the edge you are leaving, and it is the one thing the tool computes fresh each day.&lt;/li&gt;
&lt;li&gt;Honest caveats: 60 summer days only; the backtest forecast is archived GFS/ECMWF while the live tool uses NWS; the market close has slightly fresher information than the archived run, so the benchmark is a little favored.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;Free, MIT, standard-library Python, no API keys: &lt;a href="https://github.com/myfirstcodeo/kalshi-weather-fair-value" rel="noopener noreferrer"&gt;https://github.com/myfirstcodeo/kalshi-weather-fair-value&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;python weather_edge.py&lt;/code&gt; scans all 24 open series against the live NWS forecast and prints model probability, market price, edge, and a quarter-Kelly size per bracket. &lt;code&gt;python backtest.py&lt;/code&gt; reproduces every number above from public data. &lt;code&gt;python test_smoke.py&lt;/code&gt; checks the pricing math offline.&lt;/p&gt;

&lt;p&gt;The cross-venue tool that does pay for itself is the Kalshi vs Polymarket divergence scanner, a one-time $29 purchase: &lt;a href="https://instaverb.gumroad.com/l/pm-scanner" rel="noopener noreferrer"&gt;https://instaverb.gumroad.com/l/pm-scanner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not financial advice. Read the market rules before you trade anything.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>showdev</category>
      <category>statistics</category>
    </item>
    <item>
      <title>Matching Kalshi and Polymarket contracts is harder than it looks (and how I made it mostly work)</title>
      <dc:creator>myfirstcodeo</dc:creator>
      <pubDate>Wed, 23 Sep 2026 15:18:55 +0000</pubDate>
      <link>https://dev.to/myfirstcodeo/matching-kalshi-and-polymarket-contracts-is-harder-than-it-looks-and-how-i-made-it-mostly-work-4m4n</link>
      <guid>https://dev.to/myfirstcodeo/matching-kalshi-and-polymarket-contracts-is-harder-than-it-looks-and-how-i-made-it-mostly-work-4m4n</guid>
      <description>&lt;p&gt;Every cross-venue "arb scanner" for prediction markets does three things: fetch both venues, decide which contracts are the same question, subtract. The first and third are trivial. The second one ate my afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data
&lt;/h2&gt;

&lt;p&gt;Both venues expose keyless market data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kalshi: &lt;code&gt;GET /trade-api/v2/events?status=open&amp;amp;with_nested_markets=true&lt;/code&gt;, cursor-paged, 200 events per page. Two gotchas. Nested markets report &lt;code&gt;status: "active"&lt;/code&gt; rather than &lt;code&gt;"open"&lt;/code&gt;, and they report &lt;code&gt;liquidity_dollars: "0.0000"&lt;/code&gt; across the board, so you gate on &lt;code&gt;volume_fp&lt;/code&gt; and a two-sided book instead. Also skip everything with an event ticker starting &lt;code&gt;KXMVE&lt;/code&gt;: those are multivariate parlay shards, and there are tens of thousands of them.&lt;/li&gt;
&lt;li&gt;Polymarket: &lt;code&gt;GET https://gamma-api.polymarket.com/markets?active=true&amp;amp;closed=false&amp;amp;order=volume24hr&amp;amp;ascending=false&lt;/code&gt;, 100 per page. Offsets past a couple thousand return HTTP 422, so sort by volume and accept that you get the liquid top of the book. That is where the arbs are anyway.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After filtering: about 28,000 Kalshi contracts with real volume, about 1,600 Polymarket contracts with real liquidity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naive matching fails in three specific ways
&lt;/h2&gt;

&lt;p&gt;I started with token Jaccard plus a difflib sequence ratio. The top "opportunities" were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"Highest temperature in Miami: 92° to 93°"&lt;/strong&gt; vs &lt;strong&gt;"between 90-91°F"&lt;/strong&gt;. Same city, same day, adjacent bracket. A 70-cent "gap."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Closest Senate race in 2026: Maine"&lt;/strong&gt; vs &lt;strong&gt;"Will the Democrats win the Maine Senate race?"&lt;/strong&gt; Shares four tokens, different question entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"When will Xi Jinping visit Taiwan? Before 2027"&lt;/strong&gt; vs &lt;strong&gt;"Will Xi Jinping visit US before 2027?"&lt;/strong&gt; The naive tokenizer dropped "us" as a two-letter word, leaving the two questions nearly identical.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each one is a class, not a one-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three vetoes fixed most of it
&lt;/h2&gt;

&lt;p&gt;Before any scoring, reject the pair if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Numbers differ.&lt;/strong&gt; Extract every number from both raw strings. If either side has any, the sets must be equal. Kills the bracket problem and most date confusion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pivot word is on one side only.&lt;/strong&gt; A short list: closest, margin, first, neither, before, after, not, win, lose, Democrats, Republicans, above, below, and so on. If the symmetric difference of the token sets hits the list, it is a different question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Places conflict.&lt;/strong&gt; A list of countries, states, and cities. If both sides mention a place and they differ, reject. This needed a "keep short tokens" exception for &lt;code&gt;us&lt;/code&gt;, &lt;code&gt;uk&lt;/code&gt;, &lt;code&gt;eu&lt;/code&gt;, &lt;code&gt;xi&lt;/code&gt;, &lt;code&gt;10y&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then require containment (shared / smaller set) of at least 0.6, and score 0.4 Jaccard + 0.3 containment + 0.3 sequence ratio. One-to-one assignment, greedy by score.&lt;/p&gt;

&lt;h2&gt;
  
  
  What survived
&lt;/h2&gt;

&lt;p&gt;On a mid-September afternoon: 21 pairs above a 3-cent gap and $500 of liquidity. The clean ones were things like Senate "Clarity Act" vote markets per senator (3 to 5 cent gaps), a Trump-speech word market (75c vs 71c), a Warsh press-conference word market. And one liquid pair, whether the US reopens its embassy in Iran before 2027, priced 3c on Kalshi and 16c on Polymarket with $223k behind it.&lt;/p&gt;

&lt;p&gt;Also one 80-cent gap on "who leads Venezuela at end of 2026: Maduro". Both books tight, both liquid. That is not free money; that is two venues resolving on different rules, and the scanner's most useful output was flagging it as "VERIFY RULES" instead of ranking it first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance note
&lt;/h2&gt;

&lt;p&gt;The first version segfaulted. Candidate generation was a token inverted index, and tokens like "win" fanned out to tens of thousands of Kalshi markets. Capping index tokens at 5% document frequency fixed it and the whole match runs in seconds.&lt;/p&gt;

&lt;p&gt;The lite version of the scanner is free on GitHub: &lt;a href="https://github.com/myfirstcodeo/pm-divergence-scanner-lite" rel="noopener noreferrer"&gt;https://github.com/myfirstcodeo/pm-divergence-scanner-lite&lt;/a&gt;. The full one with watch mode and Telegram alerts is a one-time purchase at &lt;a href="https://instaverb.gumroad.com/l/pm-scanner" rel="noopener noreferrer"&gt;https://instaverb.gumroad.com/l/pm-scanner&lt;/a&gt;. Not financial advice. Read both rule sets.&lt;/p&gt;

</description>
      <category>python</category>
      <category>algorithms</category>
      <category>showdev</category>
      <category>datascience</category>
    </item>
  </channel>
</rss>
