Binance CM-UM 架构整合 dualSidePosition:-4531 完整处理指南
c
你的策略跑了半年没事,2026-06-30 之后 dualSidePosition 某一次 切换突然被 webhook 拒绝,回传{"code":-4531,"msg":"Position mode change requires syncing UM and CM..."}—— 消息叫你先关掉 CM 里的仓位或挂单。但你明明没在做币本位。 原因是 Binance CM-UM 架构整合 dualSidePosition 这件事在 06-30 全面生效了。
没有人先跟你讲的是:Binance 从 2026-06-30 开始把 COIN-M(CM / DAPI) 并进 USDⓈ-M(UM / FAPI)的统一架构,dualSidePosition 现在整个账户只有一份, 改 UM 那边会同步改 CM,反之亦然。你以为的「切 UM 的 mode」现在会 去检查 CM 那边有没有卡什么——只要有,整个请求就被 -4531 挡回来。
这篇不整理整份整合公告,那份公告有 A / B / C / D 四大节、讲给不同 endpoint 使用者听。这篇只讲一件事:切 position mode 这个动作,现在会遇到什么、 你要怎么绕开——以及一个很多人没注意到的细节:-4531是 Binance 官方明说的暂时性错误,但那不代表你现在可以忽略它。
-4531 现在会遇到、CM 进 Guard 后不会再遇到, 但这中间你留在 CM 的孤儿订单/仓位会挡掉你所有改 mode 的请求。 第二,直接写死「重试几次就会过」是错的——没清干净的话, 等到天荒地老也不会过。第三,2026-06-29 09:00 UTC 之后, COIN-M 的 auto-cancel countdown 已经暂停,靠它做保护的策略在维护窗口内 是完全没防护的。整合了什么——你只需要记住一个字:共用
Binance 官方在 2026-06-10 发出 「Important CM-UM Integration Notice」(查证日期 2026 年 7 月),公告的第一段就把整件事的性质讲清楚: COIN-M 被并进 USDⓈ-M 共用的统一架构,多个 REST endpoint、 WebSocket 串流、以及账户层级的行为,全部对齐 USDⓈ-M 的既有惯例。这不是新增功能,是把两边的行为抹平成一致。
抹平的方式是渐进的——公告本身注明「Individual changes may be enabled at different times after the initial effective date」——完整生效的日期 是 2026-06-30。所以如果你的 bot 在 06-30 之前跑得好好的、06-30 之后 开始出现以前没看过的行为,那大概率就是整合的哪一环在你这里生效了。
整合涉及的具体项目有一大堆(下单 ack 拿掉 avgPrice/cumQuote、CM 的 stop-type 要改走新的/dapi/v1/algoOrder、rate limit 并池、STP 统一走 UM 设置), 但对「有 webhook 在改 position mode 的人」来说,影响最直接、最容易安静地坏掉的就是 dualSidePosition。 下面这张表把整合前后的差别摊开来看。
| 项目 | 整合之前 | 2026-06-30 之后 |
|---|---|---|
dualSidePosition | UM 一份、CM 一份,互不干扰 | UM 与 CM 共用一份,一次操作同时改两边 |
| 切 mode 需要清干净的范围 | 只要清 UM 那边 | UM 与 CM 两边都要清干净 |
| 挡你的错误码 | -4067/-4068 | 多一个 -4531(同步 CM 失败时触发) |
| COIN-M auto-cancel countdown | 正常运作 | 2026-06-29 09:00 UTC 起暂停,CM 恢复后才回来 |
来源:Binance Important CM-UM Integration Notice(2026-06-10 发布)与 Binance USDⓈ-M / COIN-M Futures API change-log(查证日期 2026 年 7 月)。
所以这一关不要靠自己猜。去 dashboard 或 API 对 CM 账户查一次 有没有仓位与挂单——没有就没事,有就要先清。清的逻辑在下面 「切 mode 前你要先确认哪三件事」那节。
-4531 到底在拒绝什么——它跟 -4067/-4068 不一样
Binance 官方在 2026-05-11 的 change-log 条目里加了这个错误码 (Effective Date 2026-05-13),完整原文写得很直白:
-4531: When changing UMdualSidePosition, the system will automatically sync CM dualSidePosition. If the CM account has any open position or open order, the sync cannot proceed and the UM position mode change will be rejected with error code -4531.」(查证日期 2026 年 7 月;来源:Binance USDⓈ-M Futures API change-log 2026-05-11 条目。)
翻成人话:你打的是 UM 那边的 endpoint,但 Binance 内部会替你去改 CM 的dualSidePosition——这个「替你改」的动作如果失败, 原本你发起的那笔 UM 请求就整个被回退,回传 -4531。失败的地方不在你这边,在 Binance 内部同步的那一步。
这跟你熟悉的 -4067/-4068 差在哪?差在它们指的是同一件事在不同地方发生。旧的两个讲的是 你这边(UM 账户)有挂单/有仓位;-4531 讲的是 另一边(CM 账户)有挂单/有仓位。消息本身不会告诉你是 CM 的哪个 symbol 卡住——你得自己去 CM 那边查。
| 错误码 | 在哪里卡住 | 怎么清 |
|---|---|---|
-4067 | UM 有 open orders | DELETE /fapi/v1/allOpenOrders 或逐 symbol 撤 |
-4068 | UM 有 open position | reduce-only market 单平仓,或 dashboard 手动平 |
-4531 | CM 有 open orders 或 open position(同步失败) | 先打 DELETE /dapi/v1/allOpenOrders,再平 CM 仓位 |
三个「不能改 mode」的错误码对照。来源:Binance USDⓈ-M Futures API change-log(查证日期 2026 年 7 月)。
还有一个维护窗口期间的暂时错误要顺带一提——如果你的请求刚好落在 Binance 为了 CM 迁移做维护的时段,你会拿到 -1016(「This service is no longer available.」)或-1109(「Invalid account.」)。那不是你的 参数有问题,是 Binance 刚好在动里面的东西——这种错不应该立刻重试, 等维护结束再来。
官方明说 -4531 是暂时的——但你不能就这样等
同一则 change-log 条目在最下面补了一段 Note,这一段常常被跳过, 但它决定了你的错误处理逻辑要怎么写:
(来源同上。)值得注意的是,Binance USDⓈ-M 错误码参考页面 (
developers.binance.com/en/docs/products/derivatives-trading-usds-futures/error-code, 2026-07-30 更新)到查证当下依然没有收录 -4531—— 这跟 Note 讲的「暂时性」互相印证:Binance 认为它不会活到进永久错误码表。但你要小心两件事。第一,「approximately 1 month」没有明说是从 哪天开始算的——是从 05-13 生效那天,还是从 06-30 全面生效那天? 官方没说。实务上请把它当成「至少一个月、可能更久」,不要押宝哪天会结束。
第二,即使它结束了,-4067 与 -4068不会结束——UM 那边有挂单/仓位还是会挡你改 mode,那是常态行为。 所以就算未来哪天你不再看到 -4531,你的错误处理逻辑 仍然要能处理「切 mode 之前要先清干净」这件事——只是清的范围 从 UM+CM 缩回 UM 而已。
所以你该做什么?不要写「遇到 -4531 就等一小时再重试」 这种逻辑。要写「遇到 -4531 就去查 CM 有没有东西,有就清、 清完再重试;没有就报警找人来看」。这个差别听起来很小, 但它决定了你的策略是在自动恢复还是安静地坏掉。
切 mode 前你要先确认哪三件事
Binance 官方公告在 A.1 节底部有一句 「Action required」:「before flipping dualSidePosition, ensure both UM and CM have no open orders and no open positions.」翻成你可以照做的动作,就是下面这张 checklist。
- UM 没有 open orders。打
GET /fapi/v1/openOrders,回传数组必须是空的。 有就先DELETE /fapi/v1/allOpenOrders——每个 symbol 分开撤,或用不带 symbol 的版本一次撤全部。 - UM 没有 open positions。打
GET /fapi/v2/positionRisk,每一笔的positionAmt必须是 0。有非 0 的就送 reduce-only market 单平仓,或去 dashboard 手动平。 - CM 也要做上面两件事。对应的 endpoint 是
GET /dapi/v1/openOrders与GET /dapi/v1/positionRisk。这一步在 2026-06-30 之前不需要,之后每次都要——而且忘了做的时候,Binance 给你的错误 是-4531,不是任何跟 UM 有关的字眼。
check 完之后也不能立刻 flip——中间可能有别的程序在下单。检查与切换之间要保持原子性:把整段包成一个 mutex, 或至少在切之前再 double check 一次 open orders/positions。 切完再 GET /fapi/v1/positionSide/dual 确认新的值 真的生效了——别假设 200 回来就代表成功。
顺带提醒:这一切都预设你的 API key 有 futures 交易权限、也有正确的 IP 白名单设置,否则会遇到跟 -4531 完全无关但看起来很像的-2015(invalid API-key)。API key 该怎么设,Binance API Key 安全设置 那篇有完整检查清单。
那个被大家忽略的地雷——COIN-M countdown 已暂停
如果你的架构有用到 POST /dapi/v1/countdownCancelAll(COIN-M 的 auto-cancel all open orders / countdown)做「webhook 挂了就 自动撤单」的断线保护,2026-06-29 的 change-log 有一则你需要看:
「Any countdown set before the suspension remains effective in the matching engine up until the snapshot is taken at maintenance shutdown. If the countdown timer set by the user is scheduled to fire after the maintenance snapshot, the countdown for those symbols will not take effect.」
(来源:Binance COIN-M Futures API change-log 2026-06-29 条目, 查证日期 2026 年 7 月。)
白话说:你设在维护开始之后才会触发的 countdown,不会生效。Binance 没承诺恢复时间,也没承诺恢复之后行为完全一样——公告只讲到 「will be restored after CM resumes」,一个很诚实但也很不确定的说法。
USDⓈ-M 那边的 POST /fapi/v1/countdownCancelAll 没有受这条 影响,可以继续用。所以如果你原本靠 COIN-M countdown 做保护、 现在还在等它回来——先确认你的策略在这段空窗期有没有其他断线防护, 比如服务器端的 watchdog、或直接改走 UM 的 countdown(如果你交易的合约 是永续而不是币本位交割合约,这个切换值得考虑)。更完整的断线失效 设计思路,可以看 TradingView 或交易所宕机时你的自动化策略会发生什么事 那篇。
把上面所有东西写成一段可以贴进 CI 的检查
你不需要真的把逻辑写在策略代码里——那太脆弱、也太难测。把它拆成「切 mode 的守门函数」与「错误码路由表」, 在真的要改 mode 之前调用一次。下面是这个守门函数的骨架, 用 Python 写,但把 requests 换成任何语言的 HTTP client 都一样。
# 这是一段 pseudocode 骨架,用来说明流程。错误处理、签名、
# rate limit 都省略了——实际上线前请补齐。
def ensure_side_clean(client, prefix):
"""prefix 是 '/fapi/v1' 或 '/dapi/v1'。返回 True 代表这一侧干净。"""
orders = client.get(f"{prefix}/openOrders")
if orders:
client.delete(f"{prefix}/allOpenOrders")
positions = client.get(f"{prefix}/positionRisk")
non_flat = [p for p in positions if float(p["positionAmt"]) != 0]
if non_flat:
# 这一步不自动平——不同策略对于平仓时机有自己的规则,
# 让上层决定要不要接手。
raise NotFlatError(side=prefix, positions=non_flat)
return True
def flip_dual_side_position(client, target: bool):
ensure_side_clean(client, "/fapi/v1") # UM
ensure_side_clean(client, "/dapi/v1") # CM,2026-06-30 之后必查
resp = client.post("/fapi/v1/positionSide/dual", params={"dualSidePosition": target})
if resp.status_code != 200:
code = resp.json().get("code")
# 错误码路由表——不要每个都用同样的重试策略
if code == -4531:
# 同步 CM 失败——上一次 check 到 flip 之间有东西冒出来
raise CmSyncFailed()
if code in (-4067, -4068):
raise UmNotFlat()
if code == -1016:
# 维护中,指数退避即可,不要每分钟打
raise ServiceUnavailable(retry_after=300)
raise UnknownError(code=code)
# 别假设 200 就成功——回头确认一次
current = client.get("/fapi/v1/positionSide/dual")
assert current["dualSidePosition"] == target这段的重点不是那些 API 调用——而是错误码路由表: 不同 code 用不同重试策略。-4531 与 -4067/-4068 是 「有东西没清」 的信号,重试前必须真的去清;-1016 是 「Binance 正在动」 的信号,等一下再来; 其他 code 就是未知,该报警找人。
诚实的一段:这篇没实测 -4531
我们没有替这篇文章实际触发过 -4531——一来这个错误只在 特定条件下(账户同时有 UM 与 CM 活动、且中间有孤儿订单/仓位) 才会出现,二来我们也不会为了写文章刻意在生产环境重现它。这篇的所有时间点、error payload、触发条件,都是直接引自 Binance 官方 change-log 与整合公告,不是实测结果。
另一个保留是关于「approximately 1 month」——我们没有内线知道 CM 进 Guard 的确切日期。这个推论靠的是官方在 change-log 上写的字面。你部署到生产环境 之前,建议自己再去 developers.binance.com 的 change-log 与错误码参考页核对一次日期,尤其是 -4531有没有从错误码表消失——那是 CM 进 Guard 最直接的信号。
常见问题
我根本没在做币本位,为什么会遇到 CM 相关的错误?
dualSidePosition。你改 UM 那边,Binance 内部 会自动替你去改 CM 那边。如果你的账户很久以前有做过 COIN-M 留下一张没撤的单、或一个没平的仓位,就会遇到-4531。查一次 CM 账户就知道。-4531 什么时候会完全消失?
-4067/-4068 还会挡你, 所以「切 mode 前要清干净」这个逻辑永远不会过时。POST /dapi/v1/positionSide/dual 现在还能用吗?
POST /fapi/v1/positionSide/dual现在是同一件事——打哪一个都会同时改 UM 与 CM。 Binance 没有把 dapi 这个 endpoint 拿掉,但你不用刻意用它, 用 fapi 那个效果一样。维护窗口那段时间拿到 -1016 或 -1109,我是不是要立刻重试?
-1016/-1109 都是 Binance 正在动里面东西的信号,立刻重试等于在 rate limit 上白白扣点数。 建议用指数退避(30 秒起跳,每次加倍到 5 分钟为上限), 或直接等下个小时再试——维护通常有固定的窗口。我可以直接把整个账户切成 Hedge Mode 然后就别再切了吗?
dualSidePosition 一次设好之后不需要频繁改—— 真的需要频繁切换 One-way 与 Hedge 的策略非常少。整合的影响 主要落在要改的那个瞬间;你只要一辈子只切一次, 之后就跟你无关了。Get started
把切 position mode、清 open orders、路由不同错误码这些逻辑搬进 TVSBot 的执行端——用你自己的 API key,先 dry-run,账户层级风控自己设。
免费开始使用