From dee37d3c7882e7078684d8e40ea128aec70db4e6 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 19:07:29 +0800 Subject: [PATCH] a --- trading_system/position_manager.py | 179 +++++++++++++++++++++++++++-- trading_system/strategy.py | 10 +- 2 files changed, 179 insertions(+), 10 deletions(-) diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 5bbb012..be81661 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -206,17 +206,20 @@ class PositionManager: logger.error(f"开仓失败 {symbol}: {e}") return None - async def close_position(self, symbol: str) -> bool: + async def close_position(self, symbol: str, reason: str = 'manual') -> bool: """ 平仓 Args: symbol: 交易对 + reason: 平仓原因(manual, stop_loss, take_profit, trailing_stop, sync) Returns: 是否成功 """ try: + logger.info(f"{symbol} [平仓] 开始平仓操作 (原因: {reason})") + # 获取当前持仓 positions = await self.client.get_open_positions() position = next( @@ -225,7 +228,43 @@ class PositionManager: ) if not position: - logger.warning(f"{symbol} 没有持仓") + logger.warning(f"{symbol} [平仓] 币安账户中没有持仓,可能已被平仓") + # 即使币安没有持仓,也要更新数据库状态 + 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: + try: + logger.info(f"{symbol} [平仓] 更新数据库状态为已平仓 (ID: {trade_id})...") + # 获取当前价格作为平仓价格 + ticker = await self.client.get_ticker_24h(symbol) + exit_price = ticker['price'] if ticker else position_info['entryPrice'] + + entry_price = position_info['entryPrice'] + quantity = 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 + + Trade.update_exit( + trade_id=trade_id, + exit_price=exit_price, + exit_reason=reason, + pnl=pnl, + pnl_percent=pnl_percent + ) + logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新") + except Exception as e: + logger.error(f"{symbol} [平仓] ❌ 更新数据库状态失败: {e}") + + # 清理本地记录 + await self._stop_position_monitoring(symbol) + if symbol in self.active_positions: + del self.active_positions[symbol] + return False # 确定平仓方向(与开仓相反) @@ -233,6 +272,11 @@ class PositionManager: side = 'SELL' if position_amt > 0 else 'BUY' quantity = abs(position_amt) + logger.info( + f"{symbol} [平仓] 下单信息: {side} {quantity:.4f} @ MARKET " + f"(持仓数量: {position_amt:.4f})" + ) + # 平仓 order = await self.client.place_order( symbol=symbol, @@ -242,6 +286,7 @@ class PositionManager: ) if order: + logger.info(f"{symbol} [平仓] ✓ 平仓订单已提交 (订单ID: {order.get('orderId', 'N/A')})") # 获取平仓价格 ticker = await self.client.get_ticker_24h(symbol) if not ticker: @@ -269,11 +314,14 @@ class PositionManager: Trade.update_exit( trade_id=trade_id, exit_price=exit_price, - exit_reason='manual', + exit_reason=reason, pnl=pnl, pnl_percent=pnl_percent ) - logger.info(f"✓ {symbol} 平仓记录已更新到数据库 (盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%)") + logger.info( + f"{symbol} [平仓] ✓ 数据库记录已更新 " + f"(盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%, 原因: {reason})" + ) except Exception as e: logger.error(f"❌ 更新平仓记录到数据库失败: {e}") logger.error(f" 错误类型: {type(e).__name__}") @@ -293,13 +341,18 @@ class PositionManager: if symbol in self.active_positions: del self.active_positions[symbol] - logger.info(f"平仓成功: {symbol} {side} {quantity}") + logger.info( + f"{symbol} [平仓] ✓ 平仓完成: {side} {quantity:.4f} @ {exit_price:.4f} " + f"(原因: {reason})" + ) return True return False except Exception as e: - logger.error(f"平仓失败 {symbol}: {e}") + logger.error(f"{symbol} [平仓] ❌ 平仓失败: {e}") + import traceback + logger.error(f" 错误详情:\n{traceback.format_exc()}") return False async def check_stop_loss_take_profit(self) -> List[str]: @@ -516,6 +569,98 @@ class PositionManager: logger.error(f"获取持仓摘要失败: {e}") return {} + async def sync_positions_with_binance(self): + """ + 同步币安实际持仓状态与数据库状态 + 检查哪些持仓在数据库中还是open状态,但在币安已经不存在了 + """ + if not DB_AVAILABLE or not Trade: + logger.debug("数据库不可用,跳过持仓状态同步") + return + + try: + logger.info("开始同步币安持仓状态与数据库...") + + # 1. 获取币安实际持仓 + binance_positions = await self.client.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') + 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 '无'})") + + # 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: + trades = Trade.get_by_symbol(symbol, status='open') + for trade in trades: + trade_id = trade['id'] + try: + logger.info(f"{symbol} [状态同步] 更新交易记录状态 (ID: {trade_id})...") + + # 获取当前价格作为平仓价格 + ticker = await self.client.get_ticker_24h(symbol) + exit_price = ticker['price'] if ticker else trade['entry_price'] + + # 计算盈亏 + entry_price = trade['entry_price'] + quantity = trade['quantity'] + if trade['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 + + Trade.update_exit( + trade_id=trade_id, + exit_price=exit_price, + exit_reason='sync', # 标记为同步更新 + pnl=pnl, + pnl_percent=pnl_percent + ) + + 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: + logger.error(f"{symbol} [状态同步] ❌ 更新失败 (ID: {trade_id}): {e}") + else: + logger.info("✓ 持仓状态同步完成,数据库与币安状态一致") + + # 5. 检查币安有但数据库没有记录的持仓(可能是手动开仓的) + missing_in_db = binance_symbols - db_open_symbols + if missing_in_db: + logger.info( + f"发现 {len(missing_in_db)} 个持仓在币安存在但数据库中没有记录: " + f"{', '.join(missing_in_db)} (可能是手动开仓)" + ) + + 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实时监控 @@ -747,6 +892,15 @@ class PositionManager: # 如果触发止损止盈,执行平仓 if should_close: + logger.info( + f"{symbol} [自动平仓] 开始执行平仓操作 | " + f"原因: {exit_reason} | " + f"入场价: {entry_price:.4f} | " + f"当前价: {current_price:.4f} | " + f"盈亏: {pnl_percent:.2f}% | " + f"数量: {quantity:.4f}" + ) + # 更新数据库 if DB_AVAILABLE and Trade: trade_id = position_info.get('tradeId') @@ -757,6 +911,7 @@ class PositionManager: else: # SELL pnl = (entry_price - current_price) * quantity + logger.info(f"{symbol} [自动平仓] 更新数据库记录 (ID: {trade_id})...") Trade.update_exit( trade_id=trade_id, exit_price=current_price, @@ -764,8 +919,16 @@ class PositionManager: pnl=pnl, pnl_percent=pnl_percent ) + logger.info(f"{symbol} [自动平仓] ✓ 数据库记录已更新 (盈亏: {pnl:.2f} USDT)") except Exception as e: - logger.warning(f"更新{exit_reason}记录失败: {e}") + logger.error(f"{symbol} [自动平仓] ❌ 更新数据库记录失败: {e}") + import traceback + logger.error(f" 错误详情:\n{traceback.format_exc()}") # 执行平仓(注意:这里会停止监控,所以先更新数据库) - await self.close_position(symbol) \ No newline at end of file + logger.info(f"{symbol} [自动平仓] 正在执行平仓订单...") + success = await self.close_position(symbol, reason=exit_reason) + if success: + logger.info(f"{symbol} [自动平仓] ✓ 平仓成功完成") + else: + logger.error(f"{symbol} [自动平仓] ❌ 平仓失败") \ No newline at end of file diff --git a/trading_system/strategy.py b/trading_system/strategy.py index bf69866..725dd11 100644 --- a/trading_system/strategy.py +++ b/trading_system/strategy.py @@ -154,13 +154,19 @@ class TradingStrategy: # 避免同时处理太多交易对 await asyncio.sleep(1) - # 3. 检查止损止盈(作为备用检查,WebSocket实时监控是主要方式) + # 3. 同步币安实际持仓状态与数据库(定期同步,确保状态一致) + try: + await self.position_manager.sync_positions_with_binance() + except Exception as e: + logger.warning(f"持仓状态同步失败: {e}") + + # 4. 检查止损止盈(作为备用检查,WebSocket实时监控是主要方式) # 注意:如果启用了WebSocket实时监控,这里主要是作为备用检查 closed = await self.position_manager.check_stop_loss_take_profit() if closed: logger.info(f"定时检查触发平仓: {', '.join(closed)}") - # 4. 打印持仓摘要并记录账户快照 + # 5. 打印持仓摘要并记录账户快照 summary = await self.position_manager.get_position_summary() if summary: logger.info(