Files
codex_jxs_code/strategy/run_bb_2020_2025.py
2026-02-25 06:21:49 +08:00

148 lines
5.5 KiB
Python

"""
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)