问题描述
解决方案
在 QMT 平台中,默认的策略运行机制是基于“主图驱动”的,即策略挂载在哪个标的上,就由该标的的行情(Tick 或 Bar)来驱动 handlebar 函数的执行。
如果需要在策略中同时监控和交易多个标的(例如多因子选股、配对交易或篮子交易),仅依赖主图驱动可能会导致非主图标的的信号延迟。此时,我们需要使用 ContextInfo.subscribe_quote 接口来进行独立的数据订阅,并通过回调函数来处理实时推送的数据。
以下是关于如何在 QMT 策略中管理多个行情数据订阅的详细说明和代码示例。
核心机制说明
- 订阅接口:
ContextInfo.subscribe_quote(stock_code, period, dividend_type, callback)- 该函数用于订阅指定合约的行情。
- callback:这是关键参数,当订阅的标的有行情更新时,平台会自动调用这个回调函数,而不是等待
handlebar。
- 数据获取:在回调函数中,系统会传入
datas参数,包含当前更新的数据。也可以结合ContextInfo.get_market_data_ex获取更详细的历史数据。 - 运行模式:此功能主要用于实盘或全推行情模式。
策略代码实现
以下代码展示了如何订阅一个股票列表,并在数据更新时实时打印最新价格。
# -*- coding: gbk -*-
def init(ContextInfo):
"""
初始化函数
"""
# 1. 定义需要订阅的股票池
ContextInfo.stock_list = ['600000.SH', '000001.SZ', '000300.SH']
# 2. 定义订阅的周期 (例如 'tick', '1m', '1d')
# 注意:实盘中通常订阅 'tick' 以获得最快响应
period = 'tick'
# 3. 遍历股票池进行订阅
print("开始订阅行情...")
for stock in ContextInfo.stock_list:
# subscribe_quote 返回订阅号,如果需要反订阅可以使用该返回值
# 参数说明: 代码, 周期, 复权方式('follow'跟随主图), 回调函数
ContextInfo.subscribe_quote(stock, period, 'follow', on_quote_callback)
print(f"已订阅: {stock}")
def on_quote_callback(datas):
"""
行情推送回调函数
当订阅的股票有新行情时,此函数会被触发
datas 格式: {code: dataframe}
"""
# 遍历推送过来的数据(通常一次推送可能包含一个或多个标的)
for stock_code, df in datas.items():
# 获取最新的一行数据
if not df.empty:
last_price = df.iloc[-1]['close']
# 获取当前时间 (毫秒时间戳转字符串,需自行实现或使用简单打印)
# 这里直接打印演示
print(f">>> 行情推送 [{stock_code}]: 最新价 {last_price}")
# 在此处编写您的交易逻辑
# 例如:如果价格突破某条线,则下单
# check_signal_and_order(stock_code, last_price)
def handlebar(ContextInfo):
"""
主图驱动函数
"""
# 如果策略完全依赖 subscribe_quote 的回调驱动,handlebar 可以留空
# 或者在这里处理一些定时任务、收盘后的逻辑
if ContextInfo.is_new_bar():
print(f"主图 {ContextInfo.stockcode} K线更新")
# 辅助函数示例:简单的交易逻辑封装
def check_signal_and_order(stock_code, price):
# 示例逻辑:仅作演示
pass
关键点解析
-
回调函数的定义 (
on_quote_callback):- 必须接受一个参数(示例中为
datas)。 datas是一个字典,Key 是合约代码,Value 是包含该合约最新数据的pandas.DataFrame。- 注意:回调函数中的逻辑应尽量简洁高效,避免进行大量的耗时计算,以免阻塞行情推送线程。
- 必须接受一个参数(示例中为
-
订阅与主图的关系:
- 使用了
subscribe_quote后,策略就变成了多源驱动。 handlebar依然会随主图(策略界面上选择的那个标的)更新而运行。on_quote_callback会随订阅列表中的任意标的更新而运行。
- 使用了
-
反订阅 (
unsubscribe_quote):- 如果策略运行过程中需要动态移除某个标的的监控,可以使用
ContextInfo.unsubscribe_quote(subId)。 subId是subscribe_quote调用时的返回值。
- 如果策略运行过程中需要动态移除某个标的的监控,可以使用
-
数据获取建议:
- 虽然
datas参数带有了最新的行情,但如果需要计算复杂的指标(如 MA, MACD),建议在回调函数内部调用ContextInfo.get_market_data_ex来获取指定长度的历史数据窗口。
- 虽然
常见问题 Q&A
Q: subscribe_quote 在回测模式下有效吗?
A: 在回测模式下,QMT 主要依赖 handlebar 逐根 K 线驱动。虽然接口在回测中不会报错,但回测的核心逻辑通常是基于 handlebar 遍历历史时间轴。subscribe_quote 的主要应用场景是实盘和模拟交易中的实时行情监控。
Q: 订阅的股票数量有限制吗?
A: QMT 平台本身对订阅数量有一定的内部限制,且订阅过多标的(例如几千只)会消耗大量内存和 CPU 资源,导致策略响应变慢。建议根据硬件配置合理控制订阅数量,通常几十到几百只是可以接受的。
Q: 为什么我在 on_quote_callback 里下单没有反应?
A: 请检查是否正确设置了交易账号 (ContextInfo.set_account)。此外,确保回调函数内部的逻辑没有报错(可以使用 try...except 捕获异常并打印日志),因为回调函数是在后台线程运行的,有时错误信息不如主线程明显。
Q: 如何获取 Level-2 数据(如买一卖一队列)?
A: 如果您开通了 Level-2 权限,可以在 subscribe_quote 的周期参数中使用 'l2quoteaux' (快照指标) 或 'tick' (分笔) 并配合 get_market_data_ex 获取更深度的盘口数据。