没等到回答的那张单,先查到它再决定重不重发

下单请求超时以后,先查这张单在不在,没查清就不重发。超时只说明你的程序在规定时间里没等到平台的回答,至于这张单送没送到、成没成交,它什么都没说。
下单时带了自定义订单号的,拿这个编号去查单张订单;没带的,打开当前委托和成交记录,按时间、方向、数量去对。查到了就照查到的状态往下走;查不到也先别认定它没到,隔一会儿再查一遍。
请求超时以后,这张单可能停在三种状态里
一个下单请求要走一个来回:你的程序发出去,平台收到、处理,再把结果送回来。来回中的任何一段出问题,你这边看到的都是同一行日志:请求超时。平台那边的实际情况却可能是下面三种之一。
- 没到。请求在去的路上丢了,平台从头到尾不知道有这张单。
- 到了,没成。平台收到了请求,但把它拒绝了(余额不够、数量不合规格这类原因),或者它是一张限价单,已经挂在那里等成交。
- 到了,也成了。平台收到并且成交了,只是结果在回来的路上丢了,或者回来的时候已经过了你设的超时时间。
Stripe 文档的错误处理进阶页讲网络错误时提到,碰上这类间歇性的问题,客户端并不知道服务器有没有收到请求。支付和交易在这一点上没有区别。
三种状态要做的事各不相同。没到的需要补一张;挂着的要决定留还是撤;已经成交的什么都不用补,再发一张就多买了一份。光凭日志里那一行,分不出是哪一种。
超时后先做哪几项核对:按订单号查,或者翻当前委托和成交记录
先让程序停止对这张单的自动重发。下单时带了自定义订单号,就用按订单号查询的功能查这一张;没带,就依次看当前委托(还挂着的单)、历史委托、成交记录,找发出请求那个时刻前后、方向和数量都对得上的那一笔。再看一眼持仓和可用余额有没有变。四处都看完,才判断这张单属于哪种状态。
查到了,按状态分开处理:
- 已全部成交:把成交数量和成交均价记回程序或你自己的账里,这张单到此结束。
- 挂着没成交:单子是有效的。还想要就留着,不想要就撤掉,撤完再查一次,确认状态变成了已撤销。
- 成交了一部分:先看剩下的部分还挂不挂着。还挂着的,想要就留着,不想要就撤掉;已经被取消的,要补的话,补的数量是原计划减去已成交的那部分。
- 被拒绝:读拒绝原因。余额或数量规格的问题,原样重发还是会被拒,改好以后换一个新编号再发。
查不到的时候多等一步。请求有可能还在平台那边处理,这时去查可能查不到。隔一小段时间再查一遍,间隔建议别短于你设的超时时间。连着两遍都查不到,持仓和余额也没有变化,再考虑重发:带了订单号、接口文档又写明重复编号不会生成第二张单的,沿用原来那个编号发;没带的,或者文档没写清的,照"没带订单号就重发"那一节的办法处理。
查询这个动作可以放心重复。MDN 的术语页照 RFC 9110(HTTP 协议现行的标准文本)的定义解释"幂等":发一次和发很多次,对服务器的效果一样,读取类的请求属于这一类。下单则相反,每发一次就多一张单。如果连查询都一直超时,说明网络或平台本身出了状况,这时停掉新的下单,换手机 App 或网页登录同一个账户去看。
自己起的订单号为什么能防重复下单
读取和删除这类请求是幂等的;用来新建东西的 POST 请求不保证幂等,发两次可能就建出两个。下单正是新建一张订单,所以超时后别让程序自己接着重发,除非先查清上一次没有生效,或者平台能认出这是同一张单。超时后先查单,做的就是前一件事。
让下单变得幂等的办法,是下单时自己带一个唯一的编号。服务器记住这个编号对应的处理结果,之后再收到同一个编号,就知道这是同一件事的重复,不会当成新的一件来办。Stripe 的做法是:带着同一个幂等键和同样的参数重发,拿回来的是第一次的结果;同一个键配了不同的参数则报错;键在 24 小时后过期。IETF 有一份幂等键请求头草案想把这种做法写成通用规则,第 07 版发布于 2025 年 10 月,目前是已过期状态,没有成为正式标准。草案里列的情形可以当作参考:键相同、内容不同时返回 422;第一次请求还在处理中又收到重试,返回 409。这两个数字都是错误码,平台照这份草案做的话,收到 409 说明第一次请求它已经收到、还在处理。
交易领域有同样的设计。FIX 协议(券商和交易所之间通用的下单消息格式)里每张订单带一个 ClOrdID 字段,OnixS 网站上的 FIX 4.4 字段词典对它的说明是:由买方给订单指定的唯一标识,一个交易日内必须唯一;订单跨日、全天交易的场合,建议在编号里带上日期,保证多日内也不重复。
编号要在第一次发出请求之前就生成并存下来,重试时用的必须还是它。如果程序每重试一次就生成一个新编号,对平台来说就是几张互不相干的单,去重形同虚设。编号也有期限:Stripe 是 24 小时,FIX 的最低要求是一个交易日,过了期限,同一个编号可能被当成新请求。你那家平台的下单接口有没有这类字段、叫什么名字、收到重复编号是拒绝还是返回原来那张单、编号多久之后可以复用,都要到它的接口文档里查下单那一页。
RFC 9110 发布于 2022 年 6 月;MDN 的术语页、Stripe 的错误处理进阶页、IETF 的幂等键草案和 FIX 4.4 字段词典这四处,说的是 2026 年 10 月页面上的写法。
没带订单号就重发会重复下单,已经下重了怎么处理
比如策略要用市价单买入 0.5 个币,当时价格 2000 USDT,计划花 1000 USDT。程序的设置是:超时 5 秒,失败后重试 2 次,下单不带自定义订单号。
- 第 0 秒,程序发出第一张单。平台这时处理得慢。
- 第 5 秒,程序没等到回答,记一行"超时",立刻发出第二张单,内容一样。
- 第 6 秒,平台处理完第一张:成交 0.5 个。结果发回来的时候,程序已经不等它了。
- 第 7 秒,第二张也成交 0.5 个,这次结果按时到了。程序记账:买入 0.5 个,成功。
账户里实际有 1 个,花了 2000 USDT,是计划的两倍;程序的账上只有 0.5 个。之后策略发出卖出信号,程序卖掉它记得的 0.5 个,剩下的 0.5 个留在账户里,没有任何逻辑在管。如果第二张也超时了而平台同样成交,程序还会发第三张,持仓变成 1.5 个。按这套设置,一次信号能买到的上限是 0.5 × (1 + 重试次数),重试 2 次就是 1.5 个、3000 USDT。
已经超时、又没带编号,按下面的顺序收拾:
- 暂停机器人,或者至少关掉它在这个交易对上的自动下单。
- 打开当前委托,把这次下单在这个交易对上留下的挂单撤掉,撤完刷新确认。
- 看成交记录,数一数发出请求那个时刻前后、同方向同数量的成交有几笔。
- 把账户里的实际持仓和可用余额,同程序记的数字逐项比。多出来的部分由你决定卖掉还是留下,并把程序里的记录改成和账户一致。
- 两边一致以后再恢复运行。
程序记的持仓和账户对不上这件事,在掉线重连以后也会发生,交易机器人不下单、报错怎么办的断线一节讲了重连后的对账。
超时时间、重试次数和订单号去重在机器人里怎么设
你用的工具里未必每一项都有,有哪项就设哪项。
- 请求超时时间。程序等平台的回答等多久。调长只是多等一会儿,能减少"其实成了却被记成超时"的情况,消除不了。调得太短,平台稍慢一点就会被记成超时。底层用 CCXT 的机器人,这个时间默认是 10 秒,见 CCXT 是什么,它统一了哪些叫法。
- 失败重试次数。有的工具对所有请求用同一个数字,有的把下单和查询分开设。没有订单号去重的时候,建议把下单的重试设成 0,让它超时后停下来等你查;查询的重试可以保留。
- 重试间隔。两次重试之间等多久。间隔为 0 意味着上一张单可能还在处理,下一张就已经发出去了。被限频以后间隔该怎么拉长,写在接口返回 429 之后怎么重试里。
- 自定义订单号或订单去重。开关的名字可能是"客户端订单号""订单号前缀""防重复下单"之类。开了以后要确认一件事:重试时沿用的是同一个编号。说明里没写的,去问工具的提供方。
- 启动或重连时同步订单和持仓。程序恢复运行时先从平台拉一次真实的委托和持仓,再做决定。有这个选项就打开。
改完设置,用最小下单量跑一张单,看日志里的下单请求有没有带上编号。以后日志里再出现超时,看它后面跟着的是一次查询还是又一次下单。
接着读:收到"已取消"就按原数量重发,是另一种把单下多的路子,GTC、IOC、FOK 三种订单时效里讲了取消以后该补多少;用图表警报触发下单的,警报日志记了超时而接收端可能已经下了单,排查顺序在警报响了却没下单怎么查。