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:
Axel Hocks
2026-08-19 19:06:25 +02:00
co-authored by Claude Opus 5
parent 8e7223f6ce
commit 0a465f9258
4 changed files with 144 additions and 3 deletions
+64 -1
View File
@@ -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
err = fn(sym, _atr_tf)
# ⚠⚠ 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,320,50 =
# Kostenfalle, 1517 Uhr ~0,13 = günstig. Reine Anzeige (Chip) — die