Files
AH-Oil-Trader/tests/test_pending_fill.py
T
Axel HocksandClaude Opus 5 9887e3d5da pytest-Suite: 13 Tests in ~1 s, mutations-geprueft
Im Projekt sind ueber Monate dutzende Szenario-Tests entstanden ("mit 10
Szenarien getestet", "mit 18 synthetischen Szenarien") — alle als
Wegwerf-Skripte, keiner lief je ein zweites Mal. Bei einem System, das man vor
jeder Aenderung neu starten und live verifizieren muss, ist das der teuerste
Teil der Schleife.

Erfasst sind die beiden juengsten Pfade:
  · _check_pending_fill (6 Faelle) — u.a. "fremde Position NICHT taggen" und
    "derselbe Fill zaehlt nur einmal"
  · Selbst-Kalibrierung (7 Faelle) — Episoden-Dedup, WARTEN re-armt, Auswertung
    in beide Richtungen inkl. Broker-Offset, fehlende Bar bleibt offen,
    Aggregation

Drei harte Regeln in tests/conftest.py: keine Live-DB (frische Temp-Datei je
Test), kein MT5 / kein laufender Server (gestubbt), kein Netz. Das Schema legen
die ECHTEN Konstruktoren an — HistoryLogger UND CandleLogger, denn candles_m1
gehoert nicht zum History-Schema. Ein handgebautes Test-Schema wuerde
irgendwann vom Produktivstand abweichen.

Warum Temp-DB statt Kopie der Live-DB: am 06.08. haben echte candles_m1-Zeilen
in einem vermeintlich leeren Fenster einen Test verfaelscht — zweimal sah es
nach einem Code-Fehler aus, es waren Testfehler.

MUTATIONS-GEPRUEFT, weil eine Suite die immer gruen ist wertlos waere: eine
absichtlich eingebaute "fremde Position wird doch getaggt"-Regression wird
gefangen. Nebenbefund: der Fill-Dedup ist DOPPELT gesichert (_pending_tagged
und der pop aus _pending_tickets) — nur eines zu entfernen faellt nicht auf,
beides zusammen bricht sofort zwei Tests. Gewollte Redundanz, kein
ungetesteter Zweig.

tests_rec_outcomes.py (Wegwerf-Fassung von gestern) und die verwaiste
test_pbreak.db entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 06:15:07 +02:00

75 lines
2.9 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.snap = {"ticket": 49483981, "order_type": 1,
"entry_price": 75.127}
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_signal_fill_long(engine):
engine._pending_tickets = {777: "signal"}
engine.trader.snap = {"ticket": 777, "order_type": 0, "entry_price": 75.2}
engine._check_pending_fill()
assert engine._signal_entry_count == 1
assert engine._squeeze_entry_count == 0
assert engine._bot_open_source == "auto_signal"
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.snap = {"ticket": 999, "order_type": 0, "entry_price": 75.0}
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.snap = {"ticket": 222, "order_type": 1, "entry_price": 74.9}
for _ in range(5):
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.snap = {"ticket": None}
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.snap = {"ticket": 444, "order_type": 0}
engine._check_pending_fill()
assert engine._bot_open_ticket is None