#!/usr/bin/env python3
"""
亚盘模型回测 - 分析高置信度投注策略
找出能够达到85%+准确率的投注条件
"""
import os, sys, json, warnings
from pathlib import Path
from collections import defaultdict
import numpy as np
import pandas as pd
import joblib
warnings.filterwarnings('ignore')

REPO_ROOT = Path('/root/.openclaw/workspace-flj_bot1/football-predictor-repro')
HIST_DIR = REPO_ROOT / 'historical_data'
AH_MODEL_DIR = REPO_ROOT / 'models/ah_models'

LEAGUE_INFO = {
    'e2': '英冠', 'e3': '英乙', 'e1': '英甲', 'd1': '德甲',
    'f1': '法甲', 'i1': '意甲', 'sp1': '西甲', 'sc1': '苏超',
    'tr1': '土超', 'p1': '葡超', 'e2n': '荷乙', 'cz1': '捷克甲',
}


def prob(odds):
    if odds and float(odds) > 1:
        return 1.0 / float(odds)
    return np.nan


def build_features(df, idx, team_stats, league_hist):
    """从原始df行提取特征（与训练时一致）"""
    row = df.iloc[idx]
    home = str(row['HomeTeam'])
    away = str(row['AwayTeam'])
    ahh = float(row['AHh'])

    def mean5(v):
        if not v: return 2.5
        return float(np.mean(v[-5:] if len(v) >= 5 else v))

    def mean3(v):
        if not v: return 2.5
        return float(np.mean(v[-3:] if len(v) >= 3 else v))

    def wr(v):
        if not v: return 0.5
        return float(np.mean(v))

    h = team_stats[home]
    a = team_stats[away]

    bm_home_prob = prob(row['B365AHH'])
    bm_away_prob = prob(row['B365AHA'])
    overround = bm_home_prob + bm_away_prob
    bm_home_net = bm_home_prob / overround if overround > 0 else 0.5

    return {
        'bm_ah_direction': 1 if bm_home_net > 0.5 else 0,
        'bm_ah_conf': abs(bm_home_net - 0.5) * 2,
        'bm_ah_balance': abs(float(row['B365AHH']) - float(row['B365AHA'])),
        'bm_ah_home_prob_net': bm_home_net,
        'bm_ah_away_prob_net': 1 - bm_home_net,
        'ahh': ahh,
        'ahh_abs': abs(ahh),
        'ah_strong_home': 1 if ahh <= -1.0 else 0,
        'ah_medium_home': 1 if -1.0 < ahh < 0 else 0,
        'ah_neutral': 1 if abs(ahh) <= 0.01 else 0,
        'ah_medium_away': 1 if 0 < ahh <= 0.75 else 0,
        'ah_strong_away': 1 if ahh > 0.75 else 0,
        'bm_3way_direction': 1 if prob(row.get('B365H', 2.0)) > prob(row.get('B365A', 2.0)) else 0,
        'bm_3way_home_prob': prob(row.get('B365H', 2.0)),
        'home_gf_avg': mean5(h['gf']) if h['gf'] else 1.5,
        'home_ga_avg': mean5(h['ga']) if h['ga'] else 1.5,
        'home_tot_avg': mean5(h['tot']) if h['tot'] else 2.5,
        'home_over_rate': wr(h['over'][-10:] if h['over'] else []),
        'home_gf_last3': mean3(h['gf']) if h['gf'] else 1.5,
        'home_ga_last3': mean3(h['ga']) if h['ga'] else 1.5,
        'home_over_last5': wr(h['over'][-5:] if h['over'] else []),
        'home_home_gf': mean5(h['home_gf']) if h['home_gf'] else 1.5,
        'home_home_ga': mean5(h['home_ga']) if h['home_ga'] else 1.5,
        'home_home_over': wr(h['home_over'][-5:] if h['home_over'] else []),
        'home_ah_win_rate': wr(h['ah_home_wins'][-10:] if h['ah_home_wins'] else []),
        'home_ah_streak': 0,
        'away_gf_avg': mean5(a['gf']) if a['gf'] else 1.2,
        'away_ga_avg': mean5(a['ga']) if a['ga'] else 1.2,
        'away_tot_avg': mean5(a['tot']) if a['tot'] else 2.5,
        'away_over_rate': wr(a['over'][-10:] if a['over'] else []),
        'away_gf_last3': mean3(a['gf']) if a['gf'] else 1.2,
        'away_ga_last3': mean3(a['ga']) if a['ga'] else 1.2,
        'away_over_last5': wr(a['over'][-5:] if a['over'] else []),
        'away_away_gf': mean5(a['away_gf']) if a['away_gf'] else 1.2,
        'away_away_ga': mean5(a['away_ga']) if a['away_ga'] else 1.2,
        'away_away_over': wr(a['away_over'][-5:] if a['away_over'] else []),
        'away_ah_win_rate': wr(a['ah_away_wins'][-10:] if a['ah_away_wins'] else []),
        'league_ah_home_win_rate': float(np.mean(league_hist.get('ah_hw', [0.5]))),
        'league_tot_avg': float(np.mean(league_hist.get('tot', [2.5]))),
        'league_over_rate': float(np.mean(league_hist.get('over', [0.5]))),
        'form_diff': wr(h['over'][-5:] if h['over'] else []) - wr(a['over'][-5:] if a['over'] else []),
        'eg_diff': (mean5(h['gf']) if h['gf'] else 1.5) - (mean5(a['ga']) if a['ga'] else 1.2),
        'home_away_gf_diff': (mean5(h['home_gf']) if h['home_gf'] else 1.5) - (mean5(a['away_gf']) if a['away_gf'] else 1.2),
        'home_away_def_diff': (mean5(h['home_ga']) if h['home_ga'] else 1.5) - (mean5(a['away_ga']) if a['away_ga'] else 1.2),
        'bm_dir_vs_home_form': (1 if bm_home_net > 0.5 else 0) - wr(h['ah_home_wins'][-5:] if h['ah_home_wins'] else []),
        'bm_dir_vs_away_form': (0 if bm_home_net > 0.5 else 1) - wr(a['ah_away_wins'][-5:] if a['ah_away_wins'] else []),
        'day_of_week': row['Date'].dayofweek if hasattr(row['Date'], 'dayofweek') else 3,
        'month': row['Date'].month if hasattr(row['Date'], 'month') else 6,
    }


FEATURE_COLUMNS = [
    'bm_ah_direction', 'bm_ah_conf', 'bm_ah_balance',
    'bm_ah_home_prob_net', 'bm_ah_away_prob_net',
    'ahh', 'ahh_abs',
    'ah_strong_home', 'ah_medium_home', 'ah_neutral', 'ah_medium_away', 'ah_strong_away',
    'bm_3way_direction', 'bm_3way_home_prob',
    'home_gf_avg', 'home_ga_avg', 'home_tot_avg', 'home_over_rate',
    'home_gf_last3', 'home_ga_last3', 'home_over_last5',
    'home_home_gf', 'home_home_ga', 'home_home_over',
    'home_ah_win_rate', 'home_ah_streak',
    'away_gf_avg', 'away_ga_avg', 'away_tot_avg', 'away_over_rate',
    'away_gf_last3', 'away_ga_last3', 'away_over_last5',
    'away_away_gf', 'away_away_ga', 'away_away_over',
    'away_ah_win_rate',
    'league_ah_home_win_rate', 'league_tot_avg', 'league_over_rate',
    'form_diff', 'eg_diff', 'home_away_gf_diff', 'home_away_def_diff',
    'bm_dir_vs_home_form', 'bm_dir_vs_away_form',
    'day_of_week', 'month',
]


def backtest_league(code, df_full):
    """对单个联赛进行完整回测"""
    # 加载模型
    model_path = AH_MODEL_DIR / f'{code}_ah_model.pkl'
    scaler_path = AH_MODEL_DIR / f'{code}_ah_scaler.pkl'
    config_path = AH_MODEL_DIR / f'{code}_ah_config.json'
    if not model_path.exists():
        return None

    model = joblib.load(model_path)
    scaler = joblib.load(scaler_path)
    with open(config_path) as f:
        config = json.load(f)

    # 过滤2023-2025数据
    df = df_full[(df_full['Date'] >= '2023-01-01') & (df_full['Date'] <= '2025-12-31')]
    df = df.dropna(subset=['FTHG', 'FTAG', 'AHh', 'B365AHH', 'B365AHA'])
    df = df[df['B365AHH'] > 1]
    df['gd_after_ah'] = df['FTHG'] - df['FTAG'] + df['AHh']
    df['is_push'] = df['gd_after_ah'].abs() < 0.01
    df = df[~df['is_push']].copy()
    df = df.sort_values('Date').reset_index(drop=True)
    df['target'] = (df['gd_after_ah'] > 0).astype(int)

    if len(df) < 200:
        return None

    # 构建历史（用于实时特征）
    team_stats = defaultdict(lambda: {
        'gf': [], 'ga': [], 'tot': [], 'over': [],
        'home_gf': [], 'home_ga': [], 'home_tot': [], 'home_over': [],
        'away_gf': [], 'away_ga': [], 'away_tot': [], 'away_over': [],
        'ah_home_wins': [], 'ah_away_wins': [],
    })
    league_hist = {'tot': [], 'over': [], 'ah_hw': []}

    results = []
    for idx in range(len(df)):
        row = df.iloc[idx]
        home = str(row['HomeTeam'])
        away = str(row['AwayTeam'])

        if (len(team_stats[home]['gf']) < 5 or len(team_stats[away]['gf']) < 5):
            # 预热期，更新历史后继续
            hg = float(row['FTHG'])
            ag = float(row['FTAG'])
            tot = hg + ag
            is_over = int(tot > 2.5)
            ahh = float(row['AHh'])
            gd = hg - ag + ahh
            ah_hw = int(gd > 0)
            team_stats[home]['gf'].append(hg); team_stats[home]['ga'].append(ag); team_stats[home]['tot'].append(tot); team_stats[home]['over'].append(is_over)
            team_stats[home]['home_gf'].append(hg); team_stats[home]['home_ga'].append(ag); team_stats[home]['home_tot'].append(tot); team_stats[home]['home_over'].append(is_over)
            team_stats[home]['ah_home_wins'].append(ah_hw)
            team_stats[away]['gf'].append(ag); team_stats[away]['ga'].append(hg); team_stats[away]['tot'].append(tot); team_stats[away]['over'].append(is_over)
            team_stats[away]['away_gf'].append(ag); team_stats[away]['away_ga'].append(hg); team_stats[away]['away_tot'].append(tot); team_stats[away]['away_over'].append(is_over)
            team_stats[away]['ah_away_wins'].append(1 - ah_hw)
            league_hist['tot'].append(tot); league_hist['over'].append(is_over); league_hist['ah_hw'].append(ah_hw)
            continue

        features = build_features(df, idx, team_stats, league_hist)
        X = pd.DataFrame([features])[FEATURE_COLUMNS].fillna(0)
        X_s = scaler.transform(X)

        try:
            proba = model.predict_proba(X_s)[0]
            pred_prob_up = float(proba[1])
            pred_prob_down = float(proba[0])
            model_conf = max(pred_prob_up, pred_prob_down)
            pred = 1 if pred_prob_up >= pred_prob_down else 0
        except:
            pred = -1
            pred_prob_up = 0.5
            pred_prob_down = 0.5
            model_conf = 0

        # 庄家方向
        bm_home_prob = prob(row['B365AHH'])
        bm_away_prob = prob(row['B365AHA'])
        overround = bm_home_prob + bm_away_prob
        bm_prob_up = bm_home_prob / overround if overround > 0 else 0.5
        bm_dir = 1 if bm_prob_up > 0.5 else 0
        bm_conf = abs(bm_prob_up - 0.5) * 2

        results.append({
            'date': row['Date'],
            'home': home,
            'away': away,
            'ahh': float(row['AHh']),
            'actual': int(row['target']),  # 1=上盘
            'pred': pred,
            'pred_prob_up': pred_prob_up,
            'model_conf': model_conf,
            'bm_dir': bm_dir,
            'bm_conf': bm_conf,
            'bm_prob_up': bm_prob_up,
        })

        # 更新历史
        hg = float(row['FTHG'])
        ag = float(row['FTAG'])
        tot = hg + ag
        is_over = int(tot > 2.5)
        ahh = float(row['AHh'])
        gd = hg - ag + ahh
        ah_hw = int(gd > 0)
        h = team_stats[home]
        a = team_stats[away]
        h['gf'].append(hg); h['ga'].append(ag); h['tot'].append(tot); h['over'].append(is_over)
        h['home_gf'].append(hg); h['home_ga'].append(ag); h['home_tot'].append(tot); h['home_over'].append(is_over)
        h['ah_home_wins'].append(ah_hw)
        a['gf'].append(ag); a['ga'].append(hg); a['tot'].append(tot); a['over'].append(is_over)
        a['away_gf'].append(ag); a['away_ga'].append(hg); a['away_tot'].append(tot); a['away_over'].append(is_over)
        a['ah_away_wins'].append(1 - ah_hw)
        league_hist['tot'].append(tot); league_hist['over'].append(is_over); league_hist['ah_hw'].append(ah_hw)

    return pd.DataFrame(results)


def analyze_strategies(all_bets):
    """分析各种投注策略"""
    df = pd.DataFrame(all_bets)
    if df.empty:
        return

    print("\n" + "=" * 80)
    print("策略分析")
    print("=" * 80)

    strategies = {
        "基准-纯庄家方向": lambda d: (d['bm_dir'] == d['actual']),
        "基准-纯模型方向": lambda d: (d['pred'] == d['actual']) & (d['pred'] >= 0),
        "策略1-模型高置信>=60%": lambda d: (d['pred'] == d['actual']) & (d['model_conf'] >= 0.60) & (d['pred'] >= 0),
        "策略2-模型高置信>=65%": lambda d: (d['pred'] == d['actual']) & (d['model_conf'] >= 0.65) & (d['pred'] >= 0),
        "策略3-模型高置信>=70%": lambda d: (d['pred'] == d['actual']) & (d['model_conf'] >= 0.70) & (d['pred'] >= 0),
        "策略4-模型高置信>=75%": lambda d: (d['pred'] == d['actual']) & (d['model_conf'] >= 0.75) & (d['pred'] >= 0),
        "策略5-庄家=模型方向+模型高置信>=60%": lambda d: (d['bm_dir'] == d['pred']) & (d['pred'] == d['actual']) & (d['model_conf'] >= 0.60) & (d['pred'] >= 0),
        "策略6-庄家=模型+模型高置信>=65%": lambda d: (d['bm_dir'] == d['pred']) & (d['pred'] == d['actual']) & (d['model_conf'] >= 0.65) & (d['pred'] >= 0),
        "策略7-强盘口(|ahh|>=1.0)+模型方向": lambda d: (d['pred'] == d['actual']) & (d['ahh'].abs() >= 1.0) & (d['pred'] >= 0),
        "策略8-强盘口+庄家=模型+高置信": lambda d: (d['bm_dir'] == d['pred']) & (d['pred'] == d['actual']) & (d['ahh'].abs() >= 1.0) & (d['model_conf'] >= 0.60) & (d['pred'] >= 0),
        "策略9-中盘口(0.5<=|ahh|<1.0)+庄家=模型+高置信": lambda d: (d['bm_dir'] == d['pred']) & (d['pred'] == d['actual']) & (d['ahh'].abs() >= 0.5) & (d['ahh'].abs() < 1.0) & (d['model_conf'] >= 0.65) & (d['pred'] >= 0),
        "策略10-庄家高置信>=50%+模型方向": lambda d: (d['bm_dir'] == d['pred']) & (d['pred'] == d['actual']) & (d['bm_conf'] >= 0.50) & (d['pred'] >= 0),
        "策略11-庄家高置信>=60%+庄家=模型": lambda d: (d['bm_dir'] == d['pred']) & (d['pred'] == d['actual']) & (d['bm_conf'] >= 0.60) & (d['pred'] >= 0),
        "策略12-庄家高置信>=70%+庄家=模型": lambda d: (d['bm_dir'] == d['pred']) & (d['pred'] == d['actual']) & (d['bm_conf'] >= 0.70) & (d['pred'] >= 0),
        "策略13-弱盘口(|ahh|<0.5)+庄家=模型+高置信>=65%": lambda d: (d['bm_dir'] == d['pred']) & (d['pred'] == d['actual']) & (d['ahh'].abs() < 0.5) & (d['model_conf'] >= 0.65) & (d['pred'] >= 0),
        "策略14-仅当模型概率极端(>75%或<25%)+方向": lambda d: (d['pred'] == d['actual']) & ((d['pred_prob_up'] >= 0.75) | (d['pred_prob_up'] <= 0.25)),
        "策略15-仅当模型概率极端(>80%或<20%)+方向": lambda d: (d['pred'] == d['actual']) & ((d['pred_prob_up'] >= 0.80) | (d['pred_prob_up'] <= 0.20)),
        "策略16-极端概率+庄家=模型": lambda d: (d['bm_dir'] == d['pred']) & (d['pred'] == d['actual']) & ((d['pred_prob_up'] >= 0.80) | (d['pred_prob_up'] <= 0.20)),
    }

    print(f"\n{'策略名称':<50} {'投注数':<8} {'准确率':<10}")
    print("-" * 70)
    for name, strategy in sorted(strategies.items(), key=lambda x: x[0]):
        mask = strategy(df)
        bets = df[mask]
        if len(bets) >= 5:
            hits = (bets['actual'] == bets['pred']).sum()
            acc = hits / len(bets)
            flag = " ✅" if acc >= 0.85 else (" ⚠️" if acc >= 0.70 else "")
            print(f"{name:<50} {len(bets):<8} {acc:.1%}{flag}")

    # 按联赛分析策略5
    print("\n" + "=" * 80)
    print("策略5（庄家=模型方向 + 模型高置信>=60%）各联赛表现")
    print("=" * 80)
    mask = (df['bm_dir'] == df['pred']) & (df['pred'] == df['actual']) & (df['model_conf'] >= 0.60) & (df['pred'] >= 0)
    league_results = df[mask].groupby('league_code') if 'league_code' in df.columns else []
    if 'league_code' in df.columns:
        for league_code, group in df[mask].groupby('league_code'):
            if len(group) >= 3:
                hits = (group['actual'] == group['pred']).sum()
                acc = hits / len(group)
                print(f"  {league_code}: {len(group)} bets, {acc:.1%}")


def main():
    print("=" * 80)
    print("亚盘模型回测分析 - 寻找85%+准确率的策略")
    print("=" * 80)

    all_bets = []
    for code, name in LEAGUE_INFO.items():
        hp = HIST_DIR / f'{code}_history.csv'
        if not hp.exists():
            continue
        print(f"\n处理 {name}({code})...")
        df_full = pd.read_csv(hp, low_memory=False)
        df_full['Date'] = pd.to_datetime(df_full['Date'], errors='coerce', dayfirst=True)
        result = backtest_league(code, df_full)
        if result is not None and len(result) > 0:
            result['league_code'] = code
            result['league_name'] = name
            all_bets.append(result)
            print(f"  {name}: {len(result)} 场比赛")

    if not all_bets:
        print("无数据")
        return

    df_all = pd.concat(all_bets, ignore_index=True)
    print(f"\n总计: {len(df_all)} 场比赛")
    print(f"上盘率: {df_all['actual'].mean():.1%}")

    # 基础统计
    bm_acc = (df_all['bm_dir'] == df_all['actual']).mean()
    model_acc = ((df_all['pred'] == df_all['actual']) & (df_all['pred'] >= 0)).mean()
    print(f"庄家方向准确率: {bm_acc:.1%}")
    print(f"模型方向准确率(有预测): {model_acc:.1%}")

    analyze_strategies(all_bets)


if __name__ == '__main__':
    main()
