取消按模型持仓请求
cancelPositionsMulti() 用来取消由 reqPositionsMulti() 发起的实时持仓更新。
app.cancelPositionsMulti(reqId)它必须使用原请求的 reqId。如果请求时用的是 9201,取消时也要传 9201。
和 positionMultiEnd() 的区别
Section titled “和 positionMultiEnd() 的区别”| 名称 | 中文含义 |
|---|---|
positionMultiEnd(reqId) | 某个请求编号的初始持仓批次已经返回完成 |
cancelPositionsMulti(reqId) | 主动停止继续接收这个请求编号的持仓更新 |
收到 positionMultiEnd(reqId) 不代表订阅已经取消。若程序只需要一次快照,建议收到结束回调后显式取消。
reqPositionsMulti(reqId, account, modelCode) -> positionMulti(...) -> positionMultiEnd(reqId) -> cancelPositionsMulti(reqId)取消后短时间内可能仍有已排队回调。判断是否正常时,不要只看某一瞬间,而要看取消后回调数量是否继续增长,以及是否出现非信息类错误。
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 CancelPositionsMultiApp(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) self.ready = threading.Event() self.accounts_ready = threading.Event() self.positions_end = threading.Event() self.accounts = [] self.position_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 positionMulti(self, reqId, account, modelCode, contract, pos, avgCost): self.position_count += 1
def positionMultiEnd(self, reqId): self.positions_end.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 = CancelPositionsMultiApp()req_id = 9201cancel_sent = False
try: app.connect("127.0.0.1", 7497, clientId=121) thread = threading.Thread(target=app.run, daemon=True) thread.start()
if not app.ready.wait(8): raise RuntimeError("等待 nextValidId 超时")
app.reqManagedAccts() if not app.accounts_ready.wait(5): raise RuntimeError("等待账户列表超时")
app.reqPositionsMulti(req_id, app.accounts[0], "") if not app.positions_end.wait(8): raise RuntimeError("等待 positionMultiEnd 超时")
rows_before_cancel = app.position_count
app.cancelPositionsMulti(req_id) cancel_sent = True time.sleep(0.5)
rows_after_cancel = app.position_count
finally: if app.isConnected(): app.disconnect()
print(f"POSITION_MULTI_END_RECEIVED={app.positions_end.is_set()}")print(f"CANCEL_SENT={cancel_sent}")print(f"ROWS_BEFORE_CANCEL={rows_before_cancel}")print(f"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()}")POSITION_MULTI_END_RECEIVED=TrueCANCEL_SENT=TrueROWS_BEFORE_CANCEL=0ROWS_AFTER_CANCEL_WAIT=0INFO_CODES=2104,2106,2158NON_INFO_ERROR_COUNT=0IS_CONNECTED_AFTER_DISCONNECT=FalseTWS 模拟账户没有持仓,因此取消前后行数都是 0。这里的重点是结束回调已收到、取消已发送,并且没有非信息类错误。
取消时传错 reqId 会怎样?
Section titled “取消时传错 reqId 会怎样?”可能取消不到原来的订阅,导致程序继续收到该请求的数据,或者让状态管理变得混乱。建议把 reqId 和请求对象一起保存。
可以在没有收到 positionMultiEnd() 前取消吗?
Section titled “可以在没有收到 positionMultiEnd() 前取消吗?”可以,但新手程序更容易写错状态。常见做法是先等待初始批次结束,再根据需要取消。
取消后可以重新请求吗?
Section titled “取消后可以重新请求吗?”可以。重新调用 reqPositionsMulti() 并使用新的 reqId,就可以再次订阅。