605faf5310
Add cluster audit pipeline, united EA updates, brochure generators, and publication hygiene (gitignore, MT5 path desensitization, pre-upload scan). Remove tracked reports, models, and binary artifacts from the repo. Co-authored-by: Cursor <cursoragent@cursor.com>
54 lines
1.5 KiB
Python
54 lines
1.5 KiB
Python
"""Margin helpers — uses MT5 order_calc_margin for realistic portfolio sizing."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import MetaTrader5 as mt5
|
|
|
|
|
|
def calc_margin(symbol: str, side: str, volume: float, price: float) -> float:
|
|
ot = mt5.ORDER_TYPE_BUY if side == "BUY" else mt5.ORDER_TYPE_SELL
|
|
m = mt5.order_calc_margin(ot, symbol, volume, price)
|
|
return float(m) if m is not None and m > 0 else 0.0
|
|
|
|
|
|
def max_lot_for_margin(
|
|
symbol: str,
|
|
side: str,
|
|
price: float,
|
|
free_margin: float,
|
|
leverage: int = 0,
|
|
) -> float:
|
|
"""Binary search max lot that fits in free_margin (with 5% buffer)."""
|
|
info = mt5.symbol_info(symbol)
|
|
if info is None or free_margin <= 0:
|
|
return 0.0
|
|
vmin = float(info.volume_min)
|
|
vmax = float(info.volume_max)
|
|
step = float(info.volume_step) or vmin
|
|
budget = free_margin * 0.95
|
|
lo, hi = vmin, vmax
|
|
best = 0.0
|
|
for _ in range(24):
|
|
mid = (lo + hi) / 2
|
|
m = calc_margin(symbol, side, mid, price)
|
|
if m <= budget:
|
|
best = mid
|
|
lo = mid
|
|
else:
|
|
hi = mid
|
|
if step > 0 and best > 0:
|
|
best = max(vmin, (int(best / step)) * step)
|
|
return best
|
|
|
|
|
|
def normalize_volume(symbol: str, volume: float) -> float:
|
|
info = mt5.symbol_info(symbol)
|
|
if info is None:
|
|
return volume
|
|
vmin = float(info.volume_min)
|
|
vmax = float(info.volume_max)
|
|
step = float(info.volume_step) or vmin
|
|
if step > 0:
|
|
volume = (int(volume / step)) * step
|
|
return max(vmin, min(vmax, volume))
|