#!/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 ef0: 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=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])&(p27}{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()