diff --git a/CLAUDE.md b/CLAUDE.md index d5e1358..e5fc758 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8906,6 +8906,60 @@ hat gehalten: CLAUDE.md blieb unversehrt, es entstand nur eine leere `.tmp`. ✅ Konsequenz: dieser Abschnitt wurde als **reiner UTF-8-Text** geschrieben, ohne jedes Escape — das ist der Weg, der die Falle gar nicht erst aufstellt. +## ⚠⚠ „TAGESTREND AUFWÄRTS, NEWS BULLISH — WARUM KEINE EMPFEHLUNG?" (2026-08-20) + +User-Frage. **Die Prämisse stimmt** (Tageskerze +2,47 $ = **+2,89 %**, H1 +monoton 85,31 → 87,72). Die Schlussfolgerung stimmt nicht: + +✅✅ **DIE EMPFEHLUNG WAR LONG — achtmal, zwischen 09:13 und 09:51 bei +85,97–86,53** (conf 62). Der Kurs steht jetzt 87,70, also **+1,73 $ höher**. Das +System hat den Trend also gesehen, und zwar nahe am Anfang der Bewegung. +Was jetzt passiert, ist etwas anderes: es steigt **nicht mehr spät ein**. + +**DER BLOCKER IST `stretch`** (`_STRETCH_MAX = 3,5`) — der Kurs steht weit über +der EMA50, auf **jeder** Zeitebene: +| TF | Überdehnung | | +|---|---|---| +| M5 | **+5,18×ATR** | gesperrt | +| M15 | +4,63×ATR | gesperrt | +| **M30** (Basis-TF) | **+4,36×ATR** | gesperrt | +| H1 | +4,39×ATR | gesperrt | +Reason-Text live: „überdehnt: +4.4×ATR über EMA — kein Spät-Long". +⚠ Der Block ist **nicht** der Dauerzustand: er läuft am Stück erst seit 12:58, +davor stand dort `breakout_pending`. Verteilung heute (789 Zeilen): +`entry_room` 284 · **`stretch` 188** · `min_conf` 173 · `stale` 55 · +`breakout_pending` 41 · `deadband` 40 · gerichtet 8. + +**GEMESSEN, statt die Sperre zu verteidigen** (`backtest_stretch_extrem.py`, +60k Bars, M30 **und** M5, kanonischer Exit, Echtkosten, 2 Halbjahre, +Episoden-entkoppelt, 95-%-Bootstrap): trägt ein Einstieg **jenseits** des Gates? +| Band | M30 H1 / H2 | M5 H1 / H2 | +|---|---|---| +| 3,0–3,5 (erlaubt) | −0,024 / −0,077 | −0,074 / −0,119 | +| **3,5–4,5 (gesperrt)** | **−0,053 / −0,061** | **−0,074 / −0,058** | +| **4,5+ (gesperrt)** | **−0,037 / −0,149** | **−0,110 / −0,059** | +**Alle acht gesperrten Zellen sind negativ** (PF 0,83–0,95). Die vorab fixierte +Regel (beidhälftig positiv UND Intervall ohne die Null) ist in **keiner** +erfüllt → **die Sperre bleibt.** +⚠⚠ **Die aufschlussreichste Spalte ist die Trefferquote: 57–60 % — und trotzdem +PF < 1.** Das ist exakt die dokumentierte Klein-Close-Signatur: man hat oft +recht, aber der Rücksetzer kostet mehr, als die vielen kleinen Gewinne +einbringen. Genau das heißt „Spät-Long". +⚠ Ehrlich dazu: **nichts** in der Tabelle ist überzeugend positiv, auch die +erlaubten Bänder nicht. Die 3,5 sind also keine magische Grenze — auf der +Überdehnungs-Achse ist überhaupt nichts zu holen; das Gate entfernt nur das +schlechtere Ende. + +⚠⚠ **NEWS HABEN IN DER EMPFEHLUNG BUCHSTÄBLICH KEIN GEWICHT** — geprüft, nicht +vermutet: **0 Treffer** für „news" in `core/wave_rec.py`, **0** News-Stimmen im +Verdict. Der Score (heute **+0,42**) ist reiner Kontext-Chip und liegt zudem +unter der 0,5-Schwelle, ab der die Konflikt-Warnung überhaupt erscheint. +✅ Das ist eine **Messentscheidung**, kein Versehen: am 03.08. über 38.213 Paare +gemessen liegt der stark-bullische Bucket in **BEIDEN** Hälften bei null bis +negativ (−0,003 / −0,023), der bärische **kippt das Vorzeichen**. Ein Kanal, der +nichts vorhersagt, bekommt keine Stimme — dieselbe Begründung wie bei TU (2026-07-06) +und beim Orderbuch (06.08.). + ## ⚠⚠⚠ P(break) IST LIVE **INVERTIERT** — und es steuert echtes Geld (2026-08-19) Die fällige Messung ist entscheidbar geworden: **n=530 entkoppelt** gegen die diff --git a/backtest_stretch_extrem.py b/backtest_stretch_extrem.py new file mode 100644 index 0000000..44e5e59 --- /dev/null +++ b/backtest_stretch_extrem.py @@ -0,0 +1,114 @@ +"""Traegt ein LONG, wenn der Kurs SEHR weit ueber der EMA steht? (2026-08-20) + +ANLASS (User): "der Tagestrend ist aktuell aufwaerts, auch die News deuten auf +bullish hin - warum wird das in der Empfehlung nicht beruecksichtigt." +Die Praemisse stimmt (Tageskerze +2,47 $ / +2,89 %, H1 monoton steigend). Der +Blocker ist `_STRETCH_MAX = 3,5`: der Kurs steht +4,36xATR ueber der EMA50 der +Basis-TF, das Signal wird als "Spaet-Long" unterdrueckt. + +Die 3,5 stammen aus `backtest_stretch.py` ("ab 3,5xATR kippt der Edge negativ"). +Dort wurde die SCHWELLE gesucht; hier wird die Frage des Users direkt gestellt: +was passiert, wenn man GENAU IN DIESER LAGE trotzdem long geht? + +AUFBAU. Reine Stretch-Bedingung auf M30 (die aktuelle Basis-TF) und auf M5, +kein weiteres Gate - es geht um die eine Achse. Einstieg am Bar-Close in +Trend-Richtung, kanonischer Exit (`exit_model.LIVE`), Echtkosten aus dem +Bar-Spread, zwei Halbjahre, 95-%-Bootstrap, Episoden-entkoppelt (sonst zaehlt +dieselbe Ueberdehnung dutzendfach). + +Baender: 2,0-2,5 / 2,5-3,0 / 3,0-3,5 (= erlaubt) gegen 3,5-4,5 / 4,5+ (= gesperrt). + +REGEL VORAB FIXIERT: die Sperre gehoert gelockert, wenn die gesperrten Baender +in BEIDEN Halbjahren einen positiven OeR liefern UND ihr 95-%-Intervall die +Null ausschliesst. Sonst bleibt sie. +""" +import sys + +import MetaTrader5 as mt5 + +from backtest_cost_gate import kennzahlen +from core.exit_model import LIVE, simulate +from core.squeeze_scan import atr_series + +BAENDER = [(2.0, 2.5), (2.5, 3.0), (3.0, 3.5), (3.5, 4.5), (4.5, 99.0)] +GATE = 3.5 + + +def ema_serie(C, p): + k = 2.0 / (p + 1.0); out = [C[0]] + for x in C[1:]: + out.append(x * k + out[-1] * (1 - k)) + return out + + +def lauf(H, L, C, SP, A, e50, von, bis, ende, lo, hi): + """Alle Bars im Band -> LONG bei Ueberdehnung nach OBEN, SHORT nach unten. + + Entkoppelt: je zusammenhaengender Episode im Band nur der ERSTE Bar. + """ + rs = []; im_band = False + for i in range(von, min(bis, ende)): + atr = A[i] + if not atr or atr < 0.06: + im_band = False; continue + st = (C[i] - e50[i]) / atr + drin = lo <= abs(st) < hi + if drin and not im_band: + d = 1 if st > 0 else -1 # Richtung = Trendrichtung + r = simulate(C[i], d, atr, H, L, C, i + 1, LIVE, + timestop=True, use_tp=True) + rs.append(r - ((SP[i] if SP[i] > 0 else 0.0225) / atr)) + im_band = drin + return rs + + +def zeig(lbl, k, gesperrt): + mark = " <== heute GESPERRT" if gesperrt else "" + if not k: + print(" {:<12} --{}".format(lbl, mark)); return + ci = k.get("ci"); m = "" + if ci: + m = (" [{:+.3f} .. {:+.3f}]".format(ci[0], ci[1]) + + ("" if ci[0] * ci[1] > 0 else " ~0")) + print(" {:<12} n={:>4} WR={:>3.0f}% OeR={:+.3f} PF={:.2f}{}{}" + .format(lbl, k["n"], k["wr"], k["oer"], k["pf"], m, mark)) + + +def main(): + n = int(sys.argv[1]) if len(sys.argv) > 1 else 60000 + mt5.initialize() + sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD") + if mt5.symbol_info(c)), None) + point = mt5.symbol_info(sym).point + daten = {} + for tf, lbl in ((mt5.TIMEFRAME_M30, "M30"), (mt5.TIMEFRAME_M5, "M5")): + b = mt5.copy_rates_from_pos(sym, tf, 0, n) + daten[lbl] = ([float(x["high"]) for x in b], [float(x["low"]) for x in b], + [float(x["close"]) for x in b], + [float(x["spread"]) * point for x in b]) + mt5.shutdown() + + print("=" * 96) + print(" UEBERDEHNUNG: traegt ein Einstieg JENSEITS des Gates? — {}".format(sym)) + print(" Gate live: |stretch| > {} x ATR -> WARTEN · kanonischer Exit, Echtkosten" + .format(GATE)) + print("=" * 96) + for lbl in ("M30", "M5"): + H, L, C, SP = daten[lbl] + A = atr_series(H, L, C); e50 = ema_serie(C, 50) + N = len(C); mid = N // 2; ende = N - LIVE.max_hold - 1 + print("\n {} ({} Bars)".format(lbl, N)) + print(" " + "-" * 92) + for lo, hi in BAENDER: + band = "{:.1f}-{:.1f}".format(lo, hi) if hi < 90 else "{:.1f}+".format(lo) + g = lo >= GATE + zeig("H1 " + band, kennzahlen(lauf(H, L, C, SP, A, e50, 0, mid, ende, lo, hi)), g) + zeig("H2 " + band, kennzahlen(lauf(H, L, C, SP, A, e50, mid, ende, ende, lo, hi)), g) + print() + print(" " + "=" * 92) + print(" REGEL: lockern nur, wenn die GESPERRTEN Baender beidhaelftig") + print(" positiv sind UND ihr Intervall die Null ausschliesst.") + + +if __name__ == "__main__": + main()