取消账户摘要
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。
什么时候取消
Section titled “什么时候取消”账户摘要请求是订阅式请求。只要连接还在,并且订阅没有取消,TWS 就可能继续推送更新。
常见取消时机:
| 场景 | 建议 |
|---|---|
| 只取一次账户摘要 | 收到 accountSummaryEnd(reqId) 后取消 |
| 页面或组件关闭 | 在组件销毁、页面离开或服务停止前取消 |
| 重新发起同类请求 | 先取消旧 reqId,再创建新请求 |
| 程序退出 | 先取消订阅,再断开 API 连接 |
如果是长期运行的账户面板,可以不立刻取消,但需要明确保存订阅状态,并在不再需要时取消。
没有专门的取消完成回调
Section titled “没有专门的取消完成回调”cancelAccountSummary() 不会触发 cancelAccountSummaryEnd() 这类回调。正常情况下,它只是发送一条取消消息。
因此程序通常通过这些信号判断流程是否正常:
- 发送取消前已经收到
accountSummaryEnd(reqId)。 - 取消调用没有触发非信息类错误。
- 程序退出前连接能正常断开。
- 短时间内没有继续收到该
reqId的摘要更新。
不要等待一个不存在的“取消完成”回调,否则程序会无意义地卡住。
Python 示例
Section titled “Python 示例”下面示例先请求账户摘要,收到初始批次结束信号后,立即取消订阅。公开输出只保留字段结构,不打印账户号和金额。
import threadingimport timefrom ibapi.client import EClientfrom ibapi.wrapper import EWrapper
REQ_ID = 9005TAGS = "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=TrueREQUEST_ID=9005CANCEL_SENT=TrueROWS_BEFORE_CANCEL=6ROWS_AFTER_CANCEL_WAIT=6ACCOUNT_COUNT=1RAW_CALLBACK_ROWS=6UNIQUE_FIELD_ROWS=3TAGS_RECEIVED=AvailableFunds,BuyingPower,NetLiquidationNON_INFO_ERROR_COUNT=0IS_CONNECTED_AFTER_DISCONNECT=False本次请求了 3 个字段,初始阶段收到 6 次原始回调,去重后是 3 个字段。发送取消后短暂等待没有新增回调,也没有非信息类错误。
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 取消后程序仍等待 | 误以为有取消完成回调 | 不要等待不存在的回调;发送取消后继续正常清理 |
| 取消无效 | reqId 和原始请求不一致 | 保存请求 ID,取消时使用同一个 reqId |
| 多个订阅混在一起 | 复用了还在运行的 reqId | 每个未完成请求使用唯一 reqId |
| 退出时偶发报错 | 先断开连接再取消订阅 | 程序退出前先取消订阅,再断开连接 |
| 仍收到一两条回调 | 取消消息和已在队列中的回调存在时间差 | 按 reqId 和订阅状态过滤,避免旧数据污染新状态 |
在真实系统里,可以把账户摘要请求包装成这样的生命周期:
生成 reqId发送 reqAccountSummary(reqId, "All", tags)接收 accountSummary(...)收到 accountSummaryEnd(reqId)整理初始结果调用 cancelAccountSummary(reqId)清理本地订阅状态如果页面需要持续展示账户摘要,则不要在 accountSummaryEnd() 后立刻取消,而是在页面关闭、用户停止订阅或程序退出时取消。