Einsatz-Prozentsatz je autonomem Pfad (BRK / M15) + Dashboard-Feld
User: "Baue ein Eingabefeld im Dashboard ein in welchem ich das Margin Verhaeltnis von M15 / BRK manuell setzen kann". ⚠⚠ EHRLICHE EINORDNUNG, weil die Umsetzung NICHT ist, wonach es klingt: eine GLEICHZEITIGE Aufteilung (z.B. 40 % manuell / 50 % reserviert fuer BRK) ist heute nicht baubar. Der Bot haelt EINE Position - _check_auto_squeeze feuert "nur FLAT", und Trailing, Notfall-Stop, S/R-Close, Time-Stop und Breaker verwalten je EINE Position (42 Einzel-Positions-Annahmen in engine.py, 25 in trader.py). Mit zwei Positionen auf demselben Symbol haengt der GESAMTE Schutz-Stack an einer davon, die andere liefe ungeschuetzt. Das ist kein Aufwands-, sondern ein Sicherheitsargument. GEBAUT ist deshalb das, was heute wirkt und dieselbe Frage beantwortet: je Pfad ein eigener Einsatz-Prozentsatz. 0 = globaler Wert (bisheriges Verhalten). Quellen-abhaengig verdrahtet wie die SL-Zeitebene seit 05.08.: _open() setzt config.MARGIN_BUFFER fuer die Dauer des einen Aufrufs und stellt ihn im finally-Zweig zurueck. Manuelle Trades bleiben beim globalen Wert (der Mensch ist die gemessen bessere Population, +1,56 gegen -4,71 EUR/Lot). ⚠ Direkt gesetzt statt ueber set_margin_buffer(): das loggt je Trade eine Zeile UND wuerde den globalen Wert dauerhaft ueberschreiben. ⚠⚠ ZWEI ECHTE FEHLER VOM LINTER GEFANGEN, die py_compile durchgewinkt hat: "config" war in engine.py gar nicht importiert (nur Einzelnamen via "from core.config import ..."), und in server.py hiess die Engine "eng" statt "app.state.engine". Beide sassen in Pfaden, die erst beim ERSTEN autonomen Trade bzw. beim ersten Klick gefeuert haetten - also genau die Klasse, fuer die ruff F821 am 07.08. eingebaut wurde. ⚠ Frontend: $streng statt $, weil LEER hier ausdruecklich 0 = "global" bedeutet und nicht "unveraendert"; mit dem Auffang-Element waere .value undefined -> NaN -> null. Der Render ueberschreibt das Feld nicht, solange es den Fokus hat (sonst frisst der 1-s-Snapshot die laufende Eingabe). ⚠ Beim Committen selbst zweimal danebengegriffen, beides dokumentiert weil es Fehlerklassen sind: (a) Backticks in einer Bash-Commit-Nachricht werden als Kommando-Substitution ausgefuehrt und loeschen die eingeklammerten Namen aus dem Text; (b) ein Bash-Pfad (/c/Users/...) an Python uebergeben schlaegt fehl, und "git commit --amend -F <fehlende Datei>" nahm daraufhin eine STALE COMMIT_EDITMSG - die Nachricht eines fremden Commits. Der Code war davon nie betroffen (Arbeitsbaum sauber, Remote synchron), nur die Beschriftung. Ende-zu-Ende geprueft: 50/25 gesetzt -> Snapshot -> persistiert; 150 abgelehnt; 0/0 zurueck. Deploy ueber tools/deploy.py --feld margin_brk, alle 5 Schritte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8e7223f6ce
commit
0a465f9258
@@ -607,6 +607,10 @@ class TradingEngine:
|
||||
self._cb_remember = self._cb_limit_pct or 8.0
|
||||
except (TypeError, ValueError):
|
||||
self._cb_limit_pct = 0.0
|
||||
# Einsatz je autonomem Pfad (0 = globaler `margin_buffer_pct`).
|
||||
# Wird von `runtime_state.json` ueberschrieben, falls dort gesetzt.
|
||||
self._margin_brk = 0.0
|
||||
self._margin_m15 = 0.0
|
||||
self._circuit_tripped_date = None # Berlin-Datum, an dem der Breaker griff
|
||||
self._cb_realized = (0.0, 0.0) # (ts, realisierter Tages-P&L) gecacht ~30 s
|
||||
# Gewinn-mitnehmen (Take-Profit) — symmetrisch zum Notfall-Stop: schließt
|
||||
@@ -2284,6 +2288,10 @@ class TradingEngine:
|
||||
self._manual_margin = _set(st["manual_margin"] or 0)
|
||||
if "auto_m15" in st:
|
||||
self._auto_m15 = bool(st["auto_m15"])
|
||||
if "margin_brk" in st:
|
||||
self._margin_brk = max(0.0, min(99.0, float(st["margin_brk"] or 0)))
|
||||
if "margin_m15" in st:
|
||||
self._margin_m15 = max(0.0, min(99.0, float(st["margin_m15"] or 0)))
|
||||
if "cb_remember" in st:
|
||||
self._cb_remember = float(st["cb_remember"] or 8.0)
|
||||
if "cb_limit_pct" in st:
|
||||
@@ -2323,6 +2331,8 @@ class TradingEngine:
|
||||
# 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),
|
||||
"margin_brk": getattr(self, "_margin_brk", 0.0),
|
||||
"margin_m15": getattr(self, "_margin_m15", 0.0),
|
||||
"auto_m15": getattr(self, "_auto_m15", False),
|
||||
# Kegel-ACI-Skalen (2026-08-08): ohne Persistenz waere
|
||||
# die muehsam erlernte Kalibrierung nach jedem Neustart
|
||||
@@ -2599,7 +2609,31 @@ class TradingEngine:
|
||||
# bewusst KEINE globale Umstellung. Strukturell begruendet: Breakout- und
|
||||
# Pullback-Einstiege haben verschiedene Nachlauf-Dynamik.
|
||||
_atr_tf = mt5.TIMEFRAME_M5 if source == "auto_squeeze" else None
|
||||
# ⚠⚠ EINSATZ JE PFAD (User-Wunsch 2026-08-19). Quellen-abhaengig
|
||||
# wie die SL-Zeitebene direkt darueber, und aus demselben Grund: BRK und
|
||||
# M15 sind verschiedene Populationen mit verschieden gemessener Guete
|
||||
# (autonom -4,71 EUR/Lot gegen Mensch +1,56). 0 = globaler Wert.
|
||||
# ⚠ Das ist KEINE gleichzeitige Aufteilung der Margin — der Bot haelt
|
||||
# EINE Position. Es legt fest, wie gross die Position wird, WENN der
|
||||
# jeweilige Pfad eroeffnet. Eine echte Reservierung braucht den
|
||||
# ticket-basierten Umbau (42 Einzel-Positions-Annahmen).
|
||||
_mg = {"auto_squeeze": getattr(self, "_margin_brk", 0.0),
|
||||
"auto_m15": getattr(self, "_margin_m15", 0.0)}.get(source, 0.0)
|
||||
_mg_alt = None
|
||||
if _mg > 0:
|
||||
import core.config as _cfgmod
|
||||
_mg_alt = _cfgmod.MARGIN_BUFFER
|
||||
# ⚠ direkt setzen statt ueber set_margin_buffer(): das loggt bei
|
||||
# JEDEM Trade eine Zeile und wuerde den globalen Wert ueberschreiben.
|
||||
_cfgmod.MARGIN_BUFFER = max(0.01, min(0.99, _mg / 100.0))
|
||||
log.info(f"[ORDER] Einsatz fuer {source}: {_mg:.0f}% "
|
||||
f"(global {_mg_alt*100:.0f}%)")
|
||||
try:
|
||||
err = fn(sym, _atr_tf)
|
||||
finally:
|
||||
if _mg_alt is not None:
|
||||
import core.config as _cfgmod2
|
||||
_cfgmod2.MARGIN_BUFFER = _mg_alt
|
||||
if err:
|
||||
return err
|
||||
# Vom BOT eröffnete Trades markieren → nur für die greift die 15-Min-Regel
|
||||
@@ -3394,6 +3428,32 @@ class TradingEngine:
|
||||
self._save_runtime_state()
|
||||
return v
|
||||
|
||||
def set_auto_margin(self, brk: float, m15: float) -> dict:
|
||||
"""Einsatz-Prozentsatz JE AUTONOMEM PFAD (User-Wunsch 2026-08-19).
|
||||
|
||||
0 = der globale Wert (`margin_buffer_pct`, live 40) gilt — also das
|
||||
bisherige Verhalten. > 0 = dieser Pfad eroeffnet mit genau diesem Anteil
|
||||
der freien Margin.
|
||||
|
||||
⚠⚠ DAS IST KEINE RESERVIERUNG. Der Bot haelt EINE Position; es kann
|
||||
also nie BRK und M15 gleichzeitig geben. Das Feld steuert die GROESSE der
|
||||
jeweiligen Position, nicht einen parallel vorgehaltenen Topf. Eine echte
|
||||
Reservierung verlangt den ticket-basierten Umbau (42 Einzel-Positions-
|
||||
Annahmen in `engine.py`, 25 in `trader.py`) — bis dahin wuerde der
|
||||
Schutz-Stack (Trailing, Notfall-Stop, S/R-Close, Time-Stop) an der
|
||||
FALSCHEN der beiden Positionen haengen.
|
||||
|
||||
⚠ Manuelle Trades bleiben unberuehrt — fuer die gilt weiter der globale
|
||||
Prozentsatz. Das ist Absicht: der Mensch ist die gemessen bessere
|
||||
Population (+1,56 gegen -4,71 EUR/Lot), seine Groesse gehoert ihm.
|
||||
Neustart-fest ueber `runtime_state.json`."""
|
||||
self._margin_brk = max(0.0, min(99.0, float(brk or 0)))
|
||||
self._margin_m15 = max(0.0, min(99.0, float(m15 or 0)))
|
||||
log.info(f"Einsatz je Pfad: BRK {self._margin_brk:.0f}% · "
|
||||
f"M15 {self._margin_m15:.0f}% (0 = global)")
|
||||
self._save_runtime_state()
|
||||
return {"brk": self._margin_brk, "m15": self._margin_m15}
|
||||
|
||||
def set_margin_pct(self, pct: float) -> float:
|
||||
"""Einsatz als PROZENT der freien Margin (User-Wunsch 2026-08-05).
|
||||
|
||||
@@ -4488,6 +4548,9 @@ class TradingEngine:
|
||||
# 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),
|
||||
# Einsatz je autonomem Pfad; 0 = der globale `margin_pct` gilt.
|
||||
"margin_brk": getattr(self, "_margin_brk", 0.0),
|
||||
"margin_m15": getattr(self, "_margin_m15", 0.0),
|
||||
# Live-Handelskosten relativ zur Vola (2026-07-24): Spread/ATR(M5).
|
||||
# Gemessen (`backtest_realcosts.py`): Ø 0,265×ATR, Nacht 0,32–0,50 =
|
||||
# Kostenfalle, 15–17 Uhr ~0,13 = günstig. Reine Anzeige (Chip) — die
|
||||
|
||||
@@ -412,6 +412,28 @@ async def set_manual_margin(body: dict,
|
||||
return {"ok": True, "value": val}
|
||||
|
||||
|
||||
@app.post("/api/automargin")
|
||||
async def set_auto_margin(body: dict,
|
||||
x_auth_token: str | None = Header(default=None)):
|
||||
"""Einsatz-Prozentsatz JE AUTONOMEM PFAD (User-Wunsch 2026-08-19).
|
||||
|
||||
`{"brk": 50, "m15": 25}` — 0 heisst "globaler Wert" (bisheriges Verhalten).
|
||||
⚠ KEINE gleichzeitige Reservierung: der Bot haelt EINE Position, es kann
|
||||
also nie BRK und M15 nebeneinander geben. Das Feld steuert die GROESSE der
|
||||
jeweiligen Position. Manuelle Trades bleiben beim globalen Prozentsatz."""
|
||||
_require_auth(x_auth_token)
|
||||
try:
|
||||
brk = float(body.get("brk") or 0)
|
||||
m15 = float(body.get("m15") or 0)
|
||||
except (TypeError, ValueError):
|
||||
return {"ok": False, "error": "ungueltiger Wert"}
|
||||
if brk < 0 or m15 < 0 or brk > 99 or m15 > 99:
|
||||
return {"ok": False, "error": "erlaubt sind 0 (=global) bis 99"}
|
||||
r = app.state.engine.set_auto_margin(brk, m15)
|
||||
log.info(f"[WEB] Einsatz je Pfad: BRK {r['brk']:.0f}% · M15 {r['m15']:.0f}%")
|
||||
return {"ok": True, **r}
|
||||
|
||||
|
||||
@app.post("/api/marginpct")
|
||||
async def set_margin_pct(body: dict,
|
||||
x_auth_token: str | None = Header(default=None)):
|
||||
|
||||
+45
@@ -169,6 +169,13 @@ function beepRecommendation(sig, isNew) {
|
||||
function renderBrk(d) {
|
||||
const box = $streng("card-brk");
|
||||
if (!box) return;
|
||||
// Einsatz je Pfad einspielen. ⚠ NICHT, solange das Feld den Fokus hat --
|
||||
// sonst ueberschreibt der 1-s-Snapshot die laufende Eingabe (dieselbe Falle
|
||||
// wie beim Mindestgewinn-Feld).
|
||||
[["am-brk", d.margin_brk], ["am-m15", d.margin_m15]].forEach(([id, v]) => {
|
||||
const el = $streng(id);
|
||||
if (el && document.activeElement !== el) el.value = (v > 0) ? v : "";
|
||||
});
|
||||
const b5 = d.squeeze_b5;
|
||||
if (!b5) { box.classList.add("hidden"); return; }
|
||||
box.classList.remove("hidden");
|
||||
@@ -2012,3 +2019,41 @@ document.addEventListener("visibilitychange", () => {
|
||||
});
|
||||
window.addEventListener("pageshow", _wakeRefresh);
|
||||
window.addEventListener("focus", _wakeRefresh);
|
||||
|
||||
// --- Einsatz je autonomem Pfad (User-Wunsch 2026-08-19) --------------------
|
||||
// ⚠ LEER heisst hier ausdruecklich 0 = "globaler Wert", NICHT "unveraendert".
|
||||
// Deshalb `$streng` und eine explizite NaN-Behandlung: mit dem Auffang-`$`
|
||||
// waere `.value` undefined -> NaN -> JSON.stringify macht daraus null, und
|
||||
// der Server saehe einen anderen Wert als der Nutzer eingegeben hat.
|
||||
async function setAutoMargin() {
|
||||
const eb = $streng("am-brk"), em = $streng("am-m15");
|
||||
if (!eb || !em) { toast("Eingabefelder fehlen — Seite neu laden", false); return; }
|
||||
const brk = eb.value.trim() === "" ? 0 : parseFloat(eb.value);
|
||||
const m15 = em.value.trim() === "" ? 0 : parseFloat(em.value);
|
||||
if (isNaN(brk) || isNaN(m15) || brk < 0 || m15 < 0 || brk > 99 || m15 > 99) {
|
||||
toast("Erlaubt sind 0 (= global) bis 99", false); return;
|
||||
}
|
||||
const tok = await ensureToken();
|
||||
if (tok === null) return;
|
||||
eb.blur(); em.blur();
|
||||
try {
|
||||
const r = await fetch("api/automargin", { method: "POST",
|
||||
headers: { "X-Auth-Token": tok, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ brk: brk, m15: m15 }) });
|
||||
if (r.status === 401) { clearToken(); throw new Error("401"); }
|
||||
const j = await r.json();
|
||||
if (!j.ok) { toast(j.error || "abgelehnt", false); return; }
|
||||
toast(`Einsatz: BRK ${brk || "global"} · M15 ${m15 || "global"}`, true);
|
||||
} catch (e) {
|
||||
toast(e.message === "401" ? "Token abgelaufen — neu eingeben" : "Netzwerkfehler", false);
|
||||
}
|
||||
}
|
||||
["am-brk", "am-m15"].forEach(id => {
|
||||
const el = $streng(id);
|
||||
if (!el) return;
|
||||
el.addEventListener("change", setAutoMargin);
|
||||
// Enter NUR blurren → `change` feuert dann genau EINMAL (Fix-Muster 22.07.).
|
||||
el.addEventListener("keydown", e => {
|
||||
if (e.key === "Enter") { e.preventDefault(); e.target.blur(); }
|
||||
});
|
||||
});
|
||||
|
||||
+13
-2
@@ -12,7 +12,7 @@
|
||||
⚠ LOKAL eingebunden, kein CDN: die PWA laeuft ueber WireGuard
|
||||
und muss offline funktionieren. -->
|
||||
<link rel="stylesheet" href="pico.min.css?v=1">
|
||||
<link rel="stylesheet" href="style.css?v=181">
|
||||
<link rel="stylesheet" href="style.css?v=182">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modul-Menü (ganz oben) -->
|
||||
@@ -170,6 +170,17 @@
|
||||
<div class="kv"><span>Squeeze jetzt</span><b id="brk-squeeze">—</b></div>
|
||||
<div class="kv"><span>Liegende Orders</span><b id="brk-pending">—</b></div>
|
||||
<div class="kv" title="B5 = die VORAB fixierte Abbruchregel: nach 20 Trades ab 01.08. muss Verhaeltnis (OeGewinn/OeVerlust) >= 1,0 UND PF >= 1 sein."><span>B5-Regel</span><b id="brk-b5">—</b></div>
|
||||
<div class="kv" title="Einsatz JE PFAD in Prozent der freien Margin. 0 = der globale Wert gilt (Feld unten bzw. margin_buffer_pct). WICHTIG: das ist KEINE gleichzeitige Reservierung - der Bot haelt EINE Position, es kann nie BRK und M15 nebeneinander geben. Das Feld steuert die GROESSE der jeweiligen Position. Manuelle Trades bleiben beim globalen Wert.">
|
||||
<span>Einsatz je Pfad</span>
|
||||
<b>
|
||||
<input id="am-brk" type="number" min="0" max="99" step="1" placeholder="BRK"
|
||||
style="width:56px" title="Einsatz % fuer Breakout-Trades (0 = global)">
|
||||
<span style="opacity:.6">/</span>
|
||||
<input id="am-m15" type="number" min="0" max="99" step="1" placeholder="M15"
|
||||
style="width:56px" title="Einsatz % fuer M15-Auto-Trades (0 = global)">
|
||||
<span style="opacity:.6">%</span>
|
||||
</b>
|
||||
</div>
|
||||
<div id="brk-note" class="hint"></div>
|
||||
</section>
|
||||
|
||||
@@ -381,6 +392,6 @@
|
||||
</div>
|
||||
<div id="toast" class="toast hidden"></div>
|
||||
|
||||
<script src="app.js?v=181"></script>
|
||||
<script src="app.js?v=182"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user