在 `models.py` 中新增 `get_pending_recent` 方法,用于获取最近的待处理交易记录。`binance_client.py` 中添加 `get_order_by_client_order_id` 方法,以支持按 `client_order_id` 查询订单。`position_manager.py` 中实现 `_reconcile_pending_with_binance` 方法,增强对待处理记录的对账能力。`user_data_stream.py` 中在重连前执行待处理记录对账,确保系统在断线期间的交易状态得到及时更新。这些改进提升了系统的稳定性与交易记录的准确性。
6.6 KiB
6.6 KiB
正向流程漏洞分析与加固方案
目标:弄清楚正常流程为什么会出现「币安有持仓、DB 无 open 记录」,并完善正向流程,使正常流程掌控全局。
一、正常流程梳理
1.1 开仓链路(简化)
strategy.open_position()
→ 创建 pending 记录 (DB, client_order_id)
→ place_order 下单币安
→ _wait_for_order_filled() REST 轮询
→ 若 FILLED:update_pending_to_filled(),update_open_fields()
→ 若超时/CANCELED:撤单,返回 None
1.2 两条更新路径
| 路径 | 触发点 | 说明 |
|---|---|---|
| REST 路径 | open_position 中 _wait_for_order_filled 返回 ok |
进程存活、未超时 |
| WS 路径 | UserDataStream._on_order_trade_update 收到 ORDER_TRADE_UPDATE |
需 listenKey 连接正常 |
正常情况下,REST 和 WS 都会尝试更新;update_pending_to_filled 幂等,重复更新无害。
二、遗漏场景分析
2.1 进程崩溃 / 重启(最主要)
场景:
- 创建 pending,下单币安,进入
_wait_for_order_filled轮询 - 进程在轮询过程中崩溃(OOM、kill、异常退出)
- 订单在币安成交
- 重启后:
- REST 路径不会再执行(
open_position已结束) - WS 不会重放历史推送,
ORDER_TRADE_UPDATE已丢失
- REST 路径不会再执行(
结果:pending 长期残留,币安有持仓,DB 无 open。
2.2 WS 断线期间成交
场景:
- listenKey 失效 / 网络抖动,WS 断开
- 在断开期间订单成交,未收到 ORDER_TRADE_UPDATE
- REST 路径:若
open_position仍在运行,轮询会拿到 FILLED,可以更新;若已超时返回,则不会更新
结论:只有在「WS 断线 + REST 已超时返回」时,才会出现漏记。open_position 中限价单超时后会撤单,一般不留下持仓;但若存在「撤单与成交」的竞态,仍可能漏记。
2.3 listenKey 失效
- 60 分钟无 keepalive 会失效
- 文档建议 24 小时主动重连
- 失效期间新连接不会重放历史事件
效果同 2.2。
2.4 重连空窗期
- 断线 → 60s 后重连 → 新 listenKey
- 空窗期内的成交事件永久丢失
- 若该期间 REST 也未完成轮询(例如进程崩溃),则必然漏记
2.5 update_pending_to_filled 异常
- 若
Trade.update_pending_to_filled抛异常,_on_order_trade_update会 catch 并打日志,pending 保持 - REST 路径若在调用前崩溃,则完全依赖 WS;WS 路径若异常,则完全依赖 REST
- 任一路径失败且另一路径也失败,则漏记
2.6 竞态:撤单 vs 成交
- 限价单超时,调用
cancel_order - 若撤单请求发出时订单刚好成交,撤单可能失败
- 当前逻辑:超时则
return None,pending 保留,不会执行update_pending_to_filled - 结果:pending 残留 + 币安已成交,属于漏记
三、当前 sync_positions_with_binance 的覆盖范围
| 情况 | 是否处理 |
|---|---|
| DB open、币安无 | ✅ 更新 DB 为 closed |
| DB open、币安有 | ✅ 加载到 active_positions |
| 币安有、DB 无 open | ❌ 不处理(依赖 SYNC_RECOVER_MISSING_POSITIONS) |
| DB pending、币安订单已 FILLED | ❌ 不处理 |
当前同步逻辑不包含「pending 对账」:不会主动查币安订单状态,也不会把已成交的 pending 转为 open。
四、正向流程加固方案
4.1 思路
不依赖补建(SYNC_RECOVER_MISSING_POSITIONS),在正向流程中补齐「pending 对账」能力,使:
- 有 pending 且有 client_order_id / entry_order_id 时,主动查币安订单状态
- 若已 FILLED,则执行
update_pending_to_filled,将 pending 转为 open
4.2 加固点 1:WS 重连后 pending 对账(推荐)
位置:user_data_stream.py,_run_ws 重连成功后
逻辑:
- 重连成功后,查询当前账号下 status=pending 且有 client_order_id 的记录(可限制如 24h 内)
- 对每条记录调用币安 REST:
futures_get_order(symbol, orderId)或按 client_order_id 查 - 若 status=FILLED,调用
Trade.update_pending_to_filled
意义:补齐 WS 断线期间丢失的 ORDER_TRADE_UPDATE。
4.3 加固点 2:sync_positions_with_binance 中增加 pending 对账(推荐)
位置:position_manager.sync_positions_with_binance
逻辑:
- 在现有「DB open vs 币安持仓」同步之外,增加:
- 查询
Trade.get_pending_recent(account_id, limit=50, max_age_sec=86400)(需在 models 中新增) - 对每条 pending,若存在 client_order_id 或 entry_order_id,查币安订单
- 若 FILLED,则
update_pending_to_filled或update_pending_by_entry_order_id
- 查询
意义:周期性兜底,覆盖进程重启、WS 漏推等场景。
4.4 加固点 3:撤单后校验是否已成交(可选)
位置:position_manager.open_position,在 cancel_order 之后
逻辑:
- 撤单后(或撤单异常时),再查一次订单状态
- 若 status=FILLED,则按 REST 路径正常执行
update_pending_to_filled,避免竞态漏记
意义:消除「撤单与成交」竞态导致的漏记。
4.5 加固点 4:update_pending_to_filled robustness
- 保持幂等(当前已满足)
- 异常时记录清晰日志,便于排查
- 可选:对瞬时 DB 异常做有限次重试
五、实现优先级建议
| 优先级 | 加固点 | 影响 | 复杂度 |
|---|---|---|---|
| P0 | sync_positions_with_binance 中 pending 对账 | 覆盖进程重启、WS 漏推等主要漏记 | 中 |
| P0 | WS 重连后 pending 对账 | 覆盖断线期间的漏推 | 中 |
| P1 | 撤单后校验是否已成交 | 消除竞态漏记 | 低 |
| P2 | update_pending_to_filled 重试与日志 | 提升可靠性 | 低 |
六、需要的 DB / API 支持
-
Trade 模型:
get_pending_recent(account_id, limit, max_age_sec):返回指定时间范围内的 pending 记录,用于对账
-
币安 API:
- 按
clientOrderId查询:futures_get_all_orders(symbol, limit)过滤,或使用支持origClientOrderId的接口(如有) - 按
orderId查询:futures_get_order(symbol, orderId)(已有)
- 按
-
多账号:以上逻辑需按
account_id隔离,保证对账时使用对应账号的 client / API。
七、小结
- 主要漏记来自:进程崩溃 + 订单已成交,以及 WS 断线期间的成交。
- 正向流程目前缺少「pending 对账」:不会主动用币安订单状态修正 pending。
- 加固方向:在 WS 重连后 和 sync_positions_with_binance 中加入 pending 对账,使正常流程在运行中即可发现并修正漏记,而不依赖单独的补建逻辑。