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_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:
|
for oid in order_ids:
|
||||||
try:
|
try:
|
||||||
|
# 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))
|
||||||
|
|
@ -1006,6 +1020,21 @@ async def close_position(symbol: str, account_id: int = Depends(get_account_id))
|
||||||
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 匹配)
|
||||||
|
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:
|
except Exception as e:
|
||||||
logger.warning(f"获取订单详情失败 (orderId={oid}): {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',
|
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),
|
||||||
|
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"✓ 已更新数据库记录 trade_id={trade['id']} order_id={chosen_oid} (盈亏: {pnl:.2f} USDT, {pnl_percent:.2f}%)")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -501,6 +501,9 @@ class Trade:
|
||||||
strategy_type=None,
|
strategy_type=None,
|
||||||
duration_minutes=None,
|
duration_minutes=None,
|
||||||
exit_time_ts=None,
|
exit_time_ts=None,
|
||||||
|
realized_pnl=None,
|
||||||
|
commission=None,
|
||||||
|
commission_asset=None,
|
||||||
):
|
):
|
||||||
"""更新平仓信息(使用北京时间)
|
"""更新平仓信息(使用北京时间)
|
||||||
|
|
||||||
|
|
@ -511,6 +514,9 @@ class Trade:
|
||||||
pnl: 盈亏
|
pnl: 盈亏
|
||||||
pnl_percent: 盈亏百分比
|
pnl_percent: 盈亏百分比
|
||||||
exit_order_id: 币安平仓订单号(可选,用于对账)
|
exit_order_id: 币安平仓订单号(可选,用于对账)
|
||||||
|
realized_pnl: 币安实际结算盈亏(可选)
|
||||||
|
commission: 交易手续费(可选)
|
||||||
|
commission_asset: 手续费币种(可选)
|
||||||
|
|
||||||
注意:如果 exit_order_id 已存在且属于其他交易记录,会跳过更新 exit_order_id 以避免唯一约束冲突
|
注意:如果 exit_order_id 已存在且属于其他交易记录,会跳过更新 exit_order_id 以避免唯一约束冲突
|
||||||
"""
|
"""
|
||||||
|
|
@ -520,14 +526,33 @@ class Trade:
|
||||||
except Exception:
|
except Exception:
|
||||||
exit_time = get_beijing_time()
|
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,先检查是否已被其他交易记录使用
|
# 如果提供了 exit_order_id,先检查是否已被其他交易记录使用
|
||||||
if exit_order_id is not None:
|
if exit_order_id is not None:
|
||||||
try:
|
try:
|
||||||
existing_trade = Trade.get_by_exit_order_id(exit_order_id)
|
existing_trade = Trade.get_by_exit_order_id(exit_order_id)
|
||||||
if existing_trade:
|
if existing_trade:
|
||||||
if existing_trade['id'] == trade_id:
|
if existing_trade['id'] == trade_id:
|
||||||
# exit_order_id 属于当前交易记录:允许继续更新(比如补写 exit_reason / exit_time / duration)
|
# exit_order_id 属于当前交易记录:允许继续更新
|
||||||
# 不需要提前 return
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"交易记录 {trade_id} 的 exit_order_id {exit_order_id} 已存在,将继续更新其他字段"
|
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]
|
update_values = [exit_price, exit_time, exit_reason, pnl, pnl_percent]
|
||||||
|
|
||||||
if strategy_type is not None:
|
_append_extra_fields(update_fields, update_values)
|
||||||
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)
|
|
||||||
|
|
||||||
update_values.append(trade_id)
|
update_values.append(trade_id)
|
||||||
db.execute_update(
|
db.execute_update(
|
||||||
|
|
@ -570,12 +590,7 @@ class Trade:
|
||||||
]
|
]
|
||||||
update_values = [exit_price, exit_time, exit_reason, pnl, pnl_percent, exit_order_id]
|
update_values = [exit_price, exit_time, exit_reason, pnl, pnl_percent, exit_order_id]
|
||||||
|
|
||||||
if strategy_type is not None:
|
_append_extra_fields(update_fields, update_values)
|
||||||
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)
|
|
||||||
|
|
||||||
update_values.append(trade_id)
|
update_values.append(trade_id)
|
||||||
db.execute_update(
|
db.execute_update(
|
||||||
|
|
|
||||||
|
|
@ -732,6 +732,23 @@ class BinanceClient:
|
||||||
logger.error(f"获取持仓信息失败: {e}")
|
logger.error(f"获取持仓信息失败: {e}")
|
||||||
return []
|
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]:
|
async def get_symbol_info(self, symbol: str) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
获取交易对的精度和限制信息
|
获取交易对的精度和限制信息
|
||||||
|
|
|
||||||
|
|
@ -975,6 +975,54 @@ class PositionManager:
|
||||||
if exit_order_id:
|
if exit_order_id:
|
||||||
logger.info(f"{symbol} [平仓] 币安订单号: {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')
|
entry_time = position_info.get('entryTime')
|
||||||
duration_minutes = None
|
duration_minutes = None
|
||||||
|
|
@ -1001,7 +1049,10 @@ class PositionManager:
|
||||||
pnl_percent=pnl_percent,
|
pnl_percent=pnl_percent,
|
||||||
exit_order_id=exit_order_id, # 保存币安平仓订单号
|
exit_order_id=exit_order_id, # 保存币安平仓订单号
|
||||||
strategy_type=strategy_type,
|
strategy_type=strategy_type,
|
||||||
duration_minutes=duration_minutes
|
duration_minutes=duration_minutes,
|
||||||
|
realized_pnl=realized_pnl,
|
||||||
|
commission=commission,
|
||||||
|
commission_asset=commission_asset
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{symbol} [平仓] ✓ 数据库记录已更新 "
|
f"{symbol} [平仓] ✓ 数据库记录已更新 "
|
||||||
|
|
@ -1690,7 +1741,7 @@ class PositionManager:
|
||||||
Trade.update_exit(
|
Trade.update_exit(
|
||||||
trade_id=trade_id,
|
trade_id=trade_id,
|
||||||
exit_price=current_price,
|
exit_price=current_price,
|
||||||
exit_reason=exit_reason,
|
exit_reason=exit_reason_sl,
|
||||||
pnl=pnl_amount,
|
pnl=pnl_amount,
|
||||||
pnl_percent=pnl_percent_margin,
|
pnl_percent=pnl_percent_margin,
|
||||||
strategy_type=strategy_type,
|
strategy_type=strategy_type,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user