跳转到内容

取消账户摘要

cancelAccountSummary() 用来停止某个 reqAccountSummary() 请求创建的账户摘要订阅。

self.cancelAccountSummary(reqId)

这里的“取消”不是取消账户、取消交易权限,也不是清空已经收到的数据。它只是告诉 TWS 或 IB Gateway:这个 API 客户端不再需要这个 reqId 对应的账户摘要更新。

Python API 中的调用形式如下:

app.cancelAccountSummary(reqId)
参数中文含义说明
reqId要取消的账户摘要请求 ID必须和之前 reqAccountSummary(reqId, groupName, tags) 使用的 reqId 一致

本地官方源码说明也很直接:cancelAccountSummary(reqId) cancels the request for Account Window Summary tab data。

账户摘要请求是订阅式请求。只要连接还在,并且订阅没有取消,TWS 就可能继续推送更新。

常见取消时机:

场景建议
只取一次账户摘要收到 accountSummaryEnd(reqId) 后取消
页面或组件关闭在组件销毁、页面离开或服务停止前取消
重新发起同类请求先取消旧 reqId,再创建新请求
程序退出先取消订阅,再断开 API 连接

如果是长期运行的账户面板,可以不立刻取消,但需要明确保存订阅状态,并在不再需要时取消。

cancelAccountSummary() 不会触发 cancelAccountSummaryEnd() 这类回调。正常情况下,它只是发送一条取消消息。

因此程序通常通过这些信号判断流程是否正常:

  • 发送取消前已经收到 accountSummaryEnd(reqId)
  • 取消调用没有触发非信息类错误。
  • 程序退出前连接能正常断开。
  • 短时间内没有继续收到该 reqId 的摘要更新。

不要等待一个不存在的“取消完成”回调,否则程序会无意义地卡住。

下面示例先请求账户摘要,收到初始批次结束信号后,立即取消订阅。公开输出只保留字段结构,不打印账户号和金额。

import threading
import time
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
REQ_ID = 9005
TAGS = "NetLiquidation,BuyingPower,AvailableFunds"
class CancelAccountSummaryApp(EWrapper, EClient):
def __init__(self):
EClient.__init__(self, self)
self.ready = threading.Event()
self.done = threading.Event()
self.rows = []
self.errors_seen = []
def nextValidId(self, orderId):
self.ready.set()
def accountSummary(self, reqId, account, tag, value, currency):
# account 和 value 是敏感信息,这里只保留字段名和币种。
self.rows.append((reqId, tag, currency or "EMPTY"))
def accountSummaryEnd(self, reqId):
self.done.set()
def error(self, reqId, errorTime, errorCode, errorString, advancedOrderRejectJson=""):
if errorCode not in (2104, 2106, 2158):
self.errors_seen.append((reqId, errorCode, errorString))
app = CancelAccountSummaryApp()
try:
app.connect("127.0.0.1", 7497, clientId=964)
thread = threading.Thread(target=app.run, daemon=True)
thread.start()
if not app.ready.wait(8):
raise RuntimeError("等待 nextValidId 超时")
app.reqAccountSummary(REQ_ID, "All", TAGS)
if not app.done.wait(8):
raise RuntimeError("等待 accountSummaryEnd 超时")
rows_before_cancel = len(app.rows)
app.cancelAccountSummary(REQ_ID)
time.sleep(0.3)
rows_after_cancel = len(app.rows)
print(f"ROWS_BEFORE_CANCEL={rows_before_cancel}")
print(f"ROWS_AFTER_CANCEL_WAIT={rows_after_cancel}")
finally:
if app.isConnected():
app.disconnect()

这段代码里,time.sleep(0.3) 只是为了演示短暂等待后没有新增回调。正式项目不需要依赖固定等待来证明取消成功,更重要的是维护好请求生命周期。

使用TWS 模拟账户、127.0.0.1:7497 和独立 clientId 检查时,脱敏后的输出如下:

CONNECTED=True
REQUEST_ID=9005
CANCEL_SENT=True
ROWS_BEFORE_CANCEL=6
ROWS_AFTER_CANCEL_WAIT=6
ACCOUNT_COUNT=1
RAW_CALLBACK_ROWS=6
UNIQUE_FIELD_ROWS=3
TAGS_RECEIVED=AvailableFunds,BuyingPower,NetLiquidation
NON_INFO_ERROR_COUNT=0
IS_CONNECTED_AFTER_DISCONNECT=False

本次请求了 3 个字段,初始阶段收到 6 次原始回调,去重后是 3 个字段。发送取消后短暂等待没有新增回调,也没有非信息类错误。

现象常见原因处理方式
取消后程序仍等待误以为有取消完成回调不要等待不存在的回调;发送取消后继续正常清理
取消无效reqId 和原始请求不一致保存请求 ID,取消时使用同一个 reqId
多个订阅混在一起复用了还在运行的 reqId每个未完成请求使用唯一 reqId
退出时偶发报错先断开连接再取消订阅程序退出前先取消订阅,再断开连接
仍收到一两条回调取消消息和已在队列中的回调存在时间差reqId 和订阅状态过滤,避免旧数据污染新状态

在真实系统里,可以把账户摘要请求包装成这样的生命周期:

生成 reqId
发送 reqAccountSummary(reqId, "All", tags)
接收 accountSummary(...)
收到 accountSummaryEnd(reqId)
整理初始结果
调用 cancelAccountSummary(reqId)
清理本地订阅状态

如果页面需要持续展示账户摘要,则不要在 accountSummaryEnd() 后立刻取消,而是在页面关闭、用户停止订阅或程序退出时取消。

IBKR Campus: TWS API Documentation