SL/TP nicht mehr am S/R verankert + Squeeze-Market-Fallback auf den BRK-Slot

User: "SL/TP sollte nicht am S/R verankert werden sondern immer dynamisch
berechnet werden" - nach dem Fund, dass Trades wiederholt am Level schlossen.

(1) PIVOT-ANKER DES INITIAL-SL ENTFERNT (`_SL_PIVOT_ANKER = False`).
`_calc_sl_tp` legte den SL an den naechsten M15-Pivot (LONG: pivot_low − Puffer,
SHORT: pivot_high + Puffer) und klemmte ihn erst DANACH auf 1,8–2,2×ATR. Der
Stop lag damit fuer einen LONG direkt unter der UNTERSTUETZUNG. Ueber 69
geloggte Orders war der Pivot in 4 % massgeblich (59 % gekappt, 36 % geweitet) -
und in genau diesen Faellen stand der Stop auf dem Level, an dem der Markt
ohnehin am haeufigsten durchwickt.
⚠ Der TP erbte das INDIREKT: er ist `2,0 × SL-Distanz`, skalierte also mit dem
Pivot-Abstand. Beide sind jetzt rein ATR-dynamisch (Mitte des Bandes = 2,0×ATR).

VORHER GEMESSEN (`backtest_sl_pivot.py`, 60k M5, 2 Halbjahre, gleiche
Einstiege, NUR die SL-Methode variiert, kanonischer Exit, Echtkosten):
    PIVOT  H1 -0,054 / H2 -0,040   PF 0,93 / 0,95
    FIX    H1 -0,040 / H2 +0,004   PF 0,95 / 1,01
FIX ist in BEIDEN Haelften besser (+0,014 / +0,043 R), der Worst-Case wird nicht
groesser - die vorab fixierte Regel ist erfuellt.
⚠ EHRLICH: beide KI enthalten die Null, es ist KEIN belegter Ertragsgewinn. Der
Punkt ist, dass die Verankerung nichts nuetzt UND den Stop auf das Level legt.
Das bestaetigt `backtest_sl_method.py` ("gedeckelt sind alle Methoden
gleichwertig") auf dem heutigen Exit - dort war es nur nie als NACHTEIL sichtbar.
Zurueck: `_SL_PIVOT_ANKER = True`.

(2) LUECKE AUS DEM ZWEI-SLOT-UMBAU, vom Deploy-Schritt 5 aufgedeckt:
"Auto-Squeeze-Entry fehlgeschlagen: Position bereits offen!". `engine.open_long`
routete IMMER auf `self.trader` - der Market-Fallback des Squeeze lief also
weiter ueber Slot 1 und scheiterte, sobald ein manueller Trade lief. FLAT-Check
und Pending-Manager waren gestern umgehaengt, der BESTELLWEG nicht.
Neu `_slot(source)`: auto_squeeze -> trader_brk, alles andere -> trader. Auch
der Bot-Marker (`_bot_open_ticket`) holt das Ticket jetzt vom richtigen Slot.
⚠ Genau die Fehlerklasse von Deployment-Drift Fall 8 - ein Umbau, der den
Bestellweg aendert, muss ALLE Bestellwege mitnehmen.

91 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-20 08:32:21 +02:00
co-authored by Claude Opus 5
parent e01ecde3a3
commit c081bdc734
3 changed files with 218 additions and 5 deletions
+18 -3
View File
@@ -2775,11 +2775,25 @@ class TradingEngine:
# ══════════════════════════════════════════════════════════════════════
# TRADE-AKTIONEN (für die Trade-Endpoints in server.py — Schritt 3)
# ══════════════════════════════════════════════════════════════════════
def _slot(self, source: str):
"""Welcher Trader fuer welche Quelle? (Zwei-Slot-Umbau 2026-08-19)
LUECKE, real am 2026-08-20 aufgefallen: der Market-Fallback des
Squeeze lief weiter ueber `self.trader` und scheiterte an
"Position bereits offen!", sobald ein manueller Trade lief obwohl BRK
seit gestern einen EIGENEN Slot hat. Der FLAT-Check und der
Pending-Manager waren umgehaengt, der Bestellweg nicht.
Genau die Fehlerklasse, die im Projekt als Deployment-Drift Fall 8
steht: ein Umbau, der den Bestellweg aendert, muss ALLE Bestellwege
mitnehmen sonst laeuft ein Teil weiter auf dem alten Pfad.
"""
return self.trader_brk if source == "auto_squeeze" else self.trader
def open_long(self, source: str = "manuell") -> str | None:
return self._open(self.trader.open_long, "LONG", source)
return self._open(self._slot(source).open_long, "LONG", source)
def open_short(self, source: str = "manuell") -> str | None:
return self._open(self.trader.open_short, "SHORT", source)
return self._open(self._slot(source).open_short, "SHORT", source)
def _open(self, fn, want: str, source: str = "manuell") -> str | None:
sym = self.data.symbol
@@ -2829,7 +2843,8 @@ class TradingEngine:
# (manuelle Trades bleiben in der Diskretion des Users, User-Vorgabe).
if source in ("auto_squeeze", "auto_signal"):
try:
self._bot_open_ticket = self.trader.snapshot().get("ticket")
# ⚠ Ticket vom Slot holen, der die Order gesendet hat.
self._bot_open_ticket = self._slot(source).snapshot().get("ticket")
self._bot_open_source = source
self._adverse15_done_ticket = None
except Exception: