"""
仓位管理模块 - 管理持仓和订单
"""
import asyncio
import json
import logging
import random
import time
import aiohttp
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from datetime import datetime
try:
    from .binance_client import BinanceClient, AlgoOrderPositionUnavailableError
    from .risk_manager import RiskManager
    from . import config
except ImportError:
    from binance_client import BinanceClient, AlgoOrderPositionUnavailableError
    from risk_manager import RiskManager
    import config

logger = logging.getLogger(__name__)

# 尝试导入数据库模型（如果可用）
DB_AVAILABLE = False
Trade = None
get_beijing_time = None
try:
    import sys
    from pathlib import Path
    project_root = Path(__file__).parent.parent
    backend_path = project_root / 'backend'
    if backend_path.exists():
        sys.path.insert(0, str(backend_path))
        from database.models import Trade, get_beijing_time
        DB_AVAILABLE = True
        logger.info("✓ 数据库模型导入成功，交易记录将保存到数据库")
    else:
        logger.warning("⚠ backend目录不存在，无法使用数据库功能")
        DB_AVAILABLE = False
except ImportError as e:
    logger.warning(f"⚠ 无法导入数据库模型: {e}")
    logger.warning("   交易记录将不会保存到数据库")
    DB_AVAILABLE = False
except Exception as e:
    logger.warning(f"⚠ 数据库初始化失败: {e}")
    logger.warning("   交易记录将不会保存到数据库")
    DB_AVAILABLE = False

# 如果没有导入get_beijing_time，创建一个本地版本
if get_beijing_time is None:
    from datetime import datetime, timezone, timedelta
    BEIJING_TZ = timezone(timedelta(hours=8))
    def get_beijing_time():
        """获取当前北京时间（UTC+8）"""
        return datetime.now(BEIJING_TZ).replace(tzinfo=None)

# 落库失败独立日志：币安已成交但未写入 DB 时写入此文件，便于排查与对账（不依赖 DB/Redis）
_FAILURE_LOG_DIR = None
_FAILURE_LOG_PATH = None
def _get_db_failure_log_path():
    global _FAILURE_LOG_DIR, _FAILURE_LOG_PATH
    if _FAILURE_LOG_PATH is not None:
        return _FAILURE_LOG_PATH
    try:
        root = Path(__file__).parent.parent
        _FAILURE_LOG_DIR = root / "logs"
        _FAILURE_LOG_DIR.mkdir(parents=True, exist_ok=True)
        _FAILURE_LOG_PATH = _FAILURE_LOG_DIR / "trade_db_failures.log"
        return _FAILURE_LOG_PATH
    except Exception:
        return None

def _log_trade_db_failure(symbol, entry_order_id, side, quantity, entry_price, account_id, reason, error_type=None, error_message=None):
    """将「成交但落库失败」记录到独立日志文件（一行 JSON），便于 grep/脚本统计，不依赖 DB/Redis。"""
    path = _get_db_failure_log_path()
    if not path:
        return
    try:
        rec = {
            "ts": datetime.utcnow().isoformat() + "Z",
            "symbol": symbol,
            "entry_order_id": entry_order_id,
            "side": side,
            "quantity": quantity,
            "entry_price": entry_price,
            "account_id": account_id,
            "reason": reason,
        }
        if error_type:
            rec["error_type"] = error_type
        if error_message:
            rec["error_message"] = (str(error_message) or "")[:500]
        with open(path, "a", encoding="utf-8") as f:
            f.write(json.dumps(rec, ensure_ascii=False) + "\n")
    except Exception as e:
        logger.warning(f"写入落库失败日志失败: {e}")


_TRAILING_LOG_PATH: Optional[Path] = None

def _get_trailing_stop_log_path() -> Optional[Path]:
    """保本/移动止损专用日志路径：logs/profit_protection.log，便于 tail/grep 确认是否执行。"""
    global _TRAILING_LOG_PATH
    if _TRAILING_LOG_PATH is not None:
        return _TRAILING_LOG_PATH
    try:
        root = Path(__file__).parent.parent
        log_dir = root / "logs"
        log_dir.mkdir(parents=True, exist_ok=True)
        _TRAILING_LOG_PATH = log_dir / "profit_protection.log"
        return _TRAILING_LOG_PATH
    except Exception:
        return None

def _log_trailing_stop_event(account_id: int, symbol: str, event: str, **kwargs):
    """将保本/移动止损相关事件写入专用日志（一行 JSON），便于确认是否正常执行。"""
    path = _get_trailing_stop_log_path()
    if not path:
        return
    try:
        rec = {
            "ts": datetime.utcnow().isoformat() + "Z",
            "account_id": account_id,
            "symbol": symbol,
            "event": event,
        }
        for k, v in kwargs.items():
            if v is not None and k not in rec:
                if isinstance(v, float):
                    rec[k] = round(v, 6)
                else:
                    rec[k] = v
        with open(path, "a", encoding="utf-8") as f:
            f.write(json.dumps(rec, ensure_ascii=False) + "\n")
    except Exception as e:
        logger.debug(f"写入保本/移动止损日志失败: {e}")


# User Data Stream 缓存（持仓/余额优先读 WS，减少 REST）
try:
    from .user_data_stream import get_stream_instance, get_positions_from_cache, get_balance_from_cache
except Exception:
    try:
        from user_data_stream import get_stream_instance, get_positions_from_cache, get_balance_from_cache
    except Exception:
        get_stream_instance = lambda: None
        get_positions_from_cache = lambda min_notional=1.0: []
        get_balance_from_cache = lambda: None


def _pullback_range_metrics_from_klines(
    klines: Optional[List],
    current_price: float,
) -> Optional[Tuple[float, float, float]]:
    """
    用近若干根 K 线的最高价/最低价构成区间，衡量当前价在区间内的相对位置。

    返回 (recent_low, recent_high, position_01)，其中 position_01∈[0,1]：
    - 0 表示贴在区间下沿（接近「前低」一侧）
    - 1 表示贴在区间上沿（接近「前高」一侧）

    多单若 position_01 过大，说明价格已跑到区间顶部，容易「一回撤就扫损」；
    空单若 position_01 过小，同理接近区间下沿追空风险大。
    """
    if not klines or current_price is None:
        return None
    try:
        cur = float(current_price)
    except (TypeError, ValueError):
        return None
    if cur <= 0:
        return None
    highs: List[float] = []
    lows: List[float] = []
    for k in klines:
        try:
            highs.append(float(k[2]))
            lows.append(float(k[3]))
        except (IndexError, TypeError, ValueError):
            continue
    if len(highs) < 2:
        return None
    hi = max(highs)
    lo = min(lows)
    if hi <= lo:
        return None
    pos = (cur - lo) / (hi - lo)
    return (lo, hi, pos)


class PositionManager:
    """仓位管理类"""
    
    def __init__(self, client: BinanceClient, risk_manager: RiskManager, account_id: int = None):
        """
        初始化仓位管理器
        
        Args:
            client: 币安客户端
            risk_manager: 风险管理器
            account_id: 账户ID（默认从环境变量或配置获取）
        """
        self.client = client
        self.risk_manager = risk_manager
        
        # 确定 account_id
        if account_id is not None:
            self.account_id = int(account_id)
        else:
            # 尝试从环境变量获取
            import os
            try:
                self.account_id = int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1)
            except:
                self.account_id = 1
                
        self.active_positions: Dict[str, Dict] = {}
        self._monitor_tasks: Dict[str, asyncio.Task] = {}  # WebSocket监控任务字典
        self._monitoring_enabled = True  # 是否启用实时监控
        self._pending_entry_orders: Dict[str, Dict] = {}  # 未成交的入场订单（避免重复挂单）
        self._last_entry_attempt_ms: Dict[str, int] = {}  # 每个symbol的最近一次尝试（冷却/去抖）
        # 自动平仓去抖/限流（避免止损触发后反复下单/刷屏）
        self._last_auto_close_attempt_ms: Dict[str, int] = {}
        self._last_auto_close_fail_log_ms: Dict[str, int] = {}
        # 「当前价已触发止损价，无法挂止损单→立即市价平仓」去重，避免同一 symbol 短时间多次打日志/重复平仓
        self._sl_protection_close_triggered_at: Dict[str, float] = {}  # symbol -> time.time()

    async def _get_open_positions(self, force_rest: bool = False) -> List[Dict]:
        """优先使用 User Data Stream 持仓缓存（Redis），无缓存或未启动时走 REST。多账号时必须传 account_id 读对应缓存。"""
        if not force_rest and get_stream_instance() is not None:
            min_notional = float(getattr(config, "POSITION_MIN_NOTIONAL_USDT", 1.0) or 1.0)
            redis_cache = getattr(self.client, "redis_cache", None)
            cached = await get_positions_from_cache(min_notional, redis_cache, account_id=self.account_id)
            if cached is not None:
                return cached
        return await self.client.get_open_positions()

    async def _get_account_balance(self) -> Dict:
        """优先使用 User Data Stream 余额缓存（Redis），无缓存时走 REST。"""
        if get_stream_instance() is not None:
            redis_cache = getattr(self.client, "redis_cache", None)
            bal = await get_balance_from_cache(redis_cache)
            if bal is not None:
                return bal
        return await self.client.get_account_balance()

    def _should_skip_sl_protection_close(self, symbol: str, cooldown_sec: float = 60.0) -> bool:
        """同一 symbol 在 cooldown_sec 内已触发过「价格已触发止损→立即市价平仓」则跳过，避免重复日志与重复平仓。"""
        now = time.time()
        to_remove = [s for s, t in self._sl_protection_close_triggered_at.items() if now - t > cooldown_sec]
        for s in to_remove:
            del self._sl_protection_close_triggered_at[s]
        return symbol in self._sl_protection_close_triggered_at

    def _mark_sl_protection_close_triggered(self, symbol: str) -> None:
        """标记该 symbol 已触发保护平仓（用于去重）。"""
        self._sl_protection_close_triggered_at[symbol] = time.time()

    @staticmethod
    def _pct_like_to_ratio(v: float) -> float:
        """
        将“看起来像百分比”的值转换为比例（0~1）。

        兼容两种来源：
        - 前端/后端按“比例”存储：0.006 表示 0.6%
        - 历史/默认值按“百分比数值”存储：0.6 表示 0.6%

        经验规则：
        - v > 1: 认为是 60/100 这种百分数，除以100
        - 0.05 < v <= 1: 也更可能是“0.6% 这种写法”，除以100
        - v <= 0.05: 更可能已经是比例（<=5%）
        """
        try:
            x = float(v or 0.0)
        except Exception:
            x = 0.0
        if x <= 0:
            return 0.0
        if x > 1.0:
            return x / 100.0
        if x > 0.05:
            return x / 100.0
        return x

    def _calc_limit_entry_price(self, current_price: float, side: str, offset_ratio: float) -> float:
        """根据当前价与偏移比例，计算限价入场价（BUY: 下方回调；SELL: 上方回调）"""
        try:
            cp = float(current_price)
        except Exception:
            cp = 0.0
        try:
            off = float(offset_ratio or 0.0)
        except Exception:
            off = 0.0
        if cp <= 0:
            return 0.0
        if (side or "").upper() == "BUY":
            return cp * (1 - off)
        return cp * (1 + off)

    async def _wait_for_order_filled(
        self,
        symbol: str,
        order_id: int,
        timeout_sec: int = 30,
        poll_sec: float = 1.0,
    ) -> Dict:
        """
        等待订单成交（FILLED），返回：
        { ok, status, avg_price, executed_qty, raw }
        """
        deadline = time.time() + max(1, int(timeout_sec or 1))
        last_status = None
        while time.time() < deadline:
            try:
                info = await self.client.client.futures_get_order(symbol=symbol, orderId=order_id, recvWindow=20000)
                status = info.get("status")
                last_status = status
                if status == "FILLED":
                    avg_price = float(info.get("avgPrice", 0) or 0) or float(info.get("price", 0) or 0)
                    executed_qty = float(info.get("executedQty", 0) or 0)
                    return {"ok": True, "status": status, "avg_price": avg_price, "executed_qty": executed_qty, "raw": info}
                if status in ("CANCELED", "REJECTED", "EXPIRED"):
                    return {"ok": False, "status": status, "avg_price": 0, "executed_qty": float(info.get("executedQty", 0) or 0), "raw": info}
            except Exception:
                # 忽略单次失败，继续轮询
                pass
            await asyncio.sleep(max(0.2, float(poll_sec or 1.0)))
        return {"ok": False, "status": last_status or "TIMEOUT", "avg_price": 0, "executed_qty": 0, "raw": None}
    
    async def open_position(
        self, 
        symbol: str, 
        change_percent: float,
        leverage: int = 10,
        trade_direction: Optional[str] = None,
        entry_reason: str = '',
        signal_strength: Optional[int] = None,
        market_regime: Optional[str] = None,
        trend_4h: Optional[str] = None,
        atr: Optional[float] = None,
        klines: Optional[List] = None,
        bollinger: Optional[Dict] = None,
        entry_context: Optional[Dict] = None,
        shadow_leverage_multiplier: float = 1.0,
    ) -> Optional[Dict]:
        """
        开仓
        
        Args:
            symbol: 交易对
            change_percent: 涨跌幅百分比
            leverage: 杠杆倍数
            
        Returns:
            订单信息，失败返回None
        """
        try:
            # 0) 防止同一 symbol 重复挂入场单/快速重复尝试（去抖 + 冷却）
            now_ms = int(time.time() * 1000)
            
            # ⚠️ 关键修复：检查是否已经在持仓中（防止重复开仓）
            if symbol in self.active_positions:
                logger.info(f"{symbol} [入场] 已在持仓中 (active_positions)，跳过重复开仓")
                return None

            cooldown_sec = int(config.TRADING_CONFIG.get("ENTRY_SYMBOL_COOLDOWN_SEC", 120) or 0)
            last_ms = self._last_entry_attempt_ms.get(symbol)
            if last_ms and cooldown_sec > 0 and now_ms - last_ms < cooldown_sec * 1000:
                logger.info(f"{symbol} [入场] 冷却中（{cooldown_sec}s），跳过本次自动开仓")
                return None

            pending = self._pending_entry_orders.get(symbol)
            if pending and pending.get("order_id"):
                logger.info(f"{symbol} [入场] 已有未完成入场订单(orderId={pending.get('order_id')})，跳过重复开仓")
                return None

            # 标记本次尝试（无论最终是否成交，都避免短时间内反复开仓/挂单）
            self._last_entry_attempt_ms[symbol] = now_ms

            # 判断是否应该交易
            if not await self.risk_manager.should_trade(symbol, change_percent):
                return None

            # 开仓前二次校验持仓数（同一轮扫描多 symbol 通过 should_trade 后串行开仓，避免超限）
            try:
                max_open = int(config.TRADING_CONFIG.get("MAX_OPEN_POSITIONS", 0) or 0)
                if max_open > 0:
                    positions = await self.client.get_open_positions(include_low_notional=True)
                    if positions and len(positions) >= max_open:
                        logger.info(f"{symbol} [入场] 开仓前复核：持仓数已达上限 {len(positions)}/{max_open}，跳过")
                        return None
            except Exception as e:
                logger.debug(f"{symbol} 开仓前持仓数复核异常（继续）: {e}")
            
            # 影子模式：对动态杠杆再乘系数（加仓/减仓名单）
            smx = float(shadow_leverage_multiplier or 1.0)
            if abs(smx - 1.0) > 1e-9:
                old_lev = int(leverage)
                adj_lev = int(round(leverage * smx))
                max_lev = int(config.TRADING_CONFIG.get('MAX_LEVERAGE', 125) or 125)
                min_lev = max(1, int(config.TRADING_CONFIG.get('MIN_LEVERAGE', 1) or 1))
                adj_lev = max(min_lev, min(adj_lev, max_lev))
                pa = "none"
                if entry_context and isinstance(entry_context.get("shadow_mode_applied"), dict):
                    pa = entry_context["shadow_mode_applied"].get("position_adjustment") or "none"
                reason_txt = ""
                if entry_context and isinstance(entry_context.get("shadow_mode_applied"), dict):
                    smd = entry_context["shadow_mode_applied"]
                    if pa == "increase" and smd.get("increase_detail"):
                        reason_txt = str(smd["increase_detail"].get("reason") or smd["increase_detail"].get("symbol") or "")
                    elif pa == "decrease" and smd.get("decrease_detail"):
                        reason_txt = str(smd["decrease_detail"].get("reason") or "")
                logger.info(
                    f"{symbol} [影子模式] 应用动态仓位调整：{smx}x（{old_lev}x→{adj_lev}x，position={pa}"
                    f"{', 建议: ' + reason_txt if reason_txt else ''}）"
                )
                leverage = adj_lev

            # 设置杠杆（确保为 int，避免动态杠杆传入 float 导致 API/range 报错）
            actual_leverage = await self.client.set_leverage(symbol, int(leverage))
            
            if actual_leverage <= 0:
                logger.error(f"{symbol} 无法设置有效杠杆，跳过开仓")
                return None
            
            # 使用实际生效的杠杆（可能被降级）
            if actual_leverage != int(leverage):
                logger.warning(f"{symbol} 杠杆被调整: {int(leverage)}x -> {actual_leverage}x")
                leverage = actual_leverage
            
            # 计算仓位大小（传入实际使用的杠杆）
            # ⚠️ 优化：先估算止损价格，用于固定风险百分比计算
            logger.info(f"开始为 {symbol} 计算仓位大小...")

            # 获取当前价格用于估算止损
            ticker = await self.client.get_ticker_24h(symbol)
            if not ticker:
                return None
            estimated_entry_price = ticker['price']
            estimated_side = trade_direction if trade_direction else ('BUY' if change_percent > 0 else 'SELL')

            # ===== 趋势入场过滤（防止在趋势尾部追价）=====
            try:
                use_trend_filter = bool(config.TRADING_CONFIG.get("USE_TREND_ENTRY_FILTER", False))
                if use_trend_filter and getattr(self.client, "redis_cache", None):
                    trend_state_key = f"trend_state:{symbol}"
                    trend_state = await self.client.redis_cache.get(trend_state_key)

                    if trend_state:
                        trend_dir = (trend_state.get("direction") or "").upper()
                        signal_price = float(trend_state.get("signal_price") or 0) or None

                        if signal_price and trend_dir in ("BUY", "SELL"):
                            # 使用更实时的价格（如有），否则使用估算价
                            realtime_price = None
                            try:
                                realtime_price = self.client.get_realtime_price(symbol)
                            except Exception:
                                realtime_price = None
                            current_price = float(realtime_price or estimated_entry_price)

                            max_move = float(config.TRADING_CONFIG.get("MAX_TREND_MOVE_BEFORE_ENTRY", 0.05) or 0.05)

                            too_late = False
                            move_pct = 0.0

                            if trend_dir == "BUY":
                                # 做多：如果当前价相比信号价已经上涨超过阈值，认为追在高位
                                if current_price > signal_price:
                                    move_pct = (current_price - signal_price) / signal_price
                                    if move_pct > max_move:
                                        too_late = True
                            elif trend_dir == "SELL":
                                # 做空：如果当前价相比信号价已经下跌超过阈值，认为追在低位
                                if current_price < signal_price:
                                    move_pct = (signal_price - current_price) / signal_price
                                    if move_pct > max_move:
                                        too_late = True

                            # 只有当当前入场方向与趋势方向一致时，才应用“太晚不追”规则
                            if estimated_side.upper() == trend_dir and too_late:
                                logger.info(
                                    f"{symbol} [入场过滤] 趋势方向={trend_dir}, 信号价={signal_price:.6f}, "
                                    f"当前价={current_price:.6f}, 累计趋势幅度={move_pct*100:.2f}%>允许上限={max_move*100:.2f}%，"
                                    f"认为本轮趋势入场时机已错过，跳过自动开仓"
                                )
                                return None
            except Exception as e:
                logger.debug(f"{symbol} 趋势入场过滤时出错（忽略，按正常逻辑继续）: {e}")

            # ===== 回撤/区间入场过滤（参考近 N 根 K 线的前低/前高，避免买在区间顶部、卖在区间底部）=====
            try:
                pb_enabled = bool(config.TRADING_CONFIG.get("ENTRY_PULLBACK_FILTER_ENABLED", False))
                if pb_enabled:
                    min_bars = max(3, int(config.TRADING_CONFIG.get("ENTRY_PULLBACK_MIN_BARS", 5) or 5))
                    lookback = max(min_bars, int(config.TRADING_CONFIG.get("ENTRY_PULLBACK_LOOKBACK_BARS", 24) or 24))
                    interval_pb = config.TRADING_CONFIG.get("ENTRY_PULLBACK_INTERVAL") or config.TRADING_CONFIG.get(
                        "ENTRY_INTERVAL", "1h"
                    )
                    max_long_pos = float(config.TRADING_CONFIG.get("ENTRY_PULLBACK_MAX_LONG_IN_RANGE", 0.62) or 0.62)
                    min_short_pos = float(config.TRADING_CONFIG.get("ENTRY_PULLBACK_MIN_SHORT_IN_RANGE", 0.38) or 0.38)
                    max_long_pos = min(1.0, max(0.0, max_long_pos))
                    min_short_pos = min(1.0, max(0.0, min_short_pos))
                    if min_short_pos > max_long_pos:
                        min_short_pos, max_long_pos = max_long_pos, min_short_pos

                    rt = None
                    try:
                        rt = self.client.get_realtime_price(symbol)
                    except Exception:
                        rt = None
                    cur_pb = float(rt or estimated_entry_price)

                    bars_pb = klines if klines and len(klines) >= min_bars else None
                    if not bars_pb:
                        try:
                            fetched = await self.client.get_klines(
                                symbol=symbol, interval=str(interval_pb), limit=lookback
                            )
                            bars_pb = fetched if fetched and len(fetched) >= min_bars else None
                        except Exception as e:
                            logger.debug(f"{symbol} 回撤过滤拉取K线失败（跳过该过滤）: {e}")
                            bars_pb = None

                    metrics = _pullback_range_metrics_from_klines(bars_pb, cur_pb)
                    if metrics is not None:
                        lo_pb, hi_pb, pos_pb = metrics
                        es = estimated_side.upper()
                        if es == "BUY" and pos_pb > max_long_pos:
                            logger.info(
                                f"{symbol} [回撤过滤] 做多跳过：现价={cur_pb:.6f} 在近{len(bars_pb)}根{interval_pb}区间"
                                f"[{lo_pb:.6f},{hi_pb:.6f}] 中位置={pos_pb:.2f} > 上限{max_long_pos:.2f}（接近区间上沿/前高，易回撤扫损）"
                            )
                            return None
                        if es == "SELL" and pos_pb < min_short_pos:
                            logger.info(
                                f"{symbol} [回撤过滤] 做空跳过：现价={cur_pb:.6f} 在近{len(bars_pb)}根{interval_pb}区间"
                                f"[{lo_pb:.6f},{hi_pb:.6f}] 中位置={pos_pb:.2f} < 下限{min_short_pos:.2f}（接近区间下沿/前低，易反弹扫损）"
                            )
                            return None
            except Exception as e:
                logger.debug(f"{symbol} 回撤/区间入场过滤异常（忽略，继续开仓流程）: {e}")
            
            # 估算止损价格（用于固定风险计算）
            estimated_stop_loss = None
            if atr and atr > 0:
                atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 2.5)  # 默认2.5，放宽止损提升胜率
                if estimated_side == 'BUY':
                    estimated_stop_loss = estimated_entry_price - (atr * atr_multiplier)
                else:  # SELL
                    estimated_stop_loss = estimated_entry_price + (atr * atr_multiplier)
            
            quantity, adjusted_leverage = await self.risk_manager.calculate_position_size(
                symbol, change_percent, leverage=leverage,
                entry_price=estimated_entry_price,
                stop_loss_price=estimated_stop_loss,
                side=estimated_side,
                atr=atr,
                signal_strength=signal_strength
            )
            
            # 如果 RiskManager 建议了更低的安全杠杆，则应用它
            if quantity is not None and adjusted_leverage is not None and adjusted_leverage != leverage:
                logger.info(f"{symbol} 风险控制调整杠杆: {leverage}x -> {adjusted_leverage}x (适应宽止损)")
                try:
                    new_lev = await self.client.set_leverage(symbol, int(adjusted_leverage))
                    if new_lev > 0:
                        leverage = new_lev
                    else:
                        logger.warning(f"{symbol} 调整杠杆失败，保持 {leverage}x")
                except Exception as e:
                    logger.error(f"{symbol} 调整杠杆失败: {e}，将使用原杠杆 {leverage}x 继续")
            
            if quantity is None:
                logger.warning(f"❌ {symbol} 仓位计算失败，跳过交易")
                logger.warning(f"   可能原因:")
                logger.warning(f"   1. 账户余额不足")
                logger.warning(f"   2. 单笔仓位超过限制")
                logger.warning(f"   3. 总仓位超过限制")
                logger.warning(f"   4. 无法获取价格数据")
                logger.warning(f"   5. 保证金不足最小要求（MIN_MARGIN_USDT）")
                logger.warning(f"   6. 名义价值小于0.2 USDT（避免无意义的小单子）")
                return None
            
            logger.info(f"✓ {symbol} 仓位计算成功: {quantity:.4f}")
            
            # 确定交易方向（优先使用技术指标信号）
            if trade_direction:
                side = trade_direction
            else:
                side = 'BUY' if change_percent > 0 else 'SELL'
            
            # 获取当前价格
            ticker = await self.client.get_ticker_24h(symbol)
            if not ticker:
                return None
            
            entry_price = ticker['price']
            
            # 获取K线数据用于动态止损计算（从symbol_info中获取，如果可用）
            klines = None
            bollinger = None
            if 'klines' in locals() or 'symbol_info' in locals():
                # 尝试从外部传入的symbol_info获取
                pass
            
            # 计算基于支撑/阻力的动态止损
            # 优先使用技术结构（支撑/阻力位、布林带）
            # 如果无法获取K线数据，回退到ATR或固定止损
            if not klines:
                # 如果没有传入K线数据，尝试获取
                try:
                    primary_interval = config.TRADING_CONFIG.get('PRIMARY_INTERVAL', '1h')
                    klines_data = await self.client.get_klines(
                        symbol=symbol,
                        interval=primary_interval,
                        limit=20  # 获取最近20根K线用于计算支撑/阻力
                    )
                    klines = klines_data if len(klines_data) >= 10 else None
                except Exception as e:
                    logger.debug(f"获取K线数据失败，使用固定止损: {e}")
                    klines = None
            
            # 在开仓前从Redis重新加载配置，确保使用最新配置（包括ATR参数）
            # 从Redis读取最新配置（轻量级，即时生效）
            try:
                if config._config_manager:
                    config._config_manager.reload_from_redis()
                    config.TRADING_CONFIG = config._get_trading_config()
                    logger.debug(f"{symbol} 开仓前已从Redis重新加载配置")
            except Exception as e:
                logger.debug(f"从Redis重新加载配置失败: {e}")
            
            # ===== 智能入场（方案C：趋势强更少错过，震荡更保守）=====
            smart_entry_enabled = bool(config.TRADING_CONFIG.get("SMART_ENTRY_ENABLED", True))
            # LIMIT_ORDER_OFFSET_PCT：兼容“比例/百分比”两种存储方式
            limit_offset_ratio = self._pct_like_to_ratio(float(config.TRADING_CONFIG.get("LIMIT_ORDER_OFFSET_PCT", 0.5) or 0.0))

            # 规则：趋势(trending) 或 4H共振明显 + 强信号 -> 允许“超时后市价兜底（有追价上限）”
            mr = (market_regime or "").strip().lower() if market_regime else ""
            t4 = (trend_4h or "").strip().lower() if trend_4h else ""
            ss = int(signal_strength) if signal_strength is not None else 0

            allow_market_fallback = False
            if mr == "trending":
                allow_market_fallback = True
            if ss >= int(config.TRADING_CONFIG.get("SMART_ENTRY_STRONG_SIGNAL", 8) or 8) and t4 in ("up", "down"):
                allow_market_fallback = True

            # 追价上限：超过就不追，宁愿错过（避免回到“无脑追价高频打损”）
            drift_ratio_trending = self._pct_like_to_ratio(float(config.TRADING_CONFIG.get("ENTRY_MAX_DRIFT_PCT_TRENDING", 0.6) or 0.6))
            drift_ratio_ranging = self._pct_like_to_ratio(float(config.TRADING_CONFIG.get("ENTRY_MAX_DRIFT_PCT_RANGING", 0.3) or 0.3))
            max_drift_ratio = drift_ratio_trending if allow_market_fallback else drift_ratio_ranging

            # 总等待/追价参数
            timeout_sec = int(config.TRADING_CONFIG.get("ENTRY_TIMEOUT_SEC", 180) or 180)
            step_wait_sec = int(config.TRADING_CONFIG.get("ENTRY_STEP_WAIT_SEC", 15) or 15)
            chase_steps = int(config.TRADING_CONFIG.get("ENTRY_CHASE_MAX_STEPS", 4) or 4)
            market_fallback_after_sec = int(config.TRADING_CONFIG.get("ENTRY_MARKET_FALLBACK_AFTER_SEC", 45) or 45)

            # 初始限价（回调入场）
            current_px = float(entry_price)
            initial_limit = self._calc_limit_entry_price(current_px, side, limit_offset_ratio)
            if initial_limit <= 0:
                return None

            order = None
            entry_order_id = None
            order_status = None
            actual_entry_price = None
            filled_quantity = 0.0
            entry_mode_used = "limit-only" if not smart_entry_enabled else ("limit+fallback" if allow_market_fallback else "limit-chase")

            # 生成 client_order_id：先落库 pending，WS/对账 按 client_order_id 匹配完善，确保限价/条件单事后成交也能对应 DB
            prefix = (config.TRADING_CONFIG.get("SYSTEM_ORDER_ID_PREFIX") or "").strip()
            prefix = prefix or "ats_"  # 无配置时用默认前缀，保证系统单始终有 pending 可对账
            client_order_id = f"{prefix}_{int(time.time() * 1000)}_{random.randint(0, 0xFFFF):04x}"[:36]
            pending_trade_id = None
            if DB_AVAILABLE and Trade:
                try:
                    logger.info(f"[DB] {symbol} 写入 pending 记录 client_order_id={client_order_id!r} (保证与下单一致性)")
                    pending_trade_id = Trade.create(
                symbol=symbol,
                side=side,
                quantity=quantity,
                        entry_price=entry_price,
                        leverage=leverage,
                        entry_reason=entry_reason,
                        entry_order_id=None,
                        client_order_id=client_order_id,
                        stop_loss_price=None,
                        take_profit_price=None,
                        take_profit_1=None,
                        take_profit_2=None,
                        atr=atr,
                        notional_usdt=None,
                        margin_usdt=None,
                        account_id=self.account_id,
                        entry_context=entry_context,
                        status="pending",
                    )
                    logger.info(f"[DB] {symbol} pending 已落库 id={pending_trade_id} client_order_id={client_order_id!r}")
                except Exception as e:
                    logger.error(
                        f"[DB] {symbol} 创建 pending 记录失败，将导致后续无法按 client_order_id 完善，易产生补单: "
                        f"client_order_id={client_order_id!r} error_type={type(e).__name__} error={e}"
                    )
                    _log_trade_db_failure(
                        symbol=symbol, entry_order_id=None, side=side, quantity=quantity, entry_price=entry_price,
                        account_id=self.account_id, reason="pending_create_failed",
                        error_type=type(e).__name__, error_message=str(e)
                    )
                    pending_trade_id = None

            if not smart_entry_enabled:
                # 根治方案：关闭智能入场后，回归“纯限价单模式”
                # - 不追价
                # - 不市价兜底
                # - 未在确认时间内成交则撤单并跳过（属于策略未触发入场，不是系统错误）
                confirm_timeout = int(config.TRADING_CONFIG.get("ENTRY_CONFIRM_TIMEOUT_SEC", 30) or 30)
                logger.info(
                    f"{symbol} [纯限价入场] side={side} | 限价={initial_limit:.6f} (offset={limit_offset_ratio*100:.2f}%) | "
                    f"确认超时={confirm_timeout}s（未成交将撤单跳过）"
                )
                order = await self.client.place_order(
                    symbol=symbol, side=side, quantity=quantity, order_type="LIMIT", price=initial_limit,
                    new_client_order_id=client_order_id
                )
                if not order:
                    if pending_trade_id and DB_AVAILABLE and Trade:
                        try:
                            ok = Trade.update_status(pending_trade_id, "cancelled")
                            if not ok:
                                logger.error(f"[DB] {symbol} 下单失败后更新 pending 为 cancelled 失败 trade_id={pending_trade_id}")
                        except Exception as ex:
                            logger.error(f"[DB] {symbol} 下单失败后更新 pending 为 cancelled 异常 trade_id={pending_trade_id} error={ex}")
                    return None
                entry_order_id = order.get("orderId")
                if entry_order_id:
                    self._pending_entry_orders[symbol] = {"order_id": entry_order_id, "created_at_ms": int(time.time() * 1000)}
            else:
                # 1) 先挂限价单
                logger.info(
                    f"{symbol} [智能入场] 模式={entry_mode_used} | side={side} | "
                    f"marketRegime={market_regime} trend_4h={trend_4h} strength={ss}/10 | "
                    f"初始限价={initial_limit:.6f} (offset={limit_offset_ratio*100:.2f}%) | "
                    f"追价上限={max_drift_ratio*100:.2f}%"
                )

                order = await self.client.place_order(
                    symbol=symbol, side=side, quantity=quantity, order_type="LIMIT", price=initial_limit,
                    new_client_order_id=client_order_id
                )
                if not order:
                    if pending_trade_id and DB_AVAILABLE and Trade:
                        try:
                            ok = Trade.update_status(pending_trade_id, "cancelled")
                            if not ok:
                                logger.error(f"[DB] {symbol} 智能入场首单失败后更新 pending 为 cancelled 失败 trade_id={pending_trade_id}")
                        except Exception as ex:
                            logger.error(f"[DB] {symbol} 智能入场首单失败后更新 pending 为 cancelled 异常 trade_id={pending_trade_id} error={ex}")
                    return None
                entry_order_id = order.get("orderId")
                if entry_order_id:
                    self._pending_entry_orders[symbol] = {"order_id": entry_order_id, "created_at_ms": int(time.time() * 1000)}

                start_ts = time.time()
                # 2) 分步等待 + 追价（逐步减少 offset），并在趋势强时允许市价兜底（有追价上限）
                for step in range(max(1, chase_steps)):
                    # 先等待一段时间看是否成交
                    wait_res = await self._wait_for_order_filled(symbol, int(entry_order_id), timeout_sec=step_wait_sec, poll_sec=1.0)
                    order_status = wait_res.get("status")
                    if wait_res.get("ok"):
                        actual_entry_price = float(wait_res.get("avg_price") or 0)
                        filled_quantity = float(wait_res.get("executed_qty") or 0)
                        break

                    # 未成交：如果超时太久且允许市价兜底，检查追价上限后转市价
                    elapsed = time.time() - start_ts
                    ticker2 = await self.client.get_ticker_24h(symbol)
                    cur2 = float(ticker2.get("price")) if ticker2 else current_px
                    drift_ratio = 0.0
                    try:
                        base = float(initial_limit) if float(initial_limit) > 0 else cur2
                        drift_ratio = abs((cur2 - base) / base)
                    except Exception:
                        drift_ratio = 0.0

                    if allow_market_fallback and elapsed >= market_fallback_after_sec:
                        if drift_ratio <= max_drift_ratio:
                            try:
                                await self.client.cancel_order(symbol, int(entry_order_id))
                            except Exception:
                                pass
                            self._pending_entry_orders.pop(symbol, None)
                            logger.info(f"{symbol} [智能入场] 限价超时，且偏离{drift_ratio*100:.2f}%≤{max_drift_ratio*100:.2f}%，转市价兜底")
                            order = await self.client.place_order(
                                symbol=symbol, side=side, quantity=quantity, order_type="MARKET",
                                new_client_order_id=client_order_id
                            )
                            # 关键：转市价后必须更新 entry_order_id，否则后续会继续查询“已取消的旧限价单”，导致误判 CANCELED
                            try:
                                entry_order_id = order.get("orderId") if isinstance(order, dict) else None
                            except Exception:
                                entry_order_id = None
                            break
                        else:
                            logger.info(f"{symbol} [智能入场] 限价超时，但偏离{drift_ratio*100:.2f}%>{max_drift_ratio*100:.2f}%，取消并放弃本次交易")
                            try:
                                await self.client.cancel_order(symbol, int(entry_order_id))
                            except Exception:
                                pass
                            self._pending_entry_orders.pop(symbol, None)
                            return None

                    # 震荡/不允许市价兜底：尝试追价（减小 offset -> 更靠近当前价），但不突破追价上限
                    try:
                        await self.client.cancel_order(symbol, int(entry_order_id))
                    except Exception:
                        pass

                    # offset 逐步减少到 0（越追越接近当前价）
                    step_ratio = (step + 1) / max(1, chase_steps)
                    cur_offset_ratio = max(0.0, limit_offset_ratio * (1.0 - step_ratio))
                    desired = self._calc_limit_entry_price(cur2, side, cur_offset_ratio)
                    if side == "BUY":
                        cap = initial_limit * (1 + max_drift_ratio)
                        desired = min(desired, cap)
                    else:
                        cap = initial_limit * (1 - max_drift_ratio)
                        desired = max(desired, cap)

                    if desired <= 0:
                        self._pending_entry_orders.pop(symbol, None)
                        return None

                    logger.info(
                        f"{symbol} [智能入场] 追价 step={step+1}/{chase_steps} | 当前价={cur2:.6f} | "
                        f"offset={cur_offset_ratio*100:.3f}% -> 限价={desired:.6f} | 偏离={drift_ratio*100:.2f}%"
                    )
                    order = await self.client.place_order(
                        symbol=symbol, side=side, quantity=quantity, order_type="LIMIT", price=desired,
                        new_client_order_id=client_order_id
                    )
                    if not order:
                        if pending_trade_id and DB_AVAILABLE and Trade:
                            try:
                                ok = Trade.update_status(pending_trade_id, "cancelled")
                                if not ok:
                                    logger.error(f"[DB] {symbol} 追价下单失败后更新 pending 为 cancelled 失败 trade_id={pending_trade_id}")
                            except Exception as ex:
                                logger.error(f"[DB] {symbol} 追价下单失败后更新 pending 为 cancelled 异常 trade_id={pending_trade_id} error={ex}")
                        self._pending_entry_orders.pop(symbol, None)
                        return None
                    entry_order_id = order.get("orderId")
                    if entry_order_id:
                        self._pending_entry_orders[symbol] = {"order_id": entry_order_id, "created_at_ms": int(time.time() * 1000)}

                # 如果是市价兜底或最终限价成交，这里统一继续后续流程（下面会再查实际成交）

            # ===== 统一处理：确认订单成交并获取实际成交价/数量 =====
            if order:
                if not entry_order_id:
                    entry_order_id = order.get("orderId")
                if entry_order_id:
                    logger.info(f"{symbol} [开仓] 币安订单号: {entry_order_id}")
            
                # 等待订单成交，检查订单状态并获取实际成交价格
                # 只有在订单真正成交（FILLED）后才保存到数据库
                if entry_order_id:
                    # 智能入场的限价订单可能需要更长等待，这里给一个总等待兜底（默认 30s）
                    confirm_timeout = int(config.TRADING_CONFIG.get("ENTRY_CONFIRM_TIMEOUT_SEC", 30) or 30)
                    res = await self._wait_for_order_filled(symbol, int(entry_order_id), timeout_sec=confirm_timeout, poll_sec=1.0)
                    order_status = res.get("status")
                    if res.get("ok"):
                        actual_entry_price = float(res.get("avg_price") or 0)
                        filled_quantity = float(res.get("executed_qty") or 0)
                    else:
                        # 未成交（NEW/超时/CANCELED 等）：撤单。撤单后校验是否已成交（竞态：撤单瞬间订单可能已成交）
                        logger.warning(f"{symbol} [开仓] 未成交，状态: {order_status}，撤销挂单")
                        try:
                            await self.client.cancel_order(symbol, int(entry_order_id))
                        except Exception:
                            pass
                        # 正向流程加固：撤单后校验，若已成交则继续流程
                        recheck = await self._wait_for_order_filled(symbol, int(entry_order_id), timeout_sec=5, poll_sec=0.5)
                        if recheck.get("ok"):
                            actual_entry_price = float(recheck.get("avg_price") or 0)
                            filled_quantity = float(recheck.get("executed_qty") or 0)
                            logger.info(f"{symbol} [开仓] 撤单后发现已成交，继续完善记录 (成交价={actual_entry_price:.4f} 数量={filled_quantity:.4f})")
                        else:
                            self._pending_entry_orders.pop(symbol, None)
                    return None
                
                if not actual_entry_price or actual_entry_price <= 0:
                    logger.error(f"{symbol} [开仓] ❌ 无法获取实际成交价格，不保存到数据库")
                    self._pending_entry_orders.pop(symbol, None)
                    return None
                
                if filled_quantity <= 0:
                    logger.error(f"{symbol} [开仓] ❌ 成交数量为0，不保存到数据库")
                    self._pending_entry_orders.pop(symbol, None)
                    return None
                
                # 使用实际成交价格和数量
                original_entry_price = entry_price
                entry_price = actual_entry_price
                quantity = filled_quantity  # 使用实际成交数量
                logger.info(f"{symbol} [开仓] ✓ 使用实际成交价格: {entry_price:.4f} USDT (下单时价格: {original_entry_price:.4f}), 成交数量: {quantity:.4f}")

                # 成交后清理 pending
                self._pending_entry_orders.pop(symbol, None)

                # ===== 成交后基于“实际成交价/数量”重新计算止损止盈（修复限价/滑点导致的偏差）=====
                stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.03)
                # ⚠️ 关键修复：配置值格式转换（兼容百分比形式和比例形式）
                if stop_loss_pct_margin is not None and stop_loss_pct_margin > 1:
                    stop_loss_pct_margin = stop_loss_pct_margin / 100.0
                stop_loss_price = self.risk_manager.get_stop_loss_price(
                    entry_price, side, quantity, leverage,
                    stop_loss_pct=stop_loss_pct_margin,
                    klines=klines,
                    bollinger=bollinger,
                    atr=atr
                )

                # ⚠️ 2026-01-29优化：计算止损距离用于盈亏比止盈计算（确保达到3:1目标）
                # 实际止损距离（用于 TP1 最小盈亏比，必须与真实挂单一致，避免 SL 被保证金封顶后 TP 仍按 ATR 拉很远）
                actual_stop_distance = (entry_price - stop_loss_price) if side == 'BUY' else (stop_loss_price - entry_price)
                stop_distance_for_tp = actual_stop_distance
                # 如果ATR可用，使用ATR计算更准确的止损距离（仅用于 get_take_profit_price 的盈亏比，不用于 TP1 的 MIN_RR_FOR_TP1）
                if atr is not None and atr > 0 and entry_price > 0:
                    atr_percent = atr / entry_price
                    atr_multiplier = config.TRADING_CONFIG.get('ATR_STOP_LOSS_MULTIPLIER', 3.0)  # 2026-02-12优化：默认3.0，避免噪音止损
                    stop_distance_for_tp = entry_price * atr_percent * atr_multiplier

                take_profit_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.30)
                # ⚠️ 关键修复：配置值格式转换（兼容百分比形式和比例形式）
                if take_profit_pct_margin is not None and take_profit_pct_margin > 1:
                    take_profit_pct_margin = take_profit_pct_margin / 100.0
                if take_profit_pct_margin is None or take_profit_pct_margin == 0:
                    take_profit_pct_margin = float(stop_loss_pct_margin or 0) * 3.0
                take_profit_price = self.risk_manager.get_take_profit_price(
                    entry_price, side, quantity, leverage,
                    take_profit_pct=take_profit_pct_margin,
                    atr=atr,
                    stop_distance=stop_distance_for_tp
                )

                # 分步止盈（基于“实际成交价 + 已计算的止损/止盈”）
                # 交易规模：名义/保证金（用于统计总交易量与UI展示）
                try:
                    notional_usdt = float(entry_price) * float(quantity)
                except Exception:
                    notional_usdt = None
                try:
                    margin_usdt = (float(notional_usdt) / float(leverage)) if notional_usdt is not None and float(leverage) > 0 else notional_usdt
                except Exception:
                    margin_usdt = None
                
                # 分步止盈（基于"实际成交价 + 已计算的止损/止盈"）
                # ⚠️ 第一目标使用 TAKE_PROFIT_1_PERCENT（默认15%），与第二目标 TAKE_PROFIT_PERCENT 分离，提高整体盈亏比
                # 第一目标和触发条件必须一致，都使用 TAKE_PROFIT_1_PERCENT
                take_profit_1_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.20)
                # 兼容百分比形式和比例形式
                if take_profit_1_pct_margin is not None and take_profit_1_pct_margin > 1:
                    take_profit_1_pct_margin = take_profit_1_pct_margin / 100.0
                
                # 计算基于保证金的止盈距离
                tp1_distance = 0.0
                if margin_usdt and margin_usdt > 0 and quantity > 0:
                    take_profit_1_amount = margin_usdt * take_profit_1_pct_margin
                    tp1_distance = take_profit_1_amount / quantity

                # ⚠️ 2026-02-10优化：确保TP1至少有 1.2倍 的盈亏比 (相对于【实际】止损距离)
                # 使用 actual_stop_distance（真实挂单距离），避免 SL 被保证金封顶后仍用 ATR 宽距离推高 TP 导致 4% SL vs 62% TP 的畸形
                if actual_stop_distance is not None and actual_stop_distance > 0:
                    min_rr_for_tp1 = config.TRADING_CONFIG.get('MIN_RR_FOR_TP1', 1.5)
                    min_tp1_distance = actual_stop_distance * min_rr_for_tp1
                    if min_tp1_distance > tp1_distance:
                        logger.info(f"{symbol} [优化] TP1距离 ({tp1_distance:.4f}) 小于 {min_rr_for_tp1}倍实际止损距离 ({min_tp1_distance:.4f})，已自动调整以保证盈亏比")
                        tp1_distance = min_tp1_distance

                # ⚠️ 2026-02-12优化：确保TP1至少覆盖双向手续费（避免微利单实际上亏损）
                # 手续费率估算：0.05% (Taker) * 2 (双向) * 1.5 (安全系数) = 0.15% 价格变动
                min_fee_distance = entry_price * 0.0015
                if min_fee_distance > tp1_distance:
                    logger.info(f"{symbol} [优化] TP1距离 ({tp1_distance:.4f}) 小于 手续费磨损距离 ({min_fee_distance:.4f})，已自动调整以覆盖成本")
                    tp1_distance = min_fee_distance

                # ⚠️ 止盈上限：TP1 不超过配置的止盈比例，避免“4% 止损 vs 62% 止盈”的畸形（与 USE_MARGIN_CAP_FOR_TP 一致）
                if margin_usdt and margin_usdt > 0 and quantity > 0:
                    tp1_max_pct = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.20)
                    if tp1_max_pct is not None and tp1_max_pct > 1:
                        tp1_max_pct = tp1_max_pct / 100.0
                    tp1_max_pct = (tp1_max_pct or 0.20) * 1.5  # 允许第一目标最多约 30% 保证金
                    tp1_max_amount = margin_usdt * tp1_max_pct
                    tp1_max_distance = tp1_max_amount / quantity
                    if tp1_distance > tp1_max_distance:
                        logger.info(f"{symbol} [优化] TP1距离 ({tp1_distance:.4f}) 超过止盈上限 ({tp1_max_distance:.4f}，约{tp1_max_pct*100:.0f}% margin)，已封顶")
                        tp1_distance = tp1_max_distance

                if tp1_distance > 0:
                    if side == 'BUY':
                        take_profit_1 = entry_price + tp1_distance
                    else:
                        take_profit_1 = entry_price - tp1_distance
                else:
                    if side == 'BUY':
                        take_profit_1 = entry_price + (entry_price - stop_loss_price)
                    else:
                        take_profit_1 = entry_price - (stop_loss_price - entry_price)
                take_profit_2 = take_profit_price
                if take_profit_1 is not None and take_profit_2 is not None:
                    if side == 'BUY':
                        if take_profit_1 >= take_profit_2 and take_profit_2 > entry_price:
                            closer = min(take_profit_1, take_profit_2)
                            further = max(take_profit_1, take_profit_2)
                            if closer > entry_price:
                                take_profit_1 = closer
                                take_profit_2 = further
                    else:
                        if take_profit_1 <= take_profit_2 and take_profit_2 < entry_price:
                            closer = max(take_profit_1, take_profit_2)
                            further = min(take_profit_1, take_profit_2)
                            if closer < entry_price:
                                take_profit_1 = closer
                                take_profit_2 = further
                
                # 记录到数据库：有 pending 则完善（WS 也会按 client_order_id 完善），否则新建
                trade_id = None
                if DB_AVAILABLE and Trade:
                    try:
                        if client_order_id:
                            row = Trade.get_by_client_order_id(client_order_id, self.account_id)
                            if row and str(row.get("status")) == "pending":
                                ok = Trade.update_pending_to_filled(
                                    client_order_id, self.account_id,
                                    entry_order_id, entry_price, quantity
                                )
                                if ok:
                                    row = Trade.get_by_client_order_id(client_order_id, self.account_id)
                                    trade_id = row.get("id") if row else None
                                    if trade_id:
                                        try:
                                            Trade.update_open_fields(
                                                trade_id,
                                                stop_loss_price=stop_loss_price,
                                                take_profit_price=take_profit_price,
                                                take_profit_1=take_profit_1,
                                                take_profit_2=take_profit_2,
                                                notional_usdt=notional_usdt,
                                                margin_usdt=margin_usdt,
                                                entry_context=entry_context,
                                                atr=atr,
                                            )
                                            logger.info(f"[DB] {symbol} 已完善 pending→open (ID: {trade_id}, orderId: {entry_order_id}, 成交价: {entry_price:.4f}, 数量: {quantity:.4f})")
                                        except Exception as of_ex:
                                            logger.error(f"[DB] {symbol} 完善 open 字段失败 trade_id={trade_id} (SL/TP/notional 等未写入): {of_ex}")
                                else:
                                    logger.error(
                                        f"[DB] {symbol} 完善 pending→open 失败，将走新建兜底或依赖补单: "
                                        f"client_order_id={client_order_id!r} entry_order_id={entry_order_id}"
                                    )
                            elif row and str(row.get("status")) != "pending":
                                logger.debug(f"[DB] {symbol} 已有非 pending 记录 status={row.get('status')}，将新建或跳过")
                        if trade_id is None:
                            # 无 pending 或未匹配到：走新建（兜底）
                            fallback_client_order_id = (order.get("clientOrderId") if order else None) or client_order_id
                            logger.info(f"[DB] {symbol} 无 pending 记录，新建 open 记录 client_order_id={fallback_client_order_id!r} entry_order_id={entry_order_id}")
                            # 如果 REST 已获取到 entry_order_id，直接写入；否则留空，等待 WS 推送或后续同步补全
                            trade_id = Trade.create(
                                symbol=symbol,
                                side=side,
                                quantity=quantity,
                                entry_price=entry_price,
                                leverage=leverage,
                                entry_reason=entry_reason,
                                entry_order_id=entry_order_id,  # REST 已获取则直接写入
                                client_order_id=fallback_client_order_id,
                                stop_loss_price=stop_loss_price,
                                take_profit_price=take_profit_price,
                                take_profit_1=take_profit_1,
                                take_profit_2=take_profit_2,
                                atr=atr,
                                notional_usdt=notional_usdt,
                                margin_usdt=margin_usdt,
                                entry_context=entry_context,
                                account_id=self.account_id,
                            )
                            if entry_order_id:
                                logger.info(f"✓ {symbol} 交易记录已保存到数据库 (ID: {trade_id}, 订单号: {entry_order_id}, 成交价: {entry_price:.4f}, 成交数量: {quantity:.4f})")
                            else:
                                logger.warning(f"⚠ {symbol} 交易记录已保存但 entry_order_id 为空 (ID: {trade_id})，等待 WS 推送或后续同步补全")
                                # 如果有 client_order_id，尝试通过 REST 查询订单号补全
                                if fallback_client_order_id:
                                    try:
                                        # 延迟查询，确保订单已入库
                                        await asyncio.sleep(1)
                                        orders = await self.client.client.futures_get_all_orders(
                                            symbol=symbol, limit=10, recvWindow=10000
                                        )
                                        for o in orders or []:
                                            if str(o.get("clientOrderId", "")).strip() == fallback_client_order_id:
                                                found_order_id = o.get("orderId")
                                                if found_order_id:
                                                    Trade.update_entry_order_id(trade_id, found_order_id)
                                                    logger.info(f"✓ {symbol} 已通过 REST 补全 entry_order_id: {found_order_id}")
                                                    break
                                    except Exception as e:
                                        logger.warning(f"[DB] {symbol} REST 补全 entry_order_id 失败 trade_id={trade_id}: {e}")
                    except Exception as e:
                        logger.error(f"❌ 保存交易记录到数据库失败: {e}")
                        logger.error(f"   错误类型: {type(e).__name__}")
                        import traceback
                        logger.error(f"   错误详情:\n{traceback.format_exc()}")
                        _log_trade_db_failure(
                            symbol=symbol, entry_order_id=entry_order_id, side=side,
                            quantity=quantity, entry_price=entry_price, account_id=self.account_id,
                            reason="create_exception", error_type=type(e).__name__, error_message=str(e)
                        )
                        return None
                elif not DB_AVAILABLE:
                    logger.error(f"[DB] 数据库不可用，无法保存成交记录 symbol={symbol} entry_order_id={entry_order_id} client_order_id={client_order_id!r}，将依赖补单")
                    _log_trade_db_failure(
                        symbol=symbol, entry_order_id=entry_order_id, side=side,
                        quantity=quantity, entry_price=entry_price, account_id=self.account_id,
                        reason="DB_AVAILABLE=False"
                    )
                elif not Trade:
                    logger.error(f"[DB] Trade 模型未导入，无法保存成交记录 symbol={symbol} entry_order_id={entry_order_id}，将依赖补单")
                    _log_trade_db_failure(
                        symbol=symbol, entry_order_id=entry_order_id, side=side,
                        quantity=quantity, entry_price=entry_price, account_id=self.account_id,
                        reason="Trade=None"
                    )
                
                # 记录持仓信息（包含动态止损止盈和分步止盈）
                from datetime import datetime
                position_info = {
                    'symbol': symbol,
                    'side': side,
                    'quantity': quantity,
                    'entryPrice': entry_price,
                    'changePercent': change_percent,
                    'orderId': order.get('orderId'),
                    'tradeId': trade_id,  # 数据库交易ID
                    'stopLoss': stop_loss_price,
                    'takeProfit': take_profit_price,  # 保留原始止盈价（第二目标）
                    'takeProfit1': take_profit_1,  # 第一目标（盈亏比1:1，了结50%）
                    'takeProfit2': take_profit_2,  # 第二目标（原始止盈价，剩余50%）
                    'partialProfitTaken': False,  # 是否已部分止盈
                    'remainingQuantity': quantity,  # 剩余仓位数量
                    'initialStopLoss': stop_loss_price,  # 初始止损（用于移动止损）
                    'leverage': leverage,
                    'entryReason': entry_reason,
                    'entryTime': get_beijing_time(),  # 记录入场时间（使用北京时间，用于计算持仓持续时间）
                    'strategyType': 'trend_following',  # 策略类型（简化后只有趋势跟踪）
                    'atr': atr,
                    'maxProfit': 0.0,  # 记录最大盈利（用于移动止损/滞涨判断）
                    'lastNewHighTs': None,  # 最后一次创新高的时间戳（秒），用于滞涨早止盈
                    'stagnationExitTriggered': False,  # 是否已执行滞涨分批减仓+抬止损（只执行一次）
                    'trailingStopActivated': False,  # 移动止损是否已激活
                    'breakevenStopSet': False,  # 是否已执行“盈利达阈值后移至保本”
                    'profitLockStage': 0,  # 分层锁盈阶段：0=未触发，1/2=已触发对应层
                    'account_id': self.account_id
                }
                
                self.active_positions[symbol] = position_info
                
                logger.info(
                    f"开仓成功: {symbol} {side} {quantity} @ {entry_price:.4f} "
                    f"(涨跌幅: {change_percent:.2f}%)"
                )
                
                # 验证持仓是否真的在币安存在
                try:
                    await asyncio.sleep(0.5)  # 等待一小段时间让币安更新持仓
                    positions = await self._get_open_positions()
                    binance_position = next(
                        (p for p in positions if p['symbol'] == symbol and float(p.get('positionAmt', 0)) != 0),
                        None
                    )
                    if binance_position:
                        logger.info(
                            f"{symbol} [开仓验证] ✓ 币安持仓确认: "
                            f"数量={float(binance_position.get('positionAmt', 0)):.4f}, "
                            f"入场价={float(binance_position.get('entryPrice', 0)):.4f}"
                        )
                        # 在币安侧挂“止损/止盈保护单”，避免仅依赖本地监控（服务重启/网络波动时更安全）
                        try:
                            current_mark = None
                            try:
                                current_mark = float(binance_position.get("markPrice", 0) or 0) or None
                            except Exception:
                                current_mark = None
                            await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_mark)
                        except Exception as e:
                            logger.warning(f"{symbol} 挂币安止盈止损失败（不影响持仓监控）: {e}")
                    else:
                        logger.warning(
                            f"{symbol} [开仓验证] ⚠️ 币安账户中没有持仓，可能订单未成交或被立即平仓"
                        )
                        # 清理本地记录
                        if symbol in self.active_positions:
                            del self.active_positions[symbol]
                        # 如果数据库已保存，标记为取消
                        if trade_id and DB_AVAILABLE and Trade:
                            try:
                                from database.connection import db
                                db.execute_update(
                                    "UPDATE trades SET status = 'cancelled' WHERE id = %s",
                                    (trade_id,)
                                )
                                logger.info(f"{symbol} [开仓验证] 已更新数据库状态为 cancelled (ID: {trade_id})")
                            except Exception as e:
                                logger.warning(f"{symbol} [开仓验证] 更新数据库状态失败: {e}")
                        return None
                except Exception as verify_error:
                    logger.warning(f"{symbol} [开仓验证] 验证持仓时出错: {verify_error}，继续使用本地记录")
                
                # 启动WebSocket实时监控
                if self._monitoring_enabled:
                    await self._start_position_monitoring(symbol)

                # 记录“今日开仓次数”（用于用户风控旋钮 MAX_DAILY_ENTRIES）
                try:
                    await self.risk_manager.record_entry(symbol)
                except Exception:
                    pass
                
                return position_info
            
            return None
            
        except Exception as e:
            err_msg = str(e).strip() if str(e) else f"{type(e).__name__}(无详情)"
            logger.error(f"开仓失败 {symbol}: {err_msg}", exc_info=True)
            return None
    
    async def close_position(self, symbol: str, reason: str = 'manual') -> bool:
        """
        平仓
        
        Args:
            symbol: 交易对
            reason: 平仓原因（manual, stop_loss, take_profit, trailing_stop, sync, take_profit_partial_then_take_profit, take_profit_partial_then_stop, take_profit_partial_then_trailing_stop）
            
        Returns:
            是否成功
        """
        try:
            logger.info(f"{symbol} [平仓] 开始平仓操作 (原因: {reason})")

            # 先取消币安侧的保护单（避免平仓后残留委托导致反向开仓/误触发）
            try:
                info0 = self.active_positions.get(symbol) if hasattr(self, "active_positions") else None
                if info0 and isinstance(info0, dict):
                    for k in ("exchangeSlOrderId", "exchangeTpOrderId"):
                        oid = info0.get(k)
                        if oid:
                            try:
                                await self.client.futures_cancel_algo_order(int(oid))
                            except Exception:
                                pass
                    info0.pop("exchangeSlOrderId", None)
                    info0.pop("exchangeTpOrderId", None)
            except Exception:
                pass
            
            # 获取当前持仓
            positions = await self._get_open_positions()
            position = next(
                (p for p in positions if p['symbol'] == symbol),
                None
            )

            # 缓存未命中时 REST 复核：_get_open_positions 可能来自缓存，若缓存无该 symbol 会误判为无持仓，但币安实际有仓（如对冲模式、多账号缓存隔离不全、未刷新）
            if not position:
                live_amt = await self._get_live_position_amt(symbol, position_side=None)
                if live_amt is not None and abs(live_amt) > 0:
                    try:
                        raw = await self.client.client.futures_position_information(symbol=symbol, recvWindow=20000)
                        for p in (raw or []):
                            if not isinstance(p, dict):
                                continue
                            try:
                                amt = float(p.get("positionAmt", 0))
                            except (TypeError, ValueError):
                                continue
                            if abs(amt) <= 0:
                                continue
                            position = {
                                'symbol': symbol,
                                'positionAmt': amt,
                                'entryPrice': float(p.get('entryPrice', 0)),
                                'positionSide': (p.get('positionSide') or '').strip().upper() or ('LONG' if amt > 0 else 'SHORT'),
                            }
                            logger.info(
                                f"{symbol} [平仓] 缓存未命中该 symbol，已用 REST 拉取到持仓: amt={amt}, positionSide={position['positionSide']}"
                            )
                            break
                    except Exception as e:
                        logger.debug(f"{symbol} [平仓] REST 拉取持仓失败: {e}")

            if not position:
                logger.warning(f"{symbol} [平仓] 币安账户中没有持仓，可能已被平仓")
                # 即使币安没有持仓，也要更新数据库状态
                updated = False
                if DB_AVAILABLE and Trade and symbol in self.active_positions:
                    position_info = self.active_positions[symbol]
                    trade_id = position_info.get('tradeId')
                    if trade_id:
                            logger.info(f"{symbol} [平仓] 更新数据库状态为已平仓 (ID: {trade_id})...")
                            ticker = await self.client.get_ticker_24h(symbol)
                            exit_price = float(ticker['price']) if ticker else float(position_info['entryPrice'])
                            entry_price = float(position_info['entryPrice'])
                            quantity = float(position_info['quantity'])
                            if position_info['side'] == 'BUY':
                                pnl = (exit_price - entry_price) * quantity
                                pnl_percent = ((exit_price - entry_price) / entry_price) * 100
                            else:
                                pnl = (entry_price - exit_price) * quantity
                                pnl_percent = ((entry_price - exit_price) / entry_price) * 100
                            entry_time = position_info.get('entryTime')
                            duration_minutes = None
                            if entry_time:
                                try:
                                    if isinstance(entry_time, str):
                                        entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S')
                                    else:
                                        entry_dt = entry_time
                                    exit_dt = get_beijing_time()
                                    duration = exit_dt - entry_dt
                                    duration_minutes = int(duration.total_seconds() / 60)
                                except Exception as e:
                                    logger.debug(f"计算持仓持续时间失败: {e}")
                            strategy_type = position_info.get('strategyType', 'trend_following')
                            db_update_retries = 3
                            for db_attempt in range(db_update_retries):
                                try:
                                    Trade.update_exit(
                                        trade_id=trade_id,
                                        exit_price=exit_price,
                                        exit_reason=reason,
                                        pnl=pnl,
                                        pnl_percent=pnl_percent,
                                        exit_order_id=None,
                                        strategy_type=strategy_type,
                                        duration_minutes=duration_minutes
                                    )
                                    logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新")
                                    updated = True
                                    break
                                except Exception as e:
                                    err_msg = str(e).strip() or f"{type(e).__name__}"
                                    if db_attempt < db_update_retries - 1:
                                        wait_sec = 2
                                        logger.warning(
                                            f"{symbol} [平仓] 更新数据库失败 (第 {db_attempt + 1}/{db_update_retries} 次): {err_msg}，"
                                            f"{wait_sec}秒后重试"
                                        )
                                        await asyncio.sleep(wait_sec)
                                    else:
                                        logger.error(f"{symbol} [平仓] ❌ 更新数据库状态失败: {err_msg}")
                
                # 清理本地记录
                await self._stop_position_monitoring(symbol)
                if symbol in self.active_positions:
                    del self.active_positions[symbol]
                
                # 如果更新了数据库，返回成功；否则返回失败
                return updated
            
            # 确定平仓方向（与开仓相反）；对冲模式下使用 REST 返回的 positionSide（LONG/SHORT），BOTH 或缺失时按 positionAmt 推导
            position_amt = position['positionAmt']
            side = 'SELL' if position_amt > 0 else 'BUY'
            quantity = abs(position_amt)
            ps_raw = (position.get('positionSide') or '').strip().upper()
            position_side = ps_raw if ps_raw in ('LONG', 'SHORT') else ('LONG' if position_amt > 0 else 'SHORT')

            # 二次校验：用币安实时持仓数量兜底，避免 reduceOnly 被拒绝（-2022）
            live_amt = await self._get_live_position_amt(symbol, position_side=position_side)
            if live_amt is None or abs(live_amt) <= 0:
                logger.warning(f"{symbol} [平仓] 实时查询到持仓已为0，跳过下单并按已平仓处理")
                # 复用“币安无持仓”的处理逻辑：走上面的分支
                position = None
                # 触发上方逻辑：直接返回 updated/清理
                # 这里简单调用同步函数路径
                ticker = await self.client.get_ticker_24h(symbol)
                exit_price = float(ticker['price']) if ticker else float(position.get('entryPrice', 0) if position else 0)
                await self._stop_position_monitoring(symbol)
                if symbol in self.active_positions:
                    del self.active_positions[symbol]
                logger.info(f"{symbol} [平仓] 已清理本地记录（币安无持仓）")
                return True

            # 以币安实时持仓数量为准（并向下截断到不超过持仓）
            quantity = min(quantity, abs(live_amt))
            quantity = await self._adjust_close_quantity(symbol, quantity)
            if quantity <= 0:
                logger.warning(f"{symbol} [平仓] 数量调整后为0，跳过下单并清理本地记录")
                await self._stop_position_monitoring(symbol)
                if symbol in self.active_positions:
                    del self.active_positions[symbol]
                return True
            
            logger.info(
                f"{symbol} [平仓] 下单信息: {side} {quantity:.4f} @ MARKET "
                f"(持仓数量: {position_amt:.4f})"
            )
            
            # 平仓（使用 reduceOnly=True 确保只减少持仓，不增加反向持仓）
            try:
                logger.debug(f"{symbol} [平仓] 调用 place_order: {side} {quantity:.4f} @ MARKET (reduceOnly=True)")
                order = await self.client.place_order(
                    symbol=symbol,
                    side=side,
                    quantity=quantity,
                    order_type='MARKET',
                    reduce_only=True,  # 平仓时使用 reduceOnly=True
                    position_side=position_side,  # 兼容对冲模式（Hedge）：必须指定 LONG/SHORT
                )
                logger.debug(f"{symbol} [平仓] place_order 返回: {order}")
            except Exception as order_error:
                logger.error(f"{symbol} [平仓] ❌ 下单失败: {order_error}")
                logger.error(f"   下单参数: symbol={symbol}, side={side}, quantity={quantity:.4f}, order_type=MARKET")
                logger.error(f"   错误类型: {type(order_error).__name__}")
                import traceback
                logger.error(f"   完整错误堆栈:\n{traceback.format_exc()}")
                raise  # 重新抛出异常，让外层捕获
            
            if order:
                order_id = order.get('orderId')
                logger.info(f"{symbol} [平仓] ✓ 平仓订单已提交 (订单ID: {order_id})，DB 由 WS ORDER_TRADE_UPDATE 更新")
                # 支付式闭环：平仓数据仅由 WS 推送更新，此处只做本地清理
                await self._stop_position_monitoring(symbol)
                if symbol in self.active_positions:
                    del self.active_positions[symbol]
                logger.info(f"{symbol} [平仓] ✓ 平仓完成: {side} {quantity:.4f} (原因: {reason})")
                return True
            else:
                # place_order 返回 None：可能是 -2022（ReduceOnly rejected）等竞态场景
                # 兜底：再查一次实时持仓，如果已经为0，则当作“已平仓”处理，避免刷屏与误判失败
                try:
                    live2 = await self._get_live_position_amt(symbol, position_side=position_side)
                except Exception:
                    live2 = None
                if live2 is None or abs(live2) <= 0:
                    logger.warning(f"{symbol} [平仓] 下单返回None，但实时持仓已为0，按已平仓处理（可能竞态/手动平仓）")
                await self._stop_position_monitoring(symbol)
                if symbol in self.active_positions:
                    del self.active_positions[symbol]
                return True

                logger.error(f"{symbol} [平仓] ❌ 下单返回 None（实时持仓仍存在: {live2}），可能的原因：")
                logger.error(f"   1. ReduceOnly 被拒绝（-2022）但持仓未同步")
                logger.error(f"   2. 数量精度调整后为 0 或负数")
                logger.error(f"   3. 无法获取价格信息")
                logger.error(f"   4. 其他下单错误（已在 place_order 中记录）")
                logger.error(f"   持仓信息: {side} {quantity:.4f} @ MARKET")
                
                # 尝试获取更多诊断信息
                try:
                    symbol_info = await self.client.get_symbol_info(symbol)
                    ticker = await self.client.get_ticker_24h(symbol)
                    if ticker:
                        current_price = ticker['price']
                        notional_value = quantity * current_price
                        min_notional = symbol_info.get('minNotional', 5.0) if symbol_info else 5.0
                        logger.error(f"   当前价格: {current_price:.4f} USDT")
                        logger.error(f"   订单名义价值: {notional_value:.2f} USDT")
                        logger.error(f"   最小名义价值: {min_notional:.2f} USDT")
                        if notional_value < min_notional:
                            logger.error(f"   ⚠ 订单名义价值不足，无法平仓")
                except Exception as diag_error:
                    logger.warning(f"   无法获取诊断信息: {diag_error}")
                
                return False
            
        except Exception as e:
            logger.error(f"{symbol} [平仓] ❌ 平仓失败: {e}")
            logger.error(f"   错误类型: {type(e).__name__}")
            import traceback
            logger.error(f"   完整错误堆栈:\n{traceback.format_exc()}")
            
            # 关键：平仓失败时不要盲目清理本地持仓/停止监控，否则会导致“仍有仓位但不再监控/不再自动止损止盈”
            # 仅当确认币安已无持仓（或实时持仓为0）时，才清理本地记录。
            try:
                amt0 = await self._get_live_position_amt(symbol, position_side=None)
            except Exception:
                amt0 = None
            if amt0 is not None and abs(amt0) <= 0:
                try:
                    await self._stop_position_monitoring(symbol)
                except Exception:
                    pass
                try:
                    if symbol in self.active_positions:
                        del self.active_positions[symbol]
                except Exception:
                    pass
                logger.warning(f"{symbol} [平仓] 异常后检查：币安持仓已为0，已清理本地记录")
            else:
                logger.warning(
                    f"{symbol} [平仓] 异常后检查：币安持仓仍可能存在(amt={amt0})，保留本地记录与监控，等待下次同步/重试"
                )
            
            return False

    async def _get_live_position_amt(self, symbol: str, position_side: Optional[str] = None) -> Optional[float]:
        """
        从币安原始接口读取持仓数量（避免本地状态/缓存不一致导致 reduceOnly 被拒绝）。
        - 单向模式：通常只有一个 net 持仓
        - 对冲模式：可能同时有 LONG/SHORT，两条腿用 positionSide 区分
        """
        try:
            if not getattr(self.client, "client", None):
                return None
            res = await self.client.client.futures_position_information(symbol=symbol, recvWindow=20000)
            if not isinstance(res, list):
                return None
            ps = (position_side or "").upper()
            nonzero = []
            for p in res:
                if not isinstance(p, dict):
                    continue
                try:
                    amt = float(p.get("positionAmt", 0))
                except Exception:
                    continue
                if abs(amt) <= 0:
                    continue
                nonzero.append((amt, p))
            if not nonzero:
                return 0.0
            if ps in ("LONG", "SHORT"):
                for amt, p in nonzero:
                    pps = (p.get("positionSide") or "").upper()
                    if pps == ps:
                        return amt
                # 如果没匹配到 positionSide，退化为按符号推断
                if ps == "LONG":
                    cand = next((amt for amt, _ in nonzero if amt > 0), None)
                    return cand if cand is not None else 0.0
                if ps == "SHORT":
                    cand = next((amt for amt, _ in nonzero if amt < 0), None)
                    return cand if cand is not None else 0.0
            # 没提供 position_side：返回净持仓（单向模式）
            return sum([amt for amt, _ in nonzero])
        except Exception as e:
            logger.debug(f"{symbol} 读取实时持仓失败: {e}")
            return None

    async def _adjust_close_quantity(self, symbol: str, quantity: float) -> float:
        """
        平仓数量调整：只允许向下取整（避免超过持仓导致 reduceOnly 被拒绝）。
        """
        try:
            symbol_info = await self.client.get_symbol_info(symbol)
        except Exception:
            symbol_info = None

        try:
            q = float(quantity)
        except Exception:
            return 0.0

        if not symbol_info:
            return max(0.0, q)

        try:
            step_size = float(symbol_info.get("stepSize", 0) or 0)
        except Exception:
            step_size = 0.0
        qty_precision = int(symbol_info.get("quantityPrecision", 8) or 8)

        if step_size and step_size > 0:
            q = float(int(q / step_size)) * step_size
        else:
            q = round(q, qty_precision)
        q = round(q, qty_precision)
        return max(0.0, q)

    async def _symbol_has_sltp_orders(self, symbol: str) -> bool:
        """该交易对在交易所是否存在止损/止盈类条件单（用于识别「可能为系统单」的持仓）。"""
        try:
            for o in (await self.client.get_open_orders(symbol)) or []:
                t = str(o.get("type") or "").upper()
                if t in ("STOP_MARKET", "TAKE_PROFIT_MARKET", "STOP", "TAKE_PROFIT"):
                    return True
            for o in (await self.client.futures_get_open_algo_orders(symbol, algo_type="CONDITIONAL")) or []:
                t = str(o.get("orderType") or o.get("type") or "").upper()
                if t in ("STOP_MARKET", "TAKE_PROFIT_MARKET", "STOP", "TAKE_PROFIT"):
                    return True
        except Exception:
            pass
        return False

    async def _get_sltp_from_exchange(self, symbol: str, side: str) -> tuple:
        """从币安条件单读取当前止损价、止盈价，便于重启后不覆盖已有保护。(stop_price, take_profit_price)，缺的为 None。"""
        sl_price, tp_price = None, None
        try:
            for o in (await self.client.get_open_orders(symbol)) or []:
                t = str(o.get("type") or "").upper()
                sp = o.get("stopPrice") or o.get("triggerPrice")
                try:
                    p = float(sp) if sp is not None else None
                except (TypeError, ValueError):
                    p = None
                if p is None:
                    continue
                if t in ("STOP_MARKET", "STOP"):
                    sl_price = p
                elif t in ("TAKE_PROFIT_MARKET", "TAKE_PROFIT"):
                    tp_price = p
            # 条件单多在 CONDITIONAL 里
            for o in (await self.client.futures_get_open_algo_orders(symbol, algo_type="CONDITIONAL")) or []:
                t = str(o.get("orderType") or o.get("type") or "").upper()
                sp = o.get("stopPrice") or o.get("triggerPrice") or o.get("tp")
                try:
                    p = float(sp) if sp is not None else None
                except (TypeError, ValueError):
                    p = None
                if p is None:
                    continue
                if t in ("STOP_MARKET", "STOP"):
                    sl_price = p
                elif t in ("TAKE_PROFIT_MARKET", "TAKE_PROFIT"):
                    tp_price = p
        except Exception as e:
            logger.debug(f"{symbol} 从交易所读取止损/止盈失败: {e}")
        return (sl_price, tp_price)

    def _breakeven_stop_price(self, entry_price: float, side: str, fee_buffer_pct: Optional[float] = None) -> float:
        """含手续费的保本止损价：做多=入场*(1+费率)，做空=入场*(1-费率)，平仓后不亏。"""
        if fee_buffer_pct is None:
            fee_buffer_pct = float(config.TRADING_CONFIG.get("FEE_BUFFER_PCT", 0.0015) or 0.0015)
        if fee_buffer_pct > 0.01:
            fee_buffer_pct = fee_buffer_pct / 100.0
        if side == "BUY":
            return entry_price * (1 + fee_buffer_pct)
        return entry_price * (1 - fee_buffer_pct)

    def _min_protect_amount_for_fees(self, margin: float, leverage: float) -> float:
        """移动止损时至少保护的金额（覆盖开平双向手续费，避免“保护”后仍为负）。"""
        lev = max(float(leverage or 10), 1)
        # 双向约 0.05%*2=0.1% 名义 → 保证金上约 0.1%*leverage
        return margin * (0.001 * lev)

    def _stop_price_to_lock_pct(
        self, entry_price: float, side: str, margin: float, quantity: float, lock_pct: float
    ) -> float:
        """计算「锁住 lock_pct% 保证金利润」对应的止损价。lock_pct 为百分比数值，如 5 表示 5%。"""
        if margin <= 0 or quantity <= 0:
            return self._breakeven_stop_price(entry_price, side)
        lock_ratio = float(lock_pct) / 100.0
        if lock_ratio > 1:
            lock_ratio = lock_ratio / 100.0
        lock_amount = margin * lock_ratio
        if side == 'BUY':
            return entry_price + lock_amount / quantity
        return entry_price - lock_amount / quantity

    def _normalize_ratio_config(self, value: Optional[float], default: float) -> float:
        """兼容 0.08 / 8 两种写法，统一转为比例值。"""
        try:
            ratio = float(value if value is not None else default)
        except (TypeError, ValueError):
            ratio = float(default)
        if ratio > 1:
            ratio = ratio / 100.0
        return max(0.0, ratio)

    async def _apply_profit_lock_stage(
        self,
        symbol: str,
        position_info: Dict,
        entry_price: float,
        quantity: float,
        leverage: float,
        pnl_amount: float,
        pnl_percent_margin: float,
        current_price: float,
        source: str,
    ) -> bool:
        """
        分层锁盈：
        - 第一层：盈利达到阈值后，先从“纯保本”抬到“小幅锁利”
        - 第二层：盈利进一步扩大后，将止损抬到更积极的锁利位置
        """
        side = position_info.get("side")
        if side not in ("BUY", "SELL"):
            return False

        stage1_trigger = self._normalize_ratio_config(
            config.TRADING_CONFIG.get("LOCK_PROFIT_STAGE1_TRIGGER_PCT"), 0.08
        )
        stage1_lock = self._normalize_ratio_config(
            config.TRADING_CONFIG.get("LOCK_PROFIT_STAGE1_PCT"), 0.02
        )
        stage2_trigger = self._normalize_ratio_config(
            config.TRADING_CONFIG.get("LOCK_PROFIT_STAGE2_TRIGGER_PCT"), 0.15
        )
        stage2_lock = self._normalize_ratio_config(
            config.TRADING_CONFIG.get("LOCK_PROFIT_STAGE2_PCT"), 0.05
        )

        target_stage = 0
        target_lock_ratio = 0.0
        if stage2_trigger > 0 and stage2_lock > 0 and pnl_percent_margin >= stage2_trigger * 100:
            target_stage = 2
            target_lock_ratio = stage2_lock
        elif stage1_trigger > 0 and stage1_lock > 0 and pnl_percent_margin >= stage1_trigger * 100:
            target_stage = 1
            target_lock_ratio = stage1_lock

        if target_stage <= 0 or target_lock_ratio <= 0:
            return False

        lock_quantity = float(position_info.get("remainingQuantity", quantity) or quantity)
        if lock_quantity <= 0:
            lock_quantity = float(quantity or 0)
        if lock_quantity <= 0:
            return False

        lock_margin = (entry_price * lock_quantity) / leverage if leverage > 0 else (entry_price * lock_quantity)
        target_lock_pct_number = target_lock_ratio * 100.0
        new_stop_loss = self._stop_price_to_lock_pct(
            entry_price, side, lock_margin, lock_quantity, target_lock_pct_number
        )
        breakeven = self._breakeven_stop_price(entry_price, side)
        current_sl_raw = position_info.get("stopLoss")
        current_sl = float(current_sl_raw) if current_sl_raw is not None else None

        should_update = False
        if side == "BUY":
            should_update = (
                new_stop_loss >= breakeven
                and new_stop_loss < current_price
                and (current_sl is None or new_stop_loss > current_sl)
            )
        else:
            should_update = (
                new_stop_loss <= breakeven
                and new_stop_loss > current_price
                and (current_sl is None or new_stop_loss < current_sl)
            )

        if not should_update:
            return False

        position_info["stopLoss"] = new_stop_loss
        prev_stage = int(position_info.get("profitLockStage", 0) or 0)
        position_info["profitLockStage"] = max(prev_stage, target_stage)
        logger.info(
            f"{symbol} [{source}] 分层锁盈生效：第{target_stage}层，止损上调至 {new_stop_loss:.4f} "
            f"(当前盈利 {pnl_percent_margin:.2f}% of margin，目标至少锁住 {target_lock_pct_number:.1f}%利润)"
        )
        _log_trailing_stop_event(
            self.account_id,
            symbol,
            "profit_lock_stage",
            source=source,
            stage=target_stage,
            pnl_pct=pnl_percent_margin,
            new_stop_loss=new_stop_loss,
            target_lock_pct=target_lock_pct_number,
        )
        try:
            await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
            _log_trailing_stop_event(
                self.account_id,
                symbol,
                "profit_lock_stage_sync_ok",
                source=source,
                stage=target_stage,
                new_stop_loss=new_stop_loss,
            )
        except Exception as sync_e:
            _log_trailing_stop_event(
                self.account_id,
                symbol,
                "profit_lock_stage_sync_fail",
                source=source,
                stage=target_stage,
                new_stop_loss=new_stop_loss,
                error=str(sync_e),
            )
            logger.warning(f"{symbol} [{source}] 分层锁盈同步至交易所失败: {sync_e}", exc_info=False)
        return True

    async def _ensure_exchange_sltp_orders(self, symbol: str, position_info: Dict, current_price: Optional[float] = None) -> None:
        """
        在币安侧挂止损/止盈保护单（STOP_MARKET + TAKE_PROFIT_MARKET）。
        目的：
        - 服务重启/网络波动时仍有交易所级别保护
        - 用户在币安界面能看到止损/止盈委托
        流程（与 docs/bian 条件订单交易更新推送 一致）：
        - 先检查持仓存在 → 取消旧保护单 → 再次检查持仓（避免条件单已触发平仓后仍挂单导致 -4509/超时）→ 再挂 SL/TP。
        - 条件单触发平仓后，交易所会将同 symbol 其余条件单置为 EXPIRED，见 ALGO_UPDATE。
        """
        try:
            enabled = bool(config.TRADING_CONFIG.get("EXCHANGE_SLTP_ENABLED", True))
        except Exception:
            enabled = True
        if not enabled:
            return

        if not position_info or not isinstance(position_info, dict):
            return

        # 用于 ALGO_UPDATE 时按 entry_order_id 精确匹配 DB 平仓记录
        entry_order_id = position_info.get("orderId") or position_info.get("entry_order_id")
        client_algo_id_sl = None
        client_algo_id_tp = None
        if entry_order_id:
            eid = str(entry_order_id).strip()
            if eid and len(eid) <= 30:  # 留足 SL_/TP_ 前缀
                client_algo_id_sl = f"SL_{eid}"[:36]
                client_algo_id_tp = f"TP_{eid}"[:36]

        side = (position_info.get("side") or "").upper()
        if side not in {"BUY", "SELL"}:
            return

        stop_loss = position_info.get("stopLoss")
        take_profit = position_info.get("takeProfit2") or position_info.get("takeProfit")
        try:
            stop_loss = float(stop_loss) if stop_loss is not None else None
        except Exception:
            stop_loss = None
        try:
            take_profit = float(take_profit) if take_profit is not None else None
        except Exception:
            take_profit = None

        if not stop_loss or not take_profit:
            logger.warning(f"[账号{self.account_id}] {symbol} 同步跳过: 止损或止盈为空 stop_loss={stop_loss} take_profit={take_profit}")
            return

        sync_type = "保本/移动止损同步" if (position_info.get("breakevenStopSet") or position_info.get("trailingStopActivated")) else "SL/TP同步"
        logger.info(
            f"[账号{self.account_id}] {symbol} 开始同步止损/止盈至交易所 [{sync_type}]: "
            f"止损触发价={float(stop_loss):.4f} 止盈触发价={float(take_profit):.4f}"
        )

        # 验证止损价格是否合理。保本/移动止损时：多单止损可≥入场价、空单止损可≤入场价，不得被改回亏损价
        entry_price = position_info.get("entryPrice")
        if entry_price:
            try:
                entry_price_val = float(entry_price)
                stop_loss_val = float(stop_loss)
                min_gap_pct = 0.005  # 最小 0.5% 距离，与 risk_manager 一致
                # 多单止损≥入场价 = 保本/移动止损，有效；空单止损≤入场价 = 保本/移动止损，有效
                if side == "BUY" and stop_loss_val >= entry_price_val:
                    # 保本或移动止损，保留不修正
                    pass
                elif side == "SELL" and stop_loss_val <= entry_price_val:
                    pass
                elif side == "BUY" and stop_loss_val < entry_price_val:
                    if stop_loss_val > entry_price_val * (1 - min_gap_pct):
                        safe_sl = entry_price_val * (1 - min_gap_pct)
                        logger.warning(
                            f"{symbol} 止损价({stop_loss_val:.8f})距入场过近(BUY)，已修正为 {safe_sl:.8f}"
                        )
                        stop_loss = safe_sl
                        position_info["stopLoss"] = stop_loss
                elif side == "SELL" and stop_loss_val > entry_price_val:
                    if stop_loss_val < entry_price_val * (1 + min_gap_pct):
                        safe_sl = entry_price_val * (1 + min_gap_pct)
                        logger.warning(
                            f"{symbol} 止损价({stop_loss_val:.8f})距入场过近(SELL)，已修正为 {safe_sl:.8f}"
                        )
                        stop_loss = safe_sl
                        position_info["stopLoss"] = stop_loss
            except Exception as e:
                logger.warning(f"{symbol} 验证止损价格时出错: {e}")

        # ⚠️ 关键修复：挂单前先检查持仓是否真的存在，避免 -4509（没有持仓）和 -4061（positionSide 不匹配）
        try:
            positions = await self._get_open_positions()
            live_position = next((p for p in positions if p.get('symbol') == symbol), None)
            if not live_position:
                logger.warning(f"{symbol} ⚠️ 持仓已不存在（可能已平仓），跳过挂止损/止盈单，避免 -4509/-4061 错误")
                return
            position_amt = float(live_position.get('positionAmt', 0) or 0)
            if abs(position_amt) <= 0:
                logger.warning(f"{symbol} ⚠️ 持仓数量为0，跳过挂止损/止盈单")
                return
            # 验证持仓方向是否匹配（对冲模式下需要检查 positionSide）
            live_side = "BUY" if position_amt > 0 else "SELL"
            if live_side != side:
                logger.warning(f"{symbol} ⚠️ 持仓方向不匹配（本地记录: {side}, 实际: {live_side}），跳过挂单，避免 -4061")
                return
        except Exception as e:
            logger.warning(f"{symbol} 检查持仓存在性时出错，继续尝试挂单（可能误报）: {e}")

        # 防重复：先取消旧的保护单（仅取消特定类型，避免误伤普通挂单）
        try:
            await self.client.cancel_open_algo_orders_by_order_types(
                symbol, {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"}
            )
            logger.info(
                f"[账号{self.account_id}] {symbol} 已取消旧保护单，准备挂新单 [{sync_type}] "
                f"条件价: 止损={float(stop_loss):.4f} 止盈={float(take_profit):.4f}"
            )
            if sync_type == "保本/移动止损同步":
                _log_trailing_stop_event(
                    self.account_id, symbol, "sync_to_exchange",
                    stop_loss=float(stop_loss), take_profit=float(take_profit), msg="准备挂新单"
                )
        except Exception as e:
            logger.warning(f"[账号{self.account_id}] {symbol} 取消旧保护单异常: {e}，继续尝试挂新单")

        # 取消后再次确认持仓仍存在（docs/bian：条件单触发平仓后，交易所会 EXPIRED 其他条件单；若先触发了 TP/SL 平仓，此处再挂会 -4509 或请求卡住）
        try:
            positions = await self._get_open_positions()
            live_position = next((p for p in positions if p.get('symbol') == symbol), None)
            if not live_position or abs(float(live_position.get('positionAmt', 0) or 0)) <= 0:
                logger.info(
                    f"[账号{self.account_id}] {symbol} 取消旧保护单后持仓已不存在（可能已被条件单平仓），跳过挂新单"
                )
                return
        except Exception as e:
            logger.debug(f"{symbol} 取消后复检持仓失败: {e}，继续尝试挂单")

        # 获取当前价格（如果未提供）：优先 WS 缓存（bookTicker/ticker24h）→ 持仓 markPrice → REST ticker
        if current_price is None:
            try:
                # 1) 优先 Redis/WS：最优挂单中点价或 24h ticker（进程内不保留全量）
                try:
                    try:
                        from .book_ticker_stream import get_book_ticker, get_book_ticker_from_redis
                    except ImportError:
                        from book_ticker_stream import get_book_ticker, get_book_ticker_from_redis
                    redis_cache = getattr(self.client, "redis_cache", None)
                    if redis_cache:
                        book = await get_book_ticker_from_redis(redis_cache, symbol)
                    else:
                        book = get_book_ticker(symbol)
                    if book and float(book.get("bidPrice", 0)) > 0 and float(book.get("askPrice", 0)) > 0:
                        mid = (float(book["bidPrice"]) + float(book["askPrice"])) / 2
                        current_price = mid
                        logger.debug(f"{symbol} 从 bookTicker 获取当前价格: {current_price}")
                except Exception:
                    pass
                if current_price is None:
                    try:
                        try:
                            from .ticker_24h_stream import (
                                get_tickers_24h_cache,
                                get_tickers_24h_from_redis,
                                is_ticker_24h_cache_fresh,
                            )
                        except ImportError:
                            from ticker_24h_stream import (
                                get_tickers_24h_cache,
                                get_tickers_24h_from_redis,
                                is_ticker_24h_cache_fresh,
                            )
                        if is_ticker_24h_cache_fresh(max_age_sec=120):
                            redis_cache = getattr(self.client, "redis_cache", None)
                            tickers = await get_tickers_24h_from_redis(redis_cache) if redis_cache else get_tickers_24h_cache()
                            t = tickers.get(symbol) if tickers else None
                            if t and t.get("price"):
                                current_price = float(t["price"])
                                logger.debug(f"{symbol} 从 ticker24h 获取当前价格: {current_price}")
                    except Exception:
                        pass
                # 2) 持仓标记价格（MARK_PRICE，与止损单触发基准一致）
                if current_price is None:
                    positions = await self._get_open_positions()
                    position = next((p for p in positions if p['symbol'] == symbol), None)
                    if position:
                        mark_price = position.get('markPrice')
                        if mark_price and float(mark_price) > 0:
                            current_price = float(mark_price)
                            logger.debug(f"{symbol} 从持仓获取标记价格: {current_price}")
                    # 3) REST：深度最优档中点（权重低）→ 24h ticker
                    if current_price is None:
                        depth = await self.client.get_depth(symbol, limit=5)
                        if depth:
                            bids = depth.get("bids") or []
                            asks = depth.get("asks") or []
                            if bids and asks and len(bids[0]) >= 1 and len(asks[0]) >= 1:
                                try:
                                    mid = (float(bids[0][0]) + float(asks[0][0])) / 2
                                    if mid > 0:
                                        current_price = mid
                                        logger.debug(f"{symbol} 从 depth REST 获取当前价格: {current_price}")
                                except (ValueError, TypeError, IndexError):
                                    pass
                    if current_price is None:
                        ticker = await self.client.get_ticker_24h(symbol)
                        if ticker:
                            current_price = ticker.get('price') and float(ticker['price']) or None
                            if current_price:
                                logger.debug(f"{symbol} 从 ticker REST 获取当前价格: {current_price}")
            except Exception as e:
                err_msg = getattr(e, "message", str(e)) or repr(e)
                logger.warning(f"{symbol} 获取当前价格失败: {type(e).__name__}: {err_msg}")
        
        # 如果仍然没有当前价格，记录警告
        if current_price is None:
            logger.warning(f"{symbol} ⚠️ 无法获取当前价格（已尝试 WS bookTicker/ticker24h、持仓、depth、REST ticker），止损单可能无法正确验证触发条件")

        # 在挂止损单前，检查当前价格是否已经触发止损（避免 -2021 错误）
        sl_failed_due_to_gte = False  # GTE/持仓未就绪时仅打 WARNING，不刷 ERROR
        if current_price and stop_loss:
            try:
                current_price_val = float(current_price)
                stop_loss_val = float(stop_loss)
                entry_price_val = float(entry_price) if entry_price else None
                
                # 检查是否已经触发止损
                if side == "BUY":
                    # 做多：当前价 <= 止损价，说明已触发止损
                    if current_price_val <= stop_loss_val:
                        if self._should_skip_sl_protection_close(symbol):
                            return
                        self._mark_sl_protection_close_triggered(symbol)
                        entry_price_str = f"{entry_price_val:.8f}" if entry_price_val is not None else 'N/A'
                        logger.warning(
                            f"{symbol} 当前价格({current_price_val:.8f})已触发止损价({stop_loss_val:.8f})，无法挂止损单，立即执行市价平仓保护 | 入场价: {entry_price_str}"
                        )
                        # 立即执行市价平仓
                        await self.close_position(symbol, reason='stop_loss')
                        return
                    else:
                        sl_order = await self.client.place_trigger_close_position_order(
                            symbol=symbol,
                            position_direction=side,
                            trigger_type="STOP_MARKET",
                            stop_price=stop_loss,
                            current_price=current_price,
                            working_type="MARK_PRICE",
                            client_algo_id=client_algo_id_sl,
                        )
                elif side == "SELL":
                    # 做空：当前价 >= 止损价，说明已触发止损
                    if current_price_val >= stop_loss_val:
                        if self._should_skip_sl_protection_close(symbol):
                            return
                        self._mark_sl_protection_close_triggered(symbol)
                        entry_price_str = f"{entry_price_val:.8f}" if entry_price_val is not None else 'N/A'
                        logger.warning(
                            f"{symbol} 当前价格({current_price_val:.8f})已触发止损价({stop_loss_val:.8f})，无法挂止损单，立即执行市价平仓保护 | 入场价: {entry_price_str}"
                        )
                        # 立即执行市价平仓
                        await self.close_position(symbol, reason='stop_loss')
                        return
                    else:
                        sl_order = await self.client.place_trigger_close_position_order(
                            symbol=symbol,
                            position_direction=side,
                            trigger_type="STOP_MARKET",
                            stop_price=stop_loss,
                            current_price=current_price,
                            working_type="MARK_PRICE",
                            client_algo_id=client_algo_id_sl,
                        )
                else:
                    sl_order = await self.client.place_trigger_close_position_order(
                        symbol=symbol,
                        position_direction=side,
                        trigger_type="STOP_MARKET",
                        stop_price=stop_loss,
                        current_price=current_price,
                        working_type="MARK_PRICE",
                        client_algo_id=client_algo_id_sl,
                    )
            except AlgoOrderPositionUnavailableError:
                sl_failed_due_to_gte = True
                sl_order = None
            except Exception as e:
                # 检查是否是 -2021 (立即触发)
                error_msg = str(e)
                if "-2021" in error_msg or "immediately trigger" in error_msg:
                    if self._should_skip_sl_protection_close(symbol):
                        return
                    self._mark_sl_protection_close_triggered(symbol)
                    logger.warning(f"{symbol} 止损单会立即触发(-2021)，视为已触发止损，立即执行市价平仓")
                    await self.close_position(symbol, reason='stop_loss')
                    return

                logger.warning(f"{symbol} 检查止损触发条件时出错: {e}，继续尝试挂单")
                try:
                    sl_order = await self.client.place_trigger_close_position_order(
                        symbol=symbol,
                        position_direction=side,
                        trigger_type="STOP_MARKET",
                        stop_price=stop_loss,
                        current_price=current_price,
                        working_type="MARK_PRICE",
                        client_algo_id=client_algo_id_sl,
                    )
                except AlgoOrderPositionUnavailableError:
                    sl_failed_due_to_gte = True
                    sl_order = None
                except Exception as retry_e:
                    retry_msg = str(retry_e)
                    if "-2021" in retry_msg or "immediately trigger" in retry_msg:
                        if self._should_skip_sl_protection_close(symbol):
                            return
                        self._mark_sl_protection_close_triggered(symbol)
                        logger.warning(f"{symbol} 重试挂止损单会立即触发(-2021)，立即执行市价平仓")
                        await self.close_position(symbol, reason='stop_loss')
                        return
                    logger.error(f"{symbol} 重试挂止损单失败: {retry_e}")
                    sl_order = None
        else:
            try:
                sl_order = await self.client.place_trigger_close_position_order(
                    symbol=symbol,
                    position_direction=side,
                    trigger_type="STOP_MARKET",
                    stop_price=stop_loss,
                    current_price=current_price,
                    working_type="MARK_PRICE",
                    client_algo_id=client_algo_id_sl,
                )
            except AlgoOrderPositionUnavailableError:
                sl_failed_due_to_gte = True
                sl_order = None
            except Exception as e:
                error_msg = str(e)
                if "-2021" in error_msg or "immediately trigger" in error_msg:
                    if self._should_skip_sl_protection_close(symbol):
                        return
                    self._mark_sl_protection_close_triggered(symbol)
                    logger.warning(f"{symbol} 止损单会立即触发(-2021)，视为已触发止损，立即执行市价平仓")
                    await self.close_position(symbol, reason='stop_loss')
                    return
                logger.error(f"{symbol} 挂止损单失败（API/网络）: {e}")
                sl_order = None
        
        if sl_order and entry_order_id:
            algo_id = sl_order.get("algoId")
            if algo_id is not None:
                try:
                    rc = getattr(self.client, "redis_cache", None)
                    if rc:
                        await rc.set(
                            f"ats:algo2entry:{self.account_id}:{algo_id}",
                            str(entry_order_id),
                            ttl=7 * 86400,
                        )
                except Exception:
                    pass
        if sl_order:
            logger.info(
                f"[账号{self.account_id}] {symbol} ✓ 止损单已成功挂到交易所: {sl_order.get('algoId', 'N/A')} "
                f"触发价={float(stop_loss):.4f}"
            )
        else:
            if sl_failed_due_to_gte:
                logger.warning(f"{symbol} 条件单被拒（持仓未就绪或已平），跳过交易所止损单，将依赖 WebSocket 监控")
            else:
                logger.error(f"{symbol} ❌ 止损单挂单失败！将依赖WebSocket监控，但可能无法及时止损")
                logger.error(f"  止损价格: {stop_loss:.8f}")
                logger.error(f"  当前价格: {current_price if current_price else '无（已尝试 WS bookTicker/ticker24h、持仓、REST）'}")
                logger.error(f"  持仓方向: {side}")
                logger.error(f"  ⚠️ 警告: 没有交易所级别的止损保护，如果系统崩溃或网络中断，可能无法及时止损！")
                logger.error(f"  💡 建议: 检查上方或下方日志中的 API 错误码（如 -4014 触发价步长不符、-4164 名义价值不足、-2021 会立即触发）或手动在币安界面设置止损")
            
            # ⚠️ 关键修复：止损单挂单失败后，立即检查当前价格是否已触发止损
            # 如果已触发，立即执行市价平仓，避免亏损扩大
            if current_price and stop_loss:
                try:
                    current_price_val = float(current_price)
                    stop_loss_val = float(stop_loss)
                    entry_price_val = float(entry_price) if entry_price else None
                    
                    # 检查是否已触发止损
                    should_close = False
                    if side == "BUY":
                        # 做多：当前价 <= 止损价，触发止损
                        if current_price_val <= stop_loss_val:
                            should_close = True
                    elif side == "SELL":
                        # 做空：当前价 >= 止损价，触发止损
                        if current_price_val >= stop_loss_val:
                            should_close = True
                    
                    if should_close:
                        if self._should_skip_sl_protection_close(symbol):
                            return
                        self._mark_sl_protection_close_triggered(symbol)
                        entry_price_str = f"{entry_price_val:.8f}" if entry_price_val is not None else 'N/A'
                        logger.warning(
                            f"{symbol} 止损单挂单失败，当前价格已触发止损，立即执行市价平仓保护 | "
                            f"当前价: {current_price_val:.8f}, 止损价: {stop_loss_val:.8f}, 入场价: {entry_price_str}"
                        )
                        # 立即执行市价平仓
                        if await self.close_position(symbol, reason='stop_loss'):
                            logger.info(f"{symbol} ✓ 止损平仓成功（止损单挂单失败后的保护措施）")
                        return
                    else:
                        # 未触发止损，但需要增强WebSocket监控
                        logger.warning(f"{symbol} ⚠️ 止损单挂单失败，当前价格未触发止损，将依赖WebSocket监控")
                        logger.warning(f"  当前价格: {current_price_val:.8f}, 止损价格: {stop_loss_val:.8f}")
                        logger.warning(f"  价格距离止损: {abs(current_price_val - stop_loss_val):.8f} ({abs(current_price_val - stop_loss_val)/stop_loss_val*100:.2f}%)")
                except Exception as e:
                    logger.warning(f"{symbol} 检查止损触发条件时出错: {e}，继续依赖WebSocket监控")
        
        # 在挂止盈单前，检查当前价格是否已经触发止盈
        if current_price and take_profit:
            try:
                current_price_val = float(current_price)
                take_profit_val = float(take_profit)
                
                # 检查是否已经触发止盈
                # ⚠️ 关键修复：增加0.5%的缓冲，避免因价格轻微触及或市场波动导致的过早止盈
                # 如果价格只是刚好碰到止盈价，不立即触发，而是尝试挂单或让WebSocket监控决定
                triggered_tp = False
                buffer_pct = 0.005  # 0.5% buffer
                
                if side == "BUY":
                    # 做多：当前价 >= 止盈价 * (1 + buffer)
                    if current_price_val >= take_profit_val * (1 + buffer_pct):
                        triggered_tp = True
                elif side == "SELL":
                    # 做空：当前价 <= 止盈价 * (1 - buffer)
                    if current_price_val <= take_profit_val * (1 - buffer_pct):
                        triggered_tp = True
                
                if triggered_tp:
                    logger.info(f"{symbol} 🎯 当前价格({current_price_val:.8f})已显著超过止盈价({take_profit_val:.8f})，立即执行市价止盈！")
                    await self.close_position(symbol, reason='take_profit')
                    return
            except Exception as e:
                logger.debug(f"{symbol} 检查止盈触发条件时出错: {e}")

        try:
            tp_order = await self.client.place_trigger_close_position_order(
                symbol=symbol,
                position_direction=side,
                trigger_type="TAKE_PROFIT_MARKET",
                stop_price=take_profit,
                current_price=current_price,
                working_type="MARK_PRICE",
                client_algo_id=client_algo_id_tp,
            )
        except Exception as e:
            # 处理 -2021: Order would immediately trigger
            error_msg = str(e)
            if "-2021" in error_msg or "immediately trigger" in error_msg:
                # ⚠️ 关键修复：如果止盈单会立即触发，不立即平仓，而是依赖WebSocket监控
                # 这样可以避免因价格微小波动导致的过早止盈，让利润奔跑（WebSocket有最小盈利检查）
                logger.warning(f"{symbol} ⚠️ 止盈单会立即触发(-2021)，跳过挂交易所止盈单，将依赖WebSocket监控执行止盈")
                # await self.close_position(symbol, reason='take_profit')  <-- 已移除立即平仓
                return
            else:
                logger.warning(f"{symbol} 挂止盈单失败: {e}")
                tp_order = None
        if tp_order and entry_order_id:
            algo_id = tp_order.get("algoId")
            if algo_id is not None:
                try:
                    rc = getattr(self.client, "redis_cache", None)
                    if rc:
                        await rc.set(
                            f"ats:algo2entry:{self.account_id}:{algo_id}",
                            str(entry_order_id),
                            ttl=7 * 86400,
                        )
                except Exception:
                    pass
        if tp_order:
            logger.info(
                f"[账号{self.account_id}] {symbol} ✓ 止盈单已成功挂到交易所: {tp_order.get('algoId', 'N/A')} "
                f"触发价={float(take_profit):.4f}"
            )
        else:
            logger.warning(f"{symbol} ⚠️ 止盈单挂单失败，将依赖WebSocket监控")

        try:
            # Algo 接口返回 algoId
            position_info["exchangeSlOrderId"] = sl_order.get("algoId") if isinstance(sl_order, dict) else None
        except Exception:
            position_info["exchangeSlOrderId"] = None
        try:
            position_info["exchangeTpOrderId"] = tp_order.get("algoId") if isinstance(tp_order, dict) else None
        except Exception:
            position_info["exchangeTpOrderId"] = None

        if position_info.get("exchangeSlOrderId") or position_info.get("exchangeTpOrderId"):
            logger.info(
                f"[账号{self.account_id}] {symbol} 已挂币安保护单 [{sync_type}]: "
                f"SL={position_info.get('exchangeSlOrderId') or '-'} 触发价={float(stop_loss):.4f} | "
                f"TP={position_info.get('exchangeTpOrderId') or '-'} 触发价={float(take_profit):.4f}"
            )
            if sync_type == "保本/移动止损同步":
                _log_trailing_stop_event(
                    self.account_id, symbol, "sync_to_exchange_ok",
                    stop_loss=float(stop_loss), take_profit=float(take_profit), msg="已挂币安保护单"
                )
            logger.info(f"[账号{self.account_id}] {symbol} 止损/止盈同步至交易所完成")
        else:
            logger.warning(f"[账号{self.account_id}] {symbol} 同步结束但未挂上保护单（止损或止盈挂单均失败），将依赖 WebSocket 监控")
    
    async def check_stop_loss_take_profit(self) -> List[str]:
        """
        检查止损止盈
        
        Returns:
            需要平仓的交易对列表
        """
        closed_positions = []
        
        try:
            # 获取当前持仓
            positions = await self._get_open_positions()
            position_dict = {p['symbol']: p for p in positions}
            
            for symbol, position_info in list(self.active_positions.items()):
                if symbol not in position_dict:
                    # 持仓已不存在，移除记录
                    del self.active_positions[symbol]
                    continue
                
                current_position = position_dict[symbol]
                # 统一转为 float，避免 Decimal 与 float 运算报错（DB/补建可能返回 Decimal）
                entry_price = float(position_info['entryPrice'])
                quantity = float(position_info['quantity'])
                # 获取当前标记价格
                current_price = current_position.get('markPrice', 0)
                if current_price == 0:
                    # 如果标记价格为0，尝试从ticker获取
                    ticker = await self.client.get_ticker_24h(symbol)
                    if ticker:
                        current_price = float(ticker.get('price', 0) or 0)
                    else:
                        current_price = entry_price
                else:
                    current_price = float(current_price)
                
                # 计算当前盈亏（基于保证金）
                leverage = float(position_info.get('leverage', 10) or 10)
                position_value = entry_price * quantity
                margin = position_value / leverage if leverage > 0 else position_value
                
                # 计算盈亏金额
                if position_info['side'] == 'BUY':
                    pnl_amount = (current_price - entry_price) * quantity
                else:
                    pnl_amount = (entry_price - current_price) * quantity
                
                # 计算盈亏百分比（相对于保证金，与币安一致）
                pnl_percent_margin = (pnl_amount / margin * 100) if margin > 0 else 0
                
                # 也计算价格百分比（用于显示和移动止损）
                if position_info['side'] == 'BUY':
                    pnl_percent_price = ((current_price - entry_price) / entry_price) * 100
                else:
                    pnl_percent_price = ((entry_price - current_price) / entry_price) * 100

                if bool(config.TRADING_CONFIG.get('POSITION_DETAILED_LOG_ENABLED', False)):
                    stop_loss_snapshot = position_info.get('stopLoss')
                    take_profit_1_snapshot = position_info.get('takeProfit1')
                    take_profit_2_snapshot = position_info.get('takeProfit2', position_info.get('takeProfit'))
                    logger.info(
                        f"{symbol} [持仓监控快照] side={position_info['side']} | entry={entry_price:.6f} | "
                        f"price={current_price:.6f} | sl={stop_loss_snapshot if stop_loss_snapshot is not None else '-'} | "
                        f"tp1={take_profit_1_snapshot if take_profit_1_snapshot is not None else '-'} | "
                        f"tp2={take_profit_2_snapshot if take_profit_2_snapshot is not None else '-'} | "
                        f"roe_margin={pnl_percent_margin:.2f}% | price_change={pnl_percent_price:.2f}%"
                    )
                
                # 更新最大盈利（基于保证金）及最后一次创新高时间（用于滞涨早止盈）
                max_profit = float(position_info.get('maxProfit', 0) or 0)
                if pnl_percent_margin > max_profit:
                    position_info['maxProfit'] = pnl_percent_margin
                    position_info['lastNewHighTs'] = time.time()
                
                # 滞涨早止盈：曾涨到约 N% 后 X 小时内未创新高 → 分批减仓 + 抬止损（与早止盈/移动止损并列，不冲突）
                stagnation_enabled = bool(config.TRADING_CONFIG.get('STAGNATION_EARLY_EXIT_ENABLED', False))
                if stagnation_enabled and not position_info.get('stagnationExitTriggered', False):
                    min_runup = float(config.TRADING_CONFIG.get('STAGNATION_MIN_RUNUP_PCT', 10) or 10)
                    stall_hours = float(config.TRADING_CONFIG.get('STAGNATION_NO_NEW_HIGH_HOURS', 3) or 3)
                    partial_pct = float(config.TRADING_CONFIG.get('STAGNATION_PARTIAL_CLOSE_PCT', 0.5) or 0.5)
                    lock_pct = float(config.TRADING_CONFIG.get('STAGNATION_LOCK_PCT', 5) or 5)
                    last_high_ts = position_info.get('lastNewHighTs')
                    if last_high_ts is not None and max_profit >= min_runup and pnl_percent_margin < max_profit:
                        stall_sec = stall_hours * 3600
                        if (time.time() - last_high_ts) >= stall_sec:
                            logger.info(
                                f"{symbol} [滞涨早止盈] 曾达浮盈{max_profit:.2f}%≥{min_runup}%，"
                                f"已{stall_hours:.1f}h未创新高，执行分批减仓+抬止损"
                            )
                            try:
                                close_side = 'SELL' if position_info['side'] == 'BUY' else 'BUY'
                                close_position_side = 'LONG' if position_info['side'] == 'BUY' else 'SHORT'
                                remaining_qty = float(position_info.get('remainingQuantity', quantity))
                                partial_quantity = remaining_qty * partial_pct
                                live_amt = await self._get_live_position_amt(symbol, position_side=close_position_side)
                                if live_amt is not None and abs(live_amt) > 0:
                                    partial_quantity = min(partial_quantity, abs(live_amt))
                                    partial_quantity = await self._adjust_close_quantity(symbol, partial_quantity)
                                    if partial_quantity > 0:
                                        partial_order = await self.client.place_order(
                                            symbol=symbol, side=close_side, quantity=partial_quantity,
                                            order_type='MARKET', reduce_only=True, position_side=close_position_side,
                                        )
                                        if partial_order:
                                            position_info['partialProfitTaken'] = True
                                            position_info['remainingQuantity'] = remaining_qty - partial_quantity
                                            logger.info(f"{symbol} [滞涨早止盈] 部分平仓{partial_quantity:.4f}，剩余{position_info['remainingQuantity']:.4f}")
                                remaining_after = float(position_info.get('remainingQuantity', quantity))
                                lev = float(position_info.get('leverage', 10) or 10)
                                rem_margin = (entry_price * remaining_after) / lev if lev > 0 else (entry_price * remaining_after)
                                lock_pct_use = max(lock_pct, max_profit / 2.0)
                                side_here = position_info['side']
                                new_sl = self._stop_price_to_lock_pct(entry_price, side_here, rem_margin, remaining_after, lock_pct_use)
                                breakeven = self._breakeven_stop_price(entry_price, side_here)
                                current_sl = position_info.get('stopLoss')
                                set_sl = (side_here == 'BUY' and (current_sl is None or new_sl > current_sl) and new_sl >= breakeven) or (
                                    side_here == 'SELL' and (current_sl is None or new_sl < current_sl) and new_sl <= breakeven)
                                if set_sl:
                                    position_info['stopLoss'] = new_sl
                                    logger.info(f"{symbol} [滞涨早止盈] 剩余仓位止损上移至 {new_sl:.4f}，锁定约{lock_pct_use:.1f}%利润")
                                    try:
                                        await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
                                    except Exception as sync_e:
                                        logger.warning(f"{symbol} [滞涨早止盈] 同步止损至交易所失败: {sync_e}")
                                position_info['stagnationExitTriggered'] = True
                            except Exception as e:
                                logger.warning(f"{symbol} [滞涨早止盈] 执行异常: {e}", exc_info=False)
                
                # 移动止损逻辑（盈利后保护利润，基于保证金）
                # 每次检查时从Redis重新加载配置，确保配置修改能即时生效
                try:
                    if config._config_manager:
                        config._config_manager.reload_from_redis()
                        config.TRADING_CONFIG = config._get_trading_config()
                except Exception as e:
                    logger.debug(f"从Redis重新加载配置失败: {e}")
                
                # 山寨币早止盈：盈利达标且持仓满 N 小时则市价止盈，不必等挂单 TP，避免反转回吐
                early_tp_enabled = config.TRADING_CONFIG.get('EARLY_TAKE_PROFIT_ENABLED', True)
                if early_tp_enabled and pnl_percent_margin > 0:
                    min_pnl_pct = float(config.TRADING_CONFIG.get('EARLY_TAKE_PROFIT_MIN_PNL_PCT', 10) or 10)
                    min_hold_hours = float(config.TRADING_CONFIG.get('EARLY_TAKE_PROFIT_MIN_HOLD_HOURS', 2) or 2)
                    hold_hours = 0.0
                    entry_time = position_info.get('entryTime')
                    if entry_time is not None:
                        try:
                            if isinstance(entry_time, datetime):
                                hold_hours = (get_beijing_time() - entry_time).total_seconds() / 3600.0
                            elif isinstance(entry_time, str) and len(entry_time) >= 19:
                                entry_dt = datetime.strptime(entry_time[:19], '%Y-%m-%d %H:%M:%S')
                                hold_hours = (get_beijing_time() - entry_dt).total_seconds() / 3600.0
                            else:
                                hold_hours = (time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0)) / 3600.0
                        except Exception:
                            pass
                    if pnl_percent_margin >= min_pnl_pct and hold_hours >= min_hold_hours:
                        logger.info(
                            f"{symbol} [早止盈] 盈利{pnl_percent_margin:.2f}%≥{min_pnl_pct}% 且持仓{hold_hours:.1f}h≥{min_hold_hours}h，市价止盈离场（山寨币不必久拿）"
                        )
                        if await self.close_position(symbol, reason='early_take_profit'):
                            closed_positions.append(symbol)
                        continue
                
                # 检查是否启用移动止损（默认False，需要显式启用）
                profit_protection_enabled = bool(config.TRADING_CONFIG.get('PROFIT_PROTECTION_ENABLED', True))
                use_trailing = profit_protection_enabled and bool(config.TRADING_CONFIG.get('USE_TRAILING_STOP', True))
                if use_trailing:
                    logger.debug(f"{symbol} [移动止损] 已启用，将检查移动止损逻辑")
                else:
                    if not profit_protection_enabled:
                        logger.debug(f"{symbol} [移动止损/保本] 已禁用（PROFIT_PROTECTION_ENABLED=False）")
                    else:
                        logger.debug(f"{symbol} [移动止损] 已禁用（USE_TRAILING_STOP=False），跳过移动止损检查")
                if use_trailing:
                    trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.10)  # 相对于保证金，默认 10%
                    trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.01)  # 相对于保证金
                    
                    # ⚠️ 关键修复：配置值格式转换（兼容百分比形式和比例形式）
                    # 如果值>1，认为是百分比形式，转换为比例形式
                    if trailing_activation > 1:
                        trailing_activation = trailing_activation / 100.0
                    if trailing_protect > 1:
                        trailing_protect = trailing_protect / 100.0
                    lock_pct = config.TRADING_CONFIG.get('LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT')
                    if lock_pct is None:
                        lock_pct = 0.03
                    if lock_pct and lock_pct > 1:
                        lock_pct = lock_pct / 100.0
                    
                    if not position_info.get('trailingStopActivated', False):
                        # 盈利达一定比例时尽早将止损移至含手续费保本（与实时监控一致）
                        if lock_pct > 0 and not position_info.get('breakevenStopSet', False) and pnl_percent_margin >= lock_pct * 100:
                            breakeven = self._breakeven_stop_price(entry_price, side)
                            current_sl = position_info.get('stopLoss')
                            set_be = (side == 'BUY' and (current_sl is None or current_sl < breakeven)) or (side == 'SELL' and (current_sl is None or current_sl > breakeven))
                            if set_be:
                                position_info['stopLoss'] = breakeven
                                position_info['breakevenStopSet'] = True
                                logger.info(f"{symbol} [定时检查] 盈利{pnl_percent_margin:.2f}%≥{lock_pct*100:.0f}%，止损已移至含手续费保本价 {breakeven:.4f}")
                                _log_trailing_stop_event(self.account_id, symbol, "breakeven_set", breakeven=breakeven, pnl_pct=pnl_percent_margin, source="定时检查")
                                logger.info(f"{symbol} [定时检查] 尝试将保本止损同步至交易所")
                                try:
                                    await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
                                    _log_trailing_stop_event(self.account_id, symbol, "breakeven_sync_ok", breakeven=breakeven, source="定时检查")
                                except Exception as sync_e:
                                    _log_trailing_stop_event(self.account_id, symbol, "breakeven_sync_fail", breakeven=breakeven, error=str(sync_e), source="定时检查")
                                    logger.warning(
                                        f"{symbol} [定时检查] 同步保本止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                                        exc_info=False,
                                    )
                        await self._apply_profit_lock_stage(
                            symbol=symbol,
                            position_info=position_info,
                            entry_price=entry_price,
                            quantity=quantity,
                            leverage=leverage,
                            pnl_amount=pnl_amount,
                            pnl_percent_margin=pnl_percent_margin,
                            current_price=current_price,
                            source="定时检查",
                        )
                        # 盈利超过阈值后（相对于保证金），激活移动止损
                        if pnl_percent_margin > trailing_activation * 100:
                            position_info['trailingStopActivated'] = True
                            breakeven = self._breakeven_stop_price(entry_price, side)
                            position_info['stopLoss'] = breakeven
                            logger.info(
                                f"{symbol} 移动止损激活: 止损移至含手续费保本价 {breakeven:.4f} (入场: {entry_price:.4f}) "
                                f"(盈利: {pnl_percent_margin:.2f}% of margin)"
                            )
                            _log_trailing_stop_event(self.account_id, symbol, "trailing_activated", breakeven=breakeven, pnl_pct=pnl_percent_margin, source="定时检查")
                            logger.info(f"{symbol} [定时检查] 尝试将移动止损同步至交易所")
                            try:
                                await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
                                _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_ok", new_stop_loss=breakeven, source="定时检查")
                                logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
                            except Exception as sync_e:
                                _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_fail", new_stop_loss=breakeven, error=str(sync_e), source="定时检查")
                                logger.warning(
                                    f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                                    exc_info=False,
                                )
                    else:
                        # 盈利超过阈值后，止损移至保护利润位（基于保证金）
                        # 如果已经部分止盈，使用剩余仓位计算
                        if position_info.get('partialProfitTaken', False):
                            remaining_quantity = position_info.get('remainingQuantity', quantity)
                            remaining_margin = (entry_price * remaining_quantity) / leverage if leverage > 0 else (entry_price * remaining_quantity)
                            protect_amount = max(remaining_margin * trailing_protect, self._min_protect_amount_for_fees(remaining_margin, leverage))
                            if position_info['side'] == 'BUY':
                                remaining_pnl = (current_price - entry_price) * remaining_quantity
                            else:
                                remaining_pnl = (entry_price - current_price) * remaining_quantity
                            if position_info['side'] == 'BUY':
                                new_stop_loss = entry_price + (remaining_pnl - protect_amount) / remaining_quantity
                                new_stop_loss = max(new_stop_loss, self._breakeven_stop_price(entry_price, 'BUY'))
                                current_sl = float(position_info['stopLoss']) if position_info.get('stopLoss') is not None else None
                                if current_sl is None or new_stop_loss > current_sl:
                                    position_info['stopLoss'] = new_stop_loss
                                    logger.info(
                                        f"{symbol} 移动止损更新（剩余仓位）: {new_stop_loss:.4f} "
                                        f"(保护{trailing_protect*100:.1f}% of remaining margin = {protect_amount:.4f} USDT, "
                                        f"剩余数量: {remaining_quantity:.4f})"
                                    )
                                    _log_trailing_stop_event(self.account_id, symbol, "trailing_update", new_stop_loss=new_stop_loss, source="定时检查-剩余仓位")
                                    try:
                                        await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
                                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_ok", new_stop_loss=new_stop_loss, source="定时检查")
                                        logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
                                    except Exception as sync_e:
                                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_fail", new_stop_loss=new_stop_loss, error=str(sync_e), source="定时检查")
                                        logger.warning(
                                            f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                                            exc_info=False,
                                        )
                            else:
                                # 做空锁利：止损下移，锁住 protect_amount 利润。(entry - stop)*q = protect → stop = entry - protect/q
                                new_stop_loss = entry_price - protect_amount / remaining_quantity
                                new_stop_loss = max(new_stop_loss, self._breakeven_stop_price(entry_price, 'SELL'))
                                current_sl = float(position_info['stopLoss']) if position_info.get('stopLoss') is not None else None
                                # 仅当新止损低于当前止损（下移锁利）且高于市价（锁住的是利润）时更新
                                if (current_sl is None or new_stop_loss < current_sl) and new_stop_loss > current_price and remaining_pnl > 0:
                                    position_info['stopLoss'] = new_stop_loss
                                    logger.info(
                                        f"{symbol} 移动止损更新（剩余仓位-做空锁利）: {new_stop_loss:.4f} "
                                        f"(保护{trailing_protect*100:.1f}% of remaining margin = {protect_amount:.4f} USDT, "
                                        f"剩余数量: {remaining_quantity:.4f})"
                                    )
                                    _log_trailing_stop_event(self.account_id, symbol, "trailing_update", new_stop_loss=new_stop_loss, source="定时检查-剩余仓位")
                                    try:
                                        await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
                                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_ok", new_stop_loss=new_stop_loss, source="定时检查")
                                        logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
                                    except Exception as sync_e:
                                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_fail", new_stop_loss=new_stop_loss, error=str(sync_e), source="定时检查")
                                        logger.warning(
                                            f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                                            exc_info=False,
                                        )
                        else:
                            # 未部分止盈，使用原始仓位计算；保护金额至少覆盖手续费
                            protect_amount = max(margin * trailing_protect, self._min_protect_amount_for_fees(margin, leverage))
                            if position_info['side'] == 'BUY':
                                new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity
                                new_stop_loss = max(new_stop_loss, self._breakeven_stop_price(entry_price, 'BUY'))
                                current_sl = float(position_info['stopLoss']) if position_info.get('stopLoss') is not None else None
                                if current_sl is None or new_stop_loss > current_sl:
                                    position_info['stopLoss'] = new_stop_loss
                                    logger.info(
                                        f"{symbol} 移动止损更新: {new_stop_loss:.4f} "
                                        f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)"
                                    )
                                    _log_trailing_stop_event(self.account_id, symbol, "trailing_update", new_stop_loss=new_stop_loss, source="定时检查")
                                    try:
                                        await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
                                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_ok", new_stop_loss=new_stop_loss, source="定时检查")
                                        logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
                                    except Exception as sync_e:
                                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_fail", new_stop_loss=new_stop_loss, error=str(sync_e), source="定时检查")
                                        logger.warning(
                                            f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                                            exc_info=False,
                                        )
                            else:
                                # 做空锁利：止损下移（从初始高位往市价方向移），锁住 protect_amount。(entry - stop)*q = protect → stop = entry - protect/q
                                new_stop_loss = entry_price - protect_amount / quantity
                                new_stop_loss = max(new_stop_loss, self._breakeven_stop_price(entry_price, 'SELL'))
                                current_sl = float(position_info['stopLoss']) if position_info.get('stopLoss') is not None else None
                                # 仅当新止损低于当前止损（下移锁利）且高于市价（锁住的是利润）时更新
                                if (current_sl is None or new_stop_loss < current_sl) and new_stop_loss > current_price and pnl_amount > 0:
                                    position_info['stopLoss'] = new_stop_loss
                                    logger.info(
                                        f"{symbol} 移动止损更新（做空锁利）: {new_stop_loss:.4f} "
                                        f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)"
                                    )
                                    _log_trailing_stop_event(self.account_id, symbol, "trailing_update", new_stop_loss=new_stop_loss, source="定时检查")
                                    try:
                                        await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
                                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_ok", new_stop_loss=new_stop_loss, source="定时检查")
                                        logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
                                    except Exception as sync_e:
                                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_fail", new_stop_loss=new_stop_loss, error=str(sync_e), source="定时检查")
                                        logger.warning(
                                            f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                                            exc_info=False,
                                        )
                
                # 检查止损（使用更新后的止损价，基于保证金收益比）
                # ⚠️ 重要：止损检查应该在时间锁之前，止损必须立即执行
                stop_loss_raw = position_info.get('stopLoss')
                stop_loss = float(stop_loss_raw) if stop_loss_raw is not None else None
                should_close_due_to_sl = False
                exit_reason_sl = None
                
                if stop_loss is None:
                    logger.warning(f"{symbol} 止损价未设置，跳过止损检查")
                elif stop_loss is not None:
                    # 计算止损对应的保证金百分比目标
                    if position_info['side'] == 'BUY':
                        stop_loss_amount = (entry_price - stop_loss) * quantity
                    else:  # SELL
                        stop_loss_amount = (stop_loss - entry_price) * quantity
                    
                    stop_loss_pct_margin = (stop_loss_amount / margin * 100) if margin > 0 else 0
                    
                    # 直接比较当前盈亏百分比与止损目标（基于保证金）
                    if pnl_percent_margin <= -stop_loss_pct_margin:
                        should_close_due_to_sl = True
                        # ⚠️ 2026-01-27优化：如果已部分止盈，细分状态
                        partial_profit_taken = position_info.get('partialProfitTaken', False)
                        if partial_profit_taken:
                            if position_info.get('trailingStopActivated'):
                                exit_reason_sl = 'take_profit_partial_then_trailing_stop'
                            else:
                                exit_reason_sl = 'take_profit_partial_then_stop'
                        else:
                            exit_reason_sl = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss'
                        
                        # 计算持仓时间
                        entry_time = position_info.get('entryTime')
                        hold_time_minutes = 0
                        if entry_time:
                            try:
                                if isinstance(entry_time, datetime):
                                    hold_time_sec = int((get_beijing_time() - entry_time).total_seconds())
                                else:
                                    hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0))
                                hold_time_minutes = hold_time_sec / 60.0
                            except Exception:
                                hold_time_minutes = 0
                        
                        # 详细诊断日志：记录平仓时的所有关键信息
                        logger.warning("=" * 80)
                        logger.warning(f"{symbol} [平仓诊断日志] ===== 触发止损平仓 =====")
                        logger.warning(f"  平仓原因: {exit_reason_sl}")
                        logger.warning(f"  入场价格: {entry_price:.6f} USDT")
                        logger.warning(f"  当前价格: {current_price:.4f} USDT")
                        logger.warning(f"  止损价格: {stop_loss:.4f} USDT")
                        logger.warning(f"  持仓数量: {quantity:.4f}")
                        logger.warning(f"  持仓时间: {hold_time_minutes:.1f} 分钟")
                        logger.warning(f"  入场时间: {entry_time}")
                        logger.warning(f"  当前盈亏: {pnl_percent_margin:.2f}% of margin")
                        logger.warning(f"  止损目标: -{stop_loss_pct_margin*100:.2f}% of margin")
                        logger.warning(f"  亏损金额: {abs(pnl_amount):.4f} USDT")
                        if position_info.get('trailingStopActivated'):
                            logger.warning(f"  移动止损: 已激活（从初始止损 {position_info.get('initialStopLoss', 'N/A')} 调整）")
                        logger.warning("=" * 80)
                        
                        # 止损必须立即执行，不受时间锁限制
                        # 更新数据库
                        # 更新数据库
                        if DB_AVAILABLE:
                            trade_id = position_info.get('tradeId')
                            if trade_id:
                                try:
                                    # 计算持仓持续时间
                                    entry_time = position_info.get('entryTime')
                                    duration_minutes = None
                                    if entry_time:
                                        try:
                                            if isinstance(entry_time, str):
                                                entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S')
                                            else:
                                                entry_dt = entry_time
                                            exit_dt = get_beijing_time()  # 使用北京时间计算持续时间
                                            duration = exit_dt - entry_dt
                                            duration_minutes = int(duration.total_seconds() / 60)
                                        except Exception as e:
                                            logger.debug(f"计算持仓持续时间失败: {e}")
                                    
                                    # 获取策略类型
                                    strategy_type = position_info.get('strategyType', 'trend_following')
                                    
                                    Trade.update_exit(
                                        trade_id=trade_id,
                                        exit_price=current_price,
                                        exit_reason=exit_reason_sl,
                                        pnl=pnl_amount,
                                        pnl_percent=pnl_percent_margin,
                                        strategy_type=strategy_type,
                                        duration_minutes=duration_minutes
                                    )
                                except Exception as e:
                                    logger.warning(f"更新止损记录失败: {e}")
                        # ⚠️ 关键修复：止损必须立即执行，不受时间锁限制
                        if await self.close_position(symbol, reason=exit_reason_sl):
                            closed_positions.append(symbol)
                        continue  # 止损已执行，跳过后续止盈检查
                
                # 检查分步止盈（基于保证金收益比）
                # ⚠️ 优化：已移除止盈时间锁，止盈可以立即执行（与止损一致）
                # 理由：1) 止损已不受时间锁限制，止盈也应该一致
                #       2) 分步止盈策略本身已提供利润保护（50%在1:1止盈，剩余保本）
                #       3) 交易所级别止盈单已提供保护
                #       4) 及时止盈可以保护利润，避免价格回落
                take_profit_1_raw = position_info.get('takeProfit1')  # 第一目标（盈亏比1:1）
                take_profit_1 = float(take_profit_1_raw) if take_profit_1_raw is not None else None
                take_profit_2_raw = position_info.get('takeProfit2', position_info.get('takeProfit'))  # 第二目标
                take_profit_2 = float(take_profit_2_raw) if take_profit_2_raw is not None else None
                partial_profit_taken = position_info.get('partialProfitTaken', False)
                remaining_quantity = float(position_info.get('remainingQuantity', quantity))
                
                # 第一目标：TAKE_PROFIT_1_PERCENT 止盈（默认15%保证金），了结50%仓位
                # ✅ 已移除时间锁限制，可以立即执行
                if not partial_profit_taken and take_profit_1 is not None:
                    # 直接使用配置的 TAKE_PROFIT_1_PERCENT，与开仓时计算的第一目标一致
                    take_profit_1_pct_margin_config = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.20)
                    # 兼容百分比形式和比例形式
                    if take_profit_1_pct_margin_config > 1:
                        take_profit_1_pct_margin_config = take_profit_1_pct_margin_config / 100.0
                    take_profit_1_pct_margin = take_profit_1_pct_margin_config * 100  # 转换为百分比
                    
                    # 直接比较当前盈亏百分比与第一目标（基于保证金，使用配置值）
                    if pnl_percent_margin >= take_profit_1_pct_margin:
                        logger.info(
                            f"{symbol} 触发第一目标止盈（盈亏比1:1，基于保证金）: "
                            f"当前盈亏={pnl_percent_margin:.2f}% of margin >= 目标={take_profit_1_pct_margin:.2f}% of margin | "
                            f"当前价={current_price:.4f}, 目标价={take_profit_1:.4f}"
                        )
                        # 部分平仓50%
                        partial_quantity = quantity * 0.5
                        try:
                            # 部分平仓
                            close_side = 'SELL' if position_info['side'] == 'BUY' else 'BUY'
                            close_position_side = 'LONG' if position_info['side'] == 'BUY' else 'SHORT'
                            # 二次校验并截断数量，避免 reduceOnly 被拒绝（-2022）
                            live_amt = await self._get_live_position_amt(symbol, position_side=close_position_side)
                            if live_amt is None or abs(live_amt) <= 0:
                                logger.warning(f"{symbol} 部分止盈：实时持仓已为0，跳过部分平仓")
                                continue
                            partial_quantity = min(partial_quantity, abs(live_amt))
                            partial_quantity = await self._adjust_close_quantity(symbol, partial_quantity)
                            if partial_quantity <= 0:
                                logger.warning(f"{symbol} 部分止盈：数量调整后为0，跳过")
                                continue
                            partial_order = await self.client.place_order(
                                symbol=symbol,
                                side=close_side,
                                quantity=partial_quantity,
                                order_type='MARKET',
                                reduce_only=True,  # 部分止盈必须 reduceOnly，避免反向开仓
                                position_side=close_position_side,  # 兼容对冲模式：指定要减少的持仓方向
                            )
                            if partial_order:
                                position_info['partialProfitTaken'] = True
                                position_info['remainingQuantity'] = remaining_quantity - partial_quantity
                                logger.info(
                                    f"{symbol} 部分止盈成功: 平仓{partial_quantity:.4f}，剩余{position_info['remainingQuantity']:.4f}"
                                )
                                # 分步止盈后的“保本”处理：仅在盈利保护总开关开启时移至含手续费保本价
                                if profit_protection_enabled:
                                    breakeven = self._breakeven_stop_price(entry_price, side)
                                    position_info['stopLoss'] = breakeven
                                    logger.info(
                                        f"{symbol} 部分止盈后：剩余仓位止损移至含手续费保本价 {breakeven:.4f}（入场: {entry_price:.4f}），"
                                        f"剩余50%仓位追求1.5:1止盈目标"
                                    )
                            else:
                                # 兜底：可能遇到 -2022（reduceOnly rejected）等竞态，重新查一次持仓
                                try:
                                    live2 = await self._get_live_position_amt(symbol, position_side=close_position_side)
                                except Exception:
                                    live2 = None
                                if live2 is None or abs(live2) <= 0:
                                    logger.warning(f"{symbol} 部分止盈下单返回None，但实时持仓已为0，跳过")
                                    continue
                                logger.warning(f"{symbol} 部分止盈下单返回None（实时持仓仍存在: {live2}），稍后将继续由止损/止盈逻辑处理")
                        except Exception as e:
                            logger.error(f"{symbol} 部分止盈失败: {e}")
                
                # 第二目标：原始止盈价，平掉剩余仓位（基于保证金收益比）
                # ✅ 已移除时间锁限制，可以立即执行
                if partial_profit_taken and take_profit_2 is not None:
                    # 计算第二目标对应的保证金百分比
                    if position_info['side'] == 'BUY':
                        take_profit_2_amount = (take_profit_2 - entry_price) * remaining_quantity
                    else:  # SELL
                        take_profit_2_amount = (entry_price - take_profit_2) * remaining_quantity
                    # 使用剩余仓位的保证金
                    remaining_margin = (entry_price * remaining_quantity) / leverage if leverage > 0 else (entry_price * remaining_quantity)
                    take_profit_2_pct_margin = (take_profit_2_amount / remaining_margin * 100) if remaining_margin > 0 else 0
                    # 计算剩余仓位的当前盈亏
                    if position_info['side'] == 'BUY':
                        remaining_pnl_amount = (current_price - entry_price) * remaining_quantity
                    else:
                        remaining_pnl_amount = (entry_price - current_price) * remaining_quantity
                    remaining_pnl_pct_margin = (remaining_pnl_amount / remaining_margin * 100) if remaining_margin > 0 else 0
                    
                    # 直接比较剩余仓位盈亏百分比与第二目标（基于保证金）
                    if remaining_pnl_pct_margin >= take_profit_2_pct_margin:
                        logger.info(
                            f"{symbol} 触发第二目标止盈（基于保证金）: "
                            f"剩余仓位盈亏={remaining_pnl_pct_margin:.2f}% of margin >= 目标={take_profit_2_pct_margin:.2f}% of margin | "
                            f"当前价={current_price:.4f}, 目标价={take_profit_2:.4f}, "
                            f"剩余数量={remaining_quantity:.4f}"
                        )
                        # ⚠️ 关键修复：统一使用 'take_profit_partial_then_take_profit' 来区分"第一目标止盈后第二目标止盈"
                        exit_reason = 'take_profit_partial_then_take_profit'
                        # 计算总盈亏（原始仓位 + 部分止盈的盈亏）
                        # 部分止盈时的价格需要从数据库或记录中获取，这里简化处理
                        total_pnl_amount = remaining_pnl_amount  # 简化：只计算剩余仓位盈亏
                        total_pnl_percent = (total_pnl_amount / margin * 100) if margin > 0 else 0
                        
                        # 更新数据库
                        if DB_AVAILABLE:
                            trade_id = position_info.get('tradeId')
                            if trade_id:
                                try:
                                    # 计算持仓持续时间
                                    entry_time = position_info.get('entryTime')
                                    duration_minutes = None
                                    if entry_time:
                                        try:
                                            if isinstance(entry_time, str):
                                                entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S')
                                            else:
                                                entry_dt = entry_time
                                            exit_dt = get_beijing_time()  # 使用北京时间计算持续时间
                                            duration = exit_dt - entry_dt
                                            duration_minutes = int(duration.total_seconds() / 60)
                                        except Exception as e:
                                            logger.debug(f"计算持仓持续时间失败: {e}")
                                    
                                    # 获取策略类型
                                    strategy_type = position_info.get('strategyType', 'trend_following')
                                    
                                    Trade.update_exit(
                                        trade_id=trade_id,
                                        exit_price=current_price,
                                        exit_reason=exit_reason,
                                        pnl=total_pnl_amount,
                                        pnl_percent=total_pnl_percent,
                                        strategy_type=strategy_type,
                                        duration_minutes=duration_minutes
                                    )
                                except Exception as e:
                                    logger.warning(f"更新止盈记录失败: {e}")
                        if await self.close_position(symbol, reason=exit_reason):
                            closed_positions.append(symbol)
                        continue
                else:
                    # 如果未部分止盈，但达到【第二目标】止盈价对应的收益比时，才全部平仓
                    # ⚠️ 修复：不再使用 TAKE_PROFIT_PERCENT(10%) 作为全平条件，否则会“刚赚一点就整仓止盈”
                    # 改为使用 take_profit_2 价格对应的保证金收益%，与第一目标(20%) 取较大者，避免盈利过少
                    take_profit_2_full = position_info.get('takeProfit2', position_info.get('takeProfit'))
                    take_profit_2_full = float(take_profit_2_full) if take_profit_2_full is not None else None
                    if take_profit_2_full is not None and margin and margin > 0:
                        if position_info['side'] == 'BUY':
                            take_profit_2_amount = (take_profit_2_full - entry_price) * quantity
                        else:
                            take_profit_2_amount = (entry_price - take_profit_2_full) * quantity
                        take_profit_2_pct_margin = (take_profit_2_amount / margin * 100) if margin > 0 else 0
                        # 至少要求达到第一目标对应的收益%（如 20%），避免过早全平
                        take_profit_1_pct = (config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.20) or 0.20)
                        if take_profit_1_pct > 1:
                            take_profit_1_pct = take_profit_1_pct / 100.0
                        min_pct_for_full_tp = max(take_profit_2_pct_margin, take_profit_1_pct * 100)
                        
                        if pnl_percent_margin >= min_pct_for_full_tp:
                            logger.info(
                                f"{symbol} 触发止盈（第二目标/保证金）: "
                                f"当前盈亏={pnl_percent_margin:.2f}% of margin >= 目标={min_pct_for_full_tp:.2f}% | "
                                f"当前价={current_price:.4f}, 第二目标价={take_profit_2_full:.4f}"
                            )
                            exit_reason = 'take_profit'
                            # 更新数据库
                            if DB_AVAILABLE:
                                trade_id = position_info.get('tradeId')
                                if trade_id:
                                    try:
                                        # 计算持仓持续时间和策略类型
                                        entry_time = position_info.get('entryTime')
                                        duration_minutes = None
                                        if entry_time:
                                            try:
                                                from datetime import datetime
                                                if isinstance(entry_time, str):
                                                    entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S')
                                                else:
                                                    entry_dt = entry_time
                                                exit_dt = get_beijing_time()  # 使用北京时间计算持续时间
                                                duration = exit_dt - entry_dt
                                                duration_minutes = int(duration.total_seconds() / 60)
                                            except Exception as e:
                                                logger.debug(f"计算持仓持续时间失败: {e}")
                                        
                                        strategy_type = position_info.get('strategyType', 'trend_following')
                                        
                                        Trade.update_exit(
                                            trade_id=trade_id,
                                            exit_price=current_price,
                                            exit_reason=exit_reason,
                                            pnl=pnl_amount,
                                            pnl_percent=pnl_percent_margin,
                                            strategy_type=strategy_type,
                                            duration_minutes=duration_minutes
                                        )
                                    except Exception as e:
                                        logger.warning(f"更新止盈记录失败: {e}")
                            if await self.close_position(symbol, reason=exit_reason):
                                closed_positions.append(symbol)
                            continue
                
        except Exception as e:
            err_msg = str(e).strip() or repr(e) or type(e).__name__
            logger.error(f"检查止损止盈失败: {err_msg}")
        
        return closed_positions
    
    async def get_position_summary(self) -> Dict:
        """
        获取持仓摘要
        
        Returns:
            持仓摘要信息
        """
        try:
            positions = await self._get_open_positions()
            balance = await self._get_account_balance()
            
            total_pnl = sum(p['unRealizedProfit'] for p in positions)
            
            position_list = []
            for p in positions:
                symbol = p['symbol']
                pos_data = {
                    'symbol': symbol,
                    'positionAmt': p['positionAmt'],
                    'entryPrice': p['entryPrice'],
                    'pnl': p['unRealizedProfit'],
                    'leverage': p.get('leverage', 10)
                }
                
                # 尝试从内存或数据库补充详细信息
                active_pos = self.active_positions.get(symbol)
                if active_pos:
                    pos_data.update({
                        'entry_time': active_pos.get('entryTime'),
                        'stop_loss_price': active_pos.get('stopLoss'),
                        'take_profit_price': active_pos.get('takeProfit'),
                        'atr': active_pos.get('atr'),
                        'entry_reason': active_pos.get('entryReason')
                    })
                elif DB_AVAILABLE and Trade:
                    # 从数据库查询
                    try:
                        trades = Trade.get_by_symbol(symbol, status='open', account_id=self.account_id)
                        if trades:
                            # 取最新的一个
                            trade = trades[0]
                            pos_data.update({
                                'entry_time': trade.get('entry_time'),
                                'stop_loss_price': trade.get('stop_loss_price'),
                                'take_profit_price': trade.get('take_profit_price'),
                                'atr': trade.get('atr'),
                                'entry_reason': trade.get('entry_reason')
                            })
                    except Exception:
                        pass
                
                position_list.append(pos_data)
            
            return {
                'totalPositions': len(positions),
                'totalBalance': balance.get('total', 0),
                'availableBalance': balance.get('available', 0),
                'totalPnL': total_pnl,
                'positions': position_list
            }
        except Exception as e:
            logger.error(f"获取持仓摘要失败: {e}")
            return {}
    
    async def _reconcile_pending_with_binance(self) -> int:
        """正向流程加固：对 status=pending 记录查币安订单，若已 FILLED 则更新为 open。"""
        try:
            from .pending_reconcile import reconcile_pending_with_binance
            return await reconcile_pending_with_binance(self.client, self.account_id)
        except ImportError:
            return 0
    
    async def sync_positions_with_binance(self):
        """
        同步币安实际持仓状态与数据库状态
        检查哪些持仓在数据库中还是open状态，但在币安已经不存在了
        """
        if not DB_AVAILABLE or not Trade:
            logger.debug("数据库不可用，跳过持仓状态同步")
            return
        
        try:
            logger.info("开始同步币安持仓状态与数据库...")
            
            # 0. 正向流程加固：pending 对账（补齐因 WS 断线/进程重启漏掉的 pending→open）
            n = await self._reconcile_pending_with_binance()
            if n > 0:
                logger.info(f"[账号{self.account_id}] pending 对账完成，{n} 条已更新为 open")
            
            # 1. 获取币安实际持仓
            binance_positions = await self._get_open_positions()
            binance_symbols = {p['symbol'] for p in binance_positions}
            logger.debug(f"币安实际持仓: {len(binance_symbols)} 个 ({', '.join(binance_symbols) if binance_symbols else '无'})")
            
            # 2. 获取数据库中状态为open的交易记录（仅当前账号，限制条数防内存暴增）
            db_open_trades = Trade.get_all(status='open', account_id=self.account_id, limit=500)
            db_open_symbols = {t['symbol'] for t in db_open_trades}
            logger.debug(f"数据库open状态: {len(db_open_symbols)} 个 ({', '.join(db_open_symbols) if db_open_symbols else '无'})")
            
            # 2.5 用「DB 有 open 且币安也有」的持仓填充 active_positions，确保这些持仓会被后续启动的监控覆盖
            in_both = db_open_symbols & binance_symbols
            for symbol in in_both:
                if symbol in self.active_positions:
                    continue
                trade = next((t for t in db_open_trades if t.get('symbol') == symbol), None)
                pos = next((p for p in binance_positions if p.get('symbol') == symbol), None)
                if not trade or not pos or float(pos.get('positionAmt', 0)) == 0:
                    continue
                try:
                    entry_price = float(trade.get('entry_price', 0) or pos.get('entryPrice', 0))
                    quantity = abs(float(pos.get('positionAmt', 0)))
                    side = 'BUY' if float(pos.get('positionAmt', 0)) > 0 else 'SELL'
                    leverage = float(pos.get('leverage', 10) or trade.get('leverage', 10) or 10)
                    stop_loss_price = trade.get('stop_loss_price')
                    take_profit_price = trade.get('take_profit_price') or trade.get('take_profit_1')
                    take_profit_1 = trade.get('take_profit_1')
                    take_profit_2 = trade.get('take_profit_2')
                    if stop_loss_price is None or take_profit_price is None:
                        stop_loss_pct = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.08)
                        if stop_loss_pct is not None and stop_loss_pct > 1:
                            stop_loss_pct = stop_loss_pct / 100.0
                        take_profit_pct = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.15)
                        if take_profit_pct is not None and take_profit_pct > 1:
                            take_profit_pct = take_profit_pct / 100.0
                        if not take_profit_pct:
                            take_profit_pct = (stop_loss_pct or 0.08) * 2.0
                        stop_loss_price = self.risk_manager.get_stop_loss_price(entry_price, side, quantity, leverage, stop_loss_pct=stop_loss_pct)
                        take_profit_price = take_profit_price or self.risk_manager.get_take_profit_price(entry_price, side, quantity, leverage, take_profit_pct=take_profit_pct)
                    if take_profit_2 is None and take_profit_price is not None:
                        take_profit_2 = take_profit_price
                    if take_profit_1 is not None and take_profit_2 is not None:
                        if side == 'BUY':
                            if take_profit_1 >= take_profit_2 and take_profit_2 > entry_price:
                                closer = min(take_profit_1, take_profit_2)
                                further = max(take_profit_1, take_profit_2)
                                if closer > entry_price:
                                    take_profit_1 = closer
                                    take_profit_2 = further
                        else:
                            if take_profit_1 <= take_profit_2 and take_profit_2 < entry_price:
                                closer = max(take_profit_1, take_profit_2)
                                further = min(take_profit_1, take_profit_2)
                                if closer < entry_price:
                                    take_profit_1 = closer
                                    take_profit_2 = further
                    position_info = {
                        'symbol': symbol,
                        'side': side,
                        'quantity': quantity,
                        'entryPrice': entry_price,
                        'changePercent': 0,
                        'orderId': trade.get('entry_order_id'),
                        'tradeId': trade.get('id'),
                        'stopLoss': stop_loss_price,
                        'takeProfit': take_profit_price,
                        'takeProfit1': take_profit_1,
                        'takeProfit2': take_profit_2,
                        'partialProfitTaken': False,
                        'remainingQuantity': quantity,
                        'initialStopLoss': stop_loss_price,
                        'leverage': leverage,
                        'entryReason': trade.get('entry_reason') or 'db_sync',
                        'entryTime': trade.get('entry_time'),
                        'atr': trade.get('atr'),
                        'maxProfit': 0.0,
                        'trailingStopActivated': False,
                        'breakevenStopSet': False,
                    }
                    self.active_positions[symbol] = position_info
                    logger.debug(f"{symbol} 已从 DB 载入到 active_positions，便于监控")
                except Exception as e:
                    logger.debug(f"{symbol} 载入 active_positions 失败: {e}")
            
            # 2.6 对「有仓但交易所无 SL/TP」的持仓自动补挂止损止盈（含仅在本系统 active 的仓）
            for symbol in list(self.active_positions.keys()):
                if symbol not in binance_symbols:
                    continue
                position_info = self.active_positions[symbol]
                if not position_info.get('stopLoss') and not position_info.get('takeProfit'):
                    continue
                try:
                    sl_from_ex, tp_from_ex = await self._get_sltp_from_exchange(symbol, position_info['side'])
                    if sl_from_ex is not None and tp_from_ex is not None:
                        continue
                    ticker = await self.client.get_ticker_24h(symbol)
                    current_price = float(ticker.get('price', 0) or position_info.get('entryPrice', 0))
                    await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
                    logger.info(f"[账号{self.account_id}] {symbol} [同步] 检测到交易所无止损/止盈，已补挂 SL/TP")
                except Exception as e:
                    logger.warning(f"[账号{self.account_id}] {symbol} [同步] 补挂 SL/TP 失败: {e}", exc_info=False)
            
            # 3. 找出在数据库中open但在币安已不存在的持仓
            missing_in_binance = db_open_symbols - binance_symbols
            
            if missing_in_binance:
                logger.warning(
                    f"发现 {len(missing_in_binance)} 个持仓在数据库中为open状态，但在币安已不存在: "
                    f"{', '.join(missing_in_binance)}"
                )
                
                # 4. 更新这些持仓的状态
                for symbol in missing_in_binance:
                    try:
                        trades = Trade.get_by_symbol(symbol, status='open', account_id=self.account_id)
                        if not trades:
                            logger.warning(f"{symbol} [状态同步] ⚠️ 数据库中没有找到open状态的交易记录，跳过")
                            continue
                        
                        # 过滤掉刚刚开仓的交易（给予60秒的同步缓冲期）
                        # 避免因API延迟导致刚开的仓位被误判为"丢失"从而被错误关闭
                        import time
                        now_ts = int(time.time())
                        recent_trades = []
                        valid_trades = []
                        
                        for t in trades:
                            try:
                                entry_time = t.get('entry_time')
                                if entry_time:
                                    # 处理 entry_time 可能是 datetime 对象或时间戳的情况
                                    entry_ts = 0
                                    if hasattr(entry_time, 'timestamp'):
                                        entry_ts = int(entry_time.timestamp())
                                    elif isinstance(entry_time, (int, float)):
                                        entry_ts = int(entry_time)
                                    # 如果是刚刚开仓（60秒内），跳过同步关闭
                                    if now_ts - entry_ts < 60:
                                        recent_trades.append(t)
                                        continue
                            except Exception as e:
                                logger.debug(f"{symbol} [状态同步] 检查开仓时间出错: {e}")
                            
                            valid_trades.append(t)
                            
                        if recent_trades:
                            logger.info(f"{symbol} [状态同步] 发现 {len(recent_trades)} 个刚刚开仓的交易（<60s），跳过同步关闭（防止误杀）")
                            
                        if not valid_trades:
                            if not recent_trades:
                                logger.warning(f"{symbol} [状态同步] 没有符合条件的交易记录需要更新")
                            continue
                            
                        logger.info(f"{symbol} [状态同步] 找到 {len(valid_trades)} 条open状态的交易记录，开始更新...")
                    except Exception as get_trades_error:
                        logger.error(
                            f"{symbol} [状态同步] ❌ 获取交易记录失败: "
                            f"错误类型={type(get_trades_error).__name__}, 错误消息={str(get_trades_error)}"
                        )
                        import traceback
                        logger.debug(f"{symbol} [状态同步] 错误详情:\n{traceback.format_exc()}")
                        continue
                    
                    for trade in valid_trades:
                        trade_id = trade.get('id')
                        if not trade_id:
                            logger.warning(f"{symbol} [状态同步] ⚠️ 交易记录缺少ID字段，跳过: {trade}")
                            continue
                        
                        try:
                            logger.info(
                                f"{symbol} [状态同步] 更新交易记录状态 (ID: {trade_id})... | "
                                f"入场价: {trade.get('entry_price', 'N/A')}, "
                                f"数量: {trade.get('quantity', 'N/A')}, "
                                f"方向: {trade.get('side', 'N/A')}"
                            )
                            
                            # 尝试从币安历史订单获取实际平仓价格
                            exit_price = None
                            realized_pnl = None
                            commission = None
                            close_orders = []
                            try:
                                # 优先尝试从成交记录(Trade History)获取精确的 Realized PnL
                                # 这是最准确的方式，因为它包含了资金费率、手续费扣除后的实际盈亏
                                try:
                                    recent_trades = await self.client.get_recent_trades(symbol, limit=50)
                                    
                                    # 确定过滤时间戳
                                    entry_ts_ms_filter = 0
                                    et_obj = trade.get('entry_time')
                                    if hasattr(et_obj, 'timestamp'):
                                        entry_ts_ms_filter = int(et_obj.timestamp() * 1000)
                                    elif isinstance(et_obj, (int, float)):
                                        # 区分秒和毫秒
                                        entry_ts_ms_filter = int(et_obj * 1000) if et_obj < 3000000000 else int(et_obj)
                                    elif isinstance(et_obj, str):
                                        try:
                                            from datetime import datetime
                                            dt_obj = datetime.strptime(et_obj, "%Y-%m-%d %H:%M:%S")
                                            entry_ts_ms_filter = int(dt_obj.timestamp() * 1000)
                                        except:
                                            pass
                                    
                                    if entry_ts_ms_filter > 0:
                                        # 筛选出入场之后的成交记录
                                        closing_trades = [
                                            t for t in recent_trades 
                                            if t.get('time', 0) > entry_ts_ms_filter and float(t.get('realizedPnl', 0)) != 0
                                        ]
                                        
                                        if closing_trades:
                                            total_pnl = 0.0
                                            total_comm = 0.0
                                            total_qty = 0.0
                                            total_val = 0.0
                                            
                                            for t in closing_trades:
                                                total_pnl += float(t.get('realizedPnl', 0))
                                                total_comm += float(t.get('commission', 0))
                                                qty = float(t.get('qty', 0))
                                                price = float(t.get('price', 0))
                                                total_qty += qty
                                                total_val += qty * price
                                            
                                            realized_pnl = total_pnl
                                            commission = total_comm
                                            if total_qty > 0:
                                                exit_price = total_val / total_qty
                                            
                                            logger.info(
                                                f"{symbol} [状态同步] ✓ 从成交记录获取精确盈亏: "
                                                f"PnL={realized_pnl} USDT, 均价={exit_price:.4f}"
                                            )
                                except Exception as trade_hist_err:
                                    err_msg = str(trade_hist_err).strip() or type(trade_hist_err).__name__
                                    logger.warning(f"{symbol} [状态同步] 获取成交记录失败: {err_msg}")

                                # 获取最近的平仓订单（reduceOnly=True的订单）
                                import time
                                end_time = int(time.time() * 1000)  # 当前时间（毫秒）
                                start_time = end_time - (7 * 24 * 60 * 60 * 1000)  # 最近7天
                                
                                # 获取入场时间（毫秒）用于过滤
                                entry_ts_ms = 0
                                try:
                                    et = trade.get('entry_time')
                                    if et:
                                        if isinstance(et, (int, float)):
                                            # 如果小于 3000000000，认为是秒，转毫秒
                                            entry_ts_ms = int(et * 1000) if et < 3000000000 else int(et)
                                        elif hasattr(et, 'timestamp'): # datetime object
                                            entry_ts_ms = int(et.timestamp() * 1000)
                                        elif isinstance(et, str):
                                            # 尝试解析字符串
                                            try:
                                                # 先尝试作为数字解析
                                                et_float = float(et)
                                                entry_ts_ms = int(et_float * 1000) if et_float < 3000000000 else int(et_float)
                                            except ValueError:
                                                # 尝试作为日期字符串解析
                                                try:
                                                    from datetime import datetime
                                                    dt = datetime.strptime(et, "%Y-%m-%d %H:%M:%S")
                                                    entry_ts_ms = int(dt.timestamp() * 1000)
                                                except:
                                                    pass
                                except Exception:
                                    pass

                                # 优化：如果已知入场时间，则仅查询入场之后的订单
                                if entry_ts_ms > 0:
                                    start_time = max(start_time, entry_ts_ms)
                                else:
                                    # 如果没有入场时间（如手动录入且未填时间），缩小搜索范围以防止匹配到很久以前的订单
                                    # 限制为最近1小时，避免匹配到几天的订单导致"时间倒流"
                                    start_time = end_time - (60 * 60 * 1000)
                                    logger.warning(f"{symbol} [状态同步] ⚠️ 交易记录缺少入场时间，将搜索范围限制为最近1小时")
                                
                                logger.debug(
                                    f"{symbol} [状态同步] 获取历史订单: "
                                    f"symbol={symbol}, startTime={start_time}, endTime={end_time}"
                                )
                                
                                # 获取历史订单
                                orders = await self.client.client.futures_get_all_orders(
                                    symbol=symbol,
                                    startTime=start_time,
                                    endTime=end_time,
                                    recvWindow=20000
                                )
                                
                                # 验证 orders 的类型
                                if not isinstance(orders, list):
                                    logger.warning(
                                        f"{symbol} [状态同步] ⚠️ futures_get_all_orders 返回的不是列表: "
                                        f"类型={type(orders)}, 值={orders}"
                                    )
                                    orders = []  # 设置为空列表，避免后续错误
                                
                                if not orders:
                                    logger.debug(f"{symbol} [状态同步] 未找到历史订单")
                                else:
                                    logger.debug(f"{symbol} [状态同步] 找到 {len(orders)} 个历史订单")
                                
                                # 查找最近的平仓订单（reduceOnly=True且已成交）
                                close_orders = []
                                
                                for o in orders:
                                    try:
                                        if isinstance(o, dict) and o.get('reduceOnly') == True and o.get('status') == 'FILLED':
                                            # 再次过滤掉早于入场时间的订单（双重保险）
                                            order_time = int(o.get('updateTime', 0))
                                            if entry_ts_ms > 0 and order_time < entry_ts_ms:
                                                continue
                                            # 如果没有入场时间，且订单时间早于1小时前（虽然startTime已限制，但双重检查），则跳过
                                            if entry_ts_ms == 0 and order_time < (end_time - 3600000):
                                                 continue
                                            
                                            close_orders.append(o)
                                    except (AttributeError, TypeError) as e:
                                        logger.warning(
                                            f"{symbol} [状态同步] ⚠️ 处理订单数据时出错: "
                                            f"错误类型={type(e).__name__}, 错误消息={str(e)}, "
                                            f"订单数据类型={type(o)}, 订单数据={o}"
                                        )
                                        continue
                                
                                if close_orders:
                                    # 按时间倒序排序，取最近的
                                    close_orders.sort(key=lambda x: x.get('updateTime', 0), reverse=True)
                                    latest_order = close_orders[0]
                                    
                                    # 获取平均成交价格
                                    exit_price = float(latest_order.get('avgPrice', 0))
                                    if exit_price > 0:
                                        logger.info(f"{symbol} [状态同步] 从币安历史订单获取平仓价格: {exit_price:.4f} USDT")
                                    else:
                                        logger.warning(
                                            f"{symbol} [状态同步] 历史订单中没有有效的avgPrice: {latest_order}"
                                        )
                            except KeyError as key_error:
                                # KeyError 可能是访问 orders[symbol] 或其他字典访问错误
                                logger.error(
                                    f"{symbol} [状态同步] ❌ 获取历史订单时KeyError: "
                                    f"错误key={key_error}, 错误类型={type(key_error).__name__}"
                                )
                                import traceback
                                logger.debug(f"{symbol} [状态同步] KeyError详情:\n{traceback.format_exc()}")
                            except Exception as order_error:
                                logger.warning(
                                    f"{symbol} [状态同步] 获取历史订单失败: "
                                    f"错误类型={type(order_error).__name__}, 错误消息={str(order_error)}"
                                )
                                import traceback
                                logger.debug(f"{symbol} [状态同步] 错误详情:\n{traceback.format_exc()}")
                            
                            # 如果无法从订单获取，尝试使用当前价格；若当前价格也无法获取，则保留open状态等待下次同步
                            if not exit_price or exit_price <= 0:
                                try:
                                    ticker = await self.client.get_ticker_24h(symbol)
                                    if ticker and ticker.get('price'):
                                        exit_price = float(ticker['price'])
                                        logger.warning(f"{symbol} [状态同步] 使用当前价格作为平仓价格: {exit_price:.4f} USDT")
                                    else:
                                        logger.warning(
                                            f"{symbol} [状态同步] 无法获取当前价格（ticker={ticker}），"
                                            f"保留open状态等待下次同步"
                                        )
                                        continue
                                except KeyError as key_error:
                                    # KeyError 可能是访问 ticker['price'] 时出错
                                    logger.error(
                                        f"{symbol} [状态同步] ❌ 获取当前价格时KeyError: {key_error}, "
                                        f"ticker数据: {ticker if 'ticker' in locals() else 'N/A'}"
                                    )
                                    continue
                                except Exception as ticker_error:
                                    logger.warning(
                                        f"{symbol} [状态同步] 获取当前价格失败: "
                                        f"错误类型={type(ticker_error).__name__}, 错误消息={str(ticker_error)}，"
                                        f"将保留open状态等待下次同步"
                                    )
                                    continue
                            
                            # 计算盈亏（确保所有值都是float类型，避免Decimal类型问题）
                            try:
                                entry_price = float(trade.get('entry_price', 0))
                                quantity = float(trade.get('quantity', 0))
                                
                                if entry_price <= 0 or quantity <= 0:
                                    logger.error(
                                        f"{symbol} [状态同步] ❌ 交易记录数据无效: "
                                        f"入场价={entry_price}, 数量={quantity}, 跳过更新"
                                    )
                                    continue
                                
                                # 如果已经获取到精确的 Realized PnL，直接使用
                                if realized_pnl is not None:
                                    pnl = realized_pnl
                                    # 根据 Realized PnL 反推百分比 (PnL / 保证金) 或者 (PnL / 名义价值)?
                                    # 这里保持与价格差计算一致的逻辑：(PriceDiff / EntryPrice) * 100
                                    # 如果 exit_price 是计算出来的均价，这个公式依然成立
                                    if trade.get('side') == 'BUY':
                                        pnl_percent = ((exit_price - entry_price) / entry_price) * 100
                                    else:
                                        pnl_percent = ((entry_price - exit_price) / entry_price) * 100
                                else:
                                    # 降级方案：使用价格差计算
                                    if trade.get('side') == 'BUY':
                                        pnl = (exit_price - entry_price) * quantity
                                        pnl_percent = ((exit_price - entry_price) / entry_price) * 100
                                    else:  # SELL
                                        pnl = (entry_price - exit_price) * quantity
                                        pnl_percent = ((entry_price - exit_price) / entry_price) * 100
                                
                                logger.debug(
                                    f"{symbol} [状态同步] 盈亏计算: "
                                    f"入场价={entry_price:.4f}, 平仓价={exit_price:.4f}, "
                                    f"数量={quantity:.4f}, 方向={trade.get('side', 'N/A')}, "
                                    f"盈亏={pnl:.2f} USDT ({pnl_percent:.2f}%)"
                                )
                            except (ValueError, TypeError, KeyError) as calc_error:
                                logger.error(
                                    f"{symbol} [状态同步] ❌ 计算盈亏失败 (ID: {trade_id}): "
                                    f"错误类型={type(calc_error).__name__}, 错误消息={str(calc_error)}, "
                                    f"交易记录数据: entry_price={trade.get('entry_price', 'N/A')}, "
                                    f"quantity={trade.get('quantity', 'N/A')}, side={trade.get('side', 'N/A')}"
                                )
                                continue
                            
                            # 从历史订单中获取平仓订单号
                            exit_order_id = None
                            latest_close_order = None
                            if close_orders:
                                exit_order_id = close_orders[0].get('orderId')
                                latest_close_order = close_orders[0]
                                if exit_order_id:
                                    logger.info(f"{symbol} [状态同步] 找到平仓订单号: {exit_order_id}")
                            
                            # 使用 try-except 包裹，确保异常被正确处理
                            try:
                                # 计算持仓持续时间和策略类型
                                # exit_reason 细分：优先用价格匹配和特征判断，其次看币安订单类型
                                exit_reason = "sync"
                                exit_time_ts = None
                                is_reduce_only = False
                                
                                try:
                                    if latest_close_order and isinstance(latest_close_order, dict):
                                        otype = str(
                                            latest_close_order.get("type")
                                            or latest_close_order.get("origType")
                                            or ""
                                        ).upper()
                                        is_reduce_only = latest_close_order.get("reduceOnly", False)
                                        
                                        ms = latest_close_order.get("updateTime") or latest_close_order.get("time")
                                        try:
                                            if ms:
                                                exit_time_ts = int(int(ms) / 1000)
                                        except Exception:
                                            exit_time_ts = None
                                except Exception:
                                    pass

                                # ⚠️ 关键改进：优先使用价格匹配和特征判断（提高准确性）
                                # 这对于保护单触发的 MARKET 订单特别重要（币安的保护单触发后会生成 MARKET 订单，但 reduceOnly 可能不准确）
                                try:
                                    def _close_to(a: float, b: float, max_pct: float = 0.10) -> bool:  # 从5%放宽到10%，以应对极端滑点
                                        if a <= 0 or b <= 0:
                                            return False
                                        return abs((a - b) / b) <= max_pct

                                    ep = float(exit_price or 0)
                                    entry_price_val = float(trade.get("entry_price", 0) or 0)
                                    sl = trade.get("stop_loss_price")
                                    tp = trade.get("take_profit_price")
                                    tp1 = trade.get("take_profit_1")
                                    tp2 = trade.get("take_profit_2")
                                    
                                    # 计算持仓时间和亏损比例（用于特征判断）
                                    entry_time = trade.get("entry_time")
                                    duration_minutes = None
                                    if entry_time and exit_time_ts:
                                        try:
                                            duration_minutes = (exit_time_ts - int(entry_time)) / 60.0
                                        except Exception:
                                            pass
                                    
                                    # ⚠️ 关键修复：使用基于保证金的盈亏百分比判断盈利/亏损，而不是价格涨跌幅
                                    # 因为价格涨跌幅（如2.35%）和基于保证金的盈亏百分比（如18.82%）差异很大
                                    # 对于SELL单，价格从0.119跌到0.1162，价格涨跌幅只有2.35%，但基于保证金的盈亏百分比是18.82%
                                    pnl_percent_price = float(trade.get("pnl_percent", 0) or 0)  # 价格涨跌幅（数据库可能存储的是这个）
                                    
                                    # 重新计算基于保证金的盈亏百分比（更准确）
                                    leverage = float(trade.get("leverage", 8) or 8)
                                    entry_value = entry_price * quantity
                                    margin = entry_value / leverage if leverage > 0 else entry_value
                                    pnl_percent_margin = (pnl / margin * 100) if margin > 0 else 0
                                    
                                    # 使用基于保证金的盈亏百分比判断（与实时监控逻辑一致）
                                    pnl_percent_for_judge = pnl_percent_margin
                                    
                                    # ⚠️ 2026-01-27关键修复：优先检查盈亏情况，避免盈利单被错误标记为止损
                                    # 1. 优先检查盈亏情况（使用基于保证金的盈亏百分比）
                                    if pnl_percent_for_judge > 0:
                                        # 盈利单：优先检查止盈价格匹配
                                        if tp is not None and _close_to(ep, float(tp), max_pct=0.10):
                                            exit_reason = "take_profit"
                                        elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.10):
                                            exit_reason = "take_profit"
                                        elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.10):
                                            exit_reason = "take_profit"
                                        # 如果盈利但没有匹配止盈价，可能是移动止损或手动平仓
                                        elif exit_reason == "sync":
                                            # 检查是否有移动止损标记
                                            if is_reduce_only:
                                                exit_reason = "trailing_stop"  # 可能是移动止损
                                        else:
                                                exit_reason = "manual"  # 可能是手动平仓
                                    else:
                                        # 亏损单：检查止损价格匹配
                                        if sl is not None and entry_price_val > 0 and ep > 0:
                                            sl_val = float(sl)
                                            # 价格匹配：平仓价接近止损价
                                            if _close_to(ep, sl_val, max_pct=0.10):
                                                exit_reason = "stop_loss"
                                            # 方向匹配：BUY时平仓价 < 止损价，SELL时平仓价 > 止损价
                                            elif (trade.get("side") == "BUY" and ep < sl_val) or (trade.get("side") == "SELL" and ep > sl_val):
                                                # 如果价格在止损方向，且亏损比例较大，更可能是止损触发
                                                # ⚠️ 关键修复：使用基于保证金的盈亏百分比判断
                                                if pnl_percent_for_judge < -5.0:  # 亏损超过5%（基于保证金）
                                                    exit_reason = "stop_loss"
                                                    logger.info(f"{trade.get('symbol')} [同步] 价格方向匹配止损，且亏损{pnl_percent_for_judge:.2f}% of margin，标记为止损")
                                    
                                    # 2. 如果仍未确定，检查止盈价格匹配（作为备选）；仅盈利单可标为止盈
                                    if exit_reason == "sync" and ep > 0 and pnl_percent_for_judge > 0:
                                        if tp is not None and _close_to(ep, float(tp), max_pct=0.10):
                                            exit_reason = "take_profit"
                                        elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.10):
                                            exit_reason = "take_profit"
                                        elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.10):
                                            exit_reason = "take_profit"
                                    
                                    # 3. 特征判断：如果价格不匹配，但满足止损特征，也标记为止损
                                    if exit_reason == "sync" and entry_price_val > 0 and ep > 0:
                                        # 特征1：持仓时间短（< 30分钟）且亏损
                                        # ⚠️ 关键修复：使用基于保证金的盈亏百分比判断
                                        if duration_minutes and duration_minutes < 30 and pnl_percent_for_judge < -5.0:
                                            # 特征2：价格在止损方向
                                            if sl is not None:
                                                sl_val = float(sl)
                                                if (trade.get("side") == "BUY" and ep < sl_val) or (trade.get("side") == "SELL" and ep > sl_val):
                                                    exit_reason = "stop_loss"
                                                    logger.info(f"{trade.get('symbol')} [同步] 特征判断：持仓{duration_minutes:.1f}分钟，亏损{pnl_percent_for_judge:.2f}% of margin，价格在止损方向，标记为止损")
                                    
                                    # ⚠️ 2026-02-05 修复：防止亏损单被错误标记为止盈
                                    if pnl_percent_for_judge < 0 and exit_reason == "take_profit":
                                        logger.warning(f"{symbol} [状态同步] ⚠️ 亏损单({pnl_percent_for_judge:.2f}%)被标记为止盈，强制修正为manual/stop_loss")
                                        # 如果亏损较大，归为止损
                                        if pnl_percent_for_judge < -5.0:
                                            exit_reason = "stop_loss"
                                        else:
                                            exit_reason = "manual"

                                    # 4. 如果之前标记为 sync 且是 reduceOnly 订单，但价格不匹配止损/止盈，可能是其他自动平仓（如移动止损）
                                    if exit_reason == "sync" and is_reduce_only:
                                        # 检查是否是移动止损：如果价格接近入场价，可能是移动止损触发的
                                        if entry_price_val > 0 and _close_to(ep, entry_price_val, max_pct=0.01):
                                            exit_reason = "trailing_stop"
                                    
                                    # 5. 最后才看币安订单类型（作为兜底）；亏损单不标为止盈
                                    if exit_reason == "sync" and latest_close_order and isinstance(latest_close_order, dict):
                                        otype = str(
                                            latest_close_order.get("type")
                                            or latest_close_order.get("origType")
                                            or ""
                                        ).upper()
                                        
                                        if "TRAILING" in otype:
                                            exit_reason = "trailing_stop"
                                        elif "TAKE_PROFIT" in otype and pnl_percent_for_judge > 0:
                                            exit_reason = "take_profit"
                                        elif "TAKE_PROFIT" in otype and pnl_percent_for_judge < 0:
                                            exit_reason = "stop_loss" if pnl_percent_for_judge < -5.0 else "manual"
                                        elif "STOP" in otype:
                                            exit_reason = "stop_loss"
                                        elif otype in ("MARKET", "LIMIT"):
                                            # 只有在价格和特征都不匹配，且不是 reduceOnly 时，才标记为手动平仓
                                            if not is_reduce_only:
                                                # 再次检查：如果亏损很大，更可能是止损触发（币安API可能不准确）
                                                # ⚠️ 关键修复：使用基于保证金的盈亏百分比判断
                                                if pnl_percent_for_judge < -10.0:
                                                    exit_reason = "stop_loss"  # 大额亏损，更可能是止损
                                                    logger.warning(f"{trade.get('symbol')} [同步] 大额亏损{pnl_percent_for_judge:.2f}% of margin，即使reduceOnly=false也标记为止损")
                                                else:
                                                    exit_reason = "manual"
                                except Exception as e:
                                    logger.warning(f"判断平仓原因失败: {e}")
                                    pass

                                # 持仓持续时间（分钟）：优先用币安订单时间，否则用当前时间
                                entry_time = trade.get("entry_time")
                                duration_minutes = None
                                try:
                                    et = None
                                    if isinstance(entry_time, (int, float)):
                                        et = int(entry_time)
                                    elif isinstance(entry_time, str) and entry_time.strip():
                                        # 兼容旧格式：字符串时间戳/日期字符串
                                        s = entry_time.strip()
                                        if s.isdigit():
                                            et = int(s)
                                        else:
                                            from datetime import datetime
                                            et = int(datetime.fromisoformat(s).timestamp())

                                    xt = int(exit_time_ts) if exit_time_ts is not None else int(get_beijing_time().timestamp())
                                    if et is not None and xt >= et:
                                        duration_minutes = int((xt - et) / 60)
                                except Exception as e:
                                    logger.debug(f"计算持仓持续时间失败: {e}")
                                
                                strategy_type = 'trend_following'  # 默认策略类型
                                
                                # ⚠️ 关键修复：使用基于保证金的盈亏百分比更新数据库（与实时监控逻辑一致）
                                Trade.update_exit(
                                    trade_id=trade_id,
                                    exit_price=exit_price,
                                    exit_reason=exit_reason,
                                    pnl=pnl,
                                    pnl_percent=pnl_percent_margin,  # 使用基于保证金的盈亏百分比
                                    exit_order_id=exit_order_id,  # 保存币安平仓订单号
                                    strategy_type=strategy_type,
                                    duration_minutes=duration_minutes,
                                    exit_time_ts=exit_time_ts,
                                )
                            except Exception as update_error:
                                # update_exit 内部已经有异常处理，但如果仍然失败，记录错误但不中断同步流程
                                error_str = str(update_error)
                                if "Duplicate entry" in error_str and "exit_order_id" in error_str:
                                    logger.warning(
                                        f"{symbol} [状态同步] ⚠️ exit_order_id {exit_order_id} 唯一约束冲突，"
                                        f"update_exit 内部处理失败，尝试不更新 exit_order_id"
                                    )
                                    # 再次尝试，不更新 exit_order_id
                                    try:
                                        from database.connection import db
                                        # from database.models import get_beijing_time # 移除本地导入，避免 UnboundLocalError
                                        exit_time = int(exit_time_ts) if exit_time_ts is not None else get_beijing_time()
                                        db.execute_update(
                                            """UPDATE trades 
                                               SET exit_price = %s, exit_time = %s, 
                                                   exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed'
                                               WHERE id = %s""",
                                            (exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id)
                                        )
                                        logger.info(f"{symbol} [状态同步] ✓ 已更新（跳过 exit_order_id）")
                                    except Exception as retry_error:
                                        logger.error(f"{symbol} [状态同步] ❌ 重试更新也失败: {retry_error}")
                                        raise
                                else:
                                    # 其他错误，重新抛出
                                    raise
                            
                            logger.info(
                                f"{symbol} [状态同步] ✓ 已更新 (ID: {trade_id}, "
                                f"盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%)"
                            )
                            
                            # 清理本地记录
                            if symbol in self.active_positions:
                                await self._stop_position_monitoring(symbol)
                                del self.active_positions[symbol]
                                logger.debug(f"{symbol} [状态同步] 已清理本地持仓记录")
                        
                        except Exception as e:
                            # 详细记录错误信息，包括异常类型、错误消息和堆栈跟踪
                            import traceback
                            error_type = type(e).__name__
                            error_msg = str(e)
                            error_traceback = traceback.format_exc()
                            
                            logger.error(
                                f"{symbol} [状态同步] ❌ 更新失败 (ID: {trade_id}): "
                                f"错误类型={error_type}, 错误消息={error_msg}"
                            )
                            logger.debug(
                                f"{symbol} [状态同步] 错误详情:\n{error_traceback}"
                            )
                            
                            # 如果是数据库相关错误，记录更多信息
                            if "Duplicate entry" in error_msg or "1062" in error_msg:
                                logger.error(
                                    f"{symbol} [状态同步] 数据库唯一约束冲突，"
                                    f"可能原因: exit_order_id={exit_order_id} 已被其他交易记录使用"
                                )
                            elif "database" in error_msg.lower() or "connection" in error_msg.lower():
                                logger.error(
                                    f"{symbol} [状态同步] 数据库连接或执行错误，"
                                    f"请检查数据库连接状态"
                                )
            else:
                logger.info("✓ 持仓状态同步完成，数据库与币安状态一致")
            
            # 5. 检查币安有但数据库没有记录的持仓（可能是本策略开仓后未正确落库、或其它来源）
            missing_in_db = binance_symbols - db_open_symbols
            if missing_in_db:
                logger.info(
                    f"[账号{self.account_id}] 发现 {len(missing_in_db)} 个持仓在币安存在但数据库中没有 open 记录: "
                    f"{', '.join(missing_in_db)}"
                )
                sync_create_manual = config.TRADING_CONFIG.get("SYNC_CREATE_MANUAL_ENTRY_RECORD", False)
                sync_recover = config.TRADING_CONFIG.get("SYNC_RECOVER_MISSING_POSITIONS", True)
                sync_recover_only_has_sltp = config.TRADING_CONFIG.get("SYNC_RECOVER_ONLY_WHEN_HAS_SLTP", True)
                # 订单统一由自动下单入 DB：同步/持仓 sync 不创建新记录，仅 WS 与自动开仓写库
                only_auto_creates = config.TRADING_CONFIG.get("ONLY_AUTO_TRADE_CREATES_RECORDS", True)
                if only_auto_creates:
                    sync_recover = False
                    sync_create_manual = False
                    if missing_in_db:
                        logger.info(
                            f"[账号{self.account_id}] 因 ONLY_AUTO_TRADE_CREATES_RECORDS=True 已关闭补建，"
                            f"共 {len(missing_in_db)} 个仅币安持仓未写入 DB。"
                            " 若要让币安与 DB 一致，请设 ONLY_AUTO_TRADE_CREATES_RECORDS=False 且 SYNC_RECOVER_MISSING_POSITIONS=True，然后重启或等待下次同步。"
                        )

                if sync_recover:
                    system_order_prefix = (config.TRADING_CONFIG.get("SYSTEM_ORDER_ID_PREFIX") or "").strip()
                    if system_order_prefix:
                        logger.info(
                            f"  → 补建缺失持仓：一律写入 DB、自动挂止损止盈并纳入监控（含 clientOrderId 非 {system_order_prefix!r} 前缀的来历不明仓）"
                        )
                    else:
                        logger.info("  → 补建缺失持仓：一律写入 DB、自动挂止损止盈并纳入监控（含无 SL/TP 的仓位）")
                    for symbol in missing_in_db:
                        try:
                            binance_position = next((p for p in binance_positions if p.get("symbol") == symbol), None)
                            if not binance_position or float(binance_position.get("positionAmt", 0)) == 0:
                                continue
                            position_amt = float(binance_position["positionAmt"])
                            quantity = abs(position_amt)
                            side = "BUY" if position_amt > 0 else "SELL"
                            entry_price = float(binance_position.get("entryPrice", 0))
                            notional = quantity * entry_price
                            if notional < 1.0:
                                continue
                            # 补建时尽量拿到 entry_order_id：优先用 WS 缓存的 FILLED 消息（基于消息补建），否则按时间范围查开仓订单
                            entry_order_id = None
                            client_order_id_sync = None
                            try:
                                rc = getattr(self.client, "redis_cache", None)
                                if rc:
                                    cache_key = f"ats:filled_open_orders:{self.account_id}"
                                    cached = await rc.hget(cache_key, symbol)
                                    if cached and isinstance(cached, dict):
                                        cs = (cached.get("side") or "").strip().upper()
                                        ap = float(cached.get("avgPrice") or 0)
                                        eq = float(cached.get("executedQty") or 0)
                                        if (cs == side and ap > 0
                                            and abs(ap - entry_price) / max(entry_price, 1e-9) < 0.01
                                            and abs(eq - quantity) < 1e-6):
                                            entry_order_id = cached.get("orderId")
                                            client_order_id_sync = (cached.get("clientOrderId") or "").strip() or None
                                            if entry_order_id:
                                                logger.debug(f"  {symbol} 补建从 WS 缓存 FILLED 取得 orderId={entry_order_id}, clientOrderId={client_order_id_sync!r}")
                            except Exception as e:
                                logger.debug(f"  {symbol} 补建读 WS 缓存失败: {e}")
                            if system_order_prefix and entry_order_id is None:
                                try:
                                    end_ms = int(time.time() * 1000)
                                    start_ms = end_ms - (24 * 3600 * 1000)
                                    orders = await self.client.client.futures_get_all_orders(
                                        symbol=symbol, startTime=start_ms, endTime=end_ms, recvWindow=20000
                                    )
                                    if isinstance(orders, list):
                                        open_orders = [
                                            o for o in orders
                                            if isinstance(o, dict) and o.get("reduceOnly") is False
                                            and str(o.get("side", "")).upper() == side and o.get("status") == "FILLED"
                                        ]
                                        our_orders = [o for o in open_orders if (o.get("clientOrderId") or "").startswith(system_order_prefix)]
                                        if our_orders:
                                            our_orders.sort(key=lambda x: int(x.get("updateTime", 0)), reverse=True)
                                            best = None
                                            for o in our_orders:
                                                ap = float(o.get("avgPrice") or 0)
                                                eq = float(o.get("executedQty") or o.get("origQty") or 0)
                                                if ap > 0 and abs(ap - entry_price) / max(entry_price, 1e-9) < 0.01 and abs(eq - quantity) < 1e-6:
                                                    best = o
                                                    break
                                            if best is None:
                                                best = our_orders[0]
                                            entry_order_id = best.get("orderId")
                                            client_order_id_sync = (best.get("clientOrderId") or "").strip() or None
                                            logger.debug(f"  {symbol} 补建从 get_all_orders 取得 orderId={entry_order_id}, clientOrderId={client_order_id_sync!r}")
                                except Exception as e:
                                    logger.debug(f"  {symbol} 补建 get_all_orders 取开仓订单号失败: {e}")
                            if entry_order_id is None:
                                try:
                                    recent = await self.client.get_recent_trades(symbol, limit=100)
                                    if not recent:
                                        await asyncio.sleep(2)
                                        recent = await self.client.get_recent_trades(symbol, limit=100)
                                    if recent:
                                        same_side = [t for t in recent if str(t.get("side", "")).upper() == side]
                                        same_side.sort(key=lambda x: int(x.get("time", 0)), reverse=True)
                                        if system_order_prefix and same_side:
                                            for t in same_side[:5]:
                                                oid = t.get("orderId")
                                                if not oid:
                                                    continue
                                                try:
                                                    info = await self.client.client.futures_get_order(symbol=symbol, orderId=int(oid), recvWindow=20000)
                                                    cid = (info or {}).get("clientOrderId") or ""
                                                    if cid.startswith(system_order_prefix):
                                                        entry_order_id = oid
                                                        client_order_id_sync = cid.strip() or None
                                                        break
                                                except Exception:
                                                    continue
                                        if entry_order_id is None and same_side:
                                            entry_order_id = same_side[0].get("orderId")
                                except Exception:
                                    pass
                            if entry_order_id and client_order_id_sync is None:
                                try:
                                    order_info = await self.client.client.futures_get_order(symbol=symbol, orderId=int(entry_order_id), recvWindow=20000)
                                    cid = (order_info or {}).get("clientOrderId") or ""
                                    client_order_id_sync = cid.strip() or None
                                except Exception:
                                    pass
                            # 标记是否「来历不明」：用于 DB entry_reason 与后续统计分析（sync_recovered_unknown_origin）
                            sync_unknown_origin = False
                            is_clearly_manual = False
                            if system_order_prefix and entry_order_id and client_order_id_sync and not client_order_id_sync.startswith(system_order_prefix):
                                is_clearly_manual = True
                                logger.debug(f"  {symbol} 开仓订单 clientOrderId={client_order_id_sync!r} 非系统前缀，将按来历不明仓补建并挂 SL/TP")
                            elif system_order_prefix and entry_order_id and not client_order_id_sync:
                                try:
                                    order_info = await self.client.client.futures_get_order(symbol=symbol, orderId=int(entry_order_id), recvWindow=20000)
                                    cid = (order_info or {}).get("clientOrderId") or ""
                                    if cid and not cid.startswith(system_order_prefix):
                                        is_clearly_manual = True
                                except Exception:
                                    pass
                            if system_order_prefix:
                                if is_clearly_manual and config.TRADING_CONFIG.get("SYNC_MONITOR_BINANCE_POSITIONS_WITH_SLTP", True):
                                    if await self._symbol_has_sltp_orders(symbol):
                                        is_clearly_manual = False
                                        logger.info(f"  → {symbol} 开仓订单 clientOrderId 非系统前缀，但存在止损/止盈单，按系统单补建并监控")
                                if is_clearly_manual:
                                    sync_unknown_origin = True
                                    logger.info(f"  → {symbol} 来历不明（开仓订单非系统前缀），将补建记录、自动挂止损止盈并纳入监控")
                            else:
                                if sync_recover_only_has_sltp and not (await self._symbol_has_sltp_orders(symbol)):
                                    sync_unknown_origin = True
                                    logger.info(f"  → {symbol} 无止损/止盈单，将补建记录、自动挂止损止盈并纳入监控")
                            # 不再因 is_clearly_manual 或 无 SL/TP 跳过，一律补建 + 挂 SL/TP + 监控
                            entry_reason_sync = "sync_recovered_unknown_origin" if sync_unknown_origin else "sync_recovered"
                            # 同一开仓订单在 DB 中已有记录（无论 open 或 closed）则跳过，避免重复建单（如 PYTHUSDT 同单多笔）
                            if entry_order_id and hasattr(Trade, "get_by_entry_order_id"):
                                try:
                                    existing = Trade.get_by_entry_order_id(entry_order_id)
                                    if existing and existing.get("symbol") == symbol:
                                        logger.debug(f"  {symbol} 开仓订单 {entry_order_id} 已有记录 (id={existing.get('id')}, status={existing.get('status')})，跳过补建")
                                        continue
                                except Exception:
                                    pass
                            # 从币安取真实开仓时间，避免补建后开仓时间全是“当前时间”
                            entry_time_ts = None
                            if entry_order_id:
                                try:
                                    oi = await self.client.client.futures_get_order(symbol=symbol, orderId=int(entry_order_id), recvWindow=20000)
                                    if oi and oi.get("time"):
                                        entry_time_ts = int(oi["time"]) // 1000
                                except Exception:
                                    pass
                            if entry_time_ts is None:
                                try:
                                    recent = await self.client.get_recent_trades(symbol, limit=100)
                                    same_side = [t for t in (recent or []) if str(t.get("side", "")).upper() == side]
                                    if same_side:
                                        same_side.sort(key=lambda x: int(x.get("time", 0)))
                                        entry_time_ts = int(same_side[0].get("time", 0)) // 1000
                                except Exception:
                                    pass
                            try:
                                trade_id = Trade.create(
                                    symbol=symbol,
                                    side=side,
                                    quantity=quantity,
                                    entry_price=entry_price,
                                    leverage=binance_position.get("leverage", 10),
                                    entry_reason=entry_reason_sync,
                                    entry_order_id=entry_order_id,
                                    client_order_id=client_order_id_sync,
                                    notional_usdt=notional,
                                    margin_usdt=(notional / float(binance_position.get("leverage", 10) or 10)) if float(binance_position.get("leverage", 10) or 0) > 0 else None,
                                    account_id=self.account_id,
                                    entry_time=entry_time_ts,
                                )
                                logger.info(f"  ✓ [DB] {symbol} [状态同步] 补建记录已落库 ID={trade_id} orderId={entry_order_id or '-'} entry_reason={entry_reason_sync}")
                            except Exception as create_ex:
                                logger.error(
                                    f"  [DB] {symbol} [状态同步] 补建写入 DB 失败，该持仓将无法纳入监控: "
                                    f"orderId={entry_order_id} error_type={type(create_ex).__name__} error={create_ex}"
                                )
                                _log_trade_db_failure(
                                    symbol=symbol, entry_order_id=entry_order_id, side=side, quantity=quantity,
                                    entry_price=entry_price, account_id=self.account_id,
                                    reason="sync_recover_create_failed",
                                    error_type=type(create_ex).__name__, error_message=str(create_ex)
                                )
                                raise
                            ticker = await self.client.get_ticker_24h(symbol)
                            current_price = ticker["price"] if ticker else entry_price
                            lev = float(binance_position.get("leverage", 10))
                            # ---------- 补建 SL/TP 执行顺序（必须严格保证）----------
                            # 1) 先读交易所当前条件单的止损/止盈，再决定用哪套价格，最后才调用 _ensure_exchange_sltp_orders。
                            #    否则会先用 risk_manager 算出初始止损，挂到交易所，把已有保本/移动止损覆盖掉。
                            # 2) 若交易所止损已达保本或更优 → position_info 采用交易所止损并设 breakevenStopSet=True；
                            #    否则采用 risk_manager 初始止损。止盈：交易所有则用交易所，否则用 risk_manager。
                            # 3) _ensure_exchange_sltp_orders 用当前 position_info 的 SL/TP 挂单，因此传入的必须是上面决定好的价格。
                            sl_from_ex, tp_from_ex = await self._get_sltp_from_exchange(symbol, side)
                            breakeven = self._breakeven_stop_price(entry_price, side, None)
                            use_exchange_sl = False
                            if sl_from_ex is not None:
                                if side == "BUY" and sl_from_ex >= breakeven:
                                    use_exchange_sl = True
                                elif side == "SELL" and sl_from_ex <= breakeven:
                                    use_exchange_sl = True
                            stop_loss_pct = config.TRADING_CONFIG.get("STOP_LOSS_PERCENT", 0.08)
                            if stop_loss_pct is not None and stop_loss_pct > 1:
                                stop_loss_pct = stop_loss_pct / 100.0
                            take_profit_pct = config.TRADING_CONFIG.get("TAKE_PROFIT_PERCENT", 0.15)
                            if take_profit_pct is not None and take_profit_pct > 1:
                                take_profit_pct = take_profit_pct / 100.0
                            if not take_profit_pct:
                                take_profit_pct = (stop_loss_pct or 0.08) * 2.0
                            if use_exchange_sl:
                                stop_loss_price = sl_from_ex
                                initial_stop_loss = self.risk_manager.get_stop_loss_price(entry_price, side, quantity, lev, stop_loss_pct=stop_loss_pct)
                                logger.info(f"  {symbol} [补建] 使用交易所已有止损（保本/移动）sl={stop_loss_price}，不覆盖为初始止损 {initial_stop_loss}")
                            else:
                                stop_loss_price = self.risk_manager.get_stop_loss_price(entry_price, side, quantity, lev, stop_loss_pct=stop_loss_pct)
                                initial_stop_loss = stop_loss_price
                            if tp_from_ex is not None:
                                take_profit_price = tp_from_ex
                            else:
                                take_profit_price = self.risk_manager.get_take_profit_price(entry_price, side, quantity, lev, take_profit_pct=take_profit_pct)
                            position_info = {
                                "symbol": symbol, "side": side, "quantity": quantity, "entryPrice": entry_price,
                                "changePercent": 0, "orderId": entry_order_id, "tradeId": trade_id,
                                "stopLoss": stop_loss_price, "takeProfit": take_profit_price, "initialStopLoss": initial_stop_loss,
                                "leverage": lev, "entryReason": entry_reason_sync, "atr": None, "maxProfit": 0.0, "trailingStopActivated": False,
                                "breakevenStopSet": use_exchange_sl,
                                "entryTime": entry_time_ts if entry_time_ts is not None else get_beijing_time(),
                            }
                            self.active_positions[symbol] = position_info
                            # 4) 最后再同步到交易所：用已决定好的 position_info（可能是交易所保本或 risk_manager 初始）挂单
                            try:
                                await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
                            except Exception as sltp_e:
                                logger.warning(f"  {symbol} [补建] 补挂交易所止损止盈失败（不影响监控）: {sltp_e}")
                            if self._monitoring_enabled:
                                await self._start_position_monitoring(symbol)
                        except Exception as e:
                            logger.warning(f"  ✗ {symbol} [状态同步] 补建失败: {e}")
                elif not sync_create_manual:
                    logger.info(
                        f"[账号{self.account_id}]   → 已跳过自动创建交易记录（SYNC_RECOVER_MISSING_POSITIONS 未开启或 ONLY_AUTO_TRADE_CREATES_RECORDS=True）。"
                        " 若要让币安持仓与 DB 一致：设 ONLY_AUTO_TRADE_CREATES_RECORDS=False、SYNC_RECOVER_MISSING_POSITIONS=True，重启或等待下次同步。"
                    )
                elif sync_create_manual:
                    # 为手动开仓的持仓创建数据库记录并启动监控（仅当显式开启且未走上面的「补建系统单」时）
                    for symbol in missing_in_db:
                        try:
                            # 获取币安持仓详情
                            binance_position = next(
                                (p for p in binance_positions if p['symbol'] == symbol),
                                None
                            )
                            if not binance_position:
                                continue
                            
                            position_amt = binance_position['positionAmt']
                            entry_price = binance_position['entryPrice']
                            quantity = abs(position_amt)
                            side = 'BUY' if position_amt > 0 else 'SELL'
                            
                            notional = (float(entry_price) * float(quantity)) if entry_price and quantity else 0
                            if notional < 1.0:
                                logger.debug(f"{symbol} [状态同步] 跳过灰尘持仓 (名义 {notional:.4f} USDT < 1)，不创建记录")
                                continue
                            logger.info(
                                f"{symbol} [状态同步] 检测到手动开仓，创建数据库记录... "
                                f"({side} {quantity:.4f} @ {entry_price:.4f})"
                            )
                            # 尽量从币安成交取 entry_order_id 与真实开仓时间（limit=100、空时重试一次）
                            entry_order_id = None
                            entry_time_ts = None
                            try:
                                recent = await self.client.get_recent_trades(symbol, limit=100)
                                if not recent:
                                    await asyncio.sleep(2)
                                    recent = await self.client.get_recent_trades(symbol, limit=100)
                                if recent:
                                    same_side = [t for t in recent if str(t.get('side', '')).upper() == side]
                                    if same_side:
                                        same_side.sort(key=lambda x: int(x.get('time', 0)), reverse=True)
                                        entry_order_id = same_side[0].get('orderId')
                                        # 开仓时间取同方向最早一笔成交（oldest），避免显示为“当前时间”
                                        same_side_asc = sorted(same_side, key=lambda x: int(x.get('time', 0)))
                                        entry_time_ts = int(same_side_asc[0].get('time', 0)) // 1000
                            except Exception:
                                pass
                            if entry_time_ts is None and entry_order_id:
                                try:
                                    oi = await self.client.client.futures_get_order(symbol=symbol, orderId=int(entry_order_id), recvWindow=20000)
                                    if oi and oi.get('time'):
                                        entry_time_ts = int(oi['time']) // 1000
                                except Exception:
                                    pass
                            # 创建数据库记录（显式传入 account_id、真实开仓时间）
                            try:
                                trade_id = Trade.create(
                                    symbol=symbol,
                                    side=side,
                                    quantity=quantity,
                                    entry_price=entry_price,
                                    leverage=binance_position.get('leverage', 10),
                                    entry_reason='manual_entry',  # 标记为手动开仓
                                    entry_order_id=entry_order_id,
                                    notional_usdt=notional,
                                    margin_usdt=(notional / float(binance_position.get('leverage', 10) or 10)) if float(binance_position.get('leverage', 10) or 0) > 0 else None,
                                    account_id=self.account_id,
                                    entry_time=entry_time_ts,
                                )
                                logger.info(f"[DB] {symbol} [状态同步] 手动开仓记录已落库 ID={trade_id} orderId={entry_order_id or '-'}")
                            except Exception as create_ex:
                                logger.error(
                                    f"[DB] {symbol} [状态同步] 手动开仓写入 DB 失败: "
                                    f"orderId={entry_order_id} error_type={type(create_ex).__name__} error={create_ex}"
                                )
                                _log_trade_db_failure(
                                    symbol=symbol, entry_order_id=entry_order_id, side=side, quantity=quantity,
                                    entry_price=entry_price, account_id=self.account_id,
                                    reason="manual_entry_create_failed",
                                    error_type=type(create_ex).__name__, error_message=str(create_ex)
                                )
                                continue
                            
                            # 创建本地持仓记录（用于监控）
                            ticker = await self.client.get_ticker_24h(symbol)
                            current_price = ticker['price'] if ticker else entry_price
                            # ---------- 手动开仓补建 SL/TP 顺序：先读交易所 → 决定 SL/TP → 再写 position_info ----------
                            sl_from_ex, tp_from_ex = await self._get_sltp_from_exchange(symbol, side)
                            breakeven = self._breakeven_stop_price(entry_price, side, None)
                            use_exchange_sl = False
                            if sl_from_ex is not None:
                                if side == 'BUY' and sl_from_ex >= breakeven:
                                    use_exchange_sl = True
                                elif side == 'SELL' and sl_from_ex <= breakeven:
                                    use_exchange_sl = True
                            
                            # 计算止损止盈（缺失时用 risk_manager 基于保证金）
                            leverage = binance_position.get('leverage', 10)
                            stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.08)
                            if stop_loss_pct_margin is not None and stop_loss_pct_margin > 1:
                                stop_loss_pct_margin = stop_loss_pct_margin / 100.0
                            take_profit_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.15)
                            if take_profit_pct_margin is not None and take_profit_pct_margin > 1:
                                take_profit_pct_margin = take_profit_pct_margin / 100.0
                            if take_profit_pct_margin is None or take_profit_pct_margin == 0:
                                take_profit_pct_margin = stop_loss_pct_margin * 2.0
                            
                            if use_exchange_sl:
                                stop_loss_price = sl_from_ex
                                initial_stop_loss = self.risk_manager.get_stop_loss_price(
                                    entry_price, side, quantity, leverage,
                                    stop_loss_pct=stop_loss_pct_margin
                                )
                                logger.info(f"  {symbol} [补建-手动] 使用交易所已有止损（保本/移动）sl={stop_loss_price}，不覆盖为初始止损 {initial_stop_loss}")
                            else:
                                stop_loss_price = self.risk_manager.get_stop_loss_price(
                                    entry_price, side, quantity, leverage,
                                    stop_loss_pct=stop_loss_pct_margin
                                )
                                initial_stop_loss = stop_loss_price
                            if tp_from_ex is not None:
                                take_profit_price = tp_from_ex
                            else:
                                take_profit_price = self.risk_manager.get_take_profit_price(
                                    entry_price, side, quantity, leverage,
                                    take_profit_pct=take_profit_pct_margin
                                )
                            
                            position_info = {
                                'symbol': symbol,
                                'side': side,
                                'quantity': quantity,
                                'entryPrice': entry_price,
                                'changePercent': 0,
                                'orderId': entry_order_id,
                                'tradeId': trade_id,
                                'stopLoss': stop_loss_price,
                                'takeProfit': take_profit_price,
                                'initialStopLoss': initial_stop_loss,
                                'leverage': leverage,
                                'entryReason': 'manual_entry',
                                'entryTime': entry_time_ts if entry_time_ts is not None else get_beijing_time(),
                                'atr': None,
                                'maxProfit': 0.0,
                                'trailingStopActivated': False,
                                'breakevenStopSet': use_exchange_sl
                            }
                            
                            self.active_positions[symbol] = position_info
                            
                            # 启动WebSocket监控
                            if self._monitoring_enabled:
                                await self._start_position_monitoring(symbol)
                                logger.info(f"[账号{self.account_id}] {symbol} [状态同步] ✓ 已启动实时监控")
                            
                            logger.info(f"{symbol} [状态同步] ✓ 手动开仓同步完成")
                        
                        except Exception as e:
                            logger.error(f"{symbol} [状态同步] ❌ 处理手动开仓失败: {e}")
                            import traceback
                            logger.error(f"   错误详情:\n{traceback.format_exc()}")
            
            # 6. 同步挂单信息 (STOP_MARKET / TAKE_PROFIT_MARKET)
            if self.active_positions:
                logger.debug("正在同步持仓挂单信息...")
                try:
                    tasks = []
                    symbols = list(self.active_positions.keys())
                    for symbol in symbols:
                        tasks.append(self.client.get_open_orders(symbol))
                    
                    results = await asyncio.gather(*tasks, return_exceptions=True)
                    
                    for symbol, orders in zip(symbols, results):
                        if isinstance(orders, list):
                            # Filter for relevant orders (SL/TP)
                            conditional_orders = []
                            for o in orders:
                                o_type = o.get('type')
                                # 关注止盈止损单
                                if o_type in ['STOP_MARKET', 'TAKE_PROFIT_MARKET', 'STOP', 'TAKE_PROFIT']:
                                    conditional_orders.append({
                                        'orderId': o.get('orderId'),
                                        'type': o_type,
                                        'side': o.get('side'),
                                        'stopPrice': float(o.get('stopPrice', 0)),
                                        'price': float(o.get('price', 0)),
                                        'origType': o.get('origType'),
                                        'reduceOnly': o.get('reduceOnly'),
                                        'status': o.get('status')
                                    })
                            
                            if symbol in self.active_positions:
                                self.active_positions[symbol]['openOrders'] = conditional_orders
                                logger.debug(f"{symbol} 同步挂单: {len(conditional_orders)} 个")
                        else:
                            logger.warning(f"{symbol} 获取挂单失败: {orders}")
                except Exception as e:
                    logger.error(f"同步挂单信息失败: {e}")
            
            logger.info("持仓状态同步完成")
            
        except Exception as e:
            logger.error(f"同步持仓状态失败: {e}")
            import traceback
            logger.error(f"错误详情:\n{traceback.format_exc()}")
    
    async def start_all_position_monitoring(self):
        """
        启动所有持仓的WebSocket实时监控
        """
        if not self._monitoring_enabled:
            logger.info("实时监控已禁用，跳过启动")
            return
        
        # WebSocket 现在直接使用 aiohttp，不需要检查 socket_manager
        if not self.client:
            logger.warning("客户端未初始化，无法启动实时监控")
            return
        
        # 获取当前所有持仓（与 sync 一致：仅本系统关心的持仓会进 active_positions）
        # 多账号时必须用 account_id 读缓存；若缓存返回数量少于本地记录则强制 REST 一次，避免误判「持仓已不存在」
        positions = await self._get_open_positions()
        binance_symbols = {p['symbol'] for p in positions}
        active_symbols = set(self.active_positions.keys())
        if active_symbols and len(binance_symbols) < len(active_symbols):
            try:
                rest_positions = await self.client.get_open_positions()
                if rest_positions and len(rest_positions) > len(positions):
                    positions = rest_positions
                    binance_symbols = {p['symbol'] for p in positions}
                    logger.info(f"[账号{self.account_id}] 缓存持仓数少于本地记录，已用 REST 拉取完整持仓: {len(binance_symbols)} 个")
            except Exception as e:
                logger.debug(f"[账号{self.account_id}] REST 拉取完整持仓失败: {e}")
        sync_create_manual = config.TRADING_CONFIG.get("SYNC_CREATE_MANUAL_ENTRY_RECORD", False)
        
        logger.info(f"[账号{self.account_id}] 币安持仓: {len(binance_symbols)} 个 ({', '.join(binance_symbols) if binance_symbols else '无'})")
        logger.info(f"[账号{self.account_id}] 本地持仓记录: {len(active_symbols)} 个 ({', '.join(active_symbols) if active_symbols else '无'})")
        
        # 仅为本系统已有记录的持仓启动监控；若未开启「同步创建手动开仓记录」，则不为「仅币安有仓」创建临时记录或监控
        # 例外：SYNC_MONITOR_BINANCE_POSITIONS_WITH_SLTP=True 时，对「仅币安有仓且存在止损/止盈单」的视为可监管（多为系统单），补建并监控
        only_binance = binance_symbols - active_symbols
        monitor_binance_with_sltp = config.TRADING_CONFIG.get("SYNC_MONITOR_BINANCE_POSITIONS_WITH_SLTP", True)
        if only_binance and not sync_create_manual and not monitor_binance_with_sltp:
            logger.info(f"[账号{self.account_id}] 跳过 {len(only_binance)} 个仅币安持仓的监控（SYNC_CREATE_MANUAL_ENTRY_RECORD=False 且 SYNC_MONITOR_BINANCE_POSITIONS_WITH_SLTP=False）: {', '.join(only_binance)}")
        
        for position in positions:
            symbol = position['symbol']
            if symbol not in self._monitor_tasks:
                # 若不在 active_positions：开启「同步创建手动开仓」则全部接入；或开启「监控仅币安有仓」时也接入（有 SL/TP 的视为系统单，无 SL/TP 的补挂并监控，避免裸仓）
                if symbol not in self.active_positions:
                    has_sltp = await self._symbol_has_sltp_orders(symbol) if monitor_binance_with_sltp else False
                    should_create = sync_create_manual or monitor_binance_with_sltp
                    if not should_create:
                        continue
                    if sync_create_manual:
                        logger.warning(f"[账号{self.account_id}] {symbol} 在币安有持仓但不在本地记录中，可能是手动开仓，尝试创建记录...")
                    elif has_sltp:
                        logger.info(f"[账号{self.account_id}] {symbol} 仅币安有仓且存在止损/止盈单，按系统单接入监控并补建记录")
                    else:
                        logger.info(f"[账号{self.account_id}] {symbol} 仅币安有仓且无止损/止盈单，接入监控并补挂 SL/TP（避免裸仓）")
                    try:
                        entry_price = position.get('entryPrice', 0)
                        position_amt = position['positionAmt']
                        quantity = abs(position_amt)
                        side = 'BUY' if position_amt > 0 else 'SELL'
                        
                        # 创建临时记录用于监控
                        ticker = await self.client.get_ticker_24h(symbol)
                        current_price = ticker['price'] if ticker else entry_price
                        
                        # 计算止损止盈（基于保证金）
                        try:
                            leverage = float(position.get('leverage', 10))
                        except (ValueError, TypeError):
                            leverage = 10.0
                            
                        # 优先从币安读取当前止损/止盈，避免用“初始止损”覆盖已有保本/移动止损
                        sl_from_ex, tp_from_ex = await self._get_sltp_from_exchange(symbol, side)
                        if sl_from_ex is not None or tp_from_ex is not None:
                            stop_loss_price = float(sl_from_ex) if sl_from_ex is not None else None
                            take_profit_price = float(tp_from_ex) if tp_from_ex is not None else None
                        else:
                            stop_loss_price = None
                            take_profit_price = None
                        # 若当前已明显盈利，不用交易所初始止损，直接算锁利止损（避免重启后把 226.97 这类初始止损再挂回去）
                        entry_price_f = float(entry_price)
                        current_price_f = float(current_price or entry_price_f)
                        margin = (entry_price_f * quantity) / leverage if leverage else (entry_price_f * quantity)
                        if side == 'BUY':
                            pnl_amount = (current_price_f - entry_price_f) * quantity
                        else:
                            pnl_amount = (entry_price_f - current_price_f) * quantity
                        pnl_percent_margin = (pnl_amount / margin * 100) if margin > 0 else 0
                        trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.10)
                        if trailing_activation and trailing_activation <= 1:
                            trailing_activation = trailing_activation * 100
                        if pnl_percent_margin >= (trailing_activation or 10):
                            protect_amount = max(margin * float(config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.02) or 0.02), self._min_protect_amount_for_fees(margin, leverage))
                            if side == 'BUY':
                                stop_loss_price = entry_price_f + (pnl_amount - protect_amount) / quantity
                                stop_loss_price = max(stop_loss_price, self._breakeven_stop_price(entry_price_f, 'BUY'))
                            else:
                                stop_loss_price = entry_price_f - protect_amount / quantity
                                stop_loss_price = max(stop_loss_price, self._breakeven_stop_price(entry_price_f, 'SELL'))
                            logger.info(f"[账号{self.account_id}] {symbol} 接入监控时已盈利{pnl_percent_margin:.1f}%，使用锁利止损 {stop_loss_price:.4f} 替代交易所原止损")
                            if take_profit_price is None:
                                tp_pct = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.15) or 0.15
                                if tp_pct > 1:
                                    tp_pct = tp_pct / 100.0
                                take_profit_price = self.risk_manager.get_take_profit_price(entry_price, side, quantity, leverage, take_profit_pct=tp_pct)
                        if stop_loss_price is None or take_profit_price is None:
                            stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.08)
                            if stop_loss_pct_margin is not None and stop_loss_pct_margin > 1:
                                stop_loss_pct_margin = stop_loss_pct_margin / 100.0
                            take_profit_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.15)
                            if take_profit_pct_margin is not None and take_profit_pct_margin > 1:
                                take_profit_pct_margin = take_profit_pct_margin / 100.0
                            if take_profit_pct_margin is None or take_profit_pct_margin == 0:
                                take_profit_pct_margin = (stop_loss_pct_margin or 0.05) * 2.0
                            if stop_loss_price is None:
                                stop_loss_price = self.risk_manager.get_stop_loss_price(
                                    entry_price, side, quantity, leverage,
                                    stop_loss_pct=stop_loss_pct_margin or 0.05
                                )
                            if take_profit_price is None:
                                take_profit_price = self.risk_manager.get_take_profit_price(
                                    entry_price, side, quantity, leverage,
                                    take_profit_pct=take_profit_pct_margin
                                )
                        
                        entry_reason = 'manual_entry_temp' if sync_create_manual else 'sync_recovered'
                        position_info = {
                            'symbol': symbol,
                            'side': side,
                            'quantity': quantity,
                            'entryPrice': entry_price,
                            'changePercent': 0,
                            'orderId': None,
                            'tradeId': None,
                            'stopLoss': stop_loss_price,
                            'takeProfit': take_profit_price,
                            'initialStopLoss': stop_loss_price,
                            'leverage': leverage,
                            'entryReason': entry_reason,
                            'atr': None,
                            'maxProfit': 0.0,
                            'trailingStopActivated': False,
                            'breakevenStopSet': False,
                            'profitLockStage': 0,
                        }
                        # 订单统一由自动下单入 DB，此处仅做内存监控不创建 DB 记录
                        if not sync_create_manual and DB_AVAILABLE and Trade and not config.TRADING_CONFIG.get("ONLY_AUTO_TRADE_CREATES_RECORDS", True):
                            try:
                                notional = float(entry_price) * quantity
                                trade_id = Trade.create(
                                    symbol=symbol,
                                    side=side,
                                    quantity=quantity,
                                    entry_price=entry_price,
                                    leverage=leverage,
                                    entry_reason="sync_recovered",
                                    entry_order_id=None,
                                    notional_usdt=notional,
                                    margin_usdt=(notional / leverage) if leverage else None,
                                    account_id=self.account_id,
                                    stop_loss_price=stop_loss_price,
                                    take_profit_price=take_profit_price,
                                )
                                position_info['tradeId'] = trade_id
                            except Exception as db_e:
                                logger.debug(f"{symbol} 补建 DB 记录失败（不影响监控）: {db_e}")
                        self.active_positions[symbol] = position_info
                        logger.info(f"[账号{self.account_id}] {symbol} 已创建持仓记录用于监控" + (" (已写入 DB)" if position_info.get("tradeId") else ""))
                        # 也为“现有持仓”补挂交易所保护单（重启/掉线更安全）
                        try:
                            mp = None
                            try:
                                mp = float(position.get("markPrice", 0) or 0) or None
                            except Exception:
                                mp = None
                            await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=mp or current_price)
                        except Exception as e:
                            logger.warning(f"{symbol} 补挂币安止盈止损失败（不影响监控）: {e}")
                        # 重启后立即按当前价做一次保本/移动止损检查并同步，不依赖首条 WS 推送
                        try:
                            _px = float(position.get("markPrice", 0) or 0) or float(current_price or entry_price)
                            await self._check_single_position(symbol, _px)
                        except Exception as e:
                            logger.debug(f"{symbol} 启动时保本/移动止损检查失败: {e}")
                    except Exception as e:
                        err_msg = str(e) or repr(e) or type(e).__name__
                        logger.error(
                            f"[账号{self.account_id}] {symbol} 创建临时持仓记录失败: {err_msg}",
                            exc_info=True,
                        )
                else:
                    # 已在 active_positions 的持仓：启动前统一补挂/修正交易所 SL/TP（识别缺止盈、止损过远等异常并替换）
                    position_info = self.active_positions.get(symbol)
                    if position_info and position_info.get("stopLoss") and position_info.get("takeProfit"):
                        try:
                            mp = None
                            try:
                                mp = float(position.get("markPrice", 0) or 0) or None
                            except Exception:
                                mp = None
                            await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=mp)
                        except Exception as e:
                            logger.warning(f"{symbol} 补挂/修正交易所止损止盈失败（不影响监控）: {e}")
                
                await self._start_position_monitoring(symbol)
        
        logger.info(f"[账号{self.account_id}] 已启动 {len(self._monitor_tasks)} 个持仓的实时监控")
    
    async def stop_all_position_monitoring(self):
        """
        停止所有持仓的WebSocket监控
        """
        symbols = list(self._monitor_tasks.keys())
        for symbol in symbols:
            await self._stop_position_monitoring(symbol)
        logger.info(f"已停止所有持仓监控 ({len(symbols)} 个)")
    
    async def _start_position_monitoring(self, symbol: str):
        """
        启动单个持仓的WebSocket价格监控
        
        Args:
            symbol: 交易对
        """
        if symbol in self._monitor_tasks:
            logger.debug(f"{symbol} 监控任务已存在，跳过")
            return
        
        # WebSocket 现在直接使用 aiohttp，不需要检查 socket_manager
        if not self.client:
            logger.warning(f"{symbol} 客户端未初始化，无法启动监控")
            return
        
        try:
            task = asyncio.create_task(self._monitor_position_price(symbol))
            self._monitor_tasks[symbol] = task
            logger.info(f"[账号{self.account_id}] ✓ 启动 {symbol} WebSocket实时价格监控")
        except Exception as e:
            logger.error(f"启动 {symbol} 监控失败: {e}")
    
    async def _stop_position_monitoring(self, symbol: str):
        """
        停止单个持仓的WebSocket监控
        
        Args:
            symbol: 交易对
        """
        # 幂等：可能会被多处/并发调用，先 pop 再处理，避免 KeyError
        task = self._monitor_tasks.pop(symbol, None)
        if task is None:
            return
        
        if not task.done():
            task.cancel()
            try:
                await task
            except asyncio.CancelledError:
                pass
        logger.debug(f"已停止 {symbol} 的WebSocket监控")
    
    async def _monitor_position_price(self, symbol: str):
        """
        监控单个持仓的价格（WebSocket实时推送）
        
        Args:
            symbol: 交易对
        """
        retry_count = 0
        max_retries = 5
        
        while retry_count < max_retries:
            try:
                if symbol not in self.active_positions:
                    logger.info(f"[账号{self.account_id}] {symbol} 持仓已不存在，停止监控")
                    break
                
                # 使用WebSocket订阅价格流
                # 直接使用 aiohttp 连接 Binance 期货 WebSocket API
                # 根据文档：https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams
                # 端点：wss://fstream.binance.com/ws/<symbol>@ticker
                ws_url = f"wss://fstream.binance.com/ws/{symbol.lower()}@ticker"
                
                async with aiohttp.ClientSession() as session:
                    async with session.ws_connect(ws_url) as ws:
                        logger.debug(f"{symbol} WebSocket连接已建立，开始接收价格更新")
                        retry_count = 0  # 连接成功，重置重试计数
                        
                        async for msg in ws:
                            if symbol not in self.active_positions:
                                logger.info(f"[账号{self.account_id}] {symbol} 持仓已不存在，停止监控")
                                break
                            
                            if msg.type == aiohttp.WSMsgType.TEXT:
                                try:
                                    # 解析 JSON 消息
                                    data = json.loads(msg.data)
                                    
                                    # WebSocket 返回的数据格式：{'e': '24hrTicker', 's': 'BTCUSDT', 'c': '50000.00', ...}
                                    # 根据文档，ticker 流包含 'c' 字段（最后价格）
                                    if isinstance(data, dict):
                                        if 'c' in data:  # 'c' 是当前价格
                                            current_price = float(data['c'])
                                            # 立即检查止损止盈
                                            await self._check_single_position(symbol, current_price)
                                        elif 'data' in data:
                                            # 兼容组合流格式（如果使用 /stream 端点）
                                            if isinstance(data['data'], dict) and 'c' in data['data']:
                                                current_price = float(data['data']['c'])
                                                await self._check_single_position(symbol, current_price)
                                except (KeyError, ValueError, TypeError, json.JSONDecodeError) as e:
                                    logger.debug(f"{symbol} 解析价格数据失败: {e}, 消息: {msg.data[:100] if hasattr(msg, 'data') else 'N/A'}")
                                    continue
                            elif msg.type == aiohttp.WSMsgType.ERROR:
                                logger.warning(f"{symbol} WebSocket错误: {ws.exception()}")
                                break
                            elif msg.type == aiohttp.WSMsgType.CLOSE:
                                logger.info(f"{symbol} WebSocket连接关闭")
                                break
                
            except asyncio.CancelledError:
                logger.info(f"{symbol} 监控任务已取消")
                break
            except Exception as e:
                retry_count += 1
                logger.warning(f"{symbol} WebSocket监控出错 (重试 {retry_count}/{max_retries}): {e}")
                
                if retry_count < max_retries:
                    # 指数退避重试
                    wait_time = min(2 ** retry_count, 30)
                    logger.info(f"{symbol} {wait_time}秒后重试连接...")
                    await asyncio.sleep(wait_time)
                else:
                    logger.error(f"{symbol} WebSocket监控失败，已达到最大重试次数")
                    # 回退到定时检查模式
                    logger.info(f"{symbol} 将使用定时检查模式（非实时）")
                    break
        
        # 清理任务
        if symbol in self._monitor_tasks:
            del self._monitor_tasks[symbol]
    
    async def _check_single_position(self, symbol: str, current_price: float):
        """
        检查单个持仓的止损止盈（实时检查）
        
        Args:
            symbol: 交易对
            current_price: 当前价格
        """
        position_info = self.active_positions.get(symbol)
        if not position_info:
            return
        
        # 确保所有值都是float类型
        entry_price = float(position_info['entryPrice'])
        quantity = float(position_info['quantity'])
        current_price_float = float(current_price)
        
        # 获取杠杆（确保为float）
        try:
            leverage = float(position_info.get('leverage', 10))
        except (ValueError, TypeError):
            leverage = 10.0
            
        # 计算持仓价值和保证金
        position_value = entry_price * quantity
        margin = position_value / leverage if leverage > 0 else position_value
        
        # 计算盈亏金额
        if position_info['side'] == 'BUY':
            pnl_amount = (current_price_float - entry_price) * quantity
        else:  # SELL
            pnl_amount = (entry_price - current_price_float) * quantity
        
        # 计算盈亏百分比（ROE - Return on Equity，即相对于保证金的盈亏）
        # 这是用户最关心的“盈亏比”
        pnl_percent_margin = (pnl_amount / margin * 100) if margin > 0 else 0
        
        # 也计算价格百分比（不带杠杆的原始涨跌幅）
        if position_info['side'] == 'BUY':
            pnl_percent_price = ((current_price_float - entry_price) / entry_price) * 100
        else:  # SELL
            pnl_percent_price = ((entry_price - current_price_float) / entry_price) * 100
        
        # 更新最大盈利（基于保证金）及最后一次创新高时间（用于滞涨早止盈）
        if pnl_percent_margin > position_info.get('maxProfit', 0):
            position_info['maxProfit'] = pnl_percent_margin
            position_info['lastNewHighTs'] = time.time()
        
        # 滞涨早止盈（实时监控）：曾涨到约 N% 后 X 小时内未创新高 → 分批减仓 + 抬止损
        stagnation_enabled = bool(config.TRADING_CONFIG.get('STAGNATION_EARLY_EXIT_ENABLED', False))
        if stagnation_enabled and not position_info.get('stagnationExitTriggered', False):
            max_profit = float(position_info.get('maxProfit', 0) or 0)
            min_runup = float(config.TRADING_CONFIG.get('STAGNATION_MIN_RUNUP_PCT', 10) or 10)
            stall_hours = float(config.TRADING_CONFIG.get('STAGNATION_NO_NEW_HIGH_HOURS', 3) or 3)
            partial_pct = float(config.TRADING_CONFIG.get('STAGNATION_PARTIAL_CLOSE_PCT', 0.5) or 0.5)
            lock_pct = float(config.TRADING_CONFIG.get('STAGNATION_LOCK_PCT', 5) or 5)
            last_high_ts = position_info.get('lastNewHighTs')
            if last_high_ts is not None and max_profit >= min_runup and pnl_percent_margin < max_profit:
                stall_sec = stall_hours * 3600
                if (time.time() - last_high_ts) >= stall_sec:
                    logger.info(
                        f"[账号{self.account_id}] {symbol} [滞涨早止盈-实时] 曾达浮盈{max_profit:.2f}%≥{min_runup}%，"
                        f"已{stall_hours:.1f}h未创新高，执行分批减仓+抬止损"
                    )
                    try:
                        close_side = 'SELL' if position_info['side'] == 'BUY' else 'BUY'
                        close_position_side = 'LONG' if position_info['side'] == 'BUY' else 'SHORT'
                        remaining_qty = float(position_info.get('remainingQuantity', quantity))
                        partial_quantity = remaining_qty * partial_pct
                        live_amt = await self._get_live_position_amt(symbol, position_side=close_position_side)
                        if live_amt is not None and abs(live_amt) > 0:
                            partial_quantity = min(partial_quantity, abs(live_amt))
                            partial_quantity = await self._adjust_close_quantity(symbol, partial_quantity)
                            if partial_quantity > 0:
                                partial_order = await self.client.place_order(
                                    symbol=symbol, side=close_side, quantity=partial_quantity,
                                    order_type='MARKET', reduce_only=True, position_side=close_position_side,
                                )
                                if partial_order:
                                    position_info['partialProfitTaken'] = True
                                    position_info['remainingQuantity'] = remaining_qty - partial_quantity
                                    logger.info(f"[账号{self.account_id}] {symbol} [滞涨早止盈-实时] 部分平仓{partial_quantity:.4f}，剩余{position_info['remainingQuantity']:.4f}")
                        remaining_after = float(position_info.get('remainingQuantity', quantity))
                        lev = float(position_info.get('leverage', 10) or 10)
                        rem_margin = (entry_price * remaining_after) / lev if lev > 0 else (entry_price * remaining_after)
                        lock_pct_use = max(lock_pct, max_profit / 2.0)
                        new_sl = self._stop_price_to_lock_pct(entry_price, position_info['side'], rem_margin, remaining_after, lock_pct_use)
                        breakeven = self._breakeven_stop_price(entry_price, position_info['side'])
                        current_sl = position_info.get('stopLoss')
                        side_here = position_info['side']
                        set_sl = (side_here == 'BUY' and (current_sl is None or new_sl > current_sl) and new_sl >= breakeven) or (
                            side_here == 'SELL' and (current_sl is None or new_sl < current_sl) and new_sl <= breakeven)
                        if set_sl:
                            position_info['stopLoss'] = new_sl
                            logger.info(f"[账号{self.account_id}] {symbol} [滞涨早止盈-实时] 剩余仓位止损上移至 {new_sl:.4f}，锁定约{lock_pct_use:.1f}%利润")
                            try:
                                await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
                            except Exception as sync_e:
                                logger.warning(f"[账号{self.account_id}] {symbol} [滞涨早止盈-实时] 同步止损至交易所失败: {sync_e}")
                        position_info['stagnationExitTriggered'] = True
                    except Exception as e:
                        logger.warning(f"[账号{self.account_id}] {symbol} [滞涨早止盈-实时] 执行异常: {e}", exc_info=False)
        
        # ⚠️ 2026-01-27修复：提前初始化partial_profit_taken，避免在止损检查时未定义
        partial_profit_taken = position_info.get('partialProfitTaken', False)
        remaining_quantity = position_info.get('remainingQuantity', quantity)
        
        # 移动止损逻辑（盈利后保护利润，基于保证金）
        # 每次检查时从Redis重新加载配置，确保配置修改能即时生效
        try:
            if config._config_manager:
                config._config_manager.reload_from_redis()
                config.TRADING_CONFIG = config._get_trading_config()
        except Exception as e:
            logger.debug(f"从Redis重新加载配置失败: {e}")
        
        # 时间/无盈利止损：持仓超过 N 小时且盈利未达阈值则平仓（低波动期减少磨单）
        time_stop_enabled = config.TRADING_CONFIG.get('TIME_STOP_ENABLED', False)
        if time_stop_enabled:
            max_hold_hours = float(config.get_effective_config('TIME_STOP_MAX_HOLD_HOURS', 8) or 8)
            min_pnl_to_hold = float(config.TRADING_CONFIG.get('TIME_STOP_MIN_PNL_PCT_TO_HOLD', 0.0) or 0.0)
            entry_time = position_info.get('entryTime')
            hold_hours = 0.0
            if entry_time:
                try:
                    if isinstance(entry_time, datetime):
                        hold_hours = (get_beijing_time() - entry_time).total_seconds() / 3600.0
                    elif isinstance(entry_time, str):
                        entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S')
                        hold_hours = (get_beijing_time() - entry_dt).total_seconds() / 3600.0
                    else:
                        hold_hours = (time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0)) / 3600.0
                except Exception as e:
                    logger.debug(f"{symbol} 解析 entryTime 失败: {e}")
            if hold_hours >= max_hold_hours and pnl_percent_margin < min_pnl_to_hold:
                logger.info(
                    f"{symbol} [时间止损] 持仓 {hold_hours:.1f}h >= {max_hold_hours}h 且盈亏 {pnl_percent_margin:.2f}% < {min_pnl_to_hold}%，平仓"
                )
                if await self.close_position(symbol, reason='time_stop'):
                    logger.info(f"{symbol} [时间止损] 平仓成功")
                return
        
        # ⚠️ 优化：已完全移除时间锁限制
        # 理由：1) 止损和止盈都应该立即执行，不受时间限制
        #       2) 交易所级别的止损/止盈单已提供保护
        #       3) 分步止盈策略本身已提供利润保护
        #       4) 及时执行止损/止盈可以保护资金和利润
        # 注意：如果需要防止秒级平仓，可以通过提高入场信号质量（MIN_SIGNAL_STRENGTH）来实现
        
        # 检查是否启用移动止损/保本（默认 True，与 config.py 一致；显式设为 False 才关闭）
        profit_protection_enabled = bool(config.TRADING_CONFIG.get('PROFIT_PROTECTION_ENABLED', True))
        use_trailing = profit_protection_enabled and bool(config.TRADING_CONFIG.get('USE_TRAILING_STOP', True))
        if use_trailing:
            logger.debug(f"[账号{self.account_id}] {symbol} [实时监控-移动止损] 已启用，将检查移动止损逻辑")
        else:
            if not profit_protection_enabled:
                logger.debug(f"[账号{self.account_id}] {symbol} [实时监控-移动止损/保本] 已禁用（PROFIT_PROTECTION_ENABLED=False）")
            else:
                logger.debug(f"[账号{self.account_id}] {symbol} [实时监控-移动止损] 已禁用（USE_TRAILING_STOP=False），跳过移动止损检查")
        if use_trailing:
            trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.10)  # 相对于保证金，默认 10%
            trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.02)  # 相对于保证金，默认 2%
            lock_pct = config.TRADING_CONFIG.get('LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT')
            if lock_pct is None:
                lock_pct = 0.03  # 未配置时默认 3% 移至保本
            if lock_pct and lock_pct > 1:
                lock_pct = lock_pct / 100.0
            
            if not position_info.get('trailingStopActivated', False):
                # 盈利达一定比例时尽早将止损移至含手续费保本，避免先盈后亏
                if lock_pct > 0 and not position_info.get('breakevenStopSet', False) and pnl_percent_margin >= lock_pct * 100:
                    breakeven = self._breakeven_stop_price(entry_price, position_info['side'])
                    current_sl = position_info.get('stopLoss')
                    side_here = position_info['side']
                    set_breakeven = False
                    if side_here == 'BUY' and (current_sl is None or current_sl < breakeven):
                        set_breakeven = True
                    elif side_here == 'SELL' and (current_sl is None or current_sl > breakeven):
                        set_breakeven = True
                    if set_breakeven:
                        position_info['stopLoss'] = breakeven
                        position_info['breakevenStopSet'] = True
                        logger.info(
                            f"[账号{self.account_id}] {symbol} [实时监控] 盈利{pnl_percent_margin:.2f}%≥{lock_pct*100:.0f}%，止损已移至含手续费保本价 {breakeven:.4f}（留住盈利）"
                        )
                        _log_trailing_stop_event(self.account_id, symbol, "breakeven_set", breakeven=breakeven, pnl_pct=pnl_percent_margin, source="实时监控")
                        logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 尝试将保本止损同步至交易所")
                        try:
                            await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
                            _log_trailing_stop_event(self.account_id, symbol, "breakeven_sync_ok", breakeven=breakeven, source="实时监控")
                        except Exception as sync_e:
                            _log_trailing_stop_event(self.account_id, symbol, "breakeven_sync_fail", breakeven=breakeven, error=str(sync_e), source="实时监控")
                            logger.warning(
                                f"[账号{self.account_id}] {symbol} [实时监控] 同步保本止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                                exc_info=False,
                            )
                await self._apply_profit_lock_stage(
                    symbol=symbol,
                    position_info=position_info,
                    entry_price=entry_price,
                    quantity=quantity,
                    leverage=leverage,
                    pnl_amount=pnl_amount,
                    pnl_percent_margin=pnl_percent_margin,
                    current_price=current_price_float,
                    source="实时监控",
                )
                # 盈利超过阈值后（相对于保证金），激活移动止损
                if pnl_percent_margin > trailing_activation * 100:
                    position_info['trailingStopActivated'] = True
                    # 保护利润金额至少覆盖双向手续费，避免“保护”后仍为负
                    protect_amount = max(margin * trailing_protect, self._min_protect_amount_for_fees(margin, leverage))
                    if position_info['side'] == 'BUY':
                        new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity
                        breakeven = self._breakeven_stop_price(entry_price, 'BUY')
                        new_stop_loss = max(new_stop_loss, breakeven)
                    else:  # SELL 做空锁利：止损下移，锁住 protect_amount。(entry - stop)*q = protect → stop = entry - protect/q
                        new_stop_loss = entry_price - protect_amount / quantity
                        breakeven = self._breakeven_stop_price(entry_price, 'SELL')
                        new_stop_loss = max(new_stop_loss, breakeven)
                    
                    position_info['stopLoss'] = new_stop_loss
                    logger.info(
                        f"[账号{self.account_id}] {symbol} [实时监控] 移动止损激活: 止损移至保护利润位 {new_stop_loss:.4f} "
                        f"(盈利: {pnl_percent_margin:.2f}% of margin, 保护: {trailing_protect*100:.1f}% of margin)"
                    )
                    _log_trailing_stop_event(self.account_id, symbol, "trailing_activated", new_stop_loss=new_stop_loss, pnl_pct=pnl_percent_margin, source="实时监控")
                    # 同步至交易所：取消原止损单并按新止损价重挂，使移动止损也有交易所保护
                    logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 尝试将移动止损同步至交易所")
                    try:
                        await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_ok", new_stop_loss=new_stop_loss, source="实时监控")
                        logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所")
                    except Exception as sync_e:
                        _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_fail", new_stop_loss=new_stop_loss, error=str(sync_e), source="实时监控")
                        logger.warning(
                            f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                            exc_info=False,
                        )
            else:
                # ⚠️ 优化：如果分步止盈第一目标已触发，移动止损不再更新剩余仓位的止损价
                # 原因：分步止盈第一目标触发后，剩余50%仓位止损已移至成本价（保本），等待第二目标
                # 移动止损不应该覆盖分步止盈设置的止损价
                if position_info.get('partialProfitTaken', False):
                    # 分步止盈第一目标已触发，移动止损不再更新
                    logger.debug(f"[账号{self.account_id}] {symbol} [实时监控-移动止损] 分步止盈第一目标已触发，移动止损不再更新剩余仓位止损价")
                else:
                    # 盈利超过阈值后，止损移至保护利润位（基于保证金）；保护金额至少覆盖手续费
                    protect_amount = max(margin * trailing_protect, self._min_protect_amount_for_fees(margin, leverage))
                if position_info['side'] == 'BUY':
                    new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity
                    new_stop_loss = max(new_stop_loss, self._breakeven_stop_price(entry_price, 'BUY'))
                    current_sl_buy = position_info.get('stopLoss')
                    if current_sl_buy is None or new_stop_loss > current_sl_buy:
                        position_info['stopLoss'] = new_stop_loss
                        logger.info(
                            f"[账号{self.account_id}] {symbol} [实时监控] 移动止损更新: {new_stop_loss:.4f} "
                            f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)"
                        )
                        _log_trailing_stop_event(self.account_id, symbol, "trailing_update", new_stop_loss=new_stop_loss, source="实时监控")
                        try:
                            await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
                            _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_ok", new_stop_loss=new_stop_loss, source="实时监控")
                            logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所")
                        except Exception as sync_e:
                            _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_fail", new_stop_loss=new_stop_loss, error=str(sync_e), source="实时监控")
                            logger.warning(
                                f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                                exc_info=False,
                            )
                else:  # SELL 做空锁利：止损下移，锁住 protect_amount。(entry - stop)*q = protect → stop = entry - protect/q
                    new_stop_loss = entry_price - protect_amount / quantity
                    new_stop_loss = max(new_stop_loss, self._breakeven_stop_price(entry_price, 'SELL'))
                    current_sl = position_info.get('stopLoss')
                    # 仅当新止损低于当前止损（下移锁利）且高于市价时更新
                    if (current_sl is None or new_stop_loss < current_sl) and new_stop_loss > current_price_float and pnl_amount > 0:
                        position_info['stopLoss'] = new_stop_loss
                        logger.info(
                            f"[账号{self.account_id}] {symbol} [实时监控] 移动止损更新（做空锁利）: {new_stop_loss:.4f} "
                            f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)"
                        )
                        _log_trailing_stop_event(self.account_id, symbol, "trailing_update", new_stop_loss=new_stop_loss, source="实时监控")
                        try:
                            await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
                            _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_ok", new_stop_loss=new_stop_loss, source="实时监控")
                            logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所")
                        except Exception as sync_e:
                            _log_trailing_stop_event(self.account_id, symbol, "trailing_sync_fail", new_stop_loss=new_stop_loss, error=str(sync_e), source="实时监控")
                            logger.warning(
                                f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
                                exc_info=False,
                            )
        
        # 检查止损（基于保证金收益比）
        # ⚠️ 重要：止损检查应该在时间锁之前，止损必须立即执行
        stop_loss = position_info.get('stopLoss')
        should_close_due_to_sl = False
        exit_reason_sl = None
        
        if stop_loss is not None:
            # 计算止损对应的保证金百分比目标
            # 止损金额 = (开仓价 - 止损价) × 数量 或 (止损价 - 开仓价) × 数量
            if position_info['side'] == 'BUY':
                stop_loss_amount = (entry_price - stop_loss) * quantity
            else:  # SELL
                stop_loss_amount = (stop_loss - entry_price) * quantity
            
            stop_loss_pct_margin = (stop_loss_amount / margin * 100) if margin > 0 else 0
            
            # 每5%亏损记录一次诊断日志（帮助排查问题）
            if pnl_percent_margin <= -5.0:
                should_log = (int(abs(pnl_percent_margin)) % 5 == 0) or (pnl_percent_margin <= -10.0 and pnl_percent_margin > -10.5)
                if should_log:
                    trigger_condition = pnl_percent_margin <= -stop_loss_pct_margin
                    # 计算当前价格相对于入场价的变动百分比（不带杠杆）
                    price_change_pct = 0.0
                    if entry_price > 0:
                        if position_info['side'] == 'BUY':
                            price_change_pct = (current_price_float - entry_price) / entry_price * 100
                        else:
                            price_change_pct = (entry_price - current_price_float) / entry_price * 100
                            
                    logger.warning(
                        f"[账号{self.account_id}] {symbol} [实时监控] 诊断: \n"
                        f"  • ROE(保证金盈亏): {pnl_percent_margin:.2f}% (用户关注)\n"
                        f"  • 价格变动: {price_change_pct:.2f}% (实际币价涨跌)\n"
                        f"  • 杠杆倍数: {leverage}x (放大倍数)\n"
                        f"  • 当前价: {current_price_float:.4f} | 入场价: {entry_price:.4f}\n"
                        f"  • 止损价: {stop_loss:.4f} (目标ROE: -{stop_loss_pct_margin:.2f}%)\n"
                        f"  • 触发止损: {'YES' if trigger_condition else 'NO'}"
                    )
            
            # ⚠️ 2026-01-27关键修复：止损检查前，先检查是否盈利
            # 如果盈利，不应该触发止损（除非是移动止损或分步止盈后的剩余仓位）
            # 直接比较当前盈亏百分比与止损目标（基于保证金）
            if pnl_percent_margin <= -stop_loss_pct_margin:
                # ⚠️ 额外检查：如果盈利，可能是移动止损触发，应该标记为trailing_stop
                if pnl_percent_margin > 0:
                    # 盈利单触发止损，应该是移动止损
                    if position_info.get('trailingStopActivated'):
                        exit_reason_sl = 'trailing_stop'
                        logger.warning(f"[账号{self.account_id}] {symbol} [实时监控] ⚠️ 盈利单触发止损，标记为移动止损（盈利: {pnl_percent_margin:.2f}% of margin）")
                    else:
                        # 盈利单但未激活移动止损，可能是分步止盈后的剩余仓位止损
                        if partial_profit_taken:
                            exit_reason_sl = 'take_profit_partial_then_stop'
                            logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 第一目标止盈后，剩余仓位触发止损（保本）")
                        else:
                            # 异常情况：盈利单触发止损但未激活移动止损
                            exit_reason_sl = 'trailing_stop'  # 默认标记为移动止损
                            logger.warning(f"[账号{self.account_id}] {symbol} [实时监控] ⚠️ 异常：盈利单触发止损但未激活移动止损，标记为移动止损")
                else:
                    # 正常止损逻辑
                    should_close_due_to_sl = True
                    # ⚠️ 2026-01-27优化：如果已部分止盈，细分状态
                    if partial_profit_taken:
                        if position_info.get('trailingStopActivated'):
                            exit_reason_sl = 'take_profit_partial_then_trailing_stop'
                        else:
                            exit_reason_sl = 'take_profit_partial_then_stop'
                    else:
                        exit_reason_sl = 'trailing_stop' if position_info.get('trailingStopActivated') else 'stop_loss'
                
                # 计算持仓时间
                entry_time = position_info.get('entryTime')
                hold_time_minutes = 0
                if entry_time:
                    try:
                        if isinstance(entry_time, datetime):
                            hold_time_sec = int((get_beijing_time() - entry_time).total_seconds())
                        else:
                            hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0))
                        hold_time_minutes = hold_time_sec / 60.0
                    except Exception:
                        hold_time_minutes = 0
                
                # 详细诊断日志：记录平仓时的所有关键信息
                logger.warning("=" * 80)
                logger.warning(f"[账号{self.account_id}] {symbol} [实时监控-平仓诊断日志] ===== 触发止损平仓 =====")
                logger.warning(f"  平仓原因: {exit_reason_sl}")
                logger.warning(f"  入场价格: {entry_price:.6f} USDT")
                logger.warning(f"  当前价格: {current_price_float:.6f} USDT")
                logger.warning(f"  止损价格: {stop_loss:.4f} USDT")
                logger.warning(f"  持仓数量: {quantity:.4f}")
                logger.warning(f"  持仓时间: {hold_time_minutes:.1f} 分钟")
                logger.warning(f"  入场时间: {entry_time}")
                logger.warning(f"  当前盈亏: {pnl_percent_margin:.2f}% of margin")
                logger.warning(f"  止损目标: -{stop_loss_pct_margin:.2f}% of margin")
                logger.warning(f"  亏损金额: {abs(pnl_amount):.4f} USDT")
                if position_info.get('trailingStopActivated'):
                    logger.warning(f"  移动止损: 已激活（从初始止损 {position_info.get('initialStopLoss', 'N/A')} 调整）")
                logger.warning("=" * 80)
                
                # ⚠️ 2026-01-27优化：如果已部分止盈，细分状态为"第一目标止盈后剩余仓位止损"
                if partial_profit_taken:
                    exit_reason_sl = 'take_profit_partial_then_stop'
                    logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 第一目标止盈后，剩余仓位触发止损（保本）")
                
                # ⚠️ 关键修复：止损必须立即执行，不受时间锁限制
                if await self.close_position(symbol, reason=exit_reason_sl):
                    logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 止损平仓成功（不受时间锁限制）")
                return  # 止损已执行，跳过后续止盈检查
        
        # 检查分步止盈（实时监控）
        # ⚠️ 优化：已移除止盈时间锁，止盈可以立即执行（与止损一致）
        # 理由：1) 止损已不受时间锁限制，止盈也应该一致
        #       2) 分步止盈策略本身已提供利润保护（50%在1:1止盈，剩余保本）
        #       3) 交易所级别止盈单已提供保护
        #       4) 及时止盈可以保护利润，避免价格回落
        should_close = False
        take_profit_1 = position_info.get('takeProfit1')  # 第一目标（盈亏比1:1）
        take_profit_2 = position_info.get('takeProfit2', position_info.get('takeProfit'))  # 第二目标（1.5:1）
        # ⚠️ 注意：partial_profit_taken和remaining_quantity已在方法开头初始化，这里不需要重复定义
        
        # 第一目标：TAKE_PROFIT_1_PERCENT 止盈（默认15%保证金），了结50%仓位
        if not partial_profit_taken and take_profit_1 is not None:
            # 直接使用配置的 TAKE_PROFIT_1_PERCENT，与开仓时计算的第一目标一致
            take_profit_1_pct_margin_config = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.20)
            # 兼容百分比形式和比例形式
            if take_profit_1_pct_margin_config > 1:
                take_profit_1_pct_margin_config = take_profit_1_pct_margin_config / 100.0
            take_profit_1_pct_margin = take_profit_1_pct_margin_config * 100  # 转换为百分比
            
            # 直接比较当前盈亏百分比与第一目标（基于保证金，使用配置值）
            # ⚠️ 2026-02-04 修复：增加最小盈利检查，防止因配置过低或滑点导致负盈利
            # 手续费估算：0.05% * 2 = 0.1% 价格变动。10x杠杆下约1%保证金。保留2%作为安全边际。
            min_profit_margin = 2.0
            
            if pnl_percent_margin >= take_profit_1_pct_margin and pnl_percent_margin > min_profit_margin:
                take_profit_pct_config = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.20)
                if take_profit_pct_config > 1:
                    take_profit_pct_config = take_profit_pct_config / 100.0
                take_profit_pct_display = take_profit_pct_config * 100
                logger.info(
                    f"[账号{self.account_id}] {symbol} [实时监控] 触发第一目标止盈（{take_profit_pct_display:.1f}%固定止盈，基于保证金）: "
                    f"当前盈亏={pnl_percent_margin:.2f}% of margin >= 目标={take_profit_1_pct_margin:.2f}% of margin | "
                    f"当前价={current_price_float:.4f}, 目标价={take_profit_1:.4f} | "
                    f"将平掉50%仓位，锁定{take_profit_pct_display:.1f}%盈利，剩余50%追求更高收益"
                )
                # 部分平仓50%
                partial_quantity = quantity * 0.5
                try:
                    close_side = 'SELL' if position_info['side'] == 'BUY' else 'BUY'
                    close_position_side = 'LONG' if position_info['side'] == 'BUY' else 'SHORT'
                    live_amt = await self._get_live_position_amt(symbol, position_side=close_position_side)
                    if live_amt is None or abs(live_amt) <= 0:
                        logger.warning(f"[账号{self.account_id}] {symbol} [实时监控] 部分止盈：实时持仓已为0，跳过部分平仓")
                    else:
                        partial_quantity = min(partial_quantity, abs(live_amt))
                        partial_quantity = await self._adjust_close_quantity(symbol, partial_quantity)
                        if partial_quantity > 0:
                            partial_order = await self.client.place_order(
                                symbol=symbol,
                                side=close_side,
                                quantity=partial_quantity,
                                order_type='MARKET',
                                reduce_only=True,
                                position_side=close_position_side,
                            )
                            if partial_order:
                                position_info['partialProfitTaken'] = True
                                position_info['remainingQuantity'] = remaining_quantity - partial_quantity
                                logger.info(
                                    f"[账号{self.account_id}] {symbol} [实时监控] 部分止盈成功: 平仓{partial_quantity:.4f}，剩余{position_info['remainingQuantity']:.4f}"
                                )
                                # 分步止盈后的"保本"处理：仅在盈利保护总开关开启时移至含手续费保本价
                                if profit_protection_enabled:
                                    breakeven = self._breakeven_stop_price(entry_price, position_info['side'])
                                    position_info['stopLoss'] = breakeven
                                    logger.info(
                                        f"[账号{self.account_id}] {symbol} [实时监控] 部分止盈后：剩余仓位止损移至含手续费保本价 {breakeven:.4f}（入场: {entry_price:.4f}），"
                                        f"剩余50%仓位追求更高收益（第二目标：4.0:1盈亏比或更高）"
                                    )
                except Exception as e:
                    logger.error(f"[账号{self.account_id}] {symbol} [实时监控] 部分止盈失败: {e}")
        
        # 第二目标：4.0:1止盈，平掉剩余仓位（山寨币策略）
        if partial_profit_taken and take_profit_2 is not None and not should_close:
            # 计算第二目标对应的保证金百分比（基于剩余仓位）
            if position_info['side'] == 'BUY':
                take_profit_2_amount = (take_profit_2 - entry_price) * remaining_quantity
            else:  # SELL
                take_profit_2_amount = (entry_price - take_profit_2) * remaining_quantity
            remaining_margin = (entry_price * remaining_quantity) / leverage if leverage > 0 else (entry_price * remaining_quantity)
            take_profit_2_pct_margin = (take_profit_2_amount / remaining_margin * 100) if remaining_margin > 0 else 0
            # 计算剩余仓位的当前盈亏
            if position_info['side'] == 'BUY':
                remaining_pnl_amount = (current_price_float - entry_price) * remaining_quantity
            else:
                remaining_pnl_amount = (entry_price - current_price_float) * remaining_quantity
            remaining_pnl_pct_margin = (remaining_pnl_amount / remaining_margin * 100) if remaining_margin > 0 else 0
            
            # 直接比较剩余仓位盈亏百分比与第二目标（基于保证金）
            # ⚠️ 2026-02-04 修复：增加最小盈利检查
            if remaining_pnl_pct_margin >= take_profit_2_pct_margin and remaining_pnl_pct_margin > 2.0:
                should_close = True
                # ⚠️ 2026-01-27优化：细分状态，区分"第一目标止盈后第二目标止盈"
                exit_reason = 'take_profit_partial_then_take_profit'
                logger.info(
                    f"[账号{self.account_id}] {symbol} [实时监控] 触发第二目标止盈（4.0:1，山寨币策略）: "
                    f"剩余仓位盈亏={remaining_pnl_pct_margin:.2f}% of margin >= 目标={take_profit_2_pct_margin:.2f}% of margin | "
                    f"当前价={current_price_float:.4f}, 目标价={take_profit_2:.4f}, "
                    f"剩余数量={remaining_quantity:.4f}"
                )
        
        # 检查止盈（基于保证金收益比）- 用于未启用分步止盈的情况
        if not should_close:
            take_profit = position_info.get('takeProfit')
            if take_profit is not None:
                # 计算止盈对应的保证金百分比目标
                # ⚠️ 关键修复：直接使用配置的 TAKE_PROFIT_PERCENT，而不是从止盈价格反推
                # 因为止盈价格可能使用了ATR（更远），反推会导致阈值过大，难以触发
                take_profit_pct_margin_config = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.10)
                # 兼容百分比形式和比例形式
                if take_profit_pct_margin_config > 1:
                    take_profit_pct_margin_config = take_profit_pct_margin_config / 100.0
                take_profit_pct_margin = take_profit_pct_margin_config * 100  # 转换为百分比
                
                # 计算止盈金额（用于日志显示，但不用于触发判断）
                if position_info['side'] == 'BUY':
                    take_profit_amount = (take_profit - entry_price) * quantity
                else:  # SELL
                    take_profit_amount = (entry_price - take_profit) * quantity
                take_profit_pct_margin_from_price = (take_profit_amount / margin * 100) if margin > 0 else 0
                
                # 每5%盈利记录一次诊断日志（帮助排查问题）
                if pnl_percent_margin >= 5.0:
                    should_log = (int(pnl_percent_margin) % 5 == 0) or (pnl_percent_margin >= 10.0 and pnl_percent_margin < 10.5)
                    if should_log:
                        trigger_condition = pnl_percent_margin >= take_profit_pct_margin
                        logger.warning(
                            f"[账号{self.account_id}] {symbol} [实时监控] 诊断: 盈利{pnl_percent_margin:.2f}% of margin | "
                            f"当前价: {current_price_float:.4f} | "
                            f"入场价: {entry_price:.4f} | "
                            f"止盈价: {take_profit:.4f} (配置目标: {take_profit_pct_margin:.2f}% of margin, 价格对应: {take_profit_pct_margin_from_price:.2f}%) | "
                            f"方向: {position_info['side']} | "
                            f"是否触发: {trigger_condition} | "
                            f"监控状态: {'运行中' if symbol in self._monitor_tasks else '未启动'}"
                        )
                
                # 直接比较当前盈亏百分比与止盈目标（基于保证金，使用配置值）
                # ⚠️ 2026-02-04 修复：增加最小盈利检查
                if pnl_percent_margin >= take_profit_pct_margin and pnl_percent_margin > 2.0:
                    should_close = True
                    exit_reason = 'take_profit'
                    
                    # 计算持仓时间（用于日志）
                    entry_time = position_info.get('entryTime')
                    hold_time_minutes = 0
                    if entry_time:
                        try:
                            if isinstance(entry_time, datetime):
                                hold_time_sec = int((get_beijing_time() - entry_time).total_seconds())
                            else:
                                hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0))
                            hold_time_minutes = hold_time_sec / 60.0
                        except Exception:
                            hold_time_minutes = 0
                    
                    # 详细诊断日志：记录平仓时的所有关键信息
                    logger.info("=" * 80)
                    logger.info(f"[账号{self.account_id}] {symbol} [实时监控-平仓诊断日志] ===== 触发止盈平仓 =====")
                    logger.info(f"  平仓原因: {exit_reason}")
                    logger.info(f"  入场价格: {entry_price:.6f} USDT")
                    logger.info(f"  当前价格: {current_price_float:.6f} USDT")
                    logger.info(f"  止盈价格: {take_profit:.4f} USDT")
                    logger.info(f"  持仓数量: {quantity:.4f}")
                    logger.info(f"  持仓时间: {hold_time_minutes:.1f} 分钟")
                    logger.info(f"  入场时间: {entry_time}")
                    logger.info(f"  当前盈亏: {pnl_percent_margin:.2f}% of margin")
                    logger.info(f"  止盈目标: {take_profit_pct_margin:.2f}% of margin")
                    logger.info(f"  盈利金额: {pnl_amount:.4f} USDT")
                    logger.info("=" * 80)
        
        # 如果触发止损止盈，执行平仓
        if should_close:
            # 自动平仓限流：避免同一 symbol 短时间内反复触发平仓请求（WebSocket 高频推送下很常见）
            try:
                now_ms = int(time.time() * 1000)
            except Exception:
                now_ms = None
            if now_ms is not None:
                cooldown_sec = int(config.TRADING_CONFIG.get("AUTO_CLOSE_COOLDOWN_SEC", 20) or 0)
                last_ms = self._last_auto_close_attempt_ms.get(symbol)
                if last_ms and cooldown_sec > 0 and now_ms - last_ms < cooldown_sec * 1000:
                    # 不重复刷屏：仅 debug
                    logger.debug(f"{symbol} [自动平仓] 冷却中（{cooldown_sec}s），跳过重复平仓尝试")
                    return
                self._last_auto_close_attempt_ms[symbol] = now_ms

            logger.info(
                f"{symbol} [自动平仓] 开始执行平仓操作 | "
                f"原因: {exit_reason} | "
                f"入场价: {entry_price:.4f} | "
                f"当前价: {current_price_float:.4f} | "
                f"盈亏: {pnl_percent_margin:.2f}% of margin ({pnl_amount:.4f} USDT) | "
                f"数量: {quantity:.4f}"
            )
            
            # 执行平仓（让 close_position 统一处理数据库更新，避免重复更新和状态不一致）
            logger.info(f"{symbol} [自动平仓] 正在执行平仓订单...")
            success = await self.close_position(symbol, reason=exit_reason)
            if success:
                logger.info(f"{symbol} [自动平仓] ✓ 平仓成功完成")
                # 平仓成功后，立即触发一次状态同步，确保数据库状态与币安一致
                try:
                    await asyncio.sleep(2)  # 等待2秒让币安订单完全成交
                    await self.sync_positions_with_binance()
                    logger.debug(f"{symbol} [自动平仓] 已触发状态同步")
                except Exception as sync_error:
                    logger.warning(f"{symbol} [自动平仓] 状态同步失败: {sync_error}")
            else:
                # 平仓失败：先二次核对币安是否已无仓位（常见于竞态/网络抖动/幂等场景）
                live_amt = None
                try:
                    live_amt = await self._get_live_position_amt(symbol, position_side=None)
                except Exception:
                    live_amt = None

                if live_amt is not None and abs(live_amt) <= 0:
                    logger.warning(f"{symbol} [自动平仓] 平仓返回失败，但币安持仓已为0，按已平仓处理（避免误报）")
                    # 尝试同步一次，让DB与界面尽快一致（失败也不刷屏）
                    try:
                        await self.sync_positions_with_binance()
                    except Exception:
                        pass
                    return

                # 仍有仓位：减少刷屏（按时间窗口合并/限流）
                should_log = True
                if now_ms is not None:
                    log_cd = int(config.TRADING_CONFIG.get("AUTO_CLOSE_FAIL_LOG_COOLDOWN_SEC", 600) or 0)
                    last_log = self._last_auto_close_fail_log_ms.get(symbol)
                    if last_log and log_cd > 0 and now_ms - last_log < log_cd * 1000:
                        should_log = False
                    else:
                        self._last_auto_close_fail_log_ms[symbol] = now_ms

                if should_log:
                    logger.error(f"{symbol} [自动平仓] ❌ 平仓失败（币安持仓仍存在: {live_amt}）")
                else:
                    logger.warning(f"{symbol} [自动平仓] 平仓仍失败（已在短时间内记录，暂不重复输出）")

                # 即使平仓失败，也尝试同步状态（可能币安已经平仓了）
                try:
                    await self.sync_positions_with_binance()
                except Exception as sync_error:
                    logger.warning(f"{symbol} [自动平仓] 状态同步失败: {sync_error}")
    
    async def diagnose_position(self, symbol: str):
        """
        诊断持仓状态（用于排查为什么没有自动平仓）
        
        Args:
            symbol: 交易对
        """
        try:
            logger.info(f"{symbol} [诊断] 开始诊断持仓状态...")
            
            # 1. 检查是否在active_positions中
            if symbol not in self.active_positions:
                logger.warning(f"{symbol} [诊断] ❌ 不在本地持仓记录中 (active_positions)")
                logger.warning(f"   可能原因: 手动开仓或系统重启后未同步")
                logger.warning(f"   解决方案: 等待下次状态同步或手动触发同步")
            else:
                position_info = self.active_positions[symbol]
                logger.info(f"{symbol} [诊断] ✓ 在本地持仓记录中")
                logger.info(f"   入场价: {position_info['entryPrice']:.4f}")
                logger.info(f"   方向: {position_info['side']}")
                logger.info(f"   数量: {position_info['quantity']:.4f}")
                logger.info(f"   止损价: {position_info['stopLoss']:.4f}")
                logger.info(f"   止盈价: {position_info['takeProfit']:.4f}")
            
            # 2. 检查WebSocket监控状态
            if symbol in self._monitor_tasks:
                task = self._monitor_tasks[symbol]
                if task.done():
                    logger.warning(f"{symbol} [诊断] ⚠ WebSocket监控任务已结束")
                    try:
                        await task  # 获取异常信息
                    except Exception as e:
                        logger.warning(f"   任务异常: {e}")
                else:
                    logger.info(f"{symbol} [诊断] ✓ WebSocket监控任务运行中")
            else:
                logger.warning(f"{symbol} [诊断] ❌ 没有WebSocket监控任务")
                logger.warning(f"   可能原因: 监控未启动或已停止")
            
            # 3. 获取币安实际持仓
            positions = await self._get_open_positions()
            binance_position = next((p for p in positions if p['symbol'] == symbol), None)
            
            if not binance_position:
                logger.warning(f"{symbol} [诊断] ❌ 币安账户中没有持仓")
                return
            
            logger.info(f"{symbol} [诊断] ✓ 币安账户中有持仓")
            entry_price_binance = binance_position.get('entryPrice', 0)
            mark_price = binance_position.get('markPrice', 0)
            unrealized_pnl = binance_position.get('unRealizedProfit', 0)
            
            logger.info(f"   币安入场价: {entry_price_binance:.4f}")
            logger.info(f"   标记价格: {mark_price:.4f}")
            logger.info(f"   未实现盈亏: {unrealized_pnl:.2f} USDT")
            
            # 4. 计算实际盈亏
            if symbol in self.active_positions:
                position_info = self.active_positions[symbol]
                entry_price = float(position_info['entryPrice'])
                take_profit = float(position_info['takeProfit'])
                
                if position_info['side'] == 'BUY':
                    pnl_percent = ((mark_price - entry_price) / entry_price) * 100
                    take_profit_pct = ((take_profit - entry_price) / entry_price) * 100
                    should_trigger = mark_price >= take_profit
                else:  # SELL
                    pnl_percent = ((entry_price - mark_price) / entry_price) * 100
                    take_profit_pct = ((entry_price - take_profit) / entry_price) * 100
                    should_trigger = mark_price <= take_profit
                
                logger.info(f"{symbol} [诊断] 盈亏分析:")
                logger.info(f"   当前盈亏: {pnl_percent:.2f}%")
                logger.info(f"   止盈目标: {take_profit_pct:.2f}%")
                logger.info(f"   当前价: {mark_price:.4f}")
                logger.info(f"   止盈价: {take_profit:.4f}")
                logger.info(f"   价格差: {abs(mark_price - take_profit):.4f}")
                logger.info(f"   应该触发: {should_trigger}")
                
                if pnl_percent > take_profit_pct and not should_trigger:
                    logger.error(f"{symbol} [诊断] ❌ 异常: 盈亏{pnl_percent:.2f}% > 止盈目标{take_profit_pct:.2f}%，但未触发平仓！")
                    logger.error(f"   可能原因: 浮点数精度问题或止盈价格计算错误")
            
            logger.info(f"{symbol} [诊断] 诊断完成")
            
        except Exception as e:
            logger.error(f"{symbol} [诊断] 诊断失败: {e}")
            import traceback
            logger.error(f"   错误详情:\n{traceback.format_exc()}")
