Befund: nur 46%/60% der echten Durchbrüche haben P≥40% — Durchbrüche sind über alle P-Stufen verstreut (Basisrate ~37%, AUC ~0,71). Modell kalibriert (Precision@40% = 53-57%). Schwelle 0,40 würde Abpraller mitlaufen lassen → außerhalb des validierten 0,50-0,60-Plateaus. sr_close_pbreak bleibt 0,55. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
119 lines
5.7 KiB
Python
119 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""User-Frage (2026-07-24): „Passieren die meisten Durchbrüche schon ab P>40 %?"
|
|
Recall/Precision der P(break)-Schwelle an ECHTEN Level-Touches (Ziel-Seite = die
|
|
Population, auf die der S/R-Auto-Close wirkt), 2 Halbjahre, Modell = live
|
|
`core.engine._p_break`, Break/Bounce-Definition trainingsgleich (_BRK_ATR=0,5/
|
|
_BRK_W=12 M5-Bars).
|
|
|
|
Antwortet direkt:
|
|
(A) Verteilung: von ALLEN echten Durchbrüchen — wie viel % hatten P≥X? (Recall)
|
|
(B) Precision: von den Touches mit P≥X — wie viel % brachen wirklich durch?
|
|
(C) Anteil der Touches, die bei Schwelle X als „läuft durch" gelten.
|
|
Schwellen X = 30/35/40/45/50/55/60 %. Bezug: sr_close_pbreak steuert genau diesen
|
|
Schnitt (close wenn P<X, laufen lassen wenn P≥X).
|
|
"""
|
|
import sys
|
|
import MetaTrader5 as mt5
|
|
from core.engine import _p_break
|
|
from core.wave_rec import (_EMA_FAST, _EMA_SLOW, _N_BARS, _ANGLE_LR, _ANGLE_DEAD,
|
|
_REVERSAL_STRETCH, _STRETCH_MAX)
|
|
from core.analysis import calc_trend_angle
|
|
|
|
_MAXH = 200; _ATRMIN = 0.12; _PIV_K = 3; _LOOKBACK = 300; _BRK_W = 12; _BRK_ATR = 0.5
|
|
_THRS = [0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60]
|
|
|
|
|
|
def _ema(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):
|
|
"""Ziel-Seiten-Touches (Wave-Signal-Richtung → nächstes Gegenlevel), je Touch
|
|
(p_break, brk 1/0). Break/Bounce nach _BRK_ATR/_BRK_W, Timeout = Bounce."""
|
|
out = []
|
|
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 <= -_REVERSAL_STRETCH and ad >= _ANGLE_DEAD: d = 1
|
|
elif stretch >= _REVERSAL_STRETCH 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
|
|
entry = C[i]
|
|
phis = [H[j] for j in range(i-_LOOKBACK+_PIV_K, i-_PIV_K) if H[j] == max(H[j-_PIV_K:j+_PIV_K+1])]
|
|
plos = [L[j] for j in range(i-_LOOKBACK+_PIV_K, i-_PIV_K) if L[j] == min(L[j-_PIV_K:j+_PIV_K+1])]
|
|
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
|
|
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)) >= 2.0*atr: break
|
|
if jt is None: continue
|
|
up = level+d*_BRK_ATR*atr; dn = level-d*_BRK_ATR*atr; brk = None
|
|
for j in range(jt, min(jt+_BRK_W, len(H))):
|
|
if (H[j] >= up) if d > 0 else (L[j] <= up): brk = 1; break
|
|
if (L[j] <= dn) if d > 0 else (H[j] >= dn): brk = 0; break
|
|
if brk is None: brk = 0
|
|
a_j = max(AT[jt] or atr, _ATRMIN)
|
|
mom6 = (C[jt]-C[max(0, jt-6)])*d/a_j; mom3 = (C[jt]-C[max(0, jt-3)])*d/a_j
|
|
wt = 1.0 if (EF[jt]-ES[jt])*d > 0 else 0.0
|
|
p = _p_break(mom6, mom3, wt, abs(level-entry)/a_j)
|
|
out.append((p, brk))
|
|
return out
|
|
|
|
|
|
def report(lbl, ev):
|
|
n = len(ev); breaks = [p for p, b in ev if b == 1]; nb = len(breaks)
|
|
base = 100*nb/n if n else 0
|
|
print(f"\n{lbl}: {n} Level-Touches · echte Durchbrüche {nb} ({base:.0f}% Basisrate)")
|
|
print(f" {'Schwelle':>8} | {'Recall (% der Brüche mit P≥X)':>30} | "
|
|
f"{'Precision (Bruch-Rate bei P≥X)':>30} | {'% Touches „läuft durch\"':>22}")
|
|
for X in _THRS:
|
|
run = [(p, b) for p, b in ev if p >= X]
|
|
recall = 100*sum(1 for p in breaks if p >= X)/nb if nb else 0
|
|
prec = 100*sum(1 for p, b in run if b == 1)/len(run) if run else 0
|
|
share = 100*len(run)/n if n else 0
|
|
mark = " ← 0,55 live" if abs(X-0.55) < 1e-9 else (" ← 0,40 (Frage)" if abs(X-0.40) < 1e-9 else "")
|
|
print(f" {X:>7.0%} | {recall:>28.0f} % | {prec:>28.0f} % | {share:>20.0f} %{mark}")
|
|
|
|
|
|
def main():
|
|
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
|
mt5.initialize()
|
|
sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD") if mt5.symbol_info(c)), None)
|
|
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(C, _EMA_FAST); ES = _ema(C, _EMA_SLOW); AT = _atr_series(H, L, C)
|
|
N = len(C); mid = N//2
|
|
print("="*100)
|
|
print(f" P(break)-Schwelle — Recall/Precision an echten Level-Touches — {sym} M5 ({N} Bars)")
|
|
print(f" Frage: passieren die meisten Durchbrüche schon ab P>40 %? (Recall bei X=0,40)")
|
|
print("="*100)
|
|
for lbl, lo, hi in (("H1 (alt)", _N_BARS, mid), ("H2 (neu)", mid, N)):
|
|
report(lbl, collect(lo, hi, H, L, C, EF, ES, AT))
|
|
print("\n Lesart: Recall = 'so viel % ALLER echten Durchbrüche liegen ≥ Schwelle'")
|
|
print(" (hoch = kaum Runner verpasst). Precision = 'so viel % der ≥-Schwelle-Touches")
|
|
print(" brechen wirklich durch'. sr_close_pbreak = genau dieser Schnitt.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|