diff --git a/backend/api/routes/account.py b/backend/api/routes/account.py index 6750829..6e0ce3d 100644 --- a/backend/api/routes/account.py +++ b/backend/api/routes/account.py @@ -758,9 +758,9 @@ async def fetch_realtime_positions(account_id: int): matched = None for db_trade in db_trades: try: - if abs(float(db_trade.get('entry_price', 0)) - entry_price) < 0.01: + if abs(float(db_trade.get('entry_price', 0)) - entry_price) < 0.01: matched = db_trade - break + break except Exception: continue if matched is None: @@ -938,8 +938,8 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id)) # 兼容旧逻辑:如果原始接口异常,回退到封装方法 if not nonzero_positions: try: - positions = await client.get_open_positions() - position = next((p for p in positions if p['symbol'] == symbol and float(p['positionAmt']) != 0), None) + positions = await client.get_open_positions() + position = next((p for p in positions if p['symbol'] == symbol and float(p['positionAmt']) != 0), None) if position: nonzero_positions = [(float(position["positionAmt"]), {"positionAmt": position["positionAmt"]})] except Exception: @@ -1013,7 +1013,7 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id)) if dual_side is None: if any(isinstance(p, dict) and (p.get("positionSide") in ("LONG", "SHORT")) for _, p in nonzero_positions): dual_side = True - else: + else: dual_side = False logger.info(f"{symbol} 持仓模式: {'HEDGE(对冲)' if dual_side else 'ONE-WAY(单向)'}") @@ -1067,13 +1067,13 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id)) oid = order.get("orderId") if oid: order_ids.append(oid) - except Exception as order_error: - error_msg = f"{symbol} 平仓失败:下单异常 - {str(order_error)}" - logger.error(error_msg) - logger.error(f" 错误类型: {type(order_error).__name__}") - import traceback - logger.error(f" 完整错误堆栈:\n{traceback.format_exc()}") - raise HTTPException(status_code=500, detail=error_msg) + except Exception as order_error: + error_msg = f"{symbol} 平仓失败:下单异常 - {str(order_error)}" + logger.error(error_msg) + logger.error(f" 错误类型: {type(order_error).__name__}") + import traceback + logger.error(f" 完整错误堆栈:\n{traceback.format_exc()}") + raise HTTPException(status_code=500, detail=error_msg) if not orders: raise HTTPException(status_code=400, detail=f"{symbol} 无可平仓的有效仓位(数量调整后为0或无持仓)") @@ -1103,21 +1103,21 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id)) try: # 1. 获取价格 order_info = await client.client.futures_get_order(symbol=symbol, orderId=oid) - if order_info: + if order_info: p = float(order_info.get('avgPrice', 0)) or float(order_info.get('price', 0)) if p <= 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: + 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: p = total_value / total_qty if p > 0: exit_prices[oid] = p - + # 2. 计算佣金和实际盈亏(从 recent_trades 匹配) related_trades = [t for t in recent_trades if str(t.get('orderId')) == str(oid)] if related_trades: @@ -1128,11 +1128,11 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id)) total_realized_pnl += float(t.get('realizedPnl', 0)) total_commission += float(t.get('commission', 0)) commission_assets.add(t.get('commissionAsset')) - + exit_realized_pnls[oid] = total_realized_pnl exit_commissions[oid] = total_commission exit_commission_assets[oid] = "/".join(commission_assets) if commission_assets else None - except Exception as e: + except Exception as e: logger.warning(f"获取订单详情失败 (orderId={oid}): {e}") # 兜底:如果无法获取订单价格,使用当前价格 @@ -1150,8 +1150,8 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id)) used_order_ids = set() for trade in open_trades: try: - entry_price = float(trade['entry_price']) - trade_quantity = float(trade['quantity']) + entry_price = float(trade['entry_price']) + trade_quantity = float(trade['quantity']) except Exception: continue @@ -1171,24 +1171,24 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id)) exit_price = fallback_exit_price or entry_price # 计算盈亏(数据库侧依旧按名义盈亏;收益率展示用保证金口径在前端/统计里另算) - if trade['side'] == 'BUY': - pnl = (exit_price - entry_price) * trade_quantity - pnl_percent = ((exit_price - entry_price) / entry_price) * 100 - else: - 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, + if trade['side'] == 'BUY': + pnl = (exit_price - entry_price) * trade_quantity + pnl_percent = ((exit_price - entry_price) / entry_price) * 100 + else: + 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=chosen_oid, realized_pnl=exit_realized_pnls.get(chosen_oid), commission=exit_commissions.get(chosen_oid), commission_asset=exit_commission_assets.get(chosen_oid) - ) + ) logger.info(f"✓ 已更新数据库记录 trade_id={trade['id']} order_id={chosen_oid} (盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%)") logger.info(f"✓ {symbol} 平仓成功") @@ -1839,21 +1839,21 @@ async def sync_positions( pass if not exit_price or exit_price <= 0: - ticker = await client.get_ticker_24h(symbol) - exit_price = float(ticker['price']) if ticker else entry_price - + ticker = await client.get_ticker_24h(symbol) + exit_price = float(ticker['price']) if ticker else entry_price + # 计算盈亏 if trade['side'] == 'BUY': pnl = (exit_price - entry_price) * quantity else: pnl = (entry_price - exit_price) * quantity - + # 计算基于保证金的盈亏百分比 leverage = float(trade.get('leverage', 10)) 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 - + # 从币安成交获取手续费与实际盈亏,保证统计与币安一致 sync_commission = None sync_commission_asset = None diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index 15c794f..9c74428 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -571,8 +571,8 @@ async def sync_trades_from_binance( try: # 这是平仓订单(close_orders 已经筛选出 reduceOnly=True 的订单) - # 首先检查是否已经通过订单号同步过(避免重复) - existing_trade = Trade.get_by_exit_order_id(order_id) + # 首先检查是否已经通过订单号同步过(避免重复) + existing_trade = Trade.get_by_exit_order_id(order_id) # 如果已有 exit_order_id 且 exit_reason 不是 sync,说明已完整同步,跳过 if existing_trade and existing_trade.get("exit_order_id") and existing_trade.get("exit_reason") not in (None, "", "sync"): skipped_existing += 1