Ruhende Orders: aufraeumen statt liegenlassen (User-Vorgabe)
User: "was sind ruhende orders. Loesche die wenn der trade nicht eroeffnet werden kann." Anlass: MT5 zeichnete SL/TP-Linien, obwohl keine Position lief. Das waren die liegenden Stop-Orders des Breakout-Traders - MT5 stellt jede Pending-Order samt mitgeschicktem SL selbst dar. Der Bot exportiert NIE SL/TP ins Chart (0 Treffer im .mq5, keine SL-Zeile in der CSV). Kein Anzeigefehler. ZWEI ECHTE VERWAISUNGS-PFADE GEFUNDEN. _manage_squeeze_pending stieg VOR dem Aufraeum-Block aus, wenn squeeze_pending_entry=false oder kein Symbol da war. Die Orders sind ORDER_TIME_GTC - sie verfallen NICHT. Eine so verwaiste Order haengt beim Broker, wird von niemandem mehr bewertet und kann Stunden spaeter fuellen: ohne Dedup, ohne Guard-Pruefung und (bis heute) ohne Trailing. Neu engine._pendings_abraeumen(sym, grund) - beide Pfade raeumen jetzt ab. DIE EIGENTLICHE VORGABE: ein halbes Setup wird aufgeloest. Scheitert eine Seite mit Lot-Fehler (freie Margin reicht nicht fuers Mindestlot), wird auch die Gegenseite storniert - der gemessene Squeeze ist ein Ausbruch in BEIDE Richtungen; laege nur eine Seite, handelte der Bot ihn nur noch in eine. Ausdruecklich NICHT bei "Kurs hat das Level bereits passiert": das ist voruebergehend und betrifft nur die eine Seite. Der Lot-Fehler betrifft das KONTO und damit beide. Erkannt ueber die Konstante mt5_utils.LOT_ZU_KLEIN, nicht ueber ein deutsches Wort im Fliesstext - eine Erkennung, die an einer Formulierung haengt, hoert still auf zu funktionieren und faellt dabei auf die stille Seite. ⚠⚠ DIE LOG-DROSSEL WAR AM SELBEN TAG ZUM ZWEITEN MAL AUSGEHEBELT - durch meine eigene Verbesserung. Vormittags lag der Merker in EINER Variable fuer beide Seiten (behoben). Dann bekam die Meldung ueber lot_grund ihre Zahlen, und die freie Margin aendert sich mit jedem Tick: der Text war jedes Mal neu, der Vergleich traf immer zu, wieder 2 Zeilen pro Sekunde. Gedrosselt wird jetzt auf die URSACHE, nicht auf ihre Formulierung (_err_key: alle Zahlen -> #). Gemessen: 7 Zeilen in 90 s statt ~180, und die verbliebenen sind echte Wechsel zwischen den beiden Ursachen. Lehre: eine Drossel, die einen TEXT vergleicht, ist so stabil wie der Text. Live verifiziert: 0 liegende Orders, keine verwaisten Reste, Startup-Schonfrist raeumt korrekt ab, Orders kommen danach zurueck sobald die Margin reicht. Pipeline gruen (A-G), 97 Tests, deployt mit --feld squeeze_block. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3fb9c3fd2e
commit
fd2ca46dc0
+78
-5
@@ -13,6 +13,7 @@ das MT5-Terminal. Es sollte immer nur EINE Engine-Instanz gegen das Terminal
|
||||
laufen.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
@@ -30,6 +31,7 @@ from core.config import (
|
||||
# und die `[zones]`-Sektion in der ini erhalten — reaktivierbar, falls je gewollt.
|
||||
from core.logger import get_logger
|
||||
from core.history import HistoryLogger
|
||||
from core.mt5_utils import LOT_ZU_KLEIN
|
||||
from core.mt5data import MT5Data
|
||||
from core.trader import TradeManager
|
||||
from core.trailing import TrailingManager
|
||||
@@ -202,6 +204,18 @@ def send_telegram(text, *a, **kw):
|
||||
return _send_telegram_roh(text, *a, **kw)
|
||||
|
||||
|
||||
def _err_key(msg: str) -> str:
|
||||
"""Fehlermeldung auf ihre URSACHE reduzieren — Zahlen raus.
|
||||
|
||||
⚠ `lot_grund` nennt die freie Margin, und die aendert sich im Sekundentakt.
|
||||
Ohne diese Normalisierung ist jede Drossel, die den TEXT vergleicht,
|
||||
wirkungslos. Aus
|
||||
"[MINDESTLOT] Einsatz 8 % von 39.33 EUR freier Margin = 3.15 EUR, ..."
|
||||
wird "[MINDESTLOT] Einsatz # % von #", und das bleibt stabil.
|
||||
"""
|
||||
return re.sub(r"[0-9]+(?:[.,][0-9]+)?", "#", msg or "")[:60]
|
||||
|
||||
|
||||
class TradingEngine:
|
||||
"""Headless-Pendant zu OilWidget: hält die core-Objekte, fährt die
|
||||
Refresh-Loops als Threads und liefert einen aggregierten Snapshot."""
|
||||
@@ -1738,10 +1752,19 @@ class TradingEngine:
|
||||
füllt eine, existiert eine Position → `soll` wird False und die Gegenseite
|
||||
wird im selben Tick storniert. Das ersetzt ein echtes OCO, das MT5 nicht hat.
|
||||
"""
|
||||
if not self._squeeze_pending:
|
||||
return
|
||||
sym = self.data.symbol or ""
|
||||
if not sym:
|
||||
# ⚠⚠ AUFRAEUMEN GEHT VOR (User-Vorgabe 2026-08-20: "loesche die, wenn
|
||||
# der Trade nicht eroeffnet werden kann"). Die beiden frueheren
|
||||
# Fruehausstiege liessen liegende Orders ZURUECK:
|
||||
# · `squeeze_pending_entry=false` waehrend Orders liegen
|
||||
# · kein Symbol (MT5-Aussetzer)
|
||||
# Sie sind `ORDER_TIME_GTC` — sie verfallen NICHT. Eine so verwaiste
|
||||
# Order haengt beim Broker, wird von niemandem mehr bewertet und kann
|
||||
# Stunden spaeter fuellen: ohne Dedup, ohne Guard-Pruefung und (bis zum
|
||||
# Fix von heute) ohne Trailing. Deshalb wird jetzt in beiden Faellen
|
||||
# ABGERAEUMT statt nur zurueckgekehrt.
|
||||
if not self._squeeze_pending or not sym:
|
||||
self._pendings_abraeumen(sym, "Feature aus" if sym else "kein Symbol")
|
||||
return
|
||||
try:
|
||||
# ⚠ NUR der eigene Slot entscheidet — vorher stornierte JEDE
|
||||
@@ -1782,6 +1805,13 @@ class TradingEngine:
|
||||
else:
|
||||
self.trader.cancel_pending(o.ticket)
|
||||
self._pending_tickets.pop(int(o.ticket), None)
|
||||
# ⚠⚠ LOT-FEHLER = das Setup ist NICHT handelbar (User-Vorgabe
|
||||
# 2026-08-20). Er heisst: die freie Margin reicht nicht einmal
|
||||
# fuer das Mindestlot. Steht dann noch die GEGENSEITE aus einem
|
||||
# frueheren Tick, laege ein halbes Setup am Markt — der Bot
|
||||
# handelte den Ausbruch nur noch in EINE Richtung, waehrend die
|
||||
# Messung beide unterstellt. Das wird unten aufgeloest.
|
||||
lot_fehler = False
|
||||
for otype, preis in ziel.items():
|
||||
if otype in vorhanden:
|
||||
continue
|
||||
@@ -1800,7 +1830,15 @@ class TradingEngine:
|
||||
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})")
|
||||
elif err and self._squeeze_pending_err.get(otype) != err:
|
||||
# ⚠⚠ GEDROSSELT UEBER EINEN SCHLUESSEL, NICHT UEBER DEN TEXT
|
||||
# (zweiter Fix am selben Tag). Heute Vormittag lag der Merker
|
||||
# in EINER Variable fuer beide Seiten — behoben. Dann bekam
|
||||
# die Meldung mit `lot_grund` ihre Zahlen, und die freie Margin
|
||||
# aendert sich mit JEDEM Tick: der Text war jedes Mal neu, die
|
||||
# Drossel damit erneut wirkungslos (2 Zeilen/Sekunde).
|
||||
# Gedrosselt wird jetzt auf die URSACHE, nicht auf ihre
|
||||
# Formulierung: alles bis zur ersten Zahl.
|
||||
elif err and self._squeeze_pending_err.get(otype) != _err_key(err):
|
||||
# ⚠ Nur bei ÄNDERUNG loggen: „Kurs hat das Level bereits
|
||||
# passiert" tritt im 1-s-Takt auf, sobald der Kurs am Level
|
||||
# klebt.
|
||||
@@ -1812,14 +1850,49 @@ class TradingEngine:
|
||||
# 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
|
||||
self._squeeze_pending_err[otype] = _err_key(err)
|
||||
log.info(f"Pending-Entry ({quelle}) nicht platzierbar "
|
||||
f"({preis:.3f}): {err}")
|
||||
if not t and err and LOT_ZU_KLEIN in err:
|
||||
lot_fehler = True
|
||||
# ⚠ Nur beim LOT-Fehler abraeumen — NICHT bei "Kurs hat das Level
|
||||
# bereits passiert". Der zweite ist voruebergehend und betrifft
|
||||
# naturgemaess nur die eine Seite (der Kurs steht gerade an der
|
||||
# Box-Kante); die Gegenseite bleibt ein gueltiger Einstieg. Der
|
||||
# Lot-Fehler dagegen betrifft das KONTO und damit beide Seiten.
|
||||
if lot_fehler and vorhanden:
|
||||
self._pendings_abraeumen(sym, "Einsatz reicht nicht fuer das "
|
||||
"Mindestlot — halbes Setup aufgeloest")
|
||||
self._pending_levels = ziel
|
||||
self._pending_quelle = quelle
|
||||
except Exception as e:
|
||||
log.error(f"_manage_squeeze_pending: {e}", exc_info=True)
|
||||
|
||||
def _pendings_abraeumen(self, sym: str, grund: str) -> int:
|
||||
"""Alle EIGENEN liegenden Orders stornieren. Rueckgabe: wie viele.
|
||||
|
||||
⚠ Fail-safe und still, wenn nichts liegt — die Methode laeuft im
|
||||
1-s-Takt. Geloggt wird nur, wenn wirklich storniert wurde.
|
||||
"""
|
||||
try:
|
||||
if not sym:
|
||||
sym = self.data.symbol or ""
|
||||
if not sym:
|
||||
return 0
|
||||
weg = 0
|
||||
for o in (self.trader.pending_orders(sym) or ()):
|
||||
if not self.trader.cancel_pending(o.ticket):
|
||||
self._pending_tickets.pop(int(o.ticket), None)
|
||||
weg += 1
|
||||
if weg:
|
||||
self._pending_levels = None
|
||||
self._pending_quelle = ""
|
||||
log.info(f"⏸ {weg} liegende Order(s) storniert — {grund}")
|
||||
return weg
|
||||
except Exception as e:
|
||||
log.debug(f"_pendings_abraeumen: {e}")
|
||||
return 0
|
||||
|
||||
def _check_auto_squeeze(self):
|
||||
"""Autonomer Entry auf den gemessen-validierten Volatilitäts-Squeeze-Ausbruch
|
||||
(`wave.squeeze.state == 'active'`), NUR wenn flat. Feuert **1× je Ausbruch**
|
||||
|
||||
Reference in New Issue
Block a user