""" Position sizing and trade-permission checks. """ import logging import math from datetime import date from . import config logger = logging.getLogger(__name__) MIN_LOT = 0.01 MAX_LOT = 100.0 def calculate_lot_size( symbol_info: dict, balance: float, entry_price: float, sl_price: float, ) -> float: """Risk `config.RISK_PER_TRADE` fraction of balance on a single trade.""" risk_amount = balance * config.RISK_PER_TRADE tick_value = symbol_info["trade_tick_value"] tick_size = symbol_info["trade_tick_size"] if tick_value == 0 or tick_size == 0: return MIN_LOT pip_size = tick_size * 10 pip_value = tick_value * (pip_size / tick_size) dist_pips = abs(entry_price - sl_price) / pip_size if dist_pips < config.MIN_SL_PIPS: logger.warning("SL too tight for %s: %.1f pips — skipping", symbol_info.get("name"), dist_pips) return 0.0 lot = risk_amount / (pip_value * dist_pips) lot = max(MIN_LOT, min(MAX_LOT, lot)) step = symbol_info.get("volume_step", 0.01) lot = math.floor(lot / step) * step return round(lot, 2) def can_trade(account_info: dict, open_positions: list[dict], state: dict) -> bool: if len(open_positions) >= config.MAX_POSITIONS: logger.debug("Max positions reached (%d)", config.MAX_POSITIONS) return False today = date.today().isoformat() daily_pnl = state.get("daily_pnl", {}).get(today, 0.0) balance = account_info.get("balance", 1.0) if daily_pnl / balance < -config.MAX_DAILY_LOSS: logger.warning("Daily loss limit hit: %.1f%% of balance", -daily_pnl / balance * 100) return False return True