S/R-Auto-Close komplett entfernt + Circuit-Breaker als runder CB-Knopf (v=210)
(1) S/R-AUTO-CLOSE RAUS (User-Vorgabe: "die brauche ich nicht mehr").
Der Schalter stand seit dem 19.08. ohnehin auf AUS - abgeschaltet, nachdem
analyze_pbreak_live.py das P(break)-Modell live als INVERTIERT gemessen hatte
(AUC 0,452, Klassen monoton rueckwaerts). Ohne funktionierendes Gate ist der
S/R-Close faktisch der PAUSCHALE, und der ist 2x gemessen und verworfen
(Gewinner-Kappen). Die Entfernung vollzieht den gemessenen Stand nach.
WEG: _check_sr_close, set_sr_autoclose, set_sr_close_min_gain, die
Konstruktor-Felder, die %-Armierung des Gewinn-Close, drei Snapshot-Felder,
beide runtime_state-Schluessel, die Reichweiten- und Systemstatus-Zeilen,
/api/srclose und /api/srclose_min, der Knopf S/R, das Gewinn-Close-Feld, der
Toast "Bot hat am Level geschlossen".
BLEIBT: `_sr_close_hint` (App-Hinweis, P(break)-Chartlinien, Copilot-Kontext,
pbreak_predictions-Tracking), `stop_approach`, das Modell selbst und die
closed_by=sr_close-Historie in der DB. Live gegengeprueft: die drei Felder sind
aus dem Snapshot verschwunden, sr_close_hint und stop_approach sind da, beide
Endpunkte antworten 405.
Sicherung: .removed_backup/sr_auto_close_2026-08-21.py
⚠ Der Close-Alarm gilt jetzt schlicht ab jedem Gewinn (`minGain = 0`) - die
Schwelle gab es nur fuer den Auto-Close. Das ist der Zustand von vor 2026-07.
⚠ Der Test test_automatik_zeile_nennt_alle_schalter war MITGEZAEHLT und faellt
korrekt - er ist angepasst, nicht entfernt: jetzt zwei Schalter statt drei, und
die Negativ-Pruefungen ("SIG"/"S/R-Close" duerfen NICHT auftauchen) sind der
eigentliche Wert, denn ein wieder auftauchender Pfad waere ein stiller Rueckbau.
(2) CIRCUIT BREAKER als runder Knopf "CB" rechts neben BALANCE.
⚠ Farb-Logik wie vorgegeben: GRUEN = inaktiv (aus), ROT = aktiv (scharf).
Bewusst NICHT die uebliche "gruen = geschuetzt"-Lesart - hier signalisiert Rot,
dass eine Automatik eingreifen kann. Im Code und im Markup steht das dran, damit
es niemand spaeter "korrigiert".
⚠ VERSCHOBEN, nicht kopiert: zwei Elemente mit derselben ID waeren ein stiller
Fehler, weil getElementById nur das erste findet.
⚠ Beim Entfernen zwei eigene Fehler: (a) ein Slice ueber `rindex("try:")`
schnitt durch fremde Konstruktor-Bloecke und riss Kommentar-Fragmente heraus -
zurueckgesetzt und zeilenbasiert von hinten neu gemacht; (b) beim Toast-Block
eine Klammer zu viel entfernt, von `node --check` sofort gefangen.
Pipeline A-G gruen, 97 Tests, 14 render*-Funktionen, deployt und am
ausgelieferten HTML/Snapshot verifiziert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ebf10da00e
commit
0dd2e524b7
+13
-121
@@ -444,14 +444,11 @@ class TradingEngine:
|
||||
self._close_alert_ts = 0.0 # letzter CLOSE-Telegram-Push (5-min-Cooldown)
|
||||
self._close_alert_ticket = None # Ticket, auf das sich der Cooldown bezieht
|
||||
self._close_alert_count = 0 # wie oft der Flip-Close-Alarm gefeuert hat
|
||||
self._auto_sr_close = self.cfg["trading"].get("auto_sr_close", "true").lower() == "true"
|
||||
try:
|
||||
self._sr_close_pbreak = float(self.cfg["trading"].get("sr_close_pbreak", "0.60"))
|
||||
except (TypeError, ValueError):
|
||||
self._sr_close_pbreak = 0.60
|
||||
self._sr_close_pbreak = max(0.30, min(0.90, self._sr_close_pbreak)) # sinnvolles Band
|
||||
self._sr_close_count = 0 # wie oft der Bot am S/R automatisch schloss
|
||||
self._sr_close_last_ticket = None # verhindert Doppel-Auslösung je Ticket
|
||||
# Autonomer Entry auf den gemessen-validierten Squeeze-Ausbruch (nur FLAT).
|
||||
# ⚠ ECHTE Order — Default AUS (Autonomie nie stillschweigend an). `[trading]
|
||||
# auto_squeeze=true` aktiviert. Dedup über (dir, level) des Ausbruchs.
|
||||
@@ -575,24 +572,6 @@ class TradingEngine:
|
||||
self._flip_close_count = 0
|
||||
self._pos_close_dir = None # Art) → Squeeze-Wiedereinstiegs-Sperre (gegen die
|
||||
# Open→Instant-Close-Kaskade bei zu engem Notfall-Stop)
|
||||
self._sr_close_min_gain_armed_ticket = None # Ticket, für das die %-Schwelle schon
|
||||
# aus echter Margin berechnet wurde (Retry-Schutz,
|
||||
# s. `_check_auto_close`)
|
||||
try:
|
||||
self._sr_close_min_gain = max(0.0, float(
|
||||
self.cfg["trading"].get("sr_close_min_gain", "0")))
|
||||
except (TypeError, ValueError):
|
||||
self._sr_close_min_gain = 0.0
|
||||
# Gewinn-Close automatisch auf X% des Einsatzes (Margin) DIESER Position setzen,
|
||||
# sobald sie eröffnet wird (User-Vorgabe 2026-07-23) — überschreibt den manuell
|
||||
# gemerkten/persistierten Wert bei jeder ECHT NEUEN Position (nicht beim
|
||||
# Wiedererkennen nach Neustart, s. `_check_auto_close`). 0 = aus (dann bleibt der
|
||||
# manuell gesetzte/gemerkte Wert wie bisher maßgeblich).
|
||||
try:
|
||||
self._sr_close_min_gain_pct = max(0.0, float(
|
||||
self.cfg["trading"].get("sr_close_min_gain_pct", "1.0")))
|
||||
except (TypeError, ValueError):
|
||||
self._sr_close_min_gain_pct = 1.0
|
||||
self._pb_state = None # geglättetes P(break) + Hysterese-Zone je Level
|
||||
self._stop_pb_state = None # dito für die Gegen-/Stop-Seiten-Anzeige
|
||||
# (gemessen `analyze_pbreak_flicker.py`: mom3 der
|
||||
@@ -946,7 +925,6 @@ class TradingEngine:
|
||||
self._check_slot_reichweite() # meldet ungedeckte 2. Position
|
||||
self._check_adverse15() # 15-Min-Regel (nur Bot-Trades)
|
||||
self._check_flip_close() # Empfehlung dreht → schließen
|
||||
self._check_sr_close()
|
||||
# ZUERST die ruhenden Stop-Orders pflegen (setzen/stornieren),
|
||||
# DANN der Market-Fallback: so kann eine bereits liegende Order
|
||||
# den Ausbruch am Level abfangen, bevor nachgejagt würde.
|
||||
@@ -1202,8 +1180,6 @@ class TradingEngine:
|
||||
or (getattr(self, "_emergency_pct", 0) or 0) > 0
|
||||
or (getattr(self, "_emergency_remember", 0) or 0) > 0):
|
||||
offen.append("Notfall-Stop")
|
||||
if getattr(self, "_auto_sr_close", False):
|
||||
offen.append("S/R-Close")
|
||||
if (getattr(self, "_adverse15_atr", 0) or 0) > 0:
|
||||
offen.append("15-Min-Regel")
|
||||
if getattr(self, "_flip_close", False):
|
||||
@@ -1321,7 +1297,6 @@ class TradingEngine:
|
||||
# bei offenem Trade kappte sonst die von Hand gesetzten −25 € auf −19,42).
|
||||
self._emergency_loss = self._emergency_state_loss
|
||||
self._takeprofit = self._emergency_state_tp
|
||||
self._sr_close_min_gain_armed_ticket = ticket # wiederhergestellter Wert
|
||||
# gilt als final — NICHT auf 1% zurücksetzen
|
||||
self._emergency_margin_armed_ticket = ticket # dito für den margin-%-Stop
|
||||
log.info(f"Auto-Close wiederhergestellt (Pos {ticket}): Stop "
|
||||
@@ -1359,16 +1334,8 @@ class TradingEngine:
|
||||
# umgesetzt, der Ticket-Wechsel-Zweig feuert nur 1×) — konnte den Trade zu
|
||||
# früh (falscher, zu niedriger Alt-Wert) ODER dauerhaft mit falscher Schwelle
|
||||
# laufen lassen.
|
||||
if (self._sr_close_min_gain_pct > 0
|
||||
and self._sr_close_min_gain_armed_ticket != ticket):
|
||||
margin = ps.get("margin") or 0.0
|
||||
if margin > 0:
|
||||
self._sr_close_min_gain = round(
|
||||
self._sr_close_min_gain_pct / 100.0 * margin, 2)
|
||||
self._sr_close_min_gain_armed_ticket = ticket
|
||||
self._save_runtime_state()
|
||||
log.info(f"Gewinn-Close armiert (Pos {ticket}): "
|
||||
f"+{self._sr_close_min_gain:.2f} ({self._sr_close_min_gain_pct:.0f}% Margin {margin:.2f})")
|
||||
# ⚠ Die automatische %-Armierung des Gewinn-Close ist am 2026-08-21
|
||||
# mit dem S/R-Auto-Close entfallen — sie diente ausschliesslich ihm.
|
||||
# Notfall-Stop als % der Margin — gleiches Retry-Muster (margin-Wert kann im
|
||||
# ersten Tick nach dem Öffnen noch 0 sein). Hat Vorrang vor Balance-%/Fixwert
|
||||
# (die im "echt neue Position"-Zweig oben bewusst übersprungen wurden, wenn
|
||||
@@ -1430,62 +1397,17 @@ class TradingEngine:
|
||||
self._takeprofit = None
|
||||
return
|
||||
|
||||
def _check_sr_close(self):
|
||||
"""Automatischer S/R-Close (User-Vorgabe): schließt eine Position **im Plus**
|
||||
selbstständig, wenn die gemessene Bedingung greift (Kurs klebt am gegenüber-
|
||||
liegenden Level + schwacher Anlauf → Durchbruch ~26 %, Close schlägt Laufen-
|
||||
lassen; `backtest_srbreak.py`). Nur bei `auto_sr_close=true`, genau 1×/Ticket,
|
||||
wirkt auch bei gesperrtem Handy. Fail-safe."""
|
||||
if not self._auto_sr_close:
|
||||
return
|
||||
if self._in_startup_grace(): # Startup-Schonfrist: kein Sofort-Close nach Neustart
|
||||
return
|
||||
ps = self.trader.snapshot()
|
||||
ticket = ps.get("ticket")
|
||||
if not ticket:
|
||||
self._sr_close_last_ticket = None
|
||||
return
|
||||
if ticket == self._sr_close_last_ticket: # bereits ausgelöst → nicht erneut
|
||||
return
|
||||
pnl = ps.get("pnl")
|
||||
if pnl is None or pnl <= 0: # NUR bei Trade im Plus (User-Vorgabe)
|
||||
return
|
||||
# Optionaler Mindestgewinn (User-Vorgabe; Default 0=aus — gemessen ist ohne
|
||||
# besser): unter der Schwelle wird am Level NICHT geschlossen, Trade läuft.
|
||||
if self._sr_close_min_gain > 0 and pnl < self._sr_close_min_gain:
|
||||
return
|
||||
hint = self._sr_close_hint(self.data.snapshot(), ps, self.wave.snapshot())
|
||||
if not hint or not hint.get("close"): # am Level, aber P(break) ≥ Schwelle → laufen lassen
|
||||
return
|
||||
self._sr_close_last_ticket = ticket # vor dem Close markieren (kein Doppel)
|
||||
sym = self.data.symbol or ""
|
||||
_confirm = hint.get("confirm")
|
||||
log.info(f"🎯 S/R-AUTO-CLOSE: {sym} P&L {pnl:+.2f} — am Level {hint['level']}, "
|
||||
f"Durchbruch nur ~{hint['p_break']}% (< {self._sr_close_pbreak:.0%})"
|
||||
+ (f" — Bestätigung erst ab {_confirm}" if _confirm else "")
|
||||
+ " → Gewinn sichern")
|
||||
err = self.trader.close(reason="sr_close")
|
||||
tg = self.cfg["telegram"]
|
||||
tg_on = (tg.get("enabled", "false").lower() == "true" and tg.get("bot_token"))
|
||||
if err:
|
||||
log.error(f"S/R-Auto-Close fehlgeschlagen: {err}")
|
||||
self._sr_close_last_ticket = None # Fehlschlag → nächster Tick erneut
|
||||
if tg_on:
|
||||
try: send_telegram(f"⚠ <b>S/R-Close FEHLGESCHLAGEN</b>\n{sym} P&L {pnl:+.2f} "
|
||||
f"— bitte MANUELL schließen! ({err})",
|
||||
tg["bot_token"], tg["chat_id"])
|
||||
except Exception as e: log.warning(f"S/R-Close-Telegram (Fehler): {e}")
|
||||
return
|
||||
# Richtung + Zeit merken → Auto-Squeeze-Cooldown gegen Gleichrichtungs-Roundtrip
|
||||
self._sr_close_dir = "LONG" if ps.get("order_type") == 0 else "SHORT"
|
||||
self._sr_close_ts = time.time()
|
||||
self._sr_close_count += 1
|
||||
if tg_on:
|
||||
try: send_telegram(f"🎯 <b>S/R-Auto-Close</b>\n{sym} bei P&L {pnl:+.2f} geschlossen "
|
||||
f"— Kurs klebte am Level {hint['level']} mit schwachem Anlauf "
|
||||
f"(Durchbruch ~{hint['p_break']}% unwahrscheinlich).",
|
||||
tg["bot_token"], tg["chat_id"])
|
||||
except Exception as e: log.warning(f"S/R-Close-Telegram: {e}")
|
||||
# ⚠⚠ `_check_sr_close` ENTFERNT am 2026-08-21 (User-Vorgabe). Der Schalter
|
||||
# stand seit dem 19.08. auf AUS — abgeschaltet, nachdem
|
||||
# `analyze_pbreak_live.py` das P(break)-Modell live als INVERTIERT gemessen
|
||||
# hatte (AUC 0,452, Klassen monoton rueckwaerts). Ohne funktionierendes
|
||||
# Gate ist der S/R-Close faktisch der PAUSCHALE, und der ist 2x gemessen
|
||||
# und verworfen (Gewinner-Kappen). Die Entfernung vollzieht den gemessenen
|
||||
# Stand nach; sie schaltet nichts ab, was noch lief.
|
||||
# ⚠ BLEIBT: `_sr_close_hint` (App-Hinweis, P(break)-Chartlinien,
|
||||
# Copilot-Kontext, `pbreak_predictions`-Tracking), `stop_approach`, das
|
||||
# Modell selbst und die `closed_by=sr_close`-Historie in der DB.
|
||||
# Sicherung: `.removed_backup/sr_auto_close_2026-08-21.py`.
|
||||
|
||||
def _auto_guard(self, pfad: str) -> str | None:
|
||||
"""Die NICHT richtungsabhängigen Gates für autonome Entries.
|
||||
@@ -2174,24 +2096,6 @@ class TradingEngine:
|
||||
tg["bot_token"], tg["chat_id"])
|
||||
except Exception as e: log.warning(f"Adverse15-Telegram: {e}")
|
||||
|
||||
def set_sr_autoclose(self, on: bool) -> bool:
|
||||
"""Automatischen S/R-Close an/aus (UI/Config). Rückgabe = neuer Zustand."""
|
||||
self._auto_sr_close = bool(on)
|
||||
log.info(f"Auto-S/R-Close {'AN' if self._auto_sr_close else 'AUS'}")
|
||||
self._save_runtime_state()
|
||||
return self._auto_sr_close
|
||||
|
||||
def set_sr_close_min_gain(self, value) -> float:
|
||||
"""Mindestgewinn (EUR) für den S/R-Auto-Close. 0/leer = aus."""
|
||||
try:
|
||||
v = max(0.0, float(value)) if value not in (None, "") else 0.0
|
||||
except (TypeError, ValueError):
|
||||
v = 0.0
|
||||
self._sr_close_min_gain = v
|
||||
log.info(f"S/R-Close-Mindestgewinn {('+' + format(v, '.2f')) if v > 0 else 'aus'}")
|
||||
self._save_runtime_state() # neustart-fest (Fix 2026-07-22 — war vergessen)
|
||||
return self._sr_close_min_gain
|
||||
|
||||
def set_sltp(self, sl=None, tp=None) -> dict:
|
||||
"""Manuelles SL/TP der offenen Position setzen. **Deaktiviert das Trailing**
|
||||
(User-Vorgabe) — sonst zöge es SL/TP beim nächsten Tick wieder um. Rückgabe
|
||||
@@ -2731,10 +2635,6 @@ class TradingEngine:
|
||||
# ⚠ `auto_signal` wird seit 2026-08-12 NICHT mehr gelesen (Pfad
|
||||
# entfernt). Ein Altbestand in der Datei schadet nicht — er wird
|
||||
# beim naechsten Schreiben ohnehin nicht mehr mitgeschrieben.
|
||||
if "auto_sr_close" in st:
|
||||
self._auto_sr_close = bool(st["auto_sr_close"])
|
||||
if "sr_close_min_gain" in st:
|
||||
self._sr_close_min_gain = max(0.0, float(st["sr_close_min_gain"] or 0))
|
||||
if "manual_margin" in st:
|
||||
from core.config import set_manual_margin as _set
|
||||
self._manual_margin = _set(st["manual_margin"] or 0)
|
||||
@@ -2783,8 +2683,6 @@ class TradingEngine:
|
||||
self._cone_skalen = {int(k): float(v)
|
||||
for k, v in (st["cone_skalen"] or {}).items()}
|
||||
log.info(f"Runtime-State geladen: BRK={'AN' if self._auto_squeeze else 'aus'} · "
|
||||
f"S/R-Close={'AN' if self._auto_sr_close else 'aus'} · "
|
||||
f"MinGewinn={self._sr_close_min_gain:+.2f} · "
|
||||
f"Einsatz={'auto' if not getattr(self, '_manual_margin', 0) else f'{self._manual_margin:.2f}'}"
|
||||
f" · Einsatz-%={getattr(self, '_margin_pct', 0):.0f}")
|
||||
except FileNotFoundError:
|
||||
@@ -2798,8 +2696,6 @@ class TradingEngine:
|
||||
tmp = self._runtime_state_path + ".tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump({"auto_squeeze": self._auto_squeeze,
|
||||
"auto_sr_close": self._auto_sr_close,
|
||||
"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):
|
||||
@@ -5258,10 +5154,7 @@ class TradingEngine:
|
||||
"entry_gap": self._entry_gap_cached(),
|
||||
"sr_close_hint": sr_close_hint,
|
||||
"stop_approach": stop_approach,
|
||||
"auto_sr_close": self._auto_sr_close,
|
||||
"sr_close_min_gain": self._sr_close_min_gain,
|
||||
"sr_close_pbreak": self._sr_close_pbreak,
|
||||
"sr_close_count": self._sr_close_count,
|
||||
# Schwelle der Close-Benachrichtigung — sichtbar, damit belegbar ist,
|
||||
# dass sie im LAUFENDEN Prozess angekommen ist. „Erfolg melden, wo
|
||||
# nichts geprueft wurde" ist im Projekt bereits viermal passiert.
|
||||
@@ -5488,7 +5381,6 @@ class TradingEngine:
|
||||
try:
|
||||
an = lambda b: "AN" if b else "aus" # noqa: E731
|
||||
z.append(f"Automatik: BRK {an(self._auto_squeeze)} · "
|
||||
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})")
|
||||
|
||||
Reference in New Issue
Block a user