3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在量化策略回测中,**“未来函数”(Look-ahead Bias)**是指在策略进行决策时,利用了当时那个时间点实际上无法获取的数据。这会导致回测结果虚高,实盘却严重亏损。
在 QMT 平台中,要确保回测的真实性,需要从撮合机制理解、行情数据获取、财务数据处理以及代码逻辑四个方面进行严格控制。
以下是具体的操作指南和注意事项:
QMT 的 Python 策略主要通过 handlebar(ContextInfo) 函数驱动。
默认机制(安全): 在日线回测中,handlebar 在每一根 K 线结束时被调用。此时,你拥有了这根 K 线的 Open, High, Low, Close (OHLC)。
风险点(quickTrade 参数):
passorder 函数中设置 quickTrade=1,或者使用 do_order(),意味着你试图在当前 K 线立即成交。Close(收盘价)计算信号,并在当天就以 Close 价格成交,这就是典型的用到未来数据(因为在盘中你不可能知道收盘价是多少)。在使用 ContextInfo.get_market_data_ex 或 get_history_data 时,要注意数据的时间范围。
在计算指标时,获取了包含“明天”或“未来”的数据进行归一化或计算移动平均。
始终基于当前回测进度 ContextInfo.barpos 来截取数据。
# -*- coding: gbk -*-
def handlebar(ContextInfo):
# 获取当前 K 线索引
index = ContextInfo.barpos
# 获取当前主图品种
code = ContextInfo.stockcode + '.' + ContextInfo.market
# 【正确】获取截止到当前 K 线的数据(count 设为一定长度,不要设为 -1 获取全部)
# 注意:在 handlebar 运行结束时,当前 bar 的数据是已知的
data = ContextInfo.get_market_data_ex(
['close'],
[code],
period=ContextInfo.period,
count=20, # 获取过去20根
end_time='', # 默认为当前进度时间
subscribe=False
)
if code in data:
df = data[code]
# 确保数据长度足够
if len(df) < 20:
return
# 计算指标,例如 MA20
ma20 = df['close'].mean()
# 获取上一根 K 线的收盘价(用于判断交叉)
# 这里的 iloc[-1] 是当前 bar,iloc[-2] 是上一根 bar
current_close = df['close'].iloc[-1]
prev_close = df['close'].iloc[-2]
这是量化回测中最容易忽视的未来数据陷阱。
如果在 2023-04-01 就使用了一季报的数据进行选股,就是使用了未来数据。
在使用 ContextInfo.get_financial_data 时,务必注意 report_type 参数。
report_type='announce_time' (默认值/推荐): 系统会根据公告日期来返回数据。即在 4月1日时,系统只会给你看 2022年年报的数据,只有到了 4月25日,系统才会给你看 2023年一季报的数据。这是真实的。report_type='report_time' (慎用): 系统会根据报告期返回数据。这会导致你在 4月1日就能看到 4月25日才发布的数据,造成回测虚假繁荣。# -*- coding: gbk -*-
def handlebar(ContextInfo):
# ... 省略部分代码
# 【正确】使用默认的 announce_time 或显式指定
# 这样能确保在回测历史时间点上,只获取到当时已经公告的财报
fin_data = ContextInfo.get_financial_data(
['ASHAREINCOME.net_profit_incl_min_int_inc'], # 净利润
['600000.SH'],
startDate=ContextInfo.get_bar_timetag(ContextInfo.barpos - 10), # 示例时间
endDate=ContextInfo.get_bar_timetag(ContextInfo.barpos),
report_type='announce_time' # 关键参数
)
if price <= low: buy 的逻辑。因为在实盘中,你无法预知当天的最低价是多少,除非你挂单在那个价格等待成交(但回测很难模拟挂单排队)。建议以 open 或 close 价格成交。Z-Score 标准化,然后再进行回测,这是严重的未来数据。标准化必须使用滚动窗口(Rolling Window),只利用过去 N 天的数据计算均值和方差。在运行回测前,请检查以下几点:
get_financial_data 是否使用了 announce_time 模式?barpos 及其之前的数据,没有引用 barpos + 1 的数据?ContextInfo.set_slippage)和手续费?虽然这不是未来数据,但忽略滑点会高估策略表现。Q: QMT 回测时,ContextInfo.get_market_data_ex 如果不填 end_time,默认是什么时间?
A: 在回测模式下,如果不填 end_time,默认是当前回测运行到的时间点(即 ContextInfo.barpos 对应的时间)。这保证了你不会取到比当前回测进度更晚的数据。
Q: 我想在收盘前 5 分钟交易,如何避免未来数据?
A: 你需要使用分钟线(如 5分钟或 1分钟周期)进行回测,或者在日线策略中使用 timetag_to_datetime 判断时间,并配合 do_order。但最稳健的方法是使用分钟周期回测,在每天的最后一根分钟 K 线(如 14:55)产生信号并下单。
Q: 为什么我的策略回测收益率极高,但实盘不行?
A: 除了未来数据(如提前知道财报、提前知道涨跌停)外,常见原因还包括:未计算滑点和手续费、成交量超过了市场实际流动性(买入量超过了当时的盘口挂单量)、使用了复权数据但未考虑除权日的实际买卖限制等。
Q: quickTrade=1 在回测中到底能不能用?
A: 可以用,但必须非常小心。它表示“立即下单”。如果在日线级别回测,且逻辑是 if close > ma20: passorder(..., quickTrade=1),这就是利用未来数据(因为盘中不知道收盘价)。如果逻辑是 if open > ma20: passorder(..., quickTrade=1),则是合理的(利用开盘价判断,开盘即买入)。