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:
co-authored by
Claude Opus 5
parent
d8839ef7c5
commit
5819a9df21
@@ -6209,6 +6209,34 @@ aber „20:11:42 CLOSE" sagte. ➜ **Epochs übergeben (lokal + Offset), beim Le
|
||||
Moment des Closes, der Broker den Fill). Die Summe hebt sich fast auf (+1,66 €
|
||||
über 19 Trades) → nur berichtet, **nicht** automatisch korrigiert.
|
||||
|
||||
## ✅✅ NETTO-RECHNUNG AUS DEN ECHTEN STEUER-BUCHUNGEN (2026-08-12)
|
||||
|
||||
Der seit dem 05.08. dokumentierte Defekt ist behoben. `server._add_net` schaetzte
|
||||
`net_pnl = total_pnl − gross_win × 26,375 %` und unterstellte, die Quellensteuer
|
||||
sei endgueltig verloren. Sie wird aber taeglich per `Tax settlement` erstattet.
|
||||
| Zeitraum | total_pnl | wht (ECHT) | net_pnl | einbehalten / erstattet |
|
||||
|---|---|---|---|---|
|
||||
| today | −32,72 | −68,93 | **+36,21** | −21,03 / **+89,96** |
|
||||
| week | −1.235,26 | −432,88 | −802,38 | −425,71 / +858,59 |
|
||||
| **all** | **+209,93** | **+28,93** | **+181,00** | −2.754,56 / **+2.725,63** |
|
||||
⚠⚠ **Vorher stand bei `all` −2.449,95 €** — eine Verzerrung von rund **2.630 €**,
|
||||
die aus einem profitablen Konto ein verlustreiches machte.
|
||||
✅ Die Schaetzung der EINBEHALTENEN Summe war fast exakt (2.620 gegen 2.755);
|
||||
falsch war allein die Annahme, sie bleibe weg — real werden **99 %** erstattet.
|
||||
|
||||
**Umsetzung:** `trader.steuer_buchungen(von, bis)` liest die `WHT #…`- und
|
||||
`Tax settlement`-Deals direkt (position_id == 0, damit sauber von den Trade-Deals
|
||||
getrennt — kein Doppelzaehlen). Unter `mt5_lock`, **5 min gecacht** (die Abfrage
|
||||
ueber lange Zeitraeume braucht Sekunden und gehoert nicht in jeden Aufruf).
|
||||
⚠ **Fail-safe:** antwortet MT5 nicht, faellt es auf die alte Schaetzung zurueck —
|
||||
das Feld **`wht_quelle`** sagt, welche Zahl drinsteht („echt" / „geschaetzt").
|
||||
⚠ **KURZE ZEITRAEUME sind verzerrt, in BEIDE Richtungen:** die Erstattung kommt
|
||||
erst am Folgetag. Bei „today" oben ist `wht` sogar **negativ** (−68,93), weil die
|
||||
Erstattung von GESTERN heute einging — das Netto liegt damit ueber dem Brutto.
|
||||
Das ist korrekt gebucht, aber als Tageszahl irrefuehrend. Das Flag `wht_lag`
|
||||
faengt bisher nur die eine Richtung (einbehalten, nichts erstattet).
|
||||
⚠ Zeitfalle beachtet: Epochs mit Broker-Offset uebergeben, beim Lesen abziehen.
|
||||
|
||||
## ✅ STATISTIK-MODUL: ARITHMETIK KORREKT, NETTO-RECHNUNG WEITER DEFEKT (2026-08-12)
|
||||
|
||||
Gegen unabhängige SQL geprüft, alle drei Zeiträume: **Abweichung 0,00 in jeder
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,6 +20,7 @@ Aktuell (Schritt 1): nur Read-only-Endpoints.
|
||||
GET /api/snapshot → kompletter Live-Zustand (Markt/Position/Analyse/Agent)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import time
|
||||
import asyncio
|
||||
import contextlib
|
||||
import secrets
|
||||
@@ -151,20 +152,49 @@ def logs(lines: int = 200):
|
||||
return {"error": str(e), "lines": []}
|
||||
|
||||
|
||||
def _add_net(stats: dict, rate: float) -> dict:
|
||||
def _add_net(stats: dict, rate: float, trader=None) -> dict:
|
||||
"""Ergänzt eine stats_overview-Periode um Netto-Werte nach Quellensteuer.
|
||||
WHT wird nur auf die Gewinnseite (gross_win) erhoben, Verluste bleiben voll."""
|
||||
|
||||
⚠⚠ SEIT 2026-08-12 AUS DEN **ECHTEN** BUCHUNGEN, nicht mehr geschätzt.
|
||||
Die alte Rechnung war `net_pnl = total_pnl − gross_win × 26,375 %` und
|
||||
unterstellte, die WHT sei endgültig verloren. Sie wird aber täglich per
|
||||
`Tax settlement` erstattet — gemessen über 90 Tage zu **99 %**
|
||||
(einbehalten −2.625,89 €, erstattet +2.608,83 €, verblieben −17,06 €).
|
||||
Die Schätzung sagte −2.619,93 € und machte damit aus **+153 € rund
|
||||
−2.450 €**. ✅ Die Schätzung der EINBEHALTENEN Summe war fast exakt —
|
||||
falsch war allein die Annahme, sie bleibe weg.
|
||||
⚠ Fällt der Broker aus, wird auf die alte Schätzung zurückgefallen; das Feld
|
||||
`wht_quelle` sagt, welche Zahl gerade drinsteht („echt" / „geschätzt").
|
||||
⚠ KURZE ZEITRÄUME sind naturgemäss pessimistisch: die Erstattung kommt erst
|
||||
am Folgetag. Für „today" fehlt sie also meist — deshalb `wht_lag`.
|
||||
"""
|
||||
if not stats or stats.get("error"):
|
||||
return stats
|
||||
gw = stats.get("gross_win", 0.0) or 0.0
|
||||
gl = stats.get("gross_loss", 0.0) or 0.0
|
||||
wht = gw * rate
|
||||
net_gw = gw - wht
|
||||
tp = stats.get("total_pnl", 0.0) or 0.0
|
||||
echt = None
|
||||
if trader is not None:
|
||||
try:
|
||||
echt = trader.steuer_buchungen(int(stats.get("since") or 0),
|
||||
int(stats.get("until") or time.time()))
|
||||
except Exception:
|
||||
echt = None
|
||||
if echt and echt.get("n"):
|
||||
wht = -float(echt["netto"]) # netto ist negativ → Last positiv
|
||||
stats["wht_quelle"] = "echt"
|
||||
stats["wht_brutto"] = echt["wht"]
|
||||
stats["wht_erstattet"] = echt["tax"]
|
||||
stats["wht_lag"] = bool(echt["tax"] == 0 and echt["wht"] < 0)
|
||||
else:
|
||||
wht = gw * rate
|
||||
stats["wht_quelle"] = "geschätzt"
|
||||
stats["wht_pct"] = round(rate * 100, 3)
|
||||
stats["wht"] = wht
|
||||
stats["net_pnl"] = (stats.get("total_pnl", 0.0) or 0.0) - wht
|
||||
stats["net_profit_factor"] = (net_gw / gl) if gl > 0 else None
|
||||
stats["net_avg_win"] = (stats.get("avg_win", 0.0) or 0.0) * (1 - rate)
|
||||
stats["wht"] = round(wht, 2)
|
||||
stats["net_pnl"] = round(tp - wht, 2)
|
||||
stats["net_profit_factor"] = ((gw - wht) / gl) if gl > 0 else None
|
||||
stats["net_avg_win"] = (stats.get("avg_win", 0.0) or 0.0) * (
|
||||
1 - (wht / gw if gw > 0 else 0.0))
|
||||
return stats
|
||||
|
||||
|
||||
@@ -187,7 +217,8 @@ def stats():
|
||||
out: dict = {"wht_pct": round(rate * 100, 3)}
|
||||
for period in ("today", "week", "all"):
|
||||
try:
|
||||
out[period] = _add_net(eng.history.stats_overview(period), rate)
|
||||
out[period] = _add_net(eng.history.stats_overview(period), rate,
|
||||
getattr(eng, "trader", None))
|
||||
except Exception as e:
|
||||
out[period] = {"error": str(e)}
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user