auto_trade_sys/docs/common/订单记录与币安对账流程.md
薇薇安 81747c4eef feat(account, position_manager): 优化持仓同步逻辑与日志记录
在持仓同步功能中,增加了对系统订单前缀的默认处理,确保在无配置时使用默认前缀 "ats_"。同时,调整了日志记录逻辑,明确区分系统单与手动单的补建条件,提升了日志的可读性与准确性。这一改动旨在增强系统的可用性与用户友好性,确保持仓与数据库记录的一致性。
2026-02-25 14:48:31 +08:00

11 KiB
Raw Permalink Blame History

订单记录与币安对账流程

本文梳理:订单是怎么记的何时写入 entry_order_id / exit_order_id如何与币安保持一致


〇、常见误解:为什么「持仓 5 单、订单记录只有 1 单」?

  • 持仓列表:数据来自币安 APIget_open_positions()),是交易所上真实存在的持仓,与 DB 无关。
  • 订单记录:数据来自本系统数据库 trades,只有「本系统开仓并成功写库」或「同步/补建」时才会有一条记录。

因此不是「持仓存在就一定先在 DB 有了」——恰恰相反:币安上可以有 5 个持仓(本系统开的、手动在 App 开的、其它工具开的,或本系统开了但当时写库失败),而 DB 里只有我们成功写入或补建的那几条,所以订单记录会少于或等于持仓数。要对齐做法:使用持仓同步(见下文「补建」)或同步币安订单把「币安有仓、DB 无记录」的持仓补建到 DB订单记录就会和持仓一致。


一、数据模型(与币安对应的关键字段)

字段 含义 与币安对应
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 StreamWS 推送)补全订单号

位置trading_system/user_data_stream.py

  • 开仓成交 (ORDER_TRADE_UPDATE, X=FILLED, 非 reduceOnly)
    • 若有 clientOrderIdTrade.update_pending_to_filled(client_order_id, account_id, order_id, price, quantity),把对应 pending 记录完善为 open 并写入 entry_order_id
    • 若无 clientOrderIdTrade.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.pyclose_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.pysync_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.pyPOST /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_identry_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_exitexit_order_id。补建与同步也会创建/更新记录。
entry_order_id 从哪来? 本系统开仓:下单/成交返回的 orderId补建币安 get_all_orders / get_recent_trades / futures_get_order缺失时同步币安订单接口按时间/价格匹配补全。
exit_order_id 从哪来? 本系统平仓:平仓接口返回的 orderIdWSORDER_TRADE_UPDATE / ALGO_UPDATE 回写;同步币安订单:按 reduceOnly 订单匹配并 update_exit。
如何与币安一致? 1保证写库路径都尽量写入订单号2缺订单号时用「同步订单」补全3前端与统计只用「仅可对账」口径reconciled_only=true4需要时用 verify-binance 接口校验。