Files
AH-Oil-Trader/analyze_srclose_live.py
Axel HocksandClaude Opus 5 578711a290 analyze_srclose_live.py: kontrafaktische Live-Messung des S/R-Auto-Close
User-Frage "ist der Auto-Close beim S/R eher behindernd als dienlich?".

Die rohe Statistik (99 % WR, +13,71 EUR/Trade) ist ein Konstruktions-
Artefakt - der Close feuert nur im Plus. Das Skript misst stattdessen die
Kontrafaktische: fuer jeden sr_close die naechsten 60 min aus candles_m1
(gleiches Fenster wie das Modelltraining, _BRK_W=12 M5-Bars), wie weit der
Kurs noch IN Richtung lief (entgangen) bzw. GEGEN sie (vermieden), in EUR
ueber die echte Lot-Groesse. EUR-je-Punkt wird aus den Trades selbst
kalibriert statt geraten. Broker-Offset (candles_m1.time = rohe UTC+3)
wird korrigiert.

Befund (70 auswertbare Closes): entgangen 33,68 EUR/Trade vs vermieden
25,68 -> netto -8,00 EUR/Trade. 43 % klar zu frueh geschlossen, nur 26 %
klar richtig. Deckt sich mit der Live-P(break)-Genauigkeit (58,8 % bei
einer Basisrate von 59,3 % = kaum Trennschaerfe ueber "immer Abpraller").

⚠ Einordnung: 89 der 98 Closes liefen noch auf M5-Levels; die M30-
Umstellung ist 1 Tag alt (9 Closes). Die Messung beurteilt also vor allem
die ALTE Konfiguration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 07:46:10 +02:00

142 lines
5.7 KiB
Python

"""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()