"""`_pending_ziel`: das SQUEEZE-Verhalten darf sich beim Entfernen von SIG NICHT aendern. ANLASS (2026-08-12): der Auto-Signal-Pfad wird entfernt — gemessen negativ auf BEIDEN Ausfuehrungs-Achsen (Markt: alle 8 Felder; ruhende Order mit Touch-Fill: alle 6 Felder, sogar schlechter als der Markt-Einstieg). Grund strukturell: `_pend` WANDERT, und „ein Level ist nur dann ein Ausfuehrungs-Vorteil, wenn es STILLSTEHT". ⚠⚠ DAS RISIKO IST NICHT SIG, SONDERN DER GETEILTE MANAGER. Squeeze und Signal teilen sich `_pending_ziel` — bewusst, weil zwei getrennte Manager ueber `trader.pending_orders()` die Orders des jeweils anderen sehen und sich **gegenseitig stornieren** wuerden (der Bug waere sporadisch und im Log kaum erkennbar). Dieser Test nagelt den Squeeze-Zweig fest, BEVOR der Signal-Zweig faellt, und laeuft danach unveraendert weiter. """ from __future__ import annotations import MetaTrader5 as mt5 import pytest from core.engine import TradingEngine _SQ_K = 0.1 # `_SQ_K_ENG` — Ausbruchspuffer in ×ATR class _Wave: def __init__(self, snap): self._snap = snap def snapshot(self): return self._snap class _Stub: """Minimal-Engine — nur was `_pending_ziel` liest.""" _pending_ziel = TradingEngine._pending_ziel def __init__(self, *, sq_state="armed", hi=80.5, lo=80.0, atr=0.2, sq_pending=True, guard=None): self._squeeze_pending = sq_pending self._guard = guard self.wave = _Wave({ "pb_feats": {"atr": atr}, "squeeze": {"state": sq_state, "box_hi": hi, "box_lo": lo}, }) # SIG-Felder bleiben gesetzt, damit der Test VOR und NACH dem Entfernen # laeuft: existiert der Signal-Zweig nicht mehr, werden sie ignoriert. self._signal_pending = False self._auto_signal_min_conf = 75 def _auto_guard(self, quelle): return self._guard def test_armed_legt_beide_box_grenzen(): """Der Kern: solange der Squeeze `armed` ist, liegen BEIDE Stop-Orders — je einen Ausbruchspuffer ausserhalb der Box.""" ziel, quelle = _Stub(hi=80.5, lo=80.0, atr=0.2)._pending_ziel() assert quelle == "squeeze" assert ziel == {mt5.ORDER_TYPE_BUY_STOP: round(80.5 + _SQ_K * 0.2, 3), mt5.ORDER_TYPE_SELL_STOP: round(80.0 - _SQ_K * 0.2, 3)} def test_nicht_armed_legt_nichts(): for st in ("active", None): ziel, quelle = _Stub(sq_state=st)._pending_ziel() assert ziel == {} and quelle == "" def test_guard_storniert(): """Nacht-Fenster / News / Circuit-Breaker: der Guard ist die EINE Quelle fuer beide Wege — schlaegt er zu, darf nichts liegen.""" ziel, quelle = _Stub(guard="nacht")._pending_ziel() assert ziel == {} and quelle == "" def test_feature_aus_legt_nichts(): ziel, quelle = _Stub(sq_pending=False)._pending_ziel() assert ziel == {} and quelle == "" def test_ohne_atr_kein_absturz(): s = _Stub() s.wave = _Wave({"pb_feats": {"atr": 0.0}, "squeeze": {"state": "armed", "box_hi": 80.5, "box_lo": 80.0}}) ziel, quelle = s._pending_ziel() assert ziel == {} and quelle == "" def test_fehlende_box_legt_nichts(): for hi, lo in ((None, 80.0), (80.5, None), (None, None)): ziel, quelle = _Stub(hi=hi, lo=lo)._pending_ziel() assert ziel == {} and quelle == "" def test_preise_haengen_am_atr(): """Der Puffer skaliert mit dem ATR — sonst waere er in ruhigen Phasen zu weit und in schnellen zu eng.""" a, _ = _Stub(atr=0.1)._pending_ziel() b, _ = _Stub(atr=0.4)._pending_ziel() assert a[mt5.ORDER_TYPE_BUY_STOP] < b[mt5.ORDER_TYPE_BUY_STOP] assert a[mt5.ORDER_TYPE_SELL_STOP] > b[mt5.ORDER_TYPE_SELL_STOP] if __name__ == "__main__": raise SystemExit(pytest.main([__file__, "-v"]))