User-Entscheidung. Der Pfad war seit 01.08. aus und auf BEIDEN Ausfuehrungs-Achsen gemessen negativ: Markt-Einstieg in allen 8 Feldern, ruhende Order mit Touch-Fill -0,392/-0,338 in allen 6 und damit sogar schlechter als Markt. Die +0,35/+0,32 aus backtest_auto_signal_v3.py galten in Variante B (Fill beim Close-Bruch) und waren Look-ahead - Aufschlag 0,77-0,85 R. Ursache strukturell: das Bestaetigungs-Level _pend WANDERT. Nebenbei entfaellt der offene Churn-Defekt (4x setzen/stornieren in 76 s bei identischem Level, gefunden 06.08., nie behoben). Entfernt: _check_auto_signal, set_auto_signal, der Signal-Zweig in _pending_ziel, /api/autosignal, Button + Handler, vier Snapshot-Felder, die auto_signal*/signal_pending_entry-Leser. Geblieben: AUTOSIG_*-Trades in der DB, die Backtests, wave_rec.breakout (speist block_reason) und _confirm_breakout selbst. Sicherung: .removed_backup/auto_signal_2026-08-12.py + Git. Das Risiko war nicht SIG, sondern der GETEILTE Pending-Manager (zwei getrennte Manager wuerden sich gegenseitig stornieren). Deshalb wurde das Squeeze-Verhalten VOR dem Eingriff in tests/test_pending_ziel.py festgenagelt - 7 Tests, vorher gruen, laufen unveraendert weiter. Zwei Folgefehler fing die Pruefkette sofort: ruff F821 ein verwaistes _t, py_compile ein dangling if. Zwei Tests wurden mitgezogen statt geloescht - test_signal_fill_long ist jetzt umgedreht (unbekannte Quelle darf NICHT als Squeeze durchgehen). Live verifiziert: alle vier Snapshot-Felder weg, Endpunkt tot, auto_squeeze/squeeze_pending unveraendert, 67 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
109 lines
3.8 KiB
Python
109 lines
3.8 KiB
Python
"""`_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"]))
|