S/R-Level von M5 auf M30 umgestellt + P(break)-Modell nachtrainiert

User: "DIE S/R Linien liegen oft sehr nahe beieinander" -> Option 2 (beides
umstellen, Modell nachtrainieren) gewaehlt. Erst gemessen: backtest_level_tf.py,
80k M5-Bars, 2 Halbjahre, Trades bleiben M5, nur die Level-Quelle variiert.

Ergebnis (M30 gewinnt in BEIDEN Haelften):
  TF    AUC(H2)  Abstand   <1xATR   dR bei P<0.55 (H1/H2)
  M5     0.710   0.61xATR    71%     +6698  +7749   (bisher live)
  M15    0.725   1.16xATR    46%    +10138 +12071
  M30    0.721   1.95xATR    28%    +12424 +13047   <- gewaehlt

Die Beschwerde ist damit quantifiziert: 71% der M5-Level lagen naeher als 1xATR.

Umsetzung:
- wave_rec: _m30_hl gespeichert, neues _pb_levels30 (k=3, letzte 50 M30-Bars =
  trainingsgleich zum 300-M5-Lookback), im Snapshot.
- engine._draw_levels nutzt pb_levels30 (Fallback pb_levels beim Kaltstart);
  speist Chart, MQL5-CSV, Dashboard UND den P(break)-Auto-Close.
- _PB_MU/_PB_SD/_PB_W auf M30 nachtrainiert: out-of-sample validiert (H1->H2),
  dann auf allen 80k gefittet (34771 Touches, Kalibrierung 23/25, 38/36, 58/59,
  86/87). Merkmale bleiben M5. Alte M5-Werte als Kommentar (Rueckweg).
- NICHT mitveraendert: Entry-Raum-Gate nutzt weiter M5-pb_levels
  (entry_room_atr=0.6 ist darauf kalibriert).

Verifiziert live: R/S 84.617/83.722 = 2.9xATR Abstand (vorher 0.28xATR),
M30-Pivots 4/7 statt M5 27/31, keine Fehler im Log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-07-30 17:56:20 +02:00
co-authored by Claude Opus 4.8
parent 06b37a1474
commit b77b3d60f5
4 changed files with 356 additions and 8 deletions
+24
View File
@@ -295,6 +295,14 @@ class WaveRecommender:
turn_data[_lbl] = (_c, _atr(_h, _l, _c))
if _lbl == "M5":
self._m5_hl = (_h, _l)
elif _lbl == "M30":
# M30-H/L für die S/R-LEVEL (Chart + P(break)-Auto-Close).
# Gemessen 2026-07-30 (`backtest_level_tf.py`): M30-Level
# schlagen M5 in BEIDEN Hälften (R-Ertrag ~2×, Level 1,95
# statt 0,61×ATR entfernt). Das M5-`_pb_levels` bleibt
# daneben bestehen — es speist das Entry-Raum-Gate, das
# auf M5 kalibriert ist (nicht ungemessen mitverändern!).
self._m30_hl = (_h, _l)
self._turn_due = time.time() + 15
# ── Schneller M5-Squeeze-Fetch (Variante A): entkoppelt von der 15-s-
# Ampel, kleiner 60-Bar-M5-Fetch ~alle 5 s → Ausbrüche werden ~3× schneller
@@ -377,6 +385,19 @@ class WaveRecommender:
self._pb_levels = {"ph": ph, "pl": pl}
# (Squeeze läuft jetzt auf dem schnellen ~5-s-M5-Fetch oben,
# nicht mehr hier auf dem 15-s-Ampel-Pfad — Variante A 2026-07-21.)
# ── S/R-LEVEL auf M30 (gemessen 2026-07-30, `backtest_level_tf.py`) ──
# Trainingsgleiches Fenster: der M5-Lookback von 300 Bars entspricht
# 50 M30-Bars → nur die letzten 50+K betrachten, sonst kämen Level aus
# einem viel längeren Zeitraum als im Training.
_h30, _l30 = getattr(self, "_m30_hl", (None, None))
if _h30 and len(_h30) >= 12:
_K = 3; _WIN = 50 + _K
_hs = _h30[-_WIN:]; _ls = _l30[-_WIN:]
ph30 = [round(_hs[j], 3) for j in range(_K, len(_hs) - _K)
if _hs[j] == max(_hs[j - _K:j + _K + 1])]
pl30 = [round(_ls[j], 3) for j in range(_K, len(_ls) - _K)
if _ls[j] == min(_ls[j - _K:j + _K + 1])]
self._pb_levels30 = {"ph": ph30, "pl": pl30}
# Multi-TF-Bounce: stärksten Status über M1/M5/M15/M30 wählen (H1 ist NUR
# für die Ampel im turn_data, NICHT im gemessen-neutralen Bounce → skip).
cands = []
@@ -396,6 +417,9 @@ class WaveRecommender:
snap["bounce"] = dict(self._last_bounce) # Multi-TF überschreibt Basis-TF
snap["pb_feats"] = dict(self._pb_feats) if getattr(self, "_pb_feats", None) else None
snap["pb_levels"] = dict(self._pb_levels) if getattr(self, "_pb_levels", None) else None
# M30-Level = Quelle für Chart-Linien UND P(break)-Auto-Close (gemessen besser);
# `pb_levels` (M5) bleibt für das Entry-Raum-Gate.
snap["pb_levels30"] = dict(self._pb_levels30) if getattr(self, "_pb_levels30", None) else None
snap["squeeze"] = dict(self._squeeze) if getattr(self, "_squeeze", None) else \
{"state": None, "dir": None, "level": None, "box_atr": None}
# ── Entry-Raum-Gate (gemessen `backtest_entryroom.py`, beide Hälften