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:
@@ -0,0 +1,259 @@
|
||||
"""
|
||||
多周期策略回测 — 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}<EMA={ema_4h:.2f}",
|
||||
timestamp=kline.open_time,
|
||||
)
|
||||
|
||||
# 30m RSI 过热 → 止盈出场
|
||||
if cur_rsi > 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())
|
||||
Reference in New Issue
Block a user