Warum BRK heute nicht ausgeloest hat: drei Ursachen, zwei behoben
User: "warum wurde beim letzten anstieg kein Break Out trade ausgeloest?"
Der Squeeze HAT gefeuert - mit der echten `_squeeze_one` nachgerechnet: 8 Bars
`active` heute, davon 6 LONG (00:55 / 03:00 / 07:40 / 08:35 / 11:50 / 13:00).
`squeeze_entry_count` steht trotzdem auf 0. Im Log stehen 8
`AUTO-SQUEEZE-ENTRY`-Zeilen - die Order scheiterte JEDES MAL.
URSACHE 1 - "Position bereits offen!" (1x, 08:30:34). Der Market-Fallback lief
noch ueber Slot 1, der vom manuellen Trade belegt war. Genau die Zwei-Slot-
Luecke; der `_slot()`-Fix kam um 08:32 und hat sie geschlossen.
URSACHE 2 - "Lot-Fehler" (140x, ab 13:02 JEDER Versuch). Das ist der Hauptgrund
und er ist eine Einstellung, kein Defekt: `margin_brk = 10 %`, und `calc_lots`
nimmt einen Prozentsatz der FREIEN Margin, nicht des Kontos. Die manuelle
Position bindet 707 von 776 EUR -> 10 % von ~100 EUR frei = ~10 EUR, ein
Mindestlot braucht 7,47 EUR, und die freie Margin schwankt mit dem P&L. Damit
lag BRK den ganzen Tag auf der Kippe. NICHT eigenmaechtig geaendert - das Feld
ist eine User-Entscheidung.
URSACHE 3 - "Kurs hat das Level bereits passiert" (129x). Die BUY_STOP-Seite
war nicht platzierbar, weil das Box-Dach dem Kurs ENTGEGENKAM (rollierendes
Fenster) - die wandernde Marke aus backtest_squeeze_frozen.py, hier live.
BEHOBEN (1): LOG-DROSSEL GRIFF NIE. Der Merker lag in EINER Variable, die
beiden Seiten scheitern aber im selben Tick mit VERSCHIEDENEN Fehlern
("Level passiert" oben, "Lot-Fehler" unten) - der Wert wechselte bei jedem
Aufruf. Real: 269 Zeilen an einem Tag, 2 je Sekunde. Jetzt je Order-Seite
gemerkt. Eine Drossel, die nie drosselt, ist schlimmer als keine: sie verdeckt
im Log genau die Meldung, die zaehlt.
BEHOBEN (2): "Lot-Fehler" erklaert sich jetzt selbst (`mt5_utils.lot_grund`).
Statt zwei Woertern steht dort
"Einsatz 2 % von 101.90 EUR freier Margin = 2.04 EUR, Mindestlot 0.01
braucht aber 7.47 EUR"
Beide Aufrufer (Market `_send` und Pending `place_stop`) nutzen ihn. Live
gegengeprueft an drei Einsatz-Werten.
Pipeline gruen (check_nfalle, Stufe E, 97 Tests), deployt und verifiziert:
seit dem Neustart 0 Zeilen "nicht platzierbar".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a35ea38d31
commit
b448a7ade8
+14
-5
@@ -471,7 +471,7 @@ class TradingEngine:
|
|||||||
# sich die adoptierte Position nachtraeglich dem Bot zuordnen.
|
# sich die adoptierte Position nachtraeglich dem Bot zuordnen.
|
||||||
self._pending_tickets: dict[int, str] = {}
|
self._pending_tickets: dict[int, str] = {}
|
||||||
self._pending_tagged: set = set()
|
self._pending_tagged: set = set()
|
||||||
self._squeeze_pending_err = None
|
self._squeeze_pending_err: dict = {} # je Order-Seite, s. unten
|
||||||
self._squeeze_chase_logged = None
|
self._squeeze_chase_logged = None
|
||||||
self._squeeze_reverse_sig = None # Dedup Reverse-Close (Gegen-Trade)
|
self._squeeze_reverse_sig = None # Dedup Reverse-Close (Gegen-Trade)
|
||||||
self._squeeze_entry_count = 0
|
self._squeeze_entry_count = 0
|
||||||
@@ -1774,10 +1774,19 @@ class TradingEngine:
|
|||||||
log.info(f"🎯 Pending-Entry ({quelle}) gesetzt: "
|
log.info(f"🎯 Pending-Entry ({quelle}) gesetzt: "
|
||||||
f"{'BUY_STOP' if otype == mt5.ORDER_TYPE_BUY_STOP else 'SELL_STOP'}"
|
f"{'BUY_STOP' if otype == mt5.ORDER_TYPE_BUY_STOP else 'SELL_STOP'}"
|
||||||
f" @ {preis:.3f} (T={t})")
|
f" @ {preis:.3f} (T={t})")
|
||||||
elif err and self._squeeze_pending_err != err:
|
elif err and self._squeeze_pending_err.get(otype) != err:
|
||||||
# ⚠ Nur bei ÄNDERUNG loggen: „Kurs hat das Level bereits passiert"
|
# ⚠ Nur bei ÄNDERUNG loggen: „Kurs hat das Level bereits
|
||||||
# tritt im 1-s-Takt auf, sobald der Kurs am Level klebt.
|
# passiert" tritt im 1-s-Takt auf, sobald der Kurs am Level
|
||||||
self._squeeze_pending_err = err
|
# klebt.
|
||||||
|
# ⚠⚠ JE SEITE gemerkt (Fix 2026-08-20). Vorher lag der
|
||||||
|
# Merker in EINER Variable — und weil die beiden Seiten
|
||||||
|
# im selben Tick mit VERSCHIEDENEN Fehlern scheitern
|
||||||
|
# ("Level passiert" oben, "Lot-Fehler" unten), wechselte
|
||||||
|
# der Wert bei jedem Aufruf und die Sperre griff NIE.
|
||||||
|
# Real am 20.08.: 269 Zeilen an einem Tag, 2 je Sekunde.
|
||||||
|
# Eine Drossel, die nie drosselt, ist schlimmer als keine:
|
||||||
|
# sie verdeckt im Log genau die Meldung, die zaehlt.
|
||||||
|
self._squeeze_pending_err[otype] = err
|
||||||
log.info(f"Pending-Entry ({quelle}) nicht platzierbar "
|
log.info(f"Pending-Entry ({quelle}) nicht platzierbar "
|
||||||
f"({preis:.3f}): {err}")
|
f"({preis:.3f}): {err}")
|
||||||
self._pending_levels = ziel
|
self._pending_levels = ziel
|
||||||
|
|||||||
@@ -117,6 +117,36 @@ def calc_lots(sym: str, price: float, otype: int,
|
|||||||
return min(lots, si.volume_max)
|
return min(lots, si.volume_max)
|
||||||
|
|
||||||
|
|
||||||
|
def lot_grund(sym: str, price: float, otype: int,
|
||||||
|
buffer_pct: float | None = None) -> str:
|
||||||
|
"""Warum liefert `calc_lots` 0? — als lesbarer Satz statt „Lot-Fehler".
|
||||||
|
|
||||||
|
⚠⚠ ANLASS (2026-08-20, real): der Breakout-Trader hat den ganzen Tag keinen
|
||||||
|
Trade eroeffnet und meldete nur „Lot-Fehler". Die Ursache war ein
|
||||||
|
Zusammenspiel, das man der Meldung NICHT ansah: das Feld „Einsatz je Pfad"
|
||||||
|
stand auf 10 %, und weil `calc_lots` einen Prozentsatz der FREIEN Margin
|
||||||
|
nimmt — nicht des Kontos —, blieben von 108 EUR frei nur 10,83 EUR uebrig,
|
||||||
|
waehrend ein Mindestlot 7,47 EUR braucht und die freie Margin schwankt.
|
||||||
|
Eine Fehlermeldung, die den Grund verschweigt, kostet hier Stunden.
|
||||||
|
"""
|
||||||
|
si, acc = mt5.symbol_info(sym), mt5.account_info()
|
||||||
|
if not si or not acc:
|
||||||
|
return "Lot-Fehler (kein Symbol/Konto)"
|
||||||
|
mpl = mt5.order_calc_margin(otype, sym, 1.0, price)
|
||||||
|
if not mpl or mpl <= 0:
|
||||||
|
return "Lot-Fehler (Margin je Lot nicht berechenbar)"
|
||||||
|
_man = get_manual_margin()
|
||||||
|
if _man > 0:
|
||||||
|
return (f"Einsatz {_man:.2f} EUR reicht nicht fuer das Mindestlot "
|
||||||
|
f"{si.volume_min} (braucht {si.volume_min * mpl:.2f} EUR)")
|
||||||
|
buf = (max(0.01, min(0.99, buffer_pct / 100.0))
|
||||||
|
if buffer_pct and buffer_pct > 0 else get_margin_buffer())
|
||||||
|
verf = acc.margin_free * buf
|
||||||
|
return (f"Einsatz {buf * 100:.0f} % von {acc.margin_free:.2f} EUR freier "
|
||||||
|
f"Margin = {verf:.2f} EUR, Mindestlot {si.volume_min} braucht aber "
|
||||||
|
f"{si.volume_min * mpl:.2f} EUR")
|
||||||
|
|
||||||
|
|
||||||
def calc_lots_risk(sym: str, price: float, otype: int,
|
def calc_lots_risk(sym: str, price: float, otype: int,
|
||||||
sl_distance: float | None, risk_frac: float) -> float:
|
sl_distance: float | None, risk_frac: float) -> float:
|
||||||
"""Risiko-basierte Lot-Größe: Verlust beim Initial-SL ≈ risk_frac × Equity.
|
"""Risiko-basierte Lot-Größe: Verlust beim Initial-SL ≈ risk_frac × Equity.
|
||||||
|
|||||||
+6
-4
@@ -19,8 +19,7 @@ from core.config import (
|
|||||||
)
|
)
|
||||||
from core.mt5_utils import (
|
from core.mt5_utils import (
|
||||||
mt5_lock, get_tick, get_filling, calc_lots, calc_lots_risk,
|
mt5_lock, get_tick, get_filling, calc_lots, calc_lots_risk,
|
||||||
pivot_low, pivot_high, atr_value,
|
pivot_low, pivot_high, atr_value, lot_grund,)
|
||||||
)
|
|
||||||
from core.logger import get_logger
|
from core.logger import get_logger
|
||||||
|
|
||||||
# ⚠ NUR für die Typ-Annotation `'HistoryLogger | None'` (String, wird zur Laufzeit
|
# ⚠ NUR für die Typ-Annotation `'HistoryLogger | None'` (String, wird zur Laufzeit
|
||||||
@@ -238,7 +237,8 @@ class TradeManager:
|
|||||||
else:
|
else:
|
||||||
lots = calc_lots(sym, price, otype, buffer_pct) # margin-basiert (risk_pct=0)
|
lots = calc_lots(sym, price, otype, buffer_pct) # margin-basiert (risk_pct=0)
|
||||||
if lots <= 0:
|
if lots <= 0:
|
||||||
self.last_error = "Lot-Fehler"; return None, 0.0
|
self.last_error = lot_grund(sym, price, otype, buffer_pct)
|
||||||
|
return None, 0.0
|
||||||
|
|
||||||
req = {
|
req = {
|
||||||
"action": mt5.TRADE_ACTION_DEAL, "symbol": sym, "volume": float(lots),
|
"action": mt5.TRADE_ACTION_DEAL, "symbol": sym, "volume": float(lots),
|
||||||
@@ -330,7 +330,9 @@ class TradeManager:
|
|||||||
mt5.ORDER_TYPE_BUY if ist_long else mt5.ORDER_TYPE_SELL,
|
mt5.ORDER_TYPE_BUY if ist_long else mt5.ORDER_TYPE_SELL,
|
||||||
buffer_pct)
|
buffer_pct)
|
||||||
if lots <= 0:
|
if lots <= 0:
|
||||||
return None, "Lot-Fehler"
|
return None, lot_grund(sym, trigger,
|
||||||
|
mt5.ORDER_TYPE_BUY if ist_long
|
||||||
|
else mt5.ORDER_TYPE_SELL, buffer_pct)
|
||||||
req = {"action": mt5.TRADE_ACTION_PENDING, "symbol": sym,
|
req = {"action": mt5.TRADE_ACTION_PENDING, "symbol": sym,
|
||||||
"volume": float(lots), "type": otype, "price": float(trigger),
|
"volume": float(lots), "type": otype, "price": float(trigger),
|
||||||
"magic": MAGIC, "comment": comment,
|
"magic": MAGIC, "comment": comment,
|
||||||
|
|||||||
Reference in New Issue
Block a user