This commit is contained in:
薇薇安 2026-02-25 23:07:14 +08:00
parent f3ce4d5d11
commit ac022bd62a
2 changed files with 47 additions and 47 deletions

View File

@ -758,9 +758,9 @@ async def fetch_realtime_positions(account_id: int):
matched = None matched = None
for db_trade in db_trades: for db_trade in db_trades:
try: 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 matched = db_trade
break break
except Exception: except Exception:
continue continue
if matched is None: 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: if not nonzero_positions:
try: try:
positions = await client.get_open_positions() positions = await client.get_open_positions()
position = next((p for p in positions if p['symbol'] == symbol and float(p['positionAmt']) != 0), None) position = next((p for p in positions if p['symbol'] == symbol and float(p['positionAmt']) != 0), None)
if position: if position:
nonzero_positions = [(float(position["positionAmt"]), {"positionAmt": position["positionAmt"]})] nonzero_positions = [(float(position["positionAmt"]), {"positionAmt": position["positionAmt"]})]
except Exception: 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 dual_side is None:
if any(isinstance(p, dict) and (p.get("positionSide") in ("LONG", "SHORT")) for _, p in nonzero_positions): if any(isinstance(p, dict) and (p.get("positionSide") in ("LONG", "SHORT")) for _, p in nonzero_positions):
dual_side = True dual_side = True
else: else:
dual_side = False dual_side = False
logger.info(f"{symbol} 持仓模式: {'HEDGE(对冲)' if dual_side else 'ONE-WAY(单向)'}") 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") oid = order.get("orderId")
if oid: if oid:
order_ids.append(oid) order_ids.append(oid)
except Exception as order_error: except Exception as order_error:
error_msg = f"{symbol} 平仓失败:下单异常 - {str(order_error)}" error_msg = f"{symbol} 平仓失败:下单异常 - {str(order_error)}"
logger.error(error_msg) logger.error(error_msg)
logger.error(f" 错误类型: {type(order_error).__name__}") logger.error(f" 错误类型: {type(order_error).__name__}")
import traceback import traceback
logger.error(f" 完整错误堆栈:\n{traceback.format_exc()}") logger.error(f" 完整错误堆栈:\n{traceback.format_exc()}")
raise HTTPException(status_code=500, detail=error_msg) raise HTTPException(status_code=500, detail=error_msg)
if not orders: if not orders:
raise HTTPException(status_code=400, detail=f"{symbol} 无可平仓的有效仓位数量调整后为0或无持仓") 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: try:
# 1. 获取价格 # 1. 获取价格
order_info = await client.client.futures_get_order(symbol=symbol, orderId=oid) 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)) p = float(order_info.get('avgPrice', 0)) or float(order_info.get('price', 0))
if p <= 0 and order_info.get('fills'): if p <= 0 and order_info.get('fills'):
total_qty = 0 total_qty = 0
total_value = 0 total_value = 0
for fill in order_info.get('fills', []): for fill in order_info.get('fills', []):
qty = float(fill.get('qty', 0)) qty = float(fill.get('qty', 0))
price = float(fill.get('price', 0)) price = float(fill.get('price', 0))
total_qty += qty total_qty += qty
total_value += qty * price total_value += qty * price
if total_qty > 0: if total_qty > 0:
p = total_value / total_qty p = total_value / total_qty
if p > 0: if p > 0:
exit_prices[oid] = p exit_prices[oid] = p
# 2. 计算佣金和实际盈亏(从 recent_trades 匹配) # 2. 计算佣金和实际盈亏(从 recent_trades 匹配)
related_trades = [t for t in recent_trades if str(t.get('orderId')) == str(oid)] related_trades = [t for t in recent_trades if str(t.get('orderId')) == str(oid)]
if related_trades: 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_realized_pnl += float(t.get('realizedPnl', 0))
total_commission += float(t.get('commission', 0)) total_commission += float(t.get('commission', 0))
commission_assets.add(t.get('commissionAsset')) commission_assets.add(t.get('commissionAsset'))
exit_realized_pnls[oid] = total_realized_pnl exit_realized_pnls[oid] = total_realized_pnl
exit_commissions[oid] = total_commission exit_commissions[oid] = total_commission
exit_commission_assets[oid] = "/".join(commission_assets) if commission_assets else None 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}") 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() used_order_ids = set()
for trade in open_trades: for trade in open_trades:
try: try:
entry_price = float(trade['entry_price']) entry_price = float(trade['entry_price'])
trade_quantity = float(trade['quantity']) trade_quantity = float(trade['quantity'])
except Exception: except Exception:
continue 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 exit_price = fallback_exit_price or entry_price
# 计算盈亏(数据库侧依旧按名义盈亏;收益率展示用保证金口径在前端/统计里另算) # 计算盈亏(数据库侧依旧按名义盈亏;收益率展示用保证金口径在前端/统计里另算)
if trade['side'] == 'BUY': if trade['side'] == 'BUY':
pnl = (exit_price - entry_price) * trade_quantity pnl = (exit_price - entry_price) * trade_quantity
pnl_percent = ((exit_price - entry_price) / entry_price) * 100 pnl_percent = ((exit_price - entry_price) / entry_price) * 100
else: else:
pnl = (entry_price - exit_price) * trade_quantity pnl = (entry_price - exit_price) * trade_quantity
pnl_percent = ((entry_price - exit_price) / entry_price) * 100 pnl_percent = ((entry_price - exit_price) / entry_price) * 100
Trade.update_exit( Trade.update_exit(
trade_id=trade['id'], trade_id=trade['id'],
exit_price=exit_price, exit_price=exit_price,
exit_reason='manual', exit_reason='manual',
pnl=pnl, pnl=pnl,
pnl_percent=pnl_percent, pnl_percent=pnl_percent,
exit_order_id=chosen_oid, exit_order_id=chosen_oid,
realized_pnl=exit_realized_pnls.get(chosen_oid), realized_pnl=exit_realized_pnls.get(chosen_oid),
commission=exit_commissions.get(chosen_oid), commission=exit_commissions.get(chosen_oid),
commission_asset=exit_commission_assets.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"✓ 已更新数据库记录 trade_id={trade['id']} order_id={chosen_oid} (盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%)")
logger.info(f"{symbol} 平仓成功") logger.info(f"{symbol} 平仓成功")
@ -1839,21 +1839,21 @@ async def sync_positions(
pass pass
if not exit_price or exit_price <= 0: if not exit_price or exit_price <= 0:
ticker = await client.get_ticker_24h(symbol) ticker = await client.get_ticker_24h(symbol)
exit_price = float(ticker['price']) if ticker else entry_price exit_price = float(ticker['price']) if ticker else entry_price
# 计算盈亏 # 计算盈亏
if trade['side'] == 'BUY': if trade['side'] == 'BUY':
pnl = (exit_price - entry_price) * quantity pnl = (exit_price - entry_price) * quantity
else: else:
pnl = (entry_price - exit_price) * quantity pnl = (entry_price - exit_price) * quantity
# 计算基于保证金的盈亏百分比 # 计算基于保证金的盈亏百分比
leverage = float(trade.get('leverage', 10)) leverage = float(trade.get('leverage', 10))
entry_value = entry_price * quantity entry_value = entry_price * quantity
margin = entry_value / leverage if leverage > 0 else entry_value margin = entry_value / leverage if leverage > 0 else entry_value
pnl_percent_margin = (pnl / margin * 100) if margin > 0 else 0 pnl_percent_margin = (pnl / margin * 100) if margin > 0 else 0
# 从币安成交获取手续费与实际盈亏,保证统计与币安一致 # 从币安成交获取手续费与实际盈亏,保证统计与币安一致
sync_commission = None sync_commission = None
sync_commission_asset = None sync_commission_asset = None

View File

@ -571,8 +571,8 @@ async def sync_trades_from_binance(
try: try:
# 这是平仓订单close_orders 已经筛选出 reduceOnly=True 的订单) # 这是平仓订单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说明已完整同步跳过 # 如果已有 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"): if existing_trade and existing_trade.get("exit_order_id") and existing_trade.get("exit_reason") not in (None, "", "sync"):
skipped_existing += 1 skipped_existing += 1