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
opensnapshot: collected Monday morning (or Monday after announcement for Thursday games)refreshsnapshots: every ~6 hours through the week- 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_clvcomputes the close from the last pre-kickoff
observation per book and only falls back toclose-labelled rows for
legacy data collected under the old protocol. - CLV =
(taken_price / closing_price) - 1for price CLV;open_line - closing_linefor line CLV - Critical: closing line is NEVER used as a model input. Post-hoc measurement only.
- 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
- Does H2H movement predict anything in NRL, where the line market is more efficient?
- Is totals steam more predictive in wet-weather games?
- Does the signal decay within a season (sharps get faded by the books)?
- Is velocity (pts/hr) a better threshold than abs movement?
These questions will be answered by accumulating paper trade data with CLV measurement.
Related files
- odds-movement-monitor.md — operational guide and schema
nrl-bet-advisor/data/odds_movement.py— core implementationtests/test_odds_movement_monitor.py— unit tests