Mt5pro-bot2/python/risk/manager.py

66 lines
2.9 KiB
Python

import math
from datetime import datetime, timezone
from utils.logger import get_logger
log = get_logger()
class RiskManager:
def __init__(self, cfg: dict):
self.cfg = cfg
prop = cfg.get("prop_mode", {})
self.prop = prop.get("enabled", False)
self.initial = prop.get("initial_balance")
self.daily_pct = prop["daily_loss_pct"] if self.prop else cfg["daily_loss_pct"]
self.dd_pct = prop["max_drawdown_pct"] if self.prop else cfg["max_drawdown_pct"]
self.day = None
self.day_start_equity = None
self.peak = None
self.halted_reason = None
def update(self, balance: float, equity: float):
today = datetime.now(timezone.utc).date()
if self.day != today:
self.day, self.day_start_equity, self.halted_reason = today, max(balance, equity), None
log.info(f"Nouveau jour: equity de référence {self.day_start_equity:.2f}")
self.peak = max(self.peak or equity, equity)
def check(self, equity: float):
"""Retourne (ok, raison)."""
daily_loss = (self.day_start_equity - equity) / self.day_start_equity * 100
if daily_loss >= self.daily_pct:
return False, f"Perte journalière {daily_loss:.2f}% >= {self.daily_pct}%"
ref = self.initial if (self.prop and self.initial) else self.peak
dd = (ref - equity) / ref * 100
if dd >= self.dd_pct:
return False, f"Drawdown {dd:.2f}% >= {self.dd_pct}%"
return True, ""
def lot_size(self, equity: float, sl_distance: float, info) -> float:
risk_money = equity * self.cfg["risk_per_trade_pct"] / 100
loss_per_lot = sl_distance / info.trade_tick_size * info.trade_tick_value
if loss_per_lot <= 0:
return 0.0
lots = risk_money / loss_per_lot
step = info.volume_step
lots = math.floor(lots / step) * step
return round(max(info.volume_min, min(lots, info.volume_max)), 2)
def can_open(self, positions_all, positions_symbol) -> bool:
return (len(positions_all) < self.cfg["max_open_trades"]
and len(positions_symbol) < self.cfg["max_per_symbol"])
def new_sl(self, pos, atr_value: float, price: float):
"""Break-even puis trailing. Retourne un nouveau SL ou None."""
buy = pos.type == 0
profit = (price - pos.price_open) if buy else (pos.price_open - price)
cands = []
if profit >= self.cfg["breakeven_atr"] * atr_value:
cands.append(pos.price_open)
if profit >= self.cfg["trail_atr"] * atr_value:
cands.append(price - self.cfg["trail_atr"] * atr_value if buy else price + self.cfg["trail_atr"] * atr_value)
if not cands:
return None
cand = max(cands) if buy else min(cands)
cur = pos.sl
better = cand > cur if buy else (cur == 0 or cand < cur)
return cand if better else None