Review-Durchlauf: Telemetrie-Epochen, doppeltes Verdict, Doku-Korrektur

Erster vollstaendiger Durchlauf von docs/review-prompt.md. KEINE
Strategie-Aenderung - alles Telemetrie, Doku und toter Code.

1) analyze_divergence.py kannte keine Epochen und war damit selbst
   driftanfaellig. D0 mischte Vorhersagen des alten und des am 31.07.
   nachtrainierten P(break)-Modells und meldete dessen Fehlkalibrierung als
   aktuellen Alarm (das neue Modell hat n=0, Markt seit Fr zu). B las die
   Prae-Migrations-NULLs von block_reason als blinden Fleck. C druckte bei
   0 Zeilen ein "OK", obwohl es fehlende Daten waren. Neu: _EPOCHS + _clamp().

2) Konsens-Pfeil AR;K: der MQL5-Export rechnete im ~5-s-Takt ein komplettes
   zweites _verdict(), obwohl der Indikator die Zeile seit v1.33 per Default
   verwirft. Neu [trading] export_consensus_arrow (Default false).
   Verifiziert ueber die exportierte CSV: AR;K weg, AR;L und AR;S bleiben.

3) /api/autosqueeze loggt jetzt die Herkunft ([WEB] ...) wie /api/autosignal.
   Vorher war ein Zustandswechsel nicht als Nutzeraktion belegbar.

4) CLAUDE.md: die Reversal-Kennzahl "OR +0,185 / PF 1,35 / 70 %" stand
   unkorrigiert an der Fundstelle, die Widerlegung 2000 Zeilen weiter im
   Legacy-Recheck. Korrektur an die Fundstelle geholt.

5) core/notify.py: zwei tote "import datetime" entfernt (beide Funktionen
   nutzen _time), funktional nachgetestet.

Geprueft und sauber: 0 fehlende Frontend-IDs von 86, nur 2 Config-Schluessel
ohne Leser (beide dokumentiert dormant), Snapshot-Median 13 ms und alle
DB-Abfragen <13 ms -> keine Performance-Massnahme, 124 Datei- und 83
Funktionsreferenzen in CLAUDE.md stimmen.

Zwischenverdacht zurueckgezogen: "block_reason erklaert nur 33 % der WARTEN"
war ein Migrations-Artefakt; seit 01.08. 100 % Abdeckung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-02 23:29:58 +02:00
co-authored by Claude Opus 5
parent 3124fe5b4f
commit 20cd04caa7
5 changed files with 149 additions and 18 deletions
+59 -3
View File
@@ -38,6 +38,36 @@ LOG = "oil_widget.log"
_EXP_WAIT = 43.0 # backtest_dist.py: ~57 % LONG/SHORT, ~43 % WARTEN
_EXP_WAIT_TOL = 15.0 # ab dieser Abweichung wird geflaggt (Pp)
# ── EPOCHEN ───────────────────────────────────────────────────────────────────
# ⚠ Ergänzt 2026-08-02 (Review-Durchgang 2). Der Wächter rechnete über ein starres
# Zeitfenster und wusste NICHT, ab wann ein Modell bzw. eine Telemetrie gilt.
# Dadurch meldete er zwei FEHLALARME, die beide nur Artefakte des Fensters waren:
# · D0 mischte Vorhersagen des ALTEN und des am 31.07. nachtrainierten
# P(break)-Modells (Alarm „Δ +15 Pp" stammte zu 100 % aus dem alten; das neue
# hatte n=0, weil der Markt seit Fr 22:55 zu ist).
# · B zeigte „block_reason: None 66,8 %" — die Spalte existiert aber erst seit
# dem 31.07., davor ist NULL schlicht „nicht erhoben", kein blinder Fleck.
# Ein Wächter, der nach jedem Nachtraining wochenlang Alarm schlägt, wird
# ignoriert — und dann fängt er den echten Fall auch nicht mehr.
# Beim Ändern eines Modells/einer Telemetrie hier das Datum mitziehen.
_EPOCHS = {
# P(break) auf live-spiegelnder Stichprobe neu gefittet (neue _PB_MU/_SD/_W)
"pbreak_model": "2026-07-31",
# `recommendations.block_reason` eingeführt (DB-Migration)
"block_reason": "2026-07-31",
}
def _epoch_ts(name: str) -> float:
return datetime.fromisoformat(_EPOCHS[name]).timestamp()
def _clamp(t0: float, epoch: str) -> tuple[float, bool]:
"""→ (effektiver Startzeitpunkt, wurde beschnitten?). Sorgt dafür, dass eine
Auswertung nie über den Beginn ihrer eigenen Epoche hinausreicht."""
e = _epoch_ts(epoch)
return (max(t0, e), e > t0)
def _flag(ok: bool) -> str:
return "OK " if ok else ""
@@ -65,8 +95,16 @@ def sec_a(c, t0):
def sec_b(c, t0):
print("\n" + "=" * 84)
print(" B) WELCHES GATE BLOCKT? (block_reason, seit 2026-07-31)")
print(f" B) WELCHES GATE BLOCKT? (block_reason, seit {_EPOCHS['block_reason']})")
print("=" * 84)
# ⚠ Fenster auf die Epoche beschneiden: vor der Migration ist block_reason
# NULL, weil die Spalte nicht existierte — nicht, weil kein Gate gegriffen
# hätte. Ungeschnitten meldete der Report „None 66,8 %" und legte damit einen
# blinden Fleck nahe, den es nicht gibt (nachgeprüft: seit 01.08. 100 % Abdeckung).
t0, cut = _clamp(t0, "block_reason")
if cut:
print(f" (Fenster auf den Beginn der Telemetrie gekürzt — davor wurde der "
f"Grund nicht erhoben)")
try:
c.execute("SELECT block_reason, COUNT(*) FROM recommendations "
"WHERE timestamp>=? AND signal='WARTEN' GROUP BY block_reason "
@@ -111,7 +149,13 @@ def sec_c(t0):
except FileNotFoundError:
print(" kein Log gefunden"); return
if not sw:
print(" keine TF-Wechsel im Zeitraum OK"); return
# ⚠ Kein „OK" mehr (2026-08-02): 0 Wechsel heißt bei geschlossenem Markt
# schlicht „keine Daten". Das als Entwarnung zu drucken, wäre genau die
# Sorte stiller Fehlschluss, die dieser Report aufdecken soll.
print(" 0 TF-Wechsel gefunden — das ist KEINE Entwarnung, sondern")
print(" möglicherweise fehlende Evidenz (Markt zu / Log rotiert / Bot aus).")
print(" Aussagekräftig ist dieser Abschnitt nur über Handelszeiten.")
return
days = max(1.0, (datetime.now().timestamp() - t0) / 86400)
gaps = sorted((sw[i][0] - sw[i - 1][0]).total_seconds() for i in range(1, len(sw)))
med = gaps[len(gaps) // 2] / 60 if gaps else 0
@@ -137,12 +181,24 @@ def sec_d0(c, t0):
print("\n" + "=" * 84)
print(" D0) MODELL-KALIBRIERUNG — sagt es voraus, was eintritt?")
print("=" * 84)
# ⚠ NUR Vorhersagen des AKTUELLEN Modells auswerten (Epochen-Schnitt, 2026-08-02).
# Ungeschnitten mischte diese Sektion die Vorhersagen des alten und des am
# 31.07. nachtrainierten Modells und meldete dessen Fehlkalibrierung als
# aktuellen Alarm — obwohl das neue Modell noch gar keine ausgewertete
# Vorhersage hatte. Nach einem Nachtraining hätte der Wächter so wochenlang
# falsch Alarm geschlagen, bis die Altdaten aus dem Fenster gerollt wären.
t0, cut = _clamp(t0, "pbreak_model")
if cut:
print(f" (nur Vorhersagen seit dem Nachtraining am {_EPOCHS['pbreak_model']})")
c.execute("""SELECT AVG(p_break), AVG(CASE WHEN outcome='break' THEN 100.0 ELSE 0 END),
COUNT(*) FROM pbreak_predictions
WHERE ts>=? AND outcome IS NOT NULL""", (t0,))
pm, rm, n = c.fetchone()
if not n or n < 30:
print(f" nur {n or 0} ausgewertete Vorhersagen — zu wenig"); return
print(f" nur {n or 0} ausgewertete Vorhersagen seit dem Nachtraining — zu wenig.")
print(f" (Das ist KEIN Entwarnungssignal, sondern fehlende Evidenz. "
f"Fälligkeit: Messung `pbreak_accuracy_v2`, ≥800 Vorhersagen.)")
return
gap = (rm or 0) - (pm or 0)
print(f" n={n} Ø-Vorhersage {pm:.1f} % echte Rate {rm:.1f} % "
f"Δ {gap:+.1f} Pp {_flag(abs(gap) <= 8)}")