同步币安成交的手续费与实际盈亏,确保统计一致性

This commit is contained in:
薇薇安 2026-02-14 19:15:27 +08:00
parent 78667c2604
commit c53c5fc64a
3 changed files with 97 additions and 1 deletions

View File

@ -1724,6 +1724,22 @@ async def sync_positions(
margin = entry_value / leverage if leverage > 0 else entry_value
pnl_percent_margin = (pnl / margin * 100) if margin > 0 else 0
# 从币安成交获取手续费与实际盈亏,保证统计与币安一致
sync_commission = None
sync_commission_asset = None
sync_realized_pnl = None
if exit_order_id:
try:
recent_trades = await client.get_recent_trades(symbol, limit=30)
related = [t for t in recent_trades if str(t.get('orderId')) == str(exit_order_id)]
if related:
sync_commission = sum(float(t.get('commission', 0)) for t in related)
assets = {t.get('commissionAsset') for t in related if t.get('commissionAsset')}
sync_commission_asset = "/".join(assets) if assets else None
sync_realized_pnl = sum(float(t.get('realizedPnl', 0)) for t in related)
except Exception as fee_err:
logger.debug(f"同步 {symbol} 平仓手续费失败: {fee_err}")
# 更新数据库记录
duration_minutes = None
try:
@ -1744,6 +1760,9 @@ async def sync_positions(
exit_order_id=exit_order_id,
duration_minutes=duration_minutes,
exit_time_ts=exit_time_ts,
commission=sync_commission,
commission_asset=sync_commission_asset or None,
realized_pnl=sync_realized_pnl,
)
updated_count += 1
logger.info(

View File

@ -527,6 +527,21 @@ async def sync_trades_from_binance(
except Exception:
pass
# 从币安成交获取手续费与实际盈亏,保证统计与币安一致
sync_commission = None
sync_commission_asset = None
sync_realized_pnl = None
try:
recent_trades = await client.get_recent_trades(symbol, limit=30)
related = [t for t in recent_trades if str(t.get('orderId')) == str(order_id)]
if related:
sync_commission = sum(float(t.get('commission', 0)) for t in related)
assets = {t.get('commissionAsset') for t in related if t.get('commissionAsset')}
sync_commission_asset = "/".join(assets) if assets else None
sync_realized_pnl = sum(float(t.get('realizedPnl', 0)) for t in related)
except Exception as fee_err:
logger.debug(f"同步订单 {order_id} 手续费失败: {fee_err}")
# 持仓持续时间(分钟)
duration_minutes = None
try:
@ -538,7 +553,7 @@ async def sync_trades_from_binance(
except Exception:
duration_minutes = None
# 更新数据库(包含订单号
# 更新数据库(包含订单号、手续费与实际盈亏
Trade.update_exit(
trade_id=trade_id,
exit_price=avg_price,
@ -548,6 +563,9 @@ async def sync_trades_from_binance(
exit_order_id=order_id, # 保存订单号,确保唯一性
duration_minutes=duration_minutes,
exit_time_ts=exit_time_ts,
commission=sync_commission,
commission_asset=sync_commission_asset,
realized_pnl=sync_realized_pnl,
)
updated_count += 1
logger.debug(

View File

@ -0,0 +1,59 @@
# 持仓、订单记录、统计与币安一致性说明
在引入「订单号前后一致」处理(`entry_order_id` / `exit_order_id` / `SYSTEM_ORDER_ID_PREFIX`)后,以下内容的状态如下。
---
## 一、已能保证的部分
### 1. 持仓与币安一致
- **仪表板「当前持仓」**:数据来自 **币安实时持仓**`get_open_positions()`),与币安页面一致(仅受 `POSITION_MIN_NOTIONAL_USDT` 过滤影响)。
- **补建逻辑**:只有「开仓订单 `clientOrderId` 以配置前缀开头」的持仓会补建 DB 记录,避免把手动单算进系统。
### 2. 订单记录与币安可对账
- **开仓**:系统下单时写入 `newClientOrderId = 前缀_时间戳_随机`,并保存 `entry_order_id` 到 DB。
- **平仓**:平仓时保存 `exit_order_id``Trade.update_exit` 会做 `get_by_exit_order_id` 防重复。
- **同步**
- `POST /api/account/positions/sync`:只对「开仓订单 clientOrderId 前缀匹配」的持仓补建,且从成交里取 `orderId` 作为 `entry_order_id`
- `POST /api/trades/sync-binance`:用 `get_by_exit_order_id(order_id)` 判断是否已同步,避免重复;开仓侧用 `get_by_entry_order_id(order_id)` 判断是否已存在。
- 因此:**每条 DB 记录都可与币安订单一一对应**(通过 `entry_order_id` / `exit_order_id`),订单记录与币安在「谁开的、谁平的」上一致。
### 3. 统计口径与去重
- **统计**:来自 `Trade.get_all(..., account_id)`,只统计该账号的 DB 记录。
- **净盈亏**`get_net_pnl(t)` 逻辑为:
- 若有 `realized_pnl`:用 `realized_pnl`,再若 `commission_asset == 'USDT'` 则减去 `commission`
- 否则用 `pnl`(按价格差算)。
- 胜率、总盈亏、盈亏比等均基于上述净盈亏汇总,**不会因为重复同步同一条平仓而重复计入**(由 `exit_order_id` 唯一性保证)。
---
## 二、仍依赖「谁在写」的部分
### 1. 手续费commission
| 场景 | 是否写入 commission | 说明 |
|--------------------|----------------------|------|
| 交易系统内平仓 | ✅ 是 | 从 `get_recent_trades``exit_order_id` 汇总 commission写入 `update_exit`。 |
| 仪表板「平仓」按钮 | ✅ 是 | 同上,取成交里的 commission/realizedPnl 写入。 |
| 持仓同步positions/sync | ✅ 是(已补全) | 更新 closed 前按 `exit_order_id` 拉取 `get_recent_trades`,汇总 commission/realizedPnl 并写入。 |
| 订单同步trades/sync-binance | ✅ 是(已补全) | 更新平仓记录前按订单号拉取成交,写入 commission/realized_pnl。 |
因此:**所有标记为已平仓的记录都会尽量带上手续费与实际盈亏**,统计与币安一致。
### 2. 实际盈亏realized_pnl
- **交易系统平仓、仪表板平仓**:从币安成交取 `realizedPnl` 并写入。
- **持仓同步、订单同步**:已补全逻辑,同样按 `exit_order_id` 拉取成交并写入 `realized_pnl`
- 统计中若存在 `realized_pnl` 会优先使用并再扣 USDT 手续费,否则用价格差 `pnl`
---
## 三、小结:能否「直接保证」?
- **持仓与币安一致**:可以,当前实现已保证(实时持仓 + 按前缀补建)。
- **订单记录与币安可对账**:可以,`entry_order_id` / `exit_order_id` 与防重复逻辑已保证一一对应、不重复。
- **统计准确性**:在「同一笔平仓只被记录一次」和「按净盈亏汇总」上已保证;**手续费与实际盈亏**已在所有关仓路径补全:
- 系统/仪表板平仓、持仓同步、订单同步 均会按 `exit_order_id` 拉取成交并写入 commission/realized_pnl统计与币安对齐。