"""Traegt der ENTRY-RAUM-Filter noch, wenn man am Level einsteigt? (2026-08-19) FRAGE (User: "hast Du Ideen wie ich die Empfehlung noch verbessern koennte?"). `entry_room` ist mit 60,6 % der mit ABSTAND groesste Blocker der Empfehlung (gerichtet sind nur 2,7 %). Kalibriert wurde er am 2026-07-16 mit `backtest_entryroom.py` -- dort steigt die Sim am BAR-CLOSE ein, also per MARKT. Die Begruendung lautet folgerichtig: Gegenlevel nah => Ertrag gedeckelt => nicht handeln (PF 0,42/0,66, sauber gemessen). Am 05./06.08. hat sich die Ausfuehrung geaendert und das Gate wurde NICHT nachgezogen. Seither ist belegt, dass eine RUHENDE ORDER AM LEVEL etwas grundlegend anderes ist als ein Markt-Einstieg mit dem Level als Hindernis voraus (Squeeze: -0,158 => +0,124/+0,292). Markt-Einstieg unter einem Widerstand = in die Wand hineinkaufen BUY_STOP am Widerstand = Ausbruch aus der Wand heraus Das sind GEGENTEILIGE Trades, und das Gate kann sie nicht unterscheiden -- es blockt beide. GEMESSEN wird deshalb die von `entry_room` GEBLOCKTE Gruppe getrennt, genau wie `backtest_htf_vola.py` es fuer den M30-Filter gemacht hat: A) Markt am Bar-Close (was die Sim von 2026-07-16 unterstellt) B) ruhende Stop-Order AM blockierenden Level, Fill bei BERUEHRUNG Das Level ist ein M5-Pivot aus der Vergangenheit, es STEHT also -- das ist der Unterschied zu `_pend` (wandert, `backtest_signal_touchfill.py` faellt durch) und dieselbe Eigenschaft wie die Squeeze-Box (steht, haelt). REGEL VORAB FIXIERT -- gebaut wird nur, wenn ALLES zutrifft: (1) geblockte Gruppe mit Level-Einstieg: OeR > 0 UND PF > 1 in BEIDEN Haelften (2) 95-%-KI schliesst in BEIDEN Haelften die Null aus (3) die Nachbarn (Puffer, Gueltigkeitsdauer) kippen nicht (4) n >= 100 je Haelfte GRENZEN, vorab benannt: - Kein sequentieller EIN-Slot-Lauf. Verglichen werden zwei POPULATIONEN (geblockt/durchgelassen), nicht zwei Kontostaende; dafuer ist der OeR je Trade mit KI die richtige Statistik. Ein Slot-Lauf braeuchte eine Vorrang-Entscheidung und misst dann Selektion statt Gate. - Exit-Slippage ist NICHT modelliert (senkt alle Varianten, die haeufiger handelnden staerker). - Der ATR fuer die R-Normierung ist in A und B derselbe (Signal-Bar), sonst waeren die R-Einheiten nicht vergleichbar. Aufruf: python backtest_entryroom_level.py [bars] """ import sys from bisect import bisect_left, bisect_right import MetaTrader5 as mt5 from backtest_breakout_gated import (_ATRMIN, _ATR_FLOOR_PB, _COOL, _PIV_K, _PIV_LOOK, _TIMEOUT_BARS, _TU, _atr_series, _htf_sign) from backtest_cost_gate import kennzahlen from core.analysis import calc_trend_angle from core.exit_model import LIVE, simulate from core.wave_rec import (WaveRecommender, _ANGLE_LR, _EMA_FAST, _EMA_SLOW, _ema_series) _K = 0.3 # breakout_k, Live-Wert _GATE = 0.6 # entry_room_atr, Live-Wert def sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, lo, hi): """Alle Signale NACH _build + _confirm_breakout, VOR dem Raum-Gate. Geliefert wird je Signal die Rohlage; das Gate wird hier bewusst NICHT angewandt -- seine Entscheidung ist der Gegenstand der Messung. """ out = [] pend = None i = max(lo, _PIV_LOOK + _EMA_SLOW + 5) while i < hi: atr = A[i] if not atr or atr < _ATRMIN: i += 1 continue w._pb_feats = {"atr": max(atr, _ATR_FLOOR_PB)} _a1 = bisect_left(PH, i - _PIV_LOOK); _b1 = bisect_right(PH, i - _PIV_K) _a2 = bisect_left(PL, i - _PIV_LOOK); _b2 = bisect_right(PL, i - _PIV_K) w._pb_levels = {"ph": [C[j] for j in PH[_a1:_b1]], "pl": [C[j] for j in PL[_a2:_b2]]} rec, _ = w._build(EF[i], ES[i], C[i], atr, "M5", 5, htf_trend=HTF[i], h1_trend=H1[i], angle=AD[i]) sig = rec.get("signal") if _K > 0 and sig in ("LONG", "SHORT"): d = 1 if sig == "LONG" else -1 if pend is None or pend["dir"] != d: pend = {"dir": d, "level": C[i] + d * _K * atr, "invalid": C[i] - d * _K * atr, "confirmed": False, "bar": i} if not pend["confirmed"]: broke = (C[i] >= pend["level"]) if d > 0 else (C[i] <= pend["level"]) gegen = (C[i] <= pend["invalid"]) if d > 0 else (C[i] >= pend["invalid"]) if broke: pend["confirmed"] = True else: if gegen or (i - pend["bar"]) > _TIMEOUT_BARS: pend = {"dir": d, "level": C[i] + d * _K * atr, "invalid": C[i] - d * _K * atr, "confirmed": False, "bar": i} i += 1 continue elif sig == "WARTEN": pend = None if sig not in ("LONG", "SHORT"): i += 1 continue d = 1 if sig == "LONG" else -1 # ECHTE Methode, nicht nachgebaut -- dieselbe Zahl, die das Gate benutzt. ri = w.room_info(C[i])[("long" if d > 0 else "short")] or {} out.append({"i": i, "d": d, "atr": max(atr, _ATRMIN), "px": C[i], "conf": rec.get("conf_pct"), # additiv, s. backtest_conf_gated.py "lvl": ri.get("level"), "dist": ri.get("dist_atr"), "frei": bool(ri.get("frei"))}) pend = None i += _COOL return out def handeln(sig, H, L, C, SP, ende, modus, puffer, gueltig): """R eines Signals. modus 'markt' = Bar-Close, 'level' = ruhende Stop-Order.""" i, d, atr = sig["i"], sig["d"], sig["atr"] def cost(j): return (SP[j] if SP[j] > 0 else 0.0225) / atr if modus == "markt": entry, j = C[i], i else: lvl = sig["lvl"] if lvl is None: return None # freie Bahn: kein Level zum Liegen entry = lvl + d * puffer * atr # Stop-Order MUSS auf der richtigen Marktseite liegen (Lehre 06.08.: # ohne diese Pruefung fuellt die Sim zu laengst passierten Preisen). if (d > 0 and entry <= C[i]) or (d < 0 and entry >= C[i]): return None j = None for x in range(i + 1, min(i + 1 + gueltig, ende)): if (H[x] >= entry) if d > 0 else (L[x] <= entry): j = x break if j is None: return None # nicht gefuellt = kein Trade if j + 1 >= ende: return None r = simulate(entry, d, atr, H, L, C, j + 1, LIVE, timestop=True, use_tp=True) return r - cost(j) def gruppen(sigs, H, L, C, SP, ende, puffer, gueltig): g = {"blk_markt": [], "blk_level": [], "frei_markt": [], "durch_markt": [], "durch_level": []} for s in sigs: rm = handeln(s, H, L, C, SP, ende, "markt", puffer, gueltig) rl = handeln(s, H, L, C, SP, ende, "level", puffer, gueltig) if s["frei"] or s["lvl"] is None: if rm is not None: g["frei_markt"].append(rm) continue blk = (s["dist"] is not None and s["dist"] < _GATE) if rm is not None: g["blk_markt" if blk else "durch_markt"].append(rm) if rl is not None: g["blk_level" if blk else "durch_level"].append(rl) return g def zeig(lbl, k1, k2): def f(k): if not k: return " --" ci = k.get("ci") m = "" if ci: m = (" [{:+.3f} .. {:+.3f}]".format(ci[0], ci[1]) + ("" if ci[0] * ci[1] > 0 else " ~KI enthaelt 0")) return ("n={:>4} WR={:>3.0f}% OeR={:+.3f} PF={:.2f}{}" .format(k["n"], k["wr"], k["oer"], k["pf"], m)) print(" {:<28} H1 {}".format(lbl, f(k1))) print(" {:<28} H2 {}".format("", f(k2))) 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 = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n) point = mt5.symbol_info(sym).point 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] SP = [float(b["spread"]) * point for b in bars] A = _atr_series(H, L, C) EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW) N = len(C); mid = N // 2; ende = N - LIVE.max_hold - 1 print("Vorberechnung (Winkel, HTF-Trends, Pivots) ...", flush=True) AD = [90.0] * N for i in range(_ANGLE_LR + 2, N): AD[i] = calc_trend_angle(C[i - (_ANGLE_LR + 1):i + 1], _ANGLE_LR) HTF = [0] * N; H1 = [0] * N for i in range(N): if A[i]: HTF[i] = _htf_sign(C, i, 6, A[i]) H1[i] = _htf_sign(C, i, 12, A[i]) PH = [j for j in range(_PIV_K, N - _PIV_K) if H[j] == max(H[j - _PIV_K:j + _PIV_K + 1])] PL = [j for j in range(_PIV_K, N - _PIV_K) if L[j] == min(L[j - _PIV_K:j + _PIV_K + 1])] w = WaveRecommender(_TU(), mt5.TIMEFRAME_M5) w.set_reversal_enabled(False) w.set_entry_room(_GATE) # Live-Konfiguration, nicht Default w.set_dead_hours([]) w.set_breakout_k(_K) print("=" * 96) print(" ENTRY-RAUM-GATE gegen AUSFUEHRUNG -- {} M5 ({} Bars, 2 Halbjahre)" .format(sym, N)) print(" Gate {}x ATR_M5 - breakout_k {} - Reversal AUS - Exit kanonisch" .format(_GATE, _K)) print("=" * 96) print("Signale sammeln (voller Gate-Stack, VOR dem Raum-Gate) ...", flush=True) s1 = sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, 0, mid) s2 = sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, mid, ende) print(" H1 {} - H2 {} Signale vor dem Raum-Gate".format(len(s1), len(s2))) PU, GU = 0.0, 12 g1 = gruppen(s1, H, L, C, SP, ende, PU, GU) g2 = gruppen(s2, H, L, C, SP, ende, PU, GU) print("") print(" HAUPTLAUF (Puffer {}x ATR - Order gueltig {} Bars = 1 h)".format(PU, GU)) print(" " + "-" * 92) for key, lbl in (("blk_markt", "GEBLOCKT - Markt"), ("blk_level", "GEBLOCKT - Level (ruhend)"), ("durch_markt", "durchgel. - Markt (=heute)"), ("durch_level", "durchgel. - Level (ruhend)"), ("frei_markt", "freie Bahn - Markt")): zeig(lbl, kennzahlen(g1[key]), kennzahlen(g2[key])) print("") print(" NACHBARN (Regel 3) -- nur die geblockte Gruppe am Level") print(" " + "-" * 92) for pu, gu in ((0.05, 12), (0.10, 12), (0.0, 6), (0.0, 24)): a = gruppen(s1, H, L, C, SP, ende, pu, gu)["blk_level"] b = gruppen(s2, H, L, C, SP, ende, pu, gu)["blk_level"] zeig("Puffer {:.2f} - gueltig {}".format(pu, gu), kennzahlen(a), kennzahlen(b)) print("") k1, k2 = kennzahlen(g1["blk_level"]), kennzahlen(g2["blk_level"]) ok = bool(k1 and k2 and k1["n"] >= 100 and k2["n"] >= 100 and k1["oer"] > 0 and k2["oer"] > 0 and k1["pf"] > 1 and k2["pf"] > 1 and k1["ci"] and k2["ci"] and k1["ci"][0] > 0 and k2["ci"][0] > 0) print("=" * 96) print(" URTEIL nach der vorab fixierten Regel: {}" .format("ERFUELLT -- weiterverfolgen" if ok else "NICHT erfuellt -- nichts bauen")) print("=" * 96) if __name__ == "__main__": main()