问题描述
解决方案
在聚宽(JoinQuant)平台中,批量运行回测通常在**研究环境(Research)**中进行。你可以利用 create_backtest 函数来提交回测任务,并使用 get_backtest 函数来获取回测的状态和结果。
这种方法常用于参数调优(Parameter Tuning),即使用相同的策略逻辑,但传入不同的参数(如均线周期、止损比例等)来观察策略表现。
以下是具体的实现步骤和代码示例。
核心思路
- 准备策略代码:将策略代码定义为一个字符串,或者获取现有策略的
algorithm_id。 - 设置参数集合:定义你需要遍历的参数列表(例如不同的均线天数)。
- 循环提交回测:使用
create_backtest函数,通过extras参数将外部变量传入策略的全局变量g中。 - 获取结果:使用
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说明
-
create_backtest:code: 可以直接传入策略代码字符串(如上例)。也可以使用algorithm_id指定一个已存在的策略文件。extras: 这是一个字典。它非常关键,用于向策略传递参数。字典中的key会变成策略中g对象的属性(例如extras={'x': 10}会在策略中生成g.x = 10)。注意:extras的赋值发生在initialize函数执行之后,所以如果在initialize中使用了该变量,建议先判断是否存在或设置默认值。
-
get_backtest(backtest_id):- 返回一个回测对象,用于后续查询。
-
回测对象的方法:
gt.get_status(): 获取状态(running, done, failed, none 等)。gt.get_risk(): 获取总体的风险收益指标(如年化收益、夏普比率、最大回撤等)。gt.get_results(): 获取每日的收益曲线数据。
注意事项
- 并发限制:虽然代码是串行提交的,但聚宽服务器对同时运行的回测数量有限制。如果批量任务非常多,建议在循环中适当增加
time.sleep,或者分批次运行。 - 积分消耗:创建回测可能会消耗聚宽积分(取决于当天的免费额度使用情况),请留意账户积分余额。
- 未来函数:在批量回测时,请确保
start_date和end_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 表示未开始。如果长时间处于此状态,可能是排队人数较多,或者代码有语法错误导致无法启动。建议检查代码字符串是否完整。