diff --git a/backend/api/routes/account.py b/backend/api/routes/account.py index 16828a1..af1ad3f 100644 --- a/backend/api/routes/account.py +++ b/backend/api/routes/account.py @@ -333,19 +333,18 @@ async def close_position(symbol: str): # 导入必要的模块 try: from binance_client import BinanceClient - from risk_manager import RiskManager - from position_manager import PositionManager logger.info("✓ 成功导入交易系统模块") except ImportError as import_error: logger.warning(f"首次导入失败: {import_error},尝试从trading_system路径导入") trading_system_path = project_root / 'trading_system' sys.path.insert(0, str(trading_system_path)) from binance_client import BinanceClient - from risk_manager import RiskManager - from position_manager import PositionManager logger.info("✓ 从trading_system路径导入成功") - # 创建客户端和仓位管理器 + # 导入数据库模型 + from database.models import Trade + + # 创建客户端 logger.info(f"创建BinanceClient (testnet={use_testnet})...") client = BinanceClient( api_key=api_key, @@ -358,49 +357,136 @@ async def close_position(symbol: str): logger.info("✓ 币安API连接成功") try: - logger.info("初始化RiskManager和PositionManager...") - risk_manager = RiskManager(client) - position_manager = PositionManager(client, risk_manager) - logger.info("✓ 管理器初始化成功") - - # 先检查币安是否有持仓 + # 检查币安是否有持仓 logger.info(f"检查 {symbol} 在币安的持仓状态...") positions = await client.get_open_positions() - binance_has_position = any(p['symbol'] == symbol and float(p['positionAmt']) != 0 for p in positions) + position = next((p for p in positions if p['symbol'] == symbol and float(p['positionAmt']) != 0), None) - if binance_has_position: - position_info = next((p for p in positions if p['symbol'] == symbol and float(p['positionAmt']) != 0), None) - position_amt = float(position_info['positionAmt']) if position_info else 0 - logger.info(f"✓ 币安账户中有 {symbol} 持仓: {position_amt:.4f}") - else: - logger.info(f"✓ 币安账户中没有 {symbol} 持仓") - - # 执行平仓(reason='manual' 表示手动平仓) - logger.info(f"开始执行平仓操作: {symbol}...") - success = await position_manager.close_position(symbol, reason='manual') - - if success: - logger.info(f"✓ {symbol} 平仓成功") + if not position: + logger.warning(f"⚠ {symbol} 币安账户中没有持仓,可能已被平仓") + # 检查数据库中是否有未平仓的记录,如果有则更新 + open_trades = Trade.get_by_symbol(symbol, status='open') + if open_trades: + trade = open_trades[0] + # 获取当前价格作为平仓价格 + ticker = await client.get_ticker_24h(symbol) + exit_price = float(ticker['price']) if ticker else float(trade['entry_price']) + + # 计算盈亏 + entry_price = float(trade['entry_price']) + quantity = float(trade['quantity']) + if trade['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='manual', + pnl=pnl, + pnl_percent=pnl_percent, + exit_order_id=None + ) + logger.info(f"✓ 已更新数据库记录(币安无持仓但数据库有记录)") + return { - "message": f"{symbol} 平仓成功", + "message": f"{symbol} 平仓操作完成(币安账户中没有持仓,可能已被平仓)", "symbol": symbol, "status": "closed" } - else: - # 根据币安是否有持仓来判断失败原因 - if binance_has_position: - # 币安有持仓但下单失败 - error_msg = f"{symbol} 平仓失败:币安账户中有持仓,但下单失败,请检查日志或稍后重试" - logger.error(error_msg) - raise HTTPException(status_code=500, detail=error_msg) + + # 获取持仓信息 + position_amt = float(position['positionAmt']) + logger.info(f"✓ 币安账户中有 {symbol} 持仓: {position_amt:.4f}") + + # 确定平仓方向(与持仓相反) + side = 'SELL' if position_amt > 0 else 'BUY' + quantity = abs(position_amt) + + logger.info(f"开始执行平仓操作: {symbol} {side} {quantity:.4f} @ MARKET...") + + # 直接调用 BinanceClient 平仓(使用 reduceOnly=True) + order = await client.place_order( + symbol=symbol, + side=side, + quantity=quantity, + order_type='MARKET', + reduce_only=True + ) + + if not order: + error_msg = f"{symbol} 平仓失败:下单返回 None,请检查日志" + logger.error(error_msg) + raise HTTPException(status_code=500, detail=error_msg) + + order_id = order.get('orderId') + logger.info(f"✓ {symbol} 平仓订单已提交 (订单ID: {order_id})") + + # 等待订单成交,获取实际成交价格 + import asyncio + await asyncio.sleep(1) + + # 获取订单详情 + exit_price = None + try: + order_info = await client.client.futures_get_order(symbol=symbol, orderId=order_id) + if order_info: + exit_price = float(order_info.get('avgPrice', 0)) or float(order_info.get('price', 0)) + if exit_price <= 0 and order_info.get('fills'): + # 计算加权平均成交价格 + total_qty = 0 + total_value = 0 + for fill in order_info.get('fills', []): + qty = float(fill.get('qty', 0)) + price = float(fill.get('price', 0)) + total_qty += qty + total_value += qty * price + if total_qty > 0: + exit_price = total_value / total_qty + except Exception as e: + logger.warning(f"获取订单详情失败: {e},使用当前价格") + + # 如果无法获取订单价格,使用当前价格 + if not exit_price or exit_price <= 0: + ticker = await client.get_ticker_24h(symbol) + exit_price = float(ticker['price']) if ticker else float(position.get('entryPrice', 0)) + + # 更新数据库记录 + open_trades = Trade.get_by_symbol(symbol, status='open') + if open_trades: + trade = open_trades[0] + entry_price = float(trade['entry_price']) + trade_quantity = float(trade['quantity']) + + # 计算盈亏 + if trade['side'] == 'BUY': + pnl = (exit_price - entry_price) * trade_quantity + pnl_percent = ((exit_price - entry_price) / entry_price) * 100 else: - # 币安没有持仓,可能是数据库已更新 - logger.warning(f"⚠ {symbol} 币安账户中没有持仓,可能已被平仓或数据库已更新") - return { - "message": f"{symbol} 平仓操作完成(币安账户中没有持仓,可能已被平仓)", - "symbol": symbol, - "status": "closed" - } + pnl = (entry_price - exit_price) * trade_quantity + pnl_percent = ((entry_price - exit_price) / entry_price) * 100 + + # 更新数据库 + Trade.update_exit( + trade_id=trade['id'], + exit_price=exit_price, + exit_reason='manual', + pnl=pnl, + pnl_percent=pnl_percent, + exit_order_id=order_id + ) + logger.info(f"✓ 已更新数据库记录 (盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%)") + + logger.info(f"✓ {symbol} 平仓成功") + return { + "message": f"{symbol} 平仓成功", + "symbol": symbol, + "status": "closed" + } finally: logger.info("断开币安API连接...") await client.disconnect()