跳转到内容

请求市场扫描器订阅

reqScannerSubscription() 用来提交一个市场扫描器订阅。你需要先创建 ScannerSubscription 对象,再填写扫描条件。

Python 方法签名常见写法如下:

官方参考:IBKR Campus - Request Market Scanner Subscription

app.reqScannerSubscription(
reqId,
subscription,
scannerSubscriptionOptions,
scannerSubscriptionFilterOptions,
)
参数中文说明
reqId请求编号。后续 scannerData()scannerDataEnd()error() 都会带回同一个编号。
subscriptionScannerSubscription 对象,包含扫描范围、规则和过滤条件。
scannerSubscriptionOptionsIBKR 保留选项列表,普通开发通常传 []
scannerSubscriptionFilterOptions额外过滤条件列表,新版 TWS 支持用 TagValue 扩展过滤。

scannerSubscriptionOptionsscannerSubscriptionFilterOptions 都是 TagValue 列表。普通股票扫描通常可以先传空列表;需要更细过滤时,再根据 reqScannerParameters() 返回的 XML 选择可用过滤项。

属性类型中文说明
numberOfRowsint希望返回的结果数量。官方限制下单个扫描规则最多 50 条。
instrumentstr产品类型,例如 STK 股票。
locationCodestr市场范围,例如 STK.US.MAJOR 美国主要股票市场。
scanCodestr扫描排序规则,例如 HOT_BY_VOLUME 成交量活跃。
abovePrice / belowPricefloat价格上限或下限过滤。
aboveVolumeint成交量下限过滤。
marketCapAbove / marketCapBelowfloat市值范围过滤,是否可用取决于扫描类型。
moodyRatingAbove / moodyRatingBelowstr债券评级过滤,主要用于固定收益相关扫描。
spRatingAbove / spRatingBelowstr标普评级过滤,主要用于固定收益相关扫描。
maturityDateAbove / maturityDateBelowstr到期日范围过滤,常用于债券或期货类场景。
couponRateAbove / couponRateBelowfloat票息范围过滤,常用于债券扫描。
excludeConvertiblebool是否排除可转债,适用场景取决于扫描类型。
averageOptionVolumeAboveint期权平均成交量过滤,适用于期权相关扫描。
scannerSettingPairsstr旧式扩展设置字符串;新项目优先考虑 TagValue 过滤。
stockTypeFilterstr股票类型过滤,具体取值以扫描器参数 XML 为准。

这些属性不是每种扫描都支持。写程序时应先从 reqScannerParameters() 返回的 XML 判断可用条件,再给用户开放对应筛选项。

未设置的数值字段在官方 Python API 中通常是 UNSET_DOUBLEUNSET_INTEGER,字符串字段通常是空字符串。它们表示“不启用这个过滤条件”,不是 0。不要为了“字段完整”把所有可选字段都填上,否则很容易把结果过滤到 0 行。

如果不确定某组条件是否合理,可以先在 TWS 的 Advanced Market Scanner / 高级市场扫描器界面里手动选择同样条件。界面能正常创建并返回结果后,再把同样的 instrumentlocationCodescanCode 和过滤条件搬到 API 里。

from ibapi.tag_value import TagValue
filter_options = [
# 市值大于 10,000 美元。字段名称来自扫描器参数 XML。
TagValue("usdMarketCapAbove", "10000"),
]
app.reqScannerSubscription(98001, subscription, [], filter_options)

过滤条件越多,越容易返回空结果。建议先跑通 instrumentlocationCodescanCode,再逐个增加过滤项。

from __future__ import annotations
import threading
import time
from ibapi.client import EClient
from ibapi.scanner import ScannerSubscription
from ibapi.wrapper import EWrapper
class ScannerApp(EWrapper, EClient):
def __init__(self) -> None:
EClient.__init__(self, self)
self.ready = threading.Event()
self.done = threading.Event()
def nextValidId(self, orderId: int) -> None:
self.ready.set()
def scannerData(self, reqId, rank, contractDetails, distance, benchmark, projection, legsStr) -> None:
contract = contractDetails.contract
print(rank, contract.symbol, contract.secType, contract.exchange, contract.currency, contract.conId)
def scannerDataEnd(self, reqId: int) -> None:
print("扫描结果结束:", reqId)
self.done.set()
def hot_us_stocks_by_volume() -> ScannerSubscription:
"""成交量活跃的美国主要股票。"""
subscription = ScannerSubscription()
subscription.numberOfRows = 5
subscription.instrument = "STK"
subscription.locationCode = "STK.US.MAJOR"
subscription.scanCode = "HOT_BY_VOLUME"
subscription.abovePrice = 5
subscription.aboveVolume = 100_000
return subscription
app = ScannerApp()
app.connect("127.0.0.1", 7497, clientId=98001)
thread = threading.Thread(target=app.run, daemon=True)
thread.start()
if app.ready.wait(10):
app.reqScannerSubscription(98001, hot_us_stocks_by_volume(), [], [])
app.done.wait(12)
app.cancelScannerSubscription(98001)
time.sleep(1)
app.disconnect()

先用最少字段跑通:

subscription.instrument = "STK"
subscription.locationCode = "STK.US.MAJOR"
subscription.scanCode = "HOT_BY_VOLUME"

跑通后再加过滤:

subscription.numberOfRows = 5
subscription.abovePrice = 5
subscription.aboveVolume = 100_000

如果一开始就加很多条件,返回 0 行时很难判断是权限问题、组合不支持,还是过滤条件太窄。

SCAN_END=True
SCAN_ROWS=5
NON_INFO_ERROR_COUNT=1
ERROR=reqId=98001;code=162;msg=历史市场数据服务错误消息:API scanner subscription cancelled: 98001

这里 SCAN_ROWS=5 表示订阅成功拿到 5 条结果。后面的 162 是取消订阅后 TWS 发出的提示,核心含义是扫描器订阅已取消;如果数据已经正常返回,可以把它作为取消后的状态消息处理。