Auto-Flip-Close gebaut (User-Wunsch) - trotz erneut negativer Messung
User: "schliesse den trade automatisch beim close signal". Dreht die
Empfehlung gegen die offene Position und steht sie >=0,5xATR im Plus ->
close(reason="flip_close").
VORHER NEU GEMESSEN, weil das alte Urteil (backtest_flipclose.py,
16.07.) auf einem VIERTEN Exit-Modell fusste: Trail fest 1,5 (live jetzt
1,0), keine Lock-Phase, kein Time-Stop, kein Initial-TP, _MAXH 288 statt
200, Phasen ueber den Bar-Close statt ueber das High-Water.
backtest_flipclose2.py (80k M5, 2 Halbjahre, Echtkosten, Exit aus
core/exit_model.py) bestaetigt das alte Urteil - deutlicher:
Basis ohne Flip H1 SigmaR -537 · H2 -140
Flip ab 0,0xATR Delta H1 -126 · H2 -221
Flip ab 0,3xATR Delta H1 -77 · H2 -216
Flip ab 0,5xATR Delta H1 -92 · H2 -160 <- mildeste, Default
Flip auch im Minus Delta H1 -205 · H2 -364
JEDE Variante ist in BEIDEN Haelften schlechter. Trefferquote steigt
39 -> 45 %, Ertrag faellt = Gewinner-Kappen (die nachlaufende EMA dreht
oft mitten im Pullback).
Gebaut wurde die mildeste Variante (0,5xATR Mindestgewinn), abschaltbar
ueber [trading] auto_flip_close.
Fuer die Neumessung bekam exit_model.simulate() einen stop_when-Hook
(+ ret_bar), damit die Phasen-Mechanik nicht zum fuenften Mal kopiert
werden musste. Rueckwaertskompatibilitaet verifiziert: ohne Hook bitgenau
identisch (die zunaechst gemeldeten Abweichungen kamen allein aus
LIVE.mult 1,5 -> 1,0, gegengeprueft mit mult=1.5 -> identisch).
Mit 10 synthetischen Szenarien getestet: beide Richtungen, aus, unter
Schwelle, gleichgerichtet, WARTEN, im Minus, flat, Startup-Schonfrist,
Ticket-Dedup - alle korrekt.
B4/B5-Pflicht: Live-Ertrag gegen die Erwartung halten, bei Drift
auto_flip_close=false.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f1cc3775f9
commit
fea91fddb3
@@ -318,6 +318,17 @@ class TradingEngine:
|
||||
self._open_pos_dir = None # Richtung der aktuell offenen Position (LONG/SHORT)
|
||||
self._pos_close_ts = 0.0 # Zeit + Richtung des letzten Positions-Close (JEDE
|
||||
self._news_block_logged = None # Dedup für die News-Blackout-Logzeile
|
||||
# Auto-Flip-Close (User-Wunsch 2026-07-31) — ⚠ GEMESSEN NEGATIV in beiden
|
||||
# Hälften (`backtest_flipclose2.py`), s. `_check_flip_close`. Default-Schwelle
|
||||
# 0,5×ATR = die mildeste der gemessenen Varianten.
|
||||
_tf_cfg = self.cfg["trading"]
|
||||
self._flip_close = _tf_cfg.get("auto_flip_close", "false").lower() == "true"
|
||||
try:
|
||||
self._flip_close_min_r = float(_tf_cfg.get("auto_flip_close_min_r", "0.5"))
|
||||
except (TypeError, ValueError):
|
||||
self._flip_close_min_r = 0.5
|
||||
self._flip_close_done_ticket = None
|
||||
self._flip_close_count = 0
|
||||
self._pos_close_dir = None # Art) → Squeeze-Wiedereinstiegs-Sperre (gegen die
|
||||
# Open→Instant-Close-Kaskade bei zu engem Notfall-Stop)
|
||||
self._sr_close_min_gain_armed_ticket = None # Ticket, für das die %-Schwelle schon
|
||||
@@ -605,6 +616,7 @@ class TradingEngine:
|
||||
self._check_circuit_breaker() # Tagesverlust-Stopp (Überleben zuerst)
|
||||
self._check_auto_close()
|
||||
self._check_adverse15() # 15-Min-Regel (nur Bot-Trades)
|
||||
self._check_flip_close() # Empfehlung dreht → schließen
|
||||
self._check_sr_close()
|
||||
self._check_auto_squeeze() # autonomer Entry auf Squeeze-Ausbruch
|
||||
self._check_auto_signal() # autonomer Entry auf die Empfehlung
|
||||
@@ -1420,6 +1432,85 @@ class TradingEngine:
|
||||
"news_score": None,
|
||||
}
|
||||
|
||||
def _check_flip_close(self):
|
||||
"""Auto-Flip-Close: dreht die Empfehlung GEGEN die offene Position und steht
|
||||
sie mindestens `min_r`×ATR im Plus → schließen. (`[trading] auto_flip_close`)
|
||||
|
||||
⚠⚠ **GEMESSEN NEGATIV — auf ausdrücklichen User-Wunsch trotzdem gebaut**
|
||||
(2026-07-31 „schließe den Trade automatisch beim Close-Signal").
|
||||
`backtest_flipclose2.py` (80k M5, 2 Halbjahre, Echtkosten, Exit aus
|
||||
`core/exit_model.py`) — JEDE Variante ist in BEIDEN Hälften schlechter als
|
||||
gar kein Flip-Close:
|
||||
Basis (kein Flip) H1 ΣR −537 · H2 −140
|
||||
Flip ab 0,0×ATR Δ H1 −126 · H2 −221
|
||||
Flip ab 0,3×ATR Δ H1 −77 · H2 −216
|
||||
Flip ab 0,5×ATR Δ H1 −92 · H2 −160 ← die MILDESTE, deshalb Default
|
||||
Flip auch im Minus Δ H1 −205 · H2 −364
|
||||
Mechanik: die Trefferquote steigt (39 → 45 %), der Ertrag fällt —
|
||||
**Gewinner-Kappen**, weil die nachlaufende EMA oft mitten im Pullback dreht
|
||||
und der Trade weitergelaufen wäre.
|
||||
⚠ Das bestätigt das ältere Urteil (`backtest_flipclose.py`, 2026-07-16). Die
|
||||
Neumessung war trotzdem nötig: das alte Skript nutzte ein VIERTES Exit-Modell
|
||||
(Trail fest 1,5 · keine Lock-Phase · kein Time-Stop · kein Initial-TP ·
|
||||
`_MAXH` 288 · Phasen über den Bar-Close statt über das High-Water) — auf
|
||||
dieser Grundlage wäre das Urteil für den Live-Betrieb nicht bindend gewesen.
|
||||
**B4/B5-Pflicht:** Live-Ertrag gegen die Erwartung halten; driftet es weg →
|
||||
`auto_flip_close=false`. Der Alarm (Blinken/Ton/Telegram) bleibt unabhängig
|
||||
davon bestehen.
|
||||
"""
|
||||
if not self._flip_close or self._in_startup_grace():
|
||||
return
|
||||
try:
|
||||
ps = self.trader.snapshot()
|
||||
ticket = ps.get("ticket")
|
||||
if not ticket:
|
||||
self._flip_close_done_ticket = None
|
||||
return
|
||||
if ticket == self._flip_close_done_ticket:
|
||||
return # für dieses Ticket schon ausgelöst
|
||||
pnl = float(ps.get("pnl") or 0.0)
|
||||
if pnl <= 0:
|
||||
return # nie im Minus (gemessen die schlechteste Variante)
|
||||
sig = (self.wave.signal() or {}).get("signal")
|
||||
if sig not in ("LONG", "SHORT"):
|
||||
return
|
||||
is_long = ps.get("order_type") == mt5.ORDER_TYPE_BUY
|
||||
if (is_long and sig != "SHORT") or (not is_long and sig != "LONG"):
|
||||
return # kein Gegen-Signal
|
||||
# Mindestgewinn in ×ATR (M5, wie im Backtest)
|
||||
atr = float(((self.wave.snapshot() or {}).get("pb_feats") or {}).get("atr") or 0.0)
|
||||
entry = float(ps.get("entry_price") or 0.0)
|
||||
bid = float((self.data.snapshot() or {}).get("bid") or 0.0)
|
||||
if atr > 0 and entry and bid:
|
||||
d = 1 if is_long else -1
|
||||
prof_atr = (bid - entry) * d / max(atr, 0.06)
|
||||
if prof_atr < self._flip_close_min_r:
|
||||
return
|
||||
else:
|
||||
return # ohne ATR keine Schwelle → nicht schließen
|
||||
self._flip_close_done_ticket = ticket
|
||||
log.info(f"🔄 AUTO-FLIP-CLOSE: Empfehlung dreht auf {sig} — "
|
||||
f"{'LONG' if is_long else 'SHORT'} bei P&L {pnl:+.2f} € "
|
||||
f"({prof_atr:.2f}×ATR) geschlossen")
|
||||
err = self.trader.close(reason="flip_close")
|
||||
if err:
|
||||
log.error(f"Auto-Flip-Close fehlgeschlagen: {err}")
|
||||
self._flip_close_done_ticket = None # nächster Tick erneut
|
||||
return
|
||||
self._flip_close_count += 1
|
||||
tg = self.cfg["telegram"]
|
||||
if tg.get("enabled", "false").lower() == "true" and tg.get("bot_token"):
|
||||
try:
|
||||
send_telegram(
|
||||
f"🔄 <b>AUTO-FLIP-CLOSE</b>\n{self.data.symbol or ''} "
|
||||
f"{'LONG' if is_long else 'SHORT'} geschlossen — Empfehlung "
|
||||
f"dreht auf {sig}.\nGewinn {pnl:+.2f} € ({prof_atr:.2f}×ATR)",
|
||||
tg["bot_token"], tg["chat_id"])
|
||||
except Exception as e:
|
||||
log.warning(f"Flip-Close-Telegram: {e}")
|
||||
except Exception as e:
|
||||
log.error(f"_check_flip_close: {e}", exc_info=True)
|
||||
|
||||
def _check_close_alert(self, rec: dict):
|
||||
"""Telegram-CLOSE-Alarm NUR bei Signal-Flip gegen die Position (echter
|
||||
Wende-Grund). Max. 1×/5 min (`_CLOSE_ALERT_COOLDOWN_S`) — sonst spammt es.
|
||||
@@ -2871,6 +2962,11 @@ class TradingEngine:
|
||||
"signal_entry_count": self._signal_entry_count,
|
||||
"adverse15_atr": self._adverse15_atr,
|
||||
"adverse15_count": self._adverse15_count,
|
||||
# Auto-Flip-Close (gemessen negativ, auf User-Wunsch gebaut) — für den
|
||||
# B4-Monitor sichtbar machen, wie oft er zuschlägt.
|
||||
"flip_close": self._flip_close,
|
||||
"flip_close_min_r": self._flip_close_min_r,
|
||||
"flip_close_count": self._flip_close_count,
|
||||
"last_trades": last_trades,
|
||||
# Wirtschaftskalender: {blackout, next, enabled, n} — Anzeige + Grund,
|
||||
# warum der Bot gerade keine autonome Order eröffnet.
|
||||
|
||||
Reference in New Issue
Block a user