This commit is contained in:
薇薇安 2026-02-03 09:48:37 +08:00
parent 76e6e5efd0
commit 7b8bcd758d
4 changed files with 132 additions and 17 deletions

View File

@ -989,8 +989,22 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id))
# 获取订单详情(可能多个订单,按订单号分别取价)
exit_prices = {}
exit_commissions = {}
exit_realized_pnls = {}
exit_commission_assets = {}
# 新增:获取最近成交记录以计算佣金和实际盈亏
try:
# 等待一小段时间确保成交记录已生成
await asyncio.sleep(1)
recent_trades = await client.get_recent_trades(symbol, limit=20)
except Exception as e:
logger.warning(f"获取最近成交记录失败: {e}")
recent_trades = []
for oid in order_ids:
try:
# 1. 获取价格
order_info = await client.client.futures_get_order(symbol=symbol, orderId=oid)
if order_info:
p = float(order_info.get('avgPrice', 0)) or float(order_info.get('price', 0))
@ -1006,6 +1020,21 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id))
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:
total_realized_pnl = 0.0
total_commission = 0.0
commission_assets = set()
for t in related_trades:
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:
logger.warning(f"获取订单详情失败 (orderId={oid}): {e}")
@ -1058,7 +1087,10 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id))
exit_reason='manual',
pnl=pnl,
pnl_percent=pnl_percent,
exit_order_id=chosen_oid
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}%)")

View File

@ -501,6 +501,9 @@ class Trade:
strategy_type=None,
duration_minutes=None,
exit_time_ts=None,
realized_pnl=None,
commission=None,
commission_asset=None,
):
"""更新平仓信息(使用北京时间)
@ -511,6 +514,9 @@ class Trade:
pnl: 盈亏
pnl_percent: 盈亏百分比
exit_order_id: 币安平仓订单号可选用于对账
realized_pnl: 币安实际结算盈亏可选
commission: 交易手续费可选
commission_asset: 手续费币种可选
注意如果 exit_order_id 已存在且属于其他交易记录会跳过更新 exit_order_id 以避免唯一约束冲突
"""
@ -520,14 +526,33 @@ class Trade:
except Exception:
exit_time = get_beijing_time()
# 准备额外字段更新 helper
def _append_extra_fields(fields, values):
if strategy_type is not None:
fields.append("strategy_type = %s")
values.append(strategy_type)
if duration_minutes is not None:
fields.append("duration_minutes = %s")
values.append(duration_minutes)
# 新增字段(检查是否存在)
if realized_pnl is not None and _table_has_column("trades", "realized_pnl"):
fields.append("realized_pnl = %s")
values.append(realized_pnl)
if commission is not None and _table_has_column("trades", "commission"):
fields.append("commission = %s")
values.append(commission)
if commission_asset is not None and _table_has_column("trades", "commission_asset"):
fields.append("commission_asset = %s")
values.append(commission_asset)
# 如果提供了 exit_order_id先检查是否已被其他交易记录使用
if exit_order_id is not None:
try:
existing_trade = Trade.get_by_exit_order_id(exit_order_id)
if existing_trade:
if existing_trade['id'] == trade_id:
# exit_order_id 属于当前交易记录:允许继续更新(比如补写 exit_reason / exit_time / duration
# 不需要提前 return
# exit_order_id 属于当前交易记录:允许继续更新
logger.debug(
f"交易记录 {trade_id} 的 exit_order_id {exit_order_id} 已存在,将继续更新其他字段"
)
@ -544,12 +569,7 @@ class Trade:
]
update_values = [exit_price, exit_time, exit_reason, pnl, pnl_percent]
if strategy_type is not None:
update_fields.append("strategy_type = %s")
update_values.append(strategy_type)
if duration_minutes is not None:
update_fields.append("duration_minutes = %s")
update_values.append(duration_minutes)
_append_extra_fields(update_fields, update_values)
update_values.append(trade_id)
db.execute_update(
@ -570,12 +590,7 @@ class Trade:
]
update_values = [exit_price, exit_time, exit_reason, pnl, pnl_percent, exit_order_id]
if strategy_type is not None:
update_fields.append("strategy_type = %s")
update_values.append(strategy_type)
if duration_minutes is not None:
update_fields.append("duration_minutes = %s")
update_values.append(duration_minutes)
_append_extra_fields(update_fields, update_values)
update_values.append(trade_id)
db.execute_update(

View File

@ -731,6 +731,23 @@ class BinanceClient:
except BinanceAPIException as e:
logger.error(f"获取持仓信息失败: {e}")
return []
async def get_recent_trades(self, symbol: str, limit: int = 50) -> List[Dict]:
"""
获取最近的成交记录
Args:
symbol: 交易对
limit: 获取数量
Returns:
成交记录列表
"""
try:
return await self.client.futures_account_trades(symbol=symbol, limit=limit)
except Exception as e:
logger.error(f"获取成交记录失败 {symbol}: {e}")
return []
async def get_symbol_info(self, symbol: str) -> Optional[Dict]:
"""

View File

@ -975,6 +975,54 @@ class PositionManager:
if exit_order_id:
logger.info(f"{symbol} [平仓] 币安订单号: {exit_order_id}")
# -----------------------------------------------------------
# 新增:获取实际成交详情(佣金、资金费率、实际盈亏)
# -----------------------------------------------------------
realized_pnl = None
commission = None
commission_asset = None
try:
# 等待一小段时间确保成交记录已生成
await asyncio.sleep(2)
# 获取最近的成交记录
recent_trades = await self.client.get_recent_trades(symbol, limit=10)
# 筛选属于当前平仓订单的成交记录
# 注意:一次平仓可能对应多条成交记录(分批成交)
related_trades = []
if exit_order_id:
related_trades = [t for t in recent_trades if str(t.get('orderId')) == str(exit_order_id)]
else:
# 如果没有订单号(极少见),尝试通过时间匹配
# TODO: 暂时跳过,风险较大
pass
if related_trades:
total_realized_pnl = 0.0
total_commission = 0.0
commission_assets = set()
for t in related_trades:
total_realized_pnl += float(t.get('realizedPnl', 0))
total_commission += float(t.get('commission', 0))
commission_assets.add(t.get('commissionAsset'))
realized_pnl = total_realized_pnl
commission = total_commission
commission_asset = "/".join(commission_assets) if commission_assets else None
logger.info(
f"{symbol} [平仓] 获取到实际成交详情: "
f"实际盈亏={realized_pnl} USDT, 佣金={commission} {commission_asset}"
)
else:
logger.warning(f"{symbol} [平仓] 未找到订单 {exit_order_id} 的成交记录,无法记录佣金")
except Exception as fee_error:
logger.warning(f"{symbol} [平仓] 获取成交详情失败: {fee_error}")
# 计算持仓持续时间
entry_time = position_info.get('entryTime')
duration_minutes = None
@ -1001,7 +1049,10 @@ class PositionManager:
pnl_percent=pnl_percent,
exit_order_id=exit_order_id, # 保存币安平仓订单号
strategy_type=strategy_type,
duration_minutes=duration_minutes
duration_minutes=duration_minutes,
realized_pnl=realized_pnl,
commission=commission,
commission_asset=commission_asset
)
logger.info(
f"{symbol} [平仓] ✓ 数据库记录已更新 "
@ -1690,7 +1741,7 @@ class PositionManager:
Trade.update_exit(
trade_id=trade_id,
exit_price=current_price,
exit_reason=exit_reason,
exit_reason=exit_reason_sl,
pnl=pnl_amount,
pnl_percent=pnl_percent_margin,
strategy_type=strategy_type,