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