Diesmal systematisch statt punktuell: alle Methoden von TradingEngine per AST
klassifiziert, welche Slot-Objekte sie beruehren. 36 Funktionen fassen einen
Slot an, 20 davon NUR Slot 1.
⚠⚠⚠ LUECKE 1 (gravierend): EINE FRISCH GEFUELLTE BRK-POSITION HATTE KEIN
TRAILING. trail_brk.toggle() wurde an genau ZWEI Stellen gerufen - _rebind_brk
(nach Neustart) und toggle_trail (der Knopf). Der frische Pending-Fill kam in
keiner davon vor, und das ist der haeufigste Weg, auf dem eine BRK-Position
entsteht (7 von 8 Trades seit dem Pending-Umbau). Sie lief damit nur mit dem
Broker-SL - exakt die Variante, die backtest_brk_slonly.py verworfen hat (alle
zehn KI enthalten die Null, Trefferquote 40 -> 21 %).
Von aussen unsichtbar: die Position sieht in jeder Anzeige normal aus, ihr
fehlt nur der Schutz. Ironie: _rebind_brk loggt "Trailing wiederhergestellt" -
wiederhergestellt wurde etwas, das fuer einen frischen Fill nie an war.
⚠⚠ LUECKE 2: der Market-Fallback schaltete das FALSCHE Trailing ein. _open rief
pauschal self.trail.toggle(). Zwei Schaeden auf einmal: die BRK-Position bleibt
ungeschuetzt UND auf der manuellen Position wird das Trailing wieder
eingeschaltet, auch wenn der Nutzer es dort bewusst abgeschaltet hatte (nach
einer SL-Handeingabe). Sein handgesetzter Stop waere weitergezogen worden.
Noch nicht eingetreten, weil alle acht Fallback-Versuche des 20.08. schon an
der Order scheiterten.
⚠⚠ LUECKE 3: ein geschlossener BRK-Trade loeste KEINE Telegram-Meldung aus.
_close_notify_pending wird in _check_auto_close armiert, das self.trader liest.
Wiegt schwer, weil seit dem 19.08. NUR NOCH "Trade geschlossen" durch den
Telegram-Filter kommt - der autonome Pfad haette vollstaendig still gehandelt.
Behoben in _check_brk_close_cooldown, das den BRK-Flat-Uebergang ohnehin kennt.
Dazu die Reichweiten-Warnung korrigiert: sie sagte "Broker-SL, Trailing und
Circuit-Breaker greifen weiter" - das Trailing tat es eben NICHT.
ABGESICHERT auf zwei Ebenen, beide mutationsgeprueft:
tests/test_pending_fill.py +2: der Fill MUSS trail_brk einschalten und DARF
das Trailing von Slot 1 nicht anfassen. Mutation -> Test faellt.
Pipeline-Stufe E, Regel E: jeder Pfad, der eine BRK-Position eroeffnet
(_check_pending_fill, _open), MUSS trail_brk erwaehnen. Mutation -> Exit 1.
Die Test-Fixture bekam dafuer erstmals BEIDE Trailing-Attrappen - ohne die war
der gefaehrlichste Slot-Fehler ueberhaupt nicht pruefbar.
GEPRUEFT UND SAUBER: Circuit-Breaker, close(), _check_open_notify, _price_loop,
_write_levels_file, snapshot, die Fill-Bindung, die _pos_loop-Reihenfolge.
BEWUSST NICHT GEAENDERT (Slot-1-only, aber alle Features AUS): _check_auto_close,
_check_sr_close, _check_adverse15, _check_flip_close, _check_auto_m15, set_sltp,
_check_close_alert. _check_slot_reichweite meldet es, sobald eines eingeschaltet
wird. Zweislot-faehig waere ein Umbau der Notfall-Zustandsmaschine - offen.
⚠⚠ ZWEI CONFIG-WERTE SIND AUF 0 ZURUECKGEDRIFTET (nur berichtet):
close_notify_min_eur dokumentiert 50 -> ist 0 (Telegram ganz still)
auto_emergency_margin_pct dokumentiert 8 -> ist 0
Bei close_notify_min_eur = 0 nuetzt auch Luecke 3 nichts - die Vormerkung haengt
an _close_notify_min > 0. Der Bot meldet derzeit gar keinen Trade-Abschluss.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
136 lines
6.1 KiB
Python
136 lines
6.1 KiB
Python
"""`engine._check_pending_fill` — ordnet eine gefüllte Stop-Order dem Bot zu.
|
|
|
|
HINTERGRUND (2026-08-06): eine ruhende Stop-Order füllt IM BROKER, nicht über
|
|
`engine._open`. `trader._refresh_locked` adoptiert die Position dann per
|
|
magic-match und loggt sie OHNE `setup` — vier reale Squeeze-Trades waren dadurch
|
|
von manuellen nicht unterscheidbar, und B4-Monitor, `squeeze_b5` sowie
|
|
`squeeze_entry_gap` zählten **null**. Deployment-Drift Fall 8: nicht die Strategie
|
|
driftete, sondern ihre Beobachtbarkeit.
|
|
|
|
Die Zuordnung ist eindeutig, weil bei einem Pending-Fill die POSITIONS-Nummer
|
|
identisch mit der ORDER-Nummer ist (an allen vier Broker-Datensätzen verifiziert).
|
|
"""
|
|
|
|
|
|
def test_squeeze_fill_wird_getaggt(engine):
|
|
engine._pending_tickets = {49483981: "squeeze"}
|
|
engine.trader_brk.snap = {"ticket": 49483981, "order_type": 1,
|
|
"entry_price": 75.127}
|
|
engine.trader_brk.ticket = engine.trader_brk.snap.get("ticket")
|
|
engine._check_pending_fill()
|
|
|
|
assert engine._squeeze_entry_count == 1
|
|
assert engine._bot_open_ticket == 49483981
|
|
assert engine._bot_open_source == "auto_squeeze"
|
|
assert 49483981 not in engine._pending_tickets # Merker verbraucht
|
|
|
|
|
|
def test_unbekannte_quelle_wird_NICHT_getaggt(engine):
|
|
"""⚠ Ersetzt `test_signal_fill_long` (der Auto-Signal-Pfad ist am
|
|
2026-08-12 entfernt). Der Test wurde NICHT geloescht, sondern umgedreht:
|
|
seit es nur noch den Squeeze gibt, darf ein Fill mit einer FREMDEN Quelle
|
|
nicht stillschweigend als Squeeze durchgehen — das waere genau
|
|
Deployment-Drift Fall 8 (vier reale Trades landeten als `setup=NULL` und
|
|
waren von manuellen nicht unterscheidbar), nur mit umgekehrtem Vorzeichen:
|
|
ein Fremd-Trade wuerde die B4-/B5-Monitore verfaelschen."""
|
|
engine._pending_tickets = {777: "signal"} # Altbestand / unbekannt
|
|
engine.trader_brk.snap = {"ticket": 777, "order_type": 0, "entry_price": 75.2}
|
|
engine.trader_brk.ticket = engine.trader_brk.snap.get("ticket")
|
|
engine._check_pending_fill()
|
|
|
|
assert engine._squeeze_entry_count == 0
|
|
assert engine._bot_open_source is None
|
|
# ⚠ Der Merker bleibt STEHEN — nur ein sauber zugeordneter Fill verbraucht
|
|
# ihn. So wird derselbe Fall beim naechsten Tick erneut gemeldet, statt
|
|
# still zu verschwinden.
|
|
assert 777 in engine._pending_tickets
|
|
|
|
|
|
def test_fremde_position_wird_nicht_getaggt(engine):
|
|
"""Der gefährliche Fall: ein manueller Trade darf NICHT als Bot-Trade gelten —
|
|
sonst verfälscht er genau die Monitore, die der Fix reparieren soll."""
|
|
engine._pending_tickets = {111: "squeeze"}
|
|
engine.trader_brk.snap = {"ticket": 999, "order_type": 0, "entry_price": 75.0}
|
|
engine.trader_brk.ticket = engine.trader_brk.snap.get("ticket")
|
|
engine._check_pending_fill()
|
|
|
|
assert engine._squeeze_entry_count == 0
|
|
assert engine._bot_open_ticket is None
|
|
assert engine._pending_tickets == {111: "squeeze"} # bleibt liegen
|
|
|
|
|
|
def test_derselbe_fill_zaehlt_nur_einmal(engine):
|
|
"""Der `_pos_loop` läuft im Sekundentakt — ohne Dedup zählte ein Trade
|
|
hundertfach und der B4-Monitor wäre wertlos."""
|
|
engine._pending_tickets = {222: "squeeze"}
|
|
engine.trader_brk.snap = {"ticket": 222, "order_type": 1, "entry_price": 74.9}
|
|
for _ in range(5):
|
|
engine.trader_brk.ticket = engine.trader_brk.snap.get("ticket")
|
|
engine._check_pending_fill()
|
|
|
|
assert engine._squeeze_entry_count == 1
|
|
|
|
|
|
def test_flat_ist_ein_no_op(engine):
|
|
engine._pending_tickets = {333: "squeeze"}
|
|
engine.trader_brk.snap = {"ticket": None}
|
|
engine.trader_brk.ticket = engine.trader_brk.snap.get("ticket")
|
|
engine._check_pending_fill()
|
|
|
|
assert engine._squeeze_entry_count == 0
|
|
assert engine._pending_tickets == {333: "squeeze"}
|
|
|
|
|
|
def test_ohne_pending_merker_sofort_raus(engine):
|
|
"""Der häufigste Fall — es darf keine DB-Arbeit anfallen."""
|
|
engine.trader_brk.snap = {"ticket": 444, "order_type": 0}
|
|
engine.trader_brk.ticket = engine.trader_brk.snap.get("ticket")
|
|
engine._check_pending_fill()
|
|
|
|
assert engine._bot_open_ticket is None
|
|
|
|
|
|
# ── Trailing am BRK-Slot (Fix 2026-08-20) ───────────────────────────────────
|
|
def test_fill_schaltet_das_BRK_TRAILING_ein(engine):
|
|
"""⚠⚠ DER GEFAEHRLICHSTE FUND DER SLOT-PRUEFUNG.
|
|
|
|
`trail_brk.toggle()` wurde bis zum 20.08. an genau ZWEI Stellen gerufen:
|
|
beim Rueckbinden nach einem Neustart und ueber den TRAIL-Knopf. Der
|
|
FRISCHE Pending-Fill kam in keiner davon vor - und das ist der haeufigste
|
|
Weg, auf dem eine BRK-Position entsteht (7 von 8 Trades seit dem
|
|
Pending-Umbau). Eine so gefuellte Position hatte damit nur den Broker-SL:
|
|
exakt die SL-only-Variante, die `backtest_brk_slonly.py` verworfen hat
|
|
(alle zehn KI enthalten die Null, Trefferquote faellt 40 -> 21 %).
|
|
|
|
Ohne diesen Test faellt ein Rueckbau nicht auf - die Position sieht in
|
|
jeder Anzeige normal aus, ihr fehlt nur der Schutz.
|
|
"""
|
|
engine._pending_tickets = {49483982: "squeeze"}
|
|
engine.trader_brk.snap = {"ticket": 49483982, "order_type": 0,
|
|
"entry_price": 75.5}
|
|
engine.trader_brk.ticket = 49483982
|
|
assert not engine.trail_brk.enabled, "Vorbedingung: Trailing ist AUS"
|
|
|
|
engine._check_pending_fill()
|
|
|
|
assert engine.trail_brk.enabled, (
|
|
"Der BRK-Slot laeuft nach einem Pending-Fill OHNE Trailing - "
|
|
"nur mit dem Broker-SL (gemessen durchgefallen)")
|
|
|
|
|
|
def test_fill_laesst_SLOT1_trailing_in_ruhe(engine):
|
|
"""Die Gegenprobe: der Fill darf das Trailing der MANUELLEN Position nicht
|
|
anfassen. Der Nutzer schaltet es dort bewusst ab (z. B. nach einer
|
|
SL-Handeingabe, die `deactivate()` ausloest) - ein BRK-Entry darf es ihm
|
|
nicht wieder einschalten und seinen Stop weiterziehen."""
|
|
engine.trail.enabled = False
|
|
engine._pending_tickets = {49483983: "squeeze"}
|
|
engine.trader_brk.snap = {"ticket": 49483983, "order_type": 0,
|
|
"entry_price": 75.6}
|
|
engine.trader_brk.ticket = 49483983
|
|
|
|
engine._check_pending_fill()
|
|
|
|
assert not engine.trail.enabled, (
|
|
"Ein BRK-Fill hat das Trailing der manuellen Position eingeschaltet")
|