"""
Random Forest + CLV combined strategy.
Walk-forward: train RF on prior seasons to predict away win probability,
then bet only when BOTH model pA >= threshold AND CLV > 5%.
"""
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.calibration import CalibratedClassifierCV
import warnings
warnings.filterwarnings('ignore')

START       = 1_000.0
CLV_THRESH  = 0.05
PA_THRESH   = 0.31   # model confidence threshold (from old pipeline)
KELLY_MULTS = [0.10, 0.25, 0.50]

seasons_map = {
    2017: '1718', 2018: '1819', 2019: '1920', 2020: '2021',
    2021: '2122', 2022: '2223', 2023: '2324', 2024: '2425',
}
OOS_API_SEASONS = [2021, 2022, 2023, 2024]  # = 2122, 2223, 2324, 2425

league_names = {
    'E0': 'EPL', 'E1': 'Championship', 'E2': 'League One',
    'SP1': 'La Liga', 'SP2': 'Segunda',
    'I1': 'Serie A', 'I2': 'Serie B',
    'D1': 'Bundesliga', 'D2': '2.Bundesliga',
    'F1': 'Ligue 1',
    'N1': 'Eredivisie',
    'B1': 'Belgian Pro League',
    'T1': 'Super Lig',
    'G1': 'Greek SL',
    'SC0': 'Scottish Prem',
}

def remove_margin(h, d, a):
    over = 1/h + 1/d + 1/a
    return 1/h/over, 1/d/over, 1/a/over

# ── Load features ────────────────────────────────────────────────────────────
print("Loading features...")
feat = pd.read_csv('E:/soccer/raw_api/match_features.csv')
feat['date'] = pd.to_datetime(feat['date'])

# Pivot: one row per match with home_ and away_ features
w5_cols = [c for c in feat.columns if '_w5' in c]
w10_cols = [c for c in feat.columns if '_w10' in c]
feature_cols = w5_cols + w10_cols

home_feat = feat[feat['is_home'] == 1][['fixture_id', 'date', 'div', 'season',
    'home_team', 'away_team', 'home_goals', 'away_goals'] + feature_cols].copy()
home_feat.columns = ['fixture_id', 'date', 'div', 'season',
    'home_team', 'away_team', 'home_goals', 'away_goals'] + [f'h_{c}' for c in feature_cols]

away_feat = feat[feat['is_home'] == 0][['fixture_id'] + feature_cols].copy()
away_feat.columns = ['fixture_id'] + [f'a_{c}' for c in feature_cols]

matches = home_feat.merge(away_feat, on='fixture_id', how='inner')
matches['away_win'] = (matches['away_goals'] > matches['home_goals']).astype(int)
print(f"  {len(matches):,} matches with features")

# ── Load odds from league CSVs ──────────────────────────────────────────────
print("Loading odds...")
csv_seasons = ['1718','1819','1920','2021','2122','2223','2324','2425','2526']
all_odds = []
for code in league_names.keys():
    for s in csv_seasons:
        path = f'E:/soccer/league_data/{code}_{s}.csv'
        try:
            d = pd.read_csv(path, encoding='utf-8-sig')
            if 'B365A' not in d.columns or 'PSCH' not in d.columns:
                continue
            d = d.dropna(subset=['FTR','B365A','B365H','B365D','PSCH','PSCD','PSCA'])
            d['Date'] = pd.to_datetime(d['Date'], dayfirst=True, errors='coerce')
            d = d.dropna(subset=['Date'])
            d['div'] = code
            all_odds.append(d)
        except:
            pass

odds_df = pd.concat(all_odds, ignore_index=True)
# Compute fair odds and CLV
tH, tD, tA = zip(*odds_df.apply(lambda r: remove_margin(r['PSCH'],r['PSCD'],r['PSCA']), axis=1))
odds_df['p_A'] = pd.Series(tA, index=odds_df.index)
odds_df['PSC_fair_A'] = 1 / odds_df['p_A']
odds_df['edge_A'] = (odds_df['B365A'] - odds_df['PSC_fair_A']) / odds_df['PSC_fair_A']
print(f"  {len(odds_df):,} matches with odds")

# ── Merge features with odds ────────────────────────────────────────────────
print("Merging features with odds...")
# Match on date + home team + away team + league
matches['date_str'] = matches['date'].dt.strftime('%Y-%m-%d')
odds_df['date_str'] = odds_df['Date'].dt.strftime('%Y-%m-%d')

# Normalize team names for matching
TEAM_MAP = {
    # API-Football name -> football-data name
    'manchester city': 'man city', 'manchester united': 'man united',
    'nottingham forest': "nott'm forest", 'tottenham hotspur': 'tottenham',
    'newcastle united': 'newcastle', 'wolverhampton wanderers': 'wolves',
    'west ham united': 'west ham', 'leicester city': 'leicester',
    'leeds united': 'leeds', 'brighton & hove albion': 'brighton',
    'brighton hove albion': 'brighton', 'afc bournemouth': 'bournemouth',
    'sheffield united': 'sheffield united', 'burnley fc': 'burnley',
    'crystal palace': 'crystal palace',
    'atlético madrid': 'ath madrid', 'atletico madrid': 'ath madrid',
    'athletic club': 'ath bilbao', 'athletic bilbao': 'ath bilbao',
    'real betis': 'betis', 'real sociedad': 'sociedad',
    'real valladolid': 'valladolid', 'celta vigo': 'celta',
    'deportivo alavés': 'alaves', 'deportivo alaves': 'alaves',
    'rayo vallecano': 'vallecano', 'cádiz': 'cadiz',
    'almería': 'almeria', 'las palmas': 'las palmas',
    'fc barcelona': 'barcelona', 'real madrid': 'real madrid',
    'paris saint germain': 'paris sg', 'as monaco': 'monaco',
    'olympique lyonnais': 'lyon', 'olympique de marseille': 'marseille',
    'as saint-étienne': 'st etienne', 'rc strasbourg alsace': 'strasbourg',
    'stade rennais fc': 'rennes', 'stade de reims': 'reims',
    'rc lens': 'lens', 'losc lille': 'lille',
    'fc nantes': 'nantes', 'girondins de bordeaux': 'bordeaux',
    'angers sco': 'angers', 'clermont foot': 'clermont foot',
    'estac troyes': 'troyes', 'toulouse fc': 'toulouse',
    'fc lorient': 'lorient', 'ac ajaccio': 'ajaccio',
    'stade brestois 29': 'brest', 'montpellier hsc': 'montpellier',
    'ogc nice': 'nice', 'le havre ac': 'le havre',
    'borussia dortmund': 'dortmund', 'borussia mönchengladbach': "m'gladbach",
    'borussia monchengladbach': "m'gladbach",
    'bayern munich': 'bayern munich', 'bayer 04 leverkusen': 'leverkusen',
    'bayer leverkusen': 'leverkusen',
    'rb leipzig': 'rb leipzig', 'eintracht frankfurt': 'ein frankfurt',
    'vfb stuttgart': 'stuttgart', 'vfl wolfsburg': 'wolfsburg',
    'sc freiburg': 'freiburg', '1. fc union berlin': 'union berlin',
    '1. fc köln': 'fc koln', '1. fc koln': 'fc koln', '1. fc k\x94ln': 'fc koln',
    'tsg hoffenheim': 'hoffenheim', '1899 hoffenheim': 'hoffenheim',
    'fc augsburg': 'augsburg', 'sv werder bremen': 'werder bremen',
    'werder bremen': 'werder bremen',
    '1. fc heidenheim': 'heidenheim', '1. fc heidenheim 1846': 'heidenheim',
    'hertha berlin': 'hertha', 'hertha bsc': 'hertha',
    'arminia bielefeld': 'bielefeld', 'vfl bochum': 'bochum',
    'fc schalke 04': 'schalke 04', 'greuther fürth': 'greuther furth',
    'greuther furth': 'greuther furth',
    'fortuna düsseldorf': 'fortuna dusseldorf', 'fortuna dusseldorf': 'fortuna dusseldorf',
    'darmstadt 98': 'darmstadt', 'sv darmstadt 98': 'darmstadt',
    'holstein kiel': 'holstein kiel', 'fc st. pauli': 'st pauli',
    'inter': 'inter', 'internazionale': 'inter',
    'ac milan': 'milan', 'as roma': 'roma',
    'ssc napoli': 'napoli', 'ss lazio': 'lazio',
    'juventus': 'juventus', 'atalanta': 'atalanta',
    'us sassuolo calcio': 'sassuolo', 'hellas verona': 'verona',
    'bologna fc 1909': 'bologna', 'bologna': 'bologna',
    'udinese': 'udinese', 'uc sampdoria': 'sampdoria',
    'genoa': 'genoa', 'us salernitana 1919': 'salernitana',
    'torino': 'torino', 'acf fiorentina': 'fiorentina',
    'us lecce': 'lecce', 'empoli': 'empoli',
    'spezia': 'spezia', 'us cremonese': 'cremonese',
    'cagliari': 'cagliari', 'venezia': 'venezia', 'frosinone': 'frosinone',
    'monza': 'monza', 'como': 'como', 'parma': 'parma',
    'ajax': 'ajax', 'feyenoord': 'feyenoord', 'psv eindhoven': 'psv',
    'az alkmaar': 'az alkmaar', 'az': 'az alkmaar',
    'fc twente': 'twente', 'sc heerenveen': 'heerenveen',
    'fc utrecht': 'fc utrecht', 'fc groningen': 'groningen',
    'vitesse': 'vitesse', 'sparta rotterdam': 'sparta rotterdam',
    'rkc waalwijk': 'waalwijk', 'nec nijmegen': 'nec',
    'sc cambuur': 'cambuur', 'go ahead eagles': 'go ahead eagles',
    'fc volendam': 'volendam', 'excelsior': 'excelsior',
    'almere city fc': 'almere city', 'fortuna sittard': 'fort sittard',
    'pec zwolle': 'pec zwolle', 'heracles almelo': 'heracles',
    'willem ii': 'willem ii', 'nac breda': 'nac breda',
    'club brugge kv': 'club brugge', 'royal antwerp': 'antwerp',
    'anderlecht': 'anderlecht', 'krc genk': 'genk',
    'standard liège': 'standard', 'standard liege': 'standard',
    'kaa gent': 'gent', 'oh leuven': 'oh leuven',
    'union saint-gilloise': 'st gilloise', 'union st. gilloise': 'st gilloise',
    'cercle brugge': 'cercle brugge', 'kv mechelen': 'mechelen',
    'charleroi': 'charleroi', 'kv kortrijk': 'kortrijk',
    'sint-truidense vv': 'st truiden', 'rwdm': 'rwdm',
    'as eupen': 'eupen', 'beerschot va': 'beerschot',
    'seraing': 'seraing', 'waasland-beveren': 'waasland',
    'westerlo': 'westerlo', 'fc dender': 'dender',
    'celtic': 'celtic', 'rangers': 'rangers',
    'aberdeen': 'aberdeen', 'heart of midlothian': 'hearts',
    'hearts': 'hearts', 'hibernian': 'hibernian',
    'motherwell': 'motherwell', 'st. johnstone': 'st johnstone',
    'dundee': 'dundee', 'livingston': 'livingston',
    'ross county': 'ross county', 'kilmarnock': 'kilmarnock',
    'st. mirren': 'st mirren', 'dundee united': 'dundee utd',
    'galatasaray': 'galatasaray', 'fenerbahce': 'fenerbahce',
    'fenerbahçe': 'fenerbahce', 'besiktas': 'besiktas', 'beşiktaş': 'besiktas',
    'trabzonspor': 'trabzonspor', 'istanbul basaksehir': 'basaksehir',
    'istanbul başakşehir': 'basaksehir',
    'adana demirspor': 'adana demirspor', 'antalyaspor': 'antalyaspor',
    'sivasspor': 'sivasspor', 'kayserispor': 'kayserispor',
    'alanyaspor': 'alanyaspor', 'konyaspor': 'konyaspor',
    'kasimpasa': 'kasimpasa', 'kasımpaşa': 'kasimpasa',
    'gaziantep fk': 'gaziantep', 'ankaragücü': 'ankaragucu', 'ankaragucu': 'ankaragucu',
    'hatayspor': 'hatayspor', 'giresunspor': 'giresunspor',
    'ümraniyespor': 'umraniye', 'istanbulspor': 'istanbulspor',
    'pendikspor': 'pendikspor', 'rizespor': 'rizespor',
    'samsunspor': 'samsunspor', 'bodrum fk': 'bodrum fk',
    'eyüpspor': 'eyupspor', 'eyupspor': 'eyupspor',
    'olympiacos piraeus': 'olympiakos', 'panathinaikos': 'panathinaikos',
    'paok thessaloniki fc': 'paok', 'aris thessalonikis': 'aris',
    'aek athens fc': 'aek', 'volos nfc': 'volos',
    'asteras tripolis': 'asteras tripolis', 'ionikos': 'ionikos',
    'ofi': 'ofi', 'atromitos': 'atromitos', 'pas giannina': 'giannina',
    'lamia': 'lamia', 'levadiakos': 'levadiakos',
    'panserraikos': 'panserraikos', 'kallithea': 'kallithea',
    # Bundesliga extras
    'fsv mainz 05': 'mainz', 'mainz 05': 'mainz', '1. fsv mainz 05': 'mainz',
    'sc paderborn 07': 'paderborn', 'fc nurnberg': 'nurnberg', '1. fc nürnberg': 'nurnberg',
    'hannover 96': 'hannover', 'bayern münchen': 'bayern munich', 'bayern m\x81nchen': 'bayern munich',
    'dynamo dresden': 'dresden', 'jahn regensburg': 'regensburg',
    'fc kaiserslautern': 'kaiserslautern', '1. fc kaiserslautern': 'kaiserslautern',
    'hamburger sv': 'hamburger sv', 'karlsruher sc': 'karlsruhe',
    'fc magdeburg': 'magdeburg', '1. fc magdeburg': 'magdeburg',
    'eintracht braunschweig': 'braunschweig', 'fc erzgebirge aue': 'erzgebirge aue',
    'ssv ulm 1846': 'ulm', 'preussen munster': 'preussen munster',
    'fc elversberg': 'elversberg',
    # Greek extras
    'apollon smirnis': 'apollon', 'olympiakos piraeus': 'olympiakos',
    # Dutch extras
    'roda': 'roda', 'de graafschap': 'de graafschap', 'fc eindhoven': 'eindhoven',
    'ado den haag': 'den haag',
    # Italian extras
    'chievo': 'chievo',
    # Scottish extras
    'hamilton academical': 'hamilton',
    # Spanish extras
    'deportivo la coruna': 'la coruna', 'espanyol': 'espanol',
    'real sociedad ii': 'sociedad b', 'villarreal ii': 'villarreal b',
    'barcelona b': 'barcelona b', 'rayo majadahonda': 'majadahonda',
    # French extras
    'saint etienne': 'st etienne', 'guingamp': 'guingamp', 'caen': 'caen',
    # Championship / League One / League Two additions
    'west bromwich albion': 'west brom', 'queens park rangers': 'qpr',
    'sheffield wednesday': 'sheffield weds',
    'swansea city': 'swansea', 'stoke city': 'stoke',
    'hull city': 'hull', 'cardiff city': 'cardiff',
    'coventry city': 'coventry', 'norwich city': 'norwich',
    'watford': 'watford', 'blackburn rovers': 'blackburn',
    'bristol city': 'bristol city', 'millwall': 'millwall',
    'middlesbrough': 'middlesbrough', 'sunderland': 'sunderland',
    'preston north end': 'preston', 'luton town': 'luton',
    'plymouth argyle': 'plymouth', 'ipswich town': 'ipswich',
    'rotherham united': 'rotherham', 'huddersfield town': 'huddersfield',
    'birmingham city': 'birmingham', 'reading': 'reading',
    'blackpool': 'blackpool', 'wigan athletic': 'wigan',
    'barnsley': 'barnsley', 'derby county': 'derby',
    'peterborough united': 'peterborough', 'portsmouth': 'portsmouth',
    'bolton wanderers': 'bolton', 'oxford united': 'oxford utd',
    'charlton athletic': 'charlton', 'exeter city': 'exeter',
    'port vale': 'port vale', 'shrewsbury town': 'shrewsbury',
    'fleetwood town': 'fleetwood town', 'cambridge united': 'cambridge utd',
    'cheltenham town': 'cheltenham', 'burton albion': 'burton',
    'wycombe wanderers': 'wycombe', 'morecambe': 'morecambe',
    'accrington st': 'accrington', 'accrington stanley': 'accrington',
    'lincoln city': 'lincoln', 'mk dons': 'mk dons',
    'milton keynes dons': 'mk dons',
    # Segunda
    'racing santander': 'racing sant', 'real zaragoza': 'zaragoza',
    'real oviedo': 'oviedo', 'sd huesca': 'huesca',
    'racing ferrol': 'ferrol', 'fc cartagena': 'cartagena',
    'cd mirandés': 'mirandes', 'cd mirandes': 'mirandes',
    'sd eibar': 'eibar', 'cd tenerife': 'tenerife',
    'cd leganés': 'leganes', 'cd leganes': 'leganes',
    'elche': 'elche', 'málaga': 'malaga', 'malaga': 'malaga',
    'albacete': 'albacete', 'burgos cf': 'burgos',
    'ponferradina': 'ponferradina', 'lugo': 'lugo',
    'ibiza': 'ibiza', 'villarreal b': 'villarreal b',
    'ud las palmas': 'las palmas',
    'sporting gijón': 'sp gijon', 'sporting gijon': 'sp gijon',
    'granada cf': 'granada', 'levante': 'levante',
    'real sporting de gijón': 'sp gijon',
    # Serie B
    'reggina': 'reggina', 'perugia': 'perugia',
    'como 1907': 'como', 'spal': 'spal', 'benevento': 'benevento',
    'brescia': 'brescia', 'ascoli': 'ascoli',
    'ternana': 'ternana', 'cosenza': 'cosenza',
    'cittadella': 'cittadella', 'pisa': 'pisa',
    'bari': 'bari', 'südtirol': 'sudtirol', 'sudtirol': 'sudtirol',
    'palermo': 'palermo', 'catanzaro': 'catanzaro',
    'modena': 'modena', 'sampdoria': 'sampdoria',
    'reggiana': 'reggiana', 'juve stabia': 'juve stabia',
    'mantova': 'mantova', 'cesena': 'cesena',
    'carrarese': 'carrarese',
}

def norm(s):
    import unicodedata, re
    s = str(s).lower().strip()
    # Remove accents
    s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').decode('ascii')
    if s in TEAM_MAP:
        return TEAM_MAP[s]
    # Try without FC/AFC/SC prefix/suffix
    s2 = re.sub(r'\b(fc|afc|sc|cf|cd|sd|ss|us|uc|rc|ac|as|og|rk|sv|vf[blr]|tsg|ssv|1\.|ssc)\b', '', s).strip()
    s2 = re.sub(r'\s+', ' ', s2).strip()
    if s2 in TEAM_MAP:
        return TEAM_MAP[s2]
    return TEAM_MAP.get(s, s)

matches['h_norm'] = matches['home_team'].apply(norm)
matches['a_norm'] = matches['away_team'].apply(norm)
odds_df['h_norm'] = odds_df['HomeTeam'].apply(norm)
odds_df['a_norm'] = odds_df['AwayTeam'].apply(norm)

# Merge on date + league + normalized team names
merged = matches.merge(odds_df[['date_str','div','h_norm','a_norm','B365A','B365H','B365D',
    'p_A','PSC_fair_A','edge_A','FTR','FTHG','FTAG','HTHG','HTAG']],
    on=['date_str','div','h_norm','a_norm'], how='inner')

# Report unmatched for debugging
if len(merged) < len(matches) * 0.8:
    unmatched = matches[~matches['fixture_id'].isin(merged['fixture_id'])]
    um_teams = unmatched.groupby('div').apply(
        lambda g: list(set(g['h_norm'].unique()) - set(odds_df[odds_df['div']==g.name]['h_norm'].unique()))[:5]
    )
    print(f"  WARNING: Only {len(merged)/len(matches)*100:.0f}% matched. Unmatched team samples:")
    for div, teams in um_teams.items():
        if teams:
            safe = [t.encode('ascii', 'replace').decode() for t in teams]
            print(f"    {div}: {safe}")

print(f"  {len(merged):,} matched (features + odds)")

# ── Build model features ────────────────────────────────────────────────────
h_features = [f'h_{c}' for c in feature_cols]
a_features = [f'a_{c}' for c in feature_cols]

# Difference features: home - away
diff_features = []
for hc, ac in zip(h_features, a_features):
    diff_col = 'diff_' + hc[2:]  # strip h_ prefix
    merged[diff_col] = merged[hc] - merged[ac]
    diff_features.append(diff_col)

# Use both raw and diff features
X_cols = h_features + a_features + diff_features
print(f"  {len(X_cols)} model features")

# ── Walk-forward ─────────────────────────────────────────────────────────────
print("\nWalk-forward training...")
all_bets = []

for test_season in OOS_API_SEASONS:
    train = merged[merged['season'] < test_season].dropna(subset=X_cols)
    test  = merged[merged['season'] == test_season].dropna(subset=X_cols)

    if len(train) < 500 or len(test) < 50:
        print(f"  Season {test_season}: skipping (train={len(train)}, test={len(test)})")
        continue

    X_train = train[X_cols].values
    y_train = train['away_win'].values
    X_test  = test[X_cols].values

    # Train calibrated RF
    rf = RandomForestClassifier(n_estimators=300, max_depth=12, min_samples_leaf=20,
                                 random_state=42, n_jobs=-1)
    cal_rf = CalibratedClassifierCV(rf, cv=5, method='isotonic')
    cal_rf.fit(X_train, y_train)

    # Predict
    pA = cal_rf.predict_proba(X_test)[:, 1]
    test = test.copy()
    test['model_pA'] = pA

    season_str = seasons_map[test_season]
    n_total = len(test)
    n_clv = (test['edge_A'] > CLV_THRESH).sum()
    n_model = (test['model_pA'] >= PA_THRESH).sum()

    # Combined filter: model pA >= threshold AND CLV > 5%
    combined = test[(test['model_pA'] >= PA_THRESH) & (test['edge_A'] > CLV_THRESH)]
    # Also track CLV-only and model-only for comparison
    clv_only = test[test['edge_A'] > CLV_THRESH]
    model_only = test[test['model_pA'] >= PA_THRESH]

    n_combined = len(combined)

    for _, r in combined.iterrows():
        b = r['B365A'] - 1
        p = r['p_A']
        kelly_f = max((p * b - (1 - p)) / b, 0) if b > 0 else 0
        if kelly_f <= 0:
            continue
        clv_pct = (r['B365A'] - r['PSC_fair_A']) / r['PSC_fair_A'] * 100
        all_bets.append({
            'date':           r['date_str'],
            'season':         season_str,
            'league':         league_names.get(r['div'], r['div']),
            'league_code':    r['div'],
            'home_team':      r['home_team'],
            'away_team':      r['away_team'],
            'actual_result':  r['FTR'],
            'home_goals':     int(r['FTHG']) if pd.notna(r.get('FTHG')) else '',
            'away_goals':     int(r['FTAG']) if pd.notna(r.get('FTAG')) else '',
            'ht_home':        int(r['HTHG']) if pd.notna(r.get('HTHG')) else '',
            'ht_away':        int(r['HTAG']) if pd.notna(r.get('HTAG')) else '',
            'won':            int(r['FTR'] == 'A'),
            'odds':           round(r['B365A'], 2),
            'pinnacle_fair':  round(r['PSC_fair_A'], 3),
            'clv_pct':        round(clv_pct, 2),
            'model_pA':       round(r['model_pA'], 4),
            'kelly_fraction': round(kelly_f, 4),
            'p_win_pinnacle': round(p, 4),
        })

    # Season summary
    def flat_roi(df):
        if len(df) == 0: return 0, 0, 0
        pls = [(r['B365A']-1) if r['FTR']=='A' else -1 for _, r in df.iterrows()]
        return len(pls), sum(pls)/len(pls)*100, df['away_win'].mean()*100

    n_c, roi_c, wr_c = flat_roi(clv_only)
    n_m, roi_m, wr_m = flat_roi(model_only)
    n_b, roi_b, wr_b = flat_roi(combined)

    print(f"\n  Season {season_str} ({n_total} matches):")
    print(f"    CLV only (>5%):           n={n_c:>4}  WR={wr_c:5.1f}%  ROI={roi_c:+6.1f}%")
    print(f"    Model only (pA>={PA_THRESH}):      n={n_m:>4}  WR={wr_m:5.1f}%  ROI={roi_m:+6.1f}%")
    print(f"    Combined (CLV + model):   n={n_b:>4}  WR={wr_b:5.1f}%  ROI={roi_b:+6.1f}%")

bets = pd.DataFrame(all_bets).sort_values('date').reset_index(drop=True)
print(f"\n{'='*70}")
print(f"COMBINED STRATEGY: CLV >5% + RF model pA >= {PA_THRESH}")
print(f"{'='*70}")
print(f"  Total OOS bets: {len(bets):,}")
print(f"  Date range: {bets['date'].min()} to {bets['date'].max()}")
print(f"  Win rate: {bets['won'].mean()*100:.1f}%")

flat_pl = (bets['odds'] - 1).where(bets['won'] == 1, -1)
print(f"  Flat ROI: {flat_pl.mean()*100:+.2f}%")
print(f"  Total flat P&L: {flat_pl.sum():+.1f} units")

print(f"\n  By season:")
for s, grp in bets.groupby('season'):
    fp = (grp['odds'] - 1).where(grp['won'] == 1, -1)
    print(f"    {s}  n={len(grp):>4}  WR={grp['won'].mean()*100:.1f}%  ROI={fp.mean()*100:+.1f}%")

print(f"\n  By league (min 10 bets):")
lg = bets.groupby('league').agg(n=('won','count'), wr=('won','mean')).reset_index()
lg['roi'] = bets.groupby('league').apply(
    lambda g: ((g['odds']-1).where(g['won']==1, -1)).mean()*100).values
lg = lg[lg['n'] >= 10].sort_values('roi', ascending=False)
for _, r in lg.iterrows():
    print(f"    {r['league']:<22} n={int(r['n']):>4}  WR={r['wr']*100:.1f}%  ROI={r['roi']:+.1f}%")

# ── Kelly compounding ────────────────────────────────────────────────────────
print(f"\n  Kelly compounding (from {START:,.0f}):")
for km in KELLY_MULTS:
    bk = START; peak = START; max_dd = 0.0
    for _, row in bets.iterrows():
        f = min(row['kelly_fraction'] * km, 0.25)
        stake = bk * f
        pl = stake * (row['odds'] - 1) if row['won'] else -stake
        bk = max(bk + pl, 0)
        peak = max(peak, bk)
        max_dd = max(max_dd, (peak - bk) / peak)
    print(f"    {km:.2f}x Kelly:  {bk:>12,.2f}  ({bk/START:.1f}x)  MaxDD={max_dd*100:.1f}%")

# ── Save bets ────────────────────────────────────────────────────────────────
out = r'E:\soccer\bets_rf_clv.csv'
bets.to_csv(out, index=False)
print(f"\nSaved to {out}")
print("Done.")
