# 订单记录与币安对账流程 本文梳理:**订单是怎么记的**、**何时写入 entry_order_id / exit_order_id**、**如何与币安保持一致**。 --- ## 一、数据模型(与币安对应的关键字段) | 字段 | 含义 | 与币安对应 | |------|------|------------| | `entry_order_id` | 币安开仓订单号 (orderId) | 开仓订单唯一标识,用于与币安「开仓订单」一一对应 | | `exit_order_id` | 币安平仓订单号 (orderId) | 平仓订单唯一标识,用于与币安「平仓订单」一一对应 | | `client_order_id` | 币安 clientOrderId | 系统下单时用 `SYSTEM_ORDER_ID_PREFIX_时间戳_随机`,便于区分本系统单 | | `entry_time` | 开仓时间 | 建议从币安订单/成交取真实时间,便于统计与早止盈 | | `status` | open / closed / pending | open=持仓中,closed=已平仓,pending=已下单待成交 | **「可对账」定义**(与币安一致的口径): - 有 `entry_order_id`(非空且不为 0) - 若 `status = closed`,还须有 `exit_order_id`(非空且不为 0) 接口默认 `reconciled_only=true` 时,只返回/统计满足上述条件的记录。 --- ## 二、订单记录是在哪里写的? ### 1. 本系统开仓(策略/限价开仓) **位置**:`trading_system/position_manager.py`(开仓成功、成交确认后) - **流程**:下单 → 等待成交(REST 轮询或 WS)→ 取到 `orderId`、成交价、数量 → 写库。 - **写库方式**: - 若之前已建 `status=pending`(限价先挂单):用 `Trade.update_pending_to_filled(client_order_id, account_id, entry_order_id, entry_price, quantity)` 或 `Trade.update_pending_by_entry_order_id(symbol, account_id, entry_order_id, entry_price, quantity)` 完善为 open,并写入 `entry_order_id`。 - 否则直接 `Trade.create(..., entry_order_id=orderId, client_order_id=..., status='open', entry_time=...)`。 - **entry_order_id 来源**:REST 下单返回的 `order.get("orderId")`,或成交确认时从订单查询得到。 - **entry_time**:支持传入;不传则用当前北京时间;补建/手动同步路径会从币安订单或成交取真实开仓时间。 **结论**:本系统开的仓,在正常落库且无异常时,**开仓时就会带上 entry_order_id**,与币安开仓订单一一对应。 --- ### 2. User Data Stream(WS 推送)补全订单号 **位置**:`trading_system/user_data_stream.py` - **开仓成交 (ORDER_TRADE_UPDATE, X=FILLED, 非 reduceOnly)** - 若有 `clientOrderId`:`Trade.update_pending_to_filled(client_order_id, account_id, order_id, price, quantity)`,把对应 pending 记录完善为 open 并写入 `entry_order_id`。 - 若无 clientOrderId:`Trade.update_pending_by_entry_order_id(symbol, account_id, order_id, price, quantity)`,用 symbol+account 下「唯一一条 pending 且无 entry_order_id」的记录做兜底补全。 - **平仓成交 (ORDER_TRADE_UPDATE, X=FILLED, reduceOnly)** - `Trade.set_exit_order_id_for_open_trade(symbol, account_id, order_id, entry_order_id_hint)`:给该 symbol 下当前 open 且无 exit_order_id 的记录写入平仓订单号(有 entry_order_id 时优先按 entry_order_id 精确匹配)。 - **条件单触发 (ALGO_UPDATE, X=TRIGGERED/FINISHED)** - `ai` = 触发后的普通订单 orderId,同样调用 `Trade.set_exit_order_id_for_open_trade(symbol, account_id, ai, ...)` 回写 `exit_order_id`。 **结论**:WS 负责在「开仓/平仓成交或条件单触发」时,把币安订单号回写到 DB,保证与币安一致。 --- ### 3. 本系统平仓(止损/止盈/手动平仓) **位置**:`trading_system/position_manager.py`(close_position 成功后) - **流程**:市价平仓或条件单触发 → 拿到平仓订单号 → `Trade.update_exit(trade_id, exit_price, exit_reason, pnl, pnl_percent, exit_order_id=..., exit_time_ts=..., commission=..., realized_pnl=...)`。 - **exit_order_id 来源**:平仓接口返回的 `order.get('orderId')`,或从 `get_recent_trades` 按订单号汇总。 - **commission / realized_pnl**:从币安成交 `get_recent_trades` 按该订单号汇总,写入 DB,统计与币安一致。 **结论**:本系统平的仓,平仓路径会写入 `exit_order_id` 及手续费/实际盈亏,与币安平仓订单一致。 --- ### 4. 补建「币安有仓、DB 无记录」(状态同步) **位置**:`trading_system/position_manager.py`(sync_positions_with_binance) - **何时发生**:定时同步或调用 `POST /api/account/positions/sync` 时,发现某 symbol 在币安有持仓,但 DB 没有对应 open 记录。 - **补建逻辑**(简要): - 若配置了 `SYSTEM_ORDER_ID_PREFIX`:会查该 symbol 的开仓订单(如 get_all_orders / get_recent_trades),取**同方向、时间合理**的订单作为 `entry_order_id`;若查到 `clientOrderId` 且**以系统前缀开头**则视为系统单并补建;若**明确不以系统前缀开头**则视为手动单,按配置可跳过或仍补建(如开启 SYNC_CREATE_MANUAL_ENTRY_RECORD)。 - 补建时调用 `Trade.create(..., entry_reason='sync_recovered' 或 'manual_entry', entry_order_id=..., entry_time=...)`,**尽量从币安订单/成交取真实 entry_order_id 和 entry_time**。 - **entry_order_id 来源**:`futures_get_order(entry_order_id)` 或同方向成交中最早一笔的 orderId;若拿不到则可能为空(后续靠「同步币安订单」补全)。 **结论**:补建记录会尽量带上 `entry_order_id` 和真实开仓时间;若当时拿不到,需依赖「同步币安订单」补全。 --- ### 5. 同步币安订单(后端接口) **位置**:`backend/api/routes/trades.py`,`POST /api/trades/sync-binance` - **作用**:按时间范围拉取币安历史订单,与 DB 对齐:**补全缺失的 entry_order_id / exit_order_id**,必要时**新建 DB 记录**。 - **开仓订单**: - 若 `Trade.get_by_entry_order_id(order_id)` 已存在 → 跳过。 - 否则在该 symbol 下找「时间窗口内且无 entry_order_id」的记录,按价格/数量匹配(允许少量误差)→ 匹配到则 `Trade.update_entry_order_id(trade_id, order_id)` 补全;匹配不到则若开启「全量同步」可 `Trade.create(..., entry_order_id=order_id, entry_reason='sync_from_binance', status='open')` 新建。 - **平仓订单 (reduceOnly)**: - 若 `Trade.get_by_exit_order_id(order_id)` 已存在 → 视情况跳过。 - 否则找该 symbol 的 open 或「已 closed 但 exit_order_id 为空」的记录,匹配后 `Trade.update_exit(..., exit_order_id=order_id, ...)`,并可从成交拉取 commission/realized_pnl 写入。 **结论**:**与币安订单一致的关键一步**。DB 里已有记录但缺订单号时,用此接口可把 entry_order_id / exit_order_id 补全;若 DB 完全没有记录且开了全量同步,会按币安开仓订单新建记录,保证「订单记录」与币安可对账。 --- ## 三、如何与币安订单一致?(操作与口径) ### 1. 保证「有订单号」 - **本系统开/平仓**:正常落库 + WS 回写,会自动有 `entry_order_id` / `exit_order_id`。 - **补建/手动同步**:补建时已尽量从币安取 `entry_order_id` 和 `entry_time`;若仍缺订单号(如历史旧数据),可: - 在前端「交易记录」页使用 **「同步订单」**(即 `POST /api/trades/sync-binance`),选择时间范围(如今天/7 天),必要时勾选「全量同步」; - 同步后会补全「开仓/平仓订单号」,并可能新建缺失记录;同步结果会提示补全了多少个 entry_order_id / exit_order_id。 ### 2. 只用「可对账」口径看订单与统计 - 前端「交易记录」默认 **勾选「仅可对账(与币安一致)」**(`reconciled_only=true`)。 - 接口行为: - `GET /api/trades?reconciled_only=true`:只返回「有 entry_order_id,且若已平仓则有 exit_order_id」的记录。 - `GET /api/trades/stats?reconciled_only=true`:只统计上述记录,日盈亏、胜率、总盈亏与币安一致。 - 这样**订单记录与统计都只基于「能和币安一一对上」的数据**,避免无订单号或脏数据干扰。 ### 3. 主动校验是否一致 - **接口**:`GET /api/trades/verify-binance?days=30&limit=100`(需登录与账号)。 - **作用**:对「可对账」记录逐条用币安 `futures_get_order` 校验开仓/平仓订单是否存在、symbol/side/数量是否一致;返回一致/缺失/不一致数量及明细。 - 建议定期或在对账有疑问时调用,确认 DB 与币安一致。 --- ## 四、流程简图(何时有 entry_order_id / exit_order_id) ``` 本系统开仓 → 下单得到 orderId → Trade.create(..., entry_order_id=orderId) 或 update_pending_to_filled / update_pending_by_entry_order_id → [WS] ORDER_TRADE_UPDATE FILLED 可再次补全 entry_order_id(若之前未写入) 本系统平仓 → 平仓得到 orderId → Trade.update_exit(..., exit_order_id=orderId) → [WS] ORDER_TRADE_UPDATE FILLED / ALGO_UPDATE 可再次补全 exit_order_id(若之前未写入) 币安有仓、DB 无(补建) → Trade.create(..., entry_order_id=从币安订单/成交取, entry_time=从币安取) → 若当时取不到 entry_order_id,后续靠「同步币安订单」补全 同步币安订单 (POST /api/trades/sync-binance) → 开仓订单:匹配 DB 无 entry_order_id 的记录 → update_entry_order_id;或新建 Trade(..., entry_order_id=orderId) → 平仓订单:匹配 open 或 closed 无 exit_order_id → update_exit(..., exit_order_id=orderId) → 结果:DB 与币安在「谁开的、谁平的」上一致 ``` --- ## 五、小结 | 问题 | 答案 | |------|------| | 订单是怎么记录的? | 开仓:`Trade.create` 或 update pending → open,并写 `entry_order_id`;平仓:`Trade.update_exit` 写 `exit_order_id`。补建与同步也会创建/更新记录。 | | entry_order_id 从哪来? | 本系统开仓:下单/成交返回的 orderId;补建:币安 get_all_orders / get_recent_trades / futures_get_order;缺失时:同步币安订单接口按时间/价格匹配补全。 | | exit_order_id 从哪来? | 本系统平仓:平仓接口返回的 orderId;WS:ORDER_TRADE_UPDATE / ALGO_UPDATE 回写;同步币安订单:按 reduceOnly 订单匹配并 update_exit。 | | 如何与币安一致? | 1)保证写库路径都尽量写入订单号;2)缺订单号时用「同步订单」补全;3)前端与统计只用「仅可对账」口径(reconciled_only=true);4)需要时用 verify-binance 接口校验。 |