跳转到内容

接收下一个有效 ID

nextValidId(orderId) 是 TWS 告诉 API 客户端“下一个可用订单 ID”的回调。

这个回调非常重要。很多新手下单失败,不是合约或订单字段写错,而是还没等到 nextValidId() 就调用了 placeOrder()

官方参考:IBKR Campus - TWS API 文档

def nextValidId(self, orderId: int) -> None:
self.next_order_id = orderId
self.order_id_ready.set()

建议把 orderId 保存到客户端状态里,并用 threading.Event() 或类似机制通知主逻辑:现在可以安全下单了。

order_id = app.next_order_id
app.placeOrder(order_id, contract, order)
# 成功发出一个订单请求后,本地编号向后推进。
app.next_order_id += 1

是否“成功发出”不等于是否成交。只要一个订单请求已经用某个 orderId 发送给 TWS,就不要再用同一个 orderId 提交另一个订单。

实际项目里可以把“取号并递增”封装成一个小方法,避免不同下单路径各自操作同一个变量:

def take_next_order_id(self) -> int:
if self.next_order_id is None:
raise RuntimeError("还没有收到 nextValidId,不能下单")
order_id = self.next_order_id
self.next_order_id += 1
return order_id

调用下单时:

order_id = app.take_next_order_id()
app.placeOrder(order_id, contract, order)

如果程序是多线程下单,还需要给这段取号逻辑加锁,避免两个线程同时拿到同一个 orderId

ALL_IDS=1,1

第一项来自连接握手自动触发的 nextValidId(),第二项来自显式调用 reqIds(-1) 后触发的 nextValidId()

两次都返回 1,说明这期间没有新的 API 订单占用编号。如果中间提交了订单,之后返回值通常会向后推进。

字段中文说明
orderId下一个可用于 placeOrder() 的订单编号。
next_order_id程序里保存的本地订单编号变量。
clientIdAPI 客户端编号,会影响订单归属和可见范围。
order_id_ready程序里的同步信号,用来表示已经收到可用订单编号。

如果只有一个 API 客户端向账户提交订单,通常可以在 nextValidId() 返回值基础上逐次加一。

如果多个 API 客户端、TWS 手工订单或 Master Client 同时参与,订单编号必须大于程序已经看到的其他订单编号。尤其是在收到 openOrder()orderStatus() 或调用 reqAllOpenOrders() 后,后面提交的新订单不要使用比这些回调里更小的 orderId

问题原因
placeOrder() 没反应可能还没启动 app.run() 事件循环。
订单 ID 重复本地没有在每次提交后递增。
重启后 ID 变化TWS 会维护订单序列,不能假设永远从 1 开始。
多个程序同时下单需要协调不同客户端的 ID 使用。

当程序是 Master Client,或者调用了 reqAllOpenOrders(),TWS 可能会把其它客户端或手工绑定订单的状态也回传给你。此时后续新订单的 orderId 应该大于你已经看到的订单编号。

可以这样理解:

来源对订单 ID 管理的影响
nextValidId(orderId)给出 TWS 认为下一个可用的起点。
openOrder(orderId, ...)告诉你已经可见的活动订单编号。
orderStatus(orderId, ...)告诉你订单状态变化,也会带订单编号。
本地递增变量程序实际提交新订单时使用。

保守做法是把本地 next_order_id 调整为 max(next_order_id, seen_order_id + 1),确保不会低于已经看到的订单编号。