🚀 AI 一键生成 joinquant 策略代码

JoinQuant 策略逻辑调度:该用 handle_data 还是 run_daily?

joinquant | 发布时间: 2025/12/4
以下内容由 EasyQuant 生成。

问题描述

我应该用 handle_data 还是 run_daily 来写我的策略逻辑?它们有什么不同?

解决方案

在 JoinQuant(聚宽)平台中,handle_datarun_daily 都是用于驱动策略逻辑的核心函数,但它们的设计理念和使用场景有所不同。

简短的回答是:强烈建议使用 run_daily(及其衍生的 run_weekly, run_monthly)定时函数。 它是目前平台推荐的写法,比 handle_data 更灵活、更易于管理代码结构。

以下是详细的对比分析和使用建议。

1. 核心区别对比表

特性 handle_data (旧式/简单) run_daily (新式/推荐)
触发机制 被动触发:每生成一根 K 线(Bar)就运行一次。 主动调度:根据设定的具体时间(如 '09:30', '14:50')或频率运行。
参数 (context, data):包含 data 对象,可直接取当前切片数据。 (context)不包含 data 对象,需使用 API (如 get_price) 获取数据。
灵活性 :所有逻辑(开盘、盘中、收盘)都必须写在一个函数里,代码容易臃肿。 :可以将开盘选股、盘中交易、收盘风控拆分为不同的函数,逻辑清晰。
运行频率 绑定回测频率。按天回测则每天一次,按分钟则每分钟一次。 可自定义。即使是分钟回测,也可以指定只在每天 10:00 运行一次。
Tick 级支持 不支持 Tick 级回测。 支持(配合 run_daily 调度或 handle_tick)。
官方建议 不推荐用于复杂策略。 强烈推荐

2. 深度解析

handle_data (context, data)

这是早期的 API 设计。它的逻辑非常简单粗暴:只要有新的 K 线产生,我就运行。

  • 优点:对于极简策略(例如:每天收盘看一眼,符合条件就买),写起来很快。
  • 缺点
    • 控制力弱:如果你想在“每天 10:00 买入,14:50 卖出”,在 handle_data 中你需要写大量的 if context.current_dt.hour == 10 ... 判断语句。
    • 效率问题:如果在分钟回测中,你只想每天交易一次,但 handle_data 会每分钟被调用 240 次,你必须在代码里防止重复交易。
    • 数据对象过时:它依赖的 data 参数(SecurityUnitData)在某些新版功能中支持度不如全局 API(如 attribute_history)。

run_daily (func, time)

这是定时运行框架。它的逻辑是:你告诉我什么时候运行,运行哪个函数。

  • 优点
    • 模块化:你可以定义 market_open 负责买入,market_close 负责卖出,然后在 initialize 中分别注册它们。
    • 精确控制:可以直接设置 time='09:30'time='14:50',无需在函数内部判断时间。
    • 替代性:如果你确实需要每根 Bar 都运行,可以设置 time='every_bar',完全替代 handle_data 的功能。

3. 代码对比示例

假设策略逻辑是:每天 14:50 判断均线,如果金叉则买入。

写法 A:使用 handle_data (不推荐)

在分钟回测模式下,这个函数每分钟都会运行,必须手动判断时间,否则效率低且容易出错。

def initialize(context):
    g.security = '000001.XSHE'

def handle_data(context, data):
    # 必须手动判断时间,非常繁琐
    current_hour = context.current_dt.hour
    current_minute = context.current_dt.minute
    
    # 只有在 14:50 这一分钟执行逻辑
    if current_hour == 14 and current_minute == 50:
        # 获取数据
        close_data = attribute_history(g.security, 5, '1d', ['close'])
        # ... 交易逻辑 ...
        order(g.security, 100)

写法 B:使用 run_daily (推荐)

逻辑清晰,无需判断时间,引擎会自动在 14:50 调用该函数。

def initialize(context):
    g.security = '000001.XSHE'
    # 直接注册定时任务,清晰明了
    run_daily(my_trade_logic, time='14:50')

# 自定义函数,只接受 context 参数
def my_trade_logic(context):
    # 这里不需要判断时间,进入这个函数时一定是 14:50
    # 获取数据 (注意:这里不能用 data 参数,要用 API)
    close_data = attribute_history(g.security, 5, '1d', ['close'])
    # ... 交易逻辑 ...
    order(g.security, 100)

4. 总结与建议

  1. 新策略请直接使用 run_daily。它能让你的代码结构更像专业的量化系统(初始化 -> 开盘前准备 -> 盘中交易 -> 收盘后处理)。
  2. 不要混用。虽然技术上可行,但官方文档建议不要在同一个策略中同时使用 run_dailyhandle_data,以免执行顺序混乱。
  3. 数据获取习惯。使用 run_daily 后,习惯使用 attribute_history()get_price()get_current_data() 来获取数据,而不是依赖 handle_data 传进来的 data 对象。

Q&A

Q: 如果我使用 run_daily,怎么实现“每分钟都运行”的逻辑?
A: 在 initialize 中设置 run_daily(your_func, time='every_bar')。这等同于 handle_data 的频率,但保持了代码结构的统一性。

Q: run_daily 定义的函数里没有 data 参数,我怎么获取当前价格?
A: 使用 get_current_data()[security].last_price 获取最新价,或者使用 attribute_history 获取历史序列。

Q: run_daily 可以设置多个时间点吗?
A: 可以。你可以多次调用 run_daily 注册不同的函数,或者注册同一个函数在不同的时间。例如:

run_daily(buy_func, time='10:00')
run_daily(sell_func, time='14:00')