Entry-Raum-Gate gegen Ausfuehrung gemessen: nicht gebaut

User fragte nach Verbesserungen der Empfehlung. entry_room blockt 60,6 % (gerichtet
nur 2,7 %) und ist damit der groesste Blocker -- kalibriert aber 2026-07-16 mit
MARKT-Einstieg. Seit 05./06.08. ist belegt, dass eine ruhende Order AM Level etwas
Gegenteiliges ist (in die Wand kaufen vs. aus ihr ausbrechen). Das Gate blockt beide.

Regel vorab fixiert, verfehlt: H1 bleibt in allen Varianten und ueber alle vier
Nachbarn negativ. Nichts gebaut. 26. verworfener Signal-Eingriff.

Drei Befunde, die mehr wiegen als die Ausgangsfrage:
 1 Die ruhende Order ist auf DENSELBEN Signalen beidhaelftig besser als der Markt
   (+0,109 / +0,088 R). Der Ausfuehrungs-Hebel ist real, reicht aber nicht.
 2 Das Gate ist in Wahrheit BINAER: von 4258 Signalen fallen 4 in 'Level vorhanden
   aber weit genug'. Es fragt praktisch nur, OB ein M5-Pivot existiert.
 3 Was es durchlaesst, ist selbst beidhaelftig negativ (-0,145 / -0,071) und in H2
   schlechter als die geblockte Gruppe. Das Gate sortiert Verluste um.

Deckt sich mit rec_outcomes (49,2 % / 48,3 % Treffer, n=61), das den Muenzwurf-Befund
jetzt live reproduziert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-19 01:04:55 +02:00
co-authored by Claude Opus 5
parent 5b01610634
commit be9f444507
2 changed files with 328 additions and 0 deletions
+273
View File
@@ -0,0 +1,273 @@
"""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],
"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()