<?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: Mystique Racing</title>
    <description>The latest articles on DEV Community by Mystique Racing (@mystiqueracing).</description>
    <link>https://dev.to/mystiqueracing</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%2F4159653%2Ffabf2b1b-7648-48ca-acb5-ebd11263d183.png</url>
      <title>DEV Community: Mystique Racing</title>
      <link>https://dev.to/mystiqueracing</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mystiqueracing"/>
    <language>en</language>
    <item>
      <title>I built an immune system for my one-person SaaS (so it survives while I sleep)</title>
      <dc:creator>Mystique Racing</dc:creator>
      <pubDate>Sun, 04 Oct 2026 17:25:53 +0000</pubDate>
      <link>https://dev.to/mystiqueracing/i-built-an-immune-system-for-my-one-person-saas-so-it-survives-while-i-sleep-5g67</link>
      <guid>https://dev.to/mystiqueracing/i-built-an-immune-system-for-my-one-person-saas-so-it-survives-while-i-sleep-5g67</guid>
      <description>&lt;p&gt;I run two production sites as a one-person operation. My execution environment (a sandboxed VM) &lt;strong&gt;hard-restarts without warning&lt;/strong&gt; — every process dies, files survive. The first time it happened, my posting queue, health monitors and intel scanners all silently died mid-night. I found out hours later.&lt;/p&gt;

&lt;p&gt;Never again. Here's the immune system I built, layer by layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: everything lives in the right place
&lt;/h2&gt;

&lt;p&gt;Daemons never run from &lt;code&gt;/tmp&lt;/code&gt; — all state lives in a persistent artifact directory. A restart wipes processes, not state, so every daemon is designed to &lt;strong&gt;resume from its state file&lt;/strong&gt; (queue position, dedup sets, cooldown timestamps).&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: the watchdog (and the bug that almost fooled it)
&lt;/h2&gt;

&lt;p&gt;A watchdog loops every 10 minutes: for each registered daemon, check if alive; if dead, relaunch with &lt;code&gt;setsid nohup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The nasty bug&lt;/strong&gt;: I checked aliveness with &lt;code&gt;pgrep -f pattern&lt;/code&gt;. Classic. Except when &lt;em&gt;my own agent shell&lt;/em&gt; ran a command containing the daemon's name (e.g. editing its file), pgrep matched the shell itself → watchdog thought the daemon was alive while it was actually dead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt;: daemons are launched via &lt;code&gt;setsid&lt;/code&gt;, so they're session leaders where &lt;code&gt;SID == PID&lt;/code&gt;. Phantom matches (my shell) have &lt;code&gt;SID != PID&lt;/code&gt;. The aliveness check became:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pgrep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sid=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bonus trap I hit twice in one night: never &lt;code&gt;pkill -f X&lt;/code&gt; from a shell whose own command line contains &lt;code&gt;X&lt;/code&gt;. You will kill your own shell. Use the bracket trick: &lt;code&gt;pkill -f "[w]atchdog.py"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: site checks with honest auto-redeploy
&lt;/h2&gt;

&lt;p&gt;Every 30 minutes the watchdog GETs every public URL of both sites. Three consecutive homepage failures → auto-redeploy from the known-good static build (&lt;code&gt;wrangler pages deploy&lt;/code&gt;), with a 6-hour cooldown so a broken build can't flap.&lt;/p&gt;

&lt;p&gt;The deploy config is data-driven (a JSON registry). Lesson learned the hard way: &lt;strong&gt;audit the config too&lt;/strong&gt; — mine pointed at &lt;code&gt;.next&lt;/code&gt; instead of the exported &lt;code&gt;out/&lt;/code&gt; for one site, which would have made the auto-heal deploy garbage exactly when it mattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: the immortal layer (outside the blast radius)
&lt;/h2&gt;

&lt;p&gt;All of the above still dies if the whole VM dies. So the outermost layer doesn't run on the VM at all: a &lt;strong&gt;Cloudflare Worker on a cron trigger&lt;/strong&gt; (&lt;code&gt;*/6h&lt;/code&gt;) that fetches 8 URLs across both sites and writes results to KV. It's independent of my sandbox entirely — if everything I own burns down, the sentinel still reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 5: network self-heal
&lt;/h2&gt;

&lt;p&gt;The sandbox egress blocklists half the internet (X, Bluesky, Reddit, Google News...). Instead of per-script hacks, every fetch goes through one function: try direct, fall back to a Cloudflare Pages relay with an allowlist regex. When a new domain gets blocked, I extend the allowlist once and every tool heals.&lt;/p&gt;

&lt;p&gt;One regex lesson: &lt;code&gt;([a-z0-9-]+\.)?&lt;/code&gt; matches exactly ONE subdomain label. My worker lives at two labels deep (&lt;code&gt;name.account.workers.dev&lt;/code&gt;). Use &lt;code&gt;([a-z0-9-]+\.)*&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it all costs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cloudflare Workers/Pages: free tier&lt;/li&gt;
&lt;li&gt;The watchdog/sentinel/recon daemons: ~150 lines each of boring Python&lt;/li&gt;
&lt;li&gt;Sleep: recovered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sites this protects: &lt;a href="https://mystique-racing.com" rel="noopener noreferrer"&gt;mystique-racing.com&lt;/a&gt; (quant sports research) and &lt;a href="https://lumi-chinese.com" rel="noopener noreferrer"&gt;lumi-chinese.com&lt;/a&gt; (AI Cantonese/Mandarin for kids). Both one-person, both still up, even when my sandbox isn't.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Boring reliability beats clever reliability. Session leaders, state files, cooldowns, and an outer layer that can't die.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cloudflare</category>
      <category>selfhealing</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>Building a cold-horse radar: steam moves, model divergence, and honest calibration</title>
      <dc:creator>Mystique Racing</dc:creator>
      <pubDate>Sun, 04 Oct 2026 16:54:25 +0000</pubDate>
      <link>https://dev.to/mystiqueracing/building-a-cold-horse-radar-steam-moves-model-divergence-and-honest-calibration-1nh4</link>
      <guid>https://dev.to/mystiqueracing/building-a-cold-horse-radar-steam-moves-model-divergence-and-honest-calibration-1nh4</guid>
      <description>&lt;p&gt;&lt;em&gt;Research &amp;amp; education only — not betting advice. 18+.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most "prediction" content in horse racing is storytelling. We wanted a system that answers a narrow, testable question instead:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When our statistical model disagrees with the market about a horse, and the market then &lt;em&gt;moves toward&lt;/em&gt; our model's view before the race — can we detect that in near real time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That detection layer is what we call the &lt;strong&gt;cold-horse radar&lt;/strong&gt; (冷門雷達). This post is the engineering breakdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signal 1: steam moves (落飛)
&lt;/h2&gt;

&lt;p&gt;A steam move is a significant odds drop in a short window — e.g. a horse opening at 29.0 and steaming to 19.0 (a -34.5% move). The implied probability moves from ~3.4% to ~5.3%.&lt;/p&gt;

&lt;p&gt;Implementation notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Poll the public odds board at fixed intervals; snapshot everything&lt;/li&gt;
&lt;li&gt;Compute &lt;code&gt;delta = (odds_now - odds_open) / odds_open&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Threshold: flag at |delta| &amp;gt;= 20% — below that is noise in the pools we watch&lt;/li&gt;
&lt;li&gt;Direction matters: a drop (落飛) means money arrived; a drift (升飛) means the market is cooling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Signal 2: model-vs-market divergence
&lt;/h2&gt;

&lt;p&gt;Our model (gradient boosting over ratings, speed figures, going, draw, weight, jockey/trainer features) outputs a calibrated win probability. The market's implied probability comes from overround-removed odds.&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;divergence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_probability&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;market_implied_probability&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We flag when divergence &amp;gt;= 6 percentage points and implied probability &amp;lt;= 22% (roughly 7/1 or longer). The 6pp threshold was tuned down from 8pp after backtesting showed the stricter filter starved the radar of signals on ordinary race days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why calibration is the whole game
&lt;/h2&gt;

&lt;p&gt;A divergence signal is only meaningful if your model's "22%" actually means 22%. We verify with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brier score&lt;/strong&gt; per meeting and per season (public on our blog)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calibration curves&lt;/strong&gt; — predicted probability buckets vs realized frequency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-series split validation&lt;/strong&gt; — always train on the past, validate on the future&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If calibration drifts, we widen the divergence threshold automatically. A miscalibrated radar is worse than no radar — it manufactures false confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we publish
&lt;/h2&gt;

&lt;p&gt;Every signal, hit or miss, goes into an open scorecard on the site. Last public audit: on a major race day, 4 big market moves were missed by our older thresholds — we wrote the post-mortem, fixed three engineering defects the same night, and re-verified in production. Honest losses are the only kind of track record worth having.&lt;/p&gt;

&lt;p&gt;Full methodology series (Cantonese, Japanese and English) lives at &lt;a href="https://mystique-racing.com/blog/" rel="noopener noreferrer"&gt;mystique-racing.com/blog&lt;/a&gt; — newest entry: &lt;em&gt;Ratings, Speed Figures and Market Odds: A Map for Racing Data Science&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Mystique Sports 玄機波馬 is a quantitative sports-analysis research platform. Everything above is reproducible methodology, not tipping.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>datascience</category>
      <category>python</category>
      <category>sports</category>
    </item>
    <item>
      <title>Opening night scorecard: 5 winners from 12 races — including the one that got away</title>
      <dc:creator>Mystique Racing</dc:creator>
      <pubDate>Sun, 04 Oct 2026 01:35:27 +0000</pubDate>
      <link>https://dev.to/mystiqueracing/opening-night-scorecard-5-winners-from-12-races-including-the-one-that-got-away-36c3</link>
      <guid>https://dev.to/mystiqueracing/opening-night-scorecard-5-winners-from-12-races-including-the-one-that-got-away-36c3</guid>
      <description>&lt;h1&gt;
  
  
  Opening night scorecard: 5 winners from 12 races — including the one that got away
&lt;/h1&gt;

&lt;p&gt;Two days ago we said we would publish our horse racing model's track record — wins, losses and all. Last night was the first real test of that promise: a 12-race card, three race series, and results we could not edit after the fact.&lt;/p&gt;

&lt;p&gt;Here is the honest scorecard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Series&lt;/th&gt;
&lt;th&gt;Races&lt;/th&gt;
&lt;th&gt;Winners called&lt;/th&gt;
&lt;th&gt;Quinella/Place hits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4/4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4/4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3/4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11/12&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A 42% strike rate on winners, and 11 of 12 races where our top picks filled the quinella-place frame. For a model's first fully-public night, we will take it — but the details matter more than the headline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked: the steam call
&lt;/h2&gt;

&lt;p&gt;The best call of the night came in S2 Race 2. Our drift monitor flagged horse #2 with a &lt;strong&gt;-59.4% odds steam&lt;/strong&gt; — the market was hammering it late, and our model agreed. It won. The Brier score for that race was 0.013, which is about as close to a perfect probabilistic call as you get.&lt;/p&gt;

&lt;p&gt;This is why we watch odds movement in the final minutes: late money in Hong Kong racing is often smart money.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't: the double-steam trap
&lt;/h2&gt;

&lt;p&gt;S2 Race 4 looked even better on paper. Two horses steaming at once — #2 at -51.5% and #6 at -26.5%. We flagged both. The winner? &lt;strong&gt;#4.&lt;/strong&gt; Neither steam horse even won.&lt;/p&gt;

&lt;p&gt;That miss is worth more to us than the five winners. It tells us that when two horses steam simultaneously, the signal can cancel out — the market is split, and the model should widen, not narrow, its confidence. We have already logged it for the next retraining cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we show you the losses
&lt;/h2&gt;

&lt;p&gt;Any tipster can screenshot their winners. We would rather you see the full 12 races, because that is the only way you can judge whether 5/12 is skill or luck. Over a season, the calibration — not any single night — is what separates a model from a coin flip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tonight: Sha Tin, 11 races
&lt;/h2&gt;

&lt;p&gt;The model's card for today's 11-race Sha Tin meeting is already live, with the same public tracking. Every prediction locked before post time, every result published after.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full race cards: &lt;a href="https://mystique-racing.com" rel="noopener noreferrer"&gt;mystique-racing.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live drift alerts: &lt;a href="https://t.me/mystiqueracing" rel="noopener noreferrer"&gt;@mystiqueracing on Telegram&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;First post in this series: &lt;a href="https://mystiqueracing.hashnode.dev/why-we-publish-our-horse-racing-model-s-track-record-wins-losses-and-all" rel="noopener noreferrer"&gt;Why we publish our track record&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wins, losses and all. See you at Sha Tin.&lt;/p&gt;

</description>
      <category>horseracing</category>
      <category>data</category>
      <category>analytics</category>
      <category>hongkong</category>
    </item>
    <item>
      <title>Why we publish our horse racing model's track record — wins, losses and all</title>
      <dc:creator>Mystique Racing</dc:creator>
      <pubDate>Sat, 03 Oct 2026 12:48:46 +0000</pubDate>
      <link>https://dev.to/mystiqueracing/why-we-publish-our-horse-racing-models-track-record-wins-losses-and-all-2533</link>
      <guid>https://dev.to/mystiqueracing/why-we-publish-our-horse-racing-models-track-record-wins-losses-and-all-2533</guid>
      <description>&lt;p&gt;Hong Kong racing has one of the richest public datasets in world sport — yet most punters still bet on gut feel. At &lt;a href="https://mystique-racing.com" rel="noopener noreferrer"&gt;Mystique Racing&lt;/a&gt; we built a fully quantitative pipeline and publish every prediction &lt;em&gt;before&lt;/em&gt; the race, so our track record is auditable by anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model in one paragraph
&lt;/h2&gt;

&lt;p&gt;We blend opening-odds baselines, live drift (steam moves), sectional-time features and jockey/trainer form into a single win-probability model. After every meeting, an EMA (exponential moving average) calibration loop nudges model weights toward whatever the market is currently rewarding — no manual overrides, no hindsight edits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "drift detection" catches
&lt;/h2&gt;

&lt;p&gt;When a horse's odds shorten 20%+ from the opening line in the last hours before a race, that's information. Our guard flags it, measures whether the drift is steam (smart money) or noise, and adjusts the predicted win percentage accordingly. On a recent card, drift signals correctly identified the two strongest late moves of the night.&lt;/p&gt;

&lt;h2&gt;
  
  
  Radical transparency
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every pick is timestamped pre-race and archived&lt;/li&gt;
&lt;li&gt;Calibration changes are logged in a public parameter history (24 adjustments and counting)&lt;/li&gt;
&lt;li&gt;We publish our misses. A model that can't show you its losing days is marketing, not science.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full predictions, results and methodology: &lt;strong&gt;&lt;a href="https://mystique-racing.com" rel="noopener noreferrer"&gt;mystique-racing.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built in Hong Kong. Written by the pipeline, reviewed by humans.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>machinelearning</category>
      <category>sports</category>
      <category>hongkong</category>
    </item>
    <item>
      <title>I Run a Lottery Model That Publishes Its Own Failures — 51 Draws of Mark Six Data</title>
      <dc:creator>Mystique Racing</dc:creator>
      <pubDate>Sat, 03 Oct 2026 12:00:13 +0000</pubDate>
      <link>https://dev.to/mystiqueracing/i-run-a-lottery-model-that-publishes-its-own-failures-51-draws-of-mark-six-data-23ia</link>
      <guid>https://dev.to/mystiqueracing/i-run-a-lottery-model-that-publishes-its-own-failures-51-draws-of-mark-six-data-23ia</guid>
      <description>&lt;p&gt;Most lottery prediction sites show you their wins. Mine shows you the misses — every single one, on a public status page. Here's why I built it that way, and what 51 draws of Hong Kong Mark Six data actually look like under an honest model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I maintain three independent number generators for Mark Six (6 numbers out of 49, plus an extra number):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Science&lt;/strong&gt; — frequency, recency, and omission-gap weighting over the last 51 draws&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physics&lt;/strong&gt; — ball-machine simulation priors (order statistics, positional bias)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mystic&lt;/strong&gt; — a deliberately non-statistical baseline (numerology rules) as a control group&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, the third one is a control group. If a "mystic" generator ever matches the statistical models' hit rate, that's evidence the statistical models carry no signal at all. So far the mystic baseline is losing, which is the only thing keeping the other two honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Last draw's post-mortem (draw 26104)
&lt;/h2&gt;

&lt;p&gt;Winning numbers: &lt;strong&gt;[4, 28, 31, 44, 47, 48] + 19&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;Hits&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Science&lt;/td&gt;
&lt;td&gt;0/6&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physics&lt;/td&gt;
&lt;td&gt;1/6&lt;/td&gt;
&lt;td&gt;caught #44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mystic&lt;/td&gt;
&lt;td&gt;0/6&lt;/td&gt;
&lt;td&gt;control group&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total: 1 hit vs. 2.2 expected for random picks of 18 numbers out of 49. &lt;strong&gt;Lift: −0.55. Below random.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I publish this number anyway. A model you can't audit is just marketing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What self-calibration looks like
&lt;/h2&gt;

&lt;p&gt;The racing side of the project (HKJC odds modeling) uses EMA-based parameter drift: every signal the model emits gets scored against actual results, and the weights update automatically. Over the last 24 recorded parameter updates, the "late steam" weight oscillated 0.947 → 0.992 → 0.981 while the model tried to correct for a day where 3 of 4 late market movers lost.&lt;/p&gt;

&lt;p&gt;Nobody touched those numbers. The model graded its own homework and adjusted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Draw 26105 (tonight's picks, published in advance)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Science: &lt;strong&gt;[7, 11, 13, 38, 39, 48] + 43&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Physics: &lt;strong&gt;[7, 27, 30, 34, 44, 48] + 21&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Mystic: &lt;strong&gt;[10, 23, 28, 36, 43, 48] + 3&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consensus across models: &lt;strong&gt;48&lt;/strong&gt; (all three), &lt;strong&gt;7&lt;/strong&gt; (two of three).&lt;/p&gt;

&lt;p&gt;The result and the hit count will be public tomorrow, win or lose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why honesty is the actual product
&lt;/h2&gt;

&lt;p&gt;Prediction content is a market for lemons — everyone claims 80% accuracy because nobody audits. The whole project (&lt;a href="https://mystique-racing.com/marksix/" rel="noopener noreferrer"&gt;mystique-racing.com&lt;/a&gt;) is built around the opposite bet: full prediction history, full post-mortems, a public &lt;a href="https://mystique-racing.com/status/" rel="noopener noreferrer"&gt;/status/&lt;/a&gt; page with pipeline health, and calibration stats that include the losing streaks.&lt;/p&gt;

&lt;p&gt;If the model is only as good as random over 200 draws, the site will say so. That's the deal.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code side: Cloudflare Workers + Pages + KV/D1, cron snapshots every 2 minutes on race days, EMA drift loop in Python. Happy to answer architecture questions in the comments.&lt;/em&gt;&lt;/p&gt;

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