diff --git a/CLAUDE.md b/CLAUDE.md index 3344a4a..71f1e53 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -319,6 +319,61 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: ~15 s→5 s schneller erkannt, dann ~1 s Handeln (`_pos_loop`). Gleiche validierte M5-Regel, reine Infra (kein Backtest nötig). **Variante B (Squeeze auf M1) = OFFEN/UNBELEGT** — erst backtesten, wenn der M1-Logger (`candles_m1`, seit 2026-07-21) ab ~2027-01 genug History hat. +- **⚠⚠⚠ VERWORFENE SETUPS AUF DER AUSFÜHRUNGS-ACHSE NEU GEMESSEN — und das + Ergebnis stellt den LIVE-Squeeze mit in Frage (`backtest_verworfene_v2.py`, + 2026-08-06, User: „backteste alle verworfenen Signale").** + ⚠ Bewusst **kein** erneuter Komplettlauf — der lief am 04.08. (14 Klassen, alle + blieben verworfen). Neu ist genau **eine** Achse, der Einstiegspreis, und sie + wurde mit der heutigen Korrektur angewandt: **Fill bei BERÜHRUNG**, nicht bei + Schluss-Bruch. Geprüft wurde nur, wo die Achse überhaupt greifen kann — + **stehendes Level + Ausbruchsrichtung** (Momentum scheitert, weil sein Level + wandert; Reversions-Einstiege werden von einer ruhenden Order sogar schlechter, + `backtest_candle_fade2.py`). + | Setup | A) Markt | B) Close-Bruch | **C) Berührung (real)** | n | + |---|---|---|---|---| + | *KONTROLLE Squeeze* | −0,180/−0,017 | +0,560/+0,755 | **+0,133/+0,293** | 1094 | + | ORB EU 15/30 · US 15/30 | negativ | +0,45…+0,73 | **alle 4 ❌** | ~200 | + | PDH / PDL Bruch | −0,280/+0,044 | +0,906/+1,368 | **+0,246/+0,464** | 147 | + | Asian Range Bruch | +0,021/−0,116 | +0,928/+0,718 | **+0,142/+0,134** | 218 | + | Equal H/L Bruch | −0,279/+0,228 | +0,574/+0,905 | **+0,218/+0,170** | 168 | + ✅ Die Kontrolle reproduziert `backtest_squeeze_touchfill.py` (+0,133/+0,293 + gegen +0,124/+0,292) — Pipeline in Ordnung. ✅ **ORB bleibt verworfen** in allen + vier Varianten (bestätigt 2026-07-17). ✅ Die drei Überlebenden sind + **nachbar-robust** (PDH/PDL über die Gültigkeitsdauer, Asian Range über das + Fenster, Equal H/L über Toleranz und Gültigkeit — 11 Zellen, keine kippt). + ⚠⚠ **Das sind KEINE drei neuen Signale.** PDH/PDL, Asian-Range-Extrem, Equal + Highs/Lows und die Squeeze-Box sind **dasselbe**: ein waagerechtes Level aus + früheren Kursextremen, gebrochen, mit ruhender Stop-Order betreten und mit dem + kanonischen Trailing verlassen. Deckt sich mit der Multi-Instrument-Kontrolle + („beliebige Box" ist überall positiv, die Kompression trägt +0,009…+0,155). + **Der Edge liegt in der Kombination Ausführung + Exit, nicht in der Regel, die + das Level auswählt.** + ⚠⚠⚠ **UND JETZT DER TEIL, DER AUCH DAS LAUFENDE SYSTEM BETRIFFT — mit der + gemessenen Exit-Slippage (`analyze_slippage_holdtime.py`) bleibt fast nichts + übrig:** + | Setup | 0 $ | **0,0124 $** (Tagfenster) | 0,0370 $ (alle Std) | + |---|---|---|---| + | **Squeeze (LIVE!)** | +0,133/+0,293 | **−0,002/+0,221** | −0,270/+0,080 | + | PDH / PDL | +0,246/+0,464 | **+0,099/+0,386** | −0,192/+0,231 | + | Equal H/L | +0,218/+0,170 | +0,077/+0,092 | −0,202/−0,062 | + | Asian Range | +0,142/+0,134 | −0,011/+0,065 | −0,316/−0,071 | + **Die Edges dieser ganzen Familie liegen in derselben Größenordnung wie die + Exit-Slippage.** Beim günstigsten gemessenen Wert hält nur **PDH/PDL** beide + Hälften klar; Equal H/L ist dünn, Asian Range fällt — und der **live laufende + Auto-Squeeze steht in H1 bei −0,002, also auf null**. + ⚠ Die Slippage ist dabei eine **Untergrenze** (s. Herleitung dort) — der wahre + Abzug ist größer, nicht kleiner. + ✅ **Konsequenz: KEIN neues Setup bauen.** Drei weitere Module derselben Klasse + addieren Frequenz zu einem Edge, der von den Ausstiegskosten aufgezehrt wird. + Der nächste Hebel ist **die Ausführungsqualität am EXIT** (dort ist der Posten, + der alles frisst), nicht ein weiteres Einstiegs-Signal. + ⚠ **Nicht mitgemessen, ehrlich benannt:** (a) **Volume-Profile-LVN** — stehendes + Level, aber das Profil müsste je Bar neu gebinnt werden (Laufzeit); (b) + **Chartmuster-/Doppeltop-Nackenlinie** — stehendes Level und Ausbruchsrichtung, + qualifiziert sich also, braucht aber den Muster-Detektor kausal getrieben. + **Beide bleiben offen.** Alle übrigen Ablehnungen (Inter-Market, Chop-Gate, + Meta-Labeling, RSI/MACD/ADX, HTF-Winkel, H1-Stärke, Konfluenz-Strafe) haben gar + kein Level — die Achse kann dort nichts ändern. - **⚠⚠ DER SQUEEZE TRÄGT AUCH AUF ANDEREN INSTRUMENTEN — 7 von 9 (gemessen `backtest_squeeze_multi.py`, 2026-08-06).** Anlass: `analyze_signal_live.py` hat gezeigt, dass die Richtung ein Münzwurf ist; das Grundproblem ist also nicht die diff --git a/analyze_verworfene_nachbarn.py b/analyze_verworfene_nachbarn.py new file mode 100644 index 0000000..f8acb1d --- /dev/null +++ b/analyze_verworfene_nachbarn.py @@ -0,0 +1,80 @@ +"""Nachbar-Pruefung der drei Setups, die den vollen Lauf bestanden haben. + +Kippt ein Nachbar-Parameter das Vorzeichen, ist der Treffer Rauschen — genau das +Kriterium, an dem ORB (2026-07-17) und die SMC-Sweeps gescheitert sind. +""" +import sys +sys.path.insert(0, r"c:\Users\ah\Downloads\tradingrobot") +from datetime import timedelta +import MetaTrader5 as mt5 +from backtest_cost_gate import _atr_series, kennzahlen +import backtest_verworfene_v2 as V + +mt5.initialize() +sym = "SpotCrude" +b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, 80000) +point = mt5.symbol_info(sym).point +mt5.shutdown() +H = [float(x["high"]) for x in b]; L = [float(x["low"]) for x in b] +C = [float(x["close"]) for x in b]; T = [int(x["time"]) for x in b] +SP = [float(x["spread"]) * point for x in b] +A = _atr_series(H, L, C); m = len(C) // 2 + + +def zeig(lbl, lv): + k1 = kennzahlen(V.lauf(lv, H, L, C, SP, A, 0, m, "touch")) + k2 = kennzahlen(V.lauf(lv, H, L, C, SP, A, m, len(C), "touch")) + if not k1 or not k2: + print(f" {lbl:<28} — zu wenige"); return False + gut = k1["oer"] > 0 and k2["oer"] > 0 and k1["pf"] > 1 and k2["pf"] > 1 + print(f" {lbl:<28} H1 {k1['oer']:>+7.3f} PF {k1['pf']:.2f} n{k1['n']:<5} | " + f"H2 {k2['oer']:>+7.3f} PF {k2['pf']:.2f} n{k2['n']:<5} {'OK' if gut else 'KIPPT'}") + return gut + + +print("=" * 96) +print(" NACHBAR-PRUEFUNG (Variante C = Beruehrungs-Fill, 80k M5-Bars)") +print("=" * 96) + +print("\n PDH/PDL — Gueltigkeitsdauer variiert:") +alle_pdh = True +basis = V.tagesextrem(H, L, T) +alle_pdh &= zeig("voller Folgetag (Basis)", basis) +for kuerz in (0.5, 0.25): + lv = [(a, a + int((bb - a) * kuerz), l, d) for a, bb, l, d in basis] + alle_pdh &= zeig(f"nur erste {int(kuerz*100)} % des Tages", lv) + +print("\n Asian Range — Fenster variiert:") +alle_asia = True +alle_asia &= zeig("00–08 Uhr (Basis)", V.asia(H, L, T)) +for h_ende in (7, 9): + orig = V.asia.__code__ + lv = [] + tag, sam = None, [] + for i in range(len(H)): + bz = V._berlin(T[i]) + if bz.date() != tag: + tag, sam = bz.date(), [] + if bz.hour < h_ende: + sam.append(i) + elif sam and bz.hour == h_ende and bz.minute < 5: + hi = max(H[j] for j in sam); lo = min(L[j] for j in sam) + bis = i + while bis < len(H) - 2 and V._berlin(T[bis]).date() == tag: + bis += 1 + lv.append((i, bis, hi, +1)); lv.append((i, bis, lo, -1)); sam = [] + alle_asia &= zeig(f"00–{h_ende:02d} Uhr", lv) + +print("\n Equal H/L — Toleranz und Gueltigkeit variiert:") +alle_eq = True +alle_eq &= zeig("tol 0.15 · 120 Bars (Basis)", V.equal_hl(H, L, A)) +for tol in (0.10, 0.25): + alle_eq &= zeig(f"tol {tol} · 120 Bars", V.equal_hl(H, L, A, tol=tol)) +for g in (60, 240): + alle_eq &= zeig(f"tol 0.15 · {g} Bars", V.equal_hl(H, L, A, gueltig=g)) + +print() +print("=" * 96) +for n, ok in (("PDH/PDL", alle_pdh), ("Asian Range", alle_asia), ("Equal H/L", alle_eq)): + print(f" {n:<14} → {'ROBUST' if ok else 'KIPPT bei den Nachbarn — verworfen'}") +print("=" * 96) diff --git a/analyze_verworfene_slippage.py b/analyze_verworfene_slippage.py new file mode 100644 index 0000000..df6c83f --- /dev/null +++ b/analyze_verworfene_slippage.py @@ -0,0 +1,57 @@ +"""Halten die drei Ueberlebenden auch mit gemessener Exit-Slippage? + +`backtest_verworfene_v2.lauf` rechnet nur den Spread. Aus +`analyze_slippage_holdtime.py`: E[Slippage] >= 0,0370 $ ueber alle Stunden, +0,0124 $ im Fenster 10-17. Bei ATR_M5 ~0,15 sind das 0,08-0,25 R je Trade — +gegen Edges von +0,13..+0,25 ist das nicht vernachlaessigbar. +""" +import sys +sys.path.insert(0, r"c:\Users\ah\Downloads\tradingrobot") +import MetaTrader5 as mt5 +from backtest_cost_gate import _atr_series, kennzahlen +from core.exit_model import LIVE, simulate +import backtest_verworfene_v2 as V + +mt5.initialize(); sym = "SpotCrude" +b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, 80000) +point = mt5.symbol_info(sym).point +mt5.shutdown() +H=[float(x["high"]) for x in b]; L=[float(x["low"]) for x in b] +C=[float(x["close"]) for x in b]; T=[int(x["time"]) for x in b] +SP=[float(x["spread"])*point for x in b] +A=_atr_series(H,L,C); m=len(C)//2 + + +def lauf_slip(levels, lo, hi, slip_usd): + ev=[] + for ab,bis,lvl,d in levels: + for i in range(max(ab,1), min(bis+1, len(C)-2)): + atr=A[i-1] + if not atr or atr < V._ATRMIN: continue + if not ((H[i]>=lvl) if d>0 else (L[i]<=lvl)): continue + ev.append((i,d,lvl,atr,SP[i])); break + ev.sort(); rs=[]; frei=lo + for i,d,entry,atr,sp in ev: + if not (lo<=i0 else 0.0225)/atr - slip_usd/atr) + frei=xb+1 + return rs + +setups=[("KONTROLLE Squeeze", V.squeeze_kontrolle(H,L,C,A)), + ("PDH / PDL Bruch", V.tagesextrem(H,L,T)), + ("Asian Range Bruch", V.asia(H,L,T)), + ("Equal H/L Bruch", V.equal_hl(H,L,A))] +print("="*92) +print(" EXIT-SLIPPAGE angesetzt (gemessen: >=0,0124 $ im Tagfenster, >=0,0370 $ gesamt)") +print("="*92) +print(f" {'Setup':<22} {'0,000 $':>18} {'0,0124 $':>18} {'0,0370 $':>18}") +for name,lv in setups: + z=f" {name:<22}" + for s in (0.0, 0.0124, 0.0370): + k1=kennzahlen(lauf_slip(lv,0,m,s)); k2=kennzahlen(lauf_slip(lv,m,len(C),s)) + z += (f" {k1['oer']:>+7.3f}/{k2['oer']:>+7.3f}" if k1 and k2 else f" {'—':^16}") + print(z) +print() +print(" ⚠ Die Edges liegen bei +0,13..+0,25 R — eine Slippage von 0,08..0,25 R") +print(" je Trade ist dagegen keine Randgroesse, sondern der entscheidende Posten.") diff --git a/backtest_verworfene_v2.py b/backtest_verworfene_v2.py new file mode 100644 index 0000000..0d51a74 --- /dev/null +++ b/backtest_verworfene_v2.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +"""Die verworfenen Signale — neu gemessen auf der EINEN Achse, die heute neu ist. + +ANLASS (2026-08-06, User: „backteste alle verworfenen Signale"). + +⚠ WAS DAS HIER NICHT IST: ein erneuter Durchlauf aller Ablehnungen. Der lief am +2026-08-04 (`run_all_backtests.py`, 14 Klassen, alle blieben verworfen) und waere +nur eine Wiederholung — mit dem Mehrfachvergleichs-Problem obendrauf. + +NEU ist genau eine Achse: der **Einstiegspreis**. Sie hat am 05.08. Squeeze und +Auto-Signal von negativ auf positiv gedreht. ALLE alten Ablehnungen wurden mit +Markt-/Bar-Close-Einstieg gemessen — sie koennten also unter einer Annahme +gefallen sein, die nicht mehr gilt. + +⚠⚠ ABER MIT DER HEUTIGEN KORREKTUR. `backtest_momentum_v2.py` hat gezeigt, dass +„Einstieg am Level" zweideutig ist: + (B) Fill, WO der Bar-CLOSE das Level bricht → Look-ahead, ~+0,3…0,45 R zu gut + (C) Fill bei der ERSTEN BERUEHRUNG → das, was live passiert +Gemessen wird deshalb C. B laeuft nur als Diagnose mit, um den Aufschlag sichtbar +zu machen. + +⚠⚠ UND NUR DORT, WO DIE ACHSE UEBERHAUPT GREIFEN KANN. Der Vorteil entsteht an +Levels, die **STILLSTEHEN** (Squeeze-Box: +0,124/+0,292) — nicht an mitwandernden +(Momentum: −0,04…−0,09). Geprueft werden deshalb die verworfenen Setups mit einem +stehenden Level und einer AUSBRUCHS-Richtung: + ORB (EU/US) · PDH/PDL · Asian Range · Equal Highs/Lows +⚠ Bewusst NICHT dabei, weil die Achse dort nichts aendern kann: + · Momentum, Inter-Market, Chop-Gate, Meta-Labeling, RSI/MACD/ADX — kein Level. + · Reversal/Bounce, Kerzen-Fade, POC-Reversion, Fibonacci, Kanalrand — REVERSIONS- + Einstiege. Dort fuellt eine ruhende Order gerade dann, wenn man falsch liegt + (gemessen `backtest_candle_fade2.py`: Limit am Extrem macht es SCHLECHTER). + · Volume Profile — Level steht zwar, aber das Profil muesste je Bar neu gebinnt + werden; Laufzeit unverhaeltnismaessig. Offen. + +VORAB FIXIERTE ENTSCHEIDUNGSREGEL (vor dem ersten Lauf): + (1) in Variante C: ØR > 0 UND PF > 1 in BEIDEN Haelften + (2) mind. 40 Trades je Haelfte + (3) muss die KONTROLLE (Squeeze, +0,124/+0,292) nicht schlagen, aber selbst + beidhaelftig tragen + (4) Mehrfachvergleich wird ausgewiesen: bei ~10 Tests sind ~0,5 Zufallstreffer + zu erwarten — ein einzelnes ✅ ist damit schwach. + +Aufruf: python backtest_verworfene_v2.py [bars] +""" +import sys +from datetime import datetime, timedelta, timezone +from zoneinfo import ZoneInfo + +import MetaTrader5 as mt5 + +import core.wave_rec as wr +from backtest_cost_gate import _atr_series, kennzahlen +from core.exit_model import LIVE, simulate + +_BERLIN = ZoneInfo("Europe/Berlin") +_BROKER = timezone(timedelta(hours=3)) +_UTC = timezone.utc +_ATRMIN = 0.06 +_MIN_N = 40 + + +def _berlin(ts): + return (datetime.fromtimestamp(int(ts), _UTC) + .replace(tzinfo=_BROKER).astimezone(_BERLIN)) + + +# ───────────────────────────────────────────────────────────────────────── +# Setups: jedes liefert STEHENDE Level als (ab_bar, bis_bar, level, richtung) +# richtung: +1 = Ausbruch nach oben ist LONG, −1 = nach unten ist SHORT +# ───────────────────────────────────────────────────────────────────────── +def orb(H, L, T, A, start_h, start_m, minuten): + """Opening Range Breakout: Box = erste `minuten` nach dem Open, gilt bis 22:00.""" + out, tag, box = [], None, None + for i in range(len(H)): + b = _berlin(T[i]) + d = b.date() + if d != tag: + tag, box = d, None + beginn = b.replace(hour=start_h, minute=start_m, second=0, microsecond=0) + ende = beginn + timedelta(minutes=minuten) + if box is None and b >= ende: + js = [j for j in range(max(0, i - 40), i) + if beginn <= _berlin(T[j]) < ende] + if js: + hi, lo = max(H[j] for j in js), min(L[j] for j in js) + bis = i + while bis < len(H) - 2 and _berlin(T[bis]).date() == d \ + and _berlin(T[bis]).hour < 22: + bis += 1 + out.append((i, bis, hi, +1)) + out.append((i, bis, lo, -1)) + box = True + return out + + +def tagesextrem(H, L, T): + """PDH/PDL — Hoch/Tief des Vortages, gilt den ganzen Folgetag.""" + tage = {} + for i in range(len(H)): + d = _berlin(T[i]).date() + r = tage.setdefault(d, [i, i, H[i], L[i]]) + r[1] = i; r[2] = max(r[2], H[i]); r[3] = min(r[3], L[i]) + folge = sorted(tage) + out = [] + for k in range(1, len(folge)): + _, _, hi, lo = tage[folge[k - 1]] + a, b, _, _ = tage[folge[k]] + out.append((a, b, hi, +1)) + out.append((a, b, lo, -1)) + return out + + +def asia(H, L, T): + """Asian Range 00:00–08:00 Berlin, gilt 08:00 bis Tagesende.""" + out, tag, gesammelt = [], None, [] + for i in range(len(H)): + b = _berlin(T[i]) + if b.date() != tag: + tag, gesammelt, gesetzt = b.date(), [], False + if b.hour < 8: + gesammelt.append(i) + elif gesammelt and not globals().get("_dummy"): + if b.hour == 8 and b.minute < 5: + hi = max(H[j] for j in gesammelt); lo = min(L[j] for j in gesammelt) + bis = i + while bis < len(H) - 2 and _berlin(T[bis]).date() == tag: + bis += 1 + out.append((i, bis, hi, +1)) + out.append((i, bis, lo, -1)) + gesammelt = [] + return out + + +def equal_hl(H, L, A, tol=0.15, gueltig=120): + """Equal Highs/Lows aus M5-Pivots (k=3) — zwei Extrema innerhalb tol×ATR.""" + ph = [i for i in range(3, len(H) - 3) + if H[i] == max(H[i - 3:i + 4])] + pl = [i for i in range(3, len(L) - 3) + if L[i] == min(L[i - 3:i + 4])] + out = [] + for piv, arr, d in ((ph, H, +1), (pl, L, -1)): + for a in range(1, len(piv)): + i0, i1 = piv[a - 1], piv[a] + if not (6 <= i1 - i0 <= 240): + continue + atr = A[i1] + if not atr or atr < _ATRMIN: + continue + if abs(arr[i1] - arr[i0]) > tol * atr: + continue + lvl = (arr[i1] + arr[i0]) / 2.0 + out.append((i1 + 4, min(i1 + 4 + gueltig, len(H) - 2), lvl, d)) + return out + + +def squeeze_kontrolle(H, L, C, A): + """Kontrolle: die Squeeze-Box als stehendes Level (muss +0,124/+0,292 treffen).""" + out = [] + for i in range(wr._SQ_N + 2, len(C) - 2): + atr = A[i - 1] + if not atr or atr < _ATRMIN: + continue + hb = H[i - wr._SQ_N - 1:i - 1]; lb = L[i - wr._SQ_N - 1:i - 1] + if not hb or not lb: + continue + hi, lo = max(hb), min(lb) + if (hi - lo) / atr > wr._SQ_MULT: + continue + out.append((i, i, hi + wr._SQ_K * atr, +1)) + out.append((i, i, lo - wr._SQ_K * atr, -1)) + return out + + +# ───────────────────────────────────────────────────────────────────────── +def lauf(levels, H, L, C, SP, A, lo, hi, modus): + """modus: 'markt' = Bar-Close · 'close' = Level, aber nur bei Close-Bruch + · 'touch' = Level, Fill bei erster Beruehrung (REALITAET).""" + ereignisse = [] + for ab, bis, lvl, d in levels: + for i in range(max(ab, 1), min(bis + 1, len(C) - 2)): + atr = A[i - 1] + if not atr or atr < _ATRMIN: + continue + beruehrt = (H[i] >= lvl) if d > 0 else (L[i] <= lvl) + if not beruehrt: + continue + gebrochen = (C[i] > lvl) if d > 0 else (C[i] < lvl) + if modus != "touch" and not gebrochen: + break # Level verbraucht (Docht) — kein Signal + entry = C[i] if modus == "markt" else lvl + ereignisse.append((i, d, entry, atr, SP[i])) + break # ein Fill je Level + ereignisse.sort() + rs, frei_ab = [], lo + for i, d, entry, atr, sp in ereignisse: + if not (lo <= i < hi) or i < frei_ab: + continue + r, xb = simulate(entry, d, atr, H, L, C, i + 1, LIVE, + timestop=True, use_tp=True, ret_bar=True) + rs.append(r - (sp if sp > 0 else 0.0225) / atr) + frei_ab = xb + 1 + return rs + + +def main(): + n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000 + if not mt5.initialize(): + print("MT5 nicht erreichbar"); return 1 + sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD") + if mt5.symbol_info(c)), None) + b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n) + point = mt5.symbol_info(sym).point + mt5.shutdown() + H = [float(x["high"]) for x in b]; L = [float(x["low"]) for x in b] + C = [float(x["close"]) for x in b]; T = [int(x["time"]) for x in b] + SP = [float(x["spread"]) * point for x in b] + A = _atr_series(H, L, C) + m = len(C) // 2 + + setups = [ + ("KONTROLLE Squeeze", squeeze_kontrolle(H, L, C, A)), + ("ORB EU 15 min", orb(H, L, T, A, 10, 0, 15)), + ("ORB EU 30 min", orb(H, L, T, A, 10, 0, 30)), + ("ORB US 15 min", orb(H, L, T, A, 16, 30, 15)), + ("ORB US 30 min", orb(H, L, T, A, 16, 30, 30)), + ("PDH / PDL Bruch", tagesextrem(H, L, T)), + ("Asian Range Bruch", asia(H, L, T)), + ("Equal H/L Bruch", equal_hl(H, L, A)), + ] + + print("=" * 104) + print(f" VERWORFENE SETUPS — neu auf der Ausfuehrungs-Achse ({sym}, {len(C)} M5-Bars)") + print("=" * 104) + print(" Geprueft wird NUR, was ein STEHENDES Level + Ausbruchsrichtung hat —") + print(" dort kann die Achse ueberhaupt wirken (Squeeze ja, Momentum nein).") + print(" Massgeblich ist Spalte C. B steht nur da, um den Look-ahead zu zeigen.") + print() + print(f" {'Setup':<22} {'A) Markt H1/H2':^22} {'B) Close-Bruch H1/H2':^24} " + f"{'C) BERUEHRUNG H1/H2':^24} n Urteil") + print(" " + "-" * 100) + + treffer = [] + for name, lv in setups: + z = f" {name:<22}" + erg = {} + for modus in ("markt", "close", "touch"): + k1 = kennzahlen(lauf(lv, H, L, C, SP, A, 0, m, modus)) + k2 = kennzahlen(lauf(lv, H, L, C, SP, A, m, len(C), modus)) + erg[modus] = (k1, k2) + z += (f" {k1['oer']:>+7.3f}/{k2['oer']:>+7.3f} " if k1 and k2 + else f" {'—':^16} ") + c1, c2 = erg["touch"] + if not c1 or not c2: + print(z + " —"); continue + ok = (c1["oer"] > 0 and c2["oer"] > 0 and c1["pf"] > 1 and c2["pf"] > 1 + and c1["n"] >= _MIN_N and c2["n"] >= _MIN_N) + z += f" {c1['n']:>4} {'✅' if ok else '❌'}" + if c1["n"] < _MIN_N or c2["n"] < _MIN_N: + z += " (duenn)" + print(z) + if ok and not name.startswith("KONTROLLE"): + treffer.append(name) + + print() + print("=" * 104) + print(f" ERGEBNIS: {len(treffer)} von {len(setups)-1} verworfenen Setups tragen " + f"auch mit realistischem Fill") + if treffer: + print(f" → {', '.join(treffer)}") + print(" ⚠ Vor jeder Konsequenz: Nachbar-Parameter pruefen und gegen die") + print(" Kontrolle halten. Ein einzelnes ✅ bei ~7 Tests ist erwartbar.") + else: + print(" → Die Ablehnungen bleiben bestehen. Die Ausfuehrungs-Achse rettet") + print(" keines dieser Setups.") + print() + print(" ⚠ Spalte B gegen C gelesen = der Look-ahead-Aufschlag. Ist er gross,") + print(" war das Setup nie so gut, wie ein Level-Backtest suggeriert.") + return 0 + + +if __name__ == "__main__": + sys.exit(main())