Files
codex_jxs_code/strategy/run_bb_15m_2020_2025.py
2026-02-26 16:34:30 +08:00

112 lines
4.5 KiB
Python

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