跳转到内容

实时行情限制

实时行情不是只要能连接 TWS 就一定能拿到。API 连接、合约定义、行情权限和订阅容量是四件不同的事:nextValidId() 到达只能说明 Socket 已经连上,reqContractDetails() 能返回只能说明合约能识别,真正能不能收到实时价格,还要看账户是否有对应交易所、品种、接口类型的数据权限,以及是否还有可用的行情行数。

官方参考:

限制中文解释常见表现
行情权限账户需要对应交易所、品种和 API 场景的数据权限。美股常见会涉及 NASDAQ、NYSE、ARCA、ISLAND 等交易所权限。4201008910189,或者请求发出后没有价格回调。
行情行数reqMktData()reqMktDepth()reqRealTimeBars() 等持续订阅都会占用行情行数。TWS 界面里打开的报价和 API 订阅会一起占用额度。新请求被拒绝、没有新推送、提示超出市场数据订阅数量。
实时 / 延迟类型reqMarketDataType(3) 可以让后续 reqMktData() 优先使用延迟行情;如果账户已有实时权限,TWS 可能仍返回实时数据。收到 marketDataType() 回调,或收到延迟 tick 类型。
接口差异reqMktData()reqTickByTickData()reqRealTimeBars() 都属于实时相关接口,但权限、限频和回调形式不同。一档报价可用,不代表 5 秒线、二档盘口或逐笔数据一定可用。
小周期限频实时 5 秒线属于持续订阅,同时受行情行数和小周期请求限频影响。官方要求 10 分钟内不要发起超过 60 个新的实时 5 秒线请求。批量重连、频繁换合约时更容易触发限制。
合约定义合约不唯一、交易所字段不合适或币种错误时,行情请求前就可能失败。200、没有唯一合约,或请求落到不期望的数据源。
订阅生命周期实时订阅会一直占用资源,程序结束前应主动取消。达到订阅上限,或取消未建立成功的订阅时出现 300
监管快照美国市场监管快照通过 reqMktData()regulatorySnapshot=True 请求,官方说明这类请求可能产生费用。不适合随意轮询;需要在界面上明确告知用户。

使用 TWS API 请求 AAPL 的实时 5 秒线,连接和请求都能发出,但账户没有对应数据源的 API 行情权限:

CONNECTED=True
REQUEST_SENT=True
CANCEL_SENT=True
BAR_ROWS=0
ERROR=reqId=97201;code=420;msg=实时查询无效:No market data permissions for ISLAND STK. 请求的市场数据对于API来说需要额外订阅。点击“市场数据连接”对话框中的链接获取更多详情。
ERROR=reqId=97201;code=300;msg=无法使用tickerId找到EId::97201

这里的重点是:Socket 连接没有问题,请求也发出去了;真正阻塞的是行情权限。后面的 300 不是根因,它通常发生在取消订阅时:前面的请求已经因为权限错误没有建立成功,取消时 TWS 就找不到对应的订阅编号。

如果看到 1008910189,含义也类似:代码已经到达 TWS,但账户没有该数据源或该接口场景所需的权限。不同 TWS 版本、账户类型、交易所和请求接口,错误码可能表现为 4201008910189,排查时要把错误码、合约、交易所和请求类型一起记录。

步骤检查什么
1nextValidId() 是否到达,确认 API 连接完成
2合约是否能用 reqContractDetails() 查到唯一结果
3是否调用了正确的实时接口:顶层报价用 reqMktData(),5 秒线用 reqRealTimeBars(),逐笔用 reqTickByTickData()
4请求是否返回 42010089 等权限错误
5是否需要先尝试 reqMarketDataType(3) 获取延迟报价
6是否已经发送取消请求释放订阅
7是否超出账户可用行情行数

不要把权限错误写成“代码失败”。真实项目里应把错误码、合约、交易所、请求类型和市场数据类型记录下来,让用户知道应该开通数据权限、切换延迟行情,还是换一个可用合约测试。

错误码常见含义处理方式
10089这类市场数据对 API 请求需要额外订阅。到 IBKR 的市场数据订阅页面确认交易所、品种和 API 数据权限。
10189逐笔数据请求失败,常见原因是缺少对应实时逐笔行情权限,或产品不支持该逐笔类型。检查交易所权限、产品类型和 tickType,不要把空回调当成真实无成交。
420没有对应市场数据权限,或请求的数据源不可用。换合约、换交易所字段、尝试延迟行情,或补齐权限。
200合约定义不唯一或找不到。先用合约详情接口确认 conIdprimaryExchangecurrency
300取消订阅时找不到对应请求编号。如果前面已经权限失败,这个错误可以作为后续结果记录,不要当成根因。
101 / 订阅数量提示行情行数达到限制。取消不再使用的订阅,减少 TWS 界面打开的报价,或提升账户行情额度。

行情请求代码里至少要记录这些字段:

字段为什么要记录
reqId对齐请求、回调和错误。
symbol / secType / exchange / currency判断是不是合约写错或落到了不期望的交易所。
marketDataType区分实时、冻结、延迟、延迟冻结。
whatToShow5 秒线和历史数据会用到,区分 TRADESMIDPOINTBIDASK 等。
snapshot / regulatorySnapshot区分快照、流式订阅和监管快照,避免把可能收费的请求当成普通订阅。
errorCode / errorMessage让用户知道是权限、合约、连接还是取消订阅问题。

实时行情代码不要只写一个 print(price)。更稳的写法是把订阅状态做成一张表:

active_market_data = {}
def mark_requested(req_id, contract, request_type):
active_market_data[req_id] = {
"symbol": contract.symbol,
"secType": contract.secType,
"exchange": contract.exchange,
"currency": contract.currency,
"requestType": request_type,
"status": "requested",
}
def mark_error(req_id, code, message):
row = active_market_data.get(req_id)
if row is None:
return
row["status"] = "error"
row["errorCode"] = code
row["errorMessage"] = message
def mark_cancelled(req_id):
active_market_data.pop(req_id, None)

这样用户界面里可以明确显示:某个请求是已发送、已收到行情、权限不足、合约错误,还是已经取消。对开发工具来说,这比只在终端里看一行错误更容易定位问题。