""" Gestion des positions : sizing, validation, suivi. """ import logging from typing import Optional from app.core.config import settings from app.core.exchange.base import AbstractExchange, AccountInfo, OrderResult from app.core.strategy.base import TradeSignal logger = logging.getLogger(__name__) class TradeManager: def __init__(self, exchange: AbstractExchange) -> None: self._exchange = exchange self._risk_percent = settings.bot_risk_percent self._pip_value = 0.0001 async def execute_signal(self, signal: TradeSignal) -> Optional[OrderResult]: """ Calcule le sizing et place l'ordre sur l'exchange. Retourne None si le signal est invalide ou si le sizing est trop petit. """ account = await self._exchange.get_account_info() risk_pips = abs(signal.entry_price - signal.stop_loss) / self._pip_value if risk_pips < 1: logger.warning("Signal ignoré : risque en pips trop faible (%s)", risk_pips) return None units = self._calculate_units(account, risk_pips, signal) if abs(units) < 1000: logger.warning("Signal ignoré : taille de position trop petite (%s)", units) return None logger.info( "Exécution signal %s %s — entry=%.5f SL=%.5f TP=%.5f units=%d", signal.direction, signal.signal_type, signal.entry_price, signal.stop_loss, signal.take_profit, units, ) return await self._exchange.place_order( instrument=signal.signal_type.split("+")[0] if "+" in signal.signal_type else "EUR_USD", units=units, stop_loss=signal.stop_loss, take_profit=signal.take_profit, ) def _calculate_units( self, account: AccountInfo, risk_pips: float, signal: TradeSignal, ) -> float: """ Calcule la taille de position basée sur le risque défini. Formula : units = (balance * risk%) / (risk_pips * pip_value_per_unit) Pour EUR/USD standard : pip_value_per_unit = $0.0001 """ risk_amount = account.balance * (self._risk_percent / 100) # Pour simplification : 1 pip = 0.0001, valeur pip sur mini-lot = $1 pip_value_per_unit = self._pip_value raw_units = risk_amount / (risk_pips * pip_value_per_unit) # Arrondir à la centaine inférieure units = (int(raw_units) // 100) * 100 return float(units) if signal.direction == "buy" else float(-units)