#!/usr/bin/env python3
"""S/R-Wellen-Analyse (Track B):
  TEIL 1  Containment: Welcher %-Anteil der Wellen (EMA-Richtungsmoves) bleibt
          INNERHALB der S/R-Linien — d. h. erreicht das gegenüberliegende Level
          und PRALLT AB, statt durchzubrechen? (Basisrate P(break) am Touch.)
  TEIL 2  P(Durchbruch)-Modell: logistische Regression auf beobachtbaren Merkmalen
          am Touch (Anlauf-Momentum 6/3 Bars, EMA-mit-Trend, Level-Distanz), auf H1
          trainiert und auf H2 KALIBRIERT geprüft (predicted vs. echte Bruchrate je
          Bin) — ein P ist nur brauchbar, wenn es out-of-sample kalibriert ist.
Break = nach Touch +0,5×ATR JENSEITS des Levels binnen 12 Bars, bevor 0,5×ATR zurück.
"""
import sys
import numpy as np
import MetaTrader5 as mt5
from core.analysis import calc_trend_angle
from core.wave_rec import (_EMA_FAST, _EMA_SLOW, _N_BARS, _ANGLE_LR, _ANGLE_DEAD,
                           _REVERSAL_STRETCH, _STRETCH_MAX)
_MAXH=200; _ATRMIN=0.12; _PIV_K=3; _LOOKBACK=300; _BRK_W=12; _BRK_ATR=0.5

def _ema_series(v,p):
    k=2.0/(p+1); o=[]; e=v[0]
    for i,x in enumerate(v): e=x if i==0 else x*k+e*(1-k); o.append(e)
    return o
def _atr_series(H,L,C,p=14):
    t=[0.0]
    for i in range(1,len(C)): t.append(max(H[i]-L[i],abs(H[i]-C[i-1]),abs(L[i]-C[i-1])))
    return [(sum(t[max(1,i-p+1):i+1])/max(1,len(t[max(1,i-p+1):i+1]))) if i else None for i in range(len(C))]

def collect(a,b,H,L,C,EF,ES,AT):
    """Sammelt Wellen-Touches → Liste (break?, feat-vector, reached?)."""
    TH=_REVERSAL_STRETCH; out=[]; n_wave=0; n_reach=0
    for i in range(max(a,_N_BARS,_LOOKBACK), min(b,len(C)-_MAXH-1)):
        atr=AT[i]
        if not atr or atr<=0: continue
        atr=max(atr,_ATRMIN); es=ES[i]; ef=EF[i]
        stretch=(C[i]-es)/atr
        ang=calc_trend_angle(C[i-_ANGLE_LR-2:i],_ANGLE_LR); ad=ang-90.0
        d=0
        if stretch<=-TH and ad>=_ANGLE_DEAD: d=1
        elif stretch>=TH and ad<=-_ANGLE_DEAD: d=-1
        elif abs(stretch)<_STRETCH_MAX: d=1 if ef>es else -1 if ef<es else 0
        if not d: continue
        n_wave+=1
        entry=C[i]
        # nächstes gegenüberliegendes Pivot-Level
        phis,plos=[],[]
        for j in range(i-_LOOKBACK+_PIV_K, i-_PIV_K):
            if H[j]==max(H[j-_PIV_K:j+_PIV_K+1]): phis.append(H[j])
            if L[j]==min(L[j-_PIV_K:j+_PIV_K+1]): plos.append(L[j])
        if d>0:
            cands=[p for p in phis if p>entry+0.3*atr]; level=min(cands) if cands else None
        else:
            cands=[p for p in plos if p<entry-0.3*atr]; level=max(cands) if cands else None
        if level is None: continue
        # Touch suchen (bis MAXH), solange die Welle "lebt" (kein 2×ATR-Gegenlauf)
        jt=None
        for j in range(i+1, min(i+_MAXH, len(C)-_BRK_W)):
            if ((H[j]>=level) if d>0 else (L[j]<=level)): jt=j; break
            if ((entry-L[j]) if d>0 else (H[j]-entry))*1 >= 2.0*atr: break  # Welle tot
        if jt is None: continue
        n_reach+=1
        up=level+d*_BRK_ATR*atr; dn=level-d*_BRK_ATR*atr; brk=False
        for j in range(jt, min(jt+_BRK_W, len(H))):
            if (H[j]>=up) if d>0 else (L[j]<=up): brk=True; break
            if (L[j]<=dn) if d>0 else (H[j]>=dn): brk=False; break
        mom6=(C[jt]-C[max(0,jt-6)])*d/atr
        mom3=(C[jt]-C[max(0,jt-3)])*d/atr
        wt=1.0 if (EF[jt]-ES[jt])*d>0 else 0.0
        distlvl=abs(level-entry)/atr
        out.append((1.0 if brk else 0.0, [mom6,mom3,wt,distlvl]))
    return out, n_wave, n_reach

def fit_logreg(X,y,iters=3000,lr=0.3):
    n,m=X.shape; Xb=np.hstack([np.ones((n,1)),X]); w=np.zeros(m+1)
    for _ in range(iters):
        p=1/(1+np.exp(-(Xb@w))); w-=lr*(Xb.T@(p-y))/n
    return w
def predict(w,X):
    return 1/(1+np.exp(-(np.hstack([np.ones((X.shape[0],1)),X])@w)))

def main():
    n=int(sys.argv[1]) if len(sys.argv)>1 else 80000
    mt5.initialize(); sym=None
    for c in ("SpotCrude","USOIL","WTI","XTIUSD"):
        if mt5.symbol_info(c): sym=c; break
    bars=None
    for req in (n,80000,60000,40000):
        bars=mt5.copy_rates_from_pos(sym,mt5.TIMEFRAME_M5,0,req)
        if bars is not None and len(bars)>2000: break
    mt5.shutdown()
    H=[float(b["high"]) for b in bars]; L=[float(b["low"]) for b in bars]; C=[float(b["close"]) for b in bars]
    EF=_ema_series(C,_EMA_FAST); ES=_ema_series(C,_EMA_SLOW); AT=_atr_series(H,L,C)
    mid=len(C)//2
    ev1,w1,r1=collect(_N_BARS,mid,H,L,C,EF,ES,AT)
    ev2,w2,r2=collect(mid,len(C),H,L,C,EF,ES,AT)
    print("="*90)
    print(f"  S/R-WELLEN — {sym} M5 ({len(C)} Bars)  Level=Pivot(k{_PIV_K}), Break=+{_BRK_ATR}×ATR in {_BRK_W} Bars")
    print("="*90)
    print("\nTEIL 1 — Containment (bleibt die Welle innerhalb der S/R-Linien?):")
    for lbl,ev,nw,nr in (("H1 (alt)",ev1,w1,r1),("H2 (neu)",ev2,w2,r2)):
        if not ev: continue
        pbreak=100*sum(e[0] for e in ev)/len(ev)
        print(f"  {lbl}: {nw} Wellen · {100*nr/max(1,nw):.0f}% erreichen das Level · "
              f"davon {pbreak:.0f}% DURCHBRUCH → {100-pbreak:.0f}% bleiben drin (Abpraller)")

    print("\nTEIL 2 — P(Durchbruch)-Modell (Merkmale: mom6, mom3, mit-Trend, Level-Dist):")
    X1=np.array([e[1] for e in ev1]); y1=np.array([e[0] for e in ev1])
    X2=np.array([e[1] for e in ev2]); y2=np.array([e[0] for e in ev2])
    mu=X1.mean(0); sd=X1.std(0)+1e-9
    w=fit_logreg((X1-mu)/sd,y1)
    p2=predict(w,(X2-mu)/sd)
    # AUC (schnell, via Rangvergleich)
    order=np.argsort(p2); ranks=np.empty_like(order,dtype=float); ranks[order]=np.arange(1,len(p2)+1)
    npos=y2.sum(); nneg=len(y2)-npos
    auc=(ranks[y2==1].sum()-npos*(npos+1)/2)/(npos*nneg) if npos and nneg else float('nan')
    print(f"  Trainiert auf H1, geprüft auf H2 · AUC={auc:.3f}  (0,5=Zufall; >0,6=brauchbar trennscharf)")
    print(f"  Koeffizienten (standardisiert): mom6={w[1]:+.2f} mom3={w[2]:+.2f} "
          f"mitTrend={w[3]:+.2f} dist={w[4]:+.2f} · Basis(intercept)={w[0]:+.2f}")
    print("\n  Kalibrierung auf H2 (vorhergesagtes P vs. echte Bruchrate):")
    print(f"  {'P-Bin':<12}{'n':>7}{'Ø-Vorhersage':>14}{'echt-Bruch':>12}")
    edges=[0,0.25,0.30,0.35,0.40,0.45,0.55,1.01]
    for k in range(len(edges)-1):
        m=(p2>=edges[k])&(p2<edges[k+1])
        if m.sum()<30: continue
        print(f"  {edges[k]:.2f}–{edges[k+1]:.2f}   {int(m.sum()):>7}{p2[m].mean():>13.0%}{y2[m].mean():>12.0%}")
    # Nutz-Regel: Trenn-Schwelle
    for thr in (0.40,0.45,0.50):
        run=p2>=thr
        print(f"\n  Regel „laufen lassen wenn P(break)≥{thr:.0%}\": {100*run.mean():.0f}% der Touches laufen, "
              f"davon {100*y2[run].mean():.0f}% brechen wirklich durch · "
              f"Rest schließen: {100*y2[~run].mean():.0f}% brechen (Fehl-Close)")

if __name__=="__main__":
    main()
