问题描述
解决方案
在 QMT (Quant Trader) 平台中,handlebar(ContextInfo) 函数在实时行情(运行模式)下是基于 Tick 驱动的。这意味着在同一根 K 线(Bar)内,每当有新的分笔成交(Tick)推送过来,handlebar 都会被调用一次。
如果不加限制,交易逻辑会在同一根 K 线内重复触发。为了实现**“每个新的 K 线开始时只执行一次”**,最标准的方法是使用 QMT API 提供的 ContextInfo.is_new_bar() 函数。
核心逻辑说明
ContextInfo.is_new_bar():- 该函数用于判断当前处理的数据是否为一根新的 K 线的第一个 Tick。
- 返回值:
True表示是新 K 线的开始,False表示仍在当前 K 线内部更新。
- 替代方案(手动判断
barpos):- 如果需要更复杂的控制,也可以通过记录上一次执行的 K 线索引 (
ContextInfo.barpos) 来判断索引是否发生了变化。
- 如果需要更复杂的控制,也可以通过记录上一次执行的 K 线索引 (
策略代码实现
以下是一个完整的 Python 策略示例。该策略会在每根新 K 线出现的第一个 Tick 打印日志并执行模拟下单操作。
# -*- coding: gbk -*-
import pandas as pd
def init(ContextInfo):
"""
初始化函数,策略启动时执行一次
"""
# 设置资金账号 (请替换为您的真实账号)
ContextInfo.accid = '600000248'
ContextInfo.set_account(ContextInfo.accid)
# 设置股票池 (示例:平安银行)
ContextInfo.set_universe(['000001.SZ'])
# 初始化一个自定义变量,用于记录上一次操作的 Bar 索引(作为双重验证,非必须但推荐)
ContextInfo.last_processed_barpos = -1
print("策略初始化完成")
def handlebar(ContextInfo):
"""
行情事件函数,每根 K 线或每个 Tick 运行一次
"""
# 获取当前 K 线的索引位置
current_barpos = ContextInfo.barpos
# 获取当前主图品种代码
stock_code = ContextInfo.stockcode
# --- 核心逻辑:判断是否为新 K 线 ---
# 方法 A:使用官方 API is_new_bar() (推荐)
is_new = ContextInfo.is_new_bar()
# 方法 B:手动比较 barpos (作为补充理解)
# is_new_manual = current_barpos > ContextInfo.last_processed_barpos
if is_new:
# 更新自定义的状态变量
ContextInfo.last_processed_barpos = current_barpos
# 获取当前时间,用于日志展示
# 注意:get_bar_timetag 获取的是 K 线时间戳,timetag_to_datetime 转换为字符串
timetag = ContextInfo.get_bar_timetag(current_barpos)
time_str = timetag_to_datetime(timetag, '%Y-%m-%d %H:%M:%S')
print(f"检测到新 K 线生成!时间: {time_str}, 索引: {current_barpos}")
# --- 在此处编写您的交易逻辑 ---
# 示例:获取最新价
last_price = ContextInfo.get_market_data_ex(
['close'],
[stock_code],
period=ContextInfo.period,
count=1
)[stock_code].iloc[-1]['close']
print(f"当前 {stock_code} 价格: {last_price},执行开仓操作...")
# 示例下单:按最新价买入 100 股
# 注意:实盘中请确保账号设置正确且有资金
# order_shares(stock_code, 100, 'LATEST', last_price, ContextInfo, ContextInfo.accid)
else:
# 如果不是新 K 线,则跳过,避免重复执行
pass
代码详解
-
# -*- coding: gbk -*-:- QMT 的 Python 编辑器默认使用 GBK 编码,这行代码是必须的,否则中文注释会导致乱码或报错。
-
ContextInfo.is_new_bar():- 这是控制频率的关键。在实时行情中,一根 1 分钟的 K 线可能会触发几十次
handlebar调用,但is_new_bar()只有在第一次调用时返回True。
- 这是控制频率的关键。在实时行情中,一根 1 分钟的 K 线可能会触发几十次
-
ContextInfo.barpos:- 这是当前 K 线在历史数据中的索引位置。随着时间推移,新 K 线生成时,这个数字会增加。
-
timetag_to_datetime:- QMT 内部时间通常是毫秒级时间戳,使用此函数将其转换为可读的日期字符串,方便调试。
常见问题 (Q&A)
Q: is_new_bar() 在回测模式下如何工作?
A: 在回测模式下,handlebar 本身就是逐根 K 线运行的(除非开启了 Tick 回测),所以每一根 K 线 is_new_bar() 都会返回 True。这个逻辑在回测和实盘中是兼容的。
Q: 如果我在 K 线中间启动策略,会立即下单吗?
A: 如果您在 K 线中间启动策略,handlebar 会立即运行。对于当前这根 K 线,如果它是策略启动后接收到的第一个数据包,is_new_bar() 可能会返回 True(视具体版本和启动时机而定)。如果您希望严格限制只在下一根完整的 K 线开始时才交易,可以结合 ContextInfo.is_last_bar() 和本地时间判断,或者在 init 中记录启动时的 barpos,在 handlebar 中要求 current_barpos > init_barpos 才开始交易。
Q: 为什么有时候 is_new_bar() 触发了,但是成交价和 K 线收盘价不一样?
A: 因为 is_new_bar() 对应的是新 K 线的第一个 Tick(即 Open 开盘时刻附近的数据)。此时 K 线刚刚生成,价格通常接近开盘价,而不是这根 K 线结束时的收盘价。如果您需要在 K 线走完时交易(即收盘价确定后),通常的做法是在 is_new_bar() 触发时,读取上一根 K 线(barpos - 1)的数据进行信号计算和下单。