"""Die DREI ungemessenen Konzepte aus Risueno Gomez, "Praktisches Trading 2.0" (User-Auftrag 2026-08-19: "nutze das PDF fuer neue Ideen"). TRIAGE ZUERST. Von den ~20 Konzepten des Buches sind 17 in diesem Projekt bereits gemessen und ueberwiegend verworfen (Doppeltop/-boden, Kopf-Schulter, Dreieck, Rechteck, Kanal, Marktstruktur HH/HL, Kerzenmuster inkl. Hammer/ Engulfing/Stern, Fibonacci, Order Blocks, FVG, Wyckoff, Pullback, RSI-Richtung, Liquidity Sweeps). Ungemessen sind genau drei: (1) BOLLINGER-SQUEEZE -- das Wort "Bollinger" kommt in CLAUDE.md NICHT vor. ⚠⚠ Das ist die interessanteste Luecke, weil es KEIN neues Signal ist: der live laufende Squeeze benutzt eine ATR-Box (Spanne der letzten 10 M5-Bars <= 2,5x ATR). Das Buch definiert Kompression ueber die BANDBREITE (SMA20 +- 2 Sigma). Gleiche Idee, andere Messgroesse -- also eine Variante des EINZIGEN validierten Setups statt eines 26. Signal-Eingriffs. Genau dort sagt die Projekt-Logik, man solle suchen. (2) RSI-DIVERGENZ -- gemessen wurde bisher die RSI-RICHTUNG (`backtest_confluence.py`: bestaetigender RSI kontraproduktiv, RSI GEGEN die Richtung = der Pullback-Effekt). Die DIVERGENZ (Preis neues Extrem, RSI nicht) ist eine dritte, andere Aussage und nie geprueft. Das Buch nennt sie "eines der wenigen Tools, das den Preis vorwegnehmen kann". (3) DREI WEISSE SOLDATEN / SCHWARZE KRAEHEN -- als eigene Klasse ungemessen. ⚠ Hohe Beweislast: die Kerzen-Klasse ist 4x gemessen und in der Lehrbuch-Lesart INVERTIERT. Wird deshalb in BEIDEN Lesarten getestet. ⚠⚠ EINORDNUNG DER BUCH-ZAHLEN (S. 203). Die Tabelle behauptet Kopf-Schulter 83 %, Engulfing 70-75 %, Doppelboden 70-75 %. In diesem Projekt gemessen: Muster-Ziel-Trefferquote 13-38 % (`backtest_patterns.py`), Doppeltop in allen Toleranzen durchgefallen, Kerzen-Lesart beidhaelftig invertiert. Die Tabelle nennt keine Quelle, kein n und keinen Zeitraum. Sie wird hier NICHT als Erwartung verwendet -- gemessen wird gegen null. AUFBAU: 80k M5-Bars, 2 Halbjahre, kanonischer Exit (`exit_model.LIVE`), Echtkosten aus dem Bar-Spread, Fill bei BERUEHRUNG (die am 06.08. als entscheidend gemessene Achse), 95-%-Bootstrap je Zelle. REGEL VORAB FIXIERT -- ein Konzept gilt nur dann als tragfaehig, wenn: (a) OeR > 0 UND PF > 1 in BEIDEN Haelften (b) das 95-%-KI schliesst in BEIDEN Haelften die Null aus (c) die Nachbar-Parameter kippen nicht (d) n >= 100 je Haelfte Fuer (1) zusaetzlich: die Bollinger-Variante muss die ATR-Box-KONTROLLE auf DERSELBEN Population schlagen -- sonst ist sie nur eine andere Schreibweise. Aufruf: python backtest_buch_luis.py [bars] """ import sys import MetaTrader5 as mt5 from backtest_breakout_gated import _atr_series from backtest_cost_gate import kennzahlen from core.exit_model import LIVE, simulate _ATRMIN = 0.06 _COOL = 3 def sma(C, n): out = [None] * len(C) s = 0.0 for i, c in enumerate(C): s += c if i >= n: s -= C[i - n] if i >= n - 1: out[i] = s / n return out def bb_width(C, n=20, k=2.0): """Bandbreite (oben-unten)/Mitte -- die Buch-Definition von Kompression.""" m = sma(C, n) out = [None] * len(C) for i in range(n - 1, len(C)): mu = m[i] var = sum((C[j] - mu) ** 2 for j in range(i - n + 1, i + 1)) / n sd = var ** 0.5 out[i] = (2 * k * sd / mu) if mu else None return out, m def rsi_series(C, p=14): out = [None] * len(C) if len(C) < p + 1: return out g = l = 0.0 for i in range(1, p + 1): d = C[i] - C[i - 1] g += max(d, 0.0); l += max(-d, 0.0) g /= p; l /= p out[p] = 100.0 - 100.0 / (1 + g / l) if l else 100.0 for i in range(p + 1, len(C)): d = C[i] - C[i - 1] g = (g * (p - 1) + max(d, 0.0)) / p l = (l * (p - 1) + max(-d, 0.0)) / p out[i] = 100.0 - 100.0 / (1 + g / l) if l else 100.0 return out def pivots(V, k): """Indizes lokaler Hochs/Tiefs. Kausal nutzbar erst ab j+k.""" ph, pl = [], [] for j in range(k, len(V) - k): if V[j] is None: continue w = [V[x] for x in range(j - k, j + k + 1) if V[x] is not None] if len(w) < 2 * k + 1: continue if V[j] == max(w): ph.append(j) if V[j] == min(w): pl.append(j) return ph, pl def trade(i, d, H, L, C, A, SP, ende, puffer): """Ruhende Stop-Order am Extrem der Signalbar, Fill bei BERUEHRUNG.""" atr = A[i] if not atr or atr < _ATRMIN or i + 1 >= ende: return None entry = (H[i] if d > 0 else L[i]) + d * puffer * atr j = None for x in range(i + 1, min(i + 13, ende)): if (H[x] >= entry) if d > 0 else (L[x] <= entry): j = x break if j is None or j + 1 >= ende: return None r = simulate(entry, d, atr, H, L, C, j + 1, LIVE, timestop=True, use_tp=True) return r - ((SP[j] if SP[j] > 0 else 0.0225) / atr) 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 " ~0")) return ("n={:>4} WR={:>3.0f}% OeR={:+.3f} PF={:.2f}{}" .format(k["n"], k["wr"], k["oer"], k["pf"], m)) print(" {:<30} H1 {}".format(lbl, f(k1))) print(" {:<30} 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]; O = [float(b["open"]) for b in bars] SP = [float(b["spread"]) * point for b in bars] A = _atr_series(H, L, C) N = len(C); mid = N // 2; ende = N - LIVE.max_hold - 1 print("=" * 96) print(" BUCH-KONZEPTE (Risueno Gomez) -- {} M5 ({} Bars, 2 Halbjahre)".format(sym, N)) print(" Exit kanonisch - Echtkosten - Fill bei BERUEHRUNG - 95-%-KI") print("=" * 96) # ---------------------------------------------------------------- (1) print("\n (1) BOLLINGER-SQUEEZE gegen die ATR-BOX des laufenden Systems") print(" " + "-" * 92) BW, M20 = bb_width(C) for lo_, hi_, tag in ((0, mid, "H1"), (mid, ende, "H2")): pass for pct, gu in ((0.15, 50), (0.25, 50), (0.15, 100)): res = {"bb": [[], []], "atr": [[], []]} for h, (lo_, hi_) in enumerate(((0, mid), (mid, ende))): i = max(lo_, 120) while i < hi_: atr = A[i] if not atr or atr < _ATRMIN or BW[i] is None: i += 1; continue fenster = [x for x in BW[i - gu:i] if x is not None] if len(fenster) < gu // 2: i += 1; continue schwelle = sorted(fenster)[int(pct * len(fenster))] # Buch: Kontraktion = Bandbreite im unteren Perzentil bb_komp = BW[i] <= schwelle # Projekt: Box-Spanne der letzten 10 Bars <= 2,5x ATR box_hi = max(H[i - 10:i]); box_lo = min(L[i - 10:i]) atr_komp = (box_hi - box_lo) <= 2.5 * atr d = 0 if C[i] > box_hi: d = 1 elif C[i] < box_lo: d = -1 if d: if bb_komp: r = trade(i, d, H, L, C, A, SP, ende, 0.0) if r is not None: res["bb"][h].append(r) if atr_komp: r = trade(i, d, H, L, C, A, SP, ende, 0.0) if r is not None: res["atr"][h].append(r) i += _COOL else: i += 1 zeig("BOLLINGER p{:.0f} f{}".format(pct * 100, gu), kennzahlen(res["bb"][0]), kennzahlen(res["bb"][1])) zeig(" KONTROLLE ATR-Box (live)", kennzahlen(res["atr"][0]), kennzahlen(res["atr"][1])) print("") # ---------------------------------------------------------------- (2) print(" (2) RSI-DIVERGENZ (Preis neues Extrem, RSI nicht)") print(" " + "-" * 92) R = rsi_series(C) for k in (3, 5): ph, pl = pivots(C, k) res = [[], []] gegen = [[], []] for name, piv, d in (("bull", pl, 1), ("bear", ph, -1)): for a, b in zip(piv, piv[1:]): if b - a > 60 or b - a < 5 or R[a] is None or R[b] is None: continue # bullisch: tieferes Tief im Preis, hoeheres Tief im RSI if d > 0: ok = C[b] < C[a] and R[b] > R[a] else: ok = C[b] > C[a] and R[b] < R[a] if not ok: continue i = b + k # kausal: Pivot erst k Bars spaeter bekannt if i >= ende: continue h = 0 if i < mid else 1 r = trade(i, d, H, L, C, A, SP, ende, 0.0) if r is not None: res[h].append(r) rg = trade(i, -d, H, L, C, A, SP, ende, 0.0) if rg is not None: gegen[h].append(rg) zeig("Divergenz k={} (Buch-Lesart)".format(k), kennzahlen(res[0]), kennzahlen(res[1])) zeig(" KONTROLLE Gegenrichtung", kennzahlen(gegen[0]), kennzahlen(gegen[1])) print("") # ---------------------------------------------------------------- (3) print(" (3) DREI WEISSE SOLDATEN / DREI SCHWARZE KRAEHEN") print(" " + "-" * 92) res = [[], []]; geg = [[], []] i = 5 while i < ende: atr = A[i] if not atr or atr < _ATRMIN: i += 1; continue d = 0 for dd in (1, -1): ok = True for x in (i - 2, i - 1, i): koerper = (C[x] - O[x]) * dd spanne = H[x] - L[x] if koerper <= 0 or spanne <= 0 or koerper < 0.5 * spanne: ok = False; break if x > i - 2: # oeffnet im Koerper der Vorkerze, schliesst nahe Extrem if not (min(O[x - 1], C[x - 1]) <= O[x] <= max(O[x - 1], C[x - 1])): ok = False; break if (C[x] - C[x - 1]) * dd <= 0: ok = False; break if ok: d = dd; break if d: h = 0 if i < mid else 1 r = trade(i, d, H, L, C, A, SP, ende, 0.0) if r is not None: res[h].append(r) rg = trade(i, -d, H, L, C, A, SP, ende, 0.0) if rg is not None: geg[h].append(rg) i += _COOL else: i += 1 zeig("Fortsetzung (Buch-Lesart)", kennzahlen(res[0]), kennzahlen(res[1])) zeig(" KONTROLLE Gegenrichtung", kennzahlen(geg[0]), kennzahlen(geg[1])) print("=" * 96) if __name__ == "__main__": main()