""" 多周期策略回测 — 4h 定趋势,30m 找买点 策略逻辑: 1. 4h EMA20 判断大趋势:价格 > EMA20 = 上升趋势 2. 30m RSI 寻找入场时机:上升趋势中 RSI < 35 = 回调买入 3. 出场:RSI > 70(超买)或 4h 趋势反转向下 用法: source .venv/bin/activate && python example/multi_tf_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, DBConfig from engine.backtest import BacktestEngine, BacktestConfig from engine.data import DataService from engine.indicators import ema, rsi # ============================================================ # 多周期趋势回调策略 # ============================================================ class MultiTFConfig(StrategyConfig): """多周期策略配置""" # 4h 趋势参数 trend_ema_period: int = 20 # 30m 入场参数 entry_rsi_period: int = 14 entry_rsi_threshold: float = 35.0 # RSI 低于此值视为回调 # 出场参数 exit_rsi_threshold: float = 70.0 # RSI 高于此值出场 # 数据范围(用于预加载 4h 数据) data_start: Optional[datetime] = None data_end: Optional[datetime] = None class MultiTimeframeStrategy(BaseStrategy): """多周期趋势回调策略 ┌─────────────────────────────┐ │ 4h K 线 → EMA20 判断趋势 │ │ Price > EMA20 = 上升趋势 │ └─────────────┬───────────────┘ │ 上升趋势 ▼ ┌─────────────────────────────┐ │ 30m K 线 → 寻找入场时机 │ │ RSI < 35 = 回调买入 │ └─────────────┬───────────────┘ │ 持仓中 ▼ ┌─────────────────────────────┐ │ 出场条件 │ │ RSI > 70 或 4h 趋势反转 │ └─────────────────────────────┘ """ strategy_type = "multi_tf" def __init__(self, config: MultiTFConfig): super().__init__(config) self.cfg: MultiTFConfig = config # 4h 数据(在 on_start 中加载) self._klines_4h: list[Kline] = [] self._ema_4h: list[float] = [] # 30m 数据积累 self._closes_30m: list[float] = [] # 持仓状态 self._has_position: bool = False async def on_start(self) -> None: """预加载 4h K 线数据并计算 EMA""" from engine.common.config import config as app_config ds = DataService(app_config.db) await ds.connect() try: self._klines_4h = await ds.fetch_klines( symbol=self.cfg.symbol, interval="4h", start_time=self.cfg.data_start, end_time=self.cfg.data_end, limit=1_000_000, ) closes_4h = [k.close for k in self._klines_4h] self._ema_4h = ema(closes_4h, self.cfg.trend_ema_period) finally: await ds.close() await super().on_start() def _get_4h_trend(self, ts: float) -> tuple[bool, float, float]: """获取指定时间戳对应的 4h 趋势 只使用已完成的 4h K 线(close_time <= ts),避免前视偏差。 Returns: (is_uptrend, price, ema_value) """ if not self._klines_4h: return False, 0.0, 0.0 # 从后往前找最近已完成的 4h bar for i in range(len(self._klines_4h) - 1, -1, -1): if self._klines_4h[i].close_time <= ts: price = self._klines_4h[i].close ema_val = self._ema_4h[i] if ema_val == 0.0: return False, price, ema_val return price > ema_val, price, ema_val return False, 0.0, 0.0 async def on_kline(self, kline: Kline) -> Optional[Signal]: self._closes_30m.append(kline.close) # ── 获取 4h 趋势 ── is_uptrend, price_4h, ema_4h = self._get_4h_trend(kline.open_time) # ── 计算 30m RSI ── rsi_vals = rsi(self._closes_30m, self.cfg.entry_rsi_period) cur_rsi = rsi_vals[-1] if cur_rsi == 0.0: return None # ── 出场逻辑 ── if self._has_position: # 4h 趋势反转(价格跌破 EMA)→ 止损出场 if not is_uptrend and price_4h > 0: self._has_position = False return Signal( symbol=self.cfg.symbol, side="SELL", signal_type="MARKET", confidence=0.9, reason=f"4h趋势反转 Price={price_4h:.2f} self.cfg.exit_rsi_threshold: self._has_position = False return Signal( symbol=self.cfg.symbol, side="SELL", signal_type="MARKET", confidence=0.8, reason=f"30m RSI过热 {cur_rsi:.1f}>{self.cfg.exit_rsi_threshold}", timestamp=kline.open_time, ) # ── 入场逻辑 ── if not self._has_position: # 条件1:4h 上升趋势 if not is_uptrend: return None # 条件2:30m RSI 回调到超卖区 if cur_rsi < self.cfg.entry_rsi_threshold: self._has_position = True return Signal( symbol=self.cfg.symbol, side="BUY", signal_type="MARKET", confidence=0.7, reason=( f"4h升势回调买入 | " f"4hPrice={price_4h:.0f}>EMA={ema_4h:.0f} | " f"30mRSI={cur_rsi:.1f}" ), timestamp=kline.open_time, ) return None # ============================================================ # 主函数 # ============================================================ async def main(): bt_config = BacktestConfig( symbol="ETHUSDT", interval="30m", 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, ) strategy_config = MultiTFConfig( name="multi_tf_eth", symbol="ETHUSDT", trend_ema_period=20, entry_rsi_period=14, entry_rsi_threshold=35.0, exit_rsi_threshold=70.0, data_start=bt_config.start_time, data_end=bt_config.end_time, ) print() print("╔" + "═" * 60 + "╗") print("║" + " 多周期策略 — 4h 定趋势 / 30m 找买点".center(54) + "║") print("╠" + "═" * 60 + "╣") print(f"║ {'交易对:':<8} {bt_config.symbol:<14} {'周期:':<6} {bt_config.interval:<12} ║") print(f"║ {'时间:':<8} {bt_config.start_time.date()} ~ {bt_config.end_time.date()} ║") print("╠" + "═" * 60 + "╣") print("║ 策略逻辑: ║") print(f"║ 4h EMA{strategy_config.trend_ema_period} → 判断趋势方向 ║") print(f"║ 30m RSI{strategy_config.entry_rsi_period} < {strategy_config.entry_rsi_threshold} → 回调买入 ║") print(f"║ 30m RSI{strategy_config.entry_rsi_period} > {strategy_config.exit_rsi_threshold} → 止盈 / 4h趋势反转 → 止损 ║") print("╚" + "═" * 60 + "╝") print() engine = BacktestEngine(bt_config, db_config=config.db) result = await engine.run(MultiTimeframeStrategy, strategy_config) print(result.summary()) # 打印最近交易 sells = [t for t in result.trades if t.pnl is not None] if sells: print(f"\n最近 10 笔平仓交易:") print(f"{'时间':<22} {'方向':<6} {'价格':>10} {'盈亏':>10} 原因") print("-" * 85) for t in sells[-10:]: dt = datetime.fromtimestamp(t.timestamp / 1000, tz=timezone.utc).strftime("%Y-%m-%d %H:%M") print(f"{dt:<22} {t.side:<6} {t.price:>10.2f} {t.pnl:>+10.2f} {t.reason}") print("\n回测完成。") if __name__ == "__main__": asyncio.run(main())