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
+54
View File
@@ -908,6 +908,24 @@ function render(d) {
mgi.value = mm > 0 ? String(mm) : "";
mgi.classList.toggle("on", mm > 0);
}
// Einsatz in Prozent der freien Margin. Ausgegraut, sobald links ein fester
// Betrag steht — der hat Vorrang, und ohne den Hinweis wäre unklar, was gilt.
const mpi = $("marginpct-input");
if (mpi && document.activeElement !== mpi) {
const mp = d.margin_pct || 0;
mpi.value = mp > 0 ? String(mp) : "";
const fest = (d.manual_margin || 0) > 0;
mpi.classList.toggle("on", mp > 0 && !fest);
const wrap = $("marginpct-wrap");
if (wrap) {
wrap.classList.toggle("muted", fest);
wrap.title = fest
? `Ohne Wirkung: links steht ein fester Einsatz (${fmt(d.manual_margin, 2)} €). `
+ `Der Prozentsatz (${mp} %) bleibt nur die Obergrenze.`
: "Einsatz als Prozent der freien Margin (199). "
+ "Greift, solange links kein fester Betrag steht.";
}
}
// Einblendung: der Bot hat am S/R automatisch geschlossen (Zähler stieg)
const src = d.sr_close_count;
if (src != null) {
@@ -1307,6 +1325,42 @@ async function setManualMargin() {
});
})();
// Einsatz in PROZENT der freien Margin (User-Wunsch 2026-08-05) — das Gegenstück
// zum festen Betrag oben. Steuert `margin_buffer_pct`, das bisher nur in der ini
// stand. ⚠ Ein gesetzter fester Betrag hat Vorrang; darauf weist der Toast hin.
async function setMarginPct() {
const el = $("marginpct-input"); if (!el) return;
const v = parseFloat(el.value);
if (isNaN(v) || v < 1 || v > 99) {
toast("Bitte 199 % eingeben (100 % ließe keinen Puffer für Spread/Swap)", false);
return;
}
const tok = await ensureToken();
if (tok === null) return;
el.blur();
try {
const r = await fetch("api/marginpct", { method: "POST",
headers: { "X-Auth-Token": tok, "Content-Type": "application/json" },
body: JSON.stringify({ value: v }) });
if (r.status === 401) { clearToken(); throw new Error("401"); }
const j = await r.json();
if (j.ok === false) { toast(j.error || "abgelehnt", false); return; }
const fest = parseFloat(($("margin-input") || {}).value || "0") > 0;
toast(`Einsatz: ${fmt(j.value ?? v, 0)} % der freien Margin`
+ (fest ? " — greift erst, wenn der feste Betrag links leer ist" : ""), true);
} catch (e) {
toast(e.message === "401" ? "Token abgelaufen — neu eingeben" : "Netzwerkfehler", false);
}
}
(() => {
const el = $("marginpct-input");
if (!el) return; // null-sicher (Cache-Regel)
el.addEventListener("change", setMarginPct);
el.addEventListener("keydown", e => {
if (e.key === "Enter") { e.preventDefault(); e.target.blur(); }
});
})();
// Notfall-Stop: manueller Verlust-Wert, bei dem der Server sofort schließt.
// Wieder eingeblendet 2026-07-22 (Backend/Persistenz waren die ganze Zeit aktiv).
async function setEmg() {
+11 -4
View File
@@ -6,7 +6,7 @@
<meta name="theme-color" content="#0d1117">
<title>Oil · MT5</title>
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" href="style.css?v=136">
<link rel="stylesheet" href="style.css?v=137">
</head>
<body>
<!-- Modul-Menü (ganz oben) -->
@@ -239,10 +239,17 @@
(margin_buffer_pct % der freien Margin). ⚠ BEWUSST hier und nicht in der
Trade-Leiste: die ist nur bei OFFENER Position sichtbar, der Einsatz muss
aber VOR dem Öffnen einstellbar sein. Wirkt auf ALLE neuen Positionen. -->
<span class="mg-field" title="Feste Einsatz-Margin in € — leer/0 = automatisch (95 % der freien Margin). Gilt auch für autonome Trades.">
<label for="margin-input">Einsatz</label>
<span class="mg-field" title="Feste Einsatz-Margin in € — leer/0 = automatisch (dann gilt der Prozentsatz rechts). Gilt auch für autonome Trades.">
<label for="margin-input">Einsatz</label>
<input id="margin-input" type="number" inputmode="decimal" step="10" min="0" placeholder="auto">
</span>
<!-- Prozent-Gegenstück (User 2026-08-05). Steuert `margin_buffer_pct`, das
bisher nur in der ini stand. ⚠ Der feste €-Betrag hat VORRANG — steht er,
wird dieses Feld ausgegraut, damit nicht unklar bleibt, was gilt. -->
<span class="mg-field" id="marginpct-wrap" title="Einsatz als Prozent der freien Margin (199). Greift, solange links kein fester Betrag steht.">
<label for="marginpct-input">Einsatz %</label>
<input id="marginpct-input" type="number" inputmode="decimal" step="5" min="1" max="99" placeholder="95">
</span>
<button class="btn long" data-side="long">▲ LONG</button>
<button class="btn short" data-side="short">▼ SHORT</button>
<button class="btn close" data-side="close">✕ CLOSE</button>
@@ -267,6 +274,6 @@
<div id="toast" class="toast hidden"></div>
<script src="lightweight-charts.standalone.production.js?v=114"></script>
<script src="app.js?v=136"></script>
<script src="app.js?v=137"></script>
</body>
</html>
+4
View File
@@ -528,3 +528,7 @@ main{padding-bottom:74px} /* Platz für die fixe Leiste */
/* aktiv gesetzt = sichtbar hervorgehoben, damit ein fester Einsatz nicht
unbemerkt bestehen bleibt */
.mg-field input.on { border-color: #d29922; color: #d29922; }
/* Prozent-Feld ohne Wirkung, weil links ein FESTER Betrag steht — sichtbar
zurückgenommen, statt zwei scheinbar gleichrangige Felder zu zeigen. */
.mg-field.muted { opacity: .45; }
.mg-field.muted input { border-style: dashed; }