Files
AH-Oil-Trader/analyze_srclose_reentry.py
T
Axel HocksandClaude Opus 5 9e5ea22e08 Wiedereinstieg nach falschem S/R-Close gemessen - nicht gebaut
User-Frage: wenn die Linie trotz Abprall-Berechnung bricht, waere ein
Neustart des Trades logisch. Der Fall ist kein Randfall - P(break)
liegt live bei 38,7 %, das Gate schliesst 94-97 % aller Beruehrungen.

Erst ein eigener Messfehler: der erste Lauf meldete 93 % Brueche bei
einer Basisrate von 38,7 %. Ursache - ich prueft nur EINE Barriere.
Das Training entscheidet per Rennen (Bestaetigung gegen Ablehnung, wer
zuerst kommt); eine 0,5xATR-Bewegung binnen 60 min passiert fast
immer. Mit dem Rennen: 33 % Brueche, praktisch die Basisrate. Die
Plausibilitaetspruefung hat den Fehler gefangen.

Ergebnis: n=14, Treffer 71 %, OeR +0,859, ~+501 EUR - aber das
95-%-KI ist [-0,084 ... +1,788] und enthaelt die Null. Und drei Trades
tragen alles: die drei groessten Gewinne stammen alle vom 04.08.
(TP-Deckel, zusammen +529,92 EUR), die uebrigen 11 ergeben -28,63 EUR.
Der 04.08. ist der dokumentierte Ausreisser-Tag.

Urteil: nicht bauen. Die vorab fixierte Regel ist verfehlt. Fairerweise
widerlegt der bestehende Wiedereinstiegs-Cooldown die Idee nicht - er
betraf den ungegateten Sofort-Wiedereinstieg, nicht den bestaetigten
Bruch. Sauberer naechster Schritt waere ein Backtest ueber 2
Halbjahre statt 43 Live-Closes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 09:40:21 +02:00

167 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 9497 % 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())