line-v2 roadmap — increasing LINE model accuracy (2026-07-16)
Owner intent: the line market is where model improvement can still buy
edge. H2H is frozen (λ ≈ 0.02–0.10 — the market does the predicting); the
margin channel is not: the ml/ ridge just gained −0.44 MAE from 111 new
games, line_ridge_core produced the original +4.4%-at-open ROI signal,
and lineup information (λ = 0.141) is the one channel the market underprices.
line-v2 concentrates all model effort there.
Benchmarks — do not mix them. Two line models exist with different
evaluation windows:
| Model | Eval | Current MAE | Role |
|---|---|---|---|
line_ridge_core |
Walk-forward bench (strategy-shift reporter) | 13.46 | THE promotion benchmark |
ml/ ridge margin (ml/nrl_margin_model.py) |
Own 2024+ test split | 12.57 | Production predictor (gold_nrl_predictions) |
The promotion gate is against line_ridge_core on the SAME walk-forward
protocol — the 12.57 is not comparable and must never be quoted as if it
beats 13.46.
Pre-registered gates (fixed before any build; never edited with results)
- G-L1: walk-forward out-of-sample MAE < 13.46 (beats
line_ridge_core). - G-L2: walk-forward CLA on the LINE market ≥ 0.53 with z ≥ 2
(the market must move toward the model's line disagreements). - Joint: MAE improvement without CLA evidence is NOT promotion.
- Paper gate (after G-L1+G-L2): ≥ 50 settled LINE paper trades,
mean CLV > 0, no round > 30% of profit. ROI report-only at this volume.
Phases
P0 — prerequisites (R21 week; blocks everything)
The "Before building" checklist from docs/edge-risk-and-monitoring-plan.md:
current brief Steps 1–3 pushed; stg_team_lists_latest ≥ 90% coverage of
2024–2026 rounds; position resolver assigns 1/6/7/9/13 correctly for ≥ 95%
of squads (pytest over the 10 most common team-list formats); a committed
leakage pre-test asserting the new delta columns use only rounds < target.
P1 — data foundations (R21–R22): the three pre-registered features
Built in dbt (ml_nrl_lineup_features.sql or companion), full spec already
pre-registered in docs/edge-risk-and-monitoring-plan.md:
delta_spine_lock_cohesion— time-decayed (8-week half-life) count of
games the exact 1/6/7/9/13 unit has started together, home − away.lock_playmaker_index_delta— is the named 13 a ball-player
(Yeo/Murray/Radley class) or a runner; composite of trailing
try-assists/passes/offloads per 80.-
spine_lock_games_together— raw non-decayed count (VIF-checked vs #1;
drop if > 5). -
delta_offball_effort(owner-directed 2026-07-17) — team off-ball
effort from per-player round stats (gold_nrl_player_round_stats),
built ONLY from fields we already collect:
player_offball = (
0.5 × (tackles_made / minutes_played) # defensive workrate
+ 0.3 × tackle_efficiency # made / (made+missed+ineffective)
+ 0.2 × (kick_return_metres / minutes_played) # effort in the return game
)
team_offball(round r) = minutes-weighted mean over players, trailing
4-round window ending at r−1 (placement-time safe).
delta_offball_effort = home − away, z-scored within season.
Hypothesis to TEST, not assume: high off-ball effort teams defend their
line better than the market's attack-weighted priors expect → margin
compression the line misses. Known limitation: true off-ball stats
(kick pressures, decoy runs, support runs) are NOT scraped — the NRL
match centre exposes them; extending the stats spider is logged in
docs/data-ingest-strategy.md as a candidate feed. Build the composite
from current fields first; upgrade the definition only if the scraper
extension lands.
Join into ml_nrl_match_training_examples; add to LINEUP_FEATURES in
data/backtest.py only when non-null for ≥ 80% of the 2016–2026 lineup
window. Feature 4 enters the same v2a reporter phase as 1–3 — one phase,
one gate check, no per-feature cherry-picking.
Injury data — current state (audited 2026-07-17)
We do not collect injury information anywhere (no spider, no feed, no
table). Two routes, in preference order:
- Implicit availability (free, build with P1): a player named in
round r−1's team list with minutes played, absent from round r's list,
is an availability loss.delta_availability_loss= RAPM-weighted sum
of players lost this week, home − away. Uses only data we already
stage (team lists + minutes + RAPM values); placement-time safe from
Tuesday. Captures injury + suspension + rest together — for pricing
purposes the cause doesn't matter, the absence does. - Explicit casualty-ward feed (new ingest, optional): NRL.com's
weekly casualty ward is semi-structured (player, injury, expected
return). Adds return-timeline foresight the implicit method lacks.
Logged in data-ingest-strategy as a candidate; only worth building if
route 1 shows signal.
P2 — model variants (R22–R23), each its own reporter phase
- line-v2a —
line_ridge_core+ the three P1 features. The cheapest
test of the thesis; if lineup cohesion moves line MAE, it shows here. - line-v2b — state-space margin model: team strength as a latent state
with process noise (replaces fixed ridge weights with time-varying
strengths; the variance estimate prices totals-adjacent uncertainty).
Only started if v2a's CLA direction is positive. - line-v2c — weather/ground interactions (crosswind × venue, rain ×
margin compression). Weather coverage is 99%+ post-backfill; the ridge
already carries non-zero weather coefficients — interactions are the
untested increment.
Each variant runs through scripts/report_strategy_shift.py --phase
line-v2<x> with G-L1/G-L2 evaluated against line_ridge_core — never
against each other (no incumbent-shopping).
P3 — market integration (August)
The teamlist_window snapshot CronJobs (Tuesday T+0/T+30m/T+2h/T+12h UTC,
deployed via go-cicd-ops) give the LINE market an intraweek price path for
live rounds — the first time we can measure when the line reprices after
team lists. Use it two ways: (a) LINE CLA measured against the
Tuesday-evening price, not just open→close; (b) if a v2 variant passes
G-L1/G-L2, seed LINE paper trades in the window the repricing analysis
says is softest.
P4 — promotion review (~September, aligns with the Betfair decision)
At ≥ 50 settled LINE paper trades: promotion review against the paper gate.
If Betfair historical data is purchased, re-run P2 backtests against
exchange lines before promotion.
Goal 2 (review-recommended 2026-07-20): predict the line MOVE, not the margin
The three goal sessions (line-v2a features, line-market-residual v1/v2,
line-cover-goal-v1) produced consistent, honestly-reported negative
results: the opening line itself is the best margin predictor we have
tested (MAE 13.31 vs best candidate 13.37) and cover classification
cannot beat a 0.25-Brier coin. This mirrors the H2H λ finding exactly —
team-level features cannot out-predict a settled market number. Stop
attacking the magnitude.
The better goal: predict where the LINE is going, not where the margin
will land. The lineup channel (λ=0.141) is information the market
prices late — exploit the lag, not the level:
line_move_v1(backtestable NOW): predict the sign of
close_line − open_linefrom placement-time lineup features
(rapm_lineup_delta, availability loss, spine changes). Pre-registered
gates: directional accuracy ≥ 53% with z ≥ 2 on n ≥ 500 non-zero
moves; era slice 2023+ broken out. Historical open/close pairs
already support this.line_move_tuesday_v1(as tlwindow data accrues): same target
measured open → Tuesday-evening, the actionable window. Needs ~6
Tuesdays ofteamlist_windowcaptures before first evaluation.
A model that says "this line will move toward the home side today" is
directly bankable at the open price even though it cannot beat the
closing line's accuracy — betting the move IS the favourite-side/CLV
thesis applied to the line market.
Explicit non-goals
No H2H feature work (frozen). No totals model work under this roadmap
(cpois retired; totals revisit only inside v2b's variance output). No
adjusting line_ridge_core itself — it is the fixed benchmark.