"""
streak_analysis.py
==================
Consecutive win/loss streak analysis for both EU and UJ backtests.

For each pair (trades in chronological order):
  - longest winning streak, longest losing streak
  - full distribution of streak lengths (how often N-in-a-row happens)
  - the worst losing streak's $ damage and when it occurred
  - current/most-recent streak
This tells you what runs of losses to psychologically expect, and whether
recent live runs are within normal historical bounds.

EU file: trades_real_costs.csv  (P&L col dollar_pnl_real, sort by entry_time)
UJ file: usdjpy_trades_real_costs.csv (P&L col pnl_real, sort by entry_time)
"""
import csv, sys
from pathlib import Path
from datetime import datetime
from collections import defaultdict

def resolve_base():
    for b in [Path(r"C:\Users\Administrator\OneDrive\fx_macro_intraday"),
              Path(r"C:\Users\paul_\OneDrive\fx_macro_intraday")]:
        if b.exists(): return b
    sys.exit("base not found")
BASE=resolve_base()

def parse_dt(s):
    s=(s or "").strip()
    for f in ("%Y-%m-%d %H:%M:%S","%Y-%m-%d %H:%M","%d/%m/%Y %H:%M"):
        try: return datetime.strptime(s[:19], f)
        except ValueError: continue
    return None

def load(path, pnl_col):
    rows=[]
    with open(path, newline="", encoding="utf-8-sig") as f:
        for r in csv.DictReader(f):
            e=parse_dt(r.get("entry_time"))
            try: pnl=float(r[pnl_col])
            except (ValueError,KeyError): continue
            if e is None: continue
            rows.append((e,pnl))
    rows.sort(key=lambda x:x[0])
    return rows

def analyze(rows, label):
    print(f"\n{'='*64}\n{label}  ({len(rows)} trades)\n{'='*64}")
    # build streaks: a streak is a run of same-sign results
    win_streaks=[]; loss_streaks=[]
    cur_type=None; cur_len=0; cur_pnl=0.0; cur_start=None
    worst_loss_streak={"len":0,"pnl":0.0,"start":None,"end":None}
    longest_win={"len":0,"pnl":0.0,"start":None,"end":None}
    longest_loss={"len":0,"pnl":0.0,"start":None,"end":None}
    streaks=[]  # (type, len, pnl, start, end)
    prev_dt=None
    for (dt,pnl) in rows:
        t = "W" if pnl>0 else "L"
        if t==cur_type:
            cur_len+=1; cur_pnl+=pnl; cur_end=dt
        else:
            if cur_type is not None:
                streaks.append((cur_type,cur_len,cur_pnl,cur_start,cur_end))
            cur_type=t; cur_len=1; cur_pnl=pnl; cur_start=dt; cur_end=dt
    if cur_type is not None:
        streaks.append((cur_type,cur_len,cur_pnl,cur_start,cur_end))

    win_lens=[s[1] for s in streaks if s[0]=="W"]
    loss_lens=[s[1] for s in streaks if s[0]=="L"]
    for s in streaks:
        if s[0]=="W" and s[1]>longest_win["len"]:
            longest_win={"len":s[1],"pnl":s[2],"start":s[3],"end":s[4]}
        if s[0]=="L":
            if s[1]>longest_loss["len"]:
                longest_loss={"len":s[1],"pnl":s[2],"start":s[3],"end":s[4]}
            if s[2]<worst_loss_streak["pnl"]:
                worst_loss_streak={"len":s[1],"pnl":s[2],"start":s[3],"end":s[4]}

    print(f"  Longest WIN streak : {longest_win['len']} trades  "
          f"(+${longest_win['pnl']:,.0f})  "
          f"{longest_win['start'].date() if longest_win['start'] else '?'} "
          f"-> {longest_win['end'].date() if longest_win['end'] else '?'}")
    print(f"  Longest LOSS streak: {longest_loss['len']} trades  "
          f"(${longest_loss['pnl']:,.0f})  "
          f"{longest_loss['start'].date() if longest_loss['start'] else '?'} "
          f"-> {longest_loss['end'].date() if longest_loss['end'] else '?'}")
    print(f"  Worst LOSS streak $: {worst_loss_streak['len']} trades  "
          f"(${worst_loss_streak['pnl']:,.0f})  "
          f"{worst_loss_streak['start'].date() if worst_loss_streak['start'] else '?'} "
          f"-> {worst_loss_streak['end'].date() if worst_loss_streak['end'] else '?'}")

    # distributions
    def dist(lens, kind):
        d=defaultdict(int)
        for L in lens: d[L]+=1
        print(f"\n  {kind} streak distribution (how many times each run length occurred):")
        for L in sorted(d):
            bar="#"*min(40,d[L])
            print(f"    {L:>2} in a row : {d[L]:>4}  {bar}")
    dist(win_lens,"WIN")
    dist(loss_lens,"LOSS")

    # most recent streak
    last=streaks[-1]
    print(f"\n  Most recent streak : {last[1]} {('wins' if last[0]=='W' else 'losses')} "
          f"in a row (${last[2]:,.0f}), ending {last[4].date() if last[4] else '?'}")

    # probability context
    n_loss_streaks=len(loss_lens)
    ge3=sum(1 for L in loss_lens if L>=3)
    ge4=sum(1 for L in loss_lens if L>=4)
    ge5=sum(1 for L in loss_lens if L>=5)
    print(f"\n  Losing streaks >=3 in a row: {ge3} times over the backtest")
    print(f"  Losing streaks >=4 in a row: {ge4} times")
    print(f"  Losing streaks >=5 in a row: {ge5} times")
    print(f"  -> Expect occasional runs this long; they are NORMAL, not a broken model.")

eu=load(BASE/"data"/"processed"/"trades"/"trades_real_costs.csv","dollar_pnl_real")
uj=load(BASE/"data"/"processed"/"usdjpy_trades_real_costs.csv","pnl_real")
analyze(eu,"EURUSD")
analyze(uj,"USDJPY")

# combined chronological (both pairs interleaved by entry time)
combined=sorted(eu+uj, key=lambda x:x[0])
analyze(combined,"COMBINED (EU+UJ interleaved by time)")
print(f"\n{'='*64}")
print("Use the longest/worst loss streaks to know what to expect emotionally and")
print("in drawdown. If a live losing run is <= the historical max, it's normal.")
print("="*64)
