From d0d40b8f836ddff1c71992bfe268b23b5dc3e16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Wed, 14 Jan 2026 20:04:07 +0800 Subject: [PATCH] a --- trading_system/position_manager.py | 186 +++++++++++++++++++++++++++-- trading_system/strategy.py | 2 + 2 files changed, 179 insertions(+), 9 deletions(-) diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 0487ca6..d290f63 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -751,9 +751,55 @@ class PositionManager: # 获取当前所有持仓 positions = await self.client.get_open_positions() + binance_symbols = {p['symbol'] for p in positions} + active_symbols = set(self.active_positions.keys()) + + logger.info(f"币安持仓: {len(binance_symbols)} 个 ({', '.join(binance_symbols) if binance_symbols else '无'})") + logger.info(f"本地持仓记录: {len(active_symbols)} 个 ({', '.join(active_symbols) if active_symbols else '无'})") + + # 为所有币安持仓启动监控(即使不在active_positions中,可能是手动开仓的) for position in positions: symbol = position['symbol'] - if symbol not in self._monitor_tasks and symbol in self.active_positions: + if symbol not in self._monitor_tasks: + # 如果不在active_positions中,先创建记录 + if symbol not in self.active_positions: + logger.warning(f"{symbol} 在币安有持仓但不在本地记录中,可能是手动开仓,尝试创建记录...") + # 这里会通过sync_positions_with_binance来处理,但先启动监控 + 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 + + stop_loss_price = self.risk_manager.get_stop_loss_price(entry_price, side) + take_profit_price = self.risk_manager.get_take_profit_price(entry_price, side) + + 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': position.get('leverage', 10), + 'entryReason': 'manual_entry_temp', + 'atr': None, + 'maxProfit': 0.0, + 'trailingStopActivated': False + } + self.active_positions[symbol] = position_info + logger.info(f"{symbol} 已创建临时持仓记录用于监控") + except Exception as e: + logger.error(f"{symbol} 创建临时持仓记录失败: {e}") + await self._start_position_monitoring(symbol) logger.info(f"已启动 {len(self._monitor_tasks)} 个持仓的实时监控") @@ -952,20 +998,52 @@ class PositionManager: # 检查止盈 if not should_close: - take_profit = position_info['takeProfit'] - if position_info['side'] == 'BUY' and current_price >= take_profit: + take_profit = float(position_info['takeProfit']) + # 计算止盈百分比(用于诊断) + if position_info['side'] == 'BUY': + take_profit_pct = ((take_profit - entry_price) / entry_price) * 100 + else: # SELL + take_profit_pct = ((entry_price - take_profit) / entry_price) * 100 + + # 每5%盈利记录一次诊断日志(帮助排查问题) + # 使用更宽松的条件,避免因为浮点数精度问题导致日志不输出 + if pnl_percent >= 5.0: + # 每5%记录一次,但允许一些容差 + should_log = (int(pnl_percent) % 5 == 0) or (pnl_percent >= 10.0 and pnl_percent < 10.5) + if should_log: + trigger_condition = current_price_float >= take_profit if position_info['side'] == 'BUY' else current_price_float <= take_profit + logger.warning( + f"{symbol} [实时监控] 诊断: 盈利{pnl_percent:.2f}% | " + f"当前价: {current_price_float:.4f} | " + f"入场价: {entry_price:.4f} | " + f"止盈价: {take_profit:.4f} ({take_profit_pct:.2f}%) | " + f"方向: {position_info['side']} | " + f"是否触发: {trigger_condition} | " + f"价格差: {abs(current_price_float - take_profit):.4f} | " + f"监控状态: {'运行中' if symbol in self._monitor_tasks else '未启动'}" + ) + + # 如果盈利超过止盈目标但未触发,记录警告 + if pnl_percent > take_profit_pct and not trigger_condition: + logger.error( + f"{symbol} [实时监控] ⚠️ 异常: 盈利{pnl_percent:.2f}% > 止盈目标{take_profit_pct:.2f}%,但未触发平仓!" + ) + price_diff = current_price_float - take_profit if position_info['side'] == 'BUY' else take_profit - current_price_float + logger.error(f" 当前价: {current_price_float:.4f}, 止盈价: {take_profit:.4f}, 价格差: {price_diff:.4f}") + + if position_info['side'] == 'BUY' and current_price_float >= take_profit: should_close = True exit_reason = 'take_profit' logger.info( - f"{symbol} [实时监控] 触发止盈: {current_price:.4f} >= {take_profit:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" + f"{symbol} [实时监控] 触发止盈: {current_price_float:.4f} >= {take_profit:.4f} " + f"(盈亏: {pnl_percent:.2f}%, 止盈目标: {take_profit_pct:.2f}%)" ) - elif position_info['side'] == 'SELL' and current_price <= take_profit: + elif position_info['side'] == 'SELL' and current_price_float <= take_profit: should_close = True exit_reason = 'take_profit' logger.info( - f"{symbol} [实时监控] 触发止盈: {current_price:.4f} <= {take_profit:.4f} " - f"(盈亏: {pnl_percent:.2f}%)" + f"{symbol} [实时监控] 触发止盈: {current_price_float:.4f} <= {take_profit:.4f} " + f"(盈亏: {pnl_percent:.2f}%, 止盈目标: {take_profit_pct:.2f}%)" ) # 如果触发止损止盈,执行平仓 @@ -1009,4 +1087,94 @@ class PositionManager: if success: logger.info(f"{symbol} [自动平仓] ✓ 平仓成功完成") else: - logger.error(f"{symbol} [自动平仓] ❌ 平仓失败") \ No newline at end of file + logger.error(f"{symbol} [自动平仓] ❌ 平仓失败") + + 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.client.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()}") \ No newline at end of file diff --git a/trading_system/strategy.py b/trading_system/strategy.py index bd00400..788e01d 100644 --- a/trading_system/strategy.py +++ b/trading_system/strategy.py @@ -164,6 +164,8 @@ class TradingStrategy: # 3. 同步币安实际持仓状态与数据库(定期同步,确保状态一致) try: await self.position_manager.sync_positions_with_binance() + # 同步后,确保所有持仓都有监控(包括手动开仓的) + await self.position_manager.start_all_position_monitoring() except Exception as e: logger.warning(f"持仓状态同步失败: {e}")