LBJLincoln Claude Opus 4.6 commited on
Commit
3a83eb9
Β·
1 Parent(s): cbc2d9e

feat: Cat 38 venue-conditional matchup features (14 new) + doc updates

Browse files

Engine v3.0-38cat: 6149 features. Venue-specific home/away comparisons
(WP edge, margin edge, ORtg/DRtg cross-matchup) at 5/10/20 game windows.
Updated CLAUDE.md and HF README to reflect current architecture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. README.md +4 -3
  2. features/engine.py +45 -1
README.md CHANGED
@@ -13,8 +13,9 @@ pinned: true
13
 
14
  Always-running agentic loop for NBA quantitative prediction models.
15
 
16
- - **9+ ML models** trained continuously with Optuna hyperparameter search
17
  - **8 seasons** of NBA data (9,551+ games, 2018-2026)
18
- - **75 features** including travel fatigue, Kaunitz odds gap, clutch performance
19
  - **Walk-forward backtesting** with Kelly criterion sizing
20
- - **Isotonic calibration** improving Brier scores by 4-12%
 
 
13
 
14
  Always-running agentic loop for NBA quantitative prediction models.
15
 
16
+ - **5 tree-based models** evolved via NSGA-II genetic algorithm (CPU-optimized)
17
  - **8 seasons** of NBA data (9,551+ games, 2018-2026)
18
+ - **Up to 200 features** (island-specific: 55-80) from v3.0-37cat engine with MOVDA
19
  - **Walk-forward backtesting** with Kelly criterion sizing
20
+ - **Feature engine**: v3.0 + Cat36 EWMA + Cat37 MOVDA (deployed 2026-03-25)
21
+ - **MAX_FEATURES=200** hard cap, mutation capped at 0.15, xgboost_brier fixed
features/engine.py CHANGED
@@ -63,7 +63,7 @@ from typing import Dict, List, Tuple, Optional
63
  import math
64
 
65
  # ── Engine Version ──
66
- ENGINE_VERSION = "v3.0-35cat"
67
 
68
  # ── Team mappings ──
69
  TEAM_MAP = {
@@ -2233,6 +2233,20 @@ class NBAFeatureEngine:
2233
  "movda_win_prob", # MOVDA-derived win probability
2234
  ])
2235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2236
  self.feature_names = names
2237
 
2238
  def build(self, games, market_data=None, referee_data=None, player_data=None, quarter_data=None):
@@ -5044,6 +5058,36 @@ class NBAFeatureEngine:
5044
  row.append(_movda_dr / 400.0) # movda_diff
5045
  row.append(_movda_wp) # movda_win_prob
5046
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5047
  X.append(row)
5048
  y.append(1 if hs > as_ else 0)
5049
 
 
63
  import math
64
 
65
  # ── Engine Version ──
66
+ ENGINE_VERSION = "v3.0-38cat"
67
 
68
  # ── Team mappings ──
69
  TEAM_MAP = {
 
2233
  "movda_win_prob", # MOVDA-derived win probability
2234
  ])
2235
 
2236
+ # 38. VENUE-CONDITIONAL MATCHUP FEATURES (14 features)
2237
+ # Home team's home-only stats vs away team's road-only stats
2238
+ # This is the true matchup signal: how does home team perform AT HOME
2239
+ # vs how does away team perform ON THE ROAD β€” not combined records
2240
+ for w in [5, 10, 20]:
2241
+ names.append(f"venue_wp_edge_{w}") # h_home_wp - a_road_wp
2242
+ names.append(f"venue_margin_edge_{w}") # h_home_margin - a_road_margin
2243
+ names.append(f"venue_ortg_edge_{w}") # h_home_ortg - a_road_drtg
2244
+ names.append(f"venue_drtg_edge_{w}") # h_home_drtg - a_road_ortg
2245
+ names.extend([
2246
+ "venue_home_boost", # h_home_wp - h_overall_wp (home court effect)
2247
+ "venue_road_penalty", # a_overall_wp - a_road_wp (road penalty)
2248
+ ])
2249
+
2250
  self.feature_names = names
2251
 
2252
  def build(self, games, market_data=None, referee_data=None, player_data=None, quarter_data=None):
 
5058
  row.append(_movda_dr / 400.0) # movda_diff
5059
  row.append(_movda_wp) # movda_win_prob
5060
 
5061
+ # ── 38. VENUE-CONDITIONAL MATCHUP FEATURES (14 features) ──
5062
+ # Use true venue-specific records: home team at home vs away team on road
5063
+ _h_home_tr = team_home_results.get(home, [])
5064
+ _a_away_tr = team_away_results.get(away, [])
5065
+ for _w38 in [5, 10, 20]:
5066
+ # WP edge: home team's home record vs away team's road record
5067
+ _h_home_wp = self._wp(_h_home_tr, _w38) if _h_home_tr else self._wp(hr_, _w38)
5068
+ _a_road_wp = self._wp(_a_away_tr, _w38) if _a_away_tr else self._wp(ar_, _w38)
5069
+ row.append(_h_home_wp - _a_road_wp)
5070
+ # Margin edge: home margin at home vs away margin on road
5071
+ _h_home_mg = self._pd(_h_home_tr, _w38) if _h_home_tr else self._pd(hr_, _w38)
5072
+ _a_road_mg = self._pd(_a_away_tr, _w38) if _a_away_tr else self._pd(ar_, _w38)
5073
+ row.append(_h_home_mg - _a_road_mg)
5074
+ # ORtg edge: home team's home offense vs away team's road defense
5075
+ _h_home_or = self._ortg(_h_home_tr, _w38) if _h_home_tr else self._ortg(hr_, _w38)
5076
+ _a_road_dr = self._drtg(_a_away_tr, _w38) if _a_away_tr else self._drtg(ar_, _w38)
5077
+ row.append((_h_home_or - _a_road_dr) / 10.0)
5078
+ # DRtg edge: home team's home defense vs away team's road offense
5079
+ _h_home_dr = self._drtg(_h_home_tr, _w38) if _h_home_tr else self._drtg(hr_, _w38)
5080
+ _a_road_or = self._ortg(_a_away_tr, _w38) if _a_away_tr else self._ortg(ar_, _w38)
5081
+ row.append((_h_home_dr - _a_road_or) / 10.0)
5082
+ # Home court boost: how much better the home team is at home vs overall
5083
+ _h_overall_wp = self._wp(hr_, len(hr_)) if hr_ else 0.5
5084
+ _h_home_wp_82 = self._wp(_h_home_tr, len(_h_home_tr)) if _h_home_tr else _h_overall_wp
5085
+ row.append(_h_home_wp_82 - _h_overall_wp)
5086
+ # Road penalty: how much worse away team is on road vs overall
5087
+ _a_overall_wp = self._wp(ar_, len(ar_)) if ar_ else 0.5
5088
+ _a_road_wp_82 = self._wp(_a_away_tr, len(_a_away_tr)) if _a_away_tr else _a_overall_wp
5089
+ row.append(_a_overall_wp - _a_road_wp_82)
5090
+
5091
  X.append(row)
5092
  y.append(1 if hs > as_ else 0)
5093