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: 数据读取示例
This commit is contained in:
Rekey
2026-06-12 10:27:04 +08:00
parent 4da520c14b
commit 515e61c517
21 changed files with 5194 additions and 0 deletions
+227
View File
@@ -0,0 +1,227 @@
"""
策略深度优化 — 成交量确认 + 时间止损 + 参数扫描
优化点 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())