"""Der Systemzustand im Tagesreport — sagt er die Wahrheit, und faellt er nie aus? ANLASS (2026-08-11). Das Projekt hat sieben Waechter, und sie funktionieren. Was fehlte, war der letzte Meter: die Marker-Datei `cfg_auto_sr_close` traegt den Zeitstempel **06.08. 18:10** und wurde danach NIE wieder gemeldet — fuenf Tage lang stand ein Exit aus, der am 11.08. mit **+350,81 EUR ueber 43 Trades** gemessen wurde. Ursache ist ein Kategorienfehler: die Marker-Logik behandelt einen DAUERZUSTAND wie ein EINMAL-EREIGNIS. Deshalb die Trennung: Popup = „etwas Neues", Tagesreport = „so steht es gerade". ⚠ Die wichtigste Eigenschaft dieses Blocks ist, dass er NIE den Report sprengt. Ein Report, der wegen der Statuszeile gar nicht ankommt, waere schlimmer als gar keine Statuszeile — deshalb prueft der Test vor allem die Ausfallpfade. """ from __future__ import annotations import pytest from core.engine import TradingEngine from core.history import HistoryLogger class _Hist: def __init__(self, puls): self._puls = puls def telemetrie_puls(self): if isinstance(self._puls, Exception): raise self._puls return self._puls class _Stub: _system_status = TradingEngine._system_status def __init__(self, *, brk=False, src=True, flip=False, puls=None): self._auto_squeeze = brk self._auto_sr_close = src self._flip_close = flip self.history = _Hist(puls if puls is not None else []) def _text(z): return "\n".join(z) def test_automatik_zeile_nennt_alle_schalter(): """⚠ Seit dem 2026-08-21 sind es ZWEI. Entfernt wurden der Auto-Signal-Pfad (SIG, am 12.08.) und der S/R-Auto-Close (am 21.08., User-Vorgabe — er stand seit dem 19.08. ohnehin aus, weil das P(break)-Modell live INVERTIERT misst). Der Test zaehlt sie bewusst einzeln auf: verschwindet einer stillschweigend aus der Zeile, faellt es hier auf und nicht erst, wenn monatelang niemand merkt, dass ein Schalter aus ist. ⚠ Die beiden Negativ-Pruefungen sind der eigentliche Wert: ein entfernter Pfad, der wieder auftaucht, waere ein stiller Rueckbau.""" z = _text(_Stub(brk=True, src=True, flip=False)._system_status()) assert "BRK AN" in z and "Flip aus" in z assert "SIG" not in z, "der entfernte Pfad darf nicht wieder auftauchen" assert "S/R-Close" not in z, "der S/R-Auto-Close ist entfernt (21.08.)" def test_toter_logger_wird_gemeldet(): """Der reale Fund: rec_outcomes stand 5 Tage bei 0 Zeilen.""" z = _text(_Stub(puls=["rec_outcomes: 0 Zeilen — Logger hat NIE geschrieben"]) ._system_status()) assert "⚠ Puls:" in z and "rec_outcomes" in z def test_stiller_puls_wird_ausdruecklich_bestaetigt(): """⚠ Kein Befund muss SICHTBAR sein — eine fehlende Zeile liest sich sonst wie „nicht geprueft".""" z = _text(_Stub(puls=[])._system_status()) assert "alle Telemetrie-Tabellen schreiben" in z def test_puls_fehler_wird_gemeldet_nicht_verschluckt(): """⚠ Genau die Fehlerklasse, die rec_outcomes 5 Tage verbarg: ein stiller Fallback saehe aus wie „alles gut".""" z = _text(_Stub(puls=RuntimeError("DB weg"))._system_status()) assert "⚠ Puls: Prüfung fehlgeschlagen" in z and "DB weg" in z def test_ohne_history_kein_absturz(): s = _Stub() s.history = None z = _text(s._system_status()) assert "Automatik:" in z # der Rest laeuft weiter def test_liefert_immer_zeilen_und_wirft_nie(): """Die Kern-Invariante: der Tagesreport darf an dieser Zeile nicht scheitern.""" for kw in ({}, {"brk": True}, {"puls": RuntimeError("x")}): z = _Stub(**kw)._system_status() assert isinstance(z, list) and z, "muss immer mindestens eine Zeile liefern" assert all(isinstance(x, str) for x in z) def test_puls_tabellenliste_ist_vollstaendig(): """Jede Telemetrie-Tabelle, die das Projekt schreibt, muss ueberwacht sein — sonst entsteht genau die Luecke wieder, die rec_outcomes hatte.""" ueberwacht = {t for t, *_ in HistoryLogger.PULS} for t in ("recommendations", "candles_m1", "verdict_votes", "m15_states", "cone_checks", "pbreak_predictions", "rec_outcomes"): assert t in ueberwacht, f"{t} wird nicht ueberwacht" if __name__ == "__main__": raise SystemExit(pytest.main([__file__, "-v"]))