1
This commit is contained in:
parent
f3ce4d5d11
commit
ac022bd62a
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user