Claude-skill-registry adding-new-sport
/mnt/data2/nhlstats/.github/skills/adding-new-sport/SKILL.md
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/adding-new-sport" ~/.claude/skills/majiayu000-claude-skill-registry-adding-new-sport && rm -rf "$T"
manifest:
skills/data/adding-new-sport/SKILL.mdsource content
/mnt/data2/nhlstats/.github/skills/adding-new-sport/SKILL.md
1: --- 2: name: adding-new-sport 3: description: Instructions and checklist for integrating a new sport into the betting system, including unified Elo ratings (inheriting from BaseEloRating), game downloading, and DAG configuration. 4: version: 2.1.0 5: --- 6: 7: # Adding a New Sport 8: 9: ## Overview **Status**: The unified Elo rating interface (`BaseEloRating`) is now **COMPLETED** and ready for new sports. All 9 existing sports have been successfully refactored to use this interface. 10: 11: This guide explains how to add a new sport to the multi-sport betting system. All new sport Elo implementations must inherit from the unified `BaseEloRating` interface. 12: 13: ## Required Files 14: 15: ### 1. Elo Rating Module (Unified Interface) 16: **Location:** `plugins/elo/{sport}_elo_rating.py` 17: 18: ```python 19: """ 20: {Sport} Elo Rating System. 21: 22: Production-ready Elo rating system for {Sport} predictions. 23: Inherits from BaseEloRating for unified interface. 24: """ 25: 26: from typing import Dict, Union 27: from .base_elo_rating import BaseEloRating 28: 29: 30: class {Sport}EloRating(BaseEloRating): 31: """ 32: {Sport}-specific Elo rating system. 33: 34: Inherits from BaseEloRating for unified interface. 35: """ 36: 37: def __init__(self, k_factor: float = 20.0, home_advantage: float = 100.0, initial_rating: float = 1500.0): 38: """ 39: Initialize {Sport} Elo rating system. 40: 41: Args: 42: k_factor: How quickly ratings change (20 is standard) 43: home_advantage: Elo points added for home field 44: initial_rating: Starting rating for new teams (1500 is standard) 45: """ 46: super().__init__(k_factor=k_factor, home_advantage=home_advantage, initial_rating=initial_rating) 47: 48: def predict(self, home_team: str, away_team: str, is_neutral: bool = False) -> float: 49: """ 50: Predict probability of home team winning. 51: 52: Args: 53: home_team: Name of home team 54: away_team: Name of away team 55: is_neutral: Whether the game is at a neutral site (no home advantage) 56: 57: Returns: 58: float: Probability of home win (0.0 to 1.0) 59: """ 60: # Get base ratings 61: rh = self.get_rating(home_team) 62: ra = self.get_rating(away_team) 63: 64: # Apply home advantage if not neutral 65: if not is_neutral: 66: rh = self._apply_home_advantage(rh, is_neutral=False) 67: 68: # Calculate expected score 69: return self.expected_score(rh, ra) 70: 71: def update( 72: self, 73: home_team: str, 74: away_team: str, 75: home_won: Union[bool, float], 76: is_neutral: bool = False 77: ) -> None: 78: """ 79: Update Elo ratings after a game result. 80: 81: Args: 82: home_team: Name of home team 83: away_team: Name of away team 84: home_won: Whether home team won (True/False) or score margin (float) 85: is_neutral: Whether the game was at a neutral site 86: """ 87: # Get current ratings 88: rh = self.get_rating(home_team) 89: ra = self.get_rating(away_team) 90: 91: # Apply home advantage if not neutral 92: home_rating = rh 93: if not is_neutral: 94: home_rating = self._apply_home_advantage(rh, is_neutral=False) 95: 96: # Calculate expected score for home team 97: expected_home = self.expected_score(home_rating, ra) 98: 99: # Determine actual score based on home_won 100: if isinstance(home_won, bool): 101: actual_home = 1.0 if home_won else 0.0 102: else: 103: # For score margin, use appropriate transformation 104: # Example: logistic transformation for margin effects 105: actual_home = 1.0 / (1.0 + math.exp(-home_won)) 106: 107: # Calculate rating changes 108: home_change = self._calculate_rating_change(actual_home, expected_home) 109: 110: # Update ratings (conservation of points) 111: self.ratings[home_team] = rh + home_change 112: self.ratings[away_team] = ra - home_change 113: 114: def get_rating(self, team: str) -> float: 115: """ 116: Get current Elo rating for a team. 117: 118: Args: 119: team: Name of team 120: 121: Returns: 122: float: Current Elo rating 123: """ 124: if team not in self.ratings: 125: self.ratings[team] = self.initial_rating 126: return self.ratings[team] 127: 128: def expected_score(self, rating_a: float, rating_b: float) -> float: 129: """ 130: Calculate expected score (probability of team A winning). 131: 132: Uses standard Elo formula: 133: E_A = 1 / (1 + 10^((R_B - R_A) / 400)) 134: 135: Args: 136: rating_a: Rating of team A 137: rating_b: Rating of team B 138: 139: Returns: 140: float: Probability of team A winning (0.0 to 1.0) 141: """ 142: return 1.0 / (1.0 + 10.0 ** ((rating_b - rating_a) / 400.0)) 143: 144: def get_all_ratings(self) -> Dict[str, float]: 145: """ 146: Get all current ratings. 147: 148: Returns: 149: Dict[str, float]: Copy of all team ratings 150: """ 151: return self.ratings.copy() 152: 153: # Optional: Add sport-specific methods 154: # def sport_specific_method(self, ...): 155: # """Sport-specific functionality.""" 156: ``` 157: 158: **Key Requirements:** 159: 1. Must inherit from `BaseEloRating` 160: 2. Must implement all 5 abstract methods 161: 3. Must call `super().__init__()` in constructor 162: 4. Should use `_apply_home_advantage()` and `_calculate_rating_change()` helper methods 163: 164: ### 2. Games Downloader 165: **Location:** `plugins/{sport}_games.py` 166: 167: ```python 168: """ 169: {Sport} game data downloader. 170: 171: Downloads game data from official API and loads into database. 172: """ 173: 174: import pandas as pd 175: from typing import List, Dict 176: from datetime import datetime 177: 178: def download_{sport}_games(date: str = None) -> List[Dict]: 179: """ 180: Download {sport} games from API. 181: 182: Args: 183: date: Optional date string (YYYY-MM-DD) to filter games 184: 185: Returns: 186: List of game dictionaries with standardized format 187: """ 188: # Fetch from official API 189: # Parse into standard format 190: games = [] 191: 192: # Standard game format: 193: # { 194: # 'game_id': '{SPORT}_{YYYYMMDD}_{HOME}_{AWAY}', 195: # 'sport': '{sport}', 196: # 'date': 'YYYY-MM-DD', 197: # 'home_team': 'Home Team Name', 198: # 'away_team': 'Away Team Name', 199: # 'home_score': int, 200: # 'away_score': int, 201: # 'result': 'H'/'A'/'D' (or appropriate for sport) 202: # } 203: 204: return games 205: 206: def load_{sport}_games_to_db(games: List[Dict]): 207: """ 208: Load games into unified_games table. 209: 210: Args: 211: games: List of game dictionaries 212: """ 213: from db_manager import default_db 214: 215: if not games: 216: return 217: 218: # Convert to DataFrame 219: df = pd.DataFrame(games) 220: 221: # Ensure proper column types 222: # Insert into database 223: default_db.insert_df('unified_games', df, if_exists='append') 224: 225: print(f"✓ Loaded {len(games)} {sport} games to database") 226: ``` 227: 228: ### 3. Update Elo Package Exports 229: **Location:** `plugins/elo/__init__.py` 230: 231: Add import to the `__init__.py` file: 232: 233: ```python 234: # ... existing imports 235: from .{sport}_elo_rating import {Sport}EloRating 236: 237: __all__ = [ 238: # ... existing exports 239: '{Sport}EloRating', 240: ] 241: ``` 242: 243: ### 4. DAG Configuration 244: Add to `SPORTS_CONFIG` in `dags/multi_sport_betting_workflow.py`: 245: 246: ```python 247: SPORTS_CONFIG = { 248: # ... existing sports 249: "{sport}": { 250: "elo_class": "{Sport}EloRating", # Class name (not module) 251: "games_module": "{sport}_games", 252: "kalshi_function": "fetch_{sport}_markets", 253: "elo_threshold": 0.70, # Tune with lift/gain analysis 254: "k_factor": 20.0, # Sport-specific parameters 255: "home_advantage": 100.0, 256: }, 257: } 258: ``` 259: 260: ### 5. Kalshi Market Function 261: Add to `plugins/kalshi_markets.py`: 262: 263: ```python 264: def fetch_{sport}_markets() -> List[Dict]: 265: """ 266: Fetch {SPORT} markets from Kalshi. 267: 268: Returns: 269: List of market dictionaries 270: """ 271: markets = [] 272: 273: try: 274: # Example for Kalshi API 275: response = kalshi_client.get_markets( 276: series_ticker="{SPORT}WIN", # Adjust based on sport 277: status="open", 278: limit=100 279: ) 280: 281: markets = response.get("markets", []) 282: 283: except Exception as e: 284: print(f"⚠️ Failed to fetch {sport} markets: {e}") 285: 286: return markets 287: ``` 288: 289: ### 6. Team Name Mappings 290: Update `plugins/naming_resolver.py`: 291: 292: ```python 293: # Add to TEAM_MAPPINGS dictionary 294: TEAM_MAPPINGS.update({ 295: "{sport}": { 296: "API_TEAM_NAME": "DISPLAY_TEAM_NAME", 297: # ... all teams for this sport 298: } 299: }) 300: ``` 301: 302: ## Game ID Format 303: 304: Consistent format: `{SPORT}_{YYYYMMDD}_{HOME}_{AWAY}` 305: 306: ```python 307: def generate_game_id(sport: str, date: str, home: str, away: str) -> str: 308: """ 309: Generate consistent game ID. 310: 311: Args: 312: sport: Sport abbreviation (lowercase) 313: date: Date string (YYYY-MM-DD) 314: home: Home team name 315: away: Away team name 316: 317: Returns: 318: Game ID string 319: """ 320: date_str = date.replace('-', '') 321: home_slug = "".join(filter(str.isalnum, home)).upper() 322: away_slug = "".join(filter(str.isalnum, away)).upper() 323: return f"{sport.upper()}_{date_str}_{home_slug}_{away_slug}" 324: ``` 325: 326: ## TDD Approach for New Sport 327: 328: ### 1. Create TDD Test File 329: **Location:** `tests/test_{sport}_elo_tdd.py` 330: 331: ```python 332: """ 333: Test suite for {Sport}EloRating using TDD approach. 334: Tests the new {Sport}EloRating that inherits from BaseEloRating. 335: """ 336: 337: import pytest 338: import sys 339: import os 340: sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 341: 342: from plugins.elo import BaseEloRating, {Sport}EloRating 343: 344: 345: class Test{Sport}EloRatingTDD: 346: """Test {Sport}EloRating implementation using TDD.""" 347: 348: def test_{sport}_elo_inherits_from_base(self): 349: """Test that {Sport}EloRating inherits from BaseEloRating.""" 350: assert issubclass({Sport}EloRating, BaseEloRating) 351: 352: def test_{sport}_elo_has_required_methods(self): 353: """Test that {Sport}EloRating implements all abstract methods.""" 354: elo = {Sport}EloRating() 355: 356: # Check all abstract methods exist 357: assert hasattr(elo, 'predict') 358: assert hasattr(elo, 'update') 359: assert hasattr(elo, 'get_rating') 360: assert hasattr(elo, 'expected_score') 361: assert hasattr(elo, 'get_all_ratings') 362: 363: def test_{sport}_elo_basic_functionality(self): 364: """Test basic Elo functionality.""" 365: elo = {Sport}EloRating(k_factor=20, home_advantage=100, initial_rating=1500) 366: 367: # Test rating initialization 368: assert elo.get_rating("TeamA") == 1500 369: assert elo.get_rating("TeamB") == 1500 370: 371: # Test prediction 372: prob = elo.predict("TeamA", "TeamB") 373: assert 0 <= prob <= 1 374: 375: # Test update 376: initial_rating = elo.get_rating("TeamA") 377: elo.update("TeamA", "TeamB", home_won=True) 378: new_rating = elo.get_rating("TeamA") 379: assert new_rating > initial_rating 380: 381: def test_{sport}_elo_get_all_ratings(self): 382: """Test get_all_ratings method.""" 383: elo = {Sport}EloRating() 384: 385: # Add some ratings 386: elo.update("TeamA", "TeamB", home_won=True) 387: elo.update("TeamC", "TeamD", home_won=False) 388: 389: all_ratings = elo.get_all_ratings() 390: 391: assert isinstance(all_ratings, dict) 392: assert "TeamA" in all_ratings 393: assert "TeamB" in all_ratings 394: assert "TeamC" in all_ratings 395: assert "TeamD" in all_ratings 396: assert len(all_ratings) == 4 397: 398: 399: if __name__ == "__main__": 400: pytest.main([__file__, "-v"]) 401: ``` 402: 403: ### 2. Run TDD Cycle 404: 1. **Red**: Run tests - they should fail (class doesn't exist yet) 405: 2. **Green**: Create the Elo class with minimal implementation 406: 3. **Refactor**: Add proper type hints, docstrings, error handling 407: 4. **Test**: Ensure all tests pass 408: 409: ## Validation Checklist 410: 411: - [ ] Elo class inherits from `BaseEloRating` 412: - [ ] All 5 abstract methods implemented 413: - [ ] Games download returns proper format 414: - [ ] Team names resolve correctly in naming resolver 415: - [ ] Kalshi markets parse properly 416: - [ ] Data loads to database without errors 417: - [ ] TDD tests pass with 100% coverage for Elo class 418: - [ ] DAG parses without errors 419: - [ ] Added to `plugins/elo/__init__.py` exports 420: - [ ] Updated `SPORTS_CONFIG` in DAG 421: 422: ## Testing 423: 424: ```bash 425: # Test DAG parsing 426: python dags/multi_sport_betting_workflow.py 427: 428: # Run sport-specific TDD tests 429: pytest tests/test_{sport}_elo_tdd.py -v 430: 431: # Check coverage for new Elo class 432: pytest --cov=plugins/elo/{sport}_elo_rating.py --cov-report=term-missing 433: 434: # Validate data integration 435: python -c "from data_validation import validate_{sport}_data; validate_{sport}_data().print_report()" 436: ``` 437: 438: ## Files to Reference 439: 440: - `plugins/elo/base_elo_rating.py` - Unified base class interface 441: - `plugins/elo/nba_elo_rating.py` - Template for Elo implementation 442: - `plugins/nba_games.py` - Template for games downloader 443: - `plugins/kalshi_markets.py` - Add Kalshi function here 444: - `tests/test_nba_elo_tdd.py` - Template for TDD tests 445: 446: ## Sport-Specific Considerations 447: 448: ### For Sports with Draws (Soccer) 449: - Implement `predict_3way()` and `predict_probs()` methods 450: - Add `legacy_update()` method for backward compatibility with 3-way outcomes 451: - Model draw probability based on rating difference 452: 453: ### For Score-Based Sports (MLB, NFL) 454: - Implement `update_with_scores()` method 455: - Consider margin of victory in rating changes 456: - Add `update_legacy()` method for backward compatibility 457: 458: ### For Individual Sports (Tennis) 459: - Set `home_advantage=0` (no home advantage) 460: - Consider surface-specific adjustments if needed 461: 462: ## Current Sport Implementations (Reference) 463: 464: **Successfully implemented:** 465: - `NHLEloRating` - NHL with recency weighting 466: - `NBAEloRating` - Canonical implementation 467: - `MLBEloRating` - MLB with score-based updates 468: - `NFLEloRating` - NFL with score-based updates 469: - `EPLEloRating` - EPL with 3-way outcomes 470: - `Ligue1EloRating` - Ligue1 with 3-way outcomes 471: 472: **In progress:** 473: - `NCAABEloRating` - College basketball 474: - `WNCAABEloRating` - Women's college basketball 475: - `TennisEloRating` - Tennis (no home advantage)