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:
Axel Hocks
2026-08-04 17:31:58 +02:00
co-authored by Claude Opus 5
parent 33b6b8f629
commit 6af4c868bc
11 changed files with 300 additions and 7 deletions
+22 -2
View File
@@ -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,320,50 =
# Kostenfalle, 1517 Uhr ~0,13 = günstig. Reine Anzeige (Chip) — die