""" 策略深度优化 — 成交量确认 + 时间止损 + 参数扫描 优化点 v3: 1. 成交量确认:金叉当日成交量 > 前20根均量 × 1.3,过滤缩量假突破 2. 时间止损:持仓超过48根K线自动平仓,避免死扛 3. 参数扫描:对 EMA(fast, slow) 组合做网格搜索,每个币种找最优参数 用法: source .venv/bin/activate && python example/strategy_optimize2.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, BacktestResult from engine.indicators import ema, atr # ════════════════════════════════════════════════════════ # EMA v3: 趋势 + ATR止损 + 时间止损 + 成交量确认 # ════════════════════════════════════════════════════════ class EmaV3Config(StrategyConfig): fast: int = 20 slow: int = 50 atr_period: int = 14 atr_stop_mult: float = 3.0 time_stop_bars: int = 48 # 时间止损(30m下48根=1天,4h下48根=8天) vol_factor: float = 1.3 # 成交量确认倍数 class EmaV3Strategy(BaseStrategy): """EMA v3: 金叉+放量买入,ATR止损+时间止损出场""" strategy_type = "ema_v3" def __init__(self, c: EmaV3Config): super().__init__(c) self.cfg = c self._closes: list[float] = [] self._highs: list[float] = [] self._lows: list[float] = [] self._volumes: list[float] = [] self._highest_since_entry: float = 0.0 self._bars_held: int = 0 self._in_position = 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) self._volumes.append(k.volume) n = len(self._closes) if n < self.cfg.slow + 20: return None fast = ema(self._closes, self.cfg.fast) slow = ema(self._closes, self.cfg.slow) atr_vals = atr(self._highs, self._lows, self._closes, self.cfg.atr_period) cur_f, cur_s, cur_atr = fast[-1], slow[-1], atr_vals[-1] if cur_f == 0 or cur_s == 0 or cur_atr == 0: return None # 成交量确认:最近bar成交量 > 前20根均量 × factor avg_vol = sum(self._volumes[-21:-1]) / 20 if n > 21 else 0 vol_confirmed = k.volume > avg_vol * self.cfg.vol_factor if avg_vol > 0 else True if self._in_position: self._bars_held += 1 self._highest_since_entry = max(self._highest_since_entry, k.high) stop_price = self._highest_since_entry - self.cfg.atr_stop_mult * cur_atr death_cross = fast[-2] >= slow[-2] and cur_f < cur_s time_up = self._bars_held >= self.cfg.time_stop_bars if k.close < stop_price: self._in_position = False return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"ATR止损", timestamp=k.open_time) if death_cross: self._in_position = False return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"EMA死叉", timestamp=k.open_time) if time_up: self._in_position = False return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"时间止损({self._bars_held}bar)", timestamp=k.open_time) if not self._in_position: golden = fast[-2] <= slow[-2] and cur_f > cur_s if golden and vol_confirmed: self._in_position = True self._entry_price = k.close self._highest_since_entry = k.close self._bars_held = 0 return Signal(symbol=self.cfg.symbol, side="BUY", reason=f"金叉+放量{'✓' if vol_confirmed else ''} V={k.volume:.0f}", timestamp=k.open_time) return None # ════════════════════════════════════════════════════════ # 参数扫描 + 对比 # ════════════════════════════════════════════════════════ SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"] PARAM_GRID = [ # (fast, slow) (10, 40), (10, 50), (10, 75), (20, 40), (20, 50), (20, 75), (30, 40), (30, 50), (30, 75), ] # 原始 v1 基线 BASELINE = { "BTCUSDT": (20, 50, 45.5, 0.74, -31.6), "ETHUSDT": (20, 50, 24.4, 0.47, -54.8), "BNBUSDT": (20, 50, 52.0, 0.71, -39.8), "SOLUSDT": (20, 50, 27.8, 0.49, -39.5), } async def scan_one(symbol: str, fast: int, slow: int) -> dict: """单次参数扫描""" bt = BacktestConfig( symbol=symbol, interval="4h", start_time=datetime(2024, 1, 1), end_time=datetime(2026, 1, 1), initial_capital=10_000.0, ) sc = EmaV3Config(symbol=symbol, fast=fast, slow=slow) engine = BacktestEngine(bt, db_config=config.db) r = await engine.run(EmaV3Strategy, sc) m = r.metrics return {"fast": fast, "slow": slow, "return": m.total_return_pct, "sharpe": m.sharpe_ratio, "dd": m.max_drawdown_pct, "trades": m.total_trades, "wr": m.win_rate} async def main(): print() print("═" * 105) print(" EMA v3 深度优化 — 成交量+时间止损+参数扫描 | 4h 周期 | 2024-2026") print("═" * 105) all_results: dict[str, list[dict]] = {} for symbol in SYMBOLS: print(f"\n ▸ {symbol} 参数扫描 (9 组)...") symbol_results = [] for fast, slow in PARAM_GRID: r = await scan_one(symbol, fast, slow) symbol_results.append(r) print(f" EMA({fast},{slow}) 收益={r['return']:>+6.1f}% 夏普={r['sharpe']:>6.2f} 回撤={r['dd']:>6.1f}% 交易={r['trades']:>3} 胜率={r['wr']*100:>5.1f}%") all_results[symbol] = symbol_results # ── 每个币种最佳参数 ── print("\n" + "═" * 105) print(" ■ 各币种最优参数:") print(f" {'币种':<10} {'最优参数':<16} {'收益%':>7} {'夏普':>6} {'回撤%':>7} {'交易':>5} {'胜率%':>6} vs 基线") print("─" * 105) best_params = {} for symbol in SYMBOLS: # 按夏普排序 sorted_r = sorted(all_results[symbol], key=lambda x: x["sharpe"], reverse=True) best = sorted_r[0] best_params[symbol] = (best["fast"], best["slow"]) base = BASELINE[symbol] delta = best["return"] - base[2] print(f" {symbol:<10} EMA({best['fast']},{best['slow']}){'':>8} {best['return']:>6.1f}% {best['sharpe']:>6.2f} {best['dd']:>6.1f}% {best['trades']:>5} {best['wr']*100:>5.1f}% Δ={delta:+.1f}%") # ── 汇总对比 ── print("\n" + "═" * 105) print(" ■ 三层对比:基线(v1) → 止损(v2) → 成交量+时间止损+最优参数(v3)") print(f" {'币种':<10} {'版本':<20} {'收益%':>7} {'夏普':>6} {'回撤%':>7} {'交易':>5} {'胜率%':>6}") print("─" * 105) # v2 数据(从上次运行) V2 = { "BTCUSDT": (20, 50, 7.0, 0.27, -27.8), "ETHUSDT": (20, 50, 41.3, 0.76, -29.6), "BNBUSDT": (20, 50, 43.6, 0.81, -26.1), "SOLUSDT": (20, 50, 48.1, 0.70, -26.6), } for symbol in SYMBOLS: # 基线 b = BASELINE[symbol] print(f" {symbol:<10} {'v1 原始 EMA双均线':<20} {b[2]:>6.1f}% {b[3]:>6.2f} {b[4]:>6.1f}%") # v2 v2 = V2[symbol] print(f" {symbol:<10} {'v2 +ATR止损':<20} {v2[2]:>6.1f}% {v2[3]:>6.2f} {v2[4]:>6.1f}%") # v3 最佳 best = [r for r in all_results[symbol] if r["fast"] == best_params[symbol][0] and r["slow"] == best_params[symbol][1]][0] print(f" {symbol:<10} {'v3 全优化+最优参数':<20} {best['return']:>6.1f}% {best['sharpe']:>6.2f} {best['dd']:>6.1f}%") print() # ── 组合收益 ── print("─" * 105) print(" ■ 等权组合(4币种各投入2500 USDT):") total_baseline = sum(BASELINE[s][2] for s in SYMBOLS) / 4 * 100 total_v2 = sum(V2[s][2] for s in SYMBOLS) / 4 * 100 total_v3 = sum( sorted(all_results[s], key=lambda x: x["sharpe"], reverse=True)[0]["return"] for s in SYMBOLS ) / 4 * 100 print(f" v1 基线组合: {total_baseline:+.0f} USDT") print(f" v2 止损组合: {total_v2:+.0f} USDT") print(f" v3 全优化组合: {total_v3:+.0f} USDT") print("\n═" * 105) print(" 扫描完成。") print("═" * 105) if __name__ == "__main__": asyncio.run(main())