Provider-Failover bei hartem Fehlschlag + Einsatz-Prozentfeld im Dashboard

1) LLM-Failover (core/agent.py): ein hart gescheiterter Provider wird zeitweise
uebersprungen, analyze() nimmt den naechsten aus der Kette. Hart = 401/402/403/
404 (Key/Guthaben/Modell) -> 30 min, nicht erreichbar -> 5 min. Timeout/429/5xx
loesen BEWUSST keinen Wechsel aus (voruebergehend; sonst kostet jede Lastspitze
die volle Timeout-Summe aller Anbieter). Neu im Snapshot: agent.provider_dead
mit Restminuten - der DeepSeek-Ausfall stand vorher nur im Log und blieb
deshalb 18 h unbemerkt. 9 Szenarien getestet.

2) Einsatz-Prozentfeld (web, v=137): margin_buffer_pct hatte bisher keine UI.
Neues Feld "Einsatz %" neben "Einsatz EUR", POST /api/marginpct ->
engine.set_margin_pct -> config.set_margin_buffer, Snapshot margin_pct,
neustart-fest. Der feste EUR-Betrag hat Vorrang; das Prozentfeld wird dann
ausgegraut, damit nicht unklar bleibt was gilt. Auf 1-99 % geklemmt.
Ende-zu-Ende getestet (50, 150->99, 0 und -5 abgelehnt, 95, persistiert).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-05 08:38:17 +02:00
co-authored by Claude Opus 5
parent 7e9b0196a5
commit cc4f1a0863
7 changed files with 244 additions and 26 deletions
+31 -2
View File
@@ -22,6 +22,7 @@ import MetaTrader5 as mt5
from core.config import (
HISTORY_DB_FILE, MAGIC, load_config, set_margin_buffer, set_risk_per_trade,
get_margin_buffer as _cfg_margin_buffer,
)
# ⚠ `parse_zones` wurde am 2026-07-31 hier entfernt (letzter Nutzer war `_sr_levels`,
# s. dort): die `[zones]`-Config stammte aus einem ~10 $ tieferen Kursregime und
@@ -1845,10 +1846,15 @@ 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 "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)
self._margin_pct = round(get_margin_buffer() * 100.0, 1)
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={'auto' if not getattr(self, '_manual_margin', 0) else f'{self._manual_margin:.2f}'}"
f" · Einsatz-%={getattr(self, '_margin_pct', 0):.0f}")
except FileNotFoundError:
pass
except Exception as e:
@@ -1863,7 +1869,8 @@ class TradingEngine:
"auto_signal": self._auto_signal,
"auto_sr_close": self._auto_sr_close,
"sr_close_min_gain": self._sr_close_min_gain,
"manual_margin": getattr(self, "_manual_margin", 0.0)}, f)
"manual_margin": getattr(self, "_manual_margin", 0.0),
"margin_pct": getattr(self, "_margin_pct", 0.0)}, f)
os.replace(tmp, self._runtime_state_path)
except Exception as e:
log.warning(f"runtime_state speichern: {e}")
@@ -2781,6 +2788,25 @@ class TradingEngine:
self._save_runtime_state()
return v
def set_margin_pct(self, pct: float) -> float:
"""Einsatz als PROZENT der freien Margin (User-Wunsch 2026-08-05).
Das ist die zweite Hälfte des Einsatz-Felds: `set_manual_margin` gibt einen
FESTEN Betrag vor, das hier den ANTEIL. Beides existierte bisher nur in der
`ini` (`margin_buffer_pct`, live 95) — die Prozent-Schraube hatte keine UI.
⚠ VORRANG: ein fester Betrag (`manual_margin > 0`) schlägt den Prozentsatz —
`calc_lots` prüft ihn zuerst. Der Prozentsatz bleibt dabei die Obergrenze
(freie Margin × pct), ein zu hoher Fixbetrag wird darauf gedeckelt.
⚠ Wirkt auf ALLE neuen Positionen, auch die autonomen. Neustart-fest.
⚠ `set_margin_buffer` klemmt auf 199 % — 100 % wäre ohnehin unhaltbar
(kein Puffer für Spread/Swap → Margin-Call beim Öffnen)."""
from core.config import set_margin_buffer as _set, get_margin_buffer
_set(pct)
self._margin_pct = round(get_margin_buffer() * 100.0, 1)
self._save_runtime_state()
return self._margin_pct
def _manual_stats_cached(self) -> dict | None:
"""Kennzahlen der manuellen Trades für die Karte „Manuelle Trades"
(2026-08-04). Gleiches Cache-Muster wie `_alignment_cached` — die
@@ -3342,6 +3368,9 @@ class TradingEngine:
"manual_stats": self._manual_stats_cached(),
# Manuell vorgegebene Einsatz-Margin (0 = automatisch, s. set_manual_margin)
"manual_margin": getattr(self, "_manual_margin", 0.0),
# Einsatz als % der freien Margin (`margin_buffer_pct`). Greift, solange
# kein fester Betrag gesetzt ist — der hat Vorrang (s. set_margin_pct).
"margin_pct": round(_cfg_margin_buffer() * 100.0, 1),
# Live-Handelskosten relativ zur Vola (2026-07-24): Spread/ATR(M5).
# Gemessen (`backtest_realcosts.py`): Ø 0,265×ATR, Nacht 0,320,50 =
# Kostenfalle, 1517 Uhr ~0,13 = günstig. Reine Anzeige (Chip) — die