跳转到内容

取消单个持仓盈亏请求

cancelPnLSingle() 用来取消单个持仓 PnL 订阅。

app.cancelPnLSingle(reqId)

取消时必须传原请求的 reqId。它不会按 conId 取消,而是按请求编号取消。

reqPnLSingle(reqId, account, modelCode, conid)
-> pnlSingle(...)
-> cancelPnLSingle(reqId)

如果请求没有返回 pnlSingle(),也可以取消。对新手来说,最稳妥的写法是在 finally 中检查连接状态并取消订阅。

import threading
import time
from ibapi.client import EClient
from 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 = 9302
cancel_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=True
SINGLE_ROWS_BEFORE_CANCEL=0
SINGLE_ROWS_AFTER_CANCEL_WAIT=0
INFO_CODES=2104,2106,2158
NON_INFO_ERROR_COUNT=0
IS_CONNECTED_AFTER_DISCONNECT=False

账户没有 AAPL 持仓,所以取消前后回调行数都是 0。取消动作本身没有产生非信息类错误。

建议取消。请求可能已经被 TWS 接收,只是因为没有对应持仓而没有返回数据。

不可以。取消参数是 reqId,不是合约 ID。

程序应避免重复取消同一个活跃请求。更好的做法是用状态表记录请求是否已经取消。