Files
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

219 lines
6.6 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.
"""
回测引擎使用示例 — 双策略演示
用法:
source .venv/bin/activate && python example/backtest_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
from engine.backtest import BacktestEngine, BacktestConfig
from engine.indicators import sma, rsi
# ============================================================
# 策略 1:双均线交叉
# ============================================================
class MACrossConfig(StrategyConfig):
fast_period: int = 7
slow_period: int = 25
class MACrossStrategy(BaseStrategy):
"""双均线交叉策略 — 使用 engine.indicators.sma 计算均线"""
strategy_type = "ma_cross"
def __init__(self, config: MACrossConfig):
super().__init__(config)
self.config: MACrossConfig = config
self._closes: list[float] = []
self._last_signal: Optional[str] = None
async def on_kline(self, kline: Kline) -> Optional[Signal]:
self._closes.append(kline.close)
# 使用指标库计算 SMA
fast_ma = sma(self._closes, self.config.fast_period)
slow_ma = sma(self._closes, self.config.slow_period)
fast = fast_ma[-1]
slow = slow_ma[-1]
if fast == 0.0 or slow == 0.0:
return None
if fast > slow and self._last_signal != "BUY":
self._last_signal = "BUY"
return Signal(
symbol=self.config.symbol,
side="BUY",
signal_type="MARKET",
confidence=0.8,
reason=f"金叉 MA{self.config.fast_period}>{self.config.slow_period}",
timestamp=kline.open_time,
)
if fast < slow and self._last_signal != "SELL":
self._last_signal = "SELL"
return Signal(
symbol=self.config.symbol,
side="SELL",
signal_type="MARKET",
confidence=0.8,
reason=f"死叉 MA{self.config.fast_period}<{self.config.slow_period}",
timestamp=kline.open_time,
)
return None
# ============================================================
# 策略 2RSI 超买超卖
# ============================================================
class RSIStrategyConfig(StrategyConfig):
period: int = 14
oversold: float = 30.0 # 超卖阈值
overbought: float = 70.0 # 超买阈值
class RSIStrategy(BaseStrategy):
"""RSI 超买超卖策略 — 使用 engine.indicators.rsi 计算 RSI
RSI 低于超卖线 → 买入;RSI 高于超买线 → 卖出。
"""
strategy_type = "rsi"
def __init__(self, config: RSIStrategyConfig):
super().__init__(config)
self.config: RSIStrategyConfig = config
self._closes: list[float] = []
self._has_position = False
async def on_kline(self, kline: Kline) -> Optional[Signal]:
self._closes.append(kline.close)
# 使用指标库计算 RSI
rsi_vals = rsi(self._closes, self.config.period)
current_rsi = rsi_vals[-1]
if current_rsi == 0.0:
return None
# 超卖 → 买入
if current_rsi < self.config.oversold and not self._has_position:
self._has_position = True
return Signal(
symbol=self.config.symbol,
side="BUY",
signal_type="MARKET",
confidence=0.7,
reason=f"RSI超卖 ({current_rsi:.1f} < {self.config.oversold})",
timestamp=kline.open_time,
)
# 超买 → 卖出
if current_rsi > self.config.overbought and self._has_position:
self._has_position = False
return Signal(
symbol=self.config.symbol,
side="SELL",
signal_type="MARKET",
confidence=0.7,
reason=f"RSI超买 ({current_rsi:.1f} > {self.config.overbought})",
timestamp=kline.open_time,
)
return None
# ============================================================
# 主函数
# ============================================================
async def run_backtest(
engine: BacktestEngine,
strategy_cls,
strategy_config: StrategyConfig,
label: str,
):
"""运行一次回测并打印结果"""
print(f"\n{'' * 60}")
print(f" {label}")
print(f"{'' * 60}")
result = await engine.run(strategy_cls, strategy_config)
print(result.summary())
# 最近 5 笔交易
if result.trades:
print(f"\n 最近 5 笔交易:")
print(f" {'时间':<22} {'方向':<6} {'价格':>10} {'数量':>10} {'盈亏':>10} 原因")
for t in result.trades[-5:]:
dt = datetime.fromtimestamp(t.timestamp / 1000, tz=timezone.utc).strftime("%Y-%m-%d %H:%M")
pnl_str = f"{t.pnl:+.4f}" if t.pnl is not None else ""
print(f" {dt:<22} {t.side:<6} {t.price:>10.4f} {t.quantity:>10.6f} {pnl_str:>10} {t.reason}")
return result
async def main():
# ── 回测配置 ──
bt_config = BacktestConfig(
symbol="ETHUSDT",
interval="4h",
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,
)
print(f"\n回测: {bt_config.symbol} {bt_config.interval}")
print(f"时间: {bt_config.start_time.date()} ~ {bt_config.end_time.date()}")
print(f"初始资金: {bt_config.initial_capital:.2f} USDT")
engine = BacktestEngine(bt_config, db_config=config.db)
# ── 策略 1:双均线交叉 ──
ma_config = MACrossConfig(
name="ma_cross_eth",
symbol="ETHUSDT",
fast_period=7,
slow_period=25,
)
await run_backtest(engine, MACrossStrategy, ma_config, "策略 1:双均线交叉 (MA7/MA25)")
# ── 策略 2:RSI 超买超卖 ──
rsi_config = RSIStrategyConfig(
name="rsi_eth",
symbol="ETHUSDT",
period=14,
oversold=30.0,
overbought=70.0,
)
await run_backtest(engine, RSIStrategy, rsi_config, "策略 2RSI 超买超卖 (30/70)")
print("\n全部回测完成。")
if __name__ == "__main__":
asyncio.run(main())