""" BB(10, 2.5) 均值回归策略回测 — 2020-2025 逐年 完全复现 bb_trade.py 的参数: BB(10,2.5) | 50x | 1%权益/单 | 1000U初始资金 测试两种手续费场景: A) 0.06% taker (无返佣) B) 0.025% maker + 返佣 (模拟浏览器下单) """ import sys, time sys.stdout.reconfigure(line_buffering=True) sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[1])) import numpy as np import pandas as pd import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from pathlib import Path from strategy.bb_backtest import BBConfig, run_bb_backtest from strategy.data_loader import load_klines out_dir = Path(__file__).resolve().parent / "results" out_dir.mkdir(parents=True, exist_ok=True) # ============================================================ # 加载数据 # ============================================================ YEARS = list(range(2020, 2027)) data = {} print("加载 5 分钟 K 线数据...") t0 = time.time() for y in YEARS: df = load_klines('5m', f'{y}-01-01', f'{y+1}-01-01') data[y] = df print(f" {y}: {len(df):>7,} 条 ({df.index[0]} ~ {df.index[-1]})") print(f"数据加载完成 ({time.time()-t0:.1f}s)\n") # ============================================================ # 配置 (完全匹配 bb_trade.py) # ============================================================ BASE_KWARGS = dict( bb_period=10, bb_std=2.5, leverage=50, initial_capital=200.0, margin_pct=0.01, # 1% 权益/单 max_daily_loss=50.0, fee_rate=0.0005, # 万五 (0.05%) 每侧 rebate_rate=0.0, rebate_pct=0.90, # 90% 手续费次日返还 rebate_hour_utc=0, # UTC 0点 = 北京时间早上8点 ) configs = { "A) 原版(不加仓)": BBConfig(**BASE_KWARGS, pyramid_enabled=False), "B) 衰减加仓 decay=0.99 max=10": BBConfig(**BASE_KWARGS, pyramid_enabled=True, pyramid_decay=0.99, pyramid_max=10), "C) 衰减加仓 decay=0.99 max=3": BBConfig(**BASE_KWARGS, pyramid_enabled=True, pyramid_decay=0.99, pyramid_max=3), "D) 递增加仓 +1%/次 max=3": BBConfig(**BASE_KWARGS, pyramid_enabled=True, pyramid_step=0.01, pyramid_max=3), "E) 递增加仓 +1%/次 max=10": BBConfig(**BASE_KWARGS, pyramid_enabled=True, pyramid_step=0.01, pyramid_max=10), } # ============================================================ # 运行回测 # ============================================================ all_results = {} for label, cfg in configs.items(): print("=" * 100) print(f" {label}") print(f" BB({cfg.bb_period}, {cfg.bb_std}) | {cfg.leverage}x | margin_pct={cfg.margin_pct:.0%} | fee={cfg.fee_rate:.4%}") print("=" * 100) print(f" {'年份':>6s} {'最终权益':>10s} {'收益率':>8s} {'日均PnL':>8s} {'交易次数':>8s} {'胜率':>6s} " f"{'最大回撤':>10s} {'总手续费':>10s} {'总返佣':>10s} {'Sharpe':>7s}") print("-" * 100) year_results = {} for y in YEARS: r = run_bb_backtest(data[y], cfg) d = r.daily_stats pnl = d["pnl"].astype(float) eq = d["equity"].astype(float) dd = float((eq - eq.cummax()).min()) final_eq = float(eq.iloc[-1]) ret_pct = (final_eq - cfg.initial_capital) / cfg.initial_capital * 100 n_trades = len(r.trades) win_rate = sum(1 for t in r.trades if t.net_pnl > 0) / max(n_trades, 1) * 100 avg_daily = float(pnl.mean()) sharpe = float(pnl.mean() / pnl.std()) * np.sqrt(365) if pnl.std() > 0 else 0 year_results[y] = r print(f" {y:>6d} {final_eq:>10.1f} {ret_pct:>+7.1f}% {avg_daily:>+7.2f}U " f"{n_trades:>8d} {win_rate:>5.1f}% {dd:>+10.1f} " f"{r.total_fee:>10.1f} {r.total_rebate:>10.1f} {sharpe:>7.2f}") all_results[label] = year_results print() # ============================================================ # 生成图表 # ============================================================ fig, axes = plt.subplots(len(configs), 1, figsize=(18, 6 * len(configs)), dpi=120) if len(configs) == 1: axes = [axes] colors = plt.cm.tab10(np.linspace(0, 1, len(YEARS))) for ax, (label, year_results) in zip(axes, all_results.items()): for i, y in enumerate(YEARS): r = year_results[y] eq = r.equity_curve["equity"].dropna() # 归一化到天数 (x轴) days = (eq.index - eq.index[0]).total_seconds() / 86400 ax.plot(days, eq.values, label=f"{y}", color=colors[i], linewidth=0.8) ax.set_title(f"BB(10, 2.5) 50x 1%权益 — {label}", fontsize=13, fontweight="bold") ax.set_xlabel("天数") ax.set_ylabel("权益 (USDT)") ax.axhline(y=1000, color="gray", linestyle="--", alpha=0.5) ax.legend(loc="upper left", fontsize=9) ax.grid(True, alpha=0.3) plt.tight_layout() chart_path = out_dir / "bb_trade_2020_2025_report.png" plt.savefig(chart_path, bbox_inches="tight") print(f"\n图表已保存: {chart_path}") # ============================================================ # 汇总表 # ============================================================ print("\n" + "=" * 80) print(" 汇总: 各场景各年度日均 PnL (U/day)") print("=" * 80) header = f" {'场景':<35s}" + "".join(f"{y:>10d}" for y in YEARS) print(header) print("-" * 80) for label, year_results in all_results.items(): vals = [] for y in YEARS: r = year_results[y] avg = float(r.daily_stats["pnl"].astype(float).mean()) vals.append(avg) row = f" {label:<35s}" + "".join(f"{v:>+10.2f}" for v in vals) print(row) print("=" * 80)