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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5fb1d7e268
commit
9e5ea22e08
@@ -0,0 +1,166 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user