取消账户盈亏订阅
cancelPnL() 用来取消账户级 PnL 订阅。
app.cancelPnL(reqId)它按 reqId 取消,不按账户号取消。请求时传给 reqPnL() 的请求编号,取消时也要传同一个。
reqPnL(reqId, account, modelCode) -> pnl(...) -> cancelPnL(reqId)如果程序只需要确认账户级 PnL 是否可用,收到第一条 pnl() 后就可以取消。如果是交易看板,则应在页面关闭、用户断开、程序退出前取消。
Python 取消示例
Section titled “Python 取消示例”import threadingimport time
from ibapi.client import EClientfrom ibapi.wrapper import EWrapper
INFO_CODES = {2100, 2103, 2104, 2105, 2106, 2107, 2108, 2158}
class CancelAccountPnLApp(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) self.ready = threading.Event() self.accounts_ready = threading.Event() self.pnl_ready = threading.Event() self.accounts = [] self.pnl_count = 0 self.info_codes = [] self.errors_seen = []
def nextValidId(self, orderId): self.ready.set()
def managedAccounts(self, accountsList): self.accounts = [item for item in accountsList.split(",") if item] self.accounts_ready.set()
def pnl(self, reqId, dailyPnL, unrealizedPnL, realizedPnL): self.pnl_count += 1 self.pnl_ready.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 = CancelAccountPnLApp()req_id = 9301cancel_sent = False
try: app.connect("127.0.0.1", 7497, clientId=125) thread = threading.Thread(target=app.run, daemon=True) thread.start()
connected = app.ready.wait(8)
if not connected: raise RuntimeError("等待 nextValidId 超时")
app.reqManagedAccts() if not app.accounts_ready.wait(5): raise RuntimeError("等待账户列表超时")
app.reqPnL(req_id, app.accounts[0], "") app.pnl_ready.wait(8)
rows_before_cancel = app.pnl_count app.cancelPnL(req_id) cancel_sent = True time.sleep(0.5) rows_after_cancel = app.pnl_count
finally: if app.isConnected(): app.disconnect()
print(f"ACCOUNT_CANCEL_SENT={cancel_sent}")print(f"ACCOUNT_ROWS_BEFORE_CANCEL={rows_before_cancel}")print(f"ACCOUNT_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()}")ACCOUNT_CANCEL_SENT=TrueACCOUNT_ROWS_BEFORE_CANCEL=0ACCOUNT_ROWS_AFTER_CANCEL_WAIT=0INFO_CODES=2104,2106,2158NON_INFO_ERROR_COUNT=0IS_CONNECTED_AFTER_DISCONNECT=FalseTWS 模拟账户没有收到账户级 PnL 回调,取消前后行数都是 0。取消动作没有产生非信息类错误。
取消时要传账户号吗?
Section titled “取消时要传账户号吗?”不需要。cancelPnL() 只接收 reqId。
可以多个账户同时订阅吗?
Section titled “可以多个账户同时订阅吗?”可以,但每个订阅应使用不同 reqId,并在状态表中记录 reqId -> account/modelCode 的关系。
断开连接前还需要取消吗?
Section titled “断开连接前还需要取消吗?”建议取消。即使断开连接会终止会话,显式取消能让程序状态更清晰,也方便日志排查。