取消按模型获取账户更新
cancelAccountUpdatesMulti(reqId) 用来取消由 reqAccountUpdatesMulti() 发起的按模型账户更新请求。取消时必须使用原来的 reqId,不是账户号,也不是模型组合代码。
app.cancelAccountUpdatesMulti(reqId)取消请求的关键点是:发起请求、接收回调、取消请求都围绕同一个 reqId 管理。
什么时候取消
Section titled “什么时候取消”常见取消时机如下:
| 场景 | 建议 |
|---|---|
| 页面关闭或用户切换账户 | 取消旧 reqId,再请求新账户 |
| 模型组合切换 | 取消旧模型请求,再用新 modelCode 发起请求 |
| 程序退出 | 先取消仍在使用的请求,再断开连接 |
| 只需要初始批次 | 收到 accountUpdateMultiEnd(reqId) 后取消 |
| 长时间订阅账户状态 | 保持连接,但要记录正在使用的 reqId |
如果你只是写一个命令行验证脚本,最稳妥的做法是:收到 accountUpdateMultiEnd(reqId) 后马上取消,并在 finally 里保证断开连接。
reqAccountUpdatesMulti(9101, account, modelCode, True) -> accountUpdateMulti(... reqId=9101 ...) -> accountUpdateMultiEnd(9101) -> cancelAccountUpdatesMulti(9101)取消后,短时间内可能还会看到少量已经排队的回调。判断取消是否正常,不能只看“是否还有一行回调”,更应该看:
- 是否用的是同一个
reqId。 - 是否已经收到了
accountUpdateMultiEnd(reqId)。 - 取消后等待一小段时间,回调数量是否继续快速增长。
- 是否出现非信息类错误。
Python 取消示例
Section titled “Python 取消示例”下面示例会记录取消前后的回调行数。公开输出只显示数量,不显示真实账户号和金额。
import threadingimport timefrom ibapi.client import EClientfrom ibapi.wrapper import EWrapper
REQ_ID = 9101INFO_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 = Falserows_before_cancel = 0rows_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=9101END_RECEIVED=TrueEND_REQID_MATCHES=TrueCANCEL_SENT=TrueROWS_BEFORE_CANCEL=50ROWS_AFTER_CANCEL_WAIT=50INFO_CODES=2104,2106,2158NON_INFO_ERROR_COUNT=0IS_CONNECTED_AFTER_DISCONNECT=False这次验证里,取消前收到 50 行回调,取消后等待 0.5 秒仍然是 50 行,没有新增回调,也没有非信息类错误。
和普通账户更新取消的区别
Section titled “和普通账户更新取消的区别”| 接口 | 取消方式 | 容易混淆的点 |
|---|---|---|
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)信息码 2104、2106、2158 是取消失败吗?
Section titled “信息码 2104、2106、2158 是取消失败吗?”不是。它们通常是数据服务连接状态提示。是否失败要看目标回调是否收到、取消后回调是否停止增长,以及是否有非信息类错误。