"""
Monte Carlo simulation on the full CLV Kelly backtest.
Builds the same qualifying bets as backtest_all_leagues.py, then runs
10,000 simulations randomising outcomes using Pinnacle fair probabilities.
"""
import pandas as pd
import numpy as np
import os
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]
PLOT_OUT    = r'E:\soccer\mc_kelly_backtest.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','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 loaded")

# ── Compute fair odds 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_H'] = pd.Series(tH, index=df.index)
df['p_D'] = pd.Series(tD, index=df.index)
df['p_A'] = pd.Series(tA, index=df.index)
df['PSC_fair_H'] = 1 / df['p_H']
df['PSC_fair_D'] = 1 / df['p_D']
df['PSC_fair_A'] = 1 / df['p_A']
df['edge_A'] = (df['B365A'] - df['PSC_fair_A']) / df['PSC_fair_A']
df['edge_H'] = (df['B365H'] - df['PSC_fair_H']) / df['PSC_fair_H']
df['edge_D'] = (df['B365D'] - df['PSC_fair_D']) / df['PSC_fair_D']

# ── Pick best outcome per league (highest ROI at >5% edge, min 20 bets) ───────
def simulate_flat(bets):
    if not bets: return 0, 0.0, 0.0, 0.0
    profits = [(o-1) if w else -1 for o,w in bets]
    n = len(profits)
    total = sum(profits)
    return n, round(total,2), round(total/n*100,2), round(sum(w for _,w in bets)/n*100,1)

best_bets = []
for name in sorted(league_names.values()):
    best_roi = -999
    best_row = None
    for outcome, edge_col, odds_col, result in [
        ('Away',  'edge_A', 'B365A', 'A'),
        ('Home',  'edge_H', 'B365H', 'H'),
        ('Draw',  'edge_D', 'B365D', 'D'),
    ]:
        sub = df[(df['LeagueName']==name) & (df[edge_col] > 0.05)]
        bets = [(r[odds_col], r['FTR']==result) for _,r in sub.iterrows()]
        n, p, roi, wr = simulate_flat(bets)
        if n >= 20 and roi > best_roi:
            best_roi = roi
            best_row = (name, outcome, sub, odds_col, result)
    if best_row:
        best_bets.append(best_row)

# ── Build full bet list with Kelly fractions and Pinnacle true probabilities ───
all_rows = []
for name, outcome, sub, odds_col, result in best_bets:
    p_col = {'A': 'p_A', 'H': 'p_H', 'D': 'p_D'}[result]
    for _, r in sub.iterrows():
        b = r[odds_col] - 1
        p = r[p_col]  # Pinnacle fair probability (true prob estimate)
        kelly_f = max((p * b - (1 - p)) / b, 0) if b > 0 else 0
        if kelly_f > 0:
            all_rows.append({
                'Date':    r['Date'],
                'League':  name,
                'Outcome': outcome,
                'Odds':    r[odds_col],
                'Kelly':   kelly_f,
                'p_win':   p,          # Pinnacle fair prob for MC sampling
                'Won':     r['FTR'] == result,
            })

bets_df = pd.DataFrame(all_rows).sort_values('Date').reset_index(drop=True)
bets_df['Week'] = bets_df['Date'].dt.to_period('W')
print(f"  {len(bets_df):,} qualifying bets  ({bets_df['Date'].min().date()} to {bets_df['Date'].max().date()})")
print(f"  Overall historical win rate: {bets_df['Won'].mean()*100:.1f}%")
print(f"  Avg Pinnacle fair win prob:  {bets_df['p_win'].mean()*100:.1f}%")

# Save bets to CSV for reference
bets_df.to_csv(r'E:\soccer\mc_bets.csv', index=False)
print(f"  Bets saved to E:\\soccer\\mc_bets.csv")

# ── Deterministic backtest (same as backtest_all_leagues.py) ──────────────────
print("\n" + "="*65)
print("DETERMINISTIC BACKTEST (actual history)")
print("="*65)
print(f"{'Kelly mult':<15} {'Final £':>12}  {'x':>7}  {'MaxDD':>7}")
print("-"*50)
for km in KELLY_MULTS + [1.0]:
    bk = START; peak = START; max_dd = 0.0
    for week, grp in bets_df.groupby('Week'):
        for _, bet in grp.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:>11,.2f}  {bk/START:>7.1f}x  {max_dd*100:>6.1f}%")

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

N = len(bets_df)
odds   = bets_df['Odds'].values
p_win  = bets_df['p_win'].values   # Pinnacle fair probability per bet
kelly  = bets_df['Kelly'].values

mc_results = {}
N_PLOT = 500  # paths to store for chart

for km in KELLY_MULTS:
    stakes = np.minimum(kelly * km, 0.25)  # fractional Kelly, capped at 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   # sample from true probabilities
        bk = START; peak = START; max_dd = 0.0
        for i in range(N):
            s = bk * stakes[i]
            bk += s * (odds[i] - 1) if outcomes[i] else -s
            if bk <= 0:
                bk = 0; 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, 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):>10,.0f}  ({np.median(terminal)/START:.1f}x)")
    print(f"    Mean:          £{np.mean(terminal):>10,.0f}  ({np.mean(terminal)/START:.1f}x)")
    print(f"    P10:           £{np.percentile(terminal,10):>10,.0f}")
    print(f"    P25:           £{np.percentile(terminal,25):>10,.0f}")
    print(f"    P75:           £{np.percentile(terminal,75):>10,.0f}")
    print(f"    P90:           £{np.percentile(terminal,90):>10,.0f}")
    print(f"    P(profit):      {(terminal>START).mean()*100:.1f}%")
    print(f"    P(> 10x):       {(terminal>START*10).mean()*100:.1f}%")
    print(f"    P(> 50x):       {(terminal>START*50).mean()*100:.1f}%")
    print(f"    P(ruin <50%):   {(terminal<START*0.5).mean()*100:.1f}%")
    print(f"    Median MaxDD:   {np.median(max_dds)*100:.1f}%")
    print(f"    95th pct DD:    {np.percentile(max_dds,95)*100:.1f}%")

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

metrics = [
    ("Median",      lambda t: f"£{np.median(t):>9,.0f}"),
    ("P10",         lambda t: f"£{np.percentile(t,10):>9,.0f}"),
    ("P25",         lambda t: f"£{np.percentile(t,25):>9,.0f}"),
    ("P75",         lambda t: f"£{np.percentile(t,75):>9,.0f}"),
    ("P90",         lambda t: f"£{np.percentile(t,90):>9,.0f}"),
    ("P(profit)",   lambda t: f"{(t>START).mean()*100:>9.1f}%"),
    ("P(> 10x)",    lambda t: f"{(t>START*10).mean()*100:>9.1f}%"),
    ("P(> 50x)",    lambda t: f"{(t>START*50).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[KELLY_MULTS[0]]['max_dds'])*100:>8.1f}%"),
]

for label, fn in metrics:
    print(f"  {label:<20}", 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: CLV Kelly Strategy — Best Outcome Per League, >5% Edge\n'
    f'{N_SIMS:,} simulations, {N:,} bets, {bets_df["Date"].min().date()} to {bets_df["Date"].max().date()}\n'
    f'Outcomes sampled from Pinnacle fair probabilities',
    fontsize=11, fontweight='bold'
)

# Terminal distribution
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):,.0f}', 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 bankroll distribution\n(capped at 95th pct for readability)')
ax.legend(fontsize=8)
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f'£{x:,.0f}'))

# Median paths over time
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}'))

# Max drawdown distribution
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)
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f'{x:.0f}%'))

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