I finished sixth in a ten-team fantasy league last season — the last playoff spot, 6-7, with points-for (1754.8) coming in almost exactly level with points-against (1753.5). That is the statistical signature of an average team that backed into the bracket. I wanted to know how much of that was the roster and how much was the schedule, so before thinking about 2026 I built a Monte Carlo simulator whose only job was to take the schedule away.
The league is Sleeper, ten teams, Superflex PPR — you can start up to two quarterbacks, which makes QBs the scarce, premium asset and warps every draft in the room. The agent is DELPHI, a lower-stakes cousin to the infra agents I usually write about here: same discipline, a fantasy football scoreboard instead of production. The one design rule I set on day one is the one thing I'd defend from the whole project:
Keep the LLM out of the plumbing. Ingestion + simulation = plain scheduled Python.
Data comes from nflverse — parquet pulled straight from their GitHub releases and cached locally so every backtest is reproducible and offline. The weekly player-stats table is 114 columns per player per week; the cached seasons run 18,831 rows for 2022, 18,643 for 2023, and 18,981 for 2024. No model touches a network call at simulation time.
Talent versus a schedule that mugged someone
The season sim seeds each team's weekly score as a Normal(mean, std) drawn from that team's real 2025 Sleeper scores, floors it at zero, then randomizes the schedule tens of thousands of times and runs a full six-team bracket with byes and reseeded semis. Fifty thousand seasons, fixed seed:
.venv/bin/python -m sim.season 50000
My team came out at 135.0 PPG, a 27.5 standard deviation, 67.0% to make the playoffs, and 8.3% title equity. The champion, kanavm, sat at 148.3 PPG and 31% title equity — thirteen points a week is an enormous gap over a season. So the answer to my original question was: mostly the roster. I was a coin-flip team that got a coin-flip result.
The cleanest thing the sim surfaced wasn't about me. HarishD23 put up 141.0 PPG at an unusually low 21.1 standard deviation — a high floor, few disaster weeks — which the model rewarded with 86.7% playoff odds and 17.8% title equity, second-best in the league. In actual 2025 he went 4-9 and missed the playoffs entirely. Same roster, and the schedule mugged him. That one row is the whole talent-versus-luck argument in two numbers, and it's the reason I trust a randomized-schedule sim over a final standings table.
The real decision is the draft, not the lineup
Start/sit is a rounding error next to the draft, so the second simulator drafts the whole thing. It runs 400 mock drafts from random seats against a model of this specific room — FantasyCalc Superflex ADP plus a QB-urgency term I calibrated until about seven quarterbacks went in round one, which is what actually happened (the model produced 6.6). This league burned 7 of its 10 first-round picks on QBs and had 8 gone by pick 12. That distortion is the edge. Each drafted roster gets projected from real nflverse positional scoring curves and run through the season sim — 400 drafts × 3,000 seasons across a 199-player pool, which is where the dashboard's '6,000,000 simulated seasons' comes from once you multiply by five strategies.
The strategies ranked by title equity: elite_anchor 12.1% (146.9 PPG) at the top, then value_BPA 10.1%, hero_rb 9.4%, balanced 8.6%, and dead last, 2QB_early at 6.7% — which is a polite label for what I actually did in 2025. The headline the project prints for itself is 12.1% = 1.8x that 6.7% path. The mechanism is positional scarcity: the top-six weekly PPG for QBs runs 25.1 / 23.8 / 23.0 / 21.9 / 20.4 / 20.3, nearly flat, while tight ends fall off a cliff — 16.3 / 14.5 / 14.0 / 13.2 — so you grab one real TE before the position craters and let the room overpay for its fifth and sixth QB.
Then I tried to break the simulator
A 1.8x edge that conveniently endorses the opposite of what I did last year is exactly the kind of result you should distrust. So I spent longer auditing the model than building it, and it has four load-bearing flaws. The first is variance. The draft sim computes a lineup's weekly variance as the plain sum of its starters' variances:
var = sum(p["std"] ** 2 for p in st) + KDEF_VAR
That's the R = I special case — it assumes every starter scores independently. They don't. A quarterback and his top receiver boom together on the same fast-paced, pass-heavy afternoon; the off-diagonal correlation term is real and positive. Dropping it makes simulated team standard deviation systematically too tight, and when variance shrinks the title-equity ranking collapses into little more than a reprint of mean PPG — which quietly biases the entire sim toward high-floor anchor rosters. The fix I've spec'd replaces the sum with the exact identity Var(T) = 1ᵀ(D R D)1 and a real correlation matrix (baseline ρ = 0.08, ρ = 0.55 for a QB and his pass-catcher), validated against real 2025 team standard deviations.
The second flaw is worse because it flatters the result directly. The same FantasyCalc positional rank drives both how the opponents draft and, through the scoring-curve lookup, how many points each drafted player is projected to score. Draft value and simulated outcome are effectively the same object, so best-player-available strategies win almost by construction. In my own build spec I called it 'regressing a thing on a laundered version of itself.' It means the 12.1%-versus-6.7% gap is directional, not calibrated — I trust that elite-anchor beats 2QB-early, I do not trust the 1.8x.
Third: there are no in-season dynamics. Every team gets one frozen weekly distribution for all thirteen regular-season weeks, so an anchor QB is a permanent ~18-point asset and a streamer a permanent ~11.5-point scrub. That structurally rewards anchoring and structurally penalizes streaming — which is the entire thesis I'm trying to test. Streaming is supposed to pay off precisely in the weeks the anchor is hurt or on bye, and a frozen distribution has no byes, no injuries, and no waiver wire. Worse, the scoring curves were built with a min_games ≥ 6 filter that drops injury-shortened seasons, deleting bust risk from the data entirely.
v1 uses var = sum std² (draft.team_dist line 125) ... samples floored Normals (season.simulate line 40), which is symmetric and, when clipped at 0, inflates the mean.
That last clause is the fourth flaw. Real weekly fantasy scoring is right-skewed — the ceiling games are what you're chasing — but a Normal is symmetric, and clipping it at zero to stop negative scores shoves the mean upward on every player. The floor is a band-aid that distorts the first moment. The planned fix swaps in a Gaussian copula with per-slot shifted-lognormal margins that carry a real skew term, so positive mass is preserved without a clip.
A council of ghosts, and what's still unbuilt
I also ran the strategy past an adversarial 'Football Council' — five coaching-legend personas across two model providers, told to red-team the plan. Their first ruling on elite-anchor was a unanimous 5/5 REFINE: fading the round-one QB tax is correct, but don't pure-stream quarterbacks to rounds five and six. Secure one sturdy anchor QB by round three or four, before the Superflex QB2 cliff, then stream the second slot. As one persona put it, 'grab QB1 by rounds 3-4 in this 7-of-10-QBs-round-1 room, not 5-6.' The council sharpened the strategy rather than rubber-stamping it, which is the only reason I bothered wiring it up.
None of these are lineup win probabilities, by the way — those are a separate closed-form number the weekly engine computes from two Normals via erf (a Week-5 backtest had me a 45% underdog, 142.8 projected against 147.1). The 8.3% and 12.1% figures are season-long title equity, a different question. And the fixes above are spec, not code: the v2 numbers don't exist yet. The spec's own verify pass came back NEEDS-WORK — a double-counting interaction between the variance and in-season fixes, plus math errors in two of them — so whether the corrections shrink or flip the 12.1%-versus-6.7% gap is genuinely unknown.
Every number in this post is a 2025 backtest. The 2026 league doesn't exist yet — the config still has LEAGUE_ID_2026 set to None, nflverse hasn't published 2025 weekly stats to rebuild the curves on, and and the draft itself doesn't land until the commissioner opens the 2026 league, which hasn't happened yet. So the honest state of it is: a simulator that says the roster was the problem, a draft edge I trust in direction but not magnitude, and four fixes I already know are owed before I'd stake a real pick on it.
