取消单个持仓盈亏请求
cancelPnLSingle() 用来取消单个持仓 PnL 订阅。
app.cancelPnLSingle(reqId)取消时必须传原请求的 reqId。它不会按 conId 取消,而是按请求编号取消。
reqPnLSingle(reqId, account, modelCode, conid) -> pnlSingle(...) -> cancelPnLSingle(reqId)如果请求没有返回 pnlSingle(),也可以取消。对新手来说,最稳妥的写法是在 finally 中检查连接状态并取消订阅。
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 CancelPnLSingleApp(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) self.ready = threading.Event() self.accounts_ready = threading.Event() self.pnl_single_ready = threading.Event() self.accounts = [] self.row_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 pnlSingle(self, reqId, pos, dailyPnL, unrealizedPnL, realizedPnL, value): self.row_count += 1 self.pnl_single_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 = CancelPnLSingleApp()req_id = 9302cancel_sent = False
try: app.connect("127.0.0.1", 7497, clientId=123) 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.reqPnLSingle(req_id, app.accounts[0], "", 265598) app.pnl_single_ready.wait(5)
rows_before_cancel = app.row_count app.cancelPnLSingle(req_id) cancel_sent = True time.sleep(0.5) rows_after_cancel = app.row_count
finally: if app.isConnected(): app.disconnect()
print(f"SINGLE_CANCEL_SENT={cancel_sent}")print(f"SINGLE_ROWS_BEFORE_CANCEL={rows_before_cancel}")print(f"SINGLE_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()}")SINGLE_CANCEL_SENT=TrueSINGLE_ROWS_BEFORE_CANCEL=0SINGLE_ROWS_AFTER_CANCEL_WAIT=0INFO_CODES=2104,2106,2158NON_INFO_ERROR_COUNT=0IS_CONNECTED_AFTER_DISCONNECT=False账户没有 AAPL 持仓,所以取消前后回调行数都是 0。取消动作本身没有产生非信息类错误。
没有收到回调还要取消吗?
Section titled “没有收到回调还要取消吗?”建议取消。请求可能已经被 TWS 接收,只是因为没有对应持仓而没有返回数据。
可以用 conId 取消吗?
Section titled “可以用 conId 取消吗?”不可以。取消参数是 reqId,不是合约 ID。
可以重复取消吗?
Section titled “可以重复取消吗?”程序应避免重复取消同一个活跃请求。更好的做法是用状态表记录请求是否已经取消。