Netto-Rechnung aus den ECHTEN Steuer-Buchungen statt geschaetzt

Der seit 05.08. dokumentierte Defekt ist behoben. _add_net rechnete
net_pnl = total_pnl - gross_win * 26,375 % und unterstellte, die
Quellensteuer sei endgueltig verloren. Sie wird aber taeglich per
"Tax settlement" erstattet - real zu 99 %.

all: vorher net_pnl -2.449,95 EUR, jetzt +181,00 (total +209,93, echte
Steuerlast +28,93). Verzerrung rund 2.630 EUR - sie machte aus einem
profitablen Konto ein verlustreiches. Die Schaetzung der EINBEHALTENEN
Summe war fast exakt; falsch war allein die Annahme, sie bleibe weg.

trader.steuer_buchungen() liest die WHT-/Tax-Deals direkt
(position_id == 0, sauber von Trade-Deals getrennt), unter mt5_lock,
5 min gecacht. Fail-safe: ohne MT5 Rueckfall auf die Schaetzung, das
Feld wht_quelle sagt welche Zahl drinsteht.

Kurze Zeitraeume bleiben verzerrt, in BEIDE Richtungen - die Erstattung
kommt am Folgetag. Bei "today" ist wht sogar negativ, weil die
Erstattung von gestern heute einging. Ehrlich benannt, nicht kaschiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-12 20:44:09 +02:00
co-authored by Claude Opus 5
parent d8839ef7c5
commit 5819a9df21
3 changed files with 127 additions and 9 deletions
+59
View File
@@ -826,6 +826,65 @@ class TradeManager:
except Exception as e:
log_hist.error(f"_log_external_close: {e}")
# ── ECHTE Steuer-Buchungen statt Schaetzung ──────────────────────────────
_STEUER_TTL_S = 300 # MT5-Abfrage gehoert nicht in jeden Aufruf
def steuer_buchungen(self, von_ts: int, bis_ts: int) -> dict | None:
"""Tatsaechlich einbehaltene und erstattete Quellensteuer im Zeitfenster.
⚠⚠ WARUM DAS DIE SCHAETZUNG ERSETZT (Befund 05.08., nachgemessen 12.08.):
`server._add_net` rechnete `net_pnl = total_pnl gross_win × 26,375 %` und
unterstellte damit, die WHT auf JEDEN Gewinn-Trade sei endgueltig verloren.
Sie wird aber **taeglich per `Tax settlement` erstattet**. Gemessen ueber
90 Tage: einbehalten 2.625,89 €, erstattet +2.608,83 € → verblieben
**17,06 €**. Die Schaetzung sagte 2.619,93 € und machte damit aus
**+153 € rund 2.450 €**.
✅ Die Schaetzung der EINBEHALTENEN Summe ist fast exakt (2.620 gegen
2.626) — falsch ist allein die Annahme, sie bleibe weg.
Rueckgabe `{wht, tax, netto, n}` in Kontowaehrung (wht negativ, tax
positiv, netto = Summe) oder `None`, wenn MT5 nicht antwortet.
⚠ Die Buchungen haben `position_id == 0` und sind damit von den Trade-Deals
sauber getrennt — kein Doppelzaehlen mit `total_pnl`.
⚠ ZEITFALLE: `history_deals_get` filtert nach BROKER-Wallclock. Deshalb
Epochs mit Offset uebergeben und beim Lesen wieder abziehen — mit
`datetime`-Objekten verschiebt sich das Fenster (real zweimal passiert).
⚠ Gecacht (`_STEUER_TTL_S`), weil die Abfrage ueber lange Zeitraeume
Sekunden braucht.
"""
schluessel = (int(von_ts), int(bis_ts))
cache = getattr(self, "_steuer_cache", None)
if cache and cache[0] == schluessel and time.time() - cache[1] < self._STEUER_TTL_S:
return cache[2]
off = self._broker_offset_s()
try:
with mt5_lock(timeout=8) as got:
if not got:
return None
deals = mt5.history_deals_get(int(von_ts) + off, int(bis_ts) + off + 300)
except Exception as e:
log_hist.warning(f"steuer_buchungen: {e}")
return None
if deals is None:
return None
wht = tax = 0.0
n = 0
for d in deals:
k = (getattr(d, "comment", "") or "")
if getattr(d, "position_id", 0):
continue # Trade-Deal, nicht Steuer
ts = int(getattr(d, "time", 0) or 0) - off
if not (von_ts <= ts <= bis_ts):
continue
if "WHT" in k:
wht += float(d.profit); n += 1
elif "Tax" in k:
tax += float(d.profit); n += 1
res = {"wht": round(wht, 2), "tax": round(tax, 2),
"netto": round(wht + tax, 2), "n": n}
self._steuer_cache = (schluessel, time.time(), res)
return res
def reconcile_open_trades(self, lookback_hours: int = 168):
if not self.history:
return