""" 4h EMA50>EMA200 定大势 / 30m 找买点 策略: 4h EMA50 > EMA200 → 中长期多头趋势确认,允许做多 30m 入场 → 4h多头区间中,30m价格上穿EMA20 + 收阳 → 买入 30m 出场 → 下穿EMA20 或 4h趋势打破(EMA50200 多头趋势 + 30m 双EMA金叉 30m 用双EMA交叉替代价格穿越,大幅减少假信号。 """ strategy_type = "ema200_filter" def __init__(self, c: EMA200FilterConfig): super().__init__(c) self.cfg = c self._klines_4h: list[Kline] = [] self._closes: list[float] = [] self._highs: list[float] = [] self._lows: list[float] = [] self._highest: float = 0.0 self._in_position = False async def on_start(self): 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=datetime(2023, 1, 1), end_time=self.cfg.data_end, limit=1_000_000, ) finally: await ds.close() await super().on_start() def _is_4h_bull(self, ts: float) -> bool: if len(self._klines_4h) < 201: return False if not hasattr(self, '_ema50_4h'): closes = [k.close for k in self._klines_4h] self._ema50_4h = ema(closes, 50) self._ema200_4h = ema(closes, 200) for i in range(len(self._klines_4h) - 1, -1, -1): if self._klines_4h[i].close_time <= ts: e50 = self._ema50_4h[i] e200 = self._ema200_4h[i] return e50 > 0 and e200 > 0 and e50 > e200 return False async def on_kline(self, k: Kline) -> Optional[Signal]: self._closes.append(k.close) self._highs.append(k.high) self._lows.append(k.low) n = len(self._closes) need = self.cfg.ema30_slow + 10 if n < need: return None # 30m 双EMA(金叉/死叉) fast = ema(self._closes, self.cfg.ema30_fast) slow = ema(self._closes, self.cfg.ema30_slow) atr_vals = atr(self._highs, self._lows, self._closes, 14) cur_f, cur_s = fast[-1], slow[-1] prev_f, prev_s = fast[-2], slow[-2] cur_atr = atr_vals[-1] if cur_f == 0 or cur_s == 0 or cur_atr == 0: return None is_bull = self._is_4h_bull(k.open_time) # ── 出场 ── if self._in_position: self._highest = max(self._highest, k.high) stop = self._highest - self.cfg.atr_stop * cur_atr death = prev_f >= prev_s and cur_f < cur_s if not is_bull: self._in_position = False return Signal(symbol=self.cfg.symbol, side="SELL", reason="4h转空", timestamp=k.open_time) if k.close < stop: self._in_position = False return Signal(symbol=self.cfg.symbol, side="SELL", reason="ATR止损", timestamp=k.open_time) if death: self._in_position = False return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"30m EMA死叉", timestamp=k.open_time) # ── 入场 ── if not self._in_position and is_bull: golden = prev_f <= prev_s and cur_f > cur_s if golden and k.close > k.open: self._in_position = True self._highest = k.close return Signal(symbol=self.cfg.symbol, side="BUY", reason=f"4h多头+30m金叉", timestamp=k.open_time) return None # ════════════════════════════════════════════════════════ SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"] DATE_START = datetime(2024, 1, 1) DATE_END = datetime(2026, 1, 1) async def main(): print() print("═" * 105) print(" 4h EMA50>EMA200 定大势 / 30m 双EMA金叉 | 2024-2026") print("═" * 105) print(f" {'币种':<10} {'收益%':>7} {'夏普':>6} {'回撤%':>7} {'交易':>5} {'胜率%':>6} {'盈亏比':>6} {'持有%':>6}") print("─" * 105) for symbol in SYMBOLS: sc = EMA200FilterConfig(symbol=symbol, data_start=DATE_START, data_end=DATE_END) bt = BacktestConfig(symbol=symbol, interval="30m", start_time=DATE_START, end_time=DATE_END, initial_capital=10_000.0, warmup_bars=50) engine = BacktestEngine(bt, db_config=config.db) r = await engine.run(EMA200FilterStrategy, sc) m = r.metrics # 持仓时间占比 if r.equity_curve: bars_with_position = sum(1 for e in r.equity_curve if e.get("position", 0) > 0) position_pct = bars_with_position / len(r.equity_curve) * 100 else: position_pct = 0 print(f" {symbol:<10} {m.total_return_pct:>6.1f}% {m.sharpe_ratio:>6.2f} {m.max_drawdown_pct:>6.1f}% {m.total_trades:>5} {m.win_rate*100:>5.1f}% {m.profit_factor:>6.2f} {position_pct:>5.0f}%") # 最近平仓 sells = [t for t in r.trades if t.side == "SELL" and t.pnl is not None] if sells: for t in sells[-2:]: dt = datetime.fromtimestamp(t.timestamp / 1000, tz=timezone.utc).strftime("%m-%d %H:%M") print(f" {'':<10} └ {dt} {t.pnl:>+8.2f} USDT {t.reason}") print("─" * 105) # 对比之前最优 print("\n ■ 对比:之前最优策略") BEST = { "BTCUSDT": ("EMA v3(10,50) 4h", 39.9, 1.03, -11.5, 20), "ETHUSDT": ("EMA v3(10,75) 4h", 53.6, 1.04, -15.3, 18), "BNBUSDT": ("EMA v1(20,50) 4h", 52.0, 0.71, -39.8, 41), "SOLUSDT": ("EMA v3(30,50) 4h", 73.6, 1.18, -25.7, 13), } print(f" {'币种':<10} {'策略':<22} {'收益%':>7} {'夏普':>6} {'回撤%':>7} {'交易':>5}") for symbol in SYMBOLS: name, ret, sh, dd, tr = BEST[symbol] print(f" {symbol:<10} {name:<22} {ret:>6.1f}% {sh:>6.2f} {dd:>6.1f}% {tr:>5}") print("\n═" * 105) if __name__ == "__main__": asyncio.run(main())