#!/usr/bin/env python3 """Kosten des diskretionären Früh-Kappens — kontrafaktisch (2026-08-04). Frage: Deine MANUELLEN Closes haben einen Median-Gewinn von +6,37 €, die S/R-Auto-Closes des Bots +14,65 €. Ist das Früh-Kappen teuer — oder rettet es Trades, die sonst gedreht wären? Methode (wie `analyze_srclose_live.py`): Für jeden real geschlossenen Trade wird ab dem ECHTEN Einstieg der **kanonische Live-Exit** (`core/exit_model.LIVE`) über die M5-Bars simuliert. Verglichen wird das tatsächliche Ergebnis gegen das, was der Bot-Exit erbracht hätte. ⚠ Grenzen, die das Ergebnis NICHT überschreiten darf: · Kontrafaktisch heißt: der Trade hätte den Slot länger belegt. Folge-Trades, die real stattfanden, wären dann evtl. nicht zustande gekommen. Der Vergleich ist also PRO TRADE gültig, nicht als Kontostand-Hochrechnung. · Der EUR-Faktor wird aus den echten Trades geschätzt (Median über Trades mit klarem Move und kurzer Laufzeit, damit der Swap nicht verzerrt) — `trader.pnl` enthält Swap, eine direkte Rückrechnung wäre verfälscht. · Slippage ist nicht modelliert (real bis 0,75×ATR über den Stop hinaus gemessen). Aufruf: python analyze_manual_close.py [tage] """ import bisect import datetime as dt import sqlite3 import statistics as st import sys import MetaTrader5 as mt5 from core.exit_model import LIVE, simulate _BOFF = 3 * 3600 _ATRMIN = 0.06 def _atr_series(H, L, C, p=14): t = [0.0] for i in range(1, len(C)): t.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1]))) return [(sum(t[max(1, i - p + 1):i + 1]) / max(1, len(t[max(1, i - p + 1):i + 1]))) if i else None for i in range(len(C))] def main(): tage = int(sys.argv[1]) if len(sys.argv) > 1 else 30 mt5.initialize() bars = mt5.copy_rates_from_pos("SpotCrude", mt5.TIMEFRAME_M5, 0, 60000) mt5.shutdown() T = [int(b["time"]) - _BOFF 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] A = _atr_series(H, L, C) c = sqlite3.connect("oil_widget_history.db"); c.row_factory = sqlite3.Row seit = (dt.datetime.now() - dt.timedelta(days=tage)).timestamp() rows = [r for r in c.execute( "SELECT * FROM trades WHERE exit_time>=? AND pnl IS NOT NULL " "AND lots>0 ORDER BY entry_time", (seit,))] # EUR je 1,00 $ und Lot robust schätzen: nur Trades mit klarem Move und # kurzer Laufzeit (Swap fällt kaum ins Gewicht). f = [] for r in rows: mv = (r["exit_price"] - r["entry_price"]) * (1 if r["direction"] == "BUY" else -1) dur = (r["exit_time"] - r["entry_time"]) / 60 if abs(mv) > 0.20 and dur < 120 and r["lots"]: f.append(r["pnl"] / (mv * r["lots"])) faktor = st.median(f) if f else 87.0 print(f"EUR je 1,00 $ und Lot (geschätzt aus {len(f)} Trades): {faktor:.1f}\n") gruppen = {} for r in rows: i = bisect.bisect_left(T, r["entry_time"]) if i <= 0 or i >= len(T) - LIVE.max_hold - 2 or abs(T[i] - r["entry_time"]) > 900: continue atr = A[i] if not atr or atr < _ATRMIN: continue d = 1 if r["direction"] == "BUY" else -1 R, _xj = simulate(r["entry_price"], d, atr, H, L, C, i + 1, LIVE, timestop=True, use_tp=True, ret_bar=True) soll = R * atr * r["lots"] * faktor # was der Bot-Exit gebracht hätte ist = r["pnl"] key = (r["closed_by"] or "?", "Gewinn" if ist > 0 else "Verlust") gruppen.setdefault(key, []).append((ist, soll, r)) print(f"{'Schließgrund':<16}{'Lage':<9}{'n':>5}{'IST Σ':>10}{'Bot-Exit Σ':>12}" f"{'Δ':>10}{'Δ je Trade':>12}") print("-" * 76) for key in sorted(gruppen, key=lambda k: -len(gruppen[k])): v = gruppen[key] ist = sum(x[0] for x in v); soll = sum(x[1] for x in v) print(f"{key[0]:<16}{key[1]:<9}{len(v):>5}{ist:>+10.0f}{soll:>+12.0f}" f"{soll-ist:>+10.0f}{(soll-ist)/len(v):>+12.1f}") man = [x for k, v in gruppen.items() if k[0] == "manual" for x in v] if man: besser = sum(1 for i_, s_, _ in man if s_ > i_) print(f"\n MANUELLE Closes gesamt: n={len(man)} " f"IST {sum(x[0] for x in man):+.0f} € " f"Bot-Exit {sum(x[1] for x in man):+.0f} € " f"Δ {sum(x[1]-x[0] for x in man):+.0f} €") print(f" In {besser} von {len(man)} Fällen ({100*besser/len(man):.0f} %) hätte " f"der Bot-Exit mehr gebracht.") gew = [x for x in man if x[0] > 0] if gew: print(f" Nur die manuellen GEWINN-Closes: n={len(gew)} " f"Δ {sum(x[1]-x[0] for x in gew):+.0f} € " f"({sum(x[1]-x[0] for x in gew)/len(gew):+.1f} € je Trade)") print("\n Die 8 teuersten Früh-Kappungen:") for i_, s_, r in sorted(gew, key=lambda x: x[0] - x[1])[:8]: print(f" {dt.datetime.fromtimestamp(r['exit_time']).strftime('%d.%m %H:%M')} " f"IST {i_:+7.2f} Bot-Exit {s_:+7.2f} Δ {s_-i_:+7.2f} " f"{r['direction']} {r['lots']}L") print("\n ⚠ PRO TRADE gültig, nicht als Kontostand-Hochrechnung: ein länger") print(" laufender Trade hätte den Positions-Slot belegt und Folge-Trades verhindert.") print() if __name__ == "__main__": main()