Telegram bei gruener M15-Karte (m15_alert) + Tests verschmutzen nicht mehr das Log
User: "wenn es eine Empfehlung gibt (gruen), schicke eine Telegram-Nachricht." engine._check_m15_alert, direkt hinter _log_m15_state -- dieselbe Quelle wie Anzeige und Selbstmessung, damit die drei nicht auseinanderlaufen koennen. DIE ENTPRELLUNG IST DER GANZE BAU. Roh springt die Karte 85x/Tag auf gruen (m15_states, 3 Tage nach der Hysterese vom 12.08.) -- das waere Spam. Alle Stufen an den echten Daten kalibriert, nicht geraten: nur Flanke 85,0/Tag + Level & Richtung als Schluessel 23,7/Tag + 30 min Mindestabstand 13,3/Tag + Fenster 8-20 Uhr (Default) 7,7/Tag (Vergleich: Close-Meldung ab 50 EUR 2,4-6,1/Tag) Nach 4 h darf dasselbe Level erneut melden, sonst verschweigt der Alert ein Setup, das den ganzen Tag wiederkommt. Der Text sagt ausdruecklich, dass es KEINE Prognose ist: die Karte automatisch zu handeln faellt gemessen durch (backtest_m15_auto.py: OeR -0,162/-0,094). Die Meldung ist ein Hinweis zum Hinsehen fuer den Menschen (+3,20 EUR/Lot ueber 146 Trades). Die P(break)-Formulierung ist wortgleich zu Karte und Chart (v1.35). 13 Tests, Schwerpunkt auf dem was NICHT gesendet werden darf. Mutationsprobe in vier Zweige, alle gefangen. DIE PROBE HAT EIN TESTLOCH AUFGEDECKT: das Entfernen der Flankenerkennung liess zunaechst ALLE Tests gruen, weil Cooldown und 4-h-Schluessel dasselbe abfangen. Zwei Schutzschichten, die sich maskieren -- dasselbe Muster wie beim Fill-Dedup. Geschlossen durch test_flankenerkennung_ISOLIERT. NEBENBEFUND, behoben: Tests schrieben ins Produktions-Log (ueber 150 M15-Alert-Zeilen + Close-Push #4711 aus test_close_buchung). Ich hielt die Entprellung deshalb zuerst fuer live defekt. conftest.py entfernt den FileHandler des oil-Loggers jetzt fuer die Sitzung -- vierte Grundregel der Suite. Verifiziert: Log-Groesse vor und nach einem Komplettlauf identisch. Live verifiziert: seit dem Serverstart genau 1 Alert trotz mehrfachem gruen/gelb-Wechsel. 85 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c251652d33
commit
44191947ac
@@ -64,6 +64,9 @@ _ATR_TF_MAP = {"M1": mt5.TIMEFRAME_M1, "M5": mt5.TIMEFRAME_M5,
|
||||
"H1": mt5.TIMEFRAME_H1}
|
||||
|
||||
# Loop-Intervalle (Sekunden) — entsprechen den *_REFRESH_MS-Werten des Widgets
|
||||
# Nach dieser Zeit darf DASSELBE Level erneut melden — sonst verschweigt der
|
||||
# Alert ein Setup, das den ganzen Tag wiederkommt (s. `_check_m15_alert`).
|
||||
_ALERT_NEU_S = 4 * 3600
|
||||
_PRICE_S = 0.5
|
||||
_TREND_S = 5.0
|
||||
_POS_S = 1.0
|
||||
@@ -192,6 +195,24 @@ class TradingEngine:
|
||||
0.0, float(tg.get("close_notify_min_eur", "50") or 0))
|
||||
except (TypeError, ValueError):
|
||||
self._close_notify_min = 50.0
|
||||
# ── M15-Alert (User-Wunsch 2026-08-14) ────────────────────────────
|
||||
# Defaults an den ECHTEN Daten kalibriert, nicht geraten: roh 85
|
||||
# Gruen-Flanken/Tag → mit Level-Entprellung 23,7 → +30 min 13,3 →
|
||||
# +Fenster 8-20 Uhr **7,7/Tag** (Close-Meldung liegt bei 2,4-6,1).
|
||||
self._m15_alert = (tg.get("m15_alert", "true").strip().lower() == "true")
|
||||
try:
|
||||
self._m15_alert_cd = max(
|
||||
0.0, float(tg.get("m15_alert_cooldown_min", "30") or 0)) * 60.0
|
||||
except (TypeError, ValueError):
|
||||
self._m15_alert_cd = 1800.0
|
||||
try:
|
||||
_h = str(tg.get("m15_alert_hours", "8-20")).split("-")
|
||||
self._m15_alert_hours = (int(_h[0]), int(_h[1]))
|
||||
except (TypeError, ValueError, IndexError):
|
||||
self._m15_alert_hours = (8, 20)
|
||||
self._m15_alert_war = False # Flankenerkennung
|
||||
self._m15_alert_ts = 0.0
|
||||
self._m15_alert_key = None
|
||||
|
||||
# ── Markt / Trading / Trailing ────────────────────────────────────
|
||||
self.data = MT5Data()
|
||||
@@ -3506,6 +3527,78 @@ class TradingEngine:
|
||||
except Exception as e:
|
||||
log.debug(f"m15_state log: {e}")
|
||||
|
||||
def _check_m15_alert(self, s: dict | None):
|
||||
"""Telegram, sobald die M15-Karte auf GRÜN mit gerichteter Lesart springt.
|
||||
|
||||
User-Wunsch 2026-08-14: „wenn es eine Empfehlung gibt (grün), schicke
|
||||
eine Telegram-Nachricht."
|
||||
|
||||
⚠⚠ ENTPRELLUNG IST HIER DER GANZE BAU, nicht ein Detail. Roh gemessen
|
||||
(`m15_states`, 3 Tage nach der Hysterese vom 12.08.) springt die Karte
|
||||
**85× am Tag** auf grün — das waere Spam, kein Signal. Zwei Stufen,
|
||||
beide an den echten Daten kalibriert statt geraten:
|
||||
· **Level + Richtung** als Schluessel: gruen→gelb→gruen am SELBEN
|
||||
Level ist dasselbe Setup, das flackert, keine neue Gelegenheit.
|
||||
85 → 23,7/Tag.
|
||||
· **Mindestabstand** `m15_alert_cooldown_min` (30) → 13,3/Tag,
|
||||
und das **Stundenfenster** `m15_alert_hours` (8–20) → **7,7/Tag**.
|
||||
Zum Vergleich: die Close-Benachrichtigung liefert 2,4–6,1/Tag.
|
||||
⚠ Nach `_ALERT_NEU_S` (4 h) darf dasselbe Level erneut melden — sonst
|
||||
verschwiegen wir ein Setup, das den ganzen Tag wiederkommt.
|
||||
|
||||
⚠⚠ EHRLICH IM TEXT: die Karte ist gemessen KEIN Richtungssignal. Sie
|
||||
automatisch zu handeln faellt durch (`backtest_m15_auto.py` 13.08.:
|
||||
ØR −0,162/−0,094, KI ohne Null, schlechter als HTF-Richtung UND als das
|
||||
eigene Gegenteil). Die Meldung ist deshalb ein **Hinweis zum Hinsehen**
|
||||
fuer den Menschen — dessen Auswahl-Vorteil unabhaengig belegt ist
|
||||
(+3,20 €/Lot ueber 146 Trades) — und nennt das im Text.
|
||||
`m15_alert=false` schaltet ab.
|
||||
"""
|
||||
if not self._m15_alert or not s:
|
||||
return
|
||||
try:
|
||||
if s.get("status") != "gruen" or s.get("lesart") not in ("auf", "ab"):
|
||||
self._m15_alert_war = False
|
||||
return
|
||||
if self._m15_alert_war: # nur die FLANKE, nicht der Zustand
|
||||
return
|
||||
self._m15_alert_war = True
|
||||
now = time.time()
|
||||
h = datetime.now(_BERLIN).hour
|
||||
lo, hi = self._m15_alert_hours
|
||||
if not (lo <= h < hi):
|
||||
return
|
||||
nah = s.get("nah") or {}
|
||||
key = (round(nah.get("level") or 0, 2), s.get("lesart"))
|
||||
alt_ts = self._m15_alert_ts
|
||||
if (now - alt_ts) < self._m15_alert_cd:
|
||||
return
|
||||
if key == self._m15_alert_key and (now - alt_ts) < _ALERT_NEU_S:
|
||||
return
|
||||
tg = self.cfg["telegram"]
|
||||
if tg.get("enabled", "false").strip().lower() != "true":
|
||||
return
|
||||
self._m15_alert_ts, self._m15_alert_key = now, key
|
||||
pb = nah.get("p_break")
|
||||
lese = ("unentschieden" if pb is None or 45 <= pb <= 55 else
|
||||
f"{pb} % Bruch" if pb > 55 else f"{100 - pb} % Abprall")
|
||||
seite = "Widerstand" if nah.get("seite") == "res" else "Support"
|
||||
erl = {"SHORT": "LONG", "LONG": "SHORT"}.get(
|
||||
(s.get("bias") or {}).get("verboten"))
|
||||
k = (s.get("kosten") or {}).get("ratio")
|
||||
send_telegram(
|
||||
f"🟢 <b>M15 · Setup bereit</b>\n"
|
||||
f"{seite} M30 {nah.get('level')} · {nah.get('gap_atr15')}×ATR_M15\n"
|
||||
f"Level {lese}"
|
||||
+ (f" · erlaubt wäre {erl}" if erl else " · kein Trend-Veto")
|
||||
+ (f" · Kosten {k}×ATR" if k is not None else "")
|
||||
+ "\n<i>Bedingung erfüllt — keine Richtungsprognose "
|
||||
"(gemessen 48–51 % Treffer).</i>",
|
||||
tg.get("bot_token", "").strip(), tg.get("chat_id", "").strip())
|
||||
log.info(f"📨 M15-Alert {seite} {nah.get('level')} ({lese})")
|
||||
except Exception as e:
|
||||
log.debug(f"m15_alert: {e}")
|
||||
|
||||
def _log_cone_check(self, cone: dict | None):
|
||||
"""Legt je Horizont EINEN nicht-ueberlappenden Abdeckungs-Check an.
|
||||
|
||||
@@ -3866,6 +3959,7 @@ class TradingEngine:
|
||||
_m15 = self._m15_setup(market, wave_snap, _cone,
|
||||
self._alignment_cached(), wave_signal)
|
||||
self._log_m15_state(_m15, market)
|
||||
self._check_m15_alert(_m15) # Telegram bei Flanke auf GRUEN
|
||||
self._log_cone_check(_cone)
|
||||
# Für den KI-Copiloten festhalten (2026-08-02) — er bekam bis dahin nur
|
||||
# das Wellen-Signal und sah ausgerechnet die beiden kalibrierten Größen
|
||||
|
||||
Reference in New Issue
Block a user