Python版TWS API中递增nextOrderId时出现KeyError问题
Python版TWS API中递增nextOrderId时出现KeyError问题
我看了你遇到的问题,核心是nextOrderId的管理混乱导致BAR_TRACKER的键不匹配,进而触发KeyError。咱们一步步拆解问题根源,再给出落地的修复方案:
问题根源分析
- 键不匹配的核心原因:你在
marketMonitor里每次循环都会先执行app.nextOrderId +=1,然后用这个新ID请求历史数据;但在historicalDataUpdate里,你又用self.nextOrderId作为键存入BAR_TRACKER——这里的问题是,当后续操作(比如tradeStrategy里的循环)再次修改nextOrderId时,你再用它去访问BAR_TRACKER,这个ID已经不是当初存入数据时的那个键了,自然找不到对应条目。 tradeStrategy的循环逻辑错误:你写了for contract in BAR_TRACKER:,但BAR_TRACKER的键其实是之前的orderId,而不是合约对象。循环里你却直接用app.nextOrderId去取值,这完全是错位的,必然触发KeyError。nextOrderId的滥用:nextOrderId应该用来生成下一个新请求的唯一ID,而不是用来访问已存储的数据——已存储的数据应该用请求时实际使用的ID来关联。
修复方案
咱们从四个关键部分修改代码,彻底解决这个问题:
1. 用请求的reqId作为BAR_TRACKER的键(最可靠的方式)
historicalDataUpdate里的reqId就是你请求历史数据时传入的orderId,用它作为键完全能保证和请求一一对应,不会受nextOrderId后续变化的影响。同时要修改类里的合约存储方式,用字典替代单个变量(避免多合约覆盖):
class ibapi(EClient, EWrapper): def __init__(self): EClient.__init__(self,self) self.nextOrderId = 1 self.contract_dict = {} # 新增:用reqId作为键存储合约,避免多合约覆盖 def nextValidId(self, orderId: OrderId): self.nextOrderId = orderId def contractDetails(self, reqId: int, contractDetails: ContractDetails): self.contract_dict[reqId] = contractDetails.contract # 存入字典,替代单个变量 def historicalDataUpdate(self, reqId, bar): # 用reqId作为键,而非动态变化的nextOrderId if reqId not in BAR_TRACKER: BAR_TRACKER[reqId] = (self.contract_dict[reqId], []) BAR_TRACKER[reqId][1].append((bar.date, bar.open, bar.high, bar.low, bar.close, bar.volume))
2. 修正marketMonitor的nextOrderId使用逻辑
每次请求前,先获取当前的nextOrderId作为本次请求的reqId,然后再递增nextOrderId,确保每个请求用唯一的ID,并且这个ID能和contract_dict、BAR_TRACKER正确关联:
def marketMonitor(app: ibapi, contracts: list): for contract in contracts: current_req_id = app.nextOrderId # 先获取本次请求要用的ID app.nextOrderId += 1 # 递增,留给下一个请求用 # 用current_req_id请求数据,这个ID会在historicalDataUpdate里作为reqId传入 app.reqHistoricalData(current_req_id, contract=contract, endDateTime="", durationStr="2 D", barSizeSetting="1 min", whatToShow="TRADES", useRTH=1, formatDate=1, keepUpToDate=True, chartOptions=[])
3. 彻底修复tradeStrategy的循环逻辑
遍历BAR_TRACKER的键值对,直接用每个条目里的合约和数据,不再依赖nextOrderId:
def tradeStrategy(app: ibapi): # 初始化订单模板 order = Order() order.orderType = 'MKT' order.tif = 'GTC' order.outsideRth = True while True: # 遍历BAR_TRACKER的所有条目,每个条目是(reqId, (contract, ohlcv_list)) for req_id, (contract, ohlcv_list) in BAR_TRACKER.items(): print(f"Processing contract {contract.symbol}, OHLCV data count: {len(ohlcv_list)}") # 示例交易逻辑:如果有足够数据,计算RSI并执行交易 if len(ohlcv_list) >= 14: closes = np.array([bar[4] for bar in ohlcv_list]) rsi = ta.momentum.rsi(closes, window=14) print(f"{contract.symbol} latest RSI: {rsi[-1]}") # 假设RSI<30时买入 if rsi[-1] < 30: order.action = "BUY" order.totalQuantity = 10 app.placeOrder(app.nextOrderId, contract, order) app.nextOrderId += 1 # 下单后递增nextOrderId time.sleep(60) # 每隔60秒检查一次,避免循环太频繁
4. 修正buildContracts的逻辑
因为现在用contract_dict存储reqId和合约的映射,所以buildContracts里的循环要调整,确保每个reqId对应的合约被正确存入:
def buildContracts(app: ibapi, tickers: list): reqId = 1 contracts = [] for symbol in tickers: contract = Contract() contract.symbol = symbol contract.secType = 'STK' contract.exchange = 'SMART' contract.currency = 'USD' app.reqContractDetails(reqId, contract) # 等待合约详情返回,避免提前返回空数据 while reqId not in app.contract_dict: time.sleep(0.1) contracts.append(app.contract_dict[reqId]) reqId += 1 return contracts
额外注意事项
- 永远不要用动态变化的
nextOrderId去访问已存储的数据,已存储的数据必须用请求时实际使用的ID来关联。 - 处理多合约时,一定要用字典存储reqId和合约的映射,不能用单个变量,否则会被后续请求覆盖。
- 在
tradeStrategy的循环里加入适当休眠,既避免过度占用CPU,也符合TWS API的限流要求。
备注:内容来源于stack exchange,提问作者Dominic Buckley
相关产品推荐
相关产品推荐

