同步币安成交的手续费与实际盈亏,确保统计一致性
This commit is contained in:
parent
78667c2604
commit
c53c5fc64a
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
59
docs/订单与统计一致性说明.md
Normal file
59
docs/订单与统计一致性说明.md
Normal 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,统计与币安对齐。
|
||||
Loading…
Reference in New Issue
Block a user