"""
风险管理模块 - 严格控制仓位和风险
"""
import logging
import os
import time
from datetime import datetime, timezone, timedelta
from typing import Dict, List, Optional, Tuple
try:
    from .binance_client import BinanceClient
    from . import config
    from .atr_strategy import ATRStrategy
    from .symbol_policy import resolve_symbol_trading_policy
except ImportError:
    from binance_client import BinanceClient
    import config
    from atr_strategy import ATRStrategy
    from symbol_policy import resolve_symbol_trading_policy

logger = logging.getLogger(__name__)

# 可选：优先使用 User Data Stream 缓存（与 position_manager 一致）
def _get_stream_instance():
    try:
        from .user_data_stream import get_stream_instance
        return get_stream_instance()
    except Exception:
        return None

async def _get_balance_from_cache(client: Optional[BinanceClient] = None):
    """从缓存获取余额（优先 Redis，降级到进程内存）"""
    try:
        from .user_data_stream import get_balance_from_cache
        redis_cache = getattr(client, "redis_cache", None) if client else None
        return await get_balance_from_cache(redis_cache)
    except Exception:
        return None

async def _get_positions_from_cache(client: Optional[BinanceClient] = None):
    """从缓存获取持仓（优先 Redis，降级到进程内存）"""
    try:
        from .user_data_stream import get_positions_from_cache
        redis_cache = getattr(client, "redis_cache", None) if client else None
        min_notional = float(getattr(config, "POSITION_MIN_NOTIONAL_USDT", 1.0) or 1.0)
        return await get_positions_from_cache(min_notional, redis_cache)
    except Exception:
        return None


class RiskManager:
    """风险管理类"""
    
    def __init__(self, client: BinanceClient):
        """
        初始化风险管理器
        
        Args:
            client: 币安客户端
        """
        self.client = client
        # 不保存引用，每次都从 config.TRADING_CONFIG 读取最新配置
        # self.config = config.TRADING_CONFIG  # 移除，避免使用旧配置
        # 初始化ATR策略
        self.atr_strategy = ATRStrategy()
    
    async def check_position_size(self, symbol: str, quantity: float, leverage: Optional[int] = None) -> bool:
        """
        检查单笔仓位大小是否符合要求
        
        Args:
            symbol: 交易对
            quantity: 下单数量
            leverage: 杠杆倍数（用于换算保证金）；若不传则使用配置的基础杠杆
            
        Returns:
            是否通过检查
        """
        try:
            logger.info(f"检查 {symbol} 单笔仓位大小...")
            
            # 获取账户余额（优先 WS 缓存，Redis）
            balance = await _get_balance_from_cache(self.client) if _get_stream_instance() else None
            if balance is None:
                balance = await self.client.get_account_balance()
            available_balance = balance.get('available', 0)
            
            if available_balance <= 0:
                logger.warning(f"❌ {symbol} 账户可用余额不足: {available_balance:.2f} USDT")
                return False
            
            # 计算名义价值与保证金（名义价值/杠杆）
            ticker = await self.client.get_ticker_24h(symbol)
            if not ticker:
                logger.warning(f"❌ {symbol} 无法获取价格数据")
                return False
            
            current_price = ticker['price']
            notional_value = quantity * current_price

            actual_leverage = leverage if leverage is not None else config.TRADING_CONFIG.get('LEVERAGE', 10)
            if not actual_leverage or actual_leverage <= 0:
                actual_leverage = 10
            margin_value = notional_value / actual_leverage
            
            # 重要语义：POSITION_PERCENT 均按“保证金占用比例”计算（更符合 stop_loss/take_profit 的 margin 逻辑）
            max_position_pct = config.get_effective_config('MAX_POSITION_PERCENT', 0.20)
            max_margin_value = available_balance * max_position_pct
            min_margin_value = available_balance * config.TRADING_CONFIG['MIN_POSITION_PERCENT']
            max_margin_pct = max_position_pct * 100
            min_margin_pct = config.TRADING_CONFIG['MIN_POSITION_PERCENT'] * 100
            
            logger.info(f"  数量: {quantity:.4f}")
            logger.info(f"  价格: {current_price:.4f} USDT")
            logger.info(f"  名义价值: {notional_value:.2f} USDT")
            logger.info(f"  杠杆: {actual_leverage}x")
            logger.info(f"  保证金: {margin_value:.4f} USDT")
            logger.info(f"  单笔最大保证金: {max_margin_value:.2f} USDT ({max_margin_pct:.1f}%)")
            logger.info(f"  单笔最小保证金: {min_margin_value:.2f} USDT ({min_margin_pct:.1f}%)")
            
            # 使用小的容差来处理浮点数精度问题（0.01 USDT）
            tolerance = 0.01
            if margin_value > max_margin_value + tolerance:
                logger.warning(
                    f"❌ {symbol} 单笔保证金过大: {margin_value:.4f} USDT > "
                    f"最大限制: {max_margin_value:.2f} USDT "
                    f"(超出: {margin_value - max_margin_value:.4f} USDT)"
                )
                return False
            elif margin_value > max_margin_value:
                # 在容差范围内，允许通过（浮点数精度问题）
                logger.info(
                    f"⚠ {symbol} 保证金略超限制但 within 容差: "
                    f"{margin_value:.4f} USDT vs {max_margin_value:.2f} USDT "
                    f"(差异: {margin_value - max_margin_value:.4f} USDT)"
                )
            
            if margin_value < min_margin_value:
                logger.warning(
                    f"❌ {symbol} 单笔保证金过小: {margin_value:.4f} USDT < "
                    f"最小限制: {min_margin_value:.2f} USDT"
                )
                return False
            
            logger.info(f"✓ {symbol} 单笔仓位大小检查通过")
            
            # 检查总仓位是否超过限制
            logger.info(f"检查 {symbol} 总仓位限制...")
            if not await self.check_total_position(margin_value):
                return False
            
            logger.info(
                f"✓ {symbol} 所有仓位检查通过: 保证金 {margin_value:.4f} USDT "
                f"(账户可用余额: {available_balance:.2f} USDT)"
            )
            return True
            
        except Exception as e:
            logger.error(f"检查仓位大小失败 {symbol}: {e}", exc_info=True)
            return False
    
    async def check_total_position(self, new_position_margin: float) -> bool:
        """
        检查总仓位是否超过限制
        
        Args:
            new_position_margin: 新仓位保证金占用（USDT）
            
        Returns:
            是否通过检查
        """
        try:
            # 获取当前持仓（优先 WS 缓存，Redis）
            positions = await _get_positions_from_cache(self.client) if _get_stream_instance() else None
            if positions is None:
                positions = await self.client.get_open_positions()
            
            # 计算当前总保证金占用
            current_position_values = []
            total_margin_value = 0
            
            for pos in positions:
                notional_value = abs(pos['positionAmt'] * pos['entryPrice'])
                lv = pos.get('leverage', None)
                try:
                    lv = int(lv) if lv is not None else None
                except Exception:
                    lv = None
                if not lv or lv <= 0:
                    lv = config.TRADING_CONFIG.get('LEVERAGE', 10) or 10
                margin_value = notional_value / lv
                current_position_values.append({
                    'symbol': pos['symbol'],
                    'notional': notional_value,
                    'margin': margin_value,
                    'leverage': lv,
                    'amount': pos['positionAmt'],
                    'entryPrice': pos['entryPrice']
                })
                total_margin_value += margin_value
            
            # 加上新仓位
            total_with_new = total_margin_value + new_position_margin
            
            # 获取账户余额（优先 WS 缓存，Redis）
            balance = await _get_balance_from_cache(self.client) if _get_stream_instance() else None
            if balance is None:
                balance = await self.client.get_account_balance()
            total_balance = balance.get('total', 0)
            available_balance = balance.get('available', 0)
            
            if total_balance <= 0:
                logger.warning("账户总余额为0，无法开仓")
                return False
            
            # 币安 -2019：可用保证金不足时直接拒绝，避免下单被拒
            if available_balance is not None and float(available_balance) <= 0:
                logger.warning(
                    "可用保证金不足或为负 (available=%.2f USDT)，无法开仓，跳过",
                    float(available_balance),
                )
                return False
            if available_balance is not None and float(new_position_margin) > float(available_balance):
                logger.warning(
                    "新仓位保证金 %.2f USDT > 可用保证金 %.2f USDT，无法开仓",
                    float(new_position_margin),
                    float(available_balance),
                )
                return False
            
            max_total_margin = total_balance * config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT']
            max_total_margin_pct = config.TRADING_CONFIG['MAX_TOTAL_POSITION_PERCENT'] * 100
            
            # 详细日志
            logger.info("=" * 60)
            logger.info("总仓位检查详情:")
            logger.info(f"  账户总余额: {total_balance:.2f} USDT")
            logger.info(f"  账户可用余额: {available_balance:.2f} USDT")
            logger.info(f"  总保证金上限: {max_total_margin:.2f} USDT ({max_total_margin_pct:.1f}%)")
            logger.info(f"  当前持仓数量: {len(positions)} 个")
            
            if current_position_values:
                logger.info("  当前持仓明细:")
                for pos_info in current_position_values:
                    logger.info(
                        f"    - {pos_info['symbol']}: "
                        f"保证金 {pos_info['margin']:.4f} USDT "
                        f"(名义 {pos_info['notional']:.2f} USDT, {pos_info['leverage']}x, "
                        f"数量: {pos_info['amount']:.4f}, 入场价: {pos_info['entryPrice']:.4f})"
                    )
            
            logger.info(f"  当前总保证金: {total_margin_value:.4f} USDT")
            logger.info(f"  新仓位保证金: {new_position_margin:.4f} USDT")
            logger.info(f"  开仓后总保证金: {total_with_new:.4f} USDT")
            logger.info(f"  剩余可用保证金: {max_total_margin - total_margin_value:.4f} USDT")
            
            if total_with_new > max_total_margin:
                logger.warning("=" * 60)
                logger.warning(
                    f"❌ 总保证金超限: {total_with_new:.4f} USDT > "
                    f"最大限制: {max_total_margin:.2f} USDT"
                )
                logger.warning(
                    f"   超出: {total_with_new - max_total_margin:.4f} USDT "
                    f"({((total_with_new - max_total_margin) / max_total_margin * 100):.1f}%)"
                )
                logger.warning("   建议: 平掉部分持仓或等待现有持仓平仓后再开新仓")
                logger.warning("=" * 60)
                return False
            
            logger.info(
                f"✓ 总保证金检查通过: {total_with_new:.4f} USDT / "
                f"最大限制: {max_total_margin:.2f} USDT "
                f"({(total_with_new / max_total_margin * 100):.1f}%)"
            )
            logger.info("=" * 60)
            return True
            
        except Exception as e:
            logger.error(f"检查总仓位失败: {e}", exc_info=True)
            return False
    
    async def calculate_dynamic_leverage(
        self, 
        *args,
        symbol: Optional[str] = None,
        entry_price: Optional[float] = None,
        stop_loss_price: Optional[float] = None, 
        atr: Optional[float] = None, 
        side: Optional[str] = None,
        signal_strength: Optional[int] = None,
        **kwargs
    ) -> int:
        """
        计算动态杠杆 - 综合考虑信号强度和风险控制
        
        兼容两种调用方式：
        1. 新版本：calculate_dynamic_leverage(symbol=..., entry_price=..., stop_loss_price=..., ...)
        2. 旧版本：calculate_dynamic_leverage(signal_strength, symbol, atr=..., entry_price=...)
        
        Args:
            symbol: 交易对
            entry_price: 入场价格
            stop_loss_price: 止损价格
            atr: ATR值
            side: 交易方向
            signal_strength: 信号强度 (0-10)
            
        Returns:
            建议杠杆倍数
        """
        # 兼容旧调用方式：第一个位置参数可能是 signal_strength
        if args and len(args) > 0:
            first_arg = args[0]
            if isinstance(first_arg, int) and 0 <= first_arg <= 10:
                signal_strength = first_arg
            elif isinstance(first_arg, str):
                symbol = first_arg
            if len(args) > 1 and isinstance(args[1], str) and symbol is None:
                symbol = args[1]
            if len(args) > 2 and isinstance(args[2], (int, float)) and entry_price is None:
                entry_price = args[2]
        
        # 从 kwargs 中提取（兼容旧调用）
        if 'signal_strength' in kwargs and signal_strength is None:
            signal_strength = kwargs.get('signal_strength')
        if 'symbol' in kwargs and symbol is None:
            symbol = kwargs.get('symbol')
        if 'entry_price' in kwargs and entry_price is None:
            entry_price = kwargs.get('entry_price')
        if 'atr' in kwargs and atr is None:
            atr = kwargs.get('atr')
        
        # 默认使用配置杠杆
        default_leverage = config.TRADING_CONFIG.get('LEVERAGE', 10)
        
        if not config.TRADING_CONFIG.get('USE_DYNAMIC_LEVERAGE', False):
            return default_leverage

        # 1. 基于信号强度的杠杆计算 (进攻性)
        signal_leverage = default_leverage
        if signal_strength is not None:
            base_leverage = default_leverage
            max_leverage = config.TRADING_CONFIG.get('MAX_LEVERAGE', 20)
            min_signal_strength = config.TRADING_CONFIG.get('MIN_SIGNAL_STRENGTH', 7)
            
            if signal_strength >= min_signal_strength:
                signal_range = 10 - min_signal_strength
                leverage_range = max_leverage - base_leverage
                if signal_range > 0:
                    strength_above_min = signal_strength - min_signal_strength
                    leverage_increase = (strength_above_min / signal_range) * leverage_range
                    signal_leverage = base_leverage + leverage_increase
                    signal_leverage = min(signal_leverage, max_leverage)
            
            logger.info(f"  📊 信号强度杠杆 ({symbol or 'N/A'}): 信号={signal_strength} -> {int(signal_leverage)}x")

        # 2. 基于ATR波动率的限制 (小众币保护)
        atr_limit_leverage = 100 # 初始设为很高
        if atr and entry_price and entry_price > 0:
            atr_percent = atr / entry_price
            atr_leverage_reduction_threshold = config.TRADING_CONFIG.get('ATR_LEVERAGE_REDUCTION_THRESHOLD', 0.05)  # 5%
            max_leverage_small_cap = config.TRADING_CONFIG.get('MAX_LEVERAGE_SMALL_CAP', 8)  # 默认 8，与之前盈利阶段一致
            
            if atr_percent >= atr_leverage_reduction_threshold:
                atr_limit_leverage = max_leverage_small_cap
                logger.info(f"  ⚠️ ATR波动率 {atr_percent*100:.2f}% (高波动), 限制最大杠杆为 {atr_limit_leverage}x")

        # 3. 基于止损宽度的风险控制 (防御性 - 核心逻辑)
        risk_safe_leverage = 100 # 初始设为很高
        
        # 尝试获取止损价格（如果没有传入，尝试估算）
        target_stop_loss = stop_loss_price
        if target_stop_loss is None and atr and atr > 0 and entry_price:
            atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 3.0)
            if side == 'BUY':
                target_stop_loss = entry_price - (atr * atr_multiplier)
            elif side == 'SELL':
                target_stop_loss = entry_price + (atr * atr_multiplier)
        
        if target_stop_loss and entry_price:
            # 计算止损宽度比例
            stop_loss_width = abs(entry_price - target_stop_loss) / entry_price
            
            if stop_loss_width > 0:
                # 获取最大单笔亏损率限制 (默认20%)
                max_loss_pct = config.TRADING_CONFIG.get('MAX_SINGLE_TRADE_LOSS_PERCENT', 20.0) / 100.0
                
                # 理论杠杆 = 最大允许亏损比例 / 止损宽度比例
                # 例如：最大亏损20%，止损宽5%，则最大杠杆 = 4倍 (4 * 5% = 20%)
                theoretical_leverage = max_loss_pct / stop_loss_width
                risk_safe_leverage = int(theoretical_leverage)
                
                logger.info(f"  🛡️ 风险安全杠杆 ({symbol or 'N/A'}):")
                logger.info(f"    止损宽度: {stop_loss_width*100:.2f}%")
                logger.info(f"    最大单笔亏损限制: {max_loss_pct*100:.1f}%")
                logger.info(f"    -> 理论最大杠杆: {theoretical_leverage:.2f}x")

        # 4. 综合计算最终杠杆 (取最小值)
        final_leverage = min(int(signal_leverage), int(atr_limit_leverage), int(risk_safe_leverage))
        
        # 限制在系统最大杠杆范围内
        max_config_leverage = config.TRADING_CONFIG.get('MAX_LEVERAGE', 20)
        final_leverage = min(final_leverage, max_config_leverage)
        
        # 杠杆下限：不低于 MIN_LEVERAGE（之前盈利阶段多为固定 8x，下限 8 可避免被压到 2–4x 导致单笔盈利过少）
        min_leverage = max(1, int(config.TRADING_CONFIG.get('MIN_LEVERAGE', 1) or 1))
        final_leverage = max(min_leverage, final_leverage)
        
        # 检查交易对最大杠杆限制
        if symbol:
            try:
                symbol_info = await self.client.get_symbol_info(symbol)
                if symbol_info and 'maxLeverage' in symbol_info:
                    symbol_max_leverage = symbol_info['maxLeverage']
                    if final_leverage > symbol_max_leverage:
                        logger.warning(f"  ⚠️ {symbol} 交易所限制最大杠杆 {symbol_max_leverage}x, 调整 {final_leverage}x -> {symbol_max_leverage}x")
                        final_leverage = symbol_max_leverage
            except Exception as e:
                pass

        logger.info(f"  ⚖️ 最终动态杠杆: {final_leverage}x (信号:{int(signal_leverage)}x, ATR限制:{int(atr_limit_leverage)}x, 风险安全:{int(risk_safe_leverage)}x)")
        return final_leverage
    
    async def calculate_position_size(
        self, 
        symbol: str, 
        change_percent: float,
        leverage: Optional[int] = None,
        entry_price: Optional[float] = None,
        stop_loss_price: Optional[float] = None,
        side: Optional[str] = None,
        atr: Optional[float] = None,
        signal_strength: Optional[int] = None
    ) -> Tuple[Optional[float], int]:
        """
        根据涨跌幅和风险参数计算合适的仓位大小
        ⚠️ 优化：支持固定风险百分比计算（凯利公式）和信号强度分级
        
        Args:
            symbol: 交易对
            change_percent: 涨跌幅百分比
            leverage: 杠杆倍数（可选）
            entry_price: 入场价格（可选，如果提供则用于固定风险计算）
            stop_loss_price: 止损价格（可选，如果提供则用于固定风险计算）
            side: 交易方向 'BUY' 或 'SELL'（可选，用于固定风险计算）
            atr: ATR值（可选，用于估算止损）
            signal_strength: 信号强度（可选，用于仓位分级）
            
        Returns:
            Tuple[Optional[float], int]: (仓位数量, 实际使用的杠杆倍数)。如果无法开仓，仓位数量为None。
        """
        try:
            logger.info(f"开始计算 {symbol} 的仓位大小...")
            
            # 获取账户余额（优先 WS 缓存，Redis）
            balance = await _get_balance_from_cache(self.client) if _get_stream_instance() else None
            if balance is None:
                balance = await self.client.get_account_balance()
            available_balance = balance.get('available', 0)
            total_balance = balance.get('total', 0)
            
            logger.info(f"  账户可用余额: {available_balance:.2f} USDT")
            logger.info(f"  账户总余额: {total_balance:.2f} USDT")
            
            if available_balance <= 0:
                logger.warning(f"❌ {symbol} 账户可用余额不足: {available_balance:.2f} USDT")
                return None, 10
            
            # 获取当前价格
            ticker = await self.client.get_ticker_24h(symbol)
            if not ticker:
                logger.warning(f"❌ {symbol} 无法获取价格数据")
                return None, 10
            
            current_price = ticker['price']
            logger.info(f"  当前价格: {current_price:.4f} USDT")
            
            # -------------------------------------------------------------------------
            # 动态杠杆计算逻辑 (针对 ZROUSDT 亏损案例优化)
            # -------------------------------------------------------------------------
            calculated_leverage = None
            if config.TRADING_CONFIG.get('USE_DYNAMIC_LEVERAGE', False):
                calculated_leverage = await self.calculate_dynamic_leverage(
                    symbol=symbol,
                    entry_price=entry_price if entry_price else current_price,
                    stop_loss_price=stop_loss_price,
                    atr=atr,
                    side=side
                )
            
            # 确定最终使用的杠杆
            # 优先级: 动态计算杠杆 > 传入的leverage参数 > 配置的默认LEVERAGE
            if calculated_leverage is not None:
                actual_leverage = calculated_leverage
                if leverage is not None and leverage != actual_leverage:
                    logger.warning(f"  ⚠️ 覆盖传入的杠杆 {leverage}x -> 动态调整为 {actual_leverage}x")
            else:
                actual_leverage = leverage if leverage is not None else config.TRADING_CONFIG.get('LEVERAGE', 10)

            if not actual_leverage or actual_leverage <= 0:
                actual_leverage = 10


            # ⚠️ 优化3：固定风险百分比仓位计算（凯利公式）
            # 公式：仓位大小 = (总资金 * 每笔单子承受的风险%) / (入场价 - 止损价)
            use_fixed_risk = config.TRADING_CONFIG.get('USE_FIXED_RISK_SIZING', True)
            fixed_risk_percent = config.TRADING_CONFIG.get('FIXED_RISK_PERCENT', 0.02)  # 默认2%
            quantity = None  # 初始化为None，确保变量存在
            
            if use_fixed_risk and entry_price and side:
                try:
                    # 如果未提供止损价格，先估算
                    if stop_loss_price is None:
                        # 尝试使用ATR估算止损距离
                        if atr and atr > 0:
                            atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 3.0)  # 默认3.0，放宽止损提升胜率
                            if side == 'BUY':
                                estimated_stop_loss = entry_price - (atr * atr_multiplier)
                            else:  # SELL
                                estimated_stop_loss = entry_price + (atr * atr_multiplier)
                            stop_loss_price = estimated_stop_loss
                        else:
                            # 使用固定百分比估算（基于保证金）
                            stop_loss_pct = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.10)
                            # 先估算一个临时仓位来计算止损距离
                            temp_margin = total_balance * 0.05  # 临时使用5%保证金
                            temp_notional = temp_margin * actual_leverage
                            temp_quantity = temp_notional / entry_price if entry_price > 0 else 0
                            if temp_quantity > 0:
                                stop_loss_amount = temp_margin * stop_loss_pct
                                if side == 'BUY':
                                    estimated_stop_loss = entry_price - (stop_loss_amount / temp_quantity)
                                else:  # SELL
                                    estimated_stop_loss = entry_price + (stop_loss_amount / temp_quantity)
                                stop_loss_price = estimated_stop_loss
                            else:
                                stop_loss_price = None
                    
                    # 计算止损距离
                    if stop_loss_price is not None and stop_loss_price > 0:
                        if side == 'BUY':
                            stop_distance = entry_price - stop_loss_price
                        else:  # SELL
                            stop_distance = stop_loss_price - entry_price
                        
                        if stop_distance > 0:
                            # ⚠️ 关键安全检查：验证止损距离是否在强平风险区内
                            # 强平距离估算：Entry / Leverage
                            # 考虑到维持保证金，强平会更早发生。安全系数取0.8（即亏损80%保证金时强制止损，防止强平）
                            max_safe_stop_distance = (entry_price / actual_leverage) * 0.8
                            
                            if stop_distance > max_safe_stop_distance:
                                logger.warning(
                                    f"  ⚠️ 止损距离过大 ({stop_distance:.4f})，超过当前杠杆的安全强平距离 ({max_safe_stop_distance:.4f}, 杠杆{actual_leverage}x)"
                                )
                                
                                # 尝试降低杠杆以适应宽止损
                                # 安全杠杆 = (Entry * 0.8) / StopDistance
                                ideal_leverage_float = (entry_price * 0.8) / stop_distance
                                ideal_leverage = max(1, int(ideal_leverage_float))
                                
                                if ideal_leverage < actual_leverage:
                                    logger.info(f"  🛡️ 自动降低杠杆: {actual_leverage}x -> {ideal_leverage}x 以适应止损距离")
                                    actual_leverage = ideal_leverage
                                    
                                    # 重新计算安全距离
                                    max_safe_stop_distance = (entry_price / actual_leverage) * 0.8
                                
                                # 如果即使降到1倍杠杆还是不够（极少见），或者无法降杠杆，则必须截断止损
                                if stop_distance > max_safe_stop_distance:
                                    logger.warning(f"  ⚠️ 即使降杠杆也无法满足安全距离，必须截断止损")
                                    logger.info(f"  ✓ 自动调整止损距离至安全范围: {max_safe_stop_distance:.4f}")
                                    
                                    # 调整止损距离和价格
                                    stop_distance = max_safe_stop_distance
                                    if side == 'BUY':
                                        stop_loss_price = entry_price - stop_distance
                                    else:
                                        stop_loss_price = entry_price + stop_distance
                                        
                                    logger.info(f"  ✓ 修正后的止损价格: {stop_loss_price:.4f}")

                            # 固定风险金额
                            risk_amount = total_balance * fixed_risk_percent

                            # ⚠️ 优化：将信号强度乘数应用于固定风险金额
                            signal_multiplier = 1.0
                            if signal_strength is not None:
                                # 获取配置，如果配置为None（可能从DB读取时为空），则使用默认值
                                signal_multipliers = config.TRADING_CONFIG.get('SIGNAL_STRENGTH_POSITION_MULTIPLIER')
                                if signal_multipliers is None:
                                    signal_multipliers = {7: 0.8, 8: 1.0, 9: 1.2, 10: 1.5}
                                
                                # 确保是字典类型
                                if isinstance(signal_multipliers, dict):
                                    signal_multiplier = signal_multipliers.get(signal_strength, 1.0)
                                    if signal_multiplier != 1.0:
                                        logger.info(f"  ⚡️ 应用信号强度乘数: {signal_multiplier} (信号强度: {signal_strength})")
                                        risk_amount = risk_amount * signal_multiplier
                                else:
                                    logger.warning(f"  ⚠️ SIGNAL_STRENGTH_POSITION_MULTIPLIER 配置格式错误: {type(signal_multipliers)}")
                            
                            # 根据止损距离反算仓位
                            # 风险金额 = (入场价 - 止损价) × 数量
                            # 所以：数量 = 风险金额 / (入场价 - 止损价)
                            quantity = risk_amount / stop_distance
                            
                            # 计算对应的保证金和名义价值
                            notional_value = quantity * entry_price
                            margin_value = notional_value / actual_leverage
                            
                            logger.info(f"  ⚠️ 使用固定风险百分比计算仓位:")
                            logger.info(f"    固定风险: {fixed_risk_percent*100:.2f}% = {risk_amount:.4f} USDT")
                            logger.info(f"    止损距离: {stop_distance:.4f} USDT ({stop_distance/entry_price*100:.2f}%)")
                            logger.info(f"    计算数量: {quantity:.4f}")
                            logger.info(f"    名义价值: {notional_value:.2f} USDT")
                            logger.info(f"    保证金: {margin_value:.4f} USDT ({margin_value/total_balance*100:.2f}%)")
                            
                            # 检查是否超过最大仓位限制（低波动期自动降低单笔上限）
                            max_position_percent = config.get_effective_config('MAX_POSITION_PERCENT', 0.20)
                            
                            # ⚠️ 如果启用了固定风险模型，且计算出的仓位合理（风险可控），则放宽 MAX_POSITION_PERCENT 限制
                            # 因为固定风险模型的核心是控制亏损额，而不是仓位大小
                            if use_fixed_risk:
                                # 在固定风险模式下，允许使用高达 95% 的可用余额作为保证金（只要风险金额符合设定）
                                max_margin_value = available_balance * 0.95
                                if margin_value > max_margin_value:
                                    logger.warning(f"  ⚠️ 固定风险计算的保证金 {margin_value:.4f} USDT 超过账户可用余额 95%")
                                    logger.info(f"  ✓ 调整为最大可用余额限制: {max_margin_value:.2f} USDT")
                                    margin_value = max_margin_value
                                    notional_value = margin_value * actual_leverage
                                    quantity = notional_value / entry_price if entry_price > 0 else None
                                else:
                                    if margin_value > (available_balance * max_position_percent):
                                        logger.info(f"  ℹ️ 固定风险模式：突破单仓位限制 ({max_position_percent*100:.1f}%) -> 使用计算值 {margin_value/available_balance*100:.1f}%")
                            else:
                                max_margin_value = available_balance * max_position_percent
                                if margin_value > max_margin_value:
                                    # 如果超过最大仓位，使用最大仓位
                                    logger.warning(f"  ⚠️ 固定风险计算的保证金 {margin_value:.4f} USDT > 最大限制 {max_margin_value:.2f} USDT")
                                    logger.info(f"  ✓ 调整为最大仓位限制: {max_margin_value:.2f} USDT")
                                    margin_value = max_margin_value
                                    notional_value = margin_value * actual_leverage
                                    quantity = notional_value / entry_price if entry_price > 0 else None
                        else:
                            logger.warning(f"  ⚠️ 止损距离无效 (stop_distance={stop_distance:.4f})，将使用传统方法计算仓位")
                    else:
                        logger.warning(f"  ⚠️ 无法估算止损价格，将使用传统方法计算仓位")
                except Exception as e:
                    logger.warning(f"  ⚠️ 固定风险百分比计算失败: {e}，将使用传统方法计算仓位")
                    quantity = None  # 确保quantity为None，使用传统方法
            
            # 如果未使用固定风险计算或计算失败，使用原来的方法
            if quantity is None:
                # ⚠️ 优化3：信号强度分级 - 9-10分高权重，8分轻仓
                signal_multiplier = 1.0
                if signal_strength is not None:
                    signal_multipliers = config.TRADING_CONFIG.get('SIGNAL_STRENGTH_POSITION_MULTIPLIER', {8: 0.5, 9: 1.0, 10: 1.0})
                    signal_multiplier = signal_multipliers.get(signal_strength, 1.0)
                    if signal_strength == 8:
                        logger.info(f"  ⚠️ 信号强度8分，使用50%仓位（轻仓试探）")
                    elif signal_strength >= 9:
                        logger.info(f"  ✓ 信号强度{signal_strength}分，使用100%仓位（高质量信号）")
                
                # 根据涨跌幅调整仓位大小（涨跌幅越大，保证金占比可以适当增加）
                effective_max = config.get_effective_config('MAX_POSITION_PERCENT', 0.20)
                base_position_percent = effective_max * signal_multiplier
                max_position_percent = effective_max * signal_multiplier
                min_position_percent = config.TRADING_CONFIG['MIN_POSITION_PERCENT']

                # 涨跌幅超过5%时，可以适当增加保证金占比，但必须遵守 MAX_POSITION_PERCENT 上限
                if abs(change_percent) > 5:
                    position_percent = min(
                        base_position_percent * 1.5,
                        max_position_percent
                    )
                    logger.info(f"  涨跌幅 {change_percent:.2f}% > 5%，使用增强仓位比例: {position_percent*100:.1f}%")
                else:
                    position_percent = base_position_percent
                    logger.info(f"  涨跌幅 {change_percent:.2f}%，使用标准仓位比例: {position_percent*100:.1f}%")

                # 计算保证金与名义价值
                margin_value = available_balance * position_percent
                notional_value = margin_value * actual_leverage
                logger.info(f"  计算保证金: {margin_value:.4f} USDT ({position_percent*100:.1f}% of {available_balance:.2f})")
                logger.info(f"  计算名义价值: {notional_value:.2f} USDT (保证金 {margin_value:.4f} × 杠杆 {actual_leverage}x)")

                # 计算数量
                quantity = notional_value / current_price

            logger.info(f"  计算数量: {quantity:.4f}")
            
            # 计算名义价值和保证金（如果还未计算）
            if 'notional_value' not in locals() or 'margin_value' not in locals():
                notional_value = quantity * current_price
                margin_value = notional_value / actual_leverage
            
            # 确保仓位价值满足最小名义价值要求（币安要求至少5 USDT）
            min_notional = 5.0  # 币安合约最小名义价值
            calculated_notional = quantity * current_price
            if calculated_notional < min_notional:
                # 如果计算出的名义价值仍然不足，增加数量
                required_quantity = min_notional / current_price
                logger.warning(f"  ⚠ 计算出的名义价值 {calculated_notional:.2f} USDT < {min_notional:.2f} USDT")
                logger.info(f"  ✓ 调整数量从 {quantity:.4f} 到 {required_quantity:.4f}")
                quantity = required_quantity
                notional_value = required_quantity * current_price
                margin_value = notional_value / actual_leverage
            
            # 计算名义价值和保证金（如果还未计算）
            if 'notional_value' not in locals() or 'margin_value' not in locals():
                notional_value = quantity * current_price
                margin_value = notional_value / actual_leverage
            
            # 确保仓位价值满足最小名义价值要求（币安要求至少5 USDT）
            min_notional = 5.0  # 币安合约最小名义价值
            calculated_notional = quantity * current_price
            if calculated_notional < min_notional:
                # 如果计算出的名义价值仍然不足，增加数量
                required_quantity = min_notional / current_price
                logger.warning(f"  ⚠ 计算出的名义价值 {calculated_notional:.2f} USDT < {min_notional:.2f} USDT")
                logger.info(f"  ✓ 调整数量从 {quantity:.4f} 到 {required_quantity:.4f}")
                quantity = required_quantity
                notional_value = required_quantity * current_price
                margin_value = notional_value / actual_leverage
            
            # 检查最小保证金要求（margin 语义：MIN_MARGIN_USDT 本身就是保证金下限）
            # 2026-02-13 优化：默认最小保证金提高到 10.0 USDT，避免无效小单
            min_margin_usdt = config.TRADING_CONFIG.get('MIN_MARGIN_USDT', 10.0)
            logger.info(f"  当前保证金: {margin_value:.4f} USDT (杠杆: {actual_leverage}x)")
            
            if margin_value < min_margin_usdt:
                # 保证金不足，需要增加保证金
                logger.warning(
                    f"  ⚠ 保证金 {margin_value:.4f} USDT < 最小保证金要求 {min_margin_usdt:.2f} USDT"
                )
                
                # 检查是否可以使用更大的仓位价值（但不超过最大仓位限制）
                if use_fixed_risk:
                    # 固定风险模式下，放宽最大仓位限制（允许高达95%可用余额）
                    max_position_percent = 0.95
                    max_margin_value = available_balance * 0.95
                else:
                    max_position_percent = config.get_effective_config('MAX_POSITION_PERCENT', 0.20)
                    max_margin_value = available_balance * max_position_percent

                if min_margin_usdt <= max_margin_value:
                    margin_value = min_margin_usdt
                    notional_value = margin_value * actual_leverage
                    quantity = notional_value / current_price
                    logger.info(
                        f"  ✓ 调整保证金到 {margin_value:.2f} USDT "
                        f"(名义 {notional_value:.2f} USDT) 以满足最小保证金要求"
                    )
                else:
                    # 即使使用最大仓位也无法满足最小保证金要求
                    max_margin = max_margin_value
                    logger.warning(
                        f"  ❌ 无法满足最小保证金要求: "
                        f"需要 {min_margin_usdt:.2f} USDT 保证金，"
                        f"但最大允许 {max_margin:.2f} USDT 保证金 (MAX_POSITION_PERCENT={max_position_percent*100:.2f}%)"
                    )
                    logger.warning(
                        f"  💡 建议: 增加账户余额到至少 "
                        f"{min_margin_usdt / max_position_percent:.2f} USDT "
                        f"才能满足最小保证金要求"
                    )
                    return None, actual_leverage
            
            # 检查是否通过风险控制
            logger.info(f"  检查仓位大小是否符合风险控制要求...")
            
            # 计算最终的名义价值与保证金
            final_notional_value = quantity * current_price
            final_margin = final_notional_value / actual_leverage if actual_leverage > 0 else final_notional_value
            
            # 仓位放大系数（盈利时适当放大仓位，1.0=正常，1.2=+20%，上限2.0，仍受 MAX_POSITION_PERCENT 约束）
            position_scale = config.TRADING_CONFIG.get('POSITION_SCALE_FACTOR', 1.0)
            try:
                position_scale = float(position_scale or 1.0)
                position_scale = max(1.0, min(position_scale, 2.0))
            except (TypeError, ValueError):
                position_scale = 1.0

            # 基于 symbol 分层与时段过滤进一步缩小仓位：
            # - reduced symbol：缩小仓位
            # - bad hour：缩小仓位
            stats_scale = 1.0
            try:
                from datetime import datetime, timezone, timedelta
                from config_manager import GlobalStrategyConfigManager

                mgr = GlobalStrategyConfigManager()
                stats_hour = mgr.get("STATS_HOUR_FILTERS", {}) or {}

                symbol_policy = resolve_symbol_trading_policy(symbol)
                if symbol_policy.get("reduced"):
                    stats_scale *= float(symbol_policy.get("position_factor") or 1.0)

                # 小时过滤
                try:
                    bj = timezone(timedelta(hours=8))
                    h = datetime.now(bj).hour
                    hours = stats_hour.get("hours") or []
                    for item in hours:
                        if int(item.get("hour", -1)) == int(h):
                            pf = item.get("position_factor")
                            try:
                                pf_val = float(pf)
                            except (TypeError, ValueError):
                                pf_val = 1.0
                            # 仅在 <1 时缩小仓位；>1 的“好时段”放大留给 POSITION_SCALE_FACTOR 处理
                            if pf_val > 0 and pf_val < 1.0:
                                stats_scale *= pf_val
                            break
                except Exception:
                    pass
            except Exception:
                stats_scale = 1.0

            total_scale = position_scale * stats_scale
            if total_scale != 1.0:
                quantity = quantity * total_scale
                final_notional_value = quantity * current_price
                final_margin = final_notional_value / actual_leverage if actual_leverage > 0 else final_notional_value
                max_margin_cap = available_balance * config.get_effective_config('MAX_POSITION_PERCENT', 0.20)
                if final_margin > max_margin_cap:
                    final_margin = max_margin_cap
                    final_notional_value = final_margin * actual_leverage
                    quantity = final_notional_value / current_price if current_price and current_price > 0 else quantity
                    logger.info(f"  仓位放大后超过单笔上限，已截断至 MAX_POSITION_PERCENT 对应保证金")
                logger.info(f"  仓位缩放系数: POSITION_SCALE={position_scale:.2f}, STATS_SCALE={stats_scale:.2f} -> 最终数量: {quantity:.4f}")
            
            # 添加最小名义价值检查（0.2 USDT），避免下无意义的小单子
            MIN_NOTIONAL_VALUE = 0.2  # 最小名义价值0.2 USDT
            if final_notional_value < MIN_NOTIONAL_VALUE:
                logger.warning(
                    f"  ❌ {symbol} 名义价值 {final_notional_value:.4f} USDT < 最小要求 {MIN_NOTIONAL_VALUE:.2f} USDT"
                )
                logger.warning(f"  💡 此类小单子意义不大，拒绝开仓")
                return None, actual_leverage
            
            if await self.check_position_size(symbol, quantity, leverage=actual_leverage):
                logger.info(
                    f"✓ {symbol} 仓位计算成功: {quantity:.4f} "
                    f"(保证金: {final_margin:.4f} USDT, "
                    f"名义价值: {final_notional_value:.2f} USDT, "
                    f"保证金: {final_margin:.4f} USDT, 杠杆: {actual_leverage}x)"
                )
                return quantity, actual_leverage
            else:
                logger.warning(f"❌ {symbol} 仓位检查未通过，无法开仓")
                return None, actual_leverage
            
        except Exception as e:
            logger.error(f"计算仓位大小失败 {symbol}: {e}", exc_info=True)
            return None, 10
    
    async def should_trade(self, symbol: str, change_percent: float) -> bool:
        """
        判断是否应该交易
        
        Args:
            symbol: 交易对
            change_percent: 涨跌幅百分比
            
        Returns:
            是否应该交易
        """
        # 用户风险旋钮：自动交易总开关
        if not bool(config.TRADING_CONFIG.get("AUTO_TRADE_ENABLED", True)):
            logger.info(f"{symbol} 自动交易已关闭（AUTO_TRADE_ENABLED=false），跳过")
            return False

        # 交易对分层：停做名单直接拒绝自动开仓。
        try:
            symbol_policy = resolve_symbol_trading_policy(symbol)
            if symbol_policy.get("blocked"):
                sources = ",".join(symbol_policy.get("sources") or []) or "blocked"
                logger.info(f"{symbol} 命中停做名单（{sources}），跳过自动开仓")
                return False
        except Exception as e:
            logger.debug(f"{symbol} 检查交易对分层策略失败（忽略）: {e}")

        # 检查最小涨跌幅阈值
        if abs(change_percent) < config.TRADING_CONFIG['MIN_CHANGE_PERCENT']:
            logger.debug(f"{symbol} 涨跌幅 {change_percent:.2f}% 小于阈值")
            return False
        
        # 检查是否已有持仓 / 总持仓数量限制（用 REST 全量持仓数，确保 MAX_OPEN_POSITIONS 按实际持仓数校验，不因 min_notional 过滤漏计）
        positions = await self.client.get_open_positions(include_low_notional=True)
        if not positions:
            positions = []
        try:
            max_open = int(config.TRADING_CONFIG.get("MAX_OPEN_POSITIONS", 0) or 0)
        except Exception:
            max_open = 0
        if max_open > 0 and len(positions) >= max_open:
            logger.info(f"{symbol} 持仓数量已达上限：{len(positions)}/{max_open}，跳过开仓")
            return False

        existing_position = next(
            (p for p in positions if p['symbol'] == symbol),
            None
        )
        
        if existing_position:
            logger.info(f"{symbol} 已有持仓，跳过")
            return False

        try:
            if bool(config.TRADING_CONFIG.get("NIGHT_HOURS_NO_OPEN_ENABLED", False)):
                bj = timezone(timedelta(hours=8))
                now_bj = datetime.now(bj)
                h = now_bj.hour
                wd = now_bj.weekday()
                start_h = int(config.TRADING_CONFIG.get("NIGHT_HOURS_START", 21) or 21)
                end_h = int(config.TRADING_CONFIG.get("NIGHT_HOURS_END", 6) or 6)
                only_sunday = bool(config.TRADING_CONFIG.get("NIGHT_HOURS_ONLY_SUNDAY", True))
                if start_h > end_h:
                    in_night = (h >= start_h) or (h < end_h)
                else:
                    in_night = (h >= start_h and h < end_h)
                if in_night:
                    if only_sunday:
                        apply_block = (wd == 5 and h >= start_h) or (wd == 6 and h < end_h)
                    else:
                        apply_block = True
                    if apply_block:
                        logger.info(
                            f"{symbol} 晚间禁止开仓（{'仅周六晚～周日晨' if only_sunday else '每日'}，北京{h}时），跳过"
                        )
                        return False
        except Exception:
            pass

        # 禁止开仓小时（北京）：按历史亏损时段配置，如 "17,19,22" 表示 17/19/22 时不开新仓
        try:
            no_open_hours_raw = config.TRADING_CONFIG.get("NO_OPEN_HOURS_BJ") or ""
            if no_open_hours_raw and isinstance(no_open_hours_raw, str):
                bj = timezone(timedelta(hours=8))
                now_bj = datetime.now(bj)
                h = now_bj.hour
                parts = [x.strip() for x in no_open_hours_raw.split(",") if x.strip()]
                no_open_set = set()
                for p in parts:
                    try:
                        no_open_set.add(int(p))
                    except ValueError:
                        continue
                if no_open_set and h in no_open_set:
                    logger.info(f"{symbol} 当前北京{h}时在禁止开仓时段内（NO_OPEN_HOURS_BJ={no_open_hours_raw}），跳过")
                    return False
        except Exception:
            pass

        # 每日开仓次数限制（Redis 计数；无 Redis 时降级为内存计数）
        try:
            max_daily = int(config.TRADING_CONFIG.get("MAX_DAILY_ENTRIES", 0) or 0)
            bj = timezone(timedelta(hours=8))
            today_bj = datetime.now(bj)
            is_sunday = (today_bj.weekday() == 6)  # 0=Mon, 6=Sun
            sunday_max = int(config.TRADING_CONFIG.get("SUNDAY_MAX_OPENS", 0) or 0)
            effective_max = max_daily
            if is_sunday and sunday_max > 0:
                effective_max = min(max_daily, sunday_max)
        except Exception:
            max_daily = 0
            effective_max = 0
            is_sunday = False
            sunday_max = 0
        if max_daily > 0:
            c = await self._get_daily_entries_count()
            if c >= effective_max:
                reason = "（周日限制）" if (is_sunday and sunday_max > 0) else ""
                logger.info(f"{symbol} 今日开仓次数已达上限{reason}：{c}/{effective_max}，跳过")
                return False
        
        # ⚠️ 2026-01-29新增：检查同一交易对连续亏损情况，避免连续亏损后继续交易
        try:
            loss_cooldown_enabled = bool(config.TRADING_CONFIG.get("SYMBOL_LOSS_COOLDOWN_ENABLED", True))
            if loss_cooldown_enabled:
                max_consecutive_losses = int(config.TRADING_CONFIG.get("SYMBOL_MAX_CONSECUTIVE_LOSSES", 2) or 2)
                loss_cooldown_sec = int(config.TRADING_CONFIG.get("SYMBOL_LOSS_COOLDOWN_SEC", 3600) or 3600)  # 默认1小时
                
                # 查询该交易对最近的交易记录（仅查询已平仓的）
                recent_losses = await self._check_recent_losses(symbol, max_consecutive_losses, loss_cooldown_sec)
                if recent_losses >= max_consecutive_losses:
                    logger.info(
                        f"{symbol} [连续亏损过滤] 最近{max_consecutive_losses}次交易连续亏损，"
                        f"禁止交易{loss_cooldown_sec}秒（冷却中）"
                    )
                    return False
        except Exception as e:
            logger.debug(f"{symbol} 检查连续亏损时出错（忽略，允许交易）: {e}")
        
        return True

    async def _check_recent_losses(self, symbol: str, max_consecutive: int, cooldown_sec: int) -> int:
        """
        检查同一交易对最近的连续亏损次数
        
        Args:
            symbol: 交易对
            max_consecutive: 最大允许连续亏损次数
            cooldown_sec: 冷却时间（秒）
            
        Returns:
            连续亏损次数（如果>=max_consecutive，则应该禁止交易）
        """
        try:
            # 尝试从数据库查询最近的交易记录
            from database.models import Trade
            
            # 查询最近N+1次已平仓的交易（多查一次，确保能判断是否连续）
            # 限制 limit 避免单 symbol 拉取全表导致内存暴增（2 CPU 4G 多账号场景）
            need_count = max_consecutive + 10
            recent_trades = Trade.get_all(
                symbol=symbol,
                status='closed',
                account_id=int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1),
                limit=min(need_count, 200),
            )
            
            # 按平仓时间倒序排序（最新的在前）
            recent_trades = sorted(
                recent_trades,
                key=lambda x: (x.get('exit_time') or x.get('entry_time') or 0),
                reverse=True
            )[:max_consecutive + 1]  # 只取最近N+1次
            
            if not recent_trades:
                return 0
            
            # 检查是否在冷却时间内
            now = int(time.time())
            latest_trade = recent_trades[0]
            latest_exit_time = latest_trade.get('exit_time') or latest_trade.get('entry_time') or 0
            
            # 如果最新交易还在冷却时间内，检查连续亏损
            if now - latest_exit_time < cooldown_sec:
                consecutive_losses = 0
                for trade in recent_trades:
                    pnl = float(trade.get('pnl', 0) or 0)
                    exit_reason = str(trade.get('exit_reason', '') or '').lower()
                    
                    # 2026-02-13 优化：手动平仓（manual）通常是人为干预（如发现信号消失或误操作），
                    # 不应计入策略的连续亏损统计，避免误触发冷却。
                    if exit_reason == 'manual' or exit_reason == 'manual_close':
                        continue

                    if pnl < 0:  # 亏损
                        consecutive_losses += 1
                    else:  # 盈利，中断连续亏损
                        break
                return consecutive_losses
            
            # 如果最新交易已超过冷却时间，不限制
            return 0
            
        except Exception as e:
            logger.debug(f"查询{symbol}最近交易记录失败（忽略，允许交易）: {e}")
            return 0
    
    def _daily_entries_key(self) -> str:
        try:
            aid = int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1)
        except Exception:
            aid = 1
        bj = timezone(timedelta(hours=8))
        d = datetime.now(bj).strftime("%Y%m%d")
        return f"ats:acc:{aid}:daily_entries:{d}"

    def _seconds_until_beijing_day_end(self) -> int:
        bj = timezone(timedelta(hours=8))
        now = datetime.now(bj)
        end = (now.replace(hour=23, minute=59, second=59, microsecond=0))
        return max(60, int((end - now).total_seconds()) + 1)

    async def _get_daily_entries_count(self) -> int:
        key = self._daily_entries_key()
        try:
            # redis_cache 已有内存降级逻辑
            return int(await self.client.redis_cache.get_int(key, 0))
        except Exception:
            return 0

    async def record_entry(self, symbol: str = "") -> None:
        """在“开仓真正成功”后调用，用于累计每日开仓次数。"""
        try:
            max_daily = int(config.TRADING_CONFIG.get("MAX_DAILY_ENTRIES", 0) or 0)
        except Exception:
            max_daily = 0
        if max_daily <= 0:
            return
        key = self._daily_entries_key()
        ttl = self._seconds_until_beijing_day_end()
        try:
            n = await self.client.redis_cache.incr(key, 1, ttl=ttl)
            logger.info(f"{symbol} 今日开仓计数 +1：{n}/{max_daily}")
        except Exception:
            return
    
    def get_stop_loss_price(
        self, 
        entry_price: float, 
        side: str, 
        quantity: float,
        leverage: int,
        stop_loss_pct: Optional[float] = None,
        klines: Optional[List] = None,
        bollinger: Optional[Dict] = None,
        atr: Optional[float] = None
    ) -> float:
        """
        计算止损价格（基于保证金的盈亏金额）
        
        Args:
            entry_price: 入场价格
            side: 方向 'BUY' 或 'SELL'
            quantity: 持仓数量
            leverage: 杠杆倍数
            stop_loss_pct: 止损百分比（相对于保证金），如果为None则使用配置值
            klines: K线数据，用于计算支撑/阻力位（作为辅助参考）
            bollinger: 布林带数据，用于计算动态止损（作为辅助参考）
            atr: 平均真实波幅，用于计算动态止损（作为辅助参考）
            
        Returns:
            止损价格
        """
        # 计算保证金和仓位价值
        position_value = entry_price * quantity
        margin = position_value / leverage if leverage > 0 else position_value
        
        # 优先使用ATR动态止损（如果启用且ATR可用）
        # 计算ATR百分比（如果提供了ATR绝对值）
        atr_percent = None
        if atr is not None and atr > 0 and entry_price > 0:
            atr_percent = atr / entry_price
        
        # 获取市场波动率（如果可用）
        volatility = None  # 可以从symbol_info中获取，这里暂时为None
        
        # 使用ATR策略计算止损
        stop_loss_price_atr, stop_distance_atr, atr_details = self.atr_strategy.calculate_stop_loss(
            entry_price, side, atr, atr_percent, volatility
        )
        
        if stop_loss_price_atr is None:
            logger.debug(f"ATR不可用，使用固定百分比止损")
        
        # 获取止损百分比（相对于保证金）
        stop_loss_percent = stop_loss_pct or config.TRADING_CONFIG['STOP_LOSS_PERCENT']
        
        # ⚠️ 关键修复：配置值格式转换（兼容百分比形式和比例形式）
        # 如果值>1，认为是百分比形式，转换为比例形式
        if stop_loss_percent > 1:
            stop_loss_percent = stop_loss_percent / 100.0
        
        # 计算止损金额（相对于保证金）
        stop_loss_amount = margin * stop_loss_percent
        
        # 计算基于保证金的止损价
        # 止损金额 = (开仓价 - 止损价) × 数量
        # 所以：止损价 = 开仓价 - (止损金额 / 数量)
        if side == 'BUY':  # 做多，止损价低于入场价
            stop_loss_price_margin = entry_price - (stop_loss_amount / quantity)
        else:  # 做空，止损价高于入场价
            stop_loss_price_margin = entry_price + (stop_loss_amount / quantity)
        
        # 同时计算基于价格百分比的止损价（作为最小值保护）
        # 获取最小价格变动百分比（如果配置了）
        min_price_change_pct = config.TRADING_CONFIG.get('MIN_STOP_LOSS_PRICE_PCT', None)
        if min_price_change_pct is not None:
            # 基于价格百分比的止损价
            if side == 'BUY':
                stop_loss_price_price = entry_price * (1 - min_price_change_pct)
            else:
                stop_loss_price_price = entry_price * (1 + min_price_change_pct)
        else:
            stop_loss_price_price = None
        
        # 选择最终的止损价：优先ATR，其次保证金，最后价格百分比（取更宽松的）
        candidate_prices = []
        if stop_loss_price_atr is not None:
            candidate_prices.append(('ATR', stop_loss_price_atr))
        candidate_prices.append(('保证金', stop_loss_price_margin))
        if stop_loss_price_price is not None:
            candidate_prices.append(('价格百分比', stop_loss_price_price))
        
        # ⚠️ 优化：如果ATR可用，优先使用ATR止损，不强制取"更紧"的止损
        # 只有在ATR不可用时，才使用保证金止损作为兜底
        if stop_loss_price_atr is not None:
            stop_loss_price = stop_loss_price_atr
            selected_method = 'ATR'
            # 记录被覆盖的保证金止损（仅供参考）
            logger.debug(f"优先使用ATR止损: {stop_loss_price:.4f}, 忽略保证金止损: {stop_loss_price_margin:.4f}")
        else:
            # ATR不可用，使用保证金止损和价格百分比止损中"更紧"的一个（保护资金）
            if side == 'BUY':
                # 做多：取最大值（更高的止损价，更接近入场价）
                stop_loss_price = max(p[1] for p in candidate_prices if p[0] != 'ATR')
            else:
                # 做空：取最小值（更低的止损价，更接近入场价）
                stop_loss_price = min(p[1] for p in candidate_prices if p[0] != 'ATR')
            
            # 找到对应的策略名称
            matched = [p[0] for p in candidate_prices if abs(p[1] - stop_loss_price) < 1e-9]
            selected_method = matched[0] if matched else '保证金'
        
        # 如果提供了技术分析数据，计算技术止损（允许更紧的止损，但需要在保证金止损范围内）
        technical_stop = None
        if klines and len(klines) >= 10:
            # 计算支撑/阻力位
            low_prices = [float(k[3]) for k in klines[-20:]]  # 最近20根K线的最低价
            high_prices = [float(k[2]) for k in klines[-20:]]  # 最近20根K线的最高价
            
            if side == 'BUY':  # 做多，止损放在支撑位下方
                # 找到近期波段低点
                recent_low = min(low_prices)
                # 止损放在低点下方0.5%
                buffer = entry_price * 0.005  # 0.5%缓冲
                technical_stop = recent_low - buffer
                
                # 如果布林带可用，也可以考虑布林带下轨
                if bollinger and bollinger.get('lower'):
                    bollinger_stop = bollinger['lower'] * 0.995  # 布林带下轨下方0.5%
                    technical_stop = max(technical_stop, bollinger_stop)
                
                # 技术止损更紧，但需要确保在保证金止损范围内（不能超过保证金止损）
                if technical_stop < stop_loss_price and technical_stop >= stop_loss_price_margin:
                    # 技术止损在合理范围内，可以考虑使用
                    candidate_prices.append(('技术分析', technical_stop))
                    logger.debug(
                        f"技术止损 (BUY): {technical_stop:.4f} "
                        f"(在保证金止损范围内)"
                    )
            else:  # 做空，止损放在阻力位上方
                # 找到近期波段高点
                recent_high = max(high_prices)
                # 止损放在高点上方0.5%
                buffer = entry_price * 0.005  # 0.5%缓冲
                technical_stop = recent_high + buffer
                
                # 如果布林带可用，也可以考虑布林带上轨
                if bollinger and bollinger.get('upper'):
                    bollinger_stop = bollinger['upper'] * 1.005  # 布林带上轨上方0.5%
                    technical_stop = min(technical_stop, bollinger_stop)
                
                # 技术止损更紧，但需要确保在保证金止损范围内（不能超过保证金止损）
                if technical_stop > stop_loss_price and technical_stop <= stop_loss_price_margin:
                    # 技术止损在合理范围内，可以考虑使用
                    candidate_prices.append(('技术分析', technical_stop))
                    logger.debug(
                        f"技术止损 (SELL): {technical_stop:.4f} "
                        f"(在保证金止损范围内)"
                    )
        
        # 重新选择最终的止损价（包括技术止损）
        # ⚠️ 优化：如果列表中包含ATR止损，优先使用ATR止损（通常更宽，能容忍波动）
        # 否则使用"更紧"的止损来保护资金
        
        atr_candidate = next((p for p in candidate_prices if p[0] == 'ATR'), None)
        
        if atr_candidate:
            final_stop_loss = atr_candidate[1]
            selected_method = 'ATR'
        else:
            if side == 'BUY':
                # 做多：选择更高的止损价（更紧）
                final_stop_loss = max(p[1] for p in candidate_prices)
                selected_method = [p[0] for p in candidate_prices if p[1] == final_stop_loss][0]
            else:
                # ⚠️ 关键修复：做空必须选择更低的止损价（更接近入场价，更紧）
                # 注意：对于SELL单，止损价高于入场价，所以"更低的止损价"意味着更接近入场价
                final_stop_loss = min(p[1] for p in candidate_prices)
                selected_method = [p[0] for p in candidate_prices if p[1] == final_stop_loss][0]
        
        # ⚠️ 关键修复：验证最终止损价对应的保证金百分比不超过配置值
        if side == 'BUY':
            final_stop_loss_amount = (entry_price - final_stop_loss) * quantity
        else:
            final_stop_loss_amount = (final_stop_loss - entry_price) * quantity
        final_stop_loss_pct_margin = (final_stop_loss_amount / margin * 100) if margin > 0 else 0
        
        # ⚠️ 优化：如果使用的是ATR止损或技术止损，允许突破配置的保证金百分比限制（更宽可以）
        # 因为仓位大小已经根据风险进行了调整，所以即使单笔亏损比例大，总亏损金额仍受控
        is_atr_or_tech = selected_method in ['ATR', '技术分析']
        
        # 与策略一致：ATR/技术止损不得比配置的 STOP_LOSS_PERCENT 更紧（避免低波动时极窄止损被扫）
        # 仅用 STOP_LOSS_PERCENT 作为下限，不引入额外参数，避免与 STOP_LOSS_PERCENT/ATR 策略冲突
        if is_atr_or_tech and margin > 0 and final_stop_loss_pct_margin < (stop_loss_percent * 100):
            atr_pct_before = final_stop_loss_pct_margin
            final_stop_loss = stop_loss_price_margin
            final_stop_loss_pct_margin = stop_loss_percent * 100
            selected_method = 'ATR/技术→保证金下限'
            logger.info(
                f"ℹ️ ATR/技术止损({atr_pct_before:.2f}% 保证金) 紧于配置 STOP_LOSS_PERCENT({stop_loss_percent*100:.1f}%)，"
                f"已采用保证金止损以与策略一致"
            )
        
        # 如果最终止损价对应的保证金百分比超过配置值，强制使用保证金止损（含 ATR/技术止损）
        # 避免 ATR 过宽导致止损 -58%～-78% 保证金、长期扛单；与 USE_MARGIN_CAP_FOR_TP 一致
        use_margin_cap_sl = bool(config.TRADING_CONFIG.get('USE_MARGIN_CAP_FOR_SL', True))
        if final_stop_loss_pct_margin > (stop_loss_percent * 100):
            if not is_atr_or_tech:
                logger.warning(
                    f"⚠️ 最终止损价({final_stop_loss:.4f}, 使用{selected_method})对应的保证金百分比({final_stop_loss_pct_margin:.2f}%) "
                    f"超过配置值({stop_loss_percent*100:.1f}%)，强制使用保证金止损({stop_loss_price_margin:.4f})"
                )
                final_stop_loss = stop_loss_price_margin
                selected_method = '保证金（强制）'
                final_stop_loss_pct_margin = stop_loss_percent * 100
            elif use_margin_cap_sl:
                logger.info(
                    f"止损已按保证金上限封顶: {selected_method}止损({final_stop_loss_pct_margin:.1f}% 保证金) "
                    f"超过 STOP_LOSS_PERCENT({stop_loss_percent*100:.1f}%)，采用保证金止损({stop_loss_price_margin:.4f})"
                )
                final_stop_loss = stop_loss_price_margin
                selected_method = '保证金(止损上限)'
                final_stop_loss_pct_margin = stop_loss_percent * 100
            else:
                logger.info(
                    f"ℹ️ {selected_method}止损 ({final_stop_loss:.4f}) 超过保证金配置值 "
                    f"({final_stop_loss_pct_margin:.2f}% > {stop_loss_percent*100:.1f}%)，"
                    f"但予以保留（USE_MARGIN_CAP_FOR_SL=False）"
                )

        # ⚠️ 最终强制检查：最小止损距离（防止 -2021 Order would immediately trigger）
        # 此检查必须在最后执行，覆盖之前的逻辑，因为这是交易所/系统的硬性约束
        if stop_loss_price_price is not None:
            adjusted = False
            if side == 'BUY':
                # 做多：止损价必须足够低 (SL <= Entry * (1 - MinDist))
                if final_stop_loss > stop_loss_price_price:
                    logger.warning(
                        f"⚠️ 止损价({final_stop_loss:.4f})离入场价太近，"
                        f"强制调整为最小距离止损价({stop_loss_price_price:.4f})"
                    )
                    final_stop_loss = stop_loss_price_price
                    selected_method = '最小距离（强制）'
                    adjusted = True
            else:
                # 做空：止损价必须足够高 (SL >= Entry * (1 + MinDist))
                if final_stop_loss < stop_loss_price_price:
                    logger.warning(
                        f"⚠️ 止损价({final_stop_loss:.4f})离入场价太近，"
                        f"强制调整为最小距离止损价({stop_loss_price_price:.4f})"
                    )
                    final_stop_loss = stop_loss_price_price
                    selected_method = '最小距离（强制）'
                    adjusted = True
            
            if adjusted:
                # 重新计算保证金百分比用于日志
                if side == 'BUY':
                    final_stop_loss_amount = (entry_price - final_stop_loss) * quantity
                else:
                    final_stop_loss_amount = (final_stop_loss - entry_price) * quantity
                final_stop_loss_pct_margin = (final_stop_loss_amount / margin * 100) if margin > 0 else 0
        
        # ⚠️ 最终安全检查：确保止损价不等于入场价，且方向正确
        # 如果计算出的止损价无效（例如配置为0导致等于入场价），强制使用最小安全距离
        default_min_dist = 0.005  # 默认0.5%
        
        if side == 'BUY':
            if final_stop_loss >= entry_price:
                safe_sl = entry_price * (1 - default_min_dist)
                logger.warning(
                    f"⚠️ 计算的止损价({final_stop_loss:.4f}) >= 入场价({entry_price:.4f}) 无效 (BUY)，"
                    f"强制调整为安全止损价: {safe_sl:.4f} (-0.5%)"
                )
                final_stop_loss = safe_sl
        else:  # SELL
            if final_stop_loss <= entry_price:
                safe_sl = entry_price * (1 + default_min_dist)
                logger.warning(
                    f"⚠️ 计算的止损价({final_stop_loss:.4f}) <= 入场价({entry_price:.4f}) 无效 (SELL)，"
                    f"强制调整为安全止损价: {safe_sl:.4f} (+0.5%)"
                )
                final_stop_loss = safe_sl
        
        logger.info(
            f"最终止损 ({side}): {final_stop_loss:.4f} (使用{selected_method}), "
            + (f"ATR={stop_loss_price_atr:.4f}, " if stop_loss_price_atr else "")
            + f"保证金={stop_loss_price_margin:.4f}, "
            + (f"价格={stop_loss_price_price:.4f}, " if stop_loss_price_price else "")
            + (f"技术={technical_stop:.4f}, " if technical_stop else "")
            + f"止损金额={stop_loss_amount:.2f} USDT ({stop_loss_percent*100:.1f}% of margin), "
            + f"实际保证金百分比={final_stop_loss_pct_margin:.2f}%"
        )
        return final_stop_loss
    
    def get_take_profit_price(
        self, 
        entry_price: float, 
        side: str,
        quantity: float,
        leverage: int,
        take_profit_pct: Optional[float] = None,
        atr: Optional[float] = None,
        stop_distance: Optional[float] = None
    ) -> float:
        """
        计算止盈价格（基于保证金的盈亏金额，支持ATR动态止盈）
        
        Args:
            entry_price: 入场价格
            side: 方向 'BUY' 或 'SELL'
            quantity: 持仓数量
            leverage: 杠杆倍数
            take_profit_pct: 止盈百分比（相对于保证金），如果为None则使用配置值
            atr: 平均真实波幅，用于计算动态止盈（可选）
            
        Returns:
            止盈价格
        """
        # 计算保证金和仓位价值
        position_value = entry_price * quantity
        margin = position_value / leverage if leverage > 0 else position_value
        
        # 优先使用ATR动态止盈（如果启用且ATR可用）
        # 计算ATR百分比（如果提供了ATR绝对值）
        atr_percent = None
        if atr is not None and atr > 0 and entry_price > 0:
            atr_percent = atr / entry_price
        
        # 尝试从止损计算中获取止损距离（用于盈亏比计算）
        # 如果止损已经计算过，可以使用止损距离来计算止盈
        stop_distance_for_rr = None
        # 注意：这里无法直接获取止损距离，需要调用方传递，或者使用ATR倍数计算
        
        # 使用ATR策略计算止盈
        # 优先使用盈亏比方法（基于止损距离），如果没有止损距离则使用ATR倍数
        take_profit_price_atr, take_profit_distance_atr, atr_tp_details = self.atr_strategy.calculate_take_profit(
            entry_price, side, stop_distance, atr, atr_percent, 
            use_risk_reward_ratio=(stop_distance is not None)
        )
        
        if take_profit_price_atr is None:
            logger.debug(f"ATR不可用，使用固定百分比止盈")
        
        # 获取止盈百分比（相对于保证金）
        take_profit_percent = take_profit_pct or config.TRADING_CONFIG['TAKE_PROFIT_PERCENT']
        
        # ⚠️ 关键修复：配置值格式转换（兼容百分比形式和比例形式）
        # 如果值>1，认为是百分比形式，转换为比例形式
        if take_profit_percent > 1:
            take_profit_percent = take_profit_percent / 100.0
        
        # 计算止盈金额（相对于保证金）
        take_profit_amount = margin * take_profit_percent
        
        # 计算基于保证金的止盈价
        # 止盈金额 = (止盈价 - 开仓价) × 数量
        # 所以：止盈价 = 开仓价 + (止盈金额 / 数量)
        if side == 'BUY':  # 做多，止盈价高于入场价
            take_profit_price_margin = entry_price + (take_profit_amount / quantity)
        else:  # 做空，止盈价低于入场价
            take_profit_price_margin = entry_price - (take_profit_amount / quantity)
        
        # 同时计算基于价格百分比的止盈价（作为最小值保护）
        # 获取最小价格变动百分比（如果配置了）
        min_price_change_pct = config.TRADING_CONFIG.get('MIN_TAKE_PROFIT_PRICE_PCT', None)
        if min_price_change_pct is not None:
            # 基于价格百分比的止盈价
            if side == 'BUY':
                take_profit_price_price = entry_price * (1 + min_price_change_pct)
            else:
                take_profit_price_price = entry_price * (1 - min_price_change_pct)
        else:
            take_profit_price_price = None
        
        # ⚠️ 2026-01-29优化：优先使用基于止损距离的盈亏比止盈（确保达到3:1目标）
        # 如果ATR止盈是基于止损距离×盈亏比计算的，优先使用它（确保达到3:1盈亏比）
        candidate_prices = []
        
        # 检查是否是基于盈亏比计算的（通过details判断）
        is_rr_based = (atr_tp_details and atr_tp_details.get('method') == 'ATR止损距离×盈亏比')
        
        # 优先添加基于止损距离的盈亏比止盈（如果可用，确保达到3:1目标）
        if take_profit_price_atr is not None:
            if is_rr_based:
                candidate_prices.append(('ATR盈亏比', take_profit_price_atr))
            else:
                # ATR倍数止盈作为备选
                candidate_prices.append(('ATR倍数', take_profit_price_atr))
        
        # 添加固定百分比止盈（作为备选）
        candidate_prices.append(('保证金', take_profit_price_margin))
        if take_profit_price_price is not None:
            candidate_prices.append(('价格百分比', take_profit_price_price))
        
        # ⚠️ 2026-01-29优化：优先使用基于止损距离的盈亏比止盈（确保达到3:1目标）
        # 若开启 USE_MARGIN_CAP_FOR_TP：当盈亏比算出的止盈过远时，用「保证金止盈」封顶，便于获利离场
        use_margin_cap = bool(config.TRADING_CONFIG.get('USE_MARGIN_CAP_FOR_TP', True))
        if is_rr_based and take_profit_price_atr is not None:
            take_profit_price = take_profit_price_atr
            selected_method = 'ATR盈亏比'
            if use_margin_cap and take_profit_price_margin is not None:
                # 取「更近」的止盈，避免盈亏比止盈过远难以触及
                if side == 'BUY':
                    if take_profit_price_margin < take_profit_price:
                        take_profit_price = take_profit_price_margin
                        selected_method = '保证金(止盈上限)'
                        logger.info(f"止盈已按保证金上限封顶: 原盈亏比止盈过远，采用 TAKE_PROFIT_PERCENT 对应价格")
                else:
                    if take_profit_price_margin > take_profit_price:
                        take_profit_price = take_profit_price_margin
                        selected_method = '保证金(止盈上限)'
                        logger.info(f"止盈已按保证金上限封顶: 原盈亏比止盈过远，采用 TAKE_PROFIT_PERCENT 对应价格")
        else:
            # 如果没有基于盈亏比的止盈，选择最远的止盈（给利润更多空间，提高盈亏比）
            if side == 'BUY':
                # 做多：选择更高的止盈价（更远，给利润更多空间）
                take_profit_price = max(p[1] for p in candidate_prices)
            else:
                # 做空：选择更低的止盈价（更远，给利润更多空间）
                take_profit_price = min(p[1] for p in candidate_prices)
            selected_method = [p[0] for p in candidate_prices if p[1] == take_profit_price][0]
        
        logger.info(
            f"止盈计算 ({side}): "
            + (f"ATR={take_profit_price_atr:.4f}, " if take_profit_price_atr else "")
            + f"基于保证金={take_profit_price_margin:.4f}, "
            + (f"基于价格={take_profit_price_price:.4f}, " if take_profit_price_price else "")
            + f"最终止盈={take_profit_price:.4f} (使用{selected_method}"
            + (", 确保3:1盈亏比" if is_rr_based else ", 选择最远止盈")
            + "), "
            + f"止盈金额={take_profit_amount:.4f} USDT ({take_profit_percent*100:.1f}% of margin)"
        )
        return take_profit_price
