问题描述
解决方案
在JoinQuant(聚宽)平台中,要实现回测结束时保存交易记录和决策理由,主要涉及以下三个步骤:
- 初始化存储容器:在
initialize中定义一个全局列表(如g.records)用于暂存数据。 - 记录决策:在下单的同时,将时间、标的、操作、数量以及决策理由写入上述列表。
- 保存文件:利用
on_strategy_end(context)回调函数,在回测结束时将列表转换为 DataFrame,并使用write_fileAPI 保存为 CSV 文件。
以下是一个完整的策略示例代码,展示了如何实现这一功能。
策略代码示例
# -*- coding: utf-8 -*-
import pandas as pd
from jqdata import *
def initialize(context):
# 设置基准
set_benchmark('000300.XSHG')
# 开启动态复权
set_option('use_real_price', True)
# 定义一个全局列表,用于保存交易决策记录
g.decision_records = []
# 设置要操作的股票
g.security = '000001.XSHE'
# 每天运行
run_daily(market_open, time='every_bar')
def market_open(context):
security = g.security
# 获取过去5天和10天的收盘价
close_data = attribute_history(security, 10, '1d', ['close'])
ma5 = close_data['close'][-5:].mean()
ma10 = close_data['close'].mean()
# 获取当前持仓
curr_position = context.portfolio.positions[security].total_amount
cash = context.portfolio.available_cash
# --- 策略逻辑与记录 ---
# 金叉买入
if ma5 > ma10 and curr_position == 0:
# 下单
order_value(security, cash)
# 记录决策理由
record_decision(context, security, 'BUY', '5日均线上穿10日均线,形成金叉,看多买入')
log.info("买入 %s" % security)
# 死叉卖出
elif ma5 < ma10 and curr_position > 0:
# 下单
order_target(security, 0)
# 记录决策理由
record_decision(context, security, 'SELL', '5日均线下穿10日均线,形成死叉,止盈/止损卖出')
log.info("卖出 %s" % security)
def record_decision(context, security, action, reason):
"""
自定义函数:用于将单次决策记录添加到全局列表中
"""
record = {
'datetime': context.current_dt, # 当前时间
'security': security, # 标的代码
'action': action, # 操作方向 (BUY/SELL)
'price': context.portfolio.positions[security].price, # 当前持仓价格或最新价
'reason': reason # 决策理由
}
g.decision_records.append(record)
def on_strategy_end(context):
"""
策略运行结束时调用
在此处将记录保存为文件
"""
if len(g.decision_records) > 0:
# 将列表转换为 DataFrame
df = pd.DataFrame(g.decision_records)
# 调整列的顺序,使其更易读
cols = ['datetime', 'security', 'action', 'price', 'reason']
df = df[cols]
# 将 DataFrame 转换为 CSV 格式的字符串
# encoding='utf_8_sig' 是为了防止 Excel 打开中文乱码
content = df.to_csv(index=False, encoding='utf_8_sig')
# 文件名
file_name = 'backtest_records.csv'
# 保存文件到研究环境根目录
write_file(file_name, content, append=False)
print("策略结束,交易记录已保存至研究环境根目录: %s" % file_name)
else:
print("策略结束,无交易记录。")
代码解析
g.decision_records: 在initialize中初始化为空列表。这是内存中的临时存储容器。record_decision函数: 这是一个自定义的辅助函数。每当策略触发order相关函数时,我们手动调用这个函数,把当时的上下文(时间、股票、操作类型)和最重要的reason(理由) 存入字典,并追加到g.decision_records中。on_strategy_end(context): 这是聚宽提供的标准回调函数,仅在回测结束或模拟交易终止时运行一次。- 我们在这里利用
pandas将列表整理成表格形式。 - 使用
df.to_csv将数据序列化为 CSV 格式字符串。 - 关键 API:
write_file(path, content)将内容写入到聚宽的“研究”环境中。
- 我们在这里利用
如何查看保存的文件?
回测结束后,请前往聚宽官网顶部的 “研究” (Research) 选项卡。在研究环境的文件列表中,你将看到名为 backtest_records.csv 的文件。你可以直接点击下载或在线编辑查看。
Q&A
Q: write_file 保存的文件在哪里?
A: 文件保存在您的“研究”环境根目录下。您可以在聚宽导航栏点击“研究”查看。
Q: 为什么不直接使用 get_trades() 获取记录?
A: get_trades() 只能获取成交的客观数据(如时间、价格、数量),它无法记录您当时下单的“逻辑”或“理由”(例如“因为MACD金叉”)。如果您需要保存决策理由,必须像示例中那样手动记录。
Q: 如果回测中途报错停止,on_strategy_end 会执行吗?
A: 不会。on_strategy_end 仅在策略正常结束(完成所有日期的回测)时调用。如果担心报错导致数据丢失,可以在 after_trading_end(每天收盘后)中进行保存,但这样会频繁进行 IO 操作,降低回测速度。
Q: 可以保存为 Excel 格式吗?
A: write_file 接收的是字符串或二进制数据。虽然可以保存为 .csv(Excel可打开),但如果必须保存为 .xlsx 格式,需要使用 pandas 的 to_excel 配合 BytesIO 流,并将 write_file 的内容设为二进制流。通常情况下,CSV 格式已足够通用。