Systemzustand in den Tagesreport - der fehlende letzte Meter

Selbstaendige Empfehlung nach der Gesamtpruefung, vom User beauftragt.

Befund: die Erkennung funktioniert, die Zustellung nicht. Sieben
Waechter, jeder nach einem eigenen Vorfall gebaut, jeder funktioniert -
und trotzdem blieben diese Woche sechs Zustaende tagelang unbemerkt,
vier davon waren bereits erkannt.

Belegt an den Marker-Dateien: cfg_auto_sr_close traegt 06.08. 18:10,
cfg_auto_squeeze sogar 02.08. Der Config-Waechter meldet je Schluessel
genau einmal und schweigt danach fuer immer. auto_sr_close war damit
fuenf Tage aus - gemessen +350,81 EUR ueber 43 Trades. Ursache ist ein
Kategorienfehler: die Marker-Logik behandelt einen Dauerzustand wie ein
Einmal-Ereignis.

Gebaut: engine._system_status() als Block im Tagesreport (07:30,
Telegram + E-Mail) - der einzige Kanal, der nachweislich jeden Tag
ankommt. Saubere Trennung: Popup = "etwas Neues", Report = "so steht
es gerade". Reine Anzeige.

- telemetrie_puls() liegt in core/history.py, beide Nutzer rufen
  dieselbe Funktion (Skripte importieren core, nie umgekehrt)
- measurement_reminder wird lazy und gekapselt importiert; ein
  Fehlschlag wird sichtbar gemeldet statt still verschluckt
- tests/test_system_status.py: 7 Tests, Schwerpunkt Ausfallpfade -
  der Report darf an dieser Zeile nie scheitern

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-11 22:43:24 +02:00
co-authored by Claude Opus 5
parent dca3c11bbd
commit b18b594639
5 changed files with 297 additions and 38 deletions
+76 -1
View File
@@ -2287,14 +2287,29 @@ class TradingEngine:
else:
fc = f"nicht verfügbar ({dl.get('error') or ''})"
# ── Systemzustand (2026-08-11) ──────────────────────────────────────
# ⚠⚠ Der Tagesreport ist der einzige Kanal, der nachweislich JEDEN Tag
# ankommt (Telegram-Log lückenlos `ok`). Die Wächter kannten den
# Zustand längst — er erreichte nur niemanden. Details und Beleg in
# `_system_status()`.
try:
zust = self._system_status()
except Exception as e:
zust = [f"⚠ Systemzustand nicht ermittelbar ({e})"]
zust_txt = ("\n\n<b>⚙ Systemzustand:</b>\n" + "\n".join(zust)) if zust else ""
zust_html = ("<h3>⚙ Systemzustand</h3><p>"
+ "<br>".join(zust) + "</p>") if zust else ""
txt = (f"📊 <b>Oil Tagesreport</b>\n\n"
f"<b>Heute — Prognose (Web):</b>\n{fc}\n\n"
f"<b>Gestern ({yday}):</b>\n"
f"{n} Trades · WR {wr:.0f}% · PnL {pnl:+.2f} {cur} · PF {pf_s}")
f"{n} Trades · WR {wr:.0f}% · PnL {pnl:+.2f} {cur} · PF {pf_s}"
f"{zust_txt}")
html = (f"<h2>Oil Tagesreport</h2>"
f"<h3>Heute — Prognose (Web)</h3><p>{fc.replace(chr(10), '<br>')}</p>"
f"<h3>Gestern ({yday})</h3><p>{n} Trades · Trefferquote {wr:.0f} % · "
f"PnL <b>{pnl:+.2f} {cur}</b> · Profit-Faktor {pf_s}</p>"
f"{zust_html}"
f"<p style='color:#888;font-size:12px'>Prognose ist Dritt-Kontext "
f"(Web-Recherche), kein Handelssignal.</p>")
@@ -4334,6 +4349,66 @@ class TradingEngine:
except Exception:
return None
def _system_status(self) -> list[str]:
"""Klartext-Zeilen zum Systemzustand für den Tagesreport.
WARUM ES DAS GIBT (2026-08-11). Das Projekt hat sieben Wächter, und
sie funktionieren was fehlte, war der letzte Meter. Belegt an den
Marker-Dateien: `cfg_auto_sr_close` trägt den Zeitstempel **06.08. 18:10**
und wurde danach NIE wieder gemeldet. Fünf Tage lang stand ein Exit aus,
der am 11.08. mit **+350,81 über 43 Trades** gemessen wurde.
`cfg_auto_squeeze` trägt sogar 02.08. als der Schalter am 11.08. umgelegt
wurde, kam deshalb GAR KEINE Meldung.
Die Ursache ist ein Kategorienfehler: die Marker-Logik behandelt einen
**Dauerzustand** wie ein **Einmal-Ereignis**. Deshalb die saubere Trennung
**Popup = etwas Neues ist passiert", Tagesreport = „so steht das System
gerade"**. Der Report kommt nachweislich jeden Tag an (Telegram-Log
lückenlos `ok`), der Popup verstummt nach dem ersten Mal.
Reine Anzeige: nichts wird hier geschaltet oder entschieden.
"""
z: list[str] = []
try:
an = lambda b: "AN" if b else "aus" # noqa: E731
z.append(f"Automatik: BRK {an(self._auto_squeeze)} · "
f"SIG {an(self._auto_signal)} · "
f"S/R-Close {an(self._auto_sr_close)} · "
f"Flip {an(getattr(self, '_flip_close', False))}")
except Exception as e:
z.append(f"Automatik: nicht lesbar ({e})")
try:
puls = self.history.telemetrie_puls() if self.history else []
z.extend(f"⚠ Puls: {p}" for p in puls)
if not puls:
z.append("Puls: alle Telemetrie-Tabellen schreiben")
except Exception as e:
z.append(f"⚠ Puls: Prüfung fehlgeschlagen ({e})")
# ── Config-Drift + fällige Messungen aus dem Reminder ────────────────
# ⚠ LAZY und GEKAPSELT: `measurement_reminder` ist ein Auswertungs-Skript
# und gehört NICHT in den Live-Abhängigkeitsgraphen (derselbe Fehler wie
# der `backtest_breakout_squeeze`-Import). Der Import passiert deshalb
# erst hier, 1×/Tag, und ein Fehlschlag wird SICHTBAR gemeldet statt
# still verschluckt — ein fehlender Block sähe sonst aus wie „alles gut".
try:
import measurement_reminder as _mr
for d, cur, val in _mr.config_drift():
z.append(f"⚠ Config: {d['key']} steht auf '{cur}' "
f"(validiert: '{val}')")
faellig = []
for c in _mr.CHECKS:
try:
have = c["have"]()
if have >= c["need"]:
faellig.append(f"{c['key']} FÄLLIG")
elif have > 0:
faellig.append(f"{c['key']} {have:.0f}/{c['need']}")
except Exception:
continue
if faellig:
z.append("Messungen: " + " · ".join(faellig[:6]))
except Exception as e:
z.append(f"⚠ Config-/Messungs-Prüfung nicht verfügbar ({e})")
return z
def _check_close_notify(self):
"""Telegram bei jedem geschlossenen Trade ab `close_notify_min_eur`.