From d80d4559c5317cdf37c94abad0741670dad52851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Thu, 26 Feb 2026 09:49:58 +0800 Subject: [PATCH] =?UTF-8?q?fix(position=5Fmanager):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=E5=92=8C=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在持仓管理模块中,调整了代码缩进和结构,提升了可读性和一致性。同时,增强了日志记录,确保在保存交易记录时提供更清晰的信息。这一改动旨在提升系统的稳定性和可维护性,确保交易策略的有效性与安全性。 --- trading_system/position_manager.py | 391 ++++++++++++++--------------- 1 file changed, 193 insertions(+), 198 deletions(-) diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 2b79c8e..135b604 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -603,7 +603,7 @@ class PositionManager: entry_order_id = order.get("orderId") if isinstance(order, dict) else None except Exception: entry_order_id = None - break + break else: logger.info(f"{symbol} [智能入场] 限价超时,但偏离{drift_ratio*100:.2f}%>{max_drift_ratio*100:.2f}%,取消并放弃本次交易") try: @@ -611,7 +611,7 @@ class PositionManager: except Exception: pass self._pending_entry_orders.pop(symbol, None) - return None + return None # 震荡/不允许市价兜底:尝试追价(减小 offset -> 更靠近当前价),但不突破追价上限 try: @@ -626,7 +626,7 @@ class PositionManager: if side == "BUY": cap = initial_limit * (1 + max_drift_ratio) desired = min(desired, cap) - else: + else: cap = initial_limit * (1 - max_drift_ratio) desired = max(desired, cap) @@ -851,30 +851,30 @@ class PositionManager: logger.info(f"✓ {symbol} 已完善 pending 记录 (ID: {trade_id}, 订单号: {entry_order_id}, 成交价: {entry_price:.4f}, 成交数量: {quantity:.4f})") if trade_id is None: # 无 pending 或未匹配到:走新建(兜底) - logger.info(f"正在保存 {symbol} 交易记录到数据库...") + logger.info(f"正在保存 {symbol} 交易记录到数据库...") fallback_client_order_id = (order.get("clientOrderId") if order else None) or client_order_id # 如果 REST 已获取到 entry_order_id,直接写入;否则留空,等待 WS 推送或后续同步补全 - trade_id = Trade.create( - symbol=symbol, - side=side, + trade_id = Trade.create( + symbol=symbol, + side=side, quantity=quantity, entry_price=entry_price, - leverage=leverage, - entry_reason=entry_reason, + 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, + 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})") + 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 查询订单号补全 @@ -1084,39 +1084,39 @@ class PositionManager: entry_dt = datetime.strptime(entry_time, '%Y-%m-%d %H:%M:%S') else: entry_dt = entry_time - exit_dt = get_beijing_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}秒后重试" + 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 ) - await asyncio.sleep(wait_sec) - else: - logger.error(f"{symbol} [平仓] ❌ 更新数据库状态失败: {err_msg}") + 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) @@ -1192,7 +1192,7 @@ class PositionManager: del self.active_positions[symbol] logger.info(f"{symbol} [平仓] ✓ 平仓完成: {side} {quantity:.4f} (原因: {reason})") return True - else: + else: # place_order 返回 None:可能是 -2022(ReduceOnly rejected)等竞态场景 # 兜底:再查一次实时持仓,如果已经为0,则当作“已平仓”处理,避免刷屏与误判失败 try: @@ -1244,13 +1244,13 @@ class PositionManager: except Exception: amt0 = None if amt0 is not None and abs(amt0) <= 0: - try: - await self._stop_position_monitoring(symbol) + try: + await self._stop_position_monitoring(symbol) except Exception: pass try: - if symbol in self.active_positions: - del self.active_positions[symbol] + if symbol in self.active_positions: + del self.active_positions[symbol] except Exception: pass logger.warning(f"{symbol} [平仓] 异常后检查:币安持仓已为0,已清理本地记录") @@ -2895,14 +2895,14 @@ class PositionManager: f"{symbol} [状态同步] ❌ 获取当前价格时KeyError: {key_error}, " f"ticker数据: {ticker if 'ticker' in locals() else 'N/A'}" ) - continue + continue except Exception as ticker_error: logger.warning( f"{symbol} [状态同步] 获取当前价格失败: " f"错误类型={type(ticker_error).__name__}, 错误消息={str(ticker_error)}," f"将保留open状态等待下次同步" ) - continue + continue # 计算盈亏(确保所有值都是float类型,避免Decimal类型问题) try: @@ -2928,12 +2928,12 @@ class PositionManager: 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 + 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} [状态同步] 盈亏计算: " @@ -3002,7 +3002,7 @@ class PositionManager: # 计算持仓时间和亏损比例(用于特征判断) entry_time = trade.get("entry_time") - duration_minutes = None + duration_minutes = None if entry_time and exit_time_ts: try: duration_minutes = (exit_time_ts - int(entry_time)) / 60.0 @@ -3140,8 +3140,8 @@ class PositionManager: 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}") + except Exception as e: + logger.debug(f"计算持仓持续时间失败: {e}") strategy_type = 'trend_following' # 默认策略类型 @@ -3461,29 +3461,29 @@ class PositionManager: ) 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' - + 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})" - ) + logger.info( + f"{symbol} [状态同步] 检测到手动开仓,创建数据库记录... " + f"({side} {quantity:.4f} @ {entry_price:.4f})" + ) # 尽量从币安成交取 entry_order_id 与真实开仓时间(limit=100、空时重试一次) entry_order_id = None entry_time_ts = None @@ -3510,105 +3510,100 @@ class PositionManager: except Exception: pass # 创建数据库记录(显式传入 account_id、真实开仓时间) - trade_id = Trade.create( - symbol=symbol, - side=side, - quantity=quantity, - entry_price=entry_price, - leverage=binance_position.get('leverage', 10), - entry_reason='manual_entry', # 标记为手动开仓 + 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"{symbol} [状态同步] ✓ 数据库记录已创建 (ID: {trade_id})") - - # 创建本地持仓记录(用于监控) - ticker = await self.client.get_ticker_24h(symbol) - current_price = ticker['price'] if ticker else entry_price - # ---------- 手动开仓补建 SL/TP 顺序:先读交易所 → 决定 SL/TP → 再写 position_info ---------- - # 本分支不调用 _ensure_exchange_sltp_orders,仅写入 active_positions;后续由监控在价格推送时可能调用。 - # 若此处用 risk_manager 初始止损写 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) - # ⚠️ 关键修复:配置值格式转换(兼容百分比形式和比例形式) + ) + + logger.info(f"{symbol} [状态同步] ✓ 数据库记录已创建 (ID: {trade_id})") + + # 创建本地持仓记录(用于监控) + 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) - # ⚠️ 关键修复:配置值格式转换(兼容百分比形式和比例形式) + 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 - # 如果配置中没有设置止盈,则使用止损的2倍作为默认 - 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, # 手动开仓,无法计算涨跌幅 + 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, + '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) + } + + 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} [状态同步] ✓ 手动开仓同步完成") - logger.info(f"{symbol} [状态同步] ✓ 手动开仓同步完成") - - except Exception as e: - logger.error(f"{symbol} [状态同步] ❌ 处理手动开仓失败: {e}") - import traceback - logger.error(f" 错误详情:\n{traceback.format_exc()}") + 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: @@ -3732,24 +3727,24 @@ class PositionManager: stop_loss_price = None take_profit_price = None 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) + 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) + 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: + 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_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 - ) + 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 = { @@ -3801,7 +3796,7 @@ class PositionManager: except Exception: mp = None await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=mp or current_price) - except Exception as e: + except Exception as e: logger.warning(f"{symbol} 补挂币安止盈止损失败(不影响监控): {e}") # 重启后立即按当前价做一次保本/移动止损检查并同步,不依赖首条 WS 推送 try: @@ -4144,38 +4139,38 @@ class PositionManager: 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')) + new_stop_loss = max(new_stop_loss, self._breakeven_stop_price(entry_price, 'BUY')) if new_stop_loss > position_info['stopLoss']: position_info['stopLoss'] = new_stop_loss logger.info( - f"[账号{self.account_id}] {symbol} [实时监控] 移动止损更新: {new_stop_loss:.4f} " + f"[账号{self.account_id}] {symbol} [实时监控] 移动止损更新: {new_stop_loss:.4f} " f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)" - ) - try: - await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float) - logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所") - except Exception as sync_e: - logger.warning( - f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}", - exc_info=False, ) + try: + await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float) + logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所") + except Exception as sync_e: + logger.warning( + f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}", + exc_info=False, + ) else: # SELL - new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity - new_stop_loss = min(new_stop_loss, self._breakeven_stop_price(entry_price, 'SELL')) - if new_stop_loss > position_info['stopLoss'] and pnl_amount > 0: + new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity + new_stop_loss = min(new_stop_loss, self._breakeven_stop_price(entry_price, 'SELL')) + if new_stop_loss > position_info['stopLoss'] and pnl_amount > 0: position_info['stopLoss'] = new_stop_loss logger.info( - f"[账号{self.account_id}] {symbol} [实时监控] 移动止损更新: {new_stop_loss:.4f} " + f"[账号{self.account_id}] {symbol} [实时监控] 移动止损更新: {new_stop_loss:.4f} " f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)" - ) - try: - await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float) - logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所") - except Exception as sync_e: - logger.warning( - f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}", - exc_info=False, ) + try: + await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float) + logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所") + except Exception as sync_e: + logger.warning( + f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}", + exc_info=False, + ) # 检查止损(基于保证金收益比) # ⚠️ 重要:止损检查应该在时间锁之前,止损必须立即执行