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:
co-authored by
Claude Opus 5
parent
dca3c11bbd
commit
b18b594639
+76
-1
@@ -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`.
|
||||
|
||||
|
||||
@@ -407,6 +407,58 @@ class HistoryLogger:
|
||||
# alert „🔔 CLOSE-Signal") sowie bei Notfall-/Gewinn-Auto-Close. Die terse
|
||||
# „Sym +X"-Bestätigung bei jedem Close ist entfernt.
|
||||
|
||||
# ── TELEMETRIE-PULS ──────────────────────────────────────────────────
|
||||
# ⚠⚠ ANLASS (2026-08-11): `rec_outcomes` stand seit dem Einbau am 06.08. bei
|
||||
# 0 Zeilen — ein falsches dict liess die Schreibbedingung nie wahr werden.
|
||||
# KEIN NameError (der Linter sieht nichts), und das umgebende `except`
|
||||
# loggt auf DEBUG. Fuenf Tage still tot; aufgefallen nur, weil die Tabelle
|
||||
# neben ihren Schwestern mit 0 Zeilen stand.
|
||||
# ⚠ Warum hier und nicht im Reminder-Skript: der Puls ist eine DB-Frage, und
|
||||
# BEIDE Nutzer (Reminder-Popup + Tagesreport) brauchen ihn. Zwei Kopien
|
||||
# waeren genau die Divergenz, die im Projekt dreimal zugeschlagen hat.
|
||||
# Richtungsregel: Skripte duerfen `core` importieren, nicht umgekehrt.
|
||||
PULS = (
|
||||
# (Tabelle, Zeitspalte, erwartete Kadenz in Minuten, wozu)
|
||||
("recommendations", "timestamp", 1, "Empfehlungs-Log"),
|
||||
("candles_m1", "time", 1, "M1-Kerzen-Sammlung"),
|
||||
("verdict_votes", "ts", 1, "Modul-Stimmen"),
|
||||
("m15_states", "ts", 60, "M15-Karte Selbstmessung"),
|
||||
("cone_checks", "ts", 60, "Kegel-Kalibrierung (ACI)"),
|
||||
("pbreak_predictions", "ts", 60, "P(break)-Prognose-Tracking"),
|
||||
("rec_outcomes", "ts", 180, "Selbst-Kalibrierung Empfehlung"),
|
||||
)
|
||||
|
||||
def telemetrie_puls(self) -> list[str]:
|
||||
"""Welche Telemetrie-Tabelle schreibt nicht mehr? → Klartext-Zeilen.
|
||||
|
||||
⚠ Toleranz bewusst grosszuegig (Faktor 20 der Kadenz): ein ruhiger Markt
|
||||
oder ein Wochenende darf keinen Fehlalarm ausloesen — ein Waechter, der
|
||||
oft warnt, wird ignoriert (Lehre vom 02.08.).
|
||||
"""
|
||||
out: list[str] = []
|
||||
try:
|
||||
jetzt = time.time()
|
||||
with self._lock, self._connect() as conn:
|
||||
for tab, sp, kadenz, wozu in self.PULS:
|
||||
try:
|
||||
n = conn.execute(f"SELECT COUNT(*) FROM {tab}").fetchone()[0]
|
||||
letzt = conn.execute(f"SELECT MAX({sp}) FROM {tab}").fetchone()[0]
|
||||
except Exception:
|
||||
continue # Tabelle gibt es (noch) nicht
|
||||
if not n:
|
||||
out.append(f"{tab}: 0 Zeilen — Logger hat NIE geschrieben ({wozu})")
|
||||
continue
|
||||
# ⚠ `candles_m1.time` ist ROHE Brokerzeit (UTC+3) — ohne den
|
||||
# Abzug meldet der Puls dort dauerhaft 3 h Rueckstand.
|
||||
ts = letzt - 10800 if tab == "candles_m1" else letzt
|
||||
alter_min = (jetzt - ts) / 60.0
|
||||
if alter_min > kadenz * 20:
|
||||
out.append(f"{tab}: letzte Zeile vor {alter_min/60:.1f} h "
|
||||
f"(erwartet ~alle {kadenz} min) — {wozu}")
|
||||
except Exception:
|
||||
return out # fail-open wie der Rest der Schicht
|
||||
return out
|
||||
|
||||
def trade_by_ticket(self, ticket: int) -> dict | None:
|
||||
"""Einen Trade komplett lesen — für die Close-Benachrichtigung (2026-08-11).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user