问题描述
如何编写代码监控全市场的涨跌停家数和情绪?
解决方案
这是一个非常实用的市场监控需求。在 QMT 中,我们可以通过结合 get_full_tick(获取全市场实时快照)和 get_instrumentdetail(获取涨跌停价格)来实现这一功能。
策略设计思路
- 获取标的池:在
init中获取“沪深A股”板块的所有股票代码。 - 获取实时行情:使用
get_full_tick批量获取所有股票的最新价(lastPrice)。 - 获取涨跌停价:使用
get_instrumentdetail获取每只股票当天的涨停价(UpStopPrice)和跌停价(DownStopPrice)。- 优化点:涨跌停价全天不变,为了避免每几秒钟循环调用几千次
get_instrumentdetail导致卡顿,我们应该将这些数据缓存起来,只在初始化或第一次运行时获取。
- 优化点:涨跌停价全天不变,为了避免每几秒钟循环调用几千次
- 统计逻辑:
- 如果
最新价 >= 涨停价,涨停家数 +1。 - 如果
最新价 <= 跌停价,跌停家数 +1。 - 情绪指标:这里我们可以定义一个简单的情绪指标,例如
(涨停家数 - 跌停家数)或者涨停占比。
- 如果
- 输出与绘图:使用
print在日志输出数值,并使用ContextInfo.paint在副图上画出曲线,方便观察日内趋势。
QMT 策略代码
# -*- coding: gbk -*-
import time
def init(ContextInfo):
# 1. 设置股票池为全市场A股
# 注意:全市场股票数量较多,建议在性能较好的机器上运行
ContextInfo.stock_list = ContextInfo.get_stock_list_in_sector('沪深A股')
ContextInfo.set_universe(ContextInfo.stock_list)
# 2. 初始化全局变量
ContextInfo.limit_price_cache = {} # 用于缓存涨跌停价格,避免重复查询
ContextInfo.last_update_time = 0 # 上次更新时间
ContextInfo.update_interval = 3 # 设置更新间隔(秒),避免过于频繁刷新导致卡顿
# 设置副图显示的指标名称(可选)
ContextInfo.paint_names = ["涨停家数", "跌停家数", "市场情绪(净涨停)"]
def handlebar(ContextInfo):
# 仅在实时行情的最后一根K线(或Tick)运行,跳过历史回测阶段的逐根K线计算以节省时间
if not ContextInfo.is_last_bar():
return
# 控制运行频率,每隔 update_interval 秒运行一次
current_time = time.time()
if current_time - ContextInfo.last_update_time < ContextInfo.update_interval:
return
ContextInfo.last_update_time = current_time
# 1. 批量获取全市场实时Tick数据
# get_full_tick 返回字典: {code: {lastPrice: ..., ...}}
ticks = ContextInfo.get_full_tick(ContextInfo.stock_list)
up_limit_count = 0 # 涨停计数
down_limit_count = 0 # 跌停计数
valid_stock_count = 0 # 有效统计的股票数
# 2. 遍历所有股票进行统计
for code in ContextInfo.stock_list:
tick_data = ticks.get(code)
# 如果没有Tick数据或最新价为0(停牌或未开盘),跳过
if not tick_data or tick_data.get('lastPrice', 0) <= 0:
continue
last_price = tick_data['lastPrice']
# 3. 获取涨跌停价格(带缓存机制)
if code not in ContextInfo.limit_price_cache:
detail = ContextInfo.get_instrumentdetail(code)
# 如果获取不到详情或涨跌停价格异常,跳过
if not detail or detail['UpStopPrice'] <= 0:
continue
# 缓存该股票的涨跌停价格
ContextInfo.limit_price_cache[code] = {
'up': detail['UpStopPrice'],
'down': detail['DownStopPrice']
}
limits = ContextInfo.limit_price_cache[code]
# 4. 判断涨跌停
# 考虑到浮点数精度,虽然金融API通常处理过,但严格来说可以用极小误差范围,这里直接比较通常也没问题
if last_price >= limits['up']:
up_limit_count += 1
elif last_price <= limits['down']:
down_limit_count += 1
valid_stock_count += 1
# 5. 计算市场情绪
# 简单定义:净涨停数 = 涨停 - 跌停
market_sentiment = up_limit_count - down_limit_count
# 6. 打印日志信息
# 获取当前时间字符串
timetag = ContextInfo.get_tick_timetag()
time_str = timetag_to_datetime(timetag, '%H:%M:%S')
log_msg = f"【市场监控】{time_str} | 涨停: {up_limit_count} 家 | 跌停: {down_limit_count} 家 | 情绪(净涨停): {market_sentiment}"
print(log_msg)
# 7. 在界面画图 (需要在副图运行策略才能看到)
# 红色线画涨停
ContextInfo.paint("涨停家数", up_limit_count, -1, 0, "red", "noaxis")
# 绿色线画跌停
ContextInfo.paint("跌停家数", down_limit_count, -1, 0, "green", "noaxis")
# 黄色线画情绪
ContextInfo.paint("情绪指标", market_sentiment, -1, 0, "yellow", "noaxis")
代码关键点解析
-
缓存机制 (
ContextInfo.limit_price_cache):get_instrumentdetail是一个相对耗时的操作,且一只股票当天的涨跌停价是固定的。- 代码中设计了缓存:只有当缓存中没有该股票数据时,才去调用 API。这极大地提高了
handlebar的循环速度,防止界面卡顿。
-
批量获取数据 (
get_full_tick):- 不要在循环中一只一只地去取行情(如
get_market_data),那样效率极低。 - 使用
get_full_tick(list)一次性拿回几千只股票的数据是最高效的方法。
- 不要在循环中一只一只地去取行情(如
-
频率控制 (
update_interval):- 全市场遍历计算量较大,不需要每个 Tick(毫秒级)都算一次。代码设置了每 3 秒计算一次,既保证了实时性,又降低了 CPU 占用。
-
绘图 (
paint):ContextInfo.paint可以将计算结果实时绘制在 K 线图的副图上。- 使用方法:在 QMT 界面中,打开一个行情图(如上证指数),右键选择“叠加指标”或“副图指标”,选择这个策略,即可看到涨跌停家数的历史走势曲线。
如何运行
- 打开 QMT 的【模型研究/策略编辑器】。
- 新建一个 Python 策略,将上述代码粘贴进去。
- 点击【编译】确保无误。
- 方式一(看日志):直接点击【运行】,在下方的日志窗口观察文字输出。
- 方式二(看图表):
- 在行情界面(例如输入
000001.SH上证指数)。 - 将周期切换为【1分钟】或【分笔】。
- 在副图区域右键 -> 选择指标 -> 找到你刚才保存的策略名称。
- 你将看到红、绿、黄三条线动态展示全市场的涨跌停变化。
- 在行情界面(例如输入
Q&A
Q: 为什么有时候涨停家数比行情软件上看到的少?
A:
- ST股规则:代码直接使用了交易所给出的
UpStopPrice。有些行情软件可能包含“触及涨停但未封死”的股票,或者对涨停的定义(如ST股的5%)处理略有不同。本代码逻辑是严格的最新价 >= 涨停价。 - 北交所/科创板:
沪深A股板块通常包含主板、创业板和科创板。如果需要包含或排除特定板块(如北交所),可以在init中调整get_stock_list_in_sector的参数。
Q: 如何定义更复杂的情绪指标?
A: 你可以在代码第 5 步修改 market_sentiment 的计算公式。例如:
- 涨停占比:
up_limit_count / (up_limit_count + down_limit_count) - 全市场上涨率: 在循环中统计
last_price > pre_close的家数,计算上涨家数 / 总家数。