Zest.win version v1.1.0: Behind the Scenes: Ensemble Predictions and a Caching System That Never Goes Stale
zest.win upgraded its prediction engine with ensemble models, Platt calibration, and an event-driven caching system that keeps every page fresh.
We recently shipped two under-the-hood upgrades that touch every page on zest.win — a retooled prediction pipeline and a fundamentally new approach to caching. Here's what changed and why it matters.
From one model to a voting ensemble
Until recently, our prediction pipeline trained a single XGBoost model per target — series winner, map handicap, first tower, and so on — using the same hyperparameters across all three games. If the XGBoost model overfit a particular tournament meta, there was no second opinion to temper it. We've moved to an ensemble. Every match target is now trained independently by both XGBoost and LightGBM, two gradient-boosted tree learners with complementary bias profiles. At inference time, the pipeline runs both models, applies calibration independently to each, and produces a soft-vote average. If one model type is unavailable — say LightGBM hasn't trained yet for a new target — the ensemble silently falls back to what's available. No breaking, no gaps.
Training also got smarter. The pipeline now uses a three-way temporal split: one slice for training, one for grid search and early stopping, and a dedicated hold-out slice for Platt calibration fitting. This means the calibration data never leaks into hyperparameter selection. We also added a do-no-harm guard: if the Platt calibrator's slope comes back non-positive — a sign the calibration fit is pathological — the system rejects it and serves the raw model probabilities instead of miscalibrated ones.
Every prediction target in every game ecosystem — 15 targets for League of Legends, 10 each for CS2 and Dota 2 — now runs through the same shared core. The game-specific code is a thin spec file: which targets exist, which side is "home," what backfill windows to use. The training, inference, settlement, health checking, and event handling all live in one place, so a fix in calibration logic benefits all three games simultaneously.
Why the model's 65% should actually mean 65%
Raw model scores can be overconfident — a model might output 72% when its historical accuracy on similar predictions is closer to 64%. Platt calibration corrects this by fitting a sigmoid curve that maps raw scores to their empirical accuracy. We compute Expected Calibration Error (ECE) across 10 probability bins and log it to a model_history.csv with every training run. If accuracy drifts or calibration degrades across versions, the health monitor catches it before users see degrading predictions.
You can verify the calibration yourself on any track-record pages — the calibration chart shows exactly how well predicted probabilities line up with real outcomes.
Active caching: why pages never wait for stale data
The old caching approach was simple — set a TTL and wait. Long TTLs meant stale match lists after a schedule update. Short TTLs meant every visitor triggered a database query even when nothing had changed.
We replaced this with two-layer active caching. Layer one is server OutputCache for anonymous HTTP responses, served from Cloudflare's edge network with a 5-minute browser TTL and 1-hour edge TTL. Layer two is an in-process TaggedMemoryCache that holds expensive computations — like full match lists with model probabilities — for logged-in subscribers who bypass the OutputCache entirely.
The key difference: the cache doesn't just expire. A CacheInvalidationConsumer listens to our event broker and evicts only the entries that actually need refreshing. When a scrape completes and new match data arrives, game-wide caches for that game evict. When new odds land, match and game caches evict. When a prediction is computed, match and prediction caches evict — but tournament pages stay warm. The system maps 6 event types to specific cache tags, and an event that changed nothing (zero new matches, zero new odds) evicts nothing.
And we don't stop at eviction. After clearing stale entries, the system immediately pre-fetches the canonical subscriber match list in the background. The next logged-in user who opens zest.win gets a warm cache — no cold-start penalty.
What this means day to day:
- Faster loads: Most public pages serve from Cloudflare's edge, not our application servers.
- Real-time freshness: When the schedule changes or odds update, affected pages refresh within seconds — not minutes.
- Better-calibrated predictions: Ensemble averaging with proper calibration means the probabilities you see are what the data actually supports.
- Independently auditable: Every prediction carries its model version, timestamp, and is graded against the actual result. The track-record pages show per-target accuracy, weekly trends, and calibration curves — good weeks and bad. We'll keep publishing our model's track record in public because the only way to trust a prediction system is to grade every call it makes.
Related Analysis & Predictions
View all posts →
What’s New in ZestWin v1.5.0: Crypto Deposits, Rolling 30-Day Accuracy & Flexible Plans
ZestWin update: instant USDT-TRC20 crypto deposits, rolling 30-day model accuracy tracking, semi-annual plans, and faster edge performance.
Read →
Zest.win v1.4 — Dota 2 is in beta, confidence scores, and a whole lot under the hood
Three weeks in, we're shipping v1.4: Dota 2 leaves 'soon' and enters beta, every prediction now shows you how confident the model really is, and a proper checkout means paid plans are live.
Read →