Files
AH-Oil-Trader/tests/conftest.py
T
Axel HocksandClaude Opus 5 44191947ac Telegram bei gruener M15-Karte (m15_alert) + Tests verschmutzen nicht mehr das Log
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>
2026-08-14 08:37:53 +02:00

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