Tag P/L war falsch: Fehlbuchungen beim Schliessen behoben + repariert
User-Frage "ueberpruefe ob Tag P/L richtig berechnet wird". Die Formel (realisiert + offen) war bitgenau richtig - die Datenbasis nicht: Anzeige +14,26 EUR gegen -74,08 EUR realisiert beim Broker. Ursache, im Log im Sekundentakt (#49926460): 20:04:10 eroeffnet -> 20:04:10 "extern geschlossen" -> "kein OUT-Deal in 1 Deals" -> Fallback bucht 0,00 -> 20:11:42 der echte Close lief ins Leere. Drei Defekte: (1) positions_get sieht die frische Position einen Tick lang nicht, (2) _log_external_close buchte TROTZ "kein OUT-Deal" einen Close - dabei ist genau das der Beweis, dass sie noch offen ist, (3) log_trade_close fasste nur exit_time IS NULL an, die Fehlbuchung blockierte den echten Close dauerhaft. Bei #49852362 kostete das +5,87 statt -95,38 EUR und den falschen Tag. Behoben: (1) Abbruch statt Fallback bei "kein OUT-Deal"; (2) eine erkennbare Fehlbuchung (closed_by='unknown') darf von einem echten Close korrigiert werden - eng gefasst, gute Zeilen bleiben unberuehrt. 4 Tests inkl. Gegenprobe. Altlast: tools/repair_closes.py (Trockenlauf Standard, Backup automatisch). 18 Zeilen ueber 60 Tage korrigiert. Heute von +7,63 auf -72,67 (Restfehler 1,41). Gesamt-P&L von -282,78 auf +169,98. Zeitfalle zweimal getroffen: history_deals_get filtert nach Broker-Wallclock, und fromtimestamp(d.time, BROKER) rendert 3 h zu spaet. Aufgefallen nur, weil eine Deal-Zeit 23:11 lautete, das Log aber 20:11:42 sagte. Statistik-Modul separat geprueft: Arithmetik in allen drei Zeitraeumen bitgenau korrekt (Abweichung 0,00). Der Netto-Defekt vom 05.08. besteht weiter und ist groesser als damals: angezeigt -2.449,95 EUR, real verblieben -17,06 (99 % der Steuer werden erstattet). Nicht gebaut - die Loesung ist dokumentiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
19ee379f20
commit
d8839ef7c5
+20
-1
@@ -395,12 +395,31 @@ class HistoryLogger:
|
||||
).fetchone()
|
||||
if row and row["entry_time"] and ts < row["entry_time"]:
|
||||
ts = int(time.time())
|
||||
conn.execute("""
|
||||
cur = conn.execute("""
|
||||
UPDATE trades
|
||||
SET exit_time = ?, exit_price = ?, pnl = ?,
|
||||
closed_by = ?, commission = ?
|
||||
WHERE ticket = ? AND exit_time IS NULL
|
||||
""", (ts, exit_price, pnl, closed_by, commission, ticket))
|
||||
# ⚠⚠ KORREKTUR EINER FEHLBUCHUNG (2026-08-12). Bis dahin galt strikt
|
||||
# `exit_time IS NULL` — eine einmal geschriebene Zeile war endgueltig.
|
||||
# Das machte einen Folgefehler dauerhaft: buchte der Fallback-Pfad
|
||||
# eine Position faelschlich als geschlossen (real #49852362:
|
||||
# +5,87 statt −95,38 € und auf dem falschen Tag), lief der SPAETERE,
|
||||
# ECHTE Close still ins Leere.
|
||||
# ⚠ ENG GEFASST, damit keine gute Zeile ueberschrieben wird:
|
||||
# korrigiert wird NUR, wenn die bestehende Zeile eine erkennbare
|
||||
# Fehlbuchung ist (`closed_by='unknown'`) UND die neue Buchung
|
||||
# echte Deal-Daten mitbringt (`closed_by != 'unknown'`).
|
||||
if cur.rowcount == 0 and closed_by != "unknown":
|
||||
cur = conn.execute("""
|
||||
UPDATE trades
|
||||
SET exit_time = ?, exit_price = ?, pnl = ?,
|
||||
closed_by = ?, commission = ?
|
||||
WHERE ticket = ? AND closed_by = 'unknown'
|
||||
""", (ts, exit_price, pnl, closed_by, commission, ticket))
|
||||
if cur.rowcount:
|
||||
self._korrigiert = getattr(self, "_korrigiert", 0) + 1
|
||||
conn.commit()
|
||||
# KEIN Trade-Abschluss-Telegram mehr (User-Vorgabe): Telegram zum Thema
|
||||
# Schließen kommt nur noch beim echten Signal-Flip (engine._check_close_
|
||||
|
||||
+26
-1
@@ -779,9 +779,34 @@ class TradeManager:
|
||||
f"Externer Close: T={ticket} {closed_by} @ "
|
||||
f"{close_deal.price:.3f} pnl={total_profit:.2f}")
|
||||
return
|
||||
log_hist.warning(f"T={ticket}: kein OUT-Deal in {len(own)} Deals")
|
||||
# ⚠⚠ HIER LAG DER FEHLER (behoben 2026-08-12). Vorher fiel dieser
|
||||
# Zweig in den Fallback durch und buchte den zuletzt bekannten
|
||||
# SCHWEBE-Gewinn als realisierten Close.
|
||||
# Das ist genau falsch herum: wir haben die Deals der Position
|
||||
# GEFUNDEN und festgestellt, dass **kein OUT-Deal existiert**.
|
||||
# Das ist positive Evidenz, dass die Position NOCH OFFEN ist —
|
||||
# typischerweise ein transient leeres `positions_get` direkt
|
||||
# nach dem Eröffnen.
|
||||
# Real am 12.08.: #49926460 wurde 0 Sekunden nach dem Öffnen als
|
||||
# „extern geschlossen" mit pnl 0,00 und exit_price 0,0 gebucht;
|
||||
# der ECHTE Close 7 min später lief ins Leere, weil
|
||||
# `log_trade_close` nur Zeilen mit `exit_time IS NULL` anfasst.
|
||||
# Bei #49852362 kostete dasselbe **+5,87 statt −95,38 €** und
|
||||
# buchte den Trade auf den FALSCHEN TAG.
|
||||
# ➜ Nicht schreiben. Der nächste Tick prüft erneut; bleibt die
|
||||
# Position wirklich weg, liefert der Broker dann einen
|
||||
# OUT-Deal. `reconcile_open_trades` ist das zweite Netz.
|
||||
log_hist.warning(
|
||||
f"T={ticket}: kein OUT-Deal in {len(own)} Deals — Position gilt "
|
||||
f"als WEITER OFFEN, kein Close gebucht (Fallback uebersprungen)")
|
||||
return
|
||||
|
||||
# ── Methode 3: Fallback — letzter bekannter PnL ───────────────────
|
||||
# ⚠ Nur noch fuer den WIRKLICH blinden Fall: es liess sich KEIN
|
||||
# einziger Deal der Position abrufen (MT5-Aussetzer, Broker setzt
|
||||
# `position_id` nicht). Dann ist „offen oder zu" nicht entscheidbar,
|
||||
# und eine Zeile mit `closed_by='unknown'` ist besser als ein Trade,
|
||||
# der ewig offen in der DB haengt.
|
||||
if fallback_pnl is not None:
|
||||
self.history.log_trade_close(
|
||||
ticket=ticket,
|
||||
|
||||
Reference in New Issue
Block a user