问题描述
实际回测中发现如果每个tick都调用handle_tick()函数,会导致时间开销巨大,可不可以通过添加一些代码使得2秒或者其它时间调用handle_tick()函数,而不是每个tick都调用?
解决方案
在 JoinQuant (聚宽) 平台进行 Tick 级别回测时,由于 Tick 数据量极其庞大(例如期货每 0.5 秒一个 Tick,股票每 3 秒一个 Tick),如果每个 Tick 都执行复杂的逻辑,确实会导致回测时间开销巨大。
虽然 JoinQuant 的 API 底层在订阅 Tick 后会在每个 Tick 到来时触发 handle_tick,但我们可以通过在策略代码中引入时间戳记录与比对机制,来主动过滤掉不需要处理的 Tick,从而达到“每隔 N 秒执行一次核心逻辑”的目的。
解决方案思路
- 在
initialize函数中定义一个全局变量(如g.last_tick_time)用于记录上一次执行核心逻辑的时间,并定义一个时间间隔(如g.interval)。 - 在
handle_tick函数的开头,获取当前 Tick 的时间。 - 计算当前时间与上一次执行时间的差值,如果差值大于或等于设定的时间间隔,则执行核心逻辑并更新
g.last_tick_time;否则,直接return跳过本次处理。
Python 代码示例
以下是一个完整的代码框架示例,演示如何将 handle_tick 的实际处理频率限制为每 2 秒一次:
import datetime
def initialize(context):
# 设定基准和复权模式
set_benchmark('000300.XSHG')
set_option('use_real_price', True)
# 订阅标的(以某期货为例)
g.security = 'RB9999.XSGE'
# 初始化上次执行时间为空
g.last_tick_time = None
# 设置期望的执行间隔,例如 2 秒
g.interval = datetime.timedelta(seconds=2)
# 每天开盘前订阅 tick
run_daily(before_market_open, time='08:30', reference_security='RB9999.XSGE')
def before_market_open(context):
# 订阅 tick 事件
subscribe(g.security, 'tick')
def handle_tick(context, tick):
# 获取当前 tick 的时间
# 注意:tick.datetime 是 datetime.datetime 对象
current_time = tick.datetime
# 判断是否满足执行条件:
# 1. 第一次执行 (g.last_tick_time is None)
# 2. 当前时间距离上次执行时间已经超过或等于设定的间隔
if g.last_tick_time is None or (current_time - g.last_tick_time) >= g.interval:
# 更新上次执行时间
g.last_tick_time = current_time
# ==========================================
# 在这里编写你的核心交易逻辑,例如计算指标、下单等
# ==========================================
# log.info("执行核心逻辑,当前时间: %s, 最新价: %s", current_time, tick.current)
# 示例:获取最新价
price = tick.current
# ... 你的策略代码 ...
else:
# 如果时间间隔不足 2 秒,直接跳过,不执行任何耗时操作
pass
注意事项与优化建议
- 标的原始频率限制:请注意,股票的 Tick 快照频率本身是 3 秒一次,期货是 0.5 秒一次。如果你设置的间隔是 2 秒,对于股票而言,它依然是每 3 秒触发一次(因为底层数据就是 3 秒来的);对于期货,则会过滤掉中间的 3 个 Tick,大约每 2 秒执行一次。
- 避免在过滤逻辑前进行耗时操作:确保时间判断逻辑放在
handle_tick的最前面。在判断通过之前,不要调用get_current_tick、history等耗时的 API。 - 多标的处理:如果你同时订阅了多个标的,且希望它们独立计算时间间隔,可以将
g.last_tick_time设计为一个字典,以标的代码为 Key 分别记录时间:g.last_tick_time[tick.code] = current_time。 - 开启性能分析:如果过滤后回测依然很慢,可以在代码最上方加入
enable_profile(),运行一段短时间的回测,查看具体的耗时瓶颈在哪里。