a
This commit is contained in:
parent
76e6e5efd0
commit
7b8bcd758d
|
|
@ -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}%)")
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user