diff --git a/analyze_srclose_live.py b/analyze_srclose_live.py new file mode 100644 index 0000000..7e18096 --- /dev/null +++ b/analyze_srclose_live.py @@ -0,0 +1,141 @@ +"""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()