""" 回测引擎使用示例 — 双策略演示 用法: source .venv/bin/activate && python example/backtest_demo.py """ import asyncio import sys from datetime import datetime, timezone from pathlib import Path from typing import Optional _project_root = Path(__file__).resolve().parent.parent.parent if str(_project_root) not in sys.path: sys.path.insert(0, str(_project_root)) from engine.common.base import BaseStrategy, Signal, StrategyConfig from engine.common.models import Kline from engine.common.config import config from engine.backtest import BacktestEngine, BacktestConfig from engine.indicators import sma, rsi # ============================================================ # 策略 1:双均线交叉 # ============================================================ class MACrossConfig(StrategyConfig): fast_period: int = 7 slow_period: int = 25 class MACrossStrategy(BaseStrategy): """双均线交叉策略 — 使用 engine.indicators.sma 计算均线""" strategy_type = "ma_cross" def __init__(self, config: MACrossConfig): super().__init__(config) self.config: MACrossConfig = config self._closes: list[float] = [] self._last_signal: Optional[str] = None async def on_kline(self, kline: Kline) -> Optional[Signal]: self._closes.append(kline.close) # 使用指标库计算 SMA fast_ma = sma(self._closes, self.config.fast_period) slow_ma = sma(self._closes, self.config.slow_period) fast = fast_ma[-1] slow = slow_ma[-1] if fast == 0.0 or slow == 0.0: return None if fast > slow and self._last_signal != "BUY": self._last_signal = "BUY" return Signal( symbol=self.config.symbol, side="BUY", signal_type="MARKET", confidence=0.8, reason=f"金叉 MA{self.config.fast_period}>{self.config.slow_period}", timestamp=kline.open_time, ) if fast < slow and self._last_signal != "SELL": self._last_signal = "SELL" return Signal( symbol=self.config.symbol, side="SELL", signal_type="MARKET", confidence=0.8, reason=f"死叉 MA{self.config.fast_period}<{self.config.slow_period}", timestamp=kline.open_time, ) return None # ============================================================ # 策略 2:RSI 超买超卖 # ============================================================ class RSIStrategyConfig(StrategyConfig): period: int = 14 oversold: float = 30.0 # 超卖阈值 overbought: float = 70.0 # 超买阈值 class RSIStrategy(BaseStrategy): """RSI 超买超卖策略 — 使用 engine.indicators.rsi 计算 RSI RSI 低于超卖线 → 买入;RSI 高于超买线 → 卖出。 """ strategy_type = "rsi" def __init__(self, config: RSIStrategyConfig): super().__init__(config) self.config: RSIStrategyConfig = config self._closes: list[float] = [] self._has_position = False async def on_kline(self, kline: Kline) -> Optional[Signal]: self._closes.append(kline.close) # 使用指标库计算 RSI rsi_vals = rsi(self._closes, self.config.period) current_rsi = rsi_vals[-1] if current_rsi == 0.0: return None # 超卖 → 买入 if current_rsi < self.config.oversold and not self._has_position: self._has_position = True return Signal( symbol=self.config.symbol, side="BUY", signal_type="MARKET", confidence=0.7, reason=f"RSI超卖 ({current_rsi:.1f} < {self.config.oversold})", timestamp=kline.open_time, ) # 超买 → 卖出 if current_rsi > self.config.overbought and self._has_position: self._has_position = False return Signal( symbol=self.config.symbol, side="SELL", signal_type="MARKET", confidence=0.7, reason=f"RSI超买 ({current_rsi:.1f} > {self.config.overbought})", timestamp=kline.open_time, ) return None # ============================================================ # 主函数 # ============================================================ async def run_backtest( engine: BacktestEngine, strategy_cls, strategy_config: StrategyConfig, label: str, ): """运行一次回测并打印结果""" print(f"\n{'━' * 60}") print(f" {label}") print(f"{'━' * 60}") result = await engine.run(strategy_cls, strategy_config) print(result.summary()) # 最近 5 笔交易 if result.trades: print(f"\n 最近 5 笔交易:") print(f" {'时间':<22} {'方向':<6} {'价格':>10} {'数量':>10} {'盈亏':>10} 原因") for t in result.trades[-5:]: dt = datetime.fromtimestamp(t.timestamp / 1000, tz=timezone.utc).strftime("%Y-%m-%d %H:%M") pnl_str = f"{t.pnl:+.4f}" if t.pnl is not None else "—" print(f" {dt:<22} {t.side:<6} {t.price:>10.4f} {t.quantity:>10.6f} {pnl_str:>10} {t.reason}") return result async def main(): # ── 回测配置 ── bt_config = BacktestConfig( symbol="ETHUSDT", interval="4h", start_time=datetime(2024, 1, 1), end_time=datetime(2026, 1, 1), initial_capital=10_000.0, commission_pct=0.001, slippage_pct=0.0005, warmup_bars=100, ) print(f"\n回测: {bt_config.symbol} {bt_config.interval}") print(f"时间: {bt_config.start_time.date()} ~ {bt_config.end_time.date()}") print(f"初始资金: {bt_config.initial_capital:.2f} USDT") engine = BacktestEngine(bt_config, db_config=config.db) # ── 策略 1:双均线交叉 ── ma_config = MACrossConfig( name="ma_cross_eth", symbol="ETHUSDT", fast_period=7, slow_period=25, ) await run_backtest(engine, MACrossStrategy, ma_config, "策略 1:双均线交叉 (MA7/MA25)") # ── 策略 2:RSI 超买超卖 ── rsi_config = RSIStrategyConfig( name="rsi_eth", symbol="ETHUSDT", period=14, oversold=30.0, overbought=70.0, ) await run_backtest(engine, RSIStrategy, rsi_config, "策略 2:RSI 超买超卖 (30/70)") print("\n全部回测完成。") if __name__ == "__main__": asyncio.run(main())