Squeeze-Einstieg ueber ruhende Stop-Orders + Meta-Labeling gemessen (verworfen)
1) STOP-ORDER STATT MARKET (gebaut, live). engine._manage_squeeze_pending haelt beide Box-Grenzen mit BUY_STOP/SELL_STOP bestueckt, solange der Squeeze armed ist; trader.place_stop/cancel_pending/pending_orders neu. Groesse und SL kommen aus denselben Funktionen wie die Market-Order, nur bezogen auf den Trigger. OCO-Ersatz: faellt eine Seite, wird die andere im selben Tick storniert. Der kritische Punkt bei einer ruhenden Order sind die Gates - sie waren bisher Laufzeit-Checks im Market-Pfad. Neu: engine._squeeze_guard() als EINE Quelle fuer beide Wege, jeden Tick neu bewertet; schlaegt ein Gate zu, werden die Orders storniert. Market bleibt Fallback mit Nachjagd-Bremse squeeze_max_chase_atr=0.20 (gemessen der noch positive Bereich). Behandelt: Broker-Mindestabstand, falsche Marktseite, Toleranz 0,02 xATR gegen Order-Churn, Log nur bei Fehleraenderung, Startup-Schonfrist. 10 Szenarien getestet, live am Snapshot verifiziert. 2) META-LABELING (backtest_metalabel.py) - VERWORFEN. Zweitmodell auf 12 kausalen Merkmalen, Fit/Test in BEIDEN Richtungen: AUC 0,508 und 0,479 = Muenzwurf, Kalibrierung im Top-Bucket 74 % vorhergesagt gegen 39 % real, und 8 von 12 Gewichten kippen das Vorzeichen. Die scheinbar besseren Schwellen sind reine Handelsvermeidung und reproduzieren gegenlaeufig nicht. 23. verworfener Signal-Eingriff - deckt sich mit dem Caveat der Quelle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3b00820c15
commit
5e0e8b00ca
@@ -194,6 +194,94 @@ class TradeManager:
|
||||
self.last_error = _retcode_msg(res)
|
||||
return None, 0.0
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# PENDING STOP-ORDERS (Squeeze-Einstieg am Level, 2026-08-05)
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# WARUM: `backtest_squeeze_entry.py` hat gemessen, dass der Squeeze seinen
|
||||
# gesamten Edge im EINSTIEGSPREIS hat. Die Market-Order auf dem 5-s-Poll füllt
|
||||
# live im Median 0,306×ATR hinter dem Level (Mittel 0,741, Max 4,3) — damit
|
||||
# dreht ØR von +0,456 auf −0,158. Eine ruhende Stop-Order deckelt den Preis
|
||||
# konstruktiv: selbst mit 0,20×ATR unterstellter Fill-Slippage bleibt ØR
|
||||
# +0,244/+0,426 in beiden Halbjahren.
|
||||
|
||||
def pending_orders(self, sym: str) -> list:
|
||||
"""Eigene (magic-gleiche) Pending-Orders des Symbols."""
|
||||
with mt5_lock(timeout=5) as got:
|
||||
if not got:
|
||||
return []
|
||||
try:
|
||||
orders = mt5.orders_get(symbol=sym) or ()
|
||||
except Exception:
|
||||
return []
|
||||
return [o for o in orders if getattr(o, "magic", 0) == MAGIC]
|
||||
|
||||
def place_stop(self, sym: str, otype: int, trigger: float,
|
||||
comment: str = "Widget-SQZ") -> tuple[int | None, str]:
|
||||
"""Pending BUY_STOP/SELL_STOP am Ausbruchs-Level. → (ticket, Fehlertext).
|
||||
|
||||
Größe und SL werden mit DERSELBEN Logik bestimmt wie bei der Market-Order
|
||||
(`calc_lots` / `_calc_sl_tp`), nur bezogen auf den Trigger-Preis statt auf
|
||||
den aktuellen Tick — sonst wäre der SL-Abstand ein anderer als validiert.
|
||||
⚠ Broker-Mindestabstand (`trade_stops_level`) wird geprüft: liegt der Kurs
|
||||
schon zu nah am Trigger, lehnt MT5 die Order mit einem kryptischen retcode
|
||||
ab. Dann meldet die Funktion das sauber, und der Aufrufer entscheidet."""
|
||||
with mt5_lock(timeout=15) as got:
|
||||
if not got:
|
||||
return None, "MT5 belegt"
|
||||
si = mt5.symbol_info(sym)
|
||||
tick = get_tick(sym)
|
||||
if not si or not tick:
|
||||
return None, "Kein Symbol/Tick"
|
||||
ist_long = otype == mt5.ORDER_TYPE_BUY_STOP
|
||||
trigger = round(float(trigger), si.digits)
|
||||
# Stop-Order muss auf der richtigen Seite des Marktes liegen …
|
||||
bezug = tick.ask if ist_long else tick.bid
|
||||
if (trigger <= bezug) if ist_long else (trigger >= bezug):
|
||||
return None, f"Kurs hat das Level bereits passiert ({bezug:.3f})"
|
||||
# … und den Broker-Mindestabstand einhalten
|
||||
mind = (getattr(si, "trade_stops_level", 0) or 0) * (si.point or 0.001)
|
||||
if mind and abs(trigger - bezug) < mind:
|
||||
return None, (f"unter Broker-Mindestabstand "
|
||||
f"({abs(trigger-bezug):.3f} < {mind:.3f})")
|
||||
sl, _tp, sl_src = self._calc_sl_tp(sym, mt5.ORDER_TYPE_BUY if ist_long
|
||||
else mt5.ORDER_TYPE_SELL, trigger)
|
||||
lots = calc_lots(sym, trigger,
|
||||
mt5.ORDER_TYPE_BUY if ist_long else mt5.ORDER_TYPE_SELL)
|
||||
if lots <= 0:
|
||||
return None, "Lot-Fehler"
|
||||
req = {"action": mt5.TRADE_ACTION_PENDING, "symbol": sym,
|
||||
"volume": float(lots), "type": otype, "price": float(trigger),
|
||||
"magic": MAGIC, "comment": comment,
|
||||
"type_time": mt5.ORDER_TIME_GTC,
|
||||
"type_filling": get_filling(sym)}
|
||||
if sl:
|
||||
req["sl"] = float(sl)
|
||||
res = mt5.order_send(req)
|
||||
for mode in (mt5.ORDER_FILLING_RETURN, mt5.ORDER_FILLING_IOC,
|
||||
mt5.ORDER_FILLING_FOK):
|
||||
if res and res.retcode != 10030:
|
||||
break
|
||||
req["type_filling"] = mode
|
||||
res = mt5.order_send(req)
|
||||
if res and res.retcode == mt5.TRADE_RETCODE_DONE:
|
||||
log_trade.info(f"PENDING {'BUY_STOP' if ist_long else 'SELL_STOP'} "
|
||||
f"{lots:.2f}L @ {trigger:.3f} T={res.order} "
|
||||
f"SL={sl:.3f} ({sl_src})")
|
||||
return res.order, ""
|
||||
return None, _retcode_msg(res)
|
||||
|
||||
def cancel_pending(self, ticket: int) -> str:
|
||||
"""Eine Pending-Order löschen. → "" bei Erfolg, sonst Fehlertext."""
|
||||
with mt5_lock(timeout=15) as got:
|
||||
if not got:
|
||||
return "MT5 belegt"
|
||||
res = mt5.order_send({"action": mt5.TRADE_ACTION_REMOVE,
|
||||
"order": int(ticket)})
|
||||
if res and res.retcode == mt5.TRADE_RETCODE_DONE:
|
||||
log_trade.info(f"PENDING storniert T={ticket}")
|
||||
return ""
|
||||
return _retcode_msg(res)
|
||||
|
||||
def open_long(self, sym):
|
||||
with self._lock:
|
||||
if self.ticket:
|
||||
|
||||
Reference in New Issue
Block a user