User: "wenn es eine Empfehlung gibt (gruen), schicke eine Telegram-Nachricht." engine._check_m15_alert, direkt hinter _log_m15_state -- dieselbe Quelle wie Anzeige und Selbstmessung, damit die drei nicht auseinanderlaufen koennen. DIE ENTPRELLUNG IST DER GANZE BAU. Roh springt die Karte 85x/Tag auf gruen (m15_states, 3 Tage nach der Hysterese vom 12.08.) -- das waere Spam. Alle Stufen an den echten Daten kalibriert, nicht geraten: nur Flanke 85,0/Tag + Level & Richtung als Schluessel 23,7/Tag + 30 min Mindestabstand 13,3/Tag + Fenster 8-20 Uhr (Default) 7,7/Tag (Vergleich: Close-Meldung ab 50 EUR 2,4-6,1/Tag) Nach 4 h darf dasselbe Level erneut melden, sonst verschweigt der Alert ein Setup, das den ganzen Tag wiederkommt. Der Text sagt ausdruecklich, dass es KEINE Prognose ist: die Karte automatisch zu handeln faellt gemessen durch (backtest_m15_auto.py: OeR -0,162/-0,094). Die Meldung ist ein Hinweis zum Hinsehen fuer den Menschen (+3,20 EUR/Lot ueber 146 Trades). Die P(break)-Formulierung ist wortgleich zu Karte und Chart (v1.35). 13 Tests, Schwerpunkt auf dem was NICHT gesendet werden darf. Mutationsprobe in vier Zweige, alle gefangen. DIE PROBE HAT EIN TESTLOCH AUFGEDECKT: das Entfernen der Flankenerkennung liess zunaechst ALLE Tests gruen, weil Cooldown und 4-h-Schluessel dasselbe abfangen. Zwei Schutzschichten, die sich maskieren -- dasselbe Muster wie beim Fill-Dedup. Geschlossen durch test_flankenerkennung_ISOLIERT. NEBENBEFUND, behoben: Tests schrieben ins Produktions-Log (ueber 150 M15-Alert-Zeilen + Close-Push #4711 aus test_close_buchung). Ich hielt die Entprellung deshalb zuerst fuer live defekt. conftest.py entfernt den FileHandler des oil-Loggers jetzt fuer die Sitzung -- vierte Grundregel der Suite. Verifiziert: Log-Groesse vor und nach einem Komplettlauf identisch. Live verifiziert: seit dem Serverstart genau 1 Alert trotz mehrfachem gruen/gelb-Wechsel. 85 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
124 lines
4.5 KiB
Python
124 lines
4.5 KiB
Python
"""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
|
|
|
|
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()
|
|
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
|