diff --git a/CLAUDE.md b/CLAUDE.md index 7954039..15ad248 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7434,6 +7434,80 @@ vollständig. ⚠ **Verifikation der Zustellung steht aus** — sie erfolgt mit dem Report am nächsten Morgen um 07:30. Die Bausteine sind einzeln live gegengeprüft. +## ⚠⚠ TRADING-BUCH (Risueño Gómez) AUSGEWERTET — 17 von 20 Konzepten waren +## schon gemessen, die übrigen DREI fallen durch (`backtest_buch_luis.py`, 2026-08-19) + +User reichte „Praktisches Trading & Analyse 2.0“ ein (35 Chart-Übungen, SMC + +Wyckoff + klassische Muster). **Triage zuerst, nicht alles nachmessen.** + +| Buch-Konzept | Stand im Projekt | +|---|---| +| Doppeltop/-boden · Kopf-Schulter · Dreieck · Rechteck · Flagge | `backtest_doubletop` / `_patterns` — Ziel-Trefferquote **13–38 %** | +| Kanal · Marktstruktur HH/HL · V-Muster | `backtest_structure_tf` — **24/24 Zellen negativ** | +| Kerzen (Hammer · Engulfing · Abend-/Morgenstern · Pin Bar · Doji) | **4× gemessen, Lehrbuch-Lesart beidhälftig INVERTIERT** | +| Fibonacci | 04.08. — hält **schlechter als eine Zufallslinie** gleicher Distanz | +| Order Blocks · FVG · Liquidity Sweeps | SMC = Pivot-Zonen, 6× verworfen | +| Wyckoff Akkumulation/Distribution | `backtest_consolidation` — Basisrate **48/52 %** | +| RSI-**Richtung** | bestätigender RSI kontraproduktiv (Pullback-Effekt) | +| Pullback-Einstieg | ✅ **positiv belegt** — läuft als „⭐ tiefer Pullback“ (+15) | +| S/R als **Zone** | ✅ seit 17.08. im Chart (`RZ`/`SZ`) | +| **R:R ≥ 1:2 / 1–2 % Risiko** (Kap. 39) | ✅ **stärkste Übereinstimmung** — s. unten | + +⚠⚠ **Die Erfolgsraten-Tabelle auf S. 203 ist unbelegt und widerspricht der +Messung.** Sie behauptet Kopf-Schulter **83 %**, Engulfing 70–75 %, Doppelboden +70–75 % — ohne Quelle, ohne n, ohne Zeitraum. Gemessen sind hier 13–38 % +Ziel-Trefferquote und eine invertierte Kerzen-Lesart. **Nicht als Erwartung +verwendet**; gemessen wurde gegen null. + +**DIE DREI ECHTEN LÜCKEN — 40k M5, 2 Hälften, kanonischer Exit, Echtkosten, +Fill bei BERÜHRUNG, 95-%-KI. Alle drei mit KONTROLLGRUPPE:** + +| | H1 ØR / PF | H2 ØR / PF | +|---|---|---| +| **(1) Bollinger-Squeeze** p15 | +0,128 / 1,23 | **−0,133 / 0,80** | +| *KONTROLLE ATR-Box (live)* | *+0,101 / 1,18* | ***−0,031 / 0,95*** | +| **(2) RSI-Divergenz** k=5 | **−0,150 / 0,75** | +0,085 / 1,17 | +| *KONTROLLE Gegenrichtung* | ***+0,251 / 1,59*** [+0,030 … +0,471] | −0,052 / 0,91 | +| **(3) Drei Soldaten/Krähen** | −0,088 / 0,85 | −0,125 / 0,81 | +| *KONTROLLE Gegenrichtung* | *+0,031* | *+0,140 / 1,27* | + +❌ **Keines besteht.** Alle 30 Zellen haben ein KI, das die Null enthält — mit +**einer** Ausnahme, und die ist die **Gegenrichtung** der RSI-Divergenz. + +⚠⚠ **BEFUND 1 — die Bollinger-Definition ist SCHLECHTER als die ATR-Box, die +live läuft.** Das war der wertvollste Test, weil es kein neues Signal ist, +sondern eine Variante des **einzigen validierten Setups**: der Squeeze misst +Kompression über die Box-Spanne (≤ 2,5×ATR), das Buch über die Bandbreite +(SMA20 ± 2σ). In H2 liegt Bollinger bei −0,133 gegen −0,031 der Box — und +handelt dabei nur halb so oft (324 gegen 657). **Kein Grund zu wechseln.** +✅ Nebenbefund: die ATR-Box-Kontrolle kippt hier ebenfalls (H1 +0,101 / H2 +−0,031) — erwartbar, weil dieser Lauf **ohne** Nacht-Gate, Cooldown und +Dedup rechnet. Belastbar ist der **interne** Vergleich, nicht der Absolutwert. + +⚠⚠⚠ **BEFUND 2 — DIE FÜNFTE BESTÄTIGUNG DERSELBEN INVERSION.** In **allen +drei** Konzepten ist die Kontrollgruppe (Gegenrichtung) besser als die +Buch-Lesart; bei der RSI-Divergenz schliesst sie in H1 sogar die Null aus +(+0,251, KI [+0,030 … +0,471]). Dasselbe Muster wie bei Kerzen (4×), beim +Winkel-Bonus und beim Reversal: **die Lehrbuch-Deutung von Umkehr- und +Fortsetzungssignalen zeigt in diesem Markt systematisch in die falsche +Richtung.** ⚠ Daraus folgt NICHT „einfach umdrehen“ — die Kontrollen kippen +zwischen den Hälften genauso. + +✅✅ **WAS DAS BUCH BESTÄTIGT, IST SEIN KAPITEL 39 — und das ist die eigentliche +Ausbeute.** „Der Erfolg hängt nicht von der Trefferquote ab, sondern vom +Verhältnis ØGewinn/ØVerlust“ und „nie mehr als 1–2 % je Trade“. Genau das +misst dieses Projekt seit Monaten unabhängig: der Squeeze hat **73 % +Trefferquote und trotzdem PF 0,39**, weil das Verhältnis bei **0,14** liegt; +`analyze_sizing_live.py` misst **MaxDD 82,8 %** und einen schlechtesten +Einzeltrade von **−381 €** bei 95-%-Margin-Sizing, gegen **22,2 % MaxDD** bei +0,5 % Risiko je Trade. **Das Buch und die eigenen Zahlen sagen dasselbe — und +es ist der einzige Punkt, an dem noch nichts umgesetzt ist.** +**26., 27. und 28. verworfener Signal-Eingriff.** + +⚠ **Grenzen:** 40k Bars (nicht die üblichen 80k) — für ein **Ausschluss**-Urteil +ausreichend, für eine Bestätigung wäre es zu wenig. Je eine Definition pro +Konzept. Exit-Slippage nicht modelliert. + ## ⚠⚠ DIE KLEIN-CLOSE-FALLE REPRODUZIERT AUF DEN EIGENEN TRADES (2026-08-19) Suche nach weiteren Optimierungen. Erstmals die `ctx_*`-Telemetrie (seit 04.08.) diff --git a/backtest_buch_luis.py b/backtest_buch_luis.py new file mode 100644 index 0000000..ffff0c9 --- /dev/null +++ b/backtest_buch_luis.py @@ -0,0 +1,292 @@ +"""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()