🚀 AI 一键生成 joinquant 策略代码

如何在 JoinQuant 研究环境中通过代码批量创建和管理回测?

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

问题描述

如何在代码中批量运行回测并获取结果?

解决方案

在聚宽(JoinQuant)平台中,批量运行回测通常在**研究环境(Research)**中进行。你可以利用 create_backtest 函数来提交回测任务,并使用 get_backtest 函数来获取回测的状态和结果。

这种方法常用于参数调优(Parameter Tuning),即使用相同的策略逻辑,但传入不同的参数(如均线周期、止损比例等)来观察策略表现。

以下是具体的实现步骤和代码示例。

核心思路

  1. 准备策略代码:将策略代码定义为一个字符串,或者获取现有策略的 algorithm_id
  2. 设置参数集合:定义你需要遍历的参数列表(例如不同的均线天数)。
  3. 循环提交回测:使用 create_backtest 函数,通过 extras 参数将外部变量传入策略的全局变量 g 中。
  4. 获取结果:使用 get_backtest 获取回测对象,轮询检查状态,待回测完成后提取风险指标(如收益率、夏普比率)。

代码示例

假设我们要测试一个简单的均线策略,想要批量测试 5日、10日、20日 均线的效果。

请在聚宽的**研究环境(Notebook)**中运行以下代码:

# -*- coding: utf-8 -*-
import pandas as pd
import time
from jqdata import *

# 1. 定义策略代码字符串
# 注意:我们在 initialize 中定义了 g.ma_days 的默认值
# 实际运行时,create_backtest 的 extras 参数会覆盖这个值
strategy_code = """
# 导入函数库
from jqdata import *

def initialize(context):
    set_benchmark('000300.XSHG')
    set_option('use_real_price', True)
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 定义默认参数,会被 extras 覆盖
    if not hasattr(g, 'ma_days'):
        g.ma_days = 5
        
    g.security = '000001.XSHE'
    run_daily(market_open, time='open')

def market_open(context):
    security = g.security
    # 获取历史收盘价
    close_data = attribute_history(security, g.ma_days + 1, '1d', ['close'])
    ma = close_data['close'][-g.ma_days:].mean()
    current_price = close_data['close'][-1]
    
    # 简单的均线策略逻辑
    if current_price > ma:
        order_target_value(security, context.portfolio.total_value)
    elif current_price < ma:
        order_target(security, 0)
"""

# 2. 设置回测参数
params_list = [5, 10, 20]  # 我们要测试的均线周期列表
results = []

print("开始批量回测...")

# 3. 循环运行回测
for ma_days in params_list:
    print(f"正在提交回测,参数 ma_days={ma_days} ...")
    
    # 创建回测
    # extras={'ma_days': ma_days} 会将 ma_days 注入到策略的全局变量 g 中
    bt_id = create_backtest(
        code=strategy_code,           # 策略代码字符串
        start_date='2022-01-01',      # 回测开始日期
        end_date='2022-12-31',        # 回测结束日期
        frequency='day',              # 频率
        initial_cash=100000,          # 初始资金
        extras={'ma_days': ma_days},  # 【关键】传入动态参数
        name=f'MA_{ma_days}_Test'     # 回测名称
    )
    
    # 4. 等待回测完成并获取结果
    gt = get_backtest(bt_id)
    
    # 轮询检查状态,直到回测结束 (done) 或失败 (failed)
    while gt.get_status() not in ['done', 'failed', 'error']:
        time.sleep(2)  # 暂停2秒再次检查
        
    if gt.get_status() == 'done':
        # 获取风险指标
        risk = gt.get_risk()
        # 获取收益率 (returns) 和 夏普比率 (sharpe)
        # 注意:risk 字典中包含很多指标,如 algorithm_return, benchmark_return, sharpe, max_drawdown 等
        res = {
            'ma_days': ma_days,
            'backtest_id': bt_id,
            'total_returns': risk.get('algorithm_return', 0),
            'sharpe': risk.get('sharpe', 0),
            'max_drawdown': risk.get('max_drawdown', 0)
        }
        results.append(res)
        print(f"回测完成: ID={bt_id}, 收益率={res['total_returns']:.2%}")
    else:
        print(f"回测失败: ID={bt_id}")

# 5. 展示结果
print("-" * 30)
print("批量回测结果汇总:")
df_results = pd.DataFrame(results)
# 按收益率降序排列
df_results = df_results.sort_values(by='total_returns', ascending=False)
print(df_results)

关键API说明

  1. create_backtest:

    • code: 可以直接传入策略代码字符串(如上例)。也可以使用 algorithm_id 指定一个已存在的策略文件。
    • extras: 这是一个字典。它非常关键,用于向策略传递参数。字典中的 key 会变成策略中 g 对象的属性(例如 extras={'x': 10} 会在策略中生成 g.x = 10)。注意:extras 的赋值发生在 initialize 函数执行之后,所以如果在 initialize 中使用了该变量,建议先判断是否存在或设置默认值。
  2. get_backtest(backtest_id):

    • 返回一个回测对象,用于后续查询。
  3. 回测对象的方法:

    • gt.get_status(): 获取状态(running, done, failed, none 等)。
    • gt.get_risk(): 获取总体的风险收益指标(如年化收益、夏普比率、最大回撤等)。
    • gt.get_results(): 获取每日的收益曲线数据。

注意事项

  • 并发限制:虽然代码是串行提交的,但聚宽服务器对同时运行的回测数量有限制。如果批量任务非常多,建议在循环中适当增加 time.sleep,或者分批次运行。
  • 积分消耗:创建回测可能会消耗聚宽积分(取决于当天的免费额度使用情况),请留意账户积分余额。
  • 未来函数:在批量回测时,请确保 start_dateend_date 设置合理,避免引入未来数据。
  • Python版本create_backtest 默认使用 Python 2,如果你的策略是 Python 3,请在参数中添加 python_version=3

Q&A

Q: 如何使用我已经写好的策略文件而不是字符串?
A: 在 create_backtest 函数中,去掉 code 参数,改用 algorithm_id='你的策略ID'。策略ID可以在策略编辑页面的 URL 中找到(例如 algorithmId=xxxx)。

Q: extras 参数是在 initialize 之前还是之后生效?
A: 文档指出 extras 参数的值是在 initialize 函数执行之后才设置给 g 变量的。因此,如果你在 initialize 中直接使用 g.param,它可能是未定义或默认值。建议在 initialize 中设置默认值,或者在 before_trading_start / handle_data 中使用该参数。

Q: 为什么我的回测状态一直是 none
A: none 表示未开始。如果长时间处于此状态,可能是排队人数较多,或者代码有语法错误导致无法启动。建议检查代码字符串是否完整。