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)

  1. G-L1: walk-forward out-of-sample MAE < 13.46 (beats line_ridge_core).
  2. G-L2: walk-forward CLA on the LINE market ≥ 0.53 with z ≥ 2
    (the market must move toward the model's line disagreements).
  3. Joint: MAE improvement without CLA evidence is NOT promotion.
  4. 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:

  1. 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.
  2. 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.
  3. spine_lock_games_together — raw non-decayed count (VIF-checked vs #1;
    drop if > 5).

  4. 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:

  1. 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.
  2. 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-v2aline_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.

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_line from 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 of teamlist_window captures 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.