auto_trade_sys/docs/订单与统计一致性说明.md
薇薇安 99df066101 1
2026-02-15 00:08:12 +08:00

86 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 持仓、订单记录、统计与币安一致性说明
在引入「订单号前后一致」处理(`entry_order_id` / `exit_order_id` / `SYSTEM_ORDER_ID_PREFIX`)后,以下内容的状态如下。
---
## 一、已能保证的部分
### 1. 持仓与币安一致
- **仪表板「当前持仓」**:数据来自 **币安实时持仓**`get_open_positions()`),与币安页面一致(仅受 `POSITION_MIN_NOTIONAL_USDT` 过滤影响)。
- **补建逻辑**:配置了 `SYSTEM_ORDER_ID_PREFIX` 时,**仅当**能明确查到开仓订单且 `clientOrderId` 非空且**不以**该前缀开头时视为手动单并跳过其余含历史单、查不到订单、cid 为空)一律补建并加入监控,避免漏掉系统单。
### 2. 订单记录与币安可对账
- **开仓**:交易系统**所有开仓单**(非 reduce_only在配置了 `SYSTEM_ORDER_ID_PREFIX` 时都会带 `newClientOrderId = 前缀_时间戳_随机`(如 `SYS_xxx`),成交后写入 DB 的 `entry_order_id``client_order_id`,与币安一致。
- **成交后落库**:开仓成交后 `position_manager` 会调用 `Trade.create(..., entry_order_id=..., client_order_id=...)`,订单记录与币安一一对应;若当时落库失败(超时/崩溃),后续同步会通过「补建」把缺失记录补上并纳入监控。
- **平仓**:平仓时保存 `exit_order_id``Trade.update_exit` 会做 `get_by_exit_order_id` 防重复。
- **同步**
- `POST /api/account/positions/sync` 与进程内定时同步:仅当开仓订单 `clientOrderId` **明确非**系统前缀时跳过补建,其余一律补建(含历史单、未带前缀的单),保证「币安有仓且为系统单」都会被监控。
- `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统计与币安对齐。
---
## 四、下单路径与「意外订单」排查
### 1. 会向币安下单的入口(汇总)
| 类型 | 入口 | 触发条件 | 是否可能「意外」开仓 |
|------|------|----------|----------------------|
| **开仓** | `trading_system/position_manager.open_position` | 仅由策略在「通过风控 + 有信号」后调用;`risk_manager.should_trade` 会检查:持仓数 ≥ MAX_OPEN_POSITIONS 则 return False且检查该 symbol 是否已有持仓 | 否。持仓数/已有仓检查均基于币安 `get_open_positions()`,到上限或已有该 symbol 即跳过,不会下单。 |
| **开仓** | `backend/api/routes/account.py` 限价下单接口 | 用户在前端/API 主动发起的「手动限价开仓」 | 否,需显式调用。 |
| **平仓** | `position_manager.close_position` | 仅对 `active_positions` 中存在的 symbol 调用(止损/止盈/手动);`check_stop_loss_take_profit` 只遍历 `active_positions` | 否,只平我们监控的仓。 |
| **平仓** | backend 平仓/一键平仓 | 用户主动平仓 | 否,需显式调用。 |
| **止盈/止损单** | `position_manager._ensure_exchange_sltp_orders` | 仅在两处调用:(1) 开仓成功后为该 symbol 挂 SL/TP(2) 补建「仅币安有、DB 无」的持仓时,若开启 SYNC_CREATE_MANUAL_ENTRY_RECORD为补建记录补挂 SL/TP。挂前会先取消该 symbol 下同类型 Algo 单STOP_MARKET/TAKE_PROFIT_MARKET再下新单 | 不会产生「开仓」;可能覆盖用户在该 symbol 上已有的同类型保护单(属预期:补建后统一用系统计算的 SL/TP。 |
结论:**没有逻辑会在「不该开仓」时自动下开仓单**;止盈/止损挂单仅对「我们已纳入监控的持仓」补挂或覆盖,不会对「仅币安有、且未补建」的持仓主动挂单(因未进 `active_positions` 不会走到 `_ensure_exchange_sltp_orders`)。
### 2. 日志里「总是想下一些单」是否正常?
- **策略扫描日志**(如 `处理交易对: SIRENUSDT (UP 31.32%, 市场状态: ranging)`、`SIRENUSDT 4H趋势中性信号质量可能降低`、`SIRENUSDT 持仓数量已达上限16/16跳过开仓`
表示:每轮扫描会**评估**各交易对并可能尝试开仓,但 **`持仓数量已达上限16/16跳过开仓``risk_manager.should_trade` 已 return False****不会调用 `open_position`,也不会向币安下任何单**。这是正常、预期行为。
- **挂止盈/止损相关日志**(如「已挂币安保护单」「挂止盈单失败」等)
表示:仅对 **当前已在 `active_positions` 中且具备 SL/TP 价格的持仓** 在交易所侧挂或更新保护单;每次挂前会先取消同类型旧单再挂新单,不会重复堆积同一 symbol 的多组 SL/TP。
若希望减少「想下单」类日志的干扰,可将「跳过开仓」类日志级别调低(如改为 debug或仅在有实际下单时再打 info。