接收下一个有效 ID
nextValidId(orderId) 是 TWS 告诉 API 客户端“下一个可用订单 ID”的回调。
这个回调非常重要。很多新手下单失败,不是合约或订单字段写错,而是还没等到 nextValidId() 就调用了 placeOrder()。
def nextValidId(self, orderId: int) -> None: self.next_order_id = orderId self.order_id_ready.set()建议把 orderId 保存到客户端状态里,并用 threading.Event() 或类似机制通知主逻辑:现在可以安全下单了。
order_id = app.next_order_idapp.placeOrder(order_id, contract, order)
# 成功发出一个订单请求后,本地编号向后推进。app.next_order_id += 1是否“成功发出”不等于是否成交。只要一个订单请求已经用某个 orderId 发送给 TWS,就不要再用同一个 orderId 提交另一个订单。
推荐封装方式
Section titled “推荐封装方式”实际项目里可以把“取号并递增”封装成一个小方法,避免不同下单路径各自操作同一个变量:
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 | 程序里保存的本地订单编号变量。 |
clientId | API 客户端编号,会影响订单归属和可见范围。 |
order_id_ready | 程序里的同步信号,用来表示已经收到可用订单编号。 |
多客户端注意事项
Section titled “多客户端注意事项”如果只有一个 API 客户端向账户提交订单,通常可以在 nextValidId() 返回值基础上逐次加一。
如果多个 API 客户端、TWS 手工订单或 Master Client 同时参与,订单编号必须大于程序已经看到的其他订单编号。尤其是在收到 openOrder()、orderStatus() 或调用 reqAllOpenOrders() 后,后面提交的新订单不要使用比这些回调里更小的 orderId。
| 问题 | 原因 |
|---|---|
placeOrder() 没反应 | 可能还没启动 app.run() 事件循环。 |
| 订单 ID 重复 | 本地没有在每次提交后递增。 |
| 重启后 ID 变化 | TWS 会维护订单序列,不能假设永远从 1 开始。 |
| 多个程序同时下单 | 需要协调不同客户端的 ID 使用。 |
和 openOrder / orderStatus 的关系
Section titled “和 openOrder / orderStatus 的关系”当程序是 Master Client,或者调用了 reqAllOpenOrders(),TWS 可能会把其它客户端或手工绑定订单的状态也回传给你。此时后续新订单的 orderId 应该大于你已经看到的订单编号。
可以这样理解:
| 来源 | 对订单 ID 管理的影响 |
|---|---|
nextValidId(orderId) | 给出 TWS 认为下一个可用的起点。 |
openOrder(orderId, ...) | 告诉你已经可见的活动订单编号。 |
orderStatus(orderId, ...) | 告诉你订单状态变化,也会带订单编号。 |
| 本地递增变量 | 程序实际提交新订单时使用。 |
保守做法是把本地 next_order_id 调整为 max(next_order_id, seen_order_id + 1),确保不会低于已经看到的订单编号。