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 等分析脚本
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
"""
|
||||
读取 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)
|
||||
Reference in New Issue
Block a user