"""analyze_srclose_live.py — War der S/R-Auto-Close live dienlich oder hinderlich? User-Frage 2026-07-31 („ist der Auto-Close beim S/R eher behindernd als dienlich?"). ⚠ WARUM DIE ROHE STATISTIK NICHT REICHT: `closed_by='sr_close'` hat live 99 % WR und Ø +13,71 € — das ist ein **Konstruktions-Artefakt**, der Close feuert nur im Plus. Die Frage ist die KONTRAFAKTISCHE: was wäre passiert, wenn der Trade weitergelaufen wäre? METHODE: für jeden sr_close-Trade die nächsten 60 min aus `candles_m1` holen und messen, wie weit der Kurs danach noch IN Trade-Richtung lief (= entgangener Gewinn) bzw. GEGEN sie (= vermiedener Rückgang). Umgerechnet in EUR über die tatsächliche Lot-Größe des Trades. ⚠ Grenzen (bewusst benannt): * Der Vergleich „hätte weitergelaufen" ist eine OBERGRENZE für das Halten — er unterstellt einen perfekten Ausstieg am besten Punkt. Real hätte das Trailing einen Teil davon wieder abgegeben. Die MAE-Spalte zeigt die Gegenseite. * 60-min-Fenster = dasselbe wie beim P(break)-Modelltraining (_BRK_W=12 M5-Bars). * `candles_m1.time` ist ROHE Broker-Zeit (UTC+3), `trades.exit_time` lokale Epoch — der Offset wird unten korrigiert. """ import sqlite3 import sys import time from datetime import datetime DB = "oil_widget_history.db" WINDOW_MIN = 60 # wie beim Modelltraining (_BRK_W=12 M5-Bars) def broker_offset_s(c) -> int: """Offset zwischen candles_m1.time (Broker, UTC+3) und lokaler Epoch. Robust bestimmt: die letzte M1-Kerze ist ~jetzt, die Differenz zu time.time() auf volle Stunden gerundet. """ c.execute("SELECT MAX(time) FROM candles_m1") last = c.fetchone()[0] if not last: return 0 return round((last - time.time()) / 3600.0) * 3600 def main(): db = sqlite3.connect(DB) c = db.cursor() off = broker_offset_s(c) print(f"Broker-Offset erkannt: {off/3600:+.0f} h\n") c.execute("""SELECT ticket, direction, lots, exit_time, exit_price, pnl, entry_price FROM trades WHERE closed_by='sr_close' AND exit_time IS NOT NULL AND exit_price IS NOT NULL AND lots > 0 ORDER BY exit_time""") rows = c.fetchall() if not rows: print("Keine sr_close-Trades gefunden.") return # EUR je 1,00 Kursbewegung und Lot — aus den Trades selbst kalibriert, damit # keine Kontraktgroesse geraten werden muss. cal = [] for tk, d, lots, xt, xp, pnl, ep in rows: if ep and xp and lots and pnl is not None and abs(xp - ep) > 1e-9: sgn = 1 if (d or "").upper().startswith("B") or d == "LONG" else -1 move = (xp - ep) * sgn if abs(move) > 0.01: cal.append(abs(pnl) / (abs(move) * lots)) cal.sort() eur_per_point_lot = cal[len(cal)//2] if cal else 100.0 print(f"kalibriert: {eur_per_point_lot:.1f} EUR je 1.00 Kurs und Lot " f"(Median aus {len(cal)} Trades)\n") tot_missed = tot_saved = 0.0 n_missed = n_saved = 0 detail = [] for tk, d, lots, xt, xp, pnl, ep in rows: long = (d == "LONG") or (d or "").upper().startswith("B") sgn = 1 if long else -1 t0 = xt + off t1 = t0 + WINDOW_MIN * 60 c.execute("SELECT MAX(h), MIN(l) FROM candles_m1 WHERE time > ? AND time <= ?", (t0, t1)) hi, lo = c.fetchone() if hi is None: continue # weiter IN Richtung (entgangen) / GEGEN Richtung (vermieden), in Kurspunkten fav = (hi - xp) if long else (xp - lo) adv = (xp - lo) if long else (hi - xp) fav = max(0.0, fav) adv = max(0.0, adv) eur_fav = fav * lots * eur_per_point_lot eur_adv = adv * lots * eur_per_point_lot tot_missed += eur_fav tot_saved += eur_adv if eur_fav > 1: n_missed += 1 if eur_adv > 1: n_saved += 1 detail.append((xt, tk, pnl, eur_fav, eur_adv)) n = len(detail) print(f"=== {n} S/R-Auto-Closes, Fenster {WINDOW_MIN} min danach ===\n") realized = sum(x[2] for x in detail) print(f"realisiert (tatsaechlich kassiert) : {realized:>10.2f} EUR") print(f"entgangen (Kurs lief noch weiter) : {tot_missed:>10.2f} EUR " f"(in {n_missed}/{n} Faellen)") print(f"vermieden (Kurs drehte zurueck) : {tot_saved:>10.2f} EUR " f"(in {n_saved}/{n} Faellen)") print() print(f"Ø entgangen je Trade : {tot_missed/n:>8.2f} EUR") print(f"Ø vermieden je Trade : {tot_saved/n:>8.2f} EUR") print(f"Netto (vermieden - entgangen): {(tot_saved-tot_missed)/n:+8.2f} EUR/Trade " f"= {tot_saved-tot_missed:+.2f} EUR gesamt") print() print("⚠ 'entgangen' ist eine OBERGRENZE fuer das Halten (perfekter Ausstieg am") print(" Hoch). 'vermieden' ist die Gegenseite: so weit waere der Trade") print(" zurueckgefallen. Die Wahrheit liegt dazwischen — real haette das") print(" Trailing einen Teil des Laufs mitgenommen und einen Teil abgegeben.") # Verteilung: wie oft war der Close klar richtig / klar zu frueh? clear_right = sum(1 for x in detail if x[4] > 2 * x[3]) clear_early = sum(1 for x in detail if x[3] > 2 * x[4]) print() print(f"klar RICHTIG geschlossen (Rueckfall > 2x weiterer Lauf): {clear_right}/{n} " f"= {100*clear_right/n:.0f}%") print(f"klar ZU FRUEH (weiterer Lauf > 2x Rueckfall) : {clear_early}/{n} " f"= {100*clear_early/n:.0f}%") if "-v" in sys.argv: print("\n--- Einzelfaelle ---") for xt, tk, pnl, f, a in detail: print(f"{datetime.fromtimestamp(xt):%d.%m %H:%M} #{tk} " f"kassiert {pnl:+7.2f} | entgangen {f:6.2f} | vermieden {a:6.2f}") db.close() if __name__ == "__main__": main()