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
+77
View File
@@ -8814,6 +8814,83 @@ das der Nutzer gleichzeitig bedient, gehört die Logspur mitgelesen.**
nächsten Squeeze mit offener Position — Log `🧷 BRK-Slot uebernimmt
Pending-Fill` und `position_brk.trail == true`.
## ✅⚠⚠ G/V-TRENNUNG GEPRÜFT — die Sperre riss bei JEDEM Neustart auf (2026-08-20, v=201)
User: „überprüfe 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."
**Das Frontend ist sauber**: `#hdr-pnl` liest ausschließlich `d.position`,
`#hdr-pnl-brk` ausschließlich `d.position_brk`. Keine Summe, kein Fallback,
keine gemeinsame Variable. ⚠ Aber das ist auch die *falsche* Stelle zum Suchen:
stehen im Backend beide Felder auf derselben Position, kann keine Anzeige das
mehr richten. Geprüft wurde deshalb die **Auswahl** in
`TradeManager._refresh_locked`.
⚠⚠ **DER FUND — die Adoptions-Sperre war unvollständig, und sie riss bei JEDEM
Neustart auf.** Ein BRK-Ticket kann aus **drei** Quellen kommen; die Sperrliste
kannte nur zwei:
| Quelle | in `fremde_tickets`? | lebt |
|---|---|---|
| `trader_brk.ticket` | ✅ | nur im Speicher |
| `_pending_tickets` | ✅ | nur im Speicher |
| **`_brk_restore`** (aus `runtime_state.json`) | ❌ **fehlte** | überlebt den Neustart |
Nach einem Neustart sind die ersten beiden **leer**. Und die Reihenfolge im
`_pos_loop` besiegelt es: `trader.refresh()` läuft, und erst **drei Zeilen
später** `_rebind_brk()`. Slot 1 greift die BRK-Position also, **bevor** sie
zurückgebunden werden kann.
`_rebind_brk` erkennt das zwar und bricht ab („hängt bereits an Slot 1") —
aber damit ist der Schaden nur festgestellt, nicht behoben: die BRK-Position
hängt 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
— dieselbe Signatur wie der Fehler vom Vortag, der `_rebind_brk` überhaupt erst
nötig gemacht hat.
**Behoben**: `_brk_restore` steht jetzt mit in der Sperrliste. Der Zugriff
läuft über `getattr(..., None)`, ist also unabhängig davon, WANN das Attribut
gesetzt wird — genau die Reihenfolge-Falle, die in zwei Tagen zweimal
zugeschlagen hat.
✅✅ **ZWEITER FUND — zwei verschiedene Rechnungen nebeneinander.** Bei
geschlossenem Markt zeigt Slot 1 seit dem 01.08. eine **HL-Schätzung**
(`pnl_hl`), weil der Broker-P&L stillsteht. Der BRK-Slot bekam die nie — er
zeigte den **eingefrorenen** Broker-Wert, ohne dass man es der Anzeige ansieht.
Neu `pnl_hl_brk` (dieselbe `live_pnl`-Rechnung auf dem eigenen Slot, mit ≈
markiert). ⚠ Geprüft statt angenommen, dass `_tick_size`/`_tick_value` am
BRK-Slot überhaupt gesetzt sind — `_refresh_locked` zieht sie bei gesetztem
Ticket jeden Tick nach.
**`day_pl.open` summiert korrekt BEIDE Slots** — die Summe gehört dorthin,
die Trennung in die beiden G/V-Felder.
**BELEG — `tests/test_slot_gv.py`, 6 Tests auf dem ECHTEN `TradeManager`**
(Broker gestubbt, Sperrliste wortgleich zur Engine nachgebaut): zwei Positionen
gleichzeitig landen je im eigenen Slot · Slot 1 adoptiert die BRK-Position nicht
· ein Pending-Fill wird nicht vorher weggeschnappt · der BRK-Slot adoptiert
**nie** · und die **Regressionsprobe**, die den alten Zustand festhält: ohne den
Merker in der Sperrliste greift Slot 1 zu.
**Neue Pipeline-Stufe (E, `tools/check_slots.py`)**: die Sperrliste muss alle
**drei** Namen erwähnen. Mutationsprobe: Merker entfernt → Stufe E **Exit 1**.
⚠⚠ **Ehrlich zur Arbeitsteilung der beiden Prüfungen:** der pytest belegt den
**Mechanismus** (eine Sperrliste ohne das Ticket lässt die Adoption zu) — er
baut die Lambda selbst nach und bleibt bei der Mutation deshalb grün. Erst die
statische Stufe belegt, dass die **Engine** sie richtig verdrahtet. **Keine der
beiden allein hätte gereicht.**
⚠ Live nach dem Deploy verifiziert: Slot 1 `T=50396824` (+10,34), BRK flat,
`day_pl.open` 10,34 — kein gemeinsames Ticket. Der Neustart adoptierte das
Ticket korrekt in Slot 1 (BRK war flat, die Sperre blockt also nicht zu viel).
**Noch nicht beobachtet**: ein Neustart *mit* offener BRK-Position — der Fall,
den der Fix adressiert. Beweis wäre dann „BRK-Slot nach Neustart
zurueckgebunden" **ohne** vorangehendes „magic-match" auf dasselbe Ticket.
⚠⚠ **VIERTER Surrogat-Abbruch beim Schreiben dieses Abschnitts — und wieder
OHNE Schaden.** Ein Emoji als ZWEI getrennte Unicode-Escapes erzeugt lone
surrogates; das Öffnen im Schreibmodus kürzt die Datei aber schon VOR dem
Schreiben. Die Regel vom 19.08. (erst `.tmp`, Größe prüfen, dann `os.replace`)
hat gehalten: CLAUDE.md blieb unversehrt, es entstand nur eine leere `.tmp`.
✅ Konsequenz: dieser Abschnitt wurde als **reiner UTF-8-Text** geschrieben,
ohne jedes Escape — das ist der Weg, der die Falle gar nicht erst aufstellt.
## ⚠⚠⚠ P(break) IST LIVE **INVERTIERT** — und es steuert echtes Geld (2026-08-19)
Die fällige Messung ist entscheidbar geworden: **n=530 entkoppelt** gegen die
+33 -5
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)
_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
+159
View File
@@ -0,0 +1,159 @@
"""Zeigen G/V und G/V BRK wirklich NUR ihren eigenen Slot? (2026-08-20)
ANLASS (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."
Geprueft wird die STELLE, an der die Trennung entstehen muss - die Auswahl der
Position in `TradeManager._refresh_locked`. Das Frontend liest nur `d.position`
bzw. `d.position_brk`; wenn schon dort dieselbe Position steht, kann keine
Anzeige das noch richten.
DIE GEFAEHRLICHE STELLE ist die ADOPTION: der Haupt-Trader greift jede Position
auf dem Symbol, damit ein von Hand eroeffneter Trade den Schutz-Stack bekommt.
Genau das darf er beim BRK-Ticket NICHT - dafuer gibt es `fremde_tickets`.
Diese Sperrliste wird hier in EXAKT der Form nachgebaut, in der die Engine sie
setzt (core/engine.py, `self.trader.fremde_tickets = ...`).
"""
import types
import pytest
from core import trader as trader_mod
from core.trader import TradeManager
class _Pos:
def __init__(self, ticket, lots=1.0, entry=80.0, pnl=0.0, magic=0):
self.ticket, self.volume, self.price_open = ticket, lots, entry
self.profit, self.magic = pnl, magic
self.type, self.symbol, self.time = 0, "SpotCrude", 1_700_000_000
self.sl, self.tp = 79.0, 82.0
@pytest.fixture
def welt(monkeypatch):
"""Stub-Broker mit einer frei setzbaren Positionsliste."""
offen = {}
def positions_get(**kw):
if "ticket" in kw:
p = offen.get(int(kw["ticket"]))
return (p,) if p else ()
return tuple(offen.values())
fake = types.SimpleNamespace(
positions_get=positions_get,
order_calc_margin=lambda *a, **k: 700.0,
order_send=lambda *a, **k: None,
ORDER_TYPE_BUY=0, ORDER_TYPE_SELL=1,
TRADE_ACTION_SLTP=6, TRADE_RETCODE_DONE=10009,
symbol_info_tick=lambda s: None,
symbol_info=lambda s: None,
)
monkeypatch.setattr(trader_mod, "mt5", fake)
monkeypatch.setattr(trader_mod, "MAGIC", 4711, raising=False)
class _Lock:
def __enter__(self): return True
def __exit__(self, *a): return False
monkeypatch.setattr(trader_mod, "mt5_lock", lambda *a, **k: _Lock())
return offen
def _slots(pending=None, brk_restore=None):
"""Beide Manager wie in `TradingEngine.__init__` verdrahtet."""
haupt, brk = TradeManager(), TradeManager(nur_ticket=True)
haupt._broker_offset_s = lambda sym=None: 0
brk._broker_offset_s = lambda sym=None: 0
pend = dict(pending or {})
haupt.fremde_tickets = lambda: (
({brk.ticket} if brk.ticket else set()) | set(pend)
| ({brk_restore} if brk_restore else set())
)
return haupt, brk, pend
def _gv(haupt, brk):
"""Genau das, was `snapshot()` ans Frontend gibt."""
a, b = haupt.snapshot(), brk.snapshot()
return (a.get("ticket"), a.get("pnl")), (b.get("ticket"), b.get("pnl"))
# --------------------------------------------------------------------------
def test_zwei_positionen_jede_im_eigenen_slot(welt):
"""Der Normalfall, nach dem der User fragt: BEIDE Slots gleichzeitig."""
haupt, brk, _ = _slots()
welt[100] = _Pos(100, pnl=-12.50, magic=4711) # BRK, gefuellte Pending
brk.bind(100, "SpotCrude")
welt[200] = _Pos(200, pnl=+31.00, magic=4711) # manueller Trade
haupt.refresh("SpotCrude")
(t1, p1), (t2, p2) = _gv(haupt, brk)
assert t1 == 200 and p1 == pytest.approx(31.00), "Slot 1 hat die falsche Position"
assert t2 == 100 and p2 == pytest.approx(-12.50), "BRK hat die falsche Position"
assert t1 != t2, "BEIDE Anzeigen zeigen dasselbe Ticket"
def test_haupt_adoptiert_die_brk_position_NICHT(welt):
"""Nur BRK offen, Slot 1 flat -> Slot 1 muss LEER bleiben."""
haupt, brk, _ = _slots()
welt[100] = _Pos(100, pnl=-5.0, magic=4711)
brk.bind(100, "SpotCrude")
haupt.refresh("SpotCrude")
(t1, _), (t2, _) = _gv(haupt, brk)
assert t1 is None, "Slot 1 hat die BRK-Position adoptiert -> G/V zeigt sie doppelt"
assert t2 == 100
def test_pending_fill_wird_nicht_vorher_adoptiert(welt):
"""Der Fill liegt am Broker, bevor `_check_pending_fill` laeuft.
Die Positions-Nummer ist dann identisch mit der Order-Nummer - deshalb
haelt `_pending_tickets` die Luecke zu.
"""
haupt, brk, _ = _slots(pending={100: "squeeze"})
welt[100] = _Pos(100, pnl=+3.0, magic=4711) # gerade gefuellt
haupt.refresh("SpotCrude") # laeuft ZUERST im _pos_loop
assert haupt.snapshot().get("ticket") is None, (
"Slot 1 hat den BRK-Pending-Fill adoptiert, bevor der BRK-Slot ihn "
"uebernehmen konnte")
assert brk.bind(100, "SpotCrude")
assert brk.snapshot().get("ticket") == 100
def test_brk_slot_adoptiert_niemals(welt):
"""`nur_ticket=True` darf sich NIE etwas greifen - auch nicht bei magic."""
_, brk, _ = _slots()
welt[200] = _Pos(200, pnl=+9.0, magic=4711)
brk.refresh("SpotCrude")
assert brk.snapshot().get("ticket") is None
def test_neustart_ohne_merker_in_der_sperrliste_BRICHT(welt):
"""REGRESSIONSPROBE: so sah es aus, bevor `_brk_restore` mit sperrte.
Nach einem Neustart ist `trader_brk.ticket` None und `_pending_tickets`
leer. Steht der Merker NICHT in der Sperrliste, greift Slot 1 die
BRK-Position - und das passiert im `_pos_loop` schon eine Zeile VOR
`_rebind_brk()`.
"""
haupt, brk, _ = _slots(brk_restore=None) # <- alter Zustand
welt[100] = _Pos(100, pnl=-40.0, magic=4711)
haupt.refresh("SpotCrude")
assert haupt.snapshot().get("ticket") == 100, (
"Der Test bildet den alten Zustand nicht mehr ab")
def test_neustart_mit_merker_bleibt_getrennt(welt):
"""Mit dem Merker in der Sperrliste ueberlebt die Trennung den Neustart."""
haupt, brk, _ = _slots(brk_restore=100)
welt[100] = _Pos(100, pnl=-40.0, magic=4711)
haupt.refresh("SpotCrude")
assert haupt.snapshot().get("ticket") is None, (
"Slot 1 hat die BRK-Position trotz Merker adoptiert")
assert brk.bind(100, "SpotCrude") # _rebind_brk kommt danach
(t1, _), (t2, _) = _gv(haupt, brk)
assert t1 is None and t2 == 100
+35 -1
View File
@@ -125,12 +125,46 @@ def _close_beide(baum: ast.AST) -> list:
return []
def _sperrliste_vollstaendig(baum: ast.AST) -> list:
"""(D) Die Adoptions-Sperre muss ALLE DREI Quellen eines BRK-Tickets kennen.
⚠⚠ Sie hatte am 2026-08-20 eine Luecke, die bei JEDEM Neustart aufriss:
`trader_brk.ticket` und `_pending_tickets` leben nur im Speicher, nach einem
Neustart sind beide leer. Das Ticket steht dann allein im Merker
`_brk_restore` — und weil `trader.refresh()` im `_pos_loop` DREI Zeilen vor
`_rebind_brk()` laeuft, griff Slot 1 die BRK-Position, bevor sie
zurueckgebunden werden konnte. Folge: ihr G/V stand links statt rechts, und
„G/V BRK" zeigte „—". Belegt in `tests/test_slot_gv.py`.
"""
for kls in [n for n in ast.walk(baum) if isinstance(n, ast.ClassDef)]:
if kls.name != "TradingEngine":
continue
init = next((f for f in kls.body if isinstance(f, ast.FunctionDef)
and f.name == "__init__"), None)
if not init:
continue
for kn in ast.walk(init):
if (isinstance(kn, ast.Assign)
and any(isinstance(t, ast.Attribute) and t.attr == "fremde_tickets"
for t in kn.targets)):
q = ast.unparse(kn)
fehlt = [n for n in ("trader_brk", "_pending_tickets", "_brk_restore")
if n not in q]
if fehlt:
return ["fremde_tickets: Sperrliste erwaehnt {} NICHT — eine "
"BRK-Position kann von Slot 1 adoptiert werden "
"(G/V stuende dann im falschen Feld)"
.format(", ".join(fehlt))]
return []
return []
def pruefe() -> list:
p = WURZEL / "core" / "engine.py"
quelle = p.read_text(encoding="utf-8")
baum = ast.parse(quelle)
return (_init_reihenfolge(baum, quelle) + _slot_trennung(baum)
+ _close_beide(baum))
+ _close_beide(baum) + _sperrliste_vollstaendig(baum))
if __name__ == "__main__":
+11 -4
View File
@@ -1138,12 +1138,19 @@ function render(d) {
const bpEl2 = $("hdr-pnl-brk"), bpos = d.position_brk;
if (bpos && bpos.ticket) {
const whtB = d.wht_pct || 0;
const rawB = Number(bpos.pnl) || 0;
// ⚠⚠ Bei geschlossenem Markt DIESELBE Rechnung wie links: der
// Broker-P&L steht dann still. Ohne `pnl_hl_brk` zeigte links eine
// HL-Schätzung und rechts der eingefrorene Broker-Wert — zwei
// verschiedene Rechnungen nebeneinander, ohne sichtbaren Unterschied.
const useHlB = d.market_closed && d.pnl_hl_brk != null;
const rawB = useHlB ? d.pnl_hl_brk : (Number(bpos.pnl) || 0);
const netB = rawB > 0 ? rawB * (1 - whtB / 100) : rawB;
bpEl2.textContent = (netB >= 0 ? "+" : "") + cs + fmt(netB, 2);
bpEl2.textContent = (useHlB ? "≈" : "") + (netB >= 0 ? "+" : "") + cs + fmt(netB, 2);
bpEl2.className = netB > 0 ? "up" : netB < 0 ? "down" : "";
bpEl2.title = `Breakout-Autotrader, Ticket ${bpos.ticket} · ${bpos.lots} Lots @ `
+ `${bpos.entry} · Trailing ${bpos.trail ? "an" : "AUS"}`;
+ `${bpos.entry} · Trailing ${bpos.trail ? "an" : "AUS"}`
+ (useHlB ? `· Schätzung auf Basis des Hyperliquid-Kurses `
+ `(Broker-P&L ${fmt(bpos.pnl || 0, 2)} ${cs} eingefroren).` : "");
} else {
bpEl2.textContent = "—";
bpEl2.className = "";
@@ -2367,7 +2374,7 @@ async function closeSlot(slot, label, pnl) {
// und der Browser zeigte Altes. Statt das jedes Mal von Hand zu diagnostizieren,
// sagt die Seite es jetzt selbst.
// ⚠ Bewusst SICHTBAR statt nur Konsole: die Konsole sieht am Handy niemand.
const _JS_V = "200";
const _JS_V = "201";
{
const htmlV = document.body && document.body.dataset
? document.body.dataset.htmlV : null;
+3 -3
View File
@@ -12,9 +12,9 @@
⚠ LOKAL eingebunden, kein CDN: die PWA laeuft ueber WireGuard
und muss offline funktionieren. -->
<link rel="stylesheet" href="pico.min.css?v=1">
<link rel="stylesheet" href="style.css?v=200">
<link rel="stylesheet" href="style.css?v=201">
</head>
<body data-html-v="200">
<body data-html-v="201">
<!-- Modul-Menü (ganz oben) -->
<nav id="tabs">
<button class="tab active" data-view="dash">Dashboard</button>
@@ -421,6 +421,6 @@
</div>
<div id="toast" class="toast hidden"></div>
<script src="app.js?v=200"></script>
<script src="app.js?v=201"></script>
</body>
</html>