From 6a9b554d544e7e75ab0ca2af353ecaa251626b17 Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Sat, 8 Aug 2026 23:18:20 +0200 Subject: [PATCH] M15-Karte an 7 Tagen getestet: Veto haelt ueber 2 Halbjahre, Woche war Rauschen Wochentest (test_m15_karte_woche.py, 30.07.-07.08., 93 Episoden) meldete das Veto als FALSCH herum: MIT dem HTF OeR -0,273 / PF 0,56, GEGEN den HTF +0,258 / PF 1,83 - Differenz -0,532 R gegen die erlaubte Richtung. Gegenprobe ueber 2 Halbjahre auf DERSELBEN Population (Level-Beruehrungen, M15): H1 (n=1504) MIT -0,072 / PF 0,87 GEGEN -0,116 / PF 0,80 -> +0,044 R H2 (n=1617) MIT +0,028 / PF 1,06 GEGEN -0,012 / PF 0,98 -> +0,040 R Beidhaelftig konsistent. Die Woche war Rauschen (n=93 gegen n=3121). Dieselbe Falle wie bei der 15-Minuten-Regel und dem Nacht-Guard. Der Verdacht war trotzdem berechtigt: der HTF-Filter ist auf SIGNAL-Entries validiert (backtest_htf_filter.py), nicht auf Level-Beruehrungen. Auf der Level-Population traegt er +0,04 R statt des dokumentierten "Edge x2" - richtig, aber klein. Was der Test sonst zeigt - die Karte ist zu permissiv: gruene Bars je Tag 22-56 von 92 (24-61 %) Episoden je Tag 14,7 Kosten-Gate blockte 0 von 644 Bars Das Kosten-Gate ist auf M15 dekorativ (Spread/ATR ~0,10, Schwelle 0,32). Die Level-Einstiege selbst tragen kaum (OeR -0,072 / +0,028); das Veto macht sie nur weniger schlecht. Die einzige diskriminierende Schwelle (Reichweite 0,5xATR) ist gesetzt, nicht gemessen. Fehler im Test selbst, dokumentiert: der erste Lauf zaehlte 46 fortlaufende gruene Bars als 46 Trades und meldete 7 % Trefferquote - eine Bewegung 46-mal gezaehlt. Erst die Entkopplung macht die Zahl lesbar. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 48 ++++++++++++++ test_m15_karte_woche.py | 138 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 test_m15_karte_woche.py diff --git a/CLAUDE.md b/CLAUDE.md index 0a6ec96..8383c34 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4942,6 +4942,54 @@ Modell schliesst keine Trades. „die Rangfolge ist kaputt" ist **nicht** belegt; belegt ist nur die Basisraten-/Kalibrierungslücke. +## ⚠⚠ M15-KARTE AN 7 TAGEN GETESTET — zwei eigene Fehler, einer davon fast veröffentlicht (2026-08-08) + +**(1) BIAS WAR EIN RICHTUNGSGEBER — korrigiert zum VETO (v=155).** Der Tagestest +an 07.08. (`test_m15_karte_tag.py`) zeigte: 46 von 92 Bars grün, entkoppelt +**13 Episoden — ALLE LONG**, an einem Tag, der von 78,399 auf 77,383 **fiel**. +Trefferquote **0 %**, ØR −0,753. +⚠ Ursache strukturell, nicht Pech: gemessen ist „gegen den M30-Trend halbiert +den Edge" — eine **VERBOTS**-Aussage. Ich hatte daraus „M30+H1 einig → Bias +LONG" gemacht, also einen Richtungsgeber. **Exakt der Fehler, wegen dem die +Gesamtempfehlung entfernt wurde** (Richtung dort 48–51 % Treffer). Behoben: +`bias.richtung` ist dauerhaft `None`, das Feld liefert nur noch die **verbotene** +Richtung; das Banner nennt keine Richtung mehr. + +**(2) FAST EINEN FEHLALARM VERÖFFENTLICHT.** Der Wochentest +(`test_m15_karte_woche.py`, 30.07.–07.08., 93 Episoden) meldete das Veto als +**falsch herum**: MIT dem HTF ØR −0,273 / PF 0,56, GEGEN den HTF **+0,258 / +PF 1,83** — Differenz −0,532 R gegen die erlaubte Richtung. +✅ **Über 2 Halbjahre auf DERSELBEN Population (Level-Berührungen, M15) hält das +Veto aber:** +| | MIT dem HTF | GEGEN den HTF | Differenz | +|---|---|---|---| +| **H1** (n=1504) | −0,072 / PF 0,87 | −0,116 / PF 0,80 | **+0,044 R** | +| **H2** (n=1617) | +0,028 / PF 1,06 | −0,012 / PF 0,98 | **+0,040 R** | +Beidhälftig konsistent, gleiche Größenordnung. **Die Woche war Rauschen** — +n=93 gegen n=3121. Dieselbe Falle wie bei der 15-Minuten-Regel (31.07.) und dem +Nacht-Guard (05.08.): ein Zeitraum, der zum Beleg erhoben wird. +⚠ Der Verdacht, der mich zur Gegenprüfung brachte, war trotzdem berechtigt: der +HTF-Filter ist auf **Signal**-Entries validiert (`backtest_htf_filter.py`), nicht +auf Level-Berührungen. Auf der Level-Population trägt er **+0,04 R** statt des +dokumentierten „Edge ×2" — richtig, aber klein. + +**(3) WAS DER TEST SONST ZEIGT — die Karte ist zu permissiv:** +| | | +|---|---| +| grüne Bars je Tag | 22–56 von 92 (**24–61 %**) | +| Episoden je Tag | **14,7** | +| Kosten-Gate blockte | **0 von 644 Bars** | +Das Kosten-Gate ist auf M15 **dekorativ**: Spread/ATR liegt dort bei ~0,10, die +Schwelle bei 0,32. Und die Level-Einstiege selbst tragen kaum (ØR −0,072 / +0,028 +über 2 Halbjahre) — das Veto macht sie nur **weniger schlecht**. +⚠ Die einzige diskriminierende Schwelle (Reichweite 0,5×ATR) ist **gesetzt, nicht +gemessen**. + +⚠⚠ **Und ein Fehler im TEST selbst:** der erste Lauf zählte 46 fortlaufende +grüne Bars als 46 Trades und meldete 7 % Trefferquote — **eine Bewegung 46-mal +gezählt**. Erst die Entkopplung auf zusammenhängende Blöcke macht die Zahl +lesbar. Dieselbe Klasse wie die Clusterung bei `pbreak_predictions` (07.08.). + ## ✅ M15-KARTE „SETUP-BEREITSCHAFT" GEBAUT (2026-08-08, v=153) — Ersatz der Gesamtempfehlung Nach dem Entfernen der Gesamtempfehlung und drei negativen M15-Messungen diff --git a/test_m15_karte_woche.py b/test_m15_karte_woche.py new file mode 100644 index 0000000..aa28d72 --- /dev/null +++ b/test_m15_karte_woche.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""M15-Karte über eine ganze Woche — was behauptet sie, und stimmt es? + +⚠⚠ DIE FRAGE „WIE VIELE KORREKTE EMPFEHLUNGEN?" MUSS NEU GESTELLT WERDEN. +Seit der Korrektur vom 08.08. gibt die Karte **keine Richtung** mehr aus +(`bias.richtung` ist dauerhaft None). Grund: der erste Entwurf machte aus dem +gemessenen VERBOT („gegen den M30-Trend halbiert den Edge") einen +Richtungsgeber — und erzeugte am 07.08. dreizehn LONG-Episoden an einem Tag, +der 1,02 $ fiel (Trefferquote 0 %). + +Prüfbar ist deshalb nur, was die Karte NOCH behauptet: + (A) das VETO — ist die verbotene Richtung tatsächlich schlechter als die + erlaubte? Das ist die einzige Richtungsaussage, die überhaupt bleibt. + (B) das LEVEL — trägt eine Order AM stehenden Level (statt zum Markt)? + (C) die MECHANIK — wie oft grün/gelb/rot, welches Gate blockt? + +⚠ Beide Richtungen werden auf DENSELBEN Episoden gerechnet — nur so misst der +Vergleich das Veto und nicht zwei verschiedene Trade-Folgen. +⚠ EINE WOCHE = EIN REGIME. Das ist ausdrücklich KEIN Edge-Nachweis (die +2-Stichproben-Regel bleibt), sondern eine Mechanik- und Plausibilitätsprüfung. + +Aufruf: python test_m15_karte_woche.py [tage] (Default 7) +""" +from __future__ import annotations + +import sys +from collections import defaultdict + +import MetaTrader5 as mt5 + +from core.exit_model import LIVE, simulate +from core.sr_levels import halten, linien, naechste +from test_m15_karte_tag import _berlin, atr_series, htf_sign, m30_levels + +_KOST_MAX = 0.32 +_REICH = 0.5 # ⚠ GESETZT, nicht gemessen — der einzige diskriminierende Gate + + +def main(): + tage = int(sys.argv[1]) if len(sys.argv) > 1 else 7 + 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_M15, 0, 6000) + point = mt5.symbol_info(sym).point + mt5.shutdown() + T = [int(b["time"]) for b in bars] + 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) + S30, S1H = htf_sign(H, L, C, 2), htf_sign(H, L, C, 4) + LV = m30_levels(H, L, C) + + proTag = defaultdict(list) + for i, t in enumerate(T): + proTag[_berlin(t).strftime("%Y-%m-%d")].append(i) + datteln = sorted(proTag)[-tage:] + + print("=" * 100) + print(f" M15-KARTE — {len(datteln)} Handelstage ({datteln[0]} bis {datteln[-1]}), {sym}") + print(" ⚠ Die Karte gibt KEINE Richtung mehr aus. Geprüft wird das VETO und das LEVEL.") + print("=" * 100) + print(f"\n {'Tag':<12}{'Bars':>5}{'grün':>7}{'gelb':>7}{'rot':>6}" + f"{'Epis.':>7}{'Bewegung':>11}") + + alle_erlaubt, alle_verboten, alle_epi = [], [], 0 + tagszeilen = [] + for d in datteln: + idx = proTag[d] + if len(idx) < 20: + continue + st_liste = [] + prev_r = prev_s = None + for i in idx: + a = A[i] + if not a or not LV[i]: + st_liste.append((i, None, None, None)); continue + ph, pl = LV[i] + lv = linien(ph, pl, a) + r, s = naechste(lv, C[i]) + r = halten(r, prev_r, C[i], a, True) + s = halten(s, prev_s, C[i], a, False) + prev_r, prev_s = r, s + kr = SP[i] / a + gaps = [(abs(x - C[i]) / a, x) for x in (r, s) if x is not None] + nah = [g for g in gaps if g[0] <= _REICH] + zu = "rot" if kr >= _KOST_MAX else ("gruen" if nah else "gelb") + st_liste.append((i, zu, S30[i], nah[0][1] if nah else None)) + + gr = sum(1 for _, z, _, _ in st_liste if z == "gruen") + ge = sum(1 for _, z, _, _ in st_liste if z == "gelb") + ro = sum(1 for _, z, _, _ in st_liste if z == "rot") + + # Episoden = erste Bar je zusammenhaengendem Gruen-Block + epi, vor = [], None + for i, z, h30, lvl in st_liste: + if z == "gruen" and vor != "gruen": + epi.append((i, h30, lvl, A[i], SP[i] / A[i])) + vor = z + alle_epi += len(epi) + + # ⚠ BEIDE Richtungen auf DENSELBEN Episoden — das misst das Veto + for i, h30, lvl, a, kr in epi: + if not h30: + continue # HTF flach -> kein Veto -> nichts zu pruefen + erlaubt = 1 if h30 == 1 else -1 # mit dem HTF + alle_erlaubt.append(simulate(lvl, erlaubt, a, H, L, C, i + 1, LIVE) - kr) + alle_verboten.append(simulate(lvl, -erlaubt, a, H, L, C, i + 1, LIVE) - kr) + + bew = C[idx[-1]] - C[idx[0]] + tagszeilen.append((d, len(idx), gr, ge, ro, len(epi), bew)) + print(f" {d:<12}{len(idx):>5}{gr:>7}{ge:>7}{ro:>6}{len(epi):>7}{bew:>+10.2f} $") + + def kz(Rs, name): + if not Rs: + return f" {name:<34} —" + n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs) + g = sum(x for x in Rs if x > 0); v = -sum(x for x in Rs if x < 0) + return (f" {name:<34} n={n:>3} Treffer={100*w/n:>3.0f}% ØR={s/n:+.3f} " + f"PF={(g/v if v > 0 else 9.99):>4.2f} ΣR={s:>+6.1f}") + + print(f"\n {'-'*96}") + print(f" (A) DAS VETO — beide Richtungen auf denselben {len(alle_erlaubt)} Episoden") + print(kz(alle_erlaubt, "MIT dem HTF (erlaubt)")) + print(kz(alle_verboten, "GEGEN den HTF (verboten)")) + if alle_erlaubt and alle_verboten: + d = sum(alle_erlaubt) / len(alle_erlaubt) - sum(alle_verboten) / len(alle_verboten) + print(f" → Unterschied {d:+.3f} R je Trade zugunsten der ERLAUBTEN Richtung") + print(f" {'✅ Veto zeigt in die richtige Richtung' if d > 0 else '❌ Veto zeigt FALSCH'}") + print(f"\n Episoden gesamt: {alle_epi} an {len(tagszeilen)} Tagen " + f"= {alle_epi/max(1,len(tagszeilen)):.1f} je Tag") + print("\n ⚠ EINE WOCHE = EIN REGIME. Kein Edge-Nachweis, nur Mechanik.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())