"""
Monte Carlo on pure Away CLV, OOS-only seasons (2122-2526).
No outcome selection -- just Away bets where B365A > Pinnacle fair by >5%.
Seasons 1920+2021 treated as the "identification" period; 2122 onward is OOS.
"""
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter

np.random.seed(42)

N_SIMS      = 10_000
START       = 1_000.0
KELLY_MULTS = [0.10, 0.25, 0.50]
EDGE_THRESH = 0.05
OOS_SEASONS = ['2122','2223','2324','2425','2526']
PLOT_OUT    = r'E:\soccer\mc_oos_away_clv.png'

seasons = ['1920','2021','2122','2223','2324','2425','2526']
league_names = {
    'E0': 'EPL', 'E1': 'Championship', 'E2': 'League One', 'E3': 'League Two', 'EC': 'Conference',
    'SP1': 'La Liga', 'SP2': 'Segunda',
    'I1': 'Serie A', 'I2': 'Serie B',
    'D1': 'Bundesliga', 'D2': '2.Bundesliga',
    'F1': 'Ligue 1', 'F2': 'Ligue 2',
    'N1': 'Eredivisie',
    'B1': 'Belgian Pro League',
    'P1': 'Primeira Liga',
    'T1': 'Super Lig',
    'G1': 'Greek SL',
    'SC0': 'Scottish Prem', 'SC1': 'Scottish Champ', 'SC2': 'Scottish L1', 'SC3': 'Scottish L2',
}

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

# ── Load data ──────────────────────────────────────────────────────────────────
print("Loading data...")
all_dfs = []
for code, name in league_names.items():
    for s in seasons:
        path = f'E:/soccer/league_data/{code}_{s}.csv'
        try:
            d = pd.read_csv(path, encoding='utf-8-sig')
            if len(d) < 10 or 'FTR' not in d.columns or 'B365A' not in d.columns:
                continue
            d = d.dropna(subset=['FTR','B365A','B365H','B365D','PSCH','PSCD','PSCA'])
            if len(d) < 10: continue
            d['Season'] = s
            d['LeagueCode'] = code
            d['LeagueName'] = name
            all_dfs.append(d)
        except:
            pass

df = pd.concat(all_dfs, ignore_index=True)
df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce')
df = df.dropna(subset=['Date']).sort_values('Date').reset_index(drop=True)
print(f"  {len(df):,} matches across {df['LeagueName'].nunique()} leagues")

# ── Compute fair probs and edge ────────────────────────────────────────────────
tH, tD, tA = zip(*df.apply(lambda r: remove_margin(r['PSCH'],r['PSCD'],r['PSCA']), axis=1))
df = df.copy()
df['p_A'] = pd.Series(tA, index=df.index)
df['PSC_fair_A'] = 1 / df['p_A']
df['edge_A'] = (df['B365A'] - df['PSC_fair_A']) / df['PSC_fair_A']

# ── Filter to OOS seasons, Away CLV > 5% ──────────────────────────────────────
sub = df[(df['Season'].isin(OOS_SEASONS)) & (df['edge_A'] > EDGE_THRESH)].copy()

# Build bet list with Kelly fractions
rows = []
for _, r in sub.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:
        rows.append({
            'Date':   r['Date'],
            'Season': r['Season'],
            'League': r['LeagueName'],
            'Odds':   r['B365A'],
            'Kelly':  kelly_f,
            'p_win':  p,
            'Won':    r['FTR'] == 'A',
        })

bets = pd.DataFrame(rows).sort_values('Date').reset_index(drop=True)
print(f"  {len(bets):,} OOS Away CLV bets  ({bets['Date'].min().date()} to {bets['Date'].max().date()})")
print(f"  Overall win rate: {bets['Won'].mean()*100:.1f}%")
print(f"  Avg Pinnacle fair prob: {bets['p_win'].mean()*100:.1f}%")

# Flat stake summary
flat_pl = [(r['Odds']-1) if r['Won'] else -1 for _, r in bets.iterrows()]
print(f"  Flat ROI: {sum(flat_pl)/len(flat_pl)*100:+.2f}%")

print(f"\n  By season (flat stake):")
for s in OOS_SEASONS:
    sg = bets[bets['Season'] == s]
    if len(sg) == 0: continue
    fp = [(r['Odds']-1) if r['Won'] else -1 for _, r in sg.iterrows()]
    print(f"    {s}  n={len(sg):>4}  WR={sg['Won'].mean()*100:.1f}%  ROI={sum(fp)/len(fp)*100:+.1f}%")

# ── Deterministic Kelly compounding ───────────────────────────────────────────
print(f"\n{'='*65}")
print("DETERMINISTIC KELLY COMPOUNDING (OOS only)")
print(f"{'='*65}")
print(f"  {'Kelly mult':<16} {'Final':>12}  {'x':>7}  {'MaxDD':>7}")
print("  " + "-"*50)
for km in KELLY_MULTS + [1.0]:
    bk = START; peak = START; max_dd = 0.0
    for _, bet in bets.iterrows():
        f = min(bet['Kelly'] * km, 0.25)
        stake = bk * f
        bk += stake * (bet['Odds']-1) if bet['Won'] else -stake
        peak = max(peak, bk)
        max_dd = max(max_dd, (peak - bk) / peak)
    print(f"  {km:.2f}x Kelly      {bk:>12,.2f}  {bk/START:>7.1f}x  {max_dd*100:>6.1f}%")

# ── Monte Carlo ───────────────────────────────────────────────────────────────
print(f"\n{'='*65}")
print(f"MONTE CARLO ({N_SIMS:,} simulations, OOS bets only)")
print(f"{'='*65}")

N      = len(bets)
odds   = bets['Odds'].values
p_win  = bets['p_win'].values
kelly  = bets['Kelly'].values

mc_results = {}
N_PLOT = 500

for km in KELLY_MULTS:
    stakes = np.minimum(kelly * km, 0.25)

    terminal = np.zeros(N_SIMS)
    max_dds  = np.zeros(N_SIMS)
    paths    = np.zeros((N_PLOT, N + 1))
    paths[:, 0] = START

    for sim in range(N_SIMS):
        outcomes = np.random.random(N) < p_win
        bk = START; peak = START; max_dd = 0.0
        last_i = N - 1
        for i in range(N):
            s = bk * stakes[i]
            bk += s * (odds[i] - 1) if outcomes[i] else -s
            if bk <= 0:
                bk = 0; last_i = i; break
            peak = max(peak, bk)
            max_dd = max(max_dd, (peak - bk) / peak)
            if sim < N_PLOT:
                paths[sim, i+1] = bk
        terminal[sim] = bk
        max_dds[sim] = max_dd
        if sim < N_PLOT and bk <= 0:
            paths[sim, last_i+1:] = 0

    mc_results[km] = {'terminal': terminal, 'max_dds': max_dds, 'paths': paths}

    print(f"\n  {km:.2f}x Kelly:")
    print(f"    Median:          {np.median(terminal)/START:>8.1f}x  (£{np.median(terminal):>10,.0f})")
    print(f"    P10:             {np.percentile(terminal,10)/START:>8.1f}x")
    print(f"    P25:             {np.percentile(terminal,25)/START:>8.1f}x")
    print(f"    P75:             {np.percentile(terminal,75)/START:>8.1f}x")
    print(f"    P90:             {np.percentile(terminal,90)/START:>8.1f}x")
    print(f"    P(profit):       {(terminal>START).mean()*100:>7.1f}%")
    print(f"    P(> 5x):         {(terminal>START*5).mean()*100:>7.1f}%")
    print(f"    P(> 10x):        {(terminal>START*10).mean()*100:>7.1f}%")
    print(f"    P(ruin <50%):    {(terminal<START*0.5).mean()*100:>7.1f}%")
    print(f"    Median MaxDD:    {np.median(max_dds)*100:>7.1f}%")
    print(f"    95th pct DD:     {np.percentile(max_dds,95)*100:>7.1f}%")

# ── Summary table ─────────────────────────────────────────────────────────────
print(f"\n{'='*70}")
print(f"  {'Metric':<22}", end="")
for km in KELLY_MULTS:
    print(f"  {km:.2f}x Kelly", end="")
print()
print("  " + "-"*60)

rows_table = [
    ("Median",     lambda t: f"{np.median(t)/START:>10.1f}x"),
    ("P10",        lambda t: f"{np.percentile(t,10)/START:>10.1f}x"),
    ("P25",        lambda t: f"{np.percentile(t,25)/START:>10.1f}x"),
    ("P75",        lambda t: f"{np.percentile(t,75)/START:>10.1f}x"),
    ("P90",        lambda t: f"{np.percentile(t,90)/START:>10.1f}x"),
    ("P(profit)",  lambda t: f"{(t>START).mean()*100:>9.1f}%"),
    ("P(> 5x)",    lambda t: f"{(t>START*5).mean()*100:>9.1f}%"),
    ("P(> 10x)",   lambda t: f"{(t>START*10).mean()*100:>9.1f}%"),
    ("P(ruin)",    lambda t: f"{(t<START*0.5).mean()*100:>9.1f}%"),
    ("Med MaxDD",  lambda t: f"{np.median(mc_results[km]['max_dds'])*100:>8.1f}%"),
]

for label, fn in rows_table:
    print(f"  {label:<22}", end="")
    for km in KELLY_MULTS:
        t = mc_results[km]['terminal']
        if label == "Med MaxDD":
            print(f"  {np.median(mc_results[km]['max_dds'])*100:>9.1f}%", end="")
        else:
            print(f"  {fn(t)}", end="")
    print()

# ── Chart ─────────────────────────────────────────────────────────────────────
colors = {0.10: 'steelblue', 0.25: 'seagreen', 0.50: 'darkorange'}
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
fig.suptitle(
    f'Monte Carlo: Pure Away CLV (>5% edge vs Pinnacle) — OOS Seasons 2122-2526\n'
    f'{N_SIMS:,} simulations, {N:,} bets, {bets["Date"].min().date()} to {bets["Date"].max().date()}\n'
    f'All leagues, no outcome selection. Outcomes sampled from Pinnacle fair probabilities.',
    fontsize=10, fontweight='bold'
)

ax = axes[0]
for km in KELLY_MULTS:
    t = mc_results[km]['terminal']
    cap = np.percentile(t, 95)
    ax.hist(np.minimum(t, cap), bins=100, alpha=0.45, color=colors[km],
            label=f'{km:.2f}x Kelly  median={np.median(t)/START:.1f}x', edgecolor='none')
    ax.axvline(np.median(t), color=colors[km], linewidth=2, linestyle='--')
ax.axvline(START, color='gray', linewidth=1, linestyle=':', label='Start £1,000')
ax.set_xlabel('Terminal bankroll (£)')
ax.set_ylabel('Frequency')
ax.set_title('Terminal distribution (capped at 95th pct)')
ax.legend(fontsize=8)
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f'£{x:,.0f}'))

ax = axes[1]
x = np.arange(N + 1)
for km in KELLY_MULTS:
    pths = mc_results[km]['paths']
    p5  = np.percentile(pths, 5,  axis=0)
    p50 = np.percentile(pths, 50, axis=0)
    p95 = np.percentile(pths, 95, axis=0)
    ax.fill_between(x, p5, p95, alpha=0.12, color=colors[km])
    ax.plot(x, p50, color=colors[km], linewidth=2, label=f'{km:.2f}x Kelly median')
ax.axhline(START, color='gray', linewidth=0.8, linestyle=':')
ax.set_xlabel('Bet number')
ax.set_ylabel('Bankroll (£)')
ax.set_title('Median path +/- 5th-95th percentile')
ax.legend(fontsize=8)
ax.set_yscale('log')
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f'£{x:,.0f}'))

ax = axes[2]
for km in KELLY_MULTS:
    dd = mc_results[km]['max_dds'] * 100
    ax.hist(dd, bins=80, alpha=0.45, color=colors[km],
            label=f'{km:.2f}x Kelly  median={np.median(dd):.0f}%', edgecolor='none')
    ax.axvline(np.median(dd), color=colors[km], linewidth=2, linestyle='--')
ax.set_xlabel('Max drawdown (%)')
ax.set_ylabel('Frequency')
ax.set_title('Max drawdown distribution')
ax.legend(fontsize=8)

plt.tight_layout()
plt.savefig(PLOT_OUT, dpi=150, bbox_inches='tight')
print(f"\nChart saved to {PLOT_OUT}")
print("Done.")
