跳转到内容

取消按模型获取账户更新

cancelAccountUpdatesMulti(reqId) 用来取消由 reqAccountUpdatesMulti() 发起的按模型账户更新请求。取消时必须使用原来的 reqId,不是账户号,也不是模型组合代码。

app.cancelAccountUpdatesMulti(reqId)

取消请求的关键点是:发起请求、接收回调、取消请求都围绕同一个 reqId 管理。

常见取消时机如下:

场景建议
页面关闭或用户切换账户取消旧 reqId,再请求新账户
模型组合切换取消旧模型请求,再用新 modelCode 发起请求
程序退出先取消仍在使用的请求,再断开连接
只需要初始批次收到 accountUpdateMultiEnd(reqId) 后取消
长时间订阅账户状态保持连接,但要记录正在使用的 reqId

如果你只是写一个命令行验证脚本,最稳妥的做法是:收到 accountUpdateMultiEnd(reqId) 后马上取消,并在 finally 里保证断开连接。

reqAccountUpdatesMulti(9101, account, modelCode, True)
-> accountUpdateMulti(... reqId=9101 ...)
-> accountUpdateMultiEnd(9101)
-> cancelAccountUpdatesMulti(9101)

取消后,短时间内可能还会看到少量已经排队的回调。判断取消是否正常,不能只看“是否还有一行回调”,更应该看:

  • 是否用的是同一个 reqId
  • 是否已经收到了 accountUpdateMultiEnd(reqId)
  • 取消后等待一小段时间,回调数量是否继续快速增长。
  • 是否出现非信息类错误。

下面示例会记录取消前后的回调行数。公开输出只显示数量,不显示真实账户号和金额。

import threading
import time
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
REQ_ID = 9101
INFO_CODES = {2100, 2104, 2106, 2158}
class CancelAccountUpdatesMultiApp(EWrapper, EClient):
def __init__(self):
EClient.__init__(self, self)
self.ready = threading.Event()
self.managed_ready = threading.Event()
self.multi_end = threading.Event()
self.managed_accounts = []
self.rows = []
self.end_req_id = None
self.info_codes = []
self.errors_seen = []
def nextValidId(self, orderId):
self.ready.set()
def managedAccounts(self, accountsList):
self.managed_accounts = [item for item in accountsList.split(",") if item]
self.managed_ready.set()
def accountUpdateMulti(self, reqId, account, modelCode, key, value, currency):
if reqId == REQ_ID:
self.rows.append((key, currency or "EMPTY"))
def accountUpdateMultiEnd(self, reqId):
self.end_req_id = reqId
self.multi_end.set()
def error(self, reqId, errorTime, errorCode, errorString, advancedOrderRejectJson=""):
if errorCode in INFO_CODES:
self.info_codes.append(errorCode)
else:
self.errors_seen.append((reqId, errorCode, errorString))
app = CancelAccountUpdatesMultiApp()
cancel_sent = False
rows_before_cancel = 0
rows_after_cancel = 0
try:
app.connect("127.0.0.1", 7497, clientId=977)
thread = threading.Thread(target=app.run, daemon=True)
thread.start()
if not app.ready.wait(8):
raise RuntimeError("等待 nextValidId 超时")
app.reqManagedAccts()
if not app.managed_ready.wait(8):
raise RuntimeError("等待 managedAccounts 超时")
account = app.managed_accounts[0]
app.reqAccountUpdatesMulti(REQ_ID, account, "", True)
if not app.multi_end.wait(8):
raise RuntimeError("等待 accountUpdateMultiEnd 超时")
rows_before_cancel = len(app.rows)
app.cancelAccountUpdatesMulti(REQ_ID)
cancel_sent = True
time.sleep(0.5)
rows_after_cancel = len(app.rows)
finally:
if app.isConnected():
app.disconnect()
print(f"REQUEST_ID={REQ_ID}")
print(f"END_RECEIVED={app.multi_end.is_set()}")
print(f"END_REQID_MATCHES={app.end_req_id == REQ_ID}")
print(f"CANCEL_SENT={cancel_sent}")
print(f"ROWS_BEFORE_CANCEL={rows_before_cancel}")
print(f"ROWS_AFTER_CANCEL_WAIT={rows_after_cancel}")
print("INFO_CODES=" + (",".join(map(str, sorted(set(app.info_codes)))) if app.info_codes else "NONE"))
print(f"NON_INFO_ERROR_COUNT={len(app.errors_seen)}")
print(f"IS_CONNECTED_AFTER_DISCONNECT={app.isConnected()}")

脱敏后的参考输出如下:

REQUEST_ID=9101
END_RECEIVED=True
END_REQID_MATCHES=True
CANCEL_SENT=True
ROWS_BEFORE_CANCEL=50
ROWS_AFTER_CANCEL_WAIT=50
INFO_CODES=2104,2106,2158
NON_INFO_ERROR_COUNT=0
IS_CONNECTED_AFTER_DISCONNECT=False

这次验证里,取消前收到 50 行回调,取消后等待 0.5 秒仍然是 50 行,没有新增回调,也没有非信息类错误。

接口取消方式容易混淆的点
reqAccountUpdates()reqAccountUpdates(False, account)取消时传账户号
reqAccountUpdatesMulti()cancelAccountUpdatesMulti(reqId)取消时传请求编号

不要用 reqAccountUpdates(False, account) 去取消 multi 请求,也不要用账户号替代 reqId

取消后为什么还可能看到回调?

Section titled “取消后为什么还可能看到回调?”

异步接口里,取消指令发出时,客户端和 TWS 之间可能已经有少量数据在队列里。只要回调没有继续增长,并且没有非信息类错误,通常可以认为取消流程正常。

cancelAccountUpdatesMulti() 需要在断开连接前调用吗?

Section titled “cancelAccountUpdatesMulti() 需要在断开连接前调用吗?”

建议调用。虽然断开连接会结束这个 socket 会话,但显式取消可以让程序状态更清楚,也方便排查多个请求同时存在的问题。

取消时可以换一个新的 reqId 吗?

Section titled “取消时可以换一个新的 reqId 吗?”

不可以。取消必须使用原请求的 reqId。如果发起请求用的是 9101,取消也必须是:

app.cancelAccountUpdatesMulti(9101)

信息码 210421062158 是取消失败吗?

Section titled “信息码 2104、2106、2158 是取消失败吗?”

不是。它们通常是数据服务连接状态提示。是否失败要看目标回调是否收到、取消后回调是否停止增长,以及是否有非信息类错误。

IBKR Campus: TWS API Documentation