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:
Axel Hocks
2026-08-12 20:29:22 +02:00
co-authored by Claude Opus 5
parent 19ee379f20
commit d8839ef7c5
5 changed files with 369 additions and 2 deletions
+20 -1
View File
@@ -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
View File
@@ -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,