UI aufgeraeumt + Telegram auf Closes reduziert (v=177)
Vier User-Vorgaben:
1. Einsatz-Felder entfernt. Backend bleibt vollstaendig -- Folge: der
Prozentsatz ist ohne UI nur ueber ini + Neustart aenderbar.
2. BRK-Schalter entfernt, dafuer STOPP (Circuit-Breaker). Bewusster Tausch mit
Konsequenz: der An/Aus-Schalter des AUTONOMEN Pfads ist aus der App weg (nur
noch POST bzw. ini), sein Zustand bleibt in der Breakout-Karte sichtbar.
Dafuer ist die Notbremse bedienbar, die am 17.08. real ausgeloest hat.
set_circuit_breaker + POST /api/circuit + runtime_state (cb_limit_pct,
cb_remember -- ohne das Gedaechtnis waere die Prozentzahl nach einmal
Ausschalten fuer immer weg).
3. Telegram nur noch bei geschlossenen Trades: EINE Filterstelle vor
_send_telegram_roh statt 17 Einzeleingriffe, _TG_ERLAUBT = ('Trade
geschlossen',). Umkehren = Liste erweitern. Tages-/Wochenreport wird nur auf
dem Telegram-Weg still, die E-Mail laeuft weiter.
EINE Ausnahme mit Bypass: '<ok_word> FEHLGESCHLAGEN' beim Notfall-Close --
dort ist die Position NICHT zu und es braucht eine Handeingabe.
Verifiziert: /api/circuit vorher 405, nachher 200; Toggle aus->an->8 %/57,94 EUR
persistiert; 85 Tests, node --check, check_nfalle gruen; 0 verwaiste
JS-Referenzen; genau eine Instanz je Port.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
32c064a1a6
commit
e48ad80c0a
+77
-2
@@ -38,7 +38,7 @@ from core.wave_rec import WaveRecommender
|
||||
from core.elliott import ElliottAnalyzer
|
||||
from core.agent import TradingAgent
|
||||
from core.news import NewsFetcher, NewsTranslator
|
||||
from core.notify import send_telegram
|
||||
from core.notify import send_telegram as _send_telegram_roh
|
||||
from core.mailer import send_graph_mail
|
||||
from core.mt5_utils import mt5_lock
|
||||
from core.market_hours import session_state
|
||||
@@ -166,6 +166,40 @@ def _p_break(mom6: float, mom3: float, wt: float, dist: float) -> float:
|
||||
return 1.0 / (1.0 + math.exp(-max(-30.0, min(30.0, z))))
|
||||
|
||||
|
||||
|
||||
# --- Telegram-Gate (User-Vorgabe 2026-08-19) --------------------------------
|
||||
# "entferne alle telegram benachrichtigungen. nur noch benachrichtigungen ueber
|
||||
# geschlossene trades."
|
||||
#
|
||||
# BEWUSST EINE STELLE statt 17 Einzeleingriffe: die Sender bleiben stehen
|
||||
# (Entry-Meldungen, Reports, Alarme), nur der Versand wird gefiltert. Kleiner
|
||||
# Diff, trivial umkehrbar (Liste erweitern = wieder an) -- und beim
|
||||
# Zurueckdrehen muessen nicht 17 Stellen einzeln wieder stimmen.
|
||||
#
|
||||
# Der TAGES- und WOCHENREPORT wird dadurch nur auf dem TELEGRAM-Weg still --
|
||||
# die E-Mail laeuft weiter (er geht ohnehin an beide). Engste Lesart der
|
||||
# Vorgabe: sie nennt ausdruecklich Telegram.
|
||||
#
|
||||
# EINE AUSNAHME mit Bypass: "<ok_word> FEHLGESCHLAGEN" beim Notfall-/
|
||||
# Gewinn-Close. Dort ist die Position NICHT zu und es braucht eine
|
||||
# Handeingabe -- Stille genau dort waere die gefaehrlichste Variante.
|
||||
_TG_ERLAUBT = ("Trade geschlossen",)
|
||||
|
||||
|
||||
def send_telegram(text, *a, **kw):
|
||||
"""Gefilterter Versand -- nur Meldungen aus `_TG_ERLAUBT` gehen raus.
|
||||
|
||||
Rueckgabe False fuer Unterdruecktes, damit Aufrufer, die den Erfolg
|
||||
auswerten (z. B. der Report-Versand-Marker), nichts faelschlich als
|
||||
'zugestellt' verbuchen."""
|
||||
try:
|
||||
if not any(k in (text or "") for k in _TG_ERLAUBT):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
return _send_telegram_roh(text, *a, **kw)
|
||||
|
||||
|
||||
class TradingEngine:
|
||||
"""Headless-Pendant zu OilWidget: hält die core-Objekte, fährt die
|
||||
Refresh-Loops als Threads und liefert einen aggregierten Snapshot."""
|
||||
@@ -555,6 +589,9 @@ class TradingEngine:
|
||||
# dessen 2×ATR-SL allein schon >pct% verlieren kann). 0 = aus.
|
||||
try:
|
||||
self._cb_limit_pct = max(0.0, float(self.cfg["trading"].get("daily_loss_limit_pct", "0") or 0))
|
||||
# Toggle-Gedaechtnis: ohne das waere die Prozentzahl nach
|
||||
# einmal Ausschalten verloren (s. `set_circuit_breaker`).
|
||||
self._cb_remember = self._cb_limit_pct or 8.0
|
||||
except (TypeError, ValueError):
|
||||
self._cb_limit_pct = 0.0
|
||||
self._circuit_tripped_date = None # Berlin-Datum, an dem der Breaker griff
|
||||
@@ -1024,7 +1061,10 @@ class TradingEngine:
|
||||
if err:
|
||||
log.error(f"{ok_word} fehlgeschlagen: {err}")
|
||||
if tg_on:
|
||||
try: send_telegram(f"⚠ <b>{ok_word} FEHLGESCHLAGEN</b>\n{sym} P&L {pnl:+.2f} "
|
||||
# BYPASS am Gate vorbei: die Position ist hier NICHT geschlossen und
|
||||
# es braucht eine Handeingabe. Stille waere hier der
|
||||
# gefaehrlichste Fall.
|
||||
try: _send_telegram_roh(f"⚠ <b>{ok_word} FEHLGESCHLAGEN</b>\n{sym} P&L {pnl:+.2f} "
|
||||
f"({level_txt}) — bitte MANUELL schließen! ({err})",
|
||||
tg["bot_token"], tg["chat_id"])
|
||||
except Exception as e: log.warning(f"Auto-Close-Telegram (Fehler): {e}")
|
||||
@@ -1453,6 +1493,33 @@ class TradingEngine:
|
||||
self._save_runtime_state()
|
||||
return self._auto_squeeze
|
||||
|
||||
def set_circuit_breaker(self, on: bool) -> bool:
|
||||
"""Tagesverlust-Stopp an/aus (User-Wunsch 2026-08-19, Dashboard-Schalter).
|
||||
|
||||
⚠ Der Breaker hat KEINEN eigenen An/Aus-Schalter, sondern lebt in
|
||||
`_cb_limit_pct` (0 = aus). Ein Toggle muss die Prozentzahl also merken,
|
||||
sonst waere sie nach einmal Ausschalten fuer immer weg. `_cb_remember`
|
||||
haelt den letzten Wert > 0; Startwert ist die ini (`daily_loss_limit_pct`,
|
||||
aktuell 8), Rueckfall 8, falls die ini je auf 0 stand.
|
||||
|
||||
⚠⚠ WAS ER TUT UND WAS NICHT: er SCHLIESST die offene Position bei
|
||||
Erreichen des Tageslimits (das wirkt auf ALLE Trades) und sperrt neue
|
||||
AUTONOME Entries bis zum naechsten Tag (`_squeeze_guard`). Manuelle
|
||||
Orders sperrt er NICHT — gemessen sind nur 6,1 % der Trades autonom,
|
||||
die schliessende Haelfte ist also die wirksame. Real ausgeloest am
|
||||
17.08. 18:35 (-82,42 <= -81,45 EUR).
|
||||
"""
|
||||
if on:
|
||||
self._cb_limit_pct = float(getattr(self, "_cb_remember", 0.0) or 8.0)
|
||||
else:
|
||||
if self._cb_limit_pct > 0:
|
||||
self._cb_remember = self._cb_limit_pct
|
||||
self._cb_limit_pct = 0.0
|
||||
log.info(f"Circuit-Breaker {'AN' if self._cb_limit_pct > 0 else 'AUS'}"
|
||||
f" ({self._cb_limit_pct:.0f} %)")
|
||||
self._save_runtime_state()
|
||||
return self._cb_limit_pct > 0
|
||||
|
||||
def _check_adverse15(self):
|
||||
"""15-Minuten-Regel (User-Wunsch 2026-07-30): steht ein **vom Bot eröffneter**
|
||||
Trade 15 min nach Entry mehr als `adverse_15min_atr`×ATR im Minus → schließen
|
||||
@@ -2094,6 +2161,10 @@ class TradingEngine:
|
||||
if "manual_margin" in st:
|
||||
from core.config import set_manual_margin as _set
|
||||
self._manual_margin = _set(st["manual_margin"] or 0)
|
||||
if "cb_remember" in st:
|
||||
self._cb_remember = float(st["cb_remember"] or 8.0)
|
||||
if "cb_limit_pct" in st:
|
||||
self._cb_limit_pct = max(0.0, float(st["cb_limit_pct"] or 0))
|
||||
if "margin_pct" in st:
|
||||
from core.config import set_margin_buffer as _setp, get_margin_buffer
|
||||
_setp(float(st["margin_pct"] or 0) or 95.0)
|
||||
@@ -2125,6 +2196,10 @@ class TradingEngine:
|
||||
"sr_close_min_gain": self._sr_close_min_gain,
|
||||
"manual_margin": getattr(self, "_manual_margin", 0.0),
|
||||
"margin_pct": getattr(self, "_margin_pct", 0.0),
|
||||
# Circuit-Breaker als PROZENTZAHL (nicht bool):
|
||||
# 0 = aus. So ueberlebt auch die Hoehe den Neustart.
|
||||
"cb_limit_pct": getattr(self, "_cb_limit_pct", 0.0),
|
||||
"cb_remember": getattr(self, "_cb_remember", 8.0),
|
||||
# Kegel-ACI-Skalen (2026-08-08): ohne Persistenz waere
|
||||
# die muehsam erlernte Kalibrierung nach jedem Neustart
|
||||
# weg und liefe von 1,0 neu an. ⚠ JSON-Schluessel sind
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user