import pandas as pd, glob, os, numpy as np, warnings
warnings.filterwarnings('ignore')

files = glob.glob('E:/soccer/league_data/*.csv')
dfs = []
for f in files:
    try:
        df = pd.read_csv(f, encoding='utf-8-sig')
        df['league'] = os.path.basename(f).split('_')[0]
        dfs.append(df)
    except:
        pass

df = pd.concat(dfs, ignore_index=True)
df = df.dropna(subset=['B365H','B365D','B365A','PSH','PSD','PSA','FTR','Date'])
df['Date'] = pd.to_datetime(df['Date'], dayfirst=True, errors='coerce')
df = df.dropna(subset=['Date']).sort_values('Date').reset_index(drop=True)

df['over'] = 1/df['PSH'] + 1/df['PSD'] + 1/df['PSA']
df['pH'] = (1/df['PSH']) / df['over']
df['pD'] = (1/df['PSD']) / df['over']
df['pA'] = (1/df['PSA']) / df['over']
df['edgeH'] = (df['B365H'] - 1/df['pH']) / (1/df['pH'])
df['edgeD'] = (df['B365D'] - 1/df['pD']) / (1/df['pD'])
df['edgeA'] = (df['B365A'] - 1/df['pA']) / (1/df['pA'])

N_SIM  = 2000
START  = 1000.0
EDGE   = 0.05
ODDSMAX= 99  # no cap for this analysis

def build_pool(outcomes='A', edge_min=EDGE, odds_max=ODDSMAX):
    parts = []
    for o in outcomes:
        if o == 'H':
            mask = (df['edgeH'] >= edge_min) & (df['B365H'] <= odds_max)
            b = df[mask].copy()
            b['won'] = b['FTR'] == 'H'
            b['odds'] = b['B365H']
            b['p']    = b['pH']
        elif o == 'D':
            mask = (df['edgeD'] >= edge_min) & (df['B365D'] <= odds_max)
            b = df[mask].copy()
            b['won'] = b['FTR'] == 'D'
            b['odds'] = b['B365D']
            b['p']    = b['pD']
        else:
            mask = (df['edgeA'] >= edge_min) & (df['B365A'] <= odds_max)
            b = df[mask].copy()
            b['won'] = b['FTR'] == 'A'
            b['odds'] = b['B365A']
            b['p']    = b['pA']
        b['outcome'] = o
        parts.append(b)
    pool = pd.concat(parts).sort_values('Date').reset_index(drop=True)
    # Raw kelly fraction (no multiplier yet)
    pool['raw_kelly'] = pool.apply(
        lambda r: max((r['p']*(r['odds']-1) - (1-r['p'])) / (r['odds']-1), 0), axis=1)
    pool['pnl_unit'] = pool.apply(lambda r: r['odds']-1 if r['won'] else -1.0, axis=1)
    return pool

def kelly_compound_run(pool, km, max_pct, bankroll=START):
    """Single compounded Kelly run in date order. Returns list of (date, br)."""
    br = bankroll
    path = [(pool['Date'].iloc[0], br)]
    peak = br
    max_dd = 0
    log_rets = []
    for _, row in pool.iterrows():
        frac = min(row['raw_kelly'] * km, max_pct)
        stake = frac * br
        pnl = stake * row['pnl_unit']
        br_new = max(br + pnl, 0.01)
        peak = max(peak, br_new)
        max_dd = max(max_dd, (peak - br_new) / peak)
        log_rets.append(np.log(br_new / br))
        br = br_new
        path.append((row['Date'], br))
    sharpe = np.mean(log_rets) / np.std(log_rets) * np.sqrt(252) if np.std(log_rets)>0 else 0
    cagr = (br/START) ** (1/7) - 1
    return br, max_dd, sharpe, cagr, path

def monte_carlo(pool, km, max_pct, n=N_SIM):
    rng = np.random.default_rng(0)
    raw_k = pool['raw_kelly'].values
    pnl_u = pool['pnl_unit'].values
    finals = np.empty(n)
    for i in range(n):
        idx = rng.permutation(len(raw_k))
        br = START
        for j in idx:
            frac = min(raw_k[j] * km, max_pct)
            br = max(br + frac * br * pnl_u[j], 0.01)
        finals[i] = br
    return finals

# ── Main comparison ───────────────────────────────────────────────────────
configs = [
    ('Away only',          'A',   99),
    ('Away (odds<6)',       'A',  6.0),
    ('Home only',          'H',   99),
    ('Draw only',          'D',   99),
    ('Home+Away',          'HA',  99),
    ('Home+Away (A<6)',    'HA', 6.0),
    ('All 3',              'HDA', 99),
    ('All 3 (A<6)',        'HDA',6.0),
]

KM     = 0.25
MAXPCT = 0.10

print('Kelly multiplier: %.2fx | Max stake: %.0f%% of bankroll | Start: $%.0f | MC sims: %d' % (KM, MAXPCT*100, START, N_SIM))
print()
hdr = '{:<22} {:>5} {:>10} {:>8} {:>7} {:>7}  {:>9}  {:>9}  {:>9}'
print(hdr.format('Strategy','Bets','Final BR','CAGR','MaxDD','Sharpe','MC p10','MC med','MC p90'))
print('-'*105)

for name, outcomes, odds_max in configs:
    pool = build_pool(list(outcomes), EDGE, odds_max)
    br, max_dd, sharpe, cagr, path = kelly_compound_run(pool, KM, MAXPCT)
    finals = monte_carlo(pool, KM, MAXPCT)
    print('{:<22} {:>5} {:>10,.0f} {:>7.1%} {:>7.1%} {:>7.2f}  ${:>8,.0f}  ${:>8,.0f}  ${:>8,.0f}'.format(
        name, len(pool), br, cagr, max_dd, sharpe,
        np.percentile(finals,10), np.median(finals), np.percentile(finals,90)))

# ── Year-by-year compounded trajectory: best strategies ──────────────────
print()
print('=== Year-by-year compounded bankroll: Away (odds<6) vs Home+Away (A<6) ===')
print('{:<6} {:>10}  {:>10}  {:>10}  {:>10}'.format(
    'Year','Bets(A<6)','BR(A<6)','Bets(HA)','BR(HA)'))

pool_A  = build_pool(['A'], EDGE, 6.0)
pool_HA = build_pool(['H','A'], EDGE, 6.0)

br_A  = START
br_HA = START
years = sorted(pool_A['Date'].dt.year.unique())
for yr in years:
    gA  = pool_A [pool_A ['Date'].dt.year == yr]
    gHA = pool_HA[pool_HA['Date'].dt.year == yr]
    for _, row in gA.iterrows():
        frac = min(row['raw_kelly']*KM, MAXPCT)
        br_A = max(br_A + frac*br_A*row['pnl_unit'], 0.01)
    for _, row in gHA.iterrows():
        frac = min(row['raw_kelly']*KM, MAXPCT)
        br_HA = max(br_HA + frac*br_HA*row['pnl_unit'], 0.01)
    print('{:<6} {:>10}  {:>10,.0f}  {:>10}  {:>10,.0f}'.format(
        yr, len(gA), br_A, len(gHA), br_HA))

print()
print('=== Kelly multiplier sensitivity: Away (odds<6) ===')
pool_A = build_pool(['A'], EDGE, 6.0)
print('{:<8} {:>10} {:>8} {:>7}  {:>9}  {:>9}'.format('Kelly','Final BR','CAGR','MaxDD','MC p10','MC p90'))
for km in [0.10, 0.25, 0.50, 0.75, 1.0]:
    br, max_dd, sharpe, cagr, _ = kelly_compound_run(pool_A, km, min(km, 0.25))
    finals = monte_carlo(pool_A, km, min(km, 0.25))
    print('{:<8} {:>10,.0f} {:>7.1%} {:>7.1%}  ${:>8,.0f}  ${:>8,.0f}'.format(
        '%.0f%%'%(km*100), br, cagr, max_dd,
        np.percentile(finals,10), np.percentile(finals,90)))
