<?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: Archit Pandya</title>
    <description>The latest articles on DEV Community by Archit Pandya (@architpandya).</description>
    <link>https://dev.to/architpandya</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%2F4195065%2F01b9160a-a681-40d7-8d2a-22028402134a.jpg</url>
      <title>DEV Community: Archit Pandya</title>
      <link>https://dev.to/architpandya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/architpandya"/>
    <language>en</language>
    <item>
      <title>I built a Cricket World Cup 2027 predictor on a home NAS, and backtested it honestly</title>
      <dc:creator>Archit Pandya</dc:creator>
      <pubDate>Tue, 20 Oct 2026 00:20:16 +0000</pubDate>
      <link>https://dev.to/architpandya/i-built-a-cricket-world-cup-2027-predictor-on-a-home-nas-and-backtested-it-honestly-38l0</link>
      <guid>https://dev.to/architpandya/i-built-a-cricket-world-cup-2027-predictor-on-a-home-nas-and-backtested-it-honestly-38l0</guid>
      <description>&lt;p&gt;The ODI Cricket World Cup 2027 starts in South Africa, Zimbabwe and Namibia in October 2027. I wanted to know who is likely to win, and I wanted a project that goes all the way from raw data to a live, public model. So I built &lt;strong&gt;&lt;a href="https://pandyahomelab.com/cricket/" rel="noopener noreferrer"&gt;cricstat&lt;/a&gt;&lt;/strong&gt;, a cricket statistics site with a &lt;strong&gt;&lt;a href="https://pandyahomelab.com/cricket/predictor/" rel="noopener noreferrer"&gt;World Cup 2027 predictor&lt;/a&gt;&lt;/strong&gt;, and it runs on a Synology NAS in my home.&lt;/p&gt;

&lt;p&gt;This post covers the model, how I tested it, and the engineering around it, including the parts that didn't work out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The answer first
&lt;/h2&gt;

&lt;p&gt;As of 14 October 2026, after 50,000 simulated tournaments:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Team&lt;/th&gt;
&lt;th&gt;cricstat Elo&lt;/th&gt;
&lt;th&gt;Chance of winning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;India&lt;/td&gt;
&lt;td&gt;1615&lt;/td&gt;
&lt;td&gt;23.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;South Africa&lt;/td&gt;
&lt;td&gt;1527&lt;/td&gt;
&lt;td&gt;19.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Zealand&lt;/td&gt;
&lt;td&gt;1588&lt;/td&gt;
&lt;td&gt;17.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Australia&lt;/td&gt;
&lt;td&gt;1538&lt;/td&gt;
&lt;td&gt;10.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pakistan&lt;/td&gt;
&lt;td&gt;1521&lt;/td&gt;
&lt;td&gt;8.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;South Africa has only the fourth-highest rating but is second favourite, because it plays most of its matches at home. That's the model doing its job, not a fan's bias: every team gets the same rules and there are no hand adjustments.&lt;/p&gt;

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

&lt;p&gt;Everything comes from &lt;a href="https://cricsheet.org" rel="noopener noreferrer"&gt;Cricsheet&lt;/a&gt;, which publishes ball-by-ball data for about 23,000 matches as open data (ODC-BY). There's no API: you download zip files of JSON. A daily job on the NAS fetches the last 7 days of matches, upserts them by match ID, checks the data and only then swaps in the new database. If a check fails, the old database stays live and I get an email.&lt;/p&gt;

&lt;p&gt;A few things I learned the hard way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Match IDs aren't always numbers.&lt;/strong&gt; 25 of them look like &lt;code&gt;wi_...&lt;/code&gt; (West Indies women's domestic games). Store IDs as text everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every player statistic has to be derived&lt;/strong&gt; from the deliveries. Cricsheet gives you balls, not career averages, so I wrote a "golden figures" test set: known career numbers for real players that the build must reproduce.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some data is deliberately missing.&lt;/strong&gt; Cricsheet withholds Afghanistan's men's matches in protest at how Afghan women's cricket has been treated. Afghanistan is in the 2027 World Cup, so the model uses a small reviewed list of 188 results from public sources for ratings only. The site explains this and links to Cricsheet's reasons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Player names, birth dates and photos come from Wikidata and Wikimedia Commons (CC0 and credited free licences). That gives about 4,800 player pages and 176 team pages that search engines can read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model: Elo, then simulation
&lt;/h2&gt;

&lt;p&gt;The baseline is deliberately simple: an Elo rating for every men's ODI team, built from every match since 2002. After each match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;expected_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r_a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;r_b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;HOME&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# h = +1 home, -1 away, 0 neutral
&lt;/span&gt;&lt;span class="n"&gt;r_a&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;K&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;margin_multiplier&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;expected_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# win 1, tie 0.5, loss 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The settings were chosen by grid search over 2,016 combinations, scored on log loss. Each backtest only used data from before the period it was tested on, so the model never saw the answers. The winners: K = 20, home advantage = 75 rating points, a margin-of-victory multiplier, and 5% regression toward the mean each 1 January.&lt;/p&gt;

&lt;p&gt;Then the tournament itself is played 50,000 times in its published format: Qualifier, groups, Super 7, semi-finals and final. Each run also includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rain:&lt;/strong&gt; each host's measured no-result rate (for example, 7.6% for ODIs in South Africa).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uncertainty:&lt;/strong&gt; a random shift to every team's rating, sized from how much ratings really move over a year. A team rated 1615 today won't be exactly 1615 in a year, and pretending otherwise makes the odds too confident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The simulation is vectorised numpy and takes seconds, even on a 4-core Celeron.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it any good?
&lt;/h2&gt;

&lt;p&gt;This is the part I cared about most. Before publishing, the model had to pass four automatic gates, all on matches it had never seen, plus a final sanity review by me where I wasn't allowed to change any number:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Beat simple baselines on every men's ODI since 2019 (900 matches), each predicted before it was played.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Log loss&lt;/th&gt;
&lt;th&gt;Brier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Coin flip&lt;/td&gt;
&lt;td&gt;0.693&lt;/td&gt;
&lt;td&gt;0.247&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Win rate (last 24 months + home/away)&lt;/td&gt;
&lt;td&gt;0.662&lt;/td&gt;
&lt;td&gt;0.231&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;cricstat Elo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.605&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.205&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Be calibrated:&lt;/strong&gt; when it says 70%, about 70% should happen. The calibration slope is 0.97 (1.0 is perfect). In the 0.7–0.8 bin, the average prediction was 75.0% and the favourites won 75.1% of 181 matches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Do at least as well as the win-rate model on real World Cup matches&lt;/strong&gt;, with ratings frozen the day before each tournament. On the 92 decided matches at the 2019 and 2023 World Cups it picked the winner 76% of the time; the win-rate model managed 62%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Replay check:&lt;/strong&gt; fed the real results of 2019 and 2023, the simulator must produce the real semi-finalists. It does.&lt;/p&gt;

&lt;p&gt;And the result everyone asks about: before the 2019 World Cup, its top pick was &lt;strong&gt;England (36%)&lt;/strong&gt;, who won. Before 2023, it had &lt;strong&gt;India at 42%&lt;/strong&gt; and &lt;strong&gt;Australia at 16%&lt;/strong&gt;. Australia won. Two tournaments prove very little either way, so the site reports them but doesn't use them as a gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it gets wrong
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://pandyahomelab.com/cricket/methodology/" rel="noopener noreferrer"&gt;methodology page&lt;/a&gt; has a full "limits" section, but the main ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It rates teams, not players.&lt;/strong&gt; It knows nothing about squads, injuries, retirements, the toss or the pitch. A team that rests its stars for a series looks weaker than it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's weaker in 2026 so far:&lt;/strong&gt; 56% accuracy on 78 matches, against 69% over the whole test period. Small samples swing, but I'm watching it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Net run rate isn't modelled&lt;/strong&gt;, because Elo doesn't predict margins. Teams level on points are split by wins, then at random. It barely changes the title odds, and the page says so.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That first limit is why the next step is a machine-learning challenger: gradient boosting on Elo difference, home advantage, form and squad strength built from the actual playing XIs. After that comes a deep-learning model trained on about 11.6 million deliveries. Whichever is best calibrated on the same backtests takes over the live forecast, and the comparison gets published either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it on a NAS
&lt;/h2&gt;

&lt;p&gt;The whole thing runs at home on a Synology NAS: Celeron J3455, 16 GB RAM, no GPU. One gotcha: the CPU has &lt;strong&gt;no AVX&lt;/strong&gt;, so some prebuilt Python wheels crash with "illegal instruction" even though they install fine. Every image is now smoke-tested on the NAS itself, not just in CI.&lt;/p&gt;

&lt;p&gt;The deploy pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI (GitHub Actions):&lt;/strong&gt; lint, unit tests, metric-formula tests and schema tests on every push, then images built and pushed to GitHub Container Registry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An approval gate&lt;/strong&gt; before anything is published.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull-based deploy:&lt;/strong&gt; a scheduled job on the NAS pulls the approved image, restarts the service, smoke-tests it and rolls back on failure. GitHub never needs a way into my network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MLflow&lt;/strong&gt; logs every tuning run and backtest. The model registry only moves the "champion" alias when all four automatic gates pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A daily forecast&lt;/strong&gt; runs after the data refresh, so the odds move as teams play.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The site is served through a Cloudflare Tunnel, so there are no open ports at home. It sets no cookies and loads nothing from third parties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Predictor: &lt;a href="https://pandyahomelab.com/cricket/predictor/" rel="noopener noreferrer"&gt;pandyahomelab.com/cricket/predictor/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;How it works, with every number: &lt;a href="https://pandyahomelab.com/cricket/methodology/" rel="noopener noreferrer"&gt;pandyahomelab.com/cricket/methodology/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/avpgithub-code/pandyahomelab" rel="noopener noreferrer"&gt;github.com/avpgithub-code/pandyahomelab&lt;/a&gt; (cricstat is in &lt;code&gt;cricstat/&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd like to hear what you'd add, or where you think the model is wrong. Comment here, or use the feedback button on the site.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Match data from Cricsheet (cricsheet.org), used under the Open Data Commons Attribution License 1.0. Not affiliated with or endorsed by Cricsheet, the ICC or any cricket board. A statistical estimate for fun and learning, not betting advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>datascience</category>
      <category>mlops</category>
    </item>
  </channel>
</rss>
