freqtrade 高级回测分析:基于 Entry/Exit 标签的信号归因与指标回溯(backtesting-analysis)
在 freqtrade 中,默认的回测报告只给出整体胜率、总收益等汇总数字;当你需要回答"某一个入场信号到底赚没赚钱""哪些交易是被 stop_loss 打掉的、哪些是被 ROI 带走的""信号出现那根 K 线上 RSI/EMA 各是多少"这类归因问题时,就需要文档所描述的高级回测分析流程:先用 freqtrade backtesting --export=signals 导出信号与成交数据,再用 freqtrade backtesting-analysis 按 enter_tag / exit_tag 多维度分组统计,甚至把信号 K 线上的指标值逐笔打印出来用于精细调参。读完本篇,你将掌握完整的命令用法、各分组(analysis group 0–5)的统计口径,以及该流程在源码层面的实现细节(信号 K 线如何与交易记录匹配、分组表格如何聚合计算、CSV 导出如何落盘)。
一、为什么需要信号级分析
freqtrade 策略通常通过多个互斥的买入条件构成入场逻辑,并用 enter_tag(旧称 buy tag)标记每次入场由哪个条件触发;出场侧同样有 exit_tag、ROI、stop_loss、trailing_stop_loss 等多种原因。默认回测输出无法按这些维度拆解盈亏,因此 docs/advanced-backtesting.md 给出的方案是:
- 回测时额外导出"触发信号的 K 线"数据;
- 用独立命令
backtesting-analysis读取导出文件,输出按信号分组的多级统计表与指标明细。
需要注意文档中明确的前提:
- 该买入/卖出原因分析仅适用于回测(backtesting),不适用于 hyperopt;
- 导出的信号文件可能非常大,应定期检查
user_data/backtest_results目录并清理旧导出; - 在跑下一次回测前,应删除旧的 backtest 结果,或使用
--cache none运行回测,确保不会命中缓存结果(缓存命中时不会重新导出信号数据)。
二、第一步:带 --export=signals 运行回测
要启用信号与成交的联合导出,回测命令必须显式指定 --export 为 signals:
freqtrade backtesting -c <config.json> \
--timeframe <tf> \
--strategy <strategy_name> \
--timerange=<timerange> \
--export=signals
从 cli_options.py 的参数定义可以看到,--export 的合法取值来自 constants.EXPORT_OPTIONS,默认值是 trades(仅导出成交);选择 signals 后,freqtrade 会在 user_data/backtest_results 目录中额外输出两类 pickled 数据(策略名、交易对、以及触发入场/出场信号的 K 线 DataFrame),成功时你可以看到形如:
backtest-result-{timestamp}_signals.pklbacktest-result-{timestamp}_exited.pkl
对应的加载逻辑在 bt_fileutils.py 的 load_backtest_analysis_data() 中:它既支持从独立 .pkl 文件读取,也支持直接从回测结果 .zip 包中提取 {stem}_signals.pkl、{stem}_exited.pkl、{stem}_rejected.pkl 三份数据(函数签名中 name 只允许 "signals" | "rejected" | "exited")。因此新版 zip 打包结果与旧版散落 pkl 文件对 backtesting-analysis 都是兼容的。
三、第二步:运行 backtesting-analysis 并选择分组级别
拿到导出文件后,用如下命令分析入场/出场标签:
freqtrade backtesting-analysis -c <config.json> --analysis-groups 0 1 2 3 4 5
该命令默认读取最近一次回测结果(通过 user_data/backtest_results/.last_result.json 记录的最新文件名定位,见 bt_fileutils.py 的 get_latest_backtest_filename())。--analysis-groups 可传多个取值(0–5),从最粗到最细:
| 组号 | 分组维度 | 说明 |
|---|---|---|
| 0 | enter_tag | 总体胜率与盈亏摘要(wins/losses) |
| 1 | enter_tag | 按入场标签聚合的盈亏摘要 |
| 2 | enter_tag + exit_tag | 入场标签 × 出场标签交叉聚合 |
| 3 | pair + enter_tag | 每个交易对 × 入场标签 |
| 4 | pair + enter_tag + exit_tag | 全维度交叉(可能很大) |
| 5 | exit_tag | 按出场标签聚合 |
CLI 侧的参数定义在 cli_options.py 的 analysis_groups:nargs="+"、choices=["0".."5"],帮助文本与上表一致;完整的命令行帮助输出可参见 docs/commands/backtesting-analysis.md。
分组表格的统计口径(源码视角)
真正的聚合计算在 entryexitanalysis.py 的 _do_group_table_output() 中:
- Group 0:对每类
enter_reason计算total_num_buys、wins、losses、盈利/亏损绝对额(profit_abs_wins/profit_abs_loss)、profit_tot、wl_ratio_pct(胜率百分比)、avg_win、avg_loss,并额外算了一个期望收益比exp_ratio = (1 + avg_win/|avg_loss|) × win率 − 1(源码中exp_ratio一行,行 105–107); - Group 1–5:按各自
group_mask做groupby().agg(),输出num_buys、profit_abs_sum/median/mean、median_profit_pct、mean_profit_pct、total_profit_pct(百分比列在输出前 ×100)。
这意味着 Group 0 适合快速看"哪个入场信号赢率/赔率最优",Group 2 适合看"某入场信号主要被哪类出场收割",Group 5 适合反向审视出场机制(例如 stop_loss 造成的亏损集中在哪些标签)。
四、指定历史结果:--backtest-filename 与 --backtest-directory
默认分析的是最新一次回测;要复盘更早的结果,使用 --backtest-filename 指定文件名(在 user_data/backtest_results 下):
# 先运行一次回测导出结果,日志中会给出带时间戳的文件名
freqtrade backtesting -c <config.json> --strategy <strategy_name> \
--timerange <timerange> --export signals
# 之后指定该文件进行分析
freqtrade backtesting-analysis -c <config.json> \
--backtest-filename=backtest-result-2025-03-05_20-38-34.zip
如果结果存放在别的目录,可再用 --backtest-directory 覆盖基础目录:
freqtrade backtesting-analysis -c <config.json> \
--backtest-directory custom_results/ \
--backtest-filename backtest-result-2025-03-05_20-38-34.zip
源码上,这两个参数分别映射到 exportfilename 与 exportdirectory(cli_options.py 中 --backtest-filename/--export-filename 与 --backtest-directory/--export-directory 是同一参数的别名),最终在 entryexitanalysis.py 的 process_entry_exit_reasons() 里以 config["exportdirectory"] + config["exportfilename"] 传给 load_backtest_stats() / load_backtest_data()。注意 _normalize_filename() 的语义:传入目录时按"目录 + 文件名"解析,传入具体文件路径时直接使用该文件——所以 --backtest-filename 只写文件名即可。
五、标签过滤:--enter-reason-list 与 --exit-reason-list
只想看特定入场/出场标签时,用两个空格分隔的列表参数(默认都是 "all"):
--enter-reason-list : 要分析的入场信号列表,默认 all
--exit-reason-list : 要分析的出场信号列表,默认 all
示例(同时过滤入场标签 enter_tag_a/enter_tag_b 与出场原因 roi、custom_exit_tag_a、stop_loss):
freqtrade backtesting-analysis -c <config.json> \
--analysis-groups 0 2 \
--enter-reason-list enter_tag_a enter_tag_b \
--exit-reason-list roi custom_exit_tag_a stop_loss
实现上非常直接:entryexitanalysis.py 的 _select_rows_by_tags() 中,只要列表不含 "all",就用 df["enter_reason"].isin(...) / df["exit_reason"].isin(...) 过滤交易明细。一个实用的组合是 --analysis-groups 5 --exit-reason-list stop_loss,直接量化止损在所有标签中的分布。
六、信号 K 线指标打印:--indicator-list
这是 backtesting-analysis 最强的能力:把触发信号那根 K 线上策略计算的指标值逐笔打印出来,用于细粒度排查入场/出场时机。
freqtrade backtesting-analysis -c <config.json> \
--analysis-groups 0 2 \
--enter-reason-list enter_tag_a enter_tag_b \
--exit-reason-list roi custom_exit_tag_a stop_loss \
--indicator-list rsi rsi_1h bb_lowerband ema_9 macd macdsignal
关键规则:
- 指标必须存在于策略主 DataFrame 中(主时间框架或 informative 时间框架均可),否则会被静默忽略。源码印证:
print_results()中只有if ind in res_df的列才会进入available_inds(entryexitanalysis.py 行 253–259); - 指标值默认在入场点与出场点各显示一次,列名会加
(entry)/(exit)后缀; - 指定
--indicator-list all时为避免超大输出,只显示入场点的指标。
信号 K 线如何与交易匹配(源码原理)
匹配逻辑在 _analyze_candles_and_indicators()(entryexitanalysis.py 行 40–72):
- 入场侧:对每笔交易取
signal_candles中严格早于open_date的最后一根信号 K 线(buyf.loc[(buyf["date"] < v["open_date"])].iloc[[-1]]),把该 K 线整行(含全部指标列)并入这笔交易记录,并记signal_date; - 出场侧:
process_entry_exit_reasons()会对exited数据重复同样的流程,只是把日期列换成close_date; - 最后
_merge_dfs()以["pair", "open_date"]为键把入场表与出场表拼成宽表,冲突列自动加(entry)/(exit)后缀——这就是输出表中chikou_span (entry)/chikou_span (exit)的来源(行 273–300)。
这个"严格小于"的取法与回测语义一致:信号在 K 线收盘时产生,成交发生在其后,因此取的是产生该笔交易的那根信号 K 线本身,不会引入未来数据。
内置的交易/ K 线字段
以下字段本身就包含在分析数据里,直接写进 --indicator-list 即可打印,无需策略定义:
| 字段 | 含义 |
|---|---|
open_date / close_date |
开仓/平仓时间 |
min_rate / max_rate |
持仓期间最低/最高价 |
open / close / high / low / volume |
信号 K 线的开/收/高/低/量 |
profit_ratio / profit_abs |
交易收益率 / 绝对盈利 |
完整的交易级列清单可参考 bt_fileutils.py 中的 BT_DATA_COLUMNS(pair、stake_amount、amount、open_rate、close_rate、fee_open、fee_close、trade_duration、exit_reason、initial_stop_loss_abs、initial_stop_loss_ratio、stop_loss_abs、stop_loss_ratio、is_open、enter_tag、leverage、is_short、open_timestamp、close_timestamp、orders、funding_fees 等)。这些"贯穿整笔交易"的列在合并时不加 (entry)/(exit) 后缀——_merge_dfs() 用 signal_wide_indicators = set(available_inds) - set(BT_DATA_COLUMNS) 把这类列与信号 K 线指标区分开(行 281)。
输出示例
freqtrade backtesting-analysis -c user_data/config.json \
--analysis-groups 0 --indicator-list chikou_span tenkan_sen
文档给出的样例输出:
| pair | open_date | enter_reason | exit_reason | chikou_span (entry) | tenkan_sen (entry) | chikou_span (exit) | tenkan_sen (exit) |
|---|---|---|---|---|---|---|---|
| DOGE/USDT | 2024-07-06 00:35:00+00:00 | exit_signal | 0.105 | 0.106 | 0.105 | 0.107 | |
| BTC/USDT | 2024-08-05 14:20:00+00:00 | roi | 54643.440 | 51696.400 | 54386.000 | 52072.010 |
(entry) 是开仓时刻的指标值,(exit) 是平仓时刻的值,两者对比即可判断"信号强度在持仓期间是否衰减"。
只看入场或只看出场:--entry-only / --exit-only
--entry-only:只展示入场信号点的指标值;--exit-only:只展示出场信号点的指标值;- 使用这两种过滤时,列名不再加
(entry)/(exit)后缀; - 两者不能同时使用,
process_entry_exit_reasons()会抛出Cannot use --entry-only and --exit-only at the same time(entryexitanalysis.py 行 336–339)。
freqtrade backtesting-analysis -c user_data/config.json \
--analysis-groups 0 --indicator-list chikou_span tenkan_sen --entry-only
freqtrade backtesting-analysis -c user_data/config.json \
--analysis-groups 0 --indicator-list chikou_span tenkan_sen --exit-only
七、按日期过滤:--timerange
回测区间是 20220101-20221231,但只想看 1 月的交易明细时:
--timerange : 过滤输出交易的时间范围,起始含、结束不含,格式 YYYYMMDD-[YYYYMMDD]
freqtrade backtesting-analysis -c <config.json> --timerange 20220101-20220201
实现见 _select_rows_within_dates()(entryexitanalysis.py 行 192–198):用 TimeRange.parse_timerange() 解析后,对 open_date(入场表)/close_date(出场表)做"起点 ≥ 且终点 < "过滤。注意它只过滤分析输出,不会改变已计算好的结果文件。
八、打印被拒绝的信号:--rejected-signals
并非每个入场信号都会变成成交——价格过滤、手续费过滤、仓位已满、pairlock 等都会"拒绝"信号。加上 --rejected-signals 可以列出这些信号(列:pair、date、enter_tag,见 _do_rejected_signals_output()):
freqtrade backtesting-analysis -c <config.json> --rejected-signals
它读取的是导出包中的 {stem}_rejected.pkl。如果没有任何被拒信号,输出 There were no rejected signals.。这个功能对排查"信号很多但交易寥寥"的策略尤其有用——可以判断是过滤器在拦截还是资金/仓位限制在拦截。
九、导出 CSV:--analysis-to-csv 与 --analysis-csv-path
Group 3/4 的表格可能非常大,终端打印并不方便。--analysis-to-csv 会把所有选中的表格写入 CSV 文件(同时关闭终端打印):
freqtrade backtesting-analysis -c <config.json> --analysis-to-csv
例如:
freqtrade backtesting-analysis -c <config.json> --analysis-to-csv --rejected-signals --analysis-groups 0 1
会在 user_data/backtest_results 写出:rejected_signals.csv、group_0.csv、group_1.csv——文件名规则在 _print_table() 中:取表格名(如 Group 0:、Rejected Signals:)小写化、空格替换为下划线、去掉冒号后加 .csv。
用 --analysis-csv-path 可覆盖输出目录(默认取 exportdirectory,即 user_data/backtest_results):
freqtrade backtesting-analysis -c <config.json> \
--analysis-to-csv --analysis-csv-path another/data/path/
注意源码中有两个前置校验(entryexitanalysis.py 行 336–341):--entry-only 与 --exit-only 互斥;--analysis-to-csv 指定的目录必须已存在,否则直接报错。
十、端到端工作流与注意事项小结
把整条链路串起来:
# 1. 清缓存/删旧结果,导出信号并回测
freqtrade backtesting -c user_data/config.json --strategy MyStrat \
--timerange 20240101-20241231 --export signals --cache none
# 2. 宏观:各入场标签的胜率/盈亏
freqtrade backtesting-analysis -c user_data/config.json --analysis-groups 0 1
# 3. 微观:某入场标签在信号 K 线上的 RSI/EMA 值
freqtrade backtesting-analysis -c user_data/config.json --analysis-groups 2 \
--enter-reason-list my_entry_tag --indicator-list rsi ema_9 close profit_abs \
--analysis-to-csv
实践要点:
- 分析命令走的是"离线工具"路径:analyze_commands.py 中
start_analysis_entries_exits()以RunMode.UTIL_NO_EXCHANGE初始化配置,即该命令不需要交易所连接,只需配置文件中策略/路径信息有效; - 指标列找不到时会静默丢弃而非报错,如果输出缺少某个指标,先确认策略
populate_indicators里是否真的生成了该列; - 回测结果缓存(
--cache)命中时不重新导出信号文件,因此改策略后务必--cache none或清理user_data/backtest_results,再运行backtesting-analysis; - 多策略回测的结果中,分析流程按
backtest_stats["strategy"]中的每个策略名逐一加载与输出(process_entry_exit_reasons()的 for 循环),因此多策略场景下每个策略都会得到各自的分组表。
这套"导出信号 K 线 → 按标签分组 → 打印指标值 → CSV 落盘"的组合,把 freqtrade 回测从"结果汇总"推进到"逐笔信号审计"层面:你能精确回答每个入场/出场标签的贡献、被拒信号的去向,以及信号出现那一刻市场指标的状态,从而为策略调优提供可直接验证的证据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00