Edge 5 — Movement Refinement v1

Status: hypothesis / paper-trading
Goal: discover a repeatable 5% ROI edge from NRL odds movement + CLV
Branch: refine


Hypothesis

NRL markets are semi-efficient but thin. Sudden line movement before kickoff often reflects:
1. Sharp syndicate activity (e.g., lineup leaks, injury news)
2. Weather/venue information not yet priced by recreational books
3. Model disagreement that resolves toward the moved-side outcome

If we consistently paper-trade the moved side and accumulate positive CLV, that is necessary evidence of an edge (though not sufficient — we still need to verify it survives variance and does not decay over time).


Strategies under observation

Strategy ID Market Trigger Trade
movement_total_2pt_v1 Totals abs(latest_line - open_line) ≥ 2.0 Follow steam (over/under per direction)
movement_spread_2pt_v1 Spreads abs(latest_home_line - open_home_line) ≥ 2.0 Follow steam (home/away per direction)
movement_h2h_3pct_v1 H2H No-vig prob shift ≥ 3% Bet team with increasing probability

All strategies require ≥ 2 bookmakers in the snapshot.


Promotion gates

A strategy progresses from paper to consideration when ALL of:
- N ≥ 100 settled paper trades
- ROI > +5%
- Average clv_price > 0 (median positive, not driven by outliers)
- No single round accounts for > 30% of profit

A strategy is promoted to watch when it has passed consideration AND:
- Performance holds for ≥ 2 full NRL seasons of paper data
- At least one filter (see below) remains consistently positive

No real bets are placed unless approved explicitly outside this document.


Planned filter overlays

Each filter can be combined with any strategy to narrow scope. Collect enough paper trades on the raw strategy first; filter analysis comes second.

Filter Description
model_agrees_with_movement LightGBM/Elo model also favours the moved side
model_disagrees_with_movement Fade: model disagrees, suggesting market overreaction
weather_contradicts_total_move Total moved up but wet conditions expected
lineup_disruption_flag Key player missing announced after open snapshot
key_spine_out_flag Halfback, hooker, or five-eighth missing
goal_kicker_missing_flag Primary goal kicker unavailable
short_turnaround_flag Team played < 6 days ago
post_origin_flag Week immediately after State of Origin
low_bookmaker_count_flag Only 1 bookmaker — signal quality suspect
late_market_only_flag First snapshot < 12 hours before kickoff
stale_snapshot_flag Latest snapshot > 6 hours old at detection time

Feature families tracked

Family Features
A — Line movement open_line, latest_line, line_move_points, abs_line_move_points, movement_strength_bucket
B — Price movement open_price, latest_price, price_move
C — Timing hours_since_open, hours_to_kickoff, first_seen_at_utc, latest_seen_at_utc
D — Market breadth bookmaker_count, snapshot_count
E — Velocity movement_velocity_pts_per_hr
F — CLV closing_line, closing_price, clv_line, clv_price, positive_clv_flag
G — Context season, round, kickoff_utc, home_team, away_team
H — Filters All flags listed above (collected separately, joined at analysis time)

CLV measurement protocol

  1. open snapshot: collected Monday morning (or Monday after announcement for Thursday games)
  2. refresh snapshots: every ~6 hours through the week
  3. Closing line = the last snapshot captured BEFORE each game's kickoff, per bookmaker.
    Collect a per-game snapshot in the final hour before every kickoff (label it
    close). A single post-round snapshot is NOT a closing line — The Odds API
    drops events at commence time, so Thursday's close cannot be captured on
    Sunday. update_movement_clv computes the close from the last pre-kickoff
    observation per book and only falls back to close-labelled rows for
    legacy data collected under the old protocol.
  4. CLV = (taken_price / closing_price) - 1 for price CLV; open_line - closing_line for line CLV
  5. Critical: closing line is NEVER used as a model input. Post-hoc measurement only.
  6. Raw per-bookmaker prices are the source of truth. Consensus averages are for
    display; a taken price should be a named book's actual price wherever possible.

Current data status

Item Status
movement_odds_snapshots table Created (schema ready, no data yet)
movement_paper_trades table Created (schema ready, no data yet)
movement_total_2pt_v1 strategy Implemented, awaiting first snapshots
movement_spread_2pt_v1 strategy Implemented, awaiting first snapshots
movement_h2h_3pct_v1 strategy Implemented, awaiting first snapshots
Filter flags Scaffolded (columns exist in table), not yet populated

Backfill plan

The Odds API does not provide historical odds. Going forward only.

For retrospective analysis (if needed), consider:
- Betfair historical data (available via Betfair API, priced separately)
- OddsPortal scrape (HTML parsing, fragile)

Neither is in scope for this phase. Start paper-trading from Round 14 2026 onward.


Open questions

  1. Does H2H movement predict anything in NRL, where the line market is more efficient?
  2. Is totals steam more predictive in wet-weather games?
  3. Does the signal decay within a season (sharps get faded by the books)?
  4. Is velocity (pts/hr) a better threshold than abs movement?

These questions will be answered by accumulating paper trade data with CLV measurement.


  • odds-movement-monitor.md — operational guide and schema
  • nrl-bet-advisor/data/odds_movement.py — core implementation
  • tests/test_odds_movement_monitor.py — unit tests