"""Einsatz je Pfad: der explizite `buffer_pct` muss den globalen Wert schlagen. Hintergrund (Gesamtpruefung 2026-08-19). Der erste Entwurf hat den Einsatz je Pfad umgesetzt, indem `engine._open` den Modul-Global `config.MARGIN_BUFFER` temporaer ueberschrieb. Zwei Fehler steckten darin: (1) RACE. Der Global wirkt prozessweit. Zwischen dem Setzen in `_open` und dem `mt5_lock` in `trader._send` liegt ein Fenster von bis zu 15 s (Lock-Timeout). Eine gleichzeitige MANUELLE Order haette darin mit dem Prozentsatz des AUTONOMEN Pfads gesized. (2) WIRKUNGSLOS FUER BRK. `_manage_squeeze_pending` legt ruhende Stop-Orders ueber `trader.place_stop` -> `calc_lots`, also NICHT ueber `_open`. Da gemessen 7 von 8 BRK-Trades per ruhender Order fuellen, griff das Feld genau dort nicht, wo es hingehoert. Beides ist behoben, indem `calc_lots` einen expliziten Parameter bekommt. Diese Tests halten das fest -- ohne sie faellt ein Rueckbau auf die Global-Mutation nicht auf. ⚠ `calc_lots` braucht MT5. Statt es zu stubben wird hier die reine RECHENREGEL geprueft, die der Parameter steuert -- das ist der Teil, der kaputtgehen kann. """ import core.config as C from core.mt5_utils import calc_lots def _buf(buffer_pct): """Die Klemm-/Auswahlregel aus `calc_lots`, isoliert nachgebildet. ⚠ Bewusst KEINE Kopie der Formel: geprueft wird, dass der Parameter den Global schlaegt und dieselben Grenzen gelten (1-99 %). """ from core.config import get_margin_buffer return (max(0.01, min(0.99, buffer_pct / 100.0)) if buffer_pct and buffer_pct > 0 else get_margin_buffer()) def test_parameter_schlaegt_global(): C.set_margin_buffer(40) assert abs(_buf(None) - 0.40) < 1e-9 # kein Parameter -> global assert abs(_buf(0) - 0.40) < 1e-9 # 0 heisst ausdruecklich "global" assert abs(_buf(50) - 0.50) < 1e-9 # Parameter gewinnt assert abs(_buf(25) - 0.25) < 1e-9 def test_global_bleibt_unveraendert(): """Der Kern des Race-Fixes: ein Aufruf mit Parameter darf den PROZESSWEITEN Wert nicht anfassen -- sonst sizen parallele manuelle Orders falsch.""" C.set_margin_buffer(40) vorher = C.get_margin_buffer() _buf(90) assert C.get_margin_buffer() == vorher, "der Global wurde veraendert" def test_grenzen_geklemmt(): C.set_margin_buffer(40) assert abs(_buf(150) - 0.99) < 1e-9 # 100 % liesse keinen Puffer assert abs(_buf(-5) - 0.40) < 1e-9 # negativ -> global, nicht 0 def test_signatur_nimmt_buffer_pct(): """Haelt die Verdrahtung fest: `calc_lots` MUSS den Parameter annehmen. Ohne diesen Test faellt es nicht auf, wenn jemand die Signatur zurueckbaut -- die Aufrufer wuerden dann mit TypeError sterben, und zwar erst beim ERSTEN autonomen Trade (also im `except`-Zweig, still). """ import inspect p = inspect.signature(calc_lots).parameters assert "buffer_pct" in p, "calc_lots hat den Parameter verloren" assert p["buffer_pct"].default is None, "Default muss None (= global) sein" def test_pfade_reichen_durch(): """`place_stop` und `open_long/short` muessen den Parameter weiterreichen. ⚠ Das ist der Fund (2): BRK fuellt ueberwiegend per RUHENDER Order. Fehlt der Parameter an `place_stop`, ist das Feld fuer BRK wirkungslos -- und das faellt im Betrieb nur auf, wenn jemand die Lots nachrechnet. """ import inspect import core.trader as T for name in ("open_long", "open_short", "place_stop", "_send", "_send_locked"): p = inspect.signature(getattr(T.TradeManager, name)).parameters assert "buffer_pct" in p, f"{name} reicht buffer_pct nicht durch" def test_calc_lots_benutzt_den_parameter_wirklich(): """Schliesst die Luecke, die die Mutationsprobe aufgedeckt hat. ⚠⚠ Die Tests darueber pruefen eine NACHGEBILDETE Regel (`_buf`) -- damit faellt es NICHT auf, wenn `calc_lots` den Parameter zwar entgegennimmt, ihn aber ignoriert und wieder den Global liest. Genau diese Mutation lief bei der ersten Probe gruen durch. Das ist dieselbe Nachbau-Falle, die im Projekt schon dreimal zugeschlagen hat (`sim_run` in >=6 Skripten, der nachgebaute Level-Cluster, das fuenfte Exit-Modell) -- hier in einem TEST. `calc_lots` braucht MT5 und darf in der Suite nicht laufen (conftest-Regel), deshalb wird strukturell geprueft: der Parametername MUSS im Rumpf der Funktion vorkommen. Das ist schwaecher als ein Verhaltenstest, faengt aber genau den Rueckbau, um den es geht. """ import ast import inspect import core.mt5_utils as M baum = ast.parse(inspect.getsource(M.calc_lots)) namen = {n.id for n in ast.walk(baum) if isinstance(n, ast.Name)} assert "buffer_pct" in namen, ( "calc_lots nimmt buffer_pct entgegen, benutzt es aber nicht -- " "der Einsatz je Pfad waere wirkungslos")