"""Gemeinsame Fixtures für die Szenario-Tests. ⚠⚠ DREI HARTE REGELN, damit diese Suite jederzeit gefahrlos läuft: 1. **Nie die Live-DB anfassen.** Jeder Test bekommt eine frische Temp-Datei; `HistoryLogger.__init__` ruft `_init_db()` und legt das komplette Schema an. Die 15,7-MB-Live-DB zu kopieren wäre nicht nur langsam, sondern auch unzuverlässig — echte Zeilen haben mir am 06.08. einen Test verfälscht (echte `candles_m1` im vermeintlich leeren Fenster). 2. **Kein MT5, kein laufender Server.** Alles, was den Broker berührt, wird gestubbt. Die Tests prüfen die LOGIK, nicht die Anbindung. 3. **Keine Netzwerkzugriffe.** Die Engine wird über `__new__` ohne `__init__` erzeugt: der echte Konstruktor baut MT5-Verbindung, Threads und Config auf. Für die geprüften Methoden reichen wenige Attribute — was fehlt, fällt im Test sofort als AttributeError auf und ist damit dokumentiert, statt still zu passieren. """ import sys import types import logging from pathlib import Path import pytest sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from core.candle_logger import CandleLogger # noqa: E402 from core.engine import TradingEngine # noqa: E402 from core.history import HistoryLogger # noqa: E402 @pytest.fixture(autouse=True, scope="session") def _kein_produktions_log(): """VIERTE Grundregel der Suite: Tests schreiben NICHT ins Produktions-Log. ⚠⚠ ANLASS (2026-08-14, real passiert): beim Bau des M15-Alerts standen nach den Testläufen **über 150** `📨 M15-Alert`-Zeilen in `oil_widget.log` — dazu `Close-Push #4711` mit dem synthetischen Ticket aus `test_close_buchung.py`. Ich habe daraufhin zuerst geglaubt, die Entprellung sei live defekt, und musste die Herkunft mühsam an der Level-Folge (82.00, 82.01, …) nachweisen. In einem Projekt, dessen Forensik am Log hängt (der 18-h-Ausfall des Copiloten, der stille `positions_get`-Fehlbuchung), sind erfundene Zeilen im Produktions-Log ein echtes Risiko — nicht nur Unordnung. `core/logger.py` haengt einen `RotatingFileHandler` an den Logger `oil`, sobald `core.engine` importiert wird. Hier wird er fuer die Dauer der Sitzung entfernt und danach zurueckgegeben — die Log-AUFRUFE laufen weiter (sie gehoeren zum getesteten Code), nur ihr Ziel entfaellt. """ lg = logging.getLogger("oil") weg = [h for h in lg.handlers if isinstance(h, logging.FileHandler)] for h in weg: lg.removeHandler(h) yield for h in weg: lg.addHandler(h) @pytest.fixture def db(tmp_path): """Frische, vollständig angelegte Test-Datenbank. ⚠ `candles_m1` gehört NICHT zum `HistoryLogger`-Schema, sondern wird vom `CandleLogger` angelegt (`core/candle_logger.py`). Beide werden hier über ihre ECHTEN Konstruktoren aufgerufen — ein von Hand nachgebautes Test-Schema würde irgendwann vom Produktivstand abweichen, und der Test prüfte dann etwas anderes als das, was live läuft. """ p = tmp_path / "test.db" HistoryLogger(p) # legt trades, recommendations, rec_outcomes, … CandleLogger(str(p)) # legt candles_m1 return p @pytest.fixture def history(db): return HistoryLogger(db) class FakeTrader: """Minimaler Trader-Ersatz: liefert genau den Snapshot, den der Test setzt.""" def __init__(self): self.snap: dict = {} self.offset = 3 * 3600 # ⚠ Der BRK-Slot ist strikt ticket-gebunden; `_check_pending_fill` # prueft `ticket is None`, um zu entscheiden, ob es binden muss. # Der Test setzt es direkt, damit kein MT5 gebraucht wird. self.ticket = None def snapshot(self): return self.snap def _broker_offset_s(self, sym=None): return self.offset @pytest.fixture def engine(history): """Nackte Engine ohne `__init__` — nur die Attribute, die geprüft werden.""" e = TradingEngine.__new__(TradingEngine) e.history = history e.trader = FakeTrader() # Zweiter, strikt ticket-gebundener Slot fuer BRK (Umbau 2026-08-19). e.trader_brk = FakeTrader() e.data = types.SimpleNamespace(symbol="XTIUSD") e.cfg = {"telegram": {"enabled": "false"}} # Zustand der geprüften Pfade e._pending_tickets = {} e._pending_tagged = set() e._bot_open_ticket = None e._bot_open_source = None e._squeeze_entry_count = 0 e._signal_entry_count = 0 e._rec_epi_sig = None e._rec_eval_ts = 0.0 return e @pytest.fixture def db_umleiten(db, monkeypatch): """Leitet das modulweite `HISTORY_DB_FILE` der Engine auf die Test-DB um. ⚠ Nötig, weil `_evaluate_rec_outcomes` eine EIGENE sqlite3-Verbindung öffnet (bewusst — die Auswertung läuft außerhalb des HistoryLogger-Locks). """ import core.engine as CE monkeypatch.setattr(CE, "HISTORY_DB_FILE", str(db)) return db