Files
trade/engine/example/analyze_comparison.py
T
Rekey edc50e8809 feat: 新增2h/6h时间框架支持,策略重构为增量指标计算
- 数据层: build_aggregates_sql 新增 2h/6h 聚合视图,默认起始时间调整为 2017-05
- 模型层: KlineInterval 类型扩展 2h/6h,DataService 新增对应表名和毫秒映射
- 指标层: 新增 incremental.py 增量指标模块 (EmaInc/AtrInc/RsiInc/BbInc),O(1) per bar
- 策略重构: long_short.py 和 regime_all.py 从批量 ema/atr 迁移至增量指标,避免每 bar 重复全量计算
- regime 探测器: RegimeDetector3 改为增量 EMA200,detect() 接口简化
- 回测扩展: regime_timeframe_comparison 从 4h/1d 扩展至 2h/4h/6h/1d
- 新增示例: multi_strategy_report, vol_break_compare/periods, intraday_explore, top3_trades 等分析脚本
2026-06-13 19:30:25 +08:00

227 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
读取 full_comparison_result.json 并生成多维度排序对比表
用法:
source .venv/bin/activate && python example/analyze_comparison.py
"""
import json
import sys
from pathlib import Path
_project_root = Path(__file__).resolve().parent.parent.parent
if str(_project_root) not in sys.path:
sys.path.insert(0, str(_project_root))
JSON_PATH = _project_root / "engine" / "example" / "full_comparison_result.json"
with open(JSON_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
results = data["results"]
cfg = data["config"]
# ── 通用排序列 ──
METRICS = [
"总收益%", "年化收益%", "夏普比率", "最大回撤%", "胜率%",
"盈亏比", "交易次数", "平均盈亏", "最佳盈亏", "最差盈亏", "卡尔玛比率",
]
# 哪些指标越大越好(正序),哪些越小越好(倒序)
DESCENDING = {"总收益%", "年化收益%", "夏普比率", "胜率%", "盈亏比", "平均盈亏", "最佳盈亏", "卡尔玛比率"}
ASCENDING = {"最大回撤%", "交易次数", "最差盈亏", "耗时s"}
def sort_results(items, key, descending=True):
"""排序,返回 top N"""
return sorted(items, key=lambda x: x.get(key, -9999) if key in DESCENDING else -x.get(key, 9999), reverse=descending)
def print_table(title, rows, fields, col_widths):
"""打印格式化表格"""
print()
print("" * len(title))
print(title)
print("" * len(title))
print()
# header
header = ""
for i, f in enumerate(fields):
header += f" {f:<{col_widths[i]}}"
print(header)
# separator
sep = " " + "" * (sum(col_widths) + sum(c - len(str(fields[i])) for i, c in enumerate(col_widths)))
print(sep)
for r in rows:
line = ""
for i, f in enumerate(fields):
val = r.get(f, "")
if isinstance(val, float):
if abs(val) >= 1000:
val_str = f"{val:>{col_widths[i]}.0f}"
elif abs(val) >= 100:
val_str = f"{val:>{col_widths[i]}.1f}"
elif abs(val) >= 10:
val_str = f"{val:>{col_widths[i]}.2f}"
else:
val_str = f"{val:>{col_widths[i]}.3f}"
else:
val_str = f"{str(val):<{col_widths[i]}}"
line += f" {val_str}"
print(line)
print()
# ════════════════════════════════════════════════════════
# 表1:全局排名 — 按年化收益 Top 30
# ════════════════════════════════════════════════════════
top_annual = sort_results(results, "年化收益%", True)[:30]
print_table(
f" 全局排名 — 按年化收益 Top 30(共{len(results)}组) | 本金 {cfg['initial_capital']:,.0f} USDT",
top_annual,
fields=["策略名", "币种", "时间级别", "数据量", "总收益%", "年化收益%", "夏普比率", "最大回撤%", "胜率%", "盈亏比", "交易次数", "日期范围"],
col_widths=[22, 10, 8, 8, 9, 9, 8, 8, 7, 7, 6, 24],
)
# ════════════════════════════════════════════════════════
# 表2:按夏普比率 Top 30
# ════════════════════════════════════════════════════════
top_sharpe = sort_results(results, "夏普比率", True)[:30]
print_table(
" 全局排名 — 按夏普比率 Top 30",
top_sharpe,
fields=["策略名", "币种", "时间级别", "数据量", "年化收益%", "夏普比率", "最大回撤%", "胜率%", "盈亏比", "交易次数", "日期范围"],
col_widths=[22, 10, 8, 8, 9, 8, 8, 7, 7, 6, 24],
)
# ════════════════════════════════════════════════════════
# 表3:近半年+近一年(近期真实表现)按年化 Top 30
# ════════════════════════════════════════════════════════
recent = [r for r in results if r["数据量"] in ("近半年", "近一年")]
recent_top = sort_results(recent, "年化收益%", True)[:30]
print_table(
" 近期现实表现 — 近半年+近一年 — 按年化收益 Top 30",
recent_top,
fields=["策略名", "币种", "时间级别", "数据量", "总收益%", "年化收益%", "夏普比率", "最大回撤%", "胜率%", "盈亏比", "交易次数", "日期范围"],
col_widths=[22, 10, 8, 8, 9, 9, 8, 8, 7, 7, 6, 24],
)
# ════════════════════════════════════════════════════════
# 表4:全量数据(历史长期)按年化 Top 20
# ════════════════════════════════════════════════════════
full_data = [r for r in results if r["数据量"] == "全量"]
full_top = sort_results(full_data, "年化收益%", True)[:20]
print_table(
" 全量数据 — 按年化收益 Top 20",
full_top,
fields=["策略名", "币种", "时间级别", "总收益%", "年化收益%", "夏普比率", "最大回撤%", "胜率%", "盈亏比", "交易次数", "日期范围"],
col_widths=[22, 10, 8, 9, 9, 8, 8, 7, 7, 6, 24],
)
# ════════════════════════════════════════════════════════
# 表5:各策略在4h+1d上的近一年表现(最实用的中长线)
# ════════════════════════════════════════════════════════
mid_long = [r for r in recent if r["时间级别"] in ("4h", "1d")]
mid_sorted = sort_results(mid_long, "年化收益%", True)[:30]
print_table(
" 中长线(4h/1d)近期表现 — 按年化收益 Top 30",
mid_sorted,
fields=["策略名", "币种", "时间级别", "数据量", "总收益%", "年化收益%", "夏普比率", "最大回撤%", "胜率%", "盈亏比", "交易次数", "日期范围"],
col_widths=[22, 10, 8, 8, 9, 9, 8, 8, 7, 7, 6, 24],
)
# ════════════════════════════════════════════════════════
# 表6:策略×币种×时间级别 盈利能力矩阵(近一年年化)
# ════════════════════════════════════════════════════════
one_year = [r for r in results if r["数据量"] == "近一年"]
print()
print("" * 120)
print(" 近一年盈利能力矩阵 — 策略 × 币种 × 时间级别(年化收益%")
print("" * 120)
for tf in cfg["timeframes"]:
tf_data = [r for r in one_year if r["时间级别"] == tf]
if not tf_data:
continue
print(f"\n ▲ 时间级别: {tf}")
print(f" {'策略名':<24}", end="")
for sym in cfg["symbols"]:
print(f" {sym:>12}", end="")
print()
print(" " + "" * 80)
strategies_seen = set()
for r in sort_results(tf_data, "年化收益%", True):
if r["策略名"] not in strategies_seen:
strategies_seen.add(r["策略名"])
print(f" {r['策略名']:<24}", end="")
for sym in cfg["symbols"]:
match = [x for x in tf_data if x["策略名"] == r["策略名"] and x["币种"] == sym]
if match:
val = match[0]["年化收益%"]
color = "+" if val > 0 else ""
print(f" {color}{val:>11.1f}%", end="")
else:
print(f" {'':>12}", end="")
print()
print()
# ════════════════════════════════════════════════════════
# 表7:每组(时间级别+数据量)下的最佳策略
# ════════════════════════════════════════════════════════
print("" * 160)
print(" 每组(时间级别 + 数据量)下的最佳策略 — 按年化收益")
print("" * 160)
print()
for tf in cfg["timeframes"]:
for period in cfg["periods"]:
subset = [r for r in results if r["时间级别"] == tf and r["数据量"] == period]
if not subset:
continue
subset_sorted = sort_results(subset, "年化收益%", True)
print(f"{tf} | {period}")
print(f" {'排名':<5} {'策略名':<24} {'币种':<10} {'总收益%':>9} {'年化%':>9} {'夏普':>7} {'回撤%':>8} {'胜率%':>7} {'盈亏比':>7} {'交易':>6} {'卡尔玛':>7} {'日期范围':<24}")
print(" " + "" * 155)
for i, r in enumerate(subset_sorted[:8]):
rank = ["🥇1", "🥈2", "🥉3", " 4", " 5", " 6", " 7", " 8"][i]
print(f" {rank:<5} {r['策略名']:<24} {r['币种']:<10} {r['总收益%']:>8.1f}% {r['年化收益%']:>8.1f}% {r['夏普比率']:>7.2f} {r['最大回撤%']:>7.1f}% {r['胜率%']:>6.1f}% {r['盈亏比']:>7.2f} {r['交易次数']:>6} {r['卡尔玛比率']:>7.2f} {r['日期范围']:<24}")
print()
# ════════════════════════════════════════════════════════
# 表8:策略总览 — 每个策略在所有组合中的盈利比例
# ════════════════════════════════════════════════════════
print("" * 120)
print(" 策略胜率统计 — 每个策略在所有回测组合中的盈利比例")
print("" * 120)
print()
strategy_stats = {}
for r in results:
sn = r["策略名"]
if sn not in strategy_stats:
strategy_stats[sn] = {"total": 0, "positive": 0, "positive_annual": 0, "sum_return": 0, "sum_sharpe": 0}
strategy_stats[sn]["total"] += 1
if r["总收益%"] > 0:
strategy_stats[sn]["positive"] += 1
if r["年化收益%"] > 0:
strategy_stats[sn]["positive_annual"] += 1
strategy_stats[sn]["sum_return"] += r["年化收益%"]
strategy_stats[sn]["sum_sharpe"] += r["夏普比率"]
print(f" {'策略名':<24} {'总回测':>6} {'总收益>0':>9} {'年化>0':>8} {'平均年化%':>10} {'平均夏普':>8}")
print(" " + "" * 75)
for sn, st in sorted(strategy_stats.items(), key=lambda x: x[1]["positive_annual"] / x[1]["total"], reverse=True):
avg_ret = st["sum_return"] / st["total"]
avg_sharpe = st["sum_sharpe"] / st["total"]
pos_pct = st["positive_annual"] / st["total"] * 100
print(f" {sn:<24} {st['total']:>6} {st['positive']:>8} ({st['positive']/st['total']*100:>5.1f}%) {st['positive_annual']:>7} ({pos_pct:>5.1f}%) {avg_ret:>9.1f}% {avg_sharpe:>8.2f}")
print()
print("" * 120)