G/V-Trennung geprueft: Adoptions-Sperre riss bei JEDEM Neustart auf

User: "ueberpruefe ob wirklich die G/V und G/V BRK Anzeigen stimmen, besonders
bei gleichzeitig laufenden Trades. Die Anzeige darf jeweils nur den eigenen
Slot anzeigen."

FRONTEND SAUBER: #hdr-pnl liest nur d.position, #hdr-pnl-brk nur
d.position_brk - keine Summe, kein Fallback. Aber das ist die falsche Stelle
zum Suchen: stehen im Backend beide Felder auf derselben Position, kann keine
Anzeige das mehr richten. Geprueft wurde deshalb die AUSWAHL in
TradeManager._refresh_locked.

⚠⚠ FUND 1 - die Sperrliste kannte nur zwei von DREI Quellen eines BRK-Tickets:
  trader_brk.ticket     nur im Speicher
  _pending_tickets      nur im Speicher
  _brk_restore          FEHLTE - und nur das ueberlebt den Neustart
Nach einem Neustart sind die ersten beiden leer, und die Reihenfolge im
_pos_loop besiegelt es: trader.refresh() laeuft DREI Zeilen vor _rebind_brk().
Slot 1 greift die BRK-Position also, bevor sie zurueckgebunden werden kann.
_rebind_brk erkennt das zwar und bricht ab - damit ist der Schaden aber nur
festgestellt, nicht behoben: die BRK-Position haengt am falschen Slot, ihr G/V
steht links statt rechts, und "G/V BRK" zeigt "—". Danach sieht alles normal
aus, was es besonders schwer sichtbar macht.
Behoben ueber getattr(..., None), also unabhaengig davon, WANN das Attribut
gesetzt wird - genau die Reihenfolge-Falle, die in zwei Tagen zweimal
zugeschlagen hat.

 FUND 2 - zwei verschiedene Rechnungen nebeneinander. Bei geschlossenem
Markt zeigt Slot 1 seit dem 01.08. eine HL-Schaetzung (pnl_hl), der BRK-Slot
bekam die nie und zeigte den EINGEFRORENEN Broker-Wert, ohne dass man es der
Anzeige ansieht. Neu pnl_hl_brk, mit ≈ markiert. Geprueft statt angenommen,
dass _tick_size/_tick_value am BRK-Slot gesetzt sind.

 day_pl.open summiert korrekt BEIDE Slots - die Summe gehoert dorthin, die
Trennung in die beiden G/V-Felder.

BELEG - tests/test_slot_gv.py, 6 Tests auf dem ECHTEN TradeManager (Broker
gestubbt): zwei Positionen gleichzeitig je im eigenen Slot · Slot 1 adoptiert
die BRK-Position nicht · Pending-Fill wird nicht weggeschnappt · der BRK-Slot
adoptiert nie · plus die Regressionsprobe, die den alten Zustand festhaelt.
NEUE PIPELINE-STUFE E (tools/check_slots.py): die Sperrliste muss alle drei
Namen erwaehnen. Mutationsprobe: Merker entfernt -> Exit 1.
⚠⚠ Ehrlich zur Arbeitsteilung: der pytest belegt den MECHANISMUS, baut die
Lambda aber selbst nach und bleibt bei der Mutation gruen. Erst die statische
Stufe belegt, dass die ENGINE sie richtig verdrahtet. Keine der beiden allein
haette gereicht.

⚠⚠ NEBENBEFUND, korrigiert: mein Schreib-Helfer hat SECHS Dateien still von
LF auf CRLF gedreht (io.open(...,"w") uebersetzt auf Windows). Mein erster
Check mit `grep -c $'\r$'` meldete faelschlich 0 - erst die BYTE-Zaehlung zeigte
8959 CR in CLAUDE.md. Alle sechs auf LF zurueckgesetzt. core.autocrlf=true
haette es im Repo normalisiert, im Arbeitsbaum aber nicht.

Live verifiziert: Slot 1 T=50396824 (+10,34), BRK flat, day_pl.open 10,34 -
kein gemeinsames Ticket. Deploy mit --feld pnl_hl_brk, alle 5 Schritte gruen.
⚠ Noch nicht beobachtet: ein Neustart MIT offener BRK-Position - der Fall, den
der Fix adressiert.

v=201.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-20 13:07:16 +02:00
co-authored by Claude Opus 5
parent 3e275e7515
commit 0966caf1b8
6 changed files with 320 additions and 15 deletions
+35 -7
View File
@@ -279,9 +279,22 @@ class TradingEngine:
# ⚠ Der adoptierende Manager darf den BRK-Slot und dessen noch offene
# Pending-Tickets NICHT greifen — sonst verwalten beide dieselbe
# Position (Trailing zweimal, Notfall-Stop am falschen Trade).
# ⚠⚠ DER MERKER `_brk_restore` GEHOERT MIT IN DIE SPERRLISTE — sonst
# reisst sie bei JEDEM Neustart auf. Reihenfolge im `_pos_loop`:
# `trader.refresh()` laeuft, und erst DREI Zeilen spaeter
# `_rebind_brk()`. Nach einem Neustart ist `trader_brk.ticket` noch
# None und `_pending_tickets` leer (beide leben nur im Speicher) —
# Slot 1 greift die BRK-Position also, BEVOR sie zurueckgebunden
# werden kann. `_rebind_brk` erkennt das zwar und bricht ab, aber dann
# ist es zu spaet: die BRK-Position haengt an Slot 1, ihr G/V steht
# links statt rechts, und „G/V BRK" zeigt „—".
# Mit Regressionsprobe belegt: `tests/test_slot_gv.py`.
self.trader.fremde_tickets = lambda: (
{self.trader_brk.ticket} if self.trader_brk.ticket else set()
) | set(self._pending_tickets or {})
({self.trader_brk.ticket} if self.trader_brk.ticket else set())
| set(self._pending_tickets or {})
| ({int(self._brk_restore)} if getattr(self, "_brk_restore", None)
else set())
)
self.trail.set_notify(self._on_trail_event)
# (Dry-Run-Auto-Trader entfernt 2026-07-23 — war Verlierer im Backtest,
# UI-Toggle längst weg. `min_conf`-Anzeigekonstante bleibt, s. unten.)
@@ -4825,14 +4838,28 @@ class TradingEngine:
# drinstecken.
# ⚠ Bleibt eine SCHÄTZUNG: HL ist ein anderer Kontrakt (Basis herausgerechnet,
# aber nicht exakt), und der Spread am Montags-Open ist unbekannt.
_pnl_hl = None
_pnl_hl = _pnl_hl_brk = None
try:
if _mkt_closed and _hl_live and position and position.get("ticket"):
if _mkt_closed and _hl_live:
_mid = float(_hl_live["mid_mt5"])
_sp = float((market or {}).get("spread") or 0.0)
_v = self.trader.live_pnl(_mid - _sp / 2.0, _mid + _sp / 2.0)
if _v is not None:
_pnl_hl = round(_v, 2)
_b, _a = _mid - _sp / 2.0, _mid + _sp / 2.0
if position and position.get("ticket"):
_v = self.trader.live_pnl(_b, _a)
if _v is not None:
_pnl_hl = round(_v, 2)
# ⚠⚠ AUCH fuer den BRK-Slot (2026-08-20). Ohne das zeigte bei
# geschlossenem Markt links eine HL-SCHAETZUNG und rechts der
# EINGEFRORENE Broker-Wert — zwei verschiedene Rechnungen
# nebeneinander, ohne dass man es der Anzeige ansieht.
# `live_pnl` liest ausschliesslich den Zustand SEINER Instanz,
# ist also je Slot sauber; `_tick_size`/`_tick_value` sind am
# BRK-Slot gesetzt, weil `_refresh_locked` sie bei gesetztem
# Ticket jeden Tick nachzieht (geprueft, nicht angenommen).
if getattr(self.trader_brk, "ticket", None):
_vb = self.trader_brk.live_pnl(_b, _a)
if _vb is not None:
_pnl_hl_brk = round(_vb, 2)
except Exception:
pass
@@ -4995,6 +5022,7 @@ class TradingEngine:
"hl_live": _hl_live,
# G/V-Schaetzung auf HL-Basis, nur bei geschlossenem Markt (sonst None)
"pnl_hl": _pnl_hl,
"pnl_hl_brk": _pnl_hl_brk,
}
# ── Entry-Checkliste je Richtung (User-Vorgabe 2026-07-30: „blende einen
# Hinweis ein, wenn die Checkliste gegen meine Eröffnung ist"). Wird im