<?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: 杰克法拉利</title>
    <description>The latest articles on DEV Community by 杰克法拉利 (@_73f88ca6d9f9bd82d23).</description>
    <link>https://dev.to/_73f88ca6d9f9bd82d23</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%2F4132257%2F888702c4-1558-434a-93b5-ac20a9643393.png</url>
      <title>DEV Community: 杰克法拉利</title>
      <link>https://dev.to/_73f88ca6d9f9bd82d23</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_73f88ca6d9f9bd82d23"/>
    <language>en</language>
    <item>
      <title>I Split 220,000 Matches Into Two Channels: Retail Emotion vs. Institutional Intent</title>
      <dc:creator>杰克法拉利</dc:creator>
      <pubDate>Tue, 29 Sep 2026 04:30:05 +0000</pubDate>
      <link>https://dev.to/_73f88ca6d9f9bd82d23/i-split-220000-matches-into-two-channels-retail-emotion-vs-institutional-intent-e5l</link>
      <guid>https://dev.to/_73f88ca6d9f9bd82d23/i-split-220000-matches-into-two-channels-retail-emotion-vs-institutional-intent-e5l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;GitHub: github.com/MENG-COOLMAN/PitchQuant&lt;br&gt;
For academic research and technical exchange only. Not betting advice.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What's new in this update
&lt;/h2&gt;

&lt;p&gt;In my previous post I wrote about building a complete football data analysis pipeline from 220,000 matches, pushing direction accuracy from 48% to 55%. But one question kept bugging me — &lt;strong&gt;how should you actually read odds movement?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over the last two weeks I figured it out: the odds aren't set by one person. Two groups are buying them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retail bettors&lt;/strong&gt; chase Chinese lottery odds, piling into whichever direction is dropping, convinced "dropping odds = smart money." &lt;strong&gt;Institutions&lt;/strong&gt; move the Asian/European market with real money, pushing lines toward what they believe. These two groups disagree all the time — and when they do, who's right?&lt;/p&gt;

&lt;p&gt;The answer: &lt;strong&gt;most of the time, retail is wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So in this update (V3.5.76) I did one thing: split these two groups into two independent channels that cross-validate — and expose — each other. This post walks through the "dual-channel" mechanism and the uncomfortable finding it surfaced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why two channels are necessary
&lt;/h2&gt;

&lt;p&gt;First, a measured data point. I backtested the tick-by-tick Chinese lottery odds: &lt;strong&gt;when a direction's odds keep dropping before kickoff (retail reads this as "the bookmaker likes it"), that direction actually hits only 25% of the time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Four-match sample — the dropping direction won a quarter of them.&lt;/p&gt;

&lt;p&gt;The drop isn't wrong; the &lt;strong&gt;cause&lt;/strong&gt; of the drop is wrong. The Chinese lottery vig is as high as 12.8%, and its water levels mostly follow retail sentiment, not information. Retail piles in, odds drop, and the bookmaker is happy — because with lower odds, retail's expected value evaporates and the bookmaker's exposure shrinks.&lt;/p&gt;

&lt;p&gt;The Asian/European market, by contrast, has only ~6.9% vig. Line and water movement there is backed by real hedging pressure. Institutions don't buy retail sentiment; they buy &lt;strong&gt;their own positions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So I split the model's data flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lottery tick-by-tick odds  →  Retail channel (sentiment·12.8% vig)
Asian/European time series →  Institutional channel (intent·6.9% vig)
             ↓ cross-validate ↓
       Quadrant decision (Q1 consensus/Q2 trap/Q3 upset/Q4 block)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The four quadrants: how the two groups fight
&lt;/h2&gt;

&lt;p&gt;Crossing the two channels produces four scenarios, ranked by priority:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2 Trap alarm (most dangerous)&lt;/strong&gt;: retail floods direction A with dropping odds, institutions stay still or push the other way.&lt;br&gt;
→ Classic "trap." Retail is moving exactly where the bookmaker wants. Handling: knock the favorite down one confidence notch and force the second direction into a tie.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q1 Consensus (most trustworthy)&lt;/strong&gt;: retail drops odds on A, institutions follow by deepening the line.&lt;br&gt;
→ Both groups agree — only now is the signal actually worth something. Confidence up half a notch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4 Block signal&lt;/strong&gt;: retail sees odds rise on A (too scared to buy), institutions quietly move toward A.&lt;br&gt;
→ The bookmaker is "blocking" — deliberately inflating odds to scare off retail while quietly favoring A itself. Follow the institutional direction, +0.5 notch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3 Upset hint (weakest)&lt;/strong&gt;: retail hasn't moved, institutions move alone.&lt;br&gt;
→ Flag for attention only, no adjustment. Wait for confirmation.&lt;/p&gt;

&lt;p&gt;One hard rule here: &lt;strong&gt;the quadrants may only adjust confidence, by at most ±1 notch, and may never reverse the hardcore direction.&lt;/strong&gt; I learned this the hard way — letting weak signals overturn strong ones just hands control to noise.&lt;/p&gt;
&lt;h2&gt;
  
  
  The time dimension: when is a signal real?
&lt;/h2&gt;

&lt;p&gt;The other dimension I added is &lt;strong&gt;time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lines older than 48 hours before kickoff I call "smoke screens" — they're shaped by capital flows and the bookmaker's exploratory positioning, and carry little signal. Lines within 90 minutes of kickoff carry the most information density, because by then the money that was going to arrive has arrived, and the line basically reflects real views.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;48h   early line  → smoke screen·institutional channel downgraded to weak reference
≤90min late line  → highest information density·full weight
in between        → normal window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It sounds simple, but this grading filtered out a lot of "fake signals" in backtesting. Many seemingly bizarre odds movements, zoomed out on the time axis, turn out to be early-line noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering debt I paid off along the way
&lt;/h2&gt;

&lt;p&gt;Beyond the algorithm, the engineering got a lot of attention too. The model now runs &lt;strong&gt;413 automated checks&lt;/strong&gt; — if rules and data disagree, it fails loudly. "Looks about right" doesn't pass.&lt;/p&gt;

&lt;p&gt;A few fixes worth mentioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-language team matching&lt;/strong&gt;: snapshots store English team names, but the lottery txt files use Chinese names, and they used to never match. Now there are 191 bilingual alias mappings, covering all top-5 leagues + UCL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The auditor audits itself&lt;/strong&gt;: my check rules used to have a loophole — "keyword present = pass." Now a check needs the keyword &lt;strong&gt;plus a number&lt;/strong&gt; to count, so the auditor can't be lazy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixture regression&lt;/strong&gt;: every parsing bug we've ever hit is now frozen as a real input sample, and the model re-runs all of them on every pass — so the same bug can't come back in a different function.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The other big stuff this fortnight
&lt;/h2&gt;

&lt;p&gt;Besides the dual channel, several new modules landed that weren't in v1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UEFA Nations League sub-model&lt;/strong&gt;: national-team football is a completely different game — no transfers, no form continuity, mystery motivation in friendlies. I built a dedicated tiered rule set for the NL, backtested on 658 national-team matches, and set a "domain discipline": top-5 league thresholds don't apply to national teams, and vice versa.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;European competition three-layer calibration&lt;/strong&gt;: UCL knockout two-legged ties, group-stage round-robins, and neutral-venue finals all have completely different motivation and formats. Now each is calibrated by tier — the first-leg leader parks the bus in the second leg, group-stage dead rubbers get tanked, all quantified into the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-league sub-models&lt;/strong&gt;: each of the top-5 leagues gets its own distilled rule set (7,000-9,000 backtested matches each). Bundesliga is a goals machine, Serie A is draw-heavy, Ligue 1 is low-scoring — applying one universal threshold to every league is wrong by construction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-match learning loop&lt;/strong&gt;: after every match, the model writes the result, prediction, and actual odds back to the case library, then periodically re-runs backtests to recalibrate the lookup tables. It doesn't retrain everything on every update — only the parameters whose backtests show genuine drift.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;As always, the cold-water section:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direction accuracy: &lt;strong&gt;~55-58%&lt;/strong&gt; (clear gains on weak-signal matches, stable on strong-signal matches)&lt;/li&gt;
&lt;li&gt;Dual-channel core scenario: low European/Asian dispersion (spread&amp;lt;2%) hits **52.7%** direction accuracy; high dispersion (spread&amp;gt;10%) drops to &lt;strong&gt;44%&lt;/strong&gt; — the more aligned the institutions, the more trustworthy the signal&lt;/li&gt;
&lt;li&gt;Deep favorite + institutional alignment (home &amp;lt;1.50 and spread&amp;lt;2%): &lt;strong&gt;80.0%&lt;/strong&gt; (smaller sample, reference only)&lt;/li&gt;
&lt;li&gt;Top-2 score hit rate: &lt;strong&gt;~30%&lt;/strong&gt; (strong-signal matches)&lt;/li&gt;
&lt;li&gt;All numbers are time-split backtests on 220k historical matches. &lt;strong&gt;They do not predict the future.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the line from the project's front page: football is chaotic, markets are efficient. This project helps you &lt;strong&gt;make fewer mistakes&lt;/strong&gt; — it doesn't help you &lt;strong&gt;beat probability&lt;/strong&gt;. Negative long-term EV on sports lottery is structural, independent of any model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source
&lt;/h2&gt;

&lt;p&gt;The project is on GitHub under MIT license:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Core scripts are all Python, stdlib-heavy&lt;/li&gt;
&lt;li&gt;Distilled JSON lookup tables (de-vig calibration, score depth, handicap tiers, etc.) are included&lt;/li&gt;
&lt;li&gt;The raw 220k-match CSV is too large and has source terms, so it's not included&lt;/li&gt;
&lt;li&gt;You'll need your own API keys (odds-api and api-football both have free tiers)&lt;/li&gt;
&lt;li&gt;Calibrated for top-5 European leagues + UCL; other leagues aren't calibrated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;One last thing&lt;/strong&gt;: for academic research and technical exchange only. Not investment advice.&lt;/p&gt;

&lt;p&gt;If you're curious about telling retail and institutional money apart in odds, star it and drop an issue. Happy to discuss.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;A student studying for grad school in financial math. This project was built bit by bit in my spare time. All the data, rules, and code are on GitHub. Come build with me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>football</category>
      <category>datascience</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built a Football Data Analysis Pipeline From 220,000 Matches</title>
      <dc:creator>杰克法拉利</dc:creator>
      <pubDate>Sat, 19 Sep 2026 02:17:32 +0000</pubDate>
      <link>https://dev.to/_73f88ca6d9f9bd82d23/i-built-a-football-data-analysis-pipeline-from-220000-matches-4gen</link>
      <guid>https://dev.to/_73f88ca6d9f9bd82d23/i-built-a-football-data-analysis-pipeline-from-220000-matches-4gen</guid>
      <description>&lt;p&gt;&lt;em&gt;For academic research only. Not betting advice.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/MENG-COOLMAN/PitchQuant" rel="noopener noreferrer"&gt;MENG-COOLMAN/PitchQuant&lt;/a&gt; — MIT license, free to use.&lt;/p&gt;




&lt;h2&gt;
  
  
  The TL;DR
&lt;/h2&gt;

&lt;p&gt;Six months. 227,000 matches. One question: &lt;strong&gt;what can you actually learn from football odds?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is not a "prediction oracle." Football is chaotic and markets are efficient — I put that on the front page. But if you've ever stared at a line of odds and wondered if there's a signal hiding in the noise, this post is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;It started with a simple frustration. Two teams both at 1.50. One wins every time. The other doesn't. Why?&lt;/p&gt;

&lt;p&gt;I started pulling data — de-vigging, ELO ratings, Poisson models. At first it felt like I was onto something. Then reality hit: after a few hundred matches, &lt;strong&gt;no single indicator beats the market&lt;/strong&gt;. De-vigged probabilities land within ±5pp. ELO gaps explain maybe 10% of variance. Standard Poisson is basically guessing on 3:2 vs 2:1.&lt;/p&gt;

&lt;p&gt;But here's the thing: when you combine weak signals with proper weights and strict backtesting gates, directional accuracy climbs from 48% to around 55%. That number isn't flashy. But in the odds world, every single percentage point is real money.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;It's an &lt;strong&gt;LLM-as-runtime&lt;/strong&gt; architecture. Think of it as a very picky analyst who refuses to skip steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python scripts&lt;/strong&gt; do the math — de-vigging, Kelly criterion, Dixon-Coles Poisson, score lookup tables. The LLM doesn't question what the numbers say.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;34 knowledge files&lt;/strong&gt; tell the LLM what to look for — league quirks, European two-leg adjustments, halftime patterns, water-level traps. Every rule comes from backtests, not gut feel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;238 automated checks&lt;/strong&gt; catch mistakes. If a rule says one thing and the data says another, it fails loudly. "No number = not done" is a hard rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuuu9hd4swroi2fpt1fw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuuu9hd4swroi2fpt1fw5.png" alt="PitchQuant project README showing architecture and badges" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a single match, the pipeline runs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Odds input → De-vig probabilities → 12 hardcore checks → Odds movement morphology →
Scenario rules → 26 inference checks → League calibration → Home/away factor →
Direction → Goals magnitude → Score spectrum → Totals → Self-check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic isn't the number of steps. It's that &lt;strong&gt;each step's output feeds directly into the next&lt;/strong&gt;. No skipping. No "these two steps just happened to both be in the report."&lt;/p&gt;

&lt;h2&gt;
  
  
  Five things I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The weak-consensus trap
&lt;/h3&gt;

&lt;p&gt;When the market isn't strongly convinced (skew &amp;lt; 150%), the "favorite" wins only &lt;strong&gt;36-43%&lt;/strong&gt; of the time. That's a coin flip. Betting the favorite here loses over time. The draw is consistently underpriced and must be listed as a co-primary outcome. I learned this the expensive way.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Deep odds don't always mean safe
&lt;/h3&gt;

&lt;p&gt;Home odds below 1.30 look like a lock. But if the Asian handicap only moves 0.5-0.75 (shallow at low odds), actual home wins drop from &lt;strong&gt;74% to 56%&lt;/strong&gt;. Low odds + shallow handicap = trap. You can't see this from European odds alone — you need the Asian water level. This one signal alone was worth the entire project.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Over 2.5 and exact score are different games
&lt;/h3&gt;

&lt;p&gt;Everyone assumes "over 2.5 means big scores." Wrong. When the O2.5 line is low (strong over expectation), the Top-2 score hit rate actually &lt;strong&gt;drops&lt;/strong&gt;. Why? Because high-scoring matches have more dispersed scorelines. 2:1, 3:1, 2:2, 3:2 all go over 2.5. You nail the direction but can't pin the exact score. Took me weeks to accept this trade-off.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. More machine learning isn't always better
&lt;/h3&gt;

&lt;p&gt;I was excited about online ML (River's ADWIN drift detection). It sounded cutting-edge. Backtesting showed only a &lt;strong&gt;0.68pp&lt;/strong&gt; improvement — and only on matches where the market was already weak. Forcing it onto strong-signal matches just added noise. So I built a gate: strong market signal → learning stays out. Weak signal → learning gets a co-reference slot. Sometimes the best ML model is the one you don't use.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Poisson isn't a crystal ball
&lt;/h3&gt;

&lt;p&gt;Dixon-Coles correction helps, but it still assumes goals are independent events. In reality, a leading team sits back, the trailing team throws everyone forward. Poisson can't model that. So it's just one engine — you also need to fuse with the actual score odds distribution from the market. The market already did the hard work; your job is to read it, not replace it.&lt;/p&gt;

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

&lt;p&gt;Before anyone gets too excited:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direction accuracy: ~50% baseline, &lt;strong&gt;~55-58%&lt;/strong&gt; with model optimization&lt;/li&gt;
&lt;li&gt;Top-2 score hit rate: ~25% baseline, &lt;strong&gt;~30%&lt;/strong&gt; on strong-signal matches&lt;/li&gt;
&lt;li&gt;These are time-split backtest numbers from 227k matches. They do &lt;strong&gt;not&lt;/strong&gt; predict the future.&lt;/li&gt;
&lt;li&gt;Long-term EV on sports lottery is structurally negative. No model changes that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point isn't "how to win." It's building a data analysis methodology. If you're into quant/data science, these ideas (de-vig calibration, anti-overfitting gates, evidence-bound self-checks) generalize to any prediction project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source
&lt;/h2&gt;

&lt;p&gt;The project is on GitHub under MIT license:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkgs8bzkbzrx29dxb75k3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkgs8bzkbzrx29dxb75k3.png" alt="GitHub repository file structure" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All core scripts are Python, stdlib-heavy, no heavy frameworks&lt;/li&gt;
&lt;li&gt;Distilled JSON lookup tables (backtest artifacts) are included&lt;/li&gt;
&lt;li&gt;Raw 227k-match CSV not included (size + source terms)&lt;/li&gt;
&lt;li&gt;You need your own API keys (odds-api and api-football both have free tiers)&lt;/li&gt;
&lt;li&gt;Calibrated for top-5 European leagues + Champions League / Europa League&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One last thing: this project is for academic research and technical exchange only. It does not constitute investment advice. Football is round. Data reduces mistakes; it doesn't beat probability.&lt;/p&gt;

&lt;p&gt;If you found this interesting, star it on GitHub and drop an issue. Happy to discuss architecture, backtesting, or anything in between.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;A student studying for grad school in financial math. Built this bit by bit in my spare time. All the data, rules, and code are on GitHub. Come build with me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quantitativefinance</category>
      <category>footballanalytic</category>
      <category>python</category>
      <category>datasciencu</category>
    </item>
  </channel>
</rss>
