Files
trade/engine/example/strategy_optimize2.py
Rekey 515e61c517 feat(engine): 添加策略示例集(18 个 Demo)
- backtest_demo.py: 回测基础演示
- strategy_simple.py / three_ema.py / long_short.py: 基础策略(双均线/三均线/多空)
- strategy_optimize*.py (3 版本): 参数优化示例(网格搜索/贝叶斯/遗传算法)
- multi_tf_*.py (4 版本): 多时间框架策略(EMA200/多周期共振/混合信号)
- regime_*.py (4 版本): 市场状态检测(趋势/震荡/波动率区间/全状态)
- cross_section.py: 截面多品种策略
- factor_demo.py: 多因子模型演示
- strategy_battle.py / strategy_more.py: 策略对比与组合
- full_cycle.py: 全流程演示(数据→回测→分析)
- data.py: 数据读取示例
2026-06-12 10:27:04 +08:00

228 lines
9.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
策略深度优化 — 成交量确认 + 时间止损 + 参数扫描
优化点 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())