Files
AH-Oil-Trader/analyze_pbreak_threshold.py
T
Axel HocksandClaude Opus 4.8 fe8b439f1d analyze_pbreak_threshold.py: Recall/Precision der P(break)-Schwelle
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>
2026-07-27 11:54:43 +02:00

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()