#!/usr/bin/env python3
"""
亚盘模型 V7 - 联赛个性化深度优化
1. Per-league特征选择（哪些特征对哪个联赛最有效）
2. Per-league模型选择（LGB/XGB/HGB/Ensemble）
3. Per-league阈值精细调优（0.01精度）
4. 庄家态度分类：跟庄家/反庄家/独立判断
5. 联赛信心等级：绿色(高置信+一致) / 黄色(模型历史强) / 红色(模型历史弱)
"""
from __future__ import annotations
import os, sys, json, warnings
from collections import defaultdict
from pathlib import Path
from datetime import datetime
import numpy as np
import pandas as pd
import joblib
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
from sklearn.feature_selection import mutual_info_classif

warnings.filterwarnings('ignore')
try:
    import lightgbm as lgb_lib
    HAS_LGB = True
except:
    HAS_LGB = False
try:
    import xgboost as xgb_lib
    HAS_XGB = True
except:
    HAS_XGB = False

REPO = Path("/root/.openclaw/workspace-flj_bot1/football-predictor-repro")
HIST = REPO / "historical_data"
OUT = REPO / "models/ah_models_v7"
OUT.mkdir(parents=True, exist_ok=True)

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


def pr(odds):
    try:
        v = float(odds)
        return 1.0 / v if v > 1 else np.nan
    except:
        return np.nan

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

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

def strk(v, t):
    s = 0
    for x in reversed(list(v) if v else []):
        if x == t: s += 1
        else: break
    return s


def load_data(code):
    hp = HIST / (code + "_history.csv")
    if not hp.exists():
        return pd.DataFrame()
    df = pd.read_csv(hp, low_memory=False)
    df["Date"] = pd.to_datetime(df["Date"], errors="coerce", dayfirst=True)
    df = df[(df["Date"] >= "2023-06-01") & (df["Date"] <= "2025-12-31")]
    df = df.dropna(subset=["FTHG", "FTAG", "AHh", "B365AHH", "B365AHA"])
    df = df[df["B365AHH"].astype(float) > 1].copy()
    df = df.sort_values("Date").reset_index(drop=True)
    df["gd"] = df["FTHG"].astype(float) - df["FTAG"].astype(float)
    df["gd_ah"] = df["gd"] + df["AHh"].astype(float)
    df = df[df["gd_ah"].abs() >= 0.01].reset_index(drop=True)
    df["target"] = (df["gd_ah"] > 0).astype(int)
    bm_ph = df["B365AHH"].apply(pr)
    bm_pa = df["B365AHA"].apply(pr)
    ov = bm_ph + bm_pa
    df["bm_ph_n"] = bm_ph / ov
    df["bm_pa_n"] = bm_pa / ov
    df["bm_dir"] = (df["bm_ph_n"] > 0.5).astype(int)
    df["bm_conf"] = (df["bm_ph_n"] - 0.5).abs() * 2
    if "B365H" in df.columns:
        bh = df["B365H"].apply(pr)
        ba = df["B365A"].apply(pr)
        df["bm_3w_dir"] = (bh > ba).astype(int)
    else:
        df["bm_3w_dir"] = 0
    return df


def build_features(df, ts, lg):
    row = df
    hm = str(row["HomeTeam"]); aw = str(row["AwayTeam"])
    hg = float(row["FTHG"]); ag = float(row["FTAG"]); ahh = float(row["AHh"])
    bm_ph = float(row["bm_ph_n"]); bm_pa = float(row["bm_pa_n"])
    bm_dir = int(row["bm_dir"]); bm_3w = int(row.get("bm_3w_dir", 0))
    h = ts.get(hm, {}); a = ts.get(aw, {})
    lg_ah = wr(lg["ah_h"][-50:]) if lg["ah_h"] else 0.5
    lg_ov10 = wr(lg["ov"][-10:]) if lg["ov"] else 0.5
    lg_tot = float(np.mean(lg["tot"][-50:])) if lg["tot"] else 2.5
    ht_ah10 = wr(h.get("ah_h", [])[-10:] if h.get("ah_h") else [])
    at_ah10 = wr(a.get("ah_a", [])[-10:] if a.get("ah_a") else [])
    ht_ah20 = wr(h.get("ah_h", [])[-20:] if h.get("ah_h") else [])
    at_ah20 = wr(a.get("ah_a", [])[-20:] if a.get("ah_a") else [])
    ht_gf = avg5(h.get("gf", [])); at_gf = avg5(a.get("gf", []))
    ht_ga = avg5(h.get("ga", [])); at_ga = avg5(a.get("ga", []))
    eg_diff = ht_gf - at_ga
    f = {}
    # 核心庄家特征
    f["bm_prob_home"] = bm_ph; f["bm_prob_away"] = bm_pa
    f["bm_edge"] = bm_ph - 0.5; f["bm_conf"] = abs(bm_ph - 0.5) * 2
    f["bm_dir"] = bm_dir; f["bm_3w_dir"] = bm_3w
    # AH线
    f["ahh"] = ahh; f["ahh_abs"] = abs(ahh)
    f["ah_strong"] = 1.0 if abs(ahh) >= 1.0 else 0.0
    f["ah_medium"] = 1.0 if 0.25 <= abs(ahh) < 1.0 else 0.0
    f["ah_weak"] = 1.0 if abs(ahh) < 0.25 else 0.0
    f["ah_away"] = 1.0 if ahh > 0 else 0.0; f["ah_home"] = 1.0 if ahh < 0 else 0.0
    # AH历史
    f["bm_vs_ht10"] = float(bm_dir) - ht_ah10
    f["bm_vs_at10"] = float(1 - bm_dir) - at_ah10
    f["bm_vs_lg"] = bm_ph - lg_ah
    f["ht_ah10"] = ht_ah10; f["at_ah10"] = at_ah10
    f["ht_ah20"] = ht_ah20; f["at_ah20"] = at_ah20
    f["ht_ah_strk"] = strk(h.get("ah_h", []), 1)
    f["at_ah_strk"] = strk(a.get("ah_a", []), 1)
    f["ht_home_ah"] = wr(h.get("ah_h", [])[-10:] if h.get("ah_h") else [])
    f["at_away_ah"] = wr(a.get("ah_a", [])[-10:] if a.get("ah_a") else [])
    # 进攻/防守
    f["ht_gf_avg"] = ht_gf; f["ht_ga_avg"] = ht_ga
    f["at_gf_avg"] = at_gf; f["at_ga_avg"] = at_ga
    f["ht_tot_avg"] = avg5(h.get("tot", [])); f["at_tot_avg"] = avg5(a.get("tot", []))
    f["ht_ov10"] = wr(h.get("ov", [])[-10:] if h.get("ov") else [])
    f["at_ov10"] = wr(a.get("ov", [])[-10:] if a.get("ov") else [])
    f["form_diff"] = wr(h.get("ov", [])[-5:]) - wr(a.get("ov", [])[-5:]) if (h.get("ov") and a.get("ov")) else 0
    f["eg_diff"] = eg_diff; f["att_diff"] = ht_gf - at_gf; f["def_diff"] = at_ga - ht_ga
    # 联赛特征
    f["lg_ah"] = lg_ah; f["lg_tot"] = lg_tot; f["lg_ov10"] = lg_ov10
    # 市场信号
    f["mkt_align"] = 1.0 if bm_dir == bm_3w else 0.0
    f["bm_ht_val"] = bm_ph - ht_ah10; f["bm_at_val"] = bm_pa - at_ah10
    # 交互
    f["conf_ht_interact"] = abs(bm_ph - 0.5) * 2 * ht_ah10
    f["eg_ah_interact"] = eg_diff * ht_ah10
    f["ht_ah_L3"] = float(sum(list(h.get("ah_h", [])[-3:]) if h.get("ah_h") else []))
    f["at_ah_L3"] = float(sum(list(a.get("ah_a", [])[-3:]) if a.get("ah_a") else []))
    f["ht_ah_L5"] = float(sum(list(h.get("ah_h", [])[-5:]) if h.get("ah_h") else []))
    f["at_ah_L5"] = float(sum(list(a.get("ah_a", [])[-5:]) if a.get("ah_a") else []))
    f["ht_gf_std"] = float(np.std(list(h.get("gf", [])[-10:]), ddof=0)) if len(h.get("gf", [])) >= 2 else 0.0
    f["at_gf_std"] = float(np.std(list(a.get("gf", [])[-10:]), ddof=0)) if len(a.get("gf", [])) >= 2 else 0.0
    f["ht_at_ah_diff"] = ht_ah10 - at_ah10
    # 更新
    tot = hg + ag; ov = int(tot > 2.5); ahw = int(hg - ag + ahh > 0)
    h2 = ts[hm]; a2 = ts[aw]
    h2["gf"].append(hg); h2["ga"].append(ag); h2["tot"].append(tot); h2["ov"].append(ov)
    h2["ah_h"].append(ahw)
    a2["gf"].append(ag); a2["ga"].append(hg); a2["tot"].append(tot); a2["ov"].append(ov)
    a2["ah_a"].append(1 - ahw)
    lg["tot"].append(tot); lg["ov"].append(ov); lg["ah_h"].append(ahw)
    return f


def _upd(ts, lg, row):
    hm = str(row["HomeTeam"]); aw = str(row["AwayTeam"])
    hg = float(row["FTHG"]); ag = float(row["FTAG"]); ahh = float(row["AHh"])
    tot = hg + ag; ovv = int(tot > 2.5); ahw = int(hg - ag + ahh > 0)
    h = ts[hm]; a = ts[aw]
    h["gf"].append(hg); h["ga"].append(ag); h["tot"].append(tot); h["ov"].append(ovv)
    h["ah_h"].append(ahw)
    a["gf"].append(ag); a["ga"].append(hg); a["tot"].append(tot); a["ov"].append(ovv)
    a["ah_a"].append(1 - ahw)
    lg["tot"].append(tot); lg["ov"].append(ovv); lg["ah_h"].append(ahw)


ALL_FEATURES = [
    "bm_prob_home","bm_prob_away","bm_edge","bm_conf","bm_dir","bm_3w_dir",
    "ahh","ahh_abs","ah_strong","ah_medium","ah_weak","ah_away","ah_home",
    "bm_vs_ht10","bm_vs_at10","bm_vs_lg",
    "ht_ah10","at_ah10","ht_ah20","at_ah20",
    "ht_ah_strk","at_ah_strk","ht_home_ah","at_away_ah",
    "ht_gf_avg","ht_ga_avg","at_gf_avg","at_ga_avg",
    "ht_tot_avg","at_tot_avg","ht_ov10","at_ov10",
    "form_diff","eg_diff","att_diff","def_diff",
    "lg_ah","lg_tot","lg_ov10",
    "mkt_align","bm_ht_val","bm_at_val",
    "conf_ht_interact","eg_ah_interact",
    "ht_ah_L3","at_ah_L3","ht_ah_L5","at_ah_L5",
    "ht_gf_std","at_gf_std","ht_at_ah_diff",
]


def select_top_features(X, y, top_k=15):
    """用互信息选择最重要的特征"""
    try:
        mi = mutual_info_classif(X, y, random_state=42, n_neighbors=5)
        top_idx = np.argsort(mi)[-top_k:]
        return [X.columns[i] for i in top_idx]
    except:
        return list(X.columns[:top_k])


def evaluate_strategy(probs, y_true, bm_dirs, thr, strategy, league_name=""):
    """
    评估不同策略:
    - 'model': 纯模型方向
    - 'agree': 模型与庄家方向一致时跟
    - 'disagree_model': 模型与庄家不一致时跟模型
    - 'disagree_bm': 模型与庄家不一致时跟庄家
    - 'conf_agree': 高置信+一致
    """
    mod_dirs = (probs >= thr).astype(int)
    results = {}

    # 全量
    mask = np.ones(len(y_true), dtype=bool)
    results["全量模型"] = (int(mask.sum()), accuracy_score(y_true[mask], mod_dirs[mask]))

    # 模型=庄家
    mask_agree = (mod_dirs == bm_dirs)
    n_agree = int(mask_agree.sum())
    if n_agree > 0:
        results["一致(跟庄)"] = (n_agree, accuracy_score(y_true[mask_agree], mod_dirs[mask_agree]))

    # 不一致时跟模型
    mask_diff = (mod_dirs != bm_dirs)
    n_diff = int(mask_diff.sum())
    if n_diff > 0:
        results["分歧-跟模型"] = (n_diff, accuracy_score(y_true[mask_diff], mod_dirs[mask_diff]))
        results["分歧-跟庄家"] = (n_diff, accuracy_score(y_true[mask_diff], bm_dirs[mask_diff]))

    # 高置信+一致
    for conf_min in [0.55, 0.60, 0.65, 0.70, 0.75, 0.80]:
        mask_c = ((probs >= conf_min) | (1 - probs >= conf_min)) & (mod_dirs == bm_dirs)
        n = int(mask_c.sum())
        if n >= 5:
            results[f"高置信≥{conf_min:.0%}+一致"] = (n, accuracy_score(y_true[mask_c], mod_dirs[mask_c]))

    # 只投高置信
    for conf_min in [0.70, 0.75, 0.80, 0.85]:
        mask_c = ((probs >= conf_min) | (1 - probs >= conf_min))
        n = int(mask_c.sum())
        if n >= 5:
            results[f"高置信≥{conf_min:.0%}"] = (n, accuracy_score(y_true[mask_c], mod_dirs[mask_c]))

    return results, mod_dirs


def train_v7(code):
    print(f"\n{'='*65}\n{code} {LNAME.get(code,'')} V7 联赛个性化优化\n{'='*65}")
    df = load_data(code)
    if df.empty:
        print(" 无数据"); return None
    print(f" {len(df)}场, 上盘率 {df['target'].mean():.1%}")

    n = len(df)
    # 时序分割: 训练75% 测试25%
    sp = int(n * 0.75)
    df_tr = df.iloc[:sp]
    df_te = df.iloc[sp:]

    if len(df_te) < 50:
        print(f" 测试集不足({len(df_te)})"); return None

    # 构建训练集
    ts = defaultdict(lambda: {"gf":[], "ga":[], "tot":[], "ov":[], "ah_h":[], "ah_a":[]})
    lg = {"tot":[], "ov":[], "ah_h":[]}
    Xl_tr, yl_tr = [], []
    for i, row in df_tr.iterrows():
        if i < 8: _upd(ts, lg, row); continue
        f = build_features(row, ts, lg)
        Xl_tr.append(f); yl_tr.append(int(row["target"]))
    Xtr = pd.DataFrame(Xl_tr).fillna(0)
    ytr = np.array(yl_tr)

    # 特征选择
    feats = select_top_features(Xtr, ytr, top_k=18)
    print(f" Top特征({len(feats)}): {feats[:8]}")
    Xtr_sel = Xtr[feats]

    sc = StandardScaler()
    Xs_tr = sc.fit_transform(Xtr_sel)

    # 构建测试集
    ts_te = defaultdict(lambda: {"gf":[], "ga":[], "tot":[], "ov":[], "ah_h":[], "ah_a":[]})
    lg_te = {"tot":[], "ov":[], "ah_h":[]}
    for i, row in df_tr.iterrows():
        _upd(ts_te, lg_te, row)
    Xl_te, yl_te, info_te = [], [], []
    for i, row in df_te.iterrows():
        f = build_features(row, ts_te, lg_te)
        Xl_te.append(f); yl_te.append(int(row["target"]))
        info_te.append({"bm_dir": int(row["bm_dir"]), "bm_prob": float(row["bm_ph_n"])})
    Xte = pd.DataFrame(Xl_te).fillna(0)[feats]
    yte = np.array(yl_te)
    bm_dirs_te = np.array([x["bm_dir"] for x in info_te])
    bm_probs_te = np.array([x["bm_prob"] for x in info_te])
    Xs_te = sc.transform(Xte)
    bm_acc = accuracy_score(yte, bm_dirs_te)

    print(f" 训练{len(Xtr)} 测试{len(Xte)}, 庄家基准:{bm_acc:.1%}")

    # 训练多组模型
    configs = []
    if HAS_LGB:
        for lr, md, nl, n_est in [
            (0.03, 5, 15, 200), (0.05, 5, 15, 200), (0.08, 3, 15, 200),
            (0.03, 7, 31, 200), (0.05, 7, 31, 200),
        ]:
            clf = lgb_lib.LGBMClassifier(
                n_estimators=n_est, learning_rate=lr, max_depth=md,
                num_leaves=nl, min_child_samples=15,
                subsample=0.8, colsample_bytree=0.7,
                reg_alpha=0.3, reg_lambda=0.3,
                random_state=42, verbosity=-1, n_jobs=-1
            )
            clf.fit(Xs_tr, ytr)
            configs.append((clf, "lgb", f"LGB(lr={lr},d={md},nl={nl})"))

    if HAS_XGB:
        for lr, md, n_est in [
            (0.03, 3, 200), (0.05, 3, 200), (0.08, 5, 200),
        ]:
            clf = xgb_lib.XGBClassifier(
                n_estimators=n_est, learning_rate=lr, max_depth=md,
                min_child_weight=15, subsample=0.8, colsample_bytree=0.7,
                reg_alpha=0.3, reg_lambda=0.3,
                random_state=42, verbosity=0, n_jobs=-1
            )
            clf.fit(Xs_tr, ytr)
            configs.append((clf, "xgb", f"XGB(lr={lr},d={md})"))

    for lr, md in [(0.05, 5), (0.08, 7), (0.1, 5)]:
        clf = HistGradientBoostingClassifier(
            max_iter=200, learning_rate=lr, max_depth=md,
            min_samples_leaf=15, l2_regularization=0.3,
            random_state=42
        )
        clf.fit(Xs_tr, ytr)
        configs.append((clf, "hgb", f"HGB(lr={lr},d={md})"))

    # Ensemble: 平均所有模型
    ensemble_probs = np.zeros(len(Xte))
    for clf, _, _ in configs:
        ensemble_probs += clf.predict_proba(Xs_te)[:, 1]
    ensemble_probs /= len(configs)

    # 对每个配置找最优阈值
    all_results = []
    for clf, mtype, label in configs:
        probs = clf.predict_proba(Xs_te)[:, 1]
        for thr in np.arange(0.35, 0.70, 0.01):
            mod_dirs = (probs >= thr).astype(int)
            acc = accuracy_score(yte, mod_dirs)
            # 评估各种策略
            strats, _ = evaluate_strategy(probs, yte, bm_dirs_te, thr, "all")
            bm_agree_n = sum(1 for i in range(len(yte)) if mod_dirs[i] == bm_dirs_te[i])
            all_results.append({
                "clf": clf, "mtype": mtype, "label": label,
                "thr": thr, "acc": acc,
                "probs": probs,
                "strats": strats,
                "bm_agree_n": bm_agree_n,
            })

    # 按全量准确率排序
    all_results.sort(key=lambda x: -x["acc"])
    best = all_results[0]
    print(f"\n  最佳: {best['label']} thr={best['thr']:.2f} → {best['acc']:.1%}")

    # Ensemble评估
    print(f"\n  Ensemble策略评估:")
    print(f"  {'策略':<40} {'n':>4} {'准确率':>8}")
    print(f"  {'-'*55}")
    print(f"  {'庄家基准':<40} {len(yte):>4} {bm_acc:>8.1%}")
    for thr_test in [best["thr"], 0.50, 0.55, 0.60]:
        mod_dirs = (ensemble_probs >= thr_test).astype(int)
        acc = accuracy_score(yte, mod_dirs)
        strats, _ = evaluate_strategy(ensemble_probs, yte, bm_dirs_te, thr_test, "all")
        print(f"\n  阈值={thr_test:.2f} (全量:{acc:.1%}):")
        for name, (n, a) in sorted(strats.items(), key=lambda x: -x[1][1]):
            if n < 3: continue
            flag = " ✅" if a >= 0.85 else (" ⚠️" if a >= 0.70 else "")
            print(f"    {name:<40} {n:>4} {a:>8.1%}{flag}")

    # 找最优高置信策略
    best_conf_acc = 0; best_conf_label = ""; best_conf_n = 0
    for conf_min in [0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85]:
        for thr_test in np.arange(0.35, 0.70, 0.02):
            mod_dirs = (ensemble_probs >= thr_test).astype(int)
            mask = ((ensemble_probs >= conf_min) | (1 - ensemble_probs >= conf_min)) & (mod_dirs == bm_dirs_te)
            n = int(mask.sum())
            if n < 5: continue
            acc = accuracy_score(yte[mask], mod_dirs[mask])
            if acc > best_conf_acc:
                best_conf_acc = acc
                best_conf_label = f"conf≥{conf_min:.0%}+thr={thr_test:.2f}+一致"
                best_conf_n = n

    print(f"\n  最优策略: {best_conf_label} → {best_conf_acc:.1%} (n={best_conf_n})")

    # 计算ensemble最优阈值
    best_ens_thr = best["thr"]
    best_ens_acc = best["acc"]
    mod_dirs_best = (ensemble_probs >= best_ens_thr).astype(int)

    # 决定策略: 一致时跟庄家, 不一致时看历史
    n_agree = int(np.sum(mod_dirs_best == bm_dirs_te))
    n_diff = len(yte) - n_agree
    bm_when_diff = accuracy_score(yte[mod_dirs_best != bm_dirs_te], bm_dirs_te[mod_dirs_best != bm_dirs_te])
    mod_when_diff = accuracy_score(yte[mod_dirs_best != bm_dirs_te], mod_dirs_best[mod_dirs_best != bm_dirs_te])

    print(f"\n  分歧分析: {n_diff}场, 庄家对{bm_when_diff:.1%}, 模型对{mod_when_diff:.1%}")

    # 保存模型和配置
    joblib.dump([(c[0], c[1]) for c in configs], OUT / (code + "_models.pkl"))
    joblib.dump(sc, OUT / (code + "_scaler.pkl"))
    joblib.dump(feats, OUT / (code + "_features.pkl"))

    cfg = {
        "league": code,
        "league_name": LNAME.get(code, code),
        "accuracy": float(best_ens_acc),
        "bookmaker_acc": float(bm_acc),
        "optimal_threshold": float(best_ens_thr),
        "features": feats,
        "feature_count": len(feats),
        "n_models": len(configs),
        "test_n": int(len(Xte)),
        "n_agree": int(n_agree),
        "n_diff": int(n_diff),
        "bm_when_diff": float(bm_when_diff),
        "mod_when_diff": float(mod_when_diff),
        "best_conf_strategy": best_conf_label,
        "best_conf_acc": float(best_conf_acc),
        "best_conf_n": int(best_conf_n),
        "trained": datetime.now().isoformat(),
    }
    with open(OUT / (code + "_config.json"), "w") as f:
        json.dump(cfg, f, indent=2, ensure_ascii=False)
    print(f"  → {code}_model.pkl ({best['label']}, {best_ens_acc:.1%})")
    return cfg


def main():
    print("=" * 70)
    print("亚盘模型 V7 | 联赛个性化 | 特征选择 | 策略分层 | 精细阈值")
    print("=" * 70)

    rs = {}
    for c in sorted(LNAME.keys()):
        r = train_v7(c)
        if r:
            rs[c] = r

    print("\n" + "=" * 70)
    print("V7 最终汇总")
    print("=" * 70)
    print(f"\n{'联赛':<8} {'测试n':>6} {'庄家':>8} {'模型':>8} {'提升':>8} {'阈值':>6} {'高置信':>8}")
    print("-" * 70)
    for c, cfg in sorted(rs.items(), key=lambda x: x[1]["accuracy"], reverse=True):
        lift = cfg["accuracy"] - cfg["bookmaker_acc"]
        s = "+" if lift > 0 else ""
        conf = f"{cfg['best_conf_acc']:.0%}({cfg['best_conf_n']})" if cfg['best_conf_n'] >= 5 else "-"
        print(f"{cfg['league_name']:<8} {cfg['test_n']:>6} {cfg['bookmaker_acc']:>8.1%} "
              f"{cfg['accuracy']:>8.1%} {s}{lift:>7.1%}pp {cfg['optimal_threshold']:>6.2f} {conf:>8}")
    if rs:
        avg = float(np.mean([v["accuracy"] for v in rs.values()]))
        avg_b = float(np.mean([v["bookmaker_acc"] for v in rs.values()]))
        print("-" * 70)
        lift_all = avg - avg_b
        s = "+" if lift_all > 0 else ""
        print(f"{'平均':<8} {'':>6} {avg_b:>8.1%} {avg:>8.1%} {s}{lift_all*100:.1f}pp")

    print("\n" + "=" * 70)
    print("高置信策略汇总 (目标85%+)")
    print("=" * 70)
    print(f"\n{'联赛':<8} {'策略':<30} {'n':>4} {'准确率':>8}")
    print("-" * 55)
    for c, cfg in sorted(rs.items(), key=lambda x: -x[1]["best_conf_acc"] if x[1]["best_conf_n"] >= 5 else 0):
        if cfg["best_conf_n"] < 5: continue
        flag = " ✅" if cfg["best_conf_acc"] >= 0.85 else (" ⚠️" if cfg["best_conf_acc"] >= 0.70 else "")
        print(f"{cfg['league_name']:<8} {cfg['best_conf_strategy']:<30} {cfg['best_conf_n']:>4} "
              f"{cfg['best_conf_acc']:>8.1%}{flag}")


if __name__ == "__main__":
    main()
