""" Deep root-cause analysis on diagnostic_backtest CSV. Tests specific hypotheses with hard numbers and proposes actionable filter rules with estimated impact (units saved, ROI shift). """ import sys, os, glob import pandas as pd import numpy as np REPORTS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "reports") def latest_csv(): files = sorted(glob.glob(os.path.join(REPORTS_DIR, "diagnostic_backtest_*.csv")), key=os.path.getmtime, reverse=True) return files[0] if files else None def fmt_pct(x): return f"{x:>6.2f}%" if pd.notna(x) else " ----" def cell(df, label, mask): sub = df[mask] n = len(sub) if n == 0: return f" {label:<60} n=0" wins = (sub["won"] == True).sum() losses = (sub["won"] == False).sum() settled = wins + losses hr = 100.0 * wins / settled if settled else 0 profit = sub["unit_profit"].sum() staked = sub["stake_units"].sum() roi = 100.0 * profit / staked if staked else 0 return (f" {label:<60} n={n:>4} hit={hr:>6.2f}% " f"profit={profit:>+7.2f}u roi={roi:>+7.2f}%") def hypothesis_block(title, rows): print(f"\n{'─' * 78}") print(f" {title}") print(f"{'─' * 78}") for row in rows: print(row) def main(): csv_path = latest_csv() if not csv_path: print("No backtest CSV found") return print(f"Reading {csv_path}") df = pd.read_csv(csv_path) print(f"Loaded {len(df)} rows") # Filter only playable + settled pdf = df[(df["playable"] == True) & (df["won"].notna())].copy() pdf["won"] = pdf["won"].astype(bool) print(f"Playable + settled: {len(pdf)}") overall_hr = (pdf["won"].sum() / len(pdf)) * 100 overall_roi = 100.0 * pdf["unit_profit"].sum() / pdf["stake_units"].sum() print(f"\nOVERALL: hit={overall_hr:.2f}% roi={overall_roi:.2f}%") # ───────────────────────────────────────────────────────────────────── # H1: TRIPLE VALUE CONFIRMATION # ───────────────────────────────────────────────────────────────────── triple_confirmed_mask = ~pdf["bb_issues"].fillna("").str.contains( "triple_value_not_confirmed", na=False ) hypothesis_block( "H1: TRIPLE VALUE CONFIRMED vs NOT CONFIRMED", [ cell(pdf, "triple_value CONFIRMED", triple_confirmed_mask), cell(pdf, "triple_value NOT CONFIRMED", ~triple_confirmed_mask), ] ) # ───────────────────────────────────────────────────────────────────── # H2: TRAP MARKET FLAG # ───────────────────────────────────────────────────────────────────── trap_mask = pdf["bb_trap_market"] == True hypothesis_block( "H2: TRAP MARKET FLAG (model says band rate < implied → market overpriced)", [ cell(pdf, "trap_market_flag = TRUE (model warned)", trap_mask), cell(pdf, "trap_market_flag = FALSE", ~trap_mask), ] ) # ───────────────────────────────────────────────────────────────────── # H3: V25/V27 CONSENSUS # ───────────────────────────────────────────────────────────────────── agree_mask = pdf["v27_consensus"] == "AGREE" disagree_mask = pdf["v27_consensus"] == "DISAGREE" hypothesis_block( "H3: V25 ↔ V27 CONSENSUS", [ cell(pdf, "AGREE", agree_mask), cell(pdf, "DISAGREE", disagree_mask), cell(pdf, "neither/null", ~(agree_mask | disagree_mask)), ] ) # ───────────────────────────────────────────────────────────────────── # H4: ODDS RELIABILITY (league quality) # ───────────────────────────────────────────────────────────────────── pdf["rel_band"] = pd.cut( pdf["odds_reliability"].fillna(0.35), [0, 0.30, 0.45, 0.55, 1.0], labels=["<0.30 verylow", "0.30-0.45 low", "0.45-0.55 mid", ">=0.55 high"] ) hypothesis_block( "H4: LEAGUE ODDS RELIABILITY", [cell(pdf, str(b), pdf["rel_band"] == b) for b in pdf["rel_band"].cat.categories] ) # ───────────────────────────────────────────────────────────────────── # H5: CALIBRATOR IMPACT (raw vs calibrated) # ───────────────────────────────────────────────────────────────────── pdf["calib_delta"] = pdf["calibrated_confidence"] - pdf["raw_confidence"] pdf["delta_band"] = pd.cut( pdf["calib_delta"].fillna(0), [-100, -10, -3, 3, 10, 100], labels=["cal<raw (3..10)", "cal>>raw (+10+)"] ) hypothesis_block( "H5: CALIBRATOR DELTA (calibrated_conf - raw_conf)", [cell(pdf, str(b), pdf["delta_band"] == b) for b in pdf["delta_band"].cat.categories] ) # ───────────────────────────────────────────────────────────────────── # H6: EV EDGE # ───────────────────────────────────────────────────────────────────── pdf["edge_band"] = pd.cut( pdf["ev_edge"].fillna(0), [-10, -0.05, 0.0, 0.05, 0.10, 0.20, 10], labels=["edge<-5%", "-5%-0%", "0-5%", "5-10%", "10-20%", ">20%"] ) hypothesis_block( "H6: EV EDGE (model_prob - implied_prob)", [cell(pdf, str(b), pdf["edge_band"] == b) for b in pdf["edge_band"].cat.categories] ) # ───────────────────────────────────────────────────────────────────── # H7: ODDS x MARKET cross # ───────────────────────────────────────────────────────────────────── pdf["odds_band"] = pd.cut( pdf["odds"].fillna(0), [0, 1.30, 1.50, 1.80, 2.20, 3.00, 100], labels=["<1.30", "1.30-1.50", "1.50-1.80", "1.80-2.20", "2.20-3.00", ">3.00"] ) print(f"\n{'─' * 78}") print(f" H7: ODDS BAND × MARKET (per cell hit% / roi% / n)") print(f"{'─' * 78}") pivot_n = pdf.pivot_table(index="market", columns="odds_band", values="match_id", aggfunc="count", fill_value=0, observed=False) pivot_roi = pdf.pivot_table(index="market", columns="odds_band", values="unit_profit", aggfunc="sum", fill_value=0, observed=False) pivot_stake = pdf.pivot_table(index="market", columns="odds_band", values="stake_units", aggfunc="sum", fill_value=0, observed=False) pivot_roi_pct = (100.0 * pivot_roi / pivot_stake.replace(0, np.nan)).round(1) print("\n Bet count per cell:") print(pivot_n.to_string()) print("\n ROI% per cell:") print(pivot_roi_pct.to_string()) # ───────────────────────────────────────────────────────────────────── # H8: COMBINED FILTER SIMULATION # ───────────────────────────────────────────────────────────────────── print(f"\n{'─' * 78}") print(" H8: COMBINED FILTER SIMULATION (what if we add rules)") print(f"{'─' * 78}") def simulate(filter_name, keep_mask): kept = pdf[keep_mask] rejected = pdf[~keep_mask] if len(kept) == 0: return f" {filter_name:<55} → 0 bet remain" kept_hr = 100.0 * kept["won"].sum() / len(kept) kept_profit = kept["unit_profit"].sum() kept_staked = kept["stake_units"].sum() kept_roi = 100.0 * kept_profit / kept_staked if kept_staked else 0 saved = -rejected["unit_profit"].sum() # money we WOULD HAVE LOST return (f" {filter_name:<55} keep={len(kept):>3} hit={kept_hr:>5.1f}% " f"roi={kept_roi:>+6.2f}% saved={saved:>+6.2f}u") print(simulate("BASELINE (no extra filter)", pd.Series([True] * len(pdf), index=pdf.index))) print(simulate("REJECT triple_value_not_confirmed", ~pdf["bb_issues"].fillna("").str.contains("triple_value_not_confirmed"))) print(simulate("REJECT trap_market_flag", ~(pdf["bb_trap_market"] == True))) print(simulate("REJECT v27 DISAGREE", pdf["v27_consensus"] != "DISAGREE")) print(simulate("REJECT odds_reliability < 0.45", pdf["odds_reliability"].fillna(1.0) >= 0.45)) print(simulate("REJECT odds in 1.80-2.20", (pdf["odds"].fillna(0) < 1.80) | (pdf["odds"].fillna(0) >= 2.20))) print(simulate("REJECT ev_edge < 0", pdf["ev_edge"].fillna(0) >= 0)) print(simulate("REJECT ev_edge < 0.05", pdf["ev_edge"].fillna(0) >= 0.05)) print() print(" COMBINED rules:") # Stack 1: drop triple_not_confirmed + trap_market + DISAGREE s1 = ( ~pdf["bb_issues"].fillna("").str.contains("triple_value_not_confirmed") & ~(pdf["bb_trap_market"] == True) & (pdf["v27_consensus"] != "DISAGREE") ) print(simulate("STACK1: !triple_not_conf & !trap & !disagree", s1)) # Stack 2: + edge>=0 s2 = s1 & (pdf["ev_edge"].fillna(0) >= 0) print(simulate("STACK2: STACK1 + edge >= 0", s2)) # Stack 3: + reliability>=0.45 s3 = s2 & (pdf["odds_reliability"].fillna(1.0) >= 0.45) print(simulate("STACK3: STACK2 + reliability >= 0.45", s3)) # Stack 4: + odds outside 1.80-2.20 s4 = s3 & ((pdf["odds"].fillna(0) < 1.80) | (pdf["odds"].fillna(0) >= 2.20)) print(simulate("STACK4: STACK3 + odds NOT in 1.80-2.20", s4)) print(f"\n{'─' * 78}") print("DONE.") if __name__ == "__main__": main()