226 lines
8.2 KiB
Python
226 lines
8.2 KiB
Python
"""
|
|
BB策略回测脚本 - 2025年2月27日单日回测
|
|
参数: 逐仓 200U 1% 100倍 万五手续费 90%返佣
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
import pandas as pd
|
|
import numpy as np
|
|
from datetime import datetime
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
|
from strategy.bb_backtest import run_bb_backtest, BBConfig
|
|
from strategy.data_loader import load_klines
|
|
|
|
|
|
def main():
|
|
# 加载2025年2月27日的5分钟K线数据
|
|
print("=" * 80)
|
|
print("加载 2025年2月27日 5分钟K线数据...")
|
|
print("=" * 80)
|
|
|
|
try:
|
|
df = load_klines(
|
|
period='5m',
|
|
start_date='2025-02-27',
|
|
end_date='2025-02-28',
|
|
tz='Asia/Shanghai',
|
|
source='bitmart'
|
|
)
|
|
print(f"✓ 数据加载成功: {len(df)} 根K线")
|
|
if len(df) > 0:
|
|
print(f" 时间范围: {df.index[0]} ~ {df.index[-1]}")
|
|
else:
|
|
print("✗ 无数据可用")
|
|
return
|
|
except Exception as e:
|
|
print(f"✗ 数据加载失败: {e}")
|
|
return
|
|
|
|
# 配置回测参数
|
|
cfg = BBConfig(
|
|
# 布林带参数 (来自 bb_trade.py)
|
|
bb_period=10,
|
|
bb_std=2.5,
|
|
|
|
# 仓位管理
|
|
initial_capital=200.0, # 200U本金
|
|
margin_pct=0.01, # 每次开仓用权益的1%
|
|
leverage=100.0, # 100倍杠杆
|
|
|
|
# 逐仓配置
|
|
cross_margin=False, # 逐仓模式
|
|
maint_margin_rate=0.005, # 0.5% 维持保证金率
|
|
|
|
# 手续费和返佣
|
|
fee_rate=0.0005, # 万五手续费
|
|
rebate_rate=0.0, # 无即时返佣
|
|
rebate_pct=0.90, # 次日返佣90%
|
|
rebate_hour_utc=0, # UTC 0 点 (北京时间8点)
|
|
|
|
# 滑点
|
|
slippage_pct=0.0005, # 0.05% 滑点
|
|
|
|
# 成交模式
|
|
fill_at_close=False, # 用触轨价成交(理想化)
|
|
|
|
# 风控
|
|
max_daily_loss=50.0, # 日最大亏损50U
|
|
max_daily_loss_pct=0.0, # 不启用百分比
|
|
|
|
# 破产模型
|
|
liq_enabled=True, # 启用强平
|
|
|
|
# 加仓配置 (递增模式)
|
|
pyramid_enabled=True,
|
|
pyramid_step=0.01, # 1%增量: 开1%, 加2%, 加3%, 加4%
|
|
pyramid_max=3, # 最多加3次
|
|
)
|
|
|
|
print("\n" + "=" * 80)
|
|
print("回测参数配置")
|
|
print("=" * 80)
|
|
print(f"初始资本: {cfg.initial_capital} USDT")
|
|
print(f"杠杆: {cfg.leverage}x {'逐仓' if not cfg.cross_margin else '全仓'}")
|
|
print(f"开仓比例: {cfg.margin_pct:.0%}(递增: {cfg.pyramid_step:.0%}/次, 最多{cfg.pyramid_max}次)")
|
|
print(f"手续费: {cfg.fee_rate:.0%}")
|
|
print(f"返佣: {cfg.rebate_pct:.0%} 次日 UTC-0 {cfg.rebate_hour_utc}点")
|
|
print(f"布林带: BB({cfg.bb_period}, {cfg.bb_std})")
|
|
print(f"滑点: {cfg.slippage_pct:.0%}")
|
|
print(f"日亏损限制: {cfg.max_daily_loss} USDT")
|
|
print(f"强平: {'启用' if cfg.liq_enabled else '禁用'}")
|
|
|
|
# 运行回测
|
|
print("\n" + "=" * 80)
|
|
print("运行回测...")
|
|
print("=" * 80)
|
|
|
|
try:
|
|
result = run_bb_backtest(df, cfg)
|
|
|
|
# 打印详细结果
|
|
print_backtest_results(result)
|
|
|
|
# 保存结果
|
|
output_dir = Path(__file__).parent / "strategy" / "results"
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
# 保存交易明细
|
|
trades_df = pd.DataFrame([
|
|
{
|
|
"entry_time": t.entry_time,
|
|
"exit_time": t.exit_time,
|
|
"side": t.side,
|
|
"entry_price": f"{t.entry_price:.2f}",
|
|
"exit_price": f"{t.exit_price:.2f}",
|
|
"qty": f"{t.qty:.4f}",
|
|
"margin": f"{t.margin:.2f}",
|
|
"gross_pnl": f"{t.gross_pnl:.2f}",
|
|
"fee": f"{t.fee:.2f}",
|
|
"net_pnl": f"{t.net_pnl:.2f}",
|
|
}
|
|
for t in result.trades
|
|
])
|
|
trades_file = output_dir / f"bb_20250227_trades_{timestamp}.csv"
|
|
trades_df.to_csv(trades_file, index=False, encoding="utf-8-sig")
|
|
print(f"\n✓ 交易明细已保存: {trades_file}")
|
|
|
|
# 保存完整权益曲线
|
|
equity_file = output_dir / f"bb_20250227_equity_{timestamp}.csv"
|
|
result.equity_curve.to_csv(equity_file, encoding="utf-8-sig")
|
|
print(f"✓ 权益曲线已保存: {equity_file}")
|
|
|
|
except Exception as e:
|
|
print(f"✗ 回测失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
|
|
def print_backtest_results(result):
|
|
"""打印回测结果统计"""
|
|
print("\n" + "=" * 80)
|
|
print("回测结果汇总 (2025年2月27日)")
|
|
print("=" * 80)
|
|
|
|
# 基础统计
|
|
trades = result.trades
|
|
equity = result.equity_curve
|
|
config = result.config
|
|
|
|
initial_eq = config.initial_capital
|
|
final_eq = equity["equity"].iloc[-1]
|
|
total_pnl = final_eq - initial_eq
|
|
pnl_pct = (total_pnl / initial_eq) * 100
|
|
|
|
print(f"\n📊 核心指标")
|
|
print(f" 初始权益: {initial_eq:.2f} USDT")
|
|
print(f" 最终权益: {final_eq:.2f} USDT")
|
|
print(f" 日度收益: {total_pnl:+.2f} USDT ({pnl_pct:+.2f}%)")
|
|
print(f" 最高权益: {equity['equity'].max():.2f} USDT")
|
|
print(f" 最低权益: {equity['equity'].min():.2f} USDT")
|
|
|
|
if len(trades) > 0:
|
|
print(f"\n📈 交易统计")
|
|
print(f" 总交易数: {len(trades)} 笔")
|
|
|
|
long_trades = [t for t in trades if t.side == "long"]
|
|
short_trades = [t for t in trades if t.side == "short"]
|
|
print(f" 多头交易: {len(long_trades)} 笔")
|
|
print(f" 空头交易: {len(short_trades)} 笔")
|
|
|
|
# 胜率
|
|
win_trades = [t for t in trades if t.net_pnl > 0]
|
|
loss_trades = [t for t in trades if t.net_pnl < 0]
|
|
print(f" 盈利交易: {len(win_trades)} 笔 ({len(win_trades)/len(trades)*100:.1f}%)")
|
|
print(f" 亏损交易: {len(loss_trades)} 笔 ({len(loss_trades)/len(trades)*100:.1f}%)")
|
|
|
|
if len(win_trades) > 0:
|
|
avg_win = sum(t.net_pnl for t in win_trades) / len(win_trades)
|
|
total_win = sum(t.net_pnl for t in win_trades)
|
|
print(f" 平均盈利: {avg_win:+.2f} USDT")
|
|
print(f" 总盈利: {total_win:+.2f} USDT")
|
|
|
|
if len(loss_trades) > 0:
|
|
avg_loss = sum(t.net_pnl for t in loss_trades) / len(loss_trades)
|
|
total_loss = sum(t.net_pnl for t in loss_trades)
|
|
print(f" 平均亏损: {avg_loss:+.2f} USDT")
|
|
print(f" 总亏损: {total_loss:+.2f} USDT")
|
|
|
|
# 风险指标
|
|
total_net_pnl = sum(t.net_pnl for t in trades)
|
|
if len(trades) > 0:
|
|
max_loss_trade = min(trades, key=lambda t: t.net_pnl)
|
|
max_win_trade = max(trades, key=lambda t: t.net_pnl)
|
|
|
|
print(f"\n💰 盈亏规模")
|
|
print(f" 总手续费支出: {result.total_fee:+.2f} USDT")
|
|
print(f" 返佣总额: +{result.total_rebate:.2f} USDT")
|
|
print(f" 交易净损益: {total_net_pnl:+.2f} USDT")
|
|
print(f" 单笔最大亏损: {max_loss_trade.net_pnl:+.2f} USDT")
|
|
print(f" 单笔最大盈利: {max_win_trade.net_pnl:+.2f} USDT")
|
|
else:
|
|
print(f"\n⚠️ 本日无交易触发")
|
|
|
|
# 波动率统计
|
|
if len(equity) > 0:
|
|
print(f"\n📉 风险指标")
|
|
equity_series = equity["equity"].astype(float)
|
|
running_max = equity_series.expanding().max()
|
|
drawdown = (equity_series / running_max - 1)
|
|
max_dd = drawdown.min() * 100
|
|
|
|
print(f" 最大回撤: {max_dd:.2f}%")
|
|
print(f" 日均价格: {equity['price'].mean():.2f}")
|
|
print(f" 日最高价: {equity['price'].max():.2f}")
|
|
print(f" 日最低价: {equity['price'].min():.2f}")
|
|
|
|
print("\n" + "=" * 80)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|