""" BB(10, 2.5) 均值回归策略回测 — 15分钟K线 | 2020-2025 | 200U | 万五手续费 | 90%返佣次日8点到账 """ 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) # ============================================================ # 加载 15 分钟 K 线数据 (2020-2025) # ============================================================ YEARS = list(range(2020, 2026)) data = {} print("加载 15 分钟 K 线数据 (2020-2025)...") t0 = time.time() for y in YEARS: end_year = y + 1 if y < 2025 else 2026 # 2025 数据到 2026-01-01 前 df = load_klines('15m', f'{y}-01-01', f'{end_year}-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") # ============================================================ # 配置: 200U | 万五手续费 | 90%返佣次日8点到账 # ============================================================ cfg = BBConfig( bb_period=10, bb_std=2.5, leverage=50, initial_capital=200.0, margin_pct=0.01, # 1% 权益/单 max_daily_loss=50.0, # 固定值备用 max_daily_loss_pct=0.05, # 日亏损上限 = 当日起始权益的 5% fee_rate=0.0005, # 万五 (0.05%) 开平仓各万五 rebate_rate=0.0, rebate_pct=0.90, # 90% 手续费返佣 rebate_hour_utc=0, # UTC 0点 = 北京时间早上8点到账 # 强平 liq_enabled=True, maint_margin_rate=0.005, # 0.5% 维持保证金率 # 滑点 slippage_pct=0.0005, # 0.05% 滑点 # 市场容量限制 max_notional=500000.0, # 单笔最大名义价值 50万U # 加仓 pyramid_enabled=True, pyramid_step=0.01, # 递增加仓 pyramid_max=3, ) # ============================================================ # 运行回测 (滚仓: 200U 起,逐年累加不复位) # ============================================================ df_full = pd.concat([data[y] for y in YEARS]) r_full = run_bb_backtest(df_full, cfg) d_full = r_full.daily_stats eq_full = d_full["equity"].astype(float) pnl_full = d_full["pnl"].astype(float) eq_curve = r_full.equity_curve["equity"].dropna() final_eq = float(eq_full.iloc[-1]) ret_pct = (final_eq - 200) / 200 * 100 dd_full = float((eq_full - eq_full.cummax()).min()) sharpe_full = float(pnl_full.mean() / pnl_full.std()) * np.sqrt(365) if pnl_full.std() > 0 else 0 win_full = sum(1 for t in r_full.trades if t.net_pnl > 0) / max(len(r_full.trades), 1) * 100 print("=" * 100) print(" BB(10, 2.5) 15分钟K线 | 200U 起滚仓 | 万五 fee | 90% 返佣 | 含强平+滑点+容量限制") print("=" * 100) liq_count = sum(1 for t in r_full.trades if t.fee == 0.0 and t.net_pnl < 0) print(f" 初始本金: 200U | 最终权益: {final_eq:,.0f}U | 收益率: {ret_pct:+,.1f}%") print(f" 交易次数: {len(r_full.trades)} | 胜率: {win_full:.1f}% | 强平次数: {liq_count} | 最大回撤: {dd_full:+,.0f}U") print(f" 总手续费: {r_full.total_fee:,.0f} | 总返佣: {r_full.total_rebate:,.0f} | Sharpe: {sharpe_full:.2f}") print("-" * 100) print(" 年末权益:") for y in YEARS: mask = eq_curve.index.year == y if mask.any(): yr_eq = float(eq_curve.loc[mask].iloc[-1]) print(f" {y} 年末: {yr_eq:,.0f}U") print("=" * 100) # ============================================================ # 生成权益曲线图 (对数坐标,滚仓复利) # ============================================================ fig, ax = plt.subplots(1, 1, figsize=(14, 6), dpi=120) eq_ser = eq_curve days = (eq_ser.index - eq_ser.index[0]).total_seconds() / 86400 ax.semilogy(days, eq_ser.values.clip(min=1), color="#2563eb", linewidth=1.2) ax.axhline(y=200, color="gray", linestyle="--", alpha=0.6) ax.set_title("BB(10, 2.5) 15min | 200U | 0.05% fee 90% rebate | 2020-2025", fontsize=12, fontweight="bold") ax.set_xlabel("Days") ax.set_ylabel("Equity (USDT, log scale)") ax.grid(True, alpha=0.3) chart_path = out_dir / "bb_15m_200u_2020_2025.png" plt.savefig(chart_path, bbox_inches="tight") print(f"\n图表已保存: {chart_path}")