#!/usr/bin/env python3 """Nach einem S/R-Close bricht die Linie doch — lohnt der WIEDEREINSTIEG? ANLASS (2026-08-12, User: „falls die Linie trotz Abprall-Berechnung durchbrochen wird — logisch waere den Trade wieder neu zu starten, oder?"). ⚠ Der Fall ist kein Randfall: P(break) liegt live bei **38,7 %**, und das Gate schliesst 94–97 % aller Beruehrungen. In rund **vier von zehn** Closes bricht die Linie also doch. Die Frage ist damit handelsrelevant, nicht theoretisch. ⚠⚠ WAS DAGEGEN SPRICHT, BEVOR MAN MISST — beides ist dokumentiert: (1) Es gibt bereits einen **Wiedereinstiegs-Cooldown** (`_SQUEEZE_SR_COOLDOWN_S` = 180 s, seit 17.07.): nach einem S/R-Close darf der Auto-Squeeze **nicht in GLEICHER Richtung** sofort wieder oeffnen. Gebaut wegen eines real beobachteten 1-Sekunden-Roundtrips (LONG-Close +15 €, 1 s spaeter neuer LONG, doppelter Spread). Der Wiedereinstieg ist also schon einmal als schaedlich eingestuft worden — allerdings als SOFORTIGER, ungegateter. (2) Jeder Wiedereinstieg kostet sofort einen vollen Spread: auf M5 im Median **0,193×ATR**. Der Durchbruch muss das erst verdienen. METHODE — kontrafaktisch auf den ECHTEN Closes: · alle `closed_by='sr_close'`-Trades ab dem Nachtrainieren (31.07.) · Level ≈ Ausstiegspreis (dort wurde geschlossen) · **Bruch = Bestaetigung**, nicht die nackte Levelzahl: Kurs erreicht `level + 0,5×ATR` in Trade-Richtung — dieselbe Definition wie im P(break)-TRAINING (`_BRK_ATR=0,5`), sonst zaehlt jeder Docht als Bruch. · Wiedereinstieg als **ruhende Stop-Order** an der Bestaetigungsmarke, Fuellung bei BERUEHRUNG, kanonischer Exit, Echtkosten. · Fenster: 12 M5-Bars = 60 min (wie das Trainings-Fenster `_BRK_W`). ⚠ ENTSCHEIDUNGSREGEL, VORAB: gebaut wird nur, wenn der Wiedereinstieg in Summe positiv ist UND das 95-%-Bootstrap-Intervall die Null ausschliesst. n ist klein (~40 Closes) — ein ✅ waere hier ein Hinweis, kein Beleg; ein ❌ ist belastbar. Aufruf: python analyze_srclose_reentry.py """ from __future__ import annotations import bisect import datetime as dt import random import sqlite3 import MetaTrader5 as mt5 from core.exit_model import LIVE, simulate DB = "oil_widget_history.db" _SEIT = dt.datetime(2026, 7, 31, 12).timestamp() # P(break) nachtrainiert _BRK_ATR = 0.5 # Bestaetigung wie im Training _BRK_W = 12 # 12 M5-Bars = 60 min _EURF = 87.6 # EUR je $ je Lot (robust aus 251 Trades geschaetzt) def atr_series(H, L, C, p=14): tr = [0.0] for i in range(1, len(C)): tr.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1]))) out = [None] for i in range(1, len(C)): seg = tr[max(1, i - p + 1):i + 1] out.append(sum(seg) / len(seg)) return out def ci(v, n=4000, seed=42): if len(v) < 8: return None, None r = random.Random(seed); m = len(v) b = sorted(sum(v[r.randrange(m)] for _ in range(m)) / m for _ in range(n)) return b[int(.025 * n)], b[int(.975 * n)] def main(): db = sqlite3.connect(DB); db.row_factory = sqlite3.Row tr = [dict(r) for r in db.execute( "SELECT ticket,exit_time,exit_price,direction,lots,pnl FROM trades " "WHERE closed_by='sr_close' AND exit_time>=? AND exit_price IS NOT NULL " "ORDER BY exit_time", (_SEIT,))] 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, 20000) point = mt5.symbol_info(sym).point mt5.shutdown() T = [int(b["time"]) - 10800 for b in bars] # -> lokale Epoch 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) print("=" * 94) print(f" WIEDEREINSTIEG NACH S/R-CLOSE — {len(tr)} Closes ab 31.07. ({sym})") print(" Bruch = Kurs erreicht Level ± 0,5×ATR in Trade-Richtung binnen 60 min") print(" (dieselbe Definition wie im P(break)-Training — ein Docht zaehlt NICHT)") print("=" * 94) gebrochen, Rs, eur = 0, [], [] zeilen = [] for t in tr: i = bisect.bisect_right(T, t["exit_time"]) - 1 if i < 20 or i >= len(C) - LIVE.max_hold - 2 or not A[i]: continue d = 1 if (t["direction"] or "").upper() in ("BUY", "LONG") else -1 atr = A[i] lvl = float(t["exit_price"]) marke = lvl + d * _BRK_ATR * atr # Bestaetigung (Bruch) gegen = lvl - d * _BRK_ATR * atr # Ablehnung (Abprall) # ⚠⚠ BARRIEREN-RENNEN, nicht eine Schwelle (Korrektur 2026-08-12). # Der erste Entwurf fragte nur „erreicht der Kurs die Bestaetigung?" # — und meldete **93 % Brueche** bei einer Basisrate von 38,7 %. # Kein Marktbefund, sondern ein Code-Befund: eine 0,5×ATR-Bewegung # binnen 60 min passiert fast immer. Das Training entscheidet per # RENNEN (confirm gegen reject, wer zuerst kommt) — genauso hier. # ⚠ Innerhalb EINES Bars nicht aufloesbar → pessimistisch die # Ablehnung zaehlen (wie `simulate` bei SL/TP im selben Bar). fill = None for j in range(i + 1, min(i + 1 + _BRK_W, len(C) - LIVE.max_hold - 2)): traf_gegen = (L[j] <= gegen) if d > 0 else (H[j] >= gegen) traf_marke = (H[j] >= marke) if d > 0 else (L[j] <= marke) if traf_gegen: break # Level hat gehalten → kein Wiedereinstieg if traf_marke: fill = j; break if fill is None: continue # Level hielt oder unentschieden gebrochen += 1 kost = (SP[fill] if SP[fill] > 0 else 0.0225) / atr R = simulate(marke, d, atr, H, L, C, fill + 1, LIVE) - kost Rs.append(R) e = R * atr * (t["lots"] or 1.0) * _EURF eur.append(e) zeilen.append((dt.datetime.fromtimestamp(t["exit_time"]), d, lvl, marke, R, e)) n_aus = len([t for t in tr if bisect.bisect_right(T, t["exit_time"]) - 1 >= 20]) print(f"\n auswertbar: {n_aus} Closes") print(f" davon Linie GEBROCHEN (bestaetigt): {gebrochen} " f"= {100*gebrochen/max(1,n_aus):.0f} %") if not Rs: print(" → kein einziger bestaetigter Bruch. Der Wiedereinstieg haette nie gefeuert.") return 0 lo, hi = ci(Rs) w = sum(1 for x in Rs if x > 0) print(f"\n ── der Wiedereinstieg (ruhende Stop-Order an der Bestaetigung) ──") print(f" n={len(Rs)} Treffer {100*w/len(Rs):.0f} % ØR {sum(Rs)/len(Rs):+.3f} " f"ΣR {sum(Rs):+.2f}") print(f" 95-%-KI des ØR: " + (f"[{lo:+.3f} … {hi:+.3f}]" if lo is not None else "— zu wenig")) print(f" grob in EUR: {sum(eur):+.2f} (Ø {sum(eur)/len(eur):+.2f} je Wiedereinstieg)") print(f"\n {'Zeit':<14}{'Ri':<4}{'Level':>9}{'Einstieg':>10}{'R':>8}{'EUR':>10}") for z in zeilen[:20]: print(f" {z[0]:%d.%m %H:%M} {'▲' if z[1]>0 else '▼':<3}{z[2]:>9.3f}" f"{z[3]:>10.3f}{z[4]:>+8.2f}{z[5]:>+10.2f}") print("\n " + "-" * 90) print(" ⚠ REGEL (vorab): bauen nur, wenn ΣR positiv UND das KI die Null") print(" ausschliesst. n ist klein — ein ✅ ist ein Hinweis, kein Beleg.") print(" ⚠ Nicht modelliert: der Slot ist waehrend des Wiedereinstiegs belegt,") print(" andere Setups koennen in der Zeit nicht feuern.") return 0 if __name__ == "__main__": raise SystemExit(main())