Pending-Fill: Bot-Trades wurden nicht als solche gebucht
Auftrag "überprüfe die heutigen autotrades": die DB meldete für den 06.08. null Bot-Trades, das Log vier gefüllte SQZ-STOP-Orders. Beides stimmte. Ein Pending-Fill läuft nicht über engine._open — die Position wird von trader._refresh_locked per magic-match adoptiert, und dieser Pfad loggt nur Ticket/Symbol/Richtung/Lots/Preis. Es fehlten setup, sl_at_entry und alle 16 ctx_*-Spalten. Damit war genau die Messpipeline, die den Pending-Umbau vom 05.08. kontrollieren soll, blind für ihn (B4-Monitor, squeeze_b5, squeeze_entry_gap zählten null) — Deployment-Drift Fall 8, erste Variante, bei der nicht die Strategie driftet, sondern ihre Beobachtbarkeit. - history.tag_bot_trade(): UPDATE nur wenn setup IS NULL, stiller Fail-open - engine._pending_tickets: Ticket -> Quelle, beim Platzieren/Stornieren gepflegt - engine._check_pending_fill() im _pos_loop vor dem Pending-Manager; Zuordnung über Positions-Nr == Order-Nr (an allen 4 Fills verifiziert), setzt zusätzlich _bot_open_ticket/_source und die Entry-Zähler 6 Szenarien getestet, darunter die zwei gefährlichen: fremde Position wird nicht getaggt, derselbe Fill zählt bei Folge-Ticks nur einmal. Die 4 Trades des 06.08. nachgetragen (Backup .bak-2026-08-06). ctx_*/sl_at_entry bewusst NICHT nachgetragen: rekonstruiert wären sie später von gemessenen Werten nicht unterscheidbar. Dabei gefunden, dokumentiert, NICHT behoben: der Signal-Pfad setzt/storniert dieselbe Order 4x in 76 s bei identischem Level, weil block_reason zwischen entry_room und breakout_pending pendelt (Drift Fall 9). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
730219fce8
commit
3c1442109d
@@ -355,6 +355,11 @@ class TradingEngine:
|
||||
"signal_pending_entry", "true").lower() == "true"
|
||||
self._pending_levels = None
|
||||
self._pending_quelle = ""
|
||||
# Welche Pending-Order stammt von welchem Pfad? Bei einem FILL ist die
|
||||
# Positions-Nummer identisch mit der Order-Nummer (MT5) — nur so laesst
|
||||
# sich die adoptierte Position nachtraeglich dem Bot zuordnen.
|
||||
self._pending_tickets: dict[int, str] = {}
|
||||
self._pending_tagged: set = set()
|
||||
self._squeeze_pending_err = None
|
||||
self._squeeze_chase_logged = None
|
||||
self._signal_chase_logged = None
|
||||
@@ -707,6 +712,7 @@ class TradingEngine:
|
||||
# ZUERST die ruhenden Stop-Orders pflegen (setzen/stornieren),
|
||||
# DANN der Market-Fallback: so kann eine bereits liegende Order
|
||||
# den Ausbruch am Level abfangen, bevor nachgejagt würde.
|
||||
self._check_pending_fill() # gefuellte Pending-Order zuordnen
|
||||
self._manage_squeeze_pending()
|
||||
self._check_auto_squeeze() # autonomer Entry auf Squeeze-Ausbruch
|
||||
self._check_auto_signal() # autonomer Entry auf die Empfehlung
|
||||
@@ -1055,6 +1061,58 @@ class TradingEngine:
|
||||
"""Alias — `_check_auto_squeeze` ruft weiterhin diesen Namen."""
|
||||
return self._auto_guard("squeeze")
|
||||
|
||||
def _check_pending_fill(self):
|
||||
"""Hat eine von UNS platzierte Pending-Order gefuellt? Dann nachtragen.
|
||||
|
||||
⚠ WARUM: eine gefuellte Pending-Order laeuft NICHT durch `_open` — MT5
|
||||
oeffnet die Position selbst, und `trader._refresh_locked` adoptiert sie per
|
||||
magic-match und loggt sie OHNE Setup. Am 06.08. fuellten drei `SQZ-STOP`-
|
||||
Orders und landeten als `setup=NULL` in der DB, also ununterscheidbar von
|
||||
manuellen Trades. Damit zaehlten der B4-Monitor und die Reminder
|
||||
`squeeze_b5` / `squeeze_entry_gap` **null Bot-Trades**, obwohl der Bot
|
||||
gehandelt hatte — die Messpipeline war blind fuer genau den Umbau, den sie
|
||||
kontrollieren soll.
|
||||
Die Zuordnung ist eindeutig: bei einem Pending-Fill ist die POSITIONS-Nummer
|
||||
identisch mit der ORDER-Nummer (in den Broker-Daten vom 06.08. verifiziert).
|
||||
"""
|
||||
try:
|
||||
if not self._pending_tickets:
|
||||
return
|
||||
ps = self.trader.snapshot()
|
||||
tk = ps.get("ticket")
|
||||
if not tk or tk in self._pending_tagged:
|
||||
return
|
||||
quelle = self._pending_tickets.get(int(tk))
|
||||
if not quelle:
|
||||
return # fremde/manuelle Position
|
||||
d = "LONG" if ps.get("order_type") == 0 else "SHORT"
|
||||
setup = f"SQUEEZE_{d}" if quelle == "squeeze" else f"AUTOSIG_{d}"
|
||||
self._pending_tagged.add(tk)
|
||||
self._pending_tickets.pop(int(tk), None)
|
||||
ok = self.history.tag_bot_trade(int(tk), setup) if self.history else False
|
||||
# Dieselbe Buchfuehrung wie beim Market-Pfad, damit B4/B5 zaehlen
|
||||
self._bot_open_ticket = tk
|
||||
self._bot_open_source = "auto_squeeze" if quelle == "squeeze" else "auto_signal"
|
||||
if quelle == "squeeze":
|
||||
self._squeeze_entry_count += 1
|
||||
else:
|
||||
self._signal_entry_count += 1
|
||||
log.info(f"🎯 PENDING-FILL ({quelle}): {d} @ {ps.get('entry_price')} "
|
||||
f"— Position {tk} als {setup} getaggt"
|
||||
+ ("" if ok else " ⚠ DB-Nachtrag fehlgeschlagen"))
|
||||
tg = self.cfg["telegram"]
|
||||
if tg.get("enabled", "false").lower() == "true" and tg.get("bot_token"):
|
||||
try:
|
||||
send_telegram(f"🎯 <b>Pending-Fill ({quelle})</b>\n"
|
||||
f"{self.data.symbol or ''} {d} @ "
|
||||
f"{ps.get('entry_price')} — ruhende Stop-Order "
|
||||
f"am Level ausgeloest.",
|
||||
tg["bot_token"], tg["chat_id"])
|
||||
except Exception as e:
|
||||
log.warning(f"Pending-Fill-Telegram: {e}")
|
||||
except Exception as e:
|
||||
log.error(f"_check_pending_fill: {e}", exc_info=True)
|
||||
|
||||
def _pending_ziel(self) -> tuple[dict, str]:
|
||||
"""Welche Stop-Orders SOLLEN liegen? → ({order_type: preis}, Quelle).
|
||||
|
||||
@@ -1125,6 +1183,7 @@ class TradingEngine:
|
||||
for o in offen:
|
||||
err = self.trader.cancel_pending(o.ticket)
|
||||
if not err:
|
||||
self._pending_tickets.pop(int(o.ticket), None)
|
||||
log.info(f"⏸ Pending-Entry storniert (T={o.ticket}) — "
|
||||
+ ("Position offen" if ps.get("ticket")
|
||||
else "Setup nicht mehr gültig"))
|
||||
@@ -1145,6 +1204,7 @@ class TradingEngine:
|
||||
vorhanden[o.type] = o.ticket
|
||||
else:
|
||||
self.trader.cancel_pending(o.ticket)
|
||||
self._pending_tickets.pop(int(o.ticket), None)
|
||||
for otype, preis in ziel.items():
|
||||
if otype in vorhanden:
|
||||
continue
|
||||
@@ -1154,6 +1214,7 @@ class TradingEngine:
|
||||
# gleiche Regel wie beim Market-Pfad (s. `_open`)
|
||||
atr_tf=(mt5.TIMEFRAME_M5 if quelle == "squeeze" else None))
|
||||
if t:
|
||||
self._pending_tickets[int(t)] = quelle
|
||||
log.info(f"🎯 Pending-Entry ({quelle}) gesetzt: "
|
||||
f"{'BUY_STOP' if otype == mt5.ORDER_TYPE_BUY_STOP else 'SELL_STOP'}"
|
||||
f" @ {preis:.3f} (T={t})")
|
||||
|
||||
Reference in New Issue
Block a user