diff --git a/CLAUDE.md b/CLAUDE.md index 21b4b87..2ef335c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -357,25 +357,40 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: Berliner Stunde (nicht Signal-Edge wie `backtest_hourly_split.py`), 80k M5-Bars (17.06.25–04.08.26), DST-korrekt via `zoneinfo`, 2 Hälften, Überschuss gegen den jeweiligen Tagesschnitt (sonst färbt das Regime alle Stunden ein: H1 fiel, H2 stieg). - **Beide Hälften der Behauptung fallen durch:** Vormittag 08–12 **kippt** - (H1 +0,55 / H2 −2,90 Zehntel-Cent je Bar), Nachmittag 14–18 ist zwar - vorzeichen-robust, aber **NEGATIV in beiden** (−0,23/−2,10) — also das Gegenteil - der Vermutung. Robust auf: 0/2/5/8/15/23 Uhr · robust ab: 17/21/22 Uhr — kein - Vormittag/Nachmittag-Muster, sondern verstreute Einzelstunden aus 24 Tests. - ⚠ **Größenordnung macht es ohnehin unhandelbar:** die stärkste robuste Stunde - (23:00, +2,2/+3,4) sammelt über eine volle Stunde ~0,03–0,04 $ — der Spread allein + **ALLE VIER Tagesblöcke kippen zwischen den Hälften** — es gibt überhaupt kein + Tageszeit-Muster (Zehntel-Cent je Bar): + | Block | H1 | H2 | | + |---|---|---|---| + | Vormittag 08–12 | +0,06 | −0,43 | kippt | + | Nachmittag 14–18 | −1,26 | +1,96 | kippt | + | Abend 19–23 | −0,39 | +1,67 | kippt | + | Nacht 00–07 | +0,42 | −1,30 | kippt | + Robust auf: 2/5/20/21/23 Uhr · robust ab: 14/18/19 Uhr — verstreute Einzelstunden + aus 24 Tests, kein Vormittag/Nachmittag-Muster. + ⚠ **Größenordnung macht es ohnehin unhandelbar:** die stärkste belastbare Stunde + (20:00, +1,9/+3,3) sammelt über eine volle Stunde ~0,02–0,04 $ — der Spread allein ist **0,0225 $**. Selbst wenn die Drift echt wäre, ist sie kleiner als die Kosten. - ⚠ Std 2:00 sticht mit +33,0 in H2 hervor, hat dort aber nur n=648 (vs. ~1740 sonst) - = dünne Sonntag-Open-/Feiertagsstunde, Ausreißer-verdächtig. - ⚠⚠ **Methodik-Lehre (eigener Fehler, dokumentiert):** der ERSTE Lauf meldete - „Vormittag 08–12 robust negativ" — Artefakt eines **selbst eingebauten ATR-Filters** - (`atr < 0,06` übersprungen + ATR-Normierung). In den ruhigen Morgenstunden der - älteren Periode liegt der ATR unter dem Floor → von 1.740 Bars je Stunde blieben - **138–391** übrig, und zwar ausgerechnet die ungewöhnlich volatilen Morgen. - Genau die Stunden, um die es ging, waren eine verzerrte Teilstichprobe. Gleiche - Fehlerklasse wie das ORB-Selektions-Artefakt 2026-07-17 (ATR-Skip statt Floor). + ⚠ Std 23 sticht mit +33,0 in H2 hervor, hat aber nur n=648 (22 Uhr: 1062, sonst + ~1750) — das ist der Marktschluss-Rand, kein Befund. + ⚠⚠ **ZWEI eigene Messfehler auf dem Weg dorthin — beide dokumentiert, weil sie + Fehlerklassen sind, keine Ausrutscher:** + **(1) Selbst gebauter ATR-Filter (04.08.).** Der erste Lauf meldete „Vormittag + robust negativ" — Artefakt von `atr < 0,06` übersprungen + ATR-Normierung. In den + ruhigen Morgenstunden der älteren Periode liegt der ATR unter dem Floor → von + 1.740 Bars je Stunde blieben **138–391** übrig, und zwar die ungewöhnlich + volatilen Morgen. Gleiche Fehlerklasse wie das ORB-Selektions-Artefakt 2026-07-17. **Regel: ein Filter, der Bars nach einer Eigenschaft verwirft, die mit der untersuchten Achse korreliert, ERZEUGT den Befund.** + **(2) Zeitzone um 3 h verschoben (05.08., beim Squeeze-Nachgang aufgefallen).** + `datetime.fromtimestamp(rates["time"], UTC+3)` behandelt den Bar-Zeitstempel als + echten UTC-Epoch — MT5 liefert dort aber bereits die **Broker-Wallclock**. Ergebnis: + jede Stunde war **3 Stunden zu spät** einsortiert; der zweite Lauf meldete deshalb + „Nachmittag robust negativ", was in Wahrheit der Abend war. **Richtig:** + `fromtimestamp(ts, UTC).replace(tzinfo=UTC+3).astimezone(Berlin)` (Helfer `_berlin` + im Skript). ⚠ Der Rest des Projekts war NICHT betroffen (geprüft) — `core/gaps.py` + & Co. nutzen die richtige Konvention; der Fehler steckte nur in diesem neuen Skript. + **Plausibilitätstest, der ihn entlarvt hätte: die dünnen Stunden (n≈650/1060) müssen + am Marktschluss liegen (22/23 Uhr), nicht mitten in der Nacht.** - **Tageszeit-Gate = AUS geschaltet (User-Vorgabe 2026-07-22, `[trading] dead_hours=` leer — bewusst GEGEN die Messung).** Jetzt **config-steuerbar** (`wave.set_dead_hours`, Komma-Liste von Stunden; leer=aus). Gemessener Default im Code = `0,1,2,3,4,5,6,7,12,16` @@ -1417,6 +1432,31 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: aus (dann auch nachts, unbeaufsichtigt). Einen engen Schutz-%-Stop NUR für Squeeze wurde bewusst NICHT gebaut (enger %-Stop unter Margin-Sizing wird vom Squeeze- Rauschen ausgelöst, −48,94-€-Tag — der Squeeze braucht Raum). + ⚠⚠ **FALLBEISPIEL 05.08.2026 — der Guard hat 5 Ausbrüche geblockt, darunter den + Einstieg direkt vor einem 1,8-$-Rutsch (User-Frage „warum ist der Squeeze nicht + eingestiegen?").** Nachgerechnet mit der ECHTEN `_squeeze_one` auf den M5-Bars + (nicht aus dem Log geschlossen — der Squeeze-ZUSTAND wird nirgends geloggt, nur der + Entry): zwischen 02:00 und 07:30 Berlin feuerte 5× `active`, **alle in 0–7 Uhr**. + Der Bot war ab 01:15 flat, die Nachtsperre war also der ALLEINIGE Blocker (kein + Cooldown, keine offene Position). Kursverlauf: Hoch **76,373** um 03:30 → Tief + **74,548** um 04:40 = **−1,825 $ in 70 min**; der Ausbruch **04:00 SHORT @ 76,11** + lag praktisch auf dem Hoch. + **Mit dem kanonischen Exit** (`exit_model.LIVE`, Echtkosten aus dem Bar-Spread — + bewusst NICHT „maximale Auslenkung", s. die Korrektur bei der 15-Minuten-Regel): + 02:00 SHORT **+3,28 R** · 03:00 LONG −0,24 · 04:00 SHORT **+1,36 R** · 06:15 LONG + −0,60 · 07:30 SHORT −0,24 → **Σ +3,56 R ≈ +95 € bei 2,66 Lots**. + ⚠ **Trotzdem kein Argument gegen den Guard:** n=5 an EINEM Tag, und **92 % davon + stecken in zwei Trades** — ohne den 02:00er bleiben +0,29 R ≈ +12 €. Ein Einzeltag + taugt nicht als Beleg, auch nicht als widerlegender (dieselbe Lehre wie am 01.08.). + Die Guard-Begründung ruht auf 2 Halbjahren; dazu kommt der nicht-ökonomische Grund + (keine unbeaufsichtigten Nacht-Trades), der von Zahlen gar nicht berührt wird. + ✅ **Ein messbarer Ansatz ergibt sich aber:** die Nacht-Begründung ist eine + **Kosten**-Aussage (Spread÷ATR 0,32–0,50). In dieser Nacht lagen die realen Kosten + bei **0,11–0,22×ATR**, also klar UNTER der Falle — weil der ATR hoch war. Ein + **kosten-basiertes statt stunden-basiertes Gate** („blocke, wenn Spread/ATR > X") + würde genau solche Nächte durchlassen und ruhige weiter sperren. **Ungemessen** — + vor dem Bau 2-Stichproben-Test gegen das Stunden-Gate; und es hebelt den + Beaufsichtigungs-Grund aus, ist also zusätzlich eine User-Entscheidung. **Stop-&-Reverse = AUS (Default, `[trading] auto_squeeze_reverse=false`, `_squeeze_reverse`; User-Vorgabe 2026-07-17):** War kurz scharf, dann abgeschaltet — nur der **flat-Entry ist gemessen-validiert**, der Reverse ist unbelegt, verdoppelt diff --git a/analyze_tageszeit.py b/analyze_tageszeit.py index aa27a60..a442ba9 100644 --- a/analyze_tageszeit.py +++ b/analyze_tageszeit.py @@ -31,7 +31,22 @@ import MetaTrader5 as mt5 _BERLIN = ZoneInfo("Europe/Berlin") _BROKER = timezone(timedelta(hours=3)) # Pepperstone: UTC+3 ganzjährig -_ATRMIN = 0.06 +_UTC = timezone.utc + + +def _berlin(ts): + """Bar-Zeitstempel → echte Berliner Zeit (DST-korrekt). + + ⚠⚠ FEHLERQUELLE, hier einmal falsch gemacht (Fix 2026-08-05): MT5 liefert + `rates["time"]` so, dass `fromtimestamp(ts, UTC)` bereits die BROKER-Wallclock + zeigt — die Zahl ist KEIN echter UTC-Epoch. Ein `fromtimestamp(ts, UTC+3)` + behandelt sie fälschlich als echten Epoch und landet **3 Stunden zu spät**. + Empirisch geprüft: letzte M5-Bar ergab so 11:00, während es real 08:00 war. + Richtig ist: die Wallclock als UTC lesen, dann als UTC+3 ETIKETTIEREN und + erst danach nach Berlin umrechnen. + """ + return (datetime.fromtimestamp(int(ts), _UTC) + .replace(tzinfo=_BROKER).astimezone(_BERLIN)) def _atr_series(H, L, C, p=14): @@ -59,7 +74,7 @@ def auswerten(bars, H, L, C, A, lo, hi): out = defaultdict(list) for i in range(max(lo, 1), hi): # Broker-Epoch → echte Berliner Stunde (DST-korrekt) - t = datetime.fromtimestamp(int(bars[i]["time"]), _BROKER).astimezone(_BERLIN) + t = _berlin(bars[i]["time"]) out[t.hour].append(C[i] - C[i - 1]) return out @@ -75,8 +90,8 @@ def main(): C = [float(b["close"]) for b in bars] A = _atr_series(H, L, C) N = len(C); mid = N // 2 - t0 = datetime.fromtimestamp(int(bars[0]["time"]), _BROKER).astimezone(_BERLIN) - t1 = datetime.fromtimestamp(int(bars[-1]["time"]), _BROKER).astimezone(_BERLIN) + t0 = _berlin(bars[0]["time"]) + t1 = _berlin(bars[-1]["time"]) h1 = auswerten(bars, H, L, C, A, 0, mid) h2 = auswerten(bars, H, L, C, A, mid, N)