🚀 AI 一键生成 joinquant 策略代码

JoinQuant 回测太慢?使用 enable_profile 定位性能瓶颈

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

问题描述

我的策略回测运行很慢,如何找出是哪部分代码最耗时?

解决方案

在聚宽(JoinQuant)平台中,如果策略回测运行缓慢,最直接且官方推荐的方法是使用 性能分析函数 enable_profile()

这个函数可以帮助你精确地定位每一行代码的执行时间、调用次数以及占用的时间比例。

解决方法:使用 enable_profile()

1. 使用步骤

  1. 添加代码:在你的策略代码的最顶端(所有函数之前,包括 initialize 之前)添加 enable_profile()
  2. 运行回测:点击“运行回测”。
    • 注意:建议将回测时间缩短(例如只回测 1 周或 1 个月),因为开启性能分析本身会增加额外的运行开销,导致回测变得更慢。我们只需要短时间的数据来找出瓶颈即可。
  3. 查看结果:回测完成后,在回测详情页面的“日志”或“性能分析”标签页中,会显示详细的分析报告。

2. 代码示例

# -*- coding: utf-8 -*-
# 必须放在代码的最前面
enable_profile()

def initialize(context):
    set_benchmark('000300.XSHG')
    set_option('use_real_price', True)
    run_daily(market_open, time='every_bar')

def market_open(context):
    # 假设这里有一段耗时的代码
    for stock in get_index_stocks('000300.XSHG'):
        close_data = attribute_history(stock, 5, '1d', ['close'])

3. 如何解读分析报告

开启后,你会在日志中看到类似下面的输出。重点关注 % Time(时间占比)和 Time(总耗时)这两列。

// 函数名及所在行号
Function: market_open at line 10

// Line #: 行号
// Hits: 该行代码被执行的次数
// Time: 该行代码总共消耗的时间(单位通常是微秒)
// Per Hit: 平均每次执行消耗的时间
// % Time: 该行代码占整个函数执行时间的百分比(最关键的指标)
// Line Contents: 代码内容

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    10                                           def market_open(context):
    11         1        25000  25000.0      1.0      stocks = get_index_stocks('000300.XSHG')
    12       300      2400000   8000.0     95.0      for stock in stocks:
    13       300      2300000   7666.0     90.0          data = attribute_history(stock, 5, '1d', ['close'])

分析逻辑:

  • 如果某一行代码的 % Time 非常高(例如超过 50% 或 80%),说明这里就是性能瓶颈。
  • 如果 Hits(执行次数)非常高,说明这行代码在循环中被频繁调用,考虑是否可以移出循环或向量化处理。

常见的性能瓶颈及优化建议

根据聚宽的机制,导致回测慢的常见原因通常有以下几点:

  1. 循环调用数据 API

    • 问题:在 for 循环中逐个调用 attribute_historyget_price
    • 优化:使用批量获取数据的 API。例如 history(count, unit, field, security_list=股票列表) 可以一次性获取多只股票的数据,返回 DataFrame,效率远高于循环获取。
  2. 频繁的 IO 操作

    • 问题:在 handle_data 中频繁读写文件或进行复杂的日志输出(log.info 过于频繁)。
    • 优化:减少日志输出频率,或仅在特定条件下输出。
  3. 重复计算

    • 问题:在每分钟或每天的循环中重复计算不随时间变化的数据(如获取成分股列表)。
    • 优化:将不频繁变化的数据获取放在 initializerun_daily 的开盘前函数中,并存储在 g 全局变量里。
  4. Pandas/Numpy 使用不当

    • 问题:在循环中动态扩展 DataFrame(如 df.append),这非常耗时。
    • 优化:先收集数据到列表(list),最后一次性转换为 DataFrame。

总结

要找出耗时代码,请立即在策略第一行加上 enable_profile() 并运行一个短周期的回测。定位到具体行数后,针对性地优化数据获取方式(从循环改为批量获取)通常能带来最大的性能提升。


Q&A

Q: enable_profile() 可以在模拟交易(实盘模拟)中使用吗?
A: 不可以。enable_profile() 是回测环境专用的 API。在模拟交易中开启会报错或无效,且模拟交易本身对性能要求不同,通常不需要此类细粒度的代码行级分析。

Q: 为什么开启 enable_profile() 后,回测速度变得更慢了?
A: 这是正常的。性能分析工具需要监控每一行代码的执行并记录时间,这本身会带来巨大的系统开销。因此,该功能仅用于诊断阶段。找到问题并修改代码后,务必删除或注释掉 enable_profile() 再进行全量回测。

Q: 遇到 TimeoutError: function runs more than 1800 seconds 怎么办?
A: 这说明你的策略在某个时间单位(如一天或一分钟)内的计算量太大,超过了聚宽平台的单次运行限制。解决方法是:1. 使用 enable_profile() 找出耗时点并优化;2. 如果是初始化计算量大,尝试将计算分散到每天的 before_trading_start 中;3. 减少不必要的循环和数据查询。