Feste Einsatz-Margin (UI-Feld) + lokale Sprachausgabe via Piper
EINSATZ-FELD (User-Wunsch): Laufzeitwert MANUAL_MARGIN in core/config.py (set/get_manual_margin, Muster wie set_margin_buffer). 0 = automatisch wie bisher (margin_buffer_pct % der freien Margin), > 0 = Position wird auf genau diesen Margin-Einsatz gerechnet. - calc_lots setzt es um und DECKELT auf freie Margin x Buffer; ein zu hoher Wunschwert wird begrenzt und geloggt statt an den Broker durchgereicht. Reicht der Betrag nicht fuers Mindestlot -> 0.0 + Warnung, Trade sauber abgelehnt. - Vorrang vor dem Risiko-Modus in trader._send_locked: eine ausdrueckliche Groessenvorgabe schlaegt die Rechenregel. - Wirkt auf ALLE neuen Positionen, auch die autonomen. - UI-Feld "Einsatz" in der ORDER-Leiste (nicht in der Trade-Leiste: die ist nur bei offener Position sichtbar, der Einsatz muss vorher einstellbar sein), bernstein umrandet solange gesetzt. Enter blurrt nur, change sendet einmal. - POST /api/manualmargin, Snapshot manual_margin, neustart-fest ueber runtime_state.json. Ende-zu-Ende getestet (250 -> Snapshot -> 0 -> persistiert). - Lot-Logik isoliert geprueft: 200 EUR -> 0,58 Lots; 5000 EUR -> gedeckelt. SPRACHAUSGABE: Piper lokal (tools/speak.py). Windows-TTS funktioniert zwar, aber es ist KEINE deutsche Stimme installiert - weder SAPI5 noch OneCore (nur David/Zira/Mark, en-US); deutscher Text kaeme mit englischer Aussprache. Add-WindowsCapability scheitert ohne Adminrechte. Piper gewaehlt: laeuft lokal (passt zum Rest - WireGuard-only, Secrets in der ini, keine Cloud), kostet nichts, aus PowerShell/Python aufrufbar. Stimme thorsten-medium (de_DE), Real-Time-Faktor 0,08 (4,3 s Audio in 0,36 s). Das ZIP entpackt eine Ebene tiefer als erwartet (tools/piper/piper/piper.exe) - speak.py SUCHT Binary und Modell statt Pfade fest zu verdrahten. Der erste Entwurf scheiterte daran; der Fail-safe meldete die Pfade sauber statt stumm zu bleiben. Drei Modi getestet (Argument, Pipe, --wav). tools/piper/ ist gitignored (Binaries + 60-MB-Modell), speak.py versioniert. v=136. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
33b6b8f629
commit
6af4c868bc
@@ -58,6 +58,32 @@ def get_margin_buffer() -> float:
|
||||
return MARGIN_BUFFER
|
||||
|
||||
|
||||
# ── Manuell vorgegebene Einsatz-Margin (User-Wunsch 2026-08-04) ───────────────
|
||||
# 0 = AUS → automatische Größe wie bisher (MARGIN_BUFFER % der freien Margin bzw.
|
||||
# risiko-basiert bei RISK_PER_TRADE > 0). > 0 = die Position wird auf GENAU diesen
|
||||
# Margin-Einsatz in Kontowährung gerechnet.
|
||||
# ⚠ Die freie Margin bleibt IMMER die Obergrenze — ein zu hoher Wunschwert wird
|
||||
# gedeckelt, nicht durchgereicht (sonst Broker-Reject mit kryptischem retcode).
|
||||
MANUAL_MARGIN = 0.0
|
||||
|
||||
|
||||
def set_manual_margin(eur: float) -> float:
|
||||
"""Feste Einsatz-Margin in Kontowährung setzen. 0 = automatisch."""
|
||||
global MANUAL_MARGIN
|
||||
try:
|
||||
MANUAL_MARGIN = max(0.0, float(eur or 0))
|
||||
except (TypeError, ValueError):
|
||||
MANUAL_MARGIN = 0.0
|
||||
log.info(f"Einsatz-Margin: "
|
||||
+ (f"{MANUAL_MARGIN:.2f} (manuell)" if MANUAL_MARGIN > 0
|
||||
else "automatisch"))
|
||||
return MANUAL_MARGIN
|
||||
|
||||
|
||||
def get_manual_margin() -> float:
|
||||
return MANUAL_MARGIN
|
||||
|
||||
|
||||
def set_risk_per_trade(pct: float) -> float:
|
||||
"""Setzt das Risiko pro Trade zur Laufzeit (% der Equity). 0 = aus (Fallback
|
||||
margin-basiert). Sinnvoll 0,5–3 %."""
|
||||
|
||||
+22
-2
@@ -1815,9 +1815,13 @@ class TradingEngine:
|
||||
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)
|
||||
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"MinGewinn={self._sr_close_min_gain:+.2f} · "
|
||||
f"Einsatz={'auto' if not getattr(self, '_manual_margin', 0) else f'{self._manual_margin:.2f}'}")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
@@ -1831,7 +1835,8 @@ class TradingEngine:
|
||||
json.dump({"auto_squeeze": self._auto_squeeze,
|
||||
"auto_signal": self._auto_signal,
|
||||
"auto_sr_close": self._auto_sr_close,
|
||||
"sr_close_min_gain": self._sr_close_min_gain}, f)
|
||||
"sr_close_min_gain": self._sr_close_min_gain,
|
||||
"manual_margin": getattr(self, "_manual_margin", 0.0)}, f)
|
||||
os.replace(tmp, self._runtime_state_path)
|
||||
except Exception as e:
|
||||
log.warning(f"runtime_state speichern: {e}")
|
||||
@@ -2736,6 +2741,19 @@ class TradingEngine:
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
_SNAP_TTL_S = 0.8 # geteilter Snapshot gilt so lange als frisch
|
||||
|
||||
def set_manual_margin(self, eur: float) -> float:
|
||||
"""Feste Einsatz-Margin in Kontowährung (User-Wunsch 2026-08-04).
|
||||
0 = automatisch (wie bisher `margin_buffer_pct` % der freien Margin).
|
||||
|
||||
⚠ Wirkt auf ALLE neuen Positionen — manuelle wie autonome (Squeeze/Signal).
|
||||
Die freie Margin bleibt Obergrenze; `calc_lots` deckelt und loggt das.
|
||||
Neustart-fest über `runtime_state.json`."""
|
||||
from core.config import set_manual_margin as _set
|
||||
v = _set(eur)
|
||||
self._manual_margin = v
|
||||
self._save_runtime_state()
|
||||
return v
|
||||
|
||||
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
|
||||
@@ -3295,6 +3313,8 @@ class TradingEngine:
|
||||
# (2026-08-04). Rein deskriptiv; ~120 s gecacht, damit der heiße
|
||||
# Snapshot-Pfad nicht je Abruf die DB aggregiert.
|
||||
"manual_stats": self._manual_stats_cached(),
|
||||
# Manuell vorgegebene Einsatz-Margin (0 = automatisch, s. set_manual_margin)
|
||||
"manual_margin": getattr(self, "_manual_margin", 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
|
||||
|
||||
+20
-1
@@ -13,7 +13,8 @@ from contextlib import contextmanager
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
from core.config import (
|
||||
get_margin_buffer, SL_TF, SL_LOOKBACK, SL_WINDOW, SL_BUFFER_TICKS,
|
||||
get_margin_buffer, get_manual_margin,
|
||||
SL_TF, SL_LOOKBACK, SL_WINDOW, SL_BUFFER_TICKS,
|
||||
)
|
||||
from core.logger import get_logger
|
||||
|
||||
@@ -76,6 +77,24 @@ def calc_lots(sym: str, price: float, otype: int) -> float:
|
||||
if not mpl or mpl <= 0:
|
||||
return 0.0
|
||||
step = si.volume_step
|
||||
# ── Manuell vorgegebene Einsatz-Margin (User-Wunsch 2026-08-04) ──────────
|
||||
# Ist sie gesetzt, wird die Position auf GENAU diesen Betrag gerechnet statt
|
||||
# auf einen Prozentsatz der freien Margin. Die freie Margin bleibt Obergrenze:
|
||||
# ein zu hoher Wunschwert wird gedeckelt, nicht an den Broker durchgereicht
|
||||
# (sonst Reject mit kryptischem retcode statt sauberer Meldung).
|
||||
_man = get_manual_margin()
|
||||
if _man > 0:
|
||||
_leistbar = acc.margin_free * get_margin_buffer()
|
||||
_ziel = min(_man, _leistbar)
|
||||
lots = round(((_ziel / mpl) // step) * step, 4)
|
||||
if lots < si.volume_min:
|
||||
log.warning(f"Einsatz-Margin {_man:.2f} reicht nicht für das Mindestlot "
|
||||
f"({si.volume_min}) — Margin je Lot {mpl:.2f}")
|
||||
return 0.0
|
||||
if _ziel < _man:
|
||||
log.warning(f"Einsatz-Margin auf {_ziel:.2f} gedeckelt "
|
||||
f"(gewünscht {_man:.2f}, frei×Buffer {_leistbar:.2f})")
|
||||
return min(lots, si.volume_max)
|
||||
lots = round(((acc.margin_free * get_margin_buffer() / mpl) // step) * step, 4)
|
||||
# Margin-Guard: reicht die freie Margin nicht mal fürs Mindestvolumen, 0.0
|
||||
# zurückgeben → Caller meldet sauber „Lot-Fehler" statt Broker-Reject mit
|
||||
|
||||
+6
-1
@@ -14,7 +14,7 @@ import MetaTrader5 as mt5
|
||||
from core.config import (
|
||||
DEVIATION, MAGIC,
|
||||
SL_BUFFER_TICKS, INIT_SL_FALLBACK, INIT_TP_RR,
|
||||
INIT_SL_MIN_ATR, INIT_SL_MAX_ATR, get_risk_per_trade,
|
||||
INIT_SL_MIN_ATR, INIT_SL_MAX_ATR, get_risk_per_trade, get_manual_margin,
|
||||
)
|
||||
from core.mt5_utils import (
|
||||
mt5_lock, get_tick, get_filling, calc_lots, calc_lots_risk,
|
||||
@@ -142,6 +142,11 @@ class TradeManager:
|
||||
sl, _tp, sl_src = self._calc_sl_tp(sym, otype, price)
|
||||
risk = get_risk_per_trade()
|
||||
sl_dist = abs(price - sl) if sl else None
|
||||
# Eine manuell gesetzte Einsatz-Margin hat VORRANG vor dem Risiko-Modus
|
||||
# (2026-08-04): sie ist eine ausdrückliche Größenvorgabe des Users, keine
|
||||
# Rechenregel. `calc_lots` setzt sie um und deckelt auf die freie Margin.
|
||||
if get_manual_margin() > 0:
|
||||
risk = 0.0
|
||||
if risk > 0:
|
||||
# Risiko-Modus: KEIN stiller Fallback auf Margin-Sizing (75 % wäre ein
|
||||
# Vielfaches des gewollten Risikos). Klappt die Risiko-Rechnung nicht
|
||||
|
||||
Reference in New Issue
Block a user