Files
AH-Oil-Trader/tests/test_close_buchung.py
Axel HocksandClaude Opus 5 d8839ef7c5 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>
2026-08-12 20:29:22 +02:00

92 lines
4.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Fehlbuchung beim Schliessen: darf nicht entstehen — und muss korrigierbar sein.
ANLASS (2026-08-12, User: „ueberpruefe ob Tag P/L richtig berechnet wird").
Ergebnis: die FORMEL war richtig, die DATENBASIS nicht — Anzeige +14,26 EUR
gegen 74,08 EUR realisiert beim Broker.
⚠⚠ URSACHE, im Log im Sekundentakt sichtbar (#49926460):
20:04:10 SELL 1.64L eroeffnet
20:04:10 „Position extern geschlossen" <- 0 Sekunden spaeter
20:04:12 „kein OUT-Deal in 1 Deals"
20:04:12 Fallback-PnL 0,00 gebucht
20:11:42 der ECHTE Close — lief ins Leere
Drei Defekte griffen ineinander:
(1) `positions_get` sieht die frische Position einen Tick lang nicht,
(2) `_log_external_close` bucht TROTZ „kein OUT-Deal" einen Close,
(3) `log_trade_close` fasst nur `exit_time IS NULL` an → die Fehlbuchung
blockiert den echten Close dauerhaft.
Bei #49852362 kostete das **+5,87 statt 95,38 EUR** und buchte den Trade auf
den FALSCHEN TAG.
"""
from __future__ import annotations
import time
import pytest
# Zeitstempel MUESSEN nach der Eroeffnung liegen: `log_trade_close` schiebt
# ein exit VOR dem entry defensiv auf `jetzt` (sonst waere die Haltedauer
# negativ). Mit 1000/2000 misst man diese Defensive, nicht den Fix.
_T1 = int(time.time()) + 10
_T2 = int(time.time()) + 20
def test_fehlbuchung_wird_von_echtem_close_korrigiert(history):
"""Defekt (3): eine `unknown`-Zeile darf der Wahrheit nicht im Weg stehen."""
history.log_trade_open(ticket=4711, symbol="SpotCrude", direction="SELL",
lots=1.64, entry_price=83.162)
# Fehlbuchung, wie der Fallback sie schrieb
history.log_trade_close(ticket=4711, exit_price=0.0, pnl=0.0,
closed_by="unknown", exit_ts=_T1)
# der ECHTE Close mit Deal-Daten
history.log_trade_close(ticket=4711, exit_price=83.056, pnl=15.08,
closed_by="manual", exit_ts=_T2)
t = history.trade_by_ticket(4711)
assert t["pnl"] == 15.08, "der echte Close muss die Fehlbuchung ersetzen"
assert t["closed_by"] == "manual"
assert t["exit_price"] == 83.056
assert t["exit_time"] == _T2, "auch der TAG muss korrigiert werden"
def test_gute_zeile_wird_NICHT_ueberschrieben(history):
"""⚠ Die Gegenprobe — sonst waere der Fix schlimmer als der Fehler.
Ein sauber gebuchter Close ist endgueltig."""
history.log_trade_open(ticket=4712, symbol="SpotCrude", direction="BUY",
lots=1.0, entry_price=80.0)
history.log_trade_close(ticket=4712, exit_price=81.0, pnl=100.0,
closed_by="sl", exit_ts=_T1)
history.log_trade_close(ticket=4712, exit_price=99.9, pnl=-999.0,
closed_by="manual", exit_ts=_T2)
t = history.trade_by_ticket(4712)
assert t["pnl"] == 100.0 and t["closed_by"] == "sl" and t["exit_time"] == _T1
def test_unknown_ersetzt_nicht_unknown(history):
"""Eine zweite Fehlbuchung darf die erste nicht ueberschreiben — sonst
wandert der Zeitstempel bei jedem Tick weiter und der Trade landet
irgendwann auf einem beliebigen Tag."""
history.log_trade_open(ticket=4713, symbol="SpotCrude", direction="BUY",
lots=1.0, entry_price=80.0)
history.log_trade_close(ticket=4713, exit_price=0.0, pnl=0.0,
closed_by="unknown", exit_ts=_T1)
history.log_trade_close(ticket=4713, exit_price=0.0, pnl=5.0,
closed_by="unknown", exit_ts=_T2)
t = history.trade_by_ticket(4713)
assert t["exit_time"] == _T1 and t["pnl"] == 0.0
def test_offener_trade_bleibt_offen_ohne_close(history):
"""Grundinvariante: ohne Close-Buchung keine exit_time — sonst zaehlt ein
laufender Trade in den Tages-P&L."""
history.log_trade_open(ticket=4714, symbol="SpotCrude", direction="BUY",
lots=1.0, entry_price=80.0)
t = history.trade_by_ticket(4714)
assert t["exit_time"] is None and t["pnl"] is None
if __name__ == "__main__":
raise SystemExit(pytest.main([__file__, "-v"]))