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:
Rekey
2026-06-13 19:30:25 +08:00
parent b5cdb41993
commit edc50e8809
20 changed files with 484544 additions and 34 deletions
+226
View File
@@ -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)