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:
co-authored by
Claude Opus 5
parent
e01ecde3a3
commit
c081bdc734
+18
-3
@@ -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:
|
||||
|
||||
+34
-2
@@ -69,6 +69,11 @@ _BROKER_OFF_DEFAULT_S = 3 * 3600
|
||||
_BROKER_TICK_FRESH_S = 900
|
||||
|
||||
|
||||
# ⚠ Pivot-Anker des Initial-SL — seit 2026-08-20 AUS (s. Begruendung in
|
||||
# `_calc_sl_tp`). True stellt das alte Verhalten wieder her.
|
||||
_SL_PIVOT_ANKER = False
|
||||
|
||||
|
||||
class TradeManager:
|
||||
def __init__(self, nur_ticket: bool = False):
|
||||
"""`nur_ticket=True` => STRIKT an das eigene Ticket gebunden.
|
||||
@@ -139,9 +144,33 @@ class TradeManager:
|
||||
max_dist = (INIT_SL_MAX_ATR * atr) if atr else None
|
||||
min_dist = (INIT_SL_MIN_ATR * atr) if atr else None
|
||||
capped = floored = False
|
||||
if otype == mt5.ORDER_TYPE_BUY:
|
||||
# ⚠⚠ KEIN PIVOT-ANKER MEHR (2026-08-20, User-Fund "immer am S/R
|
||||
# geschlossen"). Der SL wurde bis heute an den naechsten M15-Pivot
|
||||
# gelegt — also fuer einen LONG direkt unter die UNTERSTUETZUNG, fuer
|
||||
# einen SHORT ueber den WIDERSTAND — und erst danach auf 1,8–2,2×ATR
|
||||
# geklemmt. Ueber 69 geloggte Orders war der Pivot in 4 % massgeblich
|
||||
# (in 59 % kappte der Deckel, in 36 % weitete der Boden auf); in diesen
|
||||
# Faellen lag der Stop exakt auf dem Level, an dem der Markt ohnehin am
|
||||
# haeufigsten durchwickt.
|
||||
# GEMESSEN mit dem HEUTIGEN Exit (`backtest_sl_pivot.py`, 60k M5,
|
||||
# 2 Halbjahre, gleiche Einstiege, nur die SL-Methode variiert):
|
||||
# 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. ⚠ Beide KI enthalten die Null — es
|
||||
# ist KEIN belegter Ertragsgewinn. Der Punkt ist ein anderer: die
|
||||
# Verankerung nuetzt nichts und legt den Stop auf das Level.
|
||||
# ⚠ Das bestaetigt `backtest_sl_method.py` (gedeckelt sind alle
|
||||
# Methoden gleichwertig, der Band-Cap dominiert) auf dem heutigen
|
||||
# Exit — dort war es nur nie als NACHTEIL sichtbar.
|
||||
# ZURUECK: `_SL_PIVOT_ANKER = True`.
|
||||
if _SL_PIVOT_ANKER and otype == mt5.ORDER_TYPE_BUY:
|
||||
piv = pivot_low(sym, entry_price)
|
||||
sl = (piv - buf) if piv else round(entry_price * (1 - INIT_SL_FALLBACK), si.digits)
|
||||
elif otype == mt5.ORDER_TYPE_BUY:
|
||||
piv = None
|
||||
sl = entry_price - (INIT_SL_MIN_ATR + INIT_SL_MAX_ATR) / 2.0 * atr \
|
||||
if atr else round(entry_price * (1 - INIT_SL_FALLBACK), si.digits)
|
||||
if max_dist and entry_price - sl > max_dist:
|
||||
sl = entry_price - max_dist; capped = True
|
||||
if min_dist and entry_price - sl < min_dist:
|
||||
@@ -149,9 +178,12 @@ class TradeManager:
|
||||
sl = round(sl, si.digits)
|
||||
sl_dist = entry_price - sl
|
||||
tp = round(entry_price + INIT_TP_RR * sl_dist, si.digits) if sl_dist > 0 else 0.0
|
||||
else:
|
||||
elif _SL_PIVOT_ANKER:
|
||||
piv = pivot_high(sym, entry_price)
|
||||
sl = (piv + buf) if piv else round(entry_price * (1 + INIT_SL_FALLBACK), si.digits)
|
||||
else:
|
||||
piv = None
|
||||
sl = entry_price + (INIT_SL_MIN_ATR + INIT_SL_MAX_ATR) / 2.0 * atr if atr else round(entry_price * (1 + INIT_SL_FALLBACK), si.digits)
|
||||
if max_dist and sl - entry_price > max_dist:
|
||||
sl = entry_price + max_dist; capped = True
|
||||
if min_dist and sl - entry_price < min_dist:
|
||||
|
||||
Reference in New Issue
Block a user