🚀 AI 一键生成 joinquant 策略代码
立即体验
👉 AI 生成代码 👈

JoinQuant 平台如何将日线策略改造为分钟级策略?(附 Python 源码)

joinquant | 发布时间: 2026/5/29
以下内容由 EasyQuant 生成。

问题描述

我想问一下关于回测中的现价。

我现在写的代码中是每天早上9点15的时候选出信号,信号是选出2个ETF中从本周期(因为是9点15选信号,实际上取的是按昨天的收盘价计算)至前20周期内的涨幅高的那个在9点30的时候买入。

# 开盘前分析买卖信号
run_daily(before_market_open, time='9:15')
# 开盘后进行交易
run_daily(market_open, time='9:30')
# 收盘后运行记录当天收益
run_daily(after_market_close, time='after_close')
我现在想信号最后收盘价是按当天每分钟的收盘价运行一次,然后买卖的判断也能是每分钟判断一次呢?要怎么做,求教

解决方案

要将您的策略从“每天判断一次”改造为“每分钟判断一次”,在 JoinQuant 平台上非常容易实现。核心思路是将回测频率设置为“分钟”,并使用每分钟都会触发的函数来执行您的逻辑。

以下是具体的改造步骤和代码示例:

核心改造步骤

  1. 修改回测频率:在 JoinQuant 的回测界面右上角,将“运行频率”从“天”修改为“分钟”。
  2. 调整定时运行函数:将 run_daily(..., time='9:15') 替换为 run_daily(..., time='every_bar'),或者直接使用 handle_data(context, data) 函数。这两个函数在分钟频率下,都会在交易时间的每一分钟被调用一次。
  3. 获取分钟级数据:使用 historyattribute_history 时,将时间单位参数从 '1d'(天)改为 '1m'(分钟)。

完整代码示例

假设您要比较的两个 ETF 是沪深300ETF(510300.XSHG)和中证500ETF(510500.XSHG),比较它们过去20分钟的涨幅,并在每分钟进行调仓:

# 导入聚宽函数库
import jqdata

def initialize(context):
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 定义要操作的两个ETF
    g.etf1 = '510300.XSHG'
    g.etf2 = '510500.XSHG'
    
    # 核心修改:设置每分钟运行一次交易函数
    run_daily(market_trade, time='every_bar')

# 每分钟都会被调用一次的函数
def market_trade(context):
    # 获取过去20分钟的收盘价数据
    # 注意:'1m'代表分钟线,获取的数据不包含当前正在运行的这一分钟(避免未来函数)
    hist1 = attribute_history(g.etf1, 20, '1m', ['close'])
    hist2 = attribute_history(g.etf2, 20, '1m', ['close'])
    
    # 确保有足够的数据(例如刚上市的标的可能数据不足)
    if len(hist1) < 20 or len(hist2) < 20:
        return
        
    # 计算过去20分钟的涨幅
    # 涨幅 = (最新一分钟的收盘价 - 20分钟前的收盘价) / 20分钟前的收盘价
    ret1 = (hist1['close'][-1] - hist1['close'][0]) / hist1['close'][0]
    ret2 = (hist2['close'][-1] - hist2['close'][0]) / hist2['close'][0]
    
    # 获取当前账户持仓
    positions = context.portfolio.positions
    
    # 比较涨幅并进行交易判断
    if ret1 > ret2:
        # 如果 ETF1 涨幅大,且当前持有 ETF2,则卖出 ETF2
        if g.etf2 in positions and positions[g.etf2].closeable_amount > 0:
            order_target(g.etf2, 0)
            log.info("卖出 %s" % g.etf2)
        
        # 全仓买入 ETF1
        if g.etf1 not in positions or positions[g.etf1].closeable_amount == 0:
            order_target_value(g.etf1, context.portfolio.total_value)
            log.info("买入 %s" % g.etf1)
            
    elif ret2 > ret1:
        # 如果 ETF2 涨幅大,且当前持有 ETF1,则卖出 ETF1
        if g.etf1 in positions and positions[g.etf1].closeable_amount > 0:
            order_target(g.etf1, 0)
            log.info("卖出 %s" % g.etf1)
        
        # 全仓买入 ETF2
        if g.etf2 not in positions or positions[g.etf2].closeable_amount == 0:
            order_target_value(g.etf2, context.portfolio.total_value)
            log.info("买入 %s" % g.etf2)

注意事项与细节解析

  1. time='every_bar' 的含义:在 run_daily 中使用 every_bar,意味着该函数会与您设置的回测频率保持一致。如果您在界面上选择了“按分钟回测”,它就会在每天的 9:30, 9:31... 一直到 15:00 每分钟执行一次。
  2. 数据的获取attribute_history(security, 20, '1m', ['close']) 获取的是前20个已经走完的分钟K线。例如,在 10:05 分执行时,获取的是 09:45 到 10:04 这20根分钟K线的收盘价。这完全符合实盘逻辑,不会引入未来函数。
  3. 交易限制:A股和场内基金实行 T+1 交易制度。如果您在同一天内买入了某个 ETF,当天是无法卖出的。代码中的 closeable_amount > 0 判断就是为了确保只卖出可用的(非今天买入的)持仓。
  4. 性能考虑:分钟级回测每天会执行 240 次逻辑判断,相比日线策略耗时会显著增加。建议在测试阶段先选择较短的时间区间(如一个月)进行验证。