Files
trade/engine/example/strategy_more.py
T
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

317 lines
12 KiB
Python
Raw 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.
"""
更多策略尝试 — 不限于趋势跟踪
新策略:
1. Donchian 海龟 — 20周期突破买入,10周期跌破卖出,ATR止损
2. 价格乖离率 — 偏离 MA50 超 2σ 时反向交易(均值回归)
3. 1h+4h 多TF动量 — 1h MACD 金叉 + 4h EMA 多头共振
币种:BTCUSDT / ETHUSDT / BNBUSDT / SOLUSDT | 周期:4h | 2024-2026
用法:
source .venv/bin/activate && python example/strategy_more.py
"""
import asyncio
import math
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.data import DataService
from engine.indicators import ema, atr, macd, sma
# ════════════════════════════════════════════════════════
# 策略 1Donchian 海龟突破
# ════════════════════════════════════════════════════════
class DonchianConfig(StrategyConfig):
entry_period: int = 20 # 突破周期
exit_period: int = 10 # 退场周期
atr_period: int = 14
atr_stop: float = 2.0 # ATR止损倍数
class DonchianStrategy(BaseStrategy):
"""海龟交易:突破N日最高买入,跌破M日最低卖出"""
strategy_type = "donchian"
def __init__(self, c: DonchianConfig):
super().__init__(c)
self.cfg = c
self._highs: list[float] = []
self._lows: list[float] = []
self._closes: list[float] = []
self._highest: float = 0.0
self._in_position = False
async def on_kline(self, k: Kline) -> Optional[Signal]:
self._highs.append(k.high)
self._lows.append(k.low)
self._closes.append(k.close)
n = len(self._closes)
if n < self.cfg.entry_period + 5:
return None
atr_vals = atr(self._highs, self._lows, self._closes, self.cfg.atr_period)
cur_atr = atr_vals[-1]
if cur_atr == 0:
return None
# 通道计算
entry_high = max(self._highs[-self.cfg.entry_period:-1])
exit_low = min(self._lows[-self.cfg.exit_period:-1])
if self._in_position:
self._highest = max(self._highest, k.high)
stop = self._highest - self.cfg.atr_stop * cur_atr
if k.close < exit_low or k.close < stop:
self._in_position = False
reason = "跌破退出通道" if k.close < exit_low else "ATR止损"
return Signal(symbol=self.cfg.symbol, side="SELL", reason=reason, timestamp=k.open_time)
if not self._in_position:
if k.close > entry_high:
self._in_position = True
self._highest = k.close
return Signal(symbol=self.cfg.symbol, side="BUY",
reason=f"突破{self.cfg.entry_period}日高 {k.close:.0f}>{entry_high:.0f}",
timestamp=k.open_time)
return None
# ════════════════════════════════════════════════════════
# 策略 2:价格乖离率(均值回归)
# ════════════════════════════════════════════════════════
class DeviationConfig(StrategyConfig):
ma_period: int = 50
entry_dev: float = -2.0 # 偏离 sigma 入场(负数=超跌)
exit_dev: float = 0.5 # 回归到此附近出场
atr_period: int = 14
atr_stop: float = 1.5
class DeviationStrategy(BaseStrategy):
"""均值回归:价格暴跌偏离均线 → 买入博反弹"""
strategy_type = "deviation"
def __init__(self, c: DeviationConfig):
super().__init__(c)
self.cfg = c
self._closes: list[float] = []
self._highs: list[float] = []
self._lows: list[float] = []
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)
n = len(self._closes)
if n < self.cfg.ma_period + 5:
return None
ma = sma(self._closes, self.cfg.ma_period)
cur_ma = ma[-1]
if cur_ma == 0:
return None
# 计算标准差
window = self._closes[-self.cfg.ma_period:]
mean = sum(window) / len(window)
variance = sum((x - mean) ** 2 for x in window) / len(window)
stdev = math.sqrt(variance) if variance > 0 else mean * 0.01
# 乖离率(sigma单位)
deviation = (k.close - cur_ma) / stdev if stdev > 0 else 0
atr_vals = atr(self._highs, self._lows, self._closes, self.cfg.atr_period)
if self._in_position:
# 回归到exit_dev sigma内 或 ATR止损
if deviation > self.cfg.exit_dev:
self._in_position = False
return Signal(symbol=self.cfg.symbol, side="SELL",
reason=f"回归均线 dev={deviation:.1f}σ", timestamp=k.open_time)
if not self._in_position:
if deviation < self.cfg.entry_dev:
self._in_position = True
return Signal(symbol=self.cfg.symbol, side="BUY",
confidence=0.6, # 逆势交易降低仓位
reason=f"超跌反弹 dev={deviation:.1f}σ P={k.close:.0f}<MA={cur_ma:.0f}",
timestamp=k.open_time)
return None
# ════════════════════════════════════════════════════════
# 策略 3:多TF动量共振 (1h MACD + 4h EMA)
# ════════════════════════════════════════════════════════
class MultiTFConfig(StrategyConfig):
ema_4h: int = 50
macd_fast: int = 12
macd_slow: int = 26
macd_signal: int = 9
atr_stop: float = 2.5
data_start: Optional[datetime] = None
data_end: Optional[datetime] = None
class MultiTFStrategy(BaseStrategy):
"""1h MACD金叉 + 4h EMA多头 → 共振做多"""
strategy_type = "multi_tf_momentum"
def __init__(self, c: MultiTFConfig):
super().__init__(c)
self.cfg = c
self._klines_4h: list[Kline] = []
self._closes_1h: list[float] = []
self._highs_1h: list[float] = []
self._lows_1h: list[float] = []
self._highest: float = 0.0
self._in_position = False
async def on_start(self):
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,
)
finally:
await ds.close()
await super().on_start()
def _is_4h_bull(self, ts: float) -> bool:
if not self._klines_4h:
return False
closes = [k.close for k in self._klines_4h]
ema_vals = ema(closes, self.cfg.ema_4h)
for i in range(len(self._klines_4h) - 1, -1, -1):
if self._klines_4h[i].close_time <= ts:
return ema_vals[i] > 0 and self._klines_4h[i].close > ema_vals[i]
return False
async def on_kline(self, k: Kline) -> Optional[Signal]:
self._closes_1h.append(k.close)
self._highs_1h.append(k.high)
self._lows_1h.append(k.low)
n = len(self._closes_1h)
if n < 40:
return None
mline, sline, _ = macd(self._closes_1h, self.cfg.macd_fast, self.cfg.macd_slow, self.cfg.macd_signal)
atr_vals = atr(self._highs_1h, self._lows_1h, self._closes_1h, 14)
cur_m, cur_s, cur_atr = mline[-1], sline[-1], atr_vals[-1]
prev_m, prev_s = mline[-2], sline[-2]
if cur_m == 0 or cur_atr == 0:
return None
is_4h_bull = self._is_4h_bull(k.open_time)
golden = prev_m <= prev_s and cur_m > cur_s
if self._in_position:
self._highest = max(self._highest, k.high)
death = prev_m >= prev_s and cur_m < cur_s
stop = self._highest - self.cfg.atr_stop * cur_atr
if death or k.close < stop or not is_4h_bull:
self._in_position = False
reason = "MACD死叉" if death else ("ATR止损" if k.close < stop else "4h转空")
return Signal(symbol=self.cfg.symbol, side="SELL", reason=reason, timestamp=k.open_time)
if not self._in_position:
if golden and is_4h_bull and cur_m > 0:
self._in_position = True
self._highest = k.close
return Signal(symbol=self.cfg.symbol, side="BUY",
reason="1hMACD金叉+4h多头", timestamp=k.open_time)
return None
# ════════════════════════════════════════════════════════
# 运行
# ════════════════════════════════════════════════════════
SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]
DATE_START = datetime(2024, 1, 1)
DATE_END = datetime(2026, 1, 1)
async def run(symbol, s_name, s_cls, s_cfg, interval="4h"):
bt = BacktestConfig(symbol=symbol, interval=interval,
start_time=DATE_START, end_time=DATE_END, initial_capital=10_000.0)
s_cfg.symbol = symbol
if hasattr(s_cfg, 'data_start'):
s_cfg.data_start = DATE_START
s_cfg.data_end = DATE_END
engine = BacktestEngine(bt, db_config=config.db)
return await engine.run(s_cls, s_cfg)
async def main():
strategies = [
("Donchian海龟", DonchianStrategy, DonchianConfig(), "4h"),
("乖离率回归", DeviationStrategy, DeviationConfig(), "4h"),
("1h+4h动量", MultiTFStrategy, MultiTFConfig(), "1h"),
]
print()
print("" * 105)
print(" 更多策略尝试 | 2024-2026")
print("" * 105)
print(f" {'策略':<16} {'币种':<10} {'收益%':>7} {'夏普':>6} {'回撤%':>7} {'交易':>5} {'胜率%':>6} {'盈亏比':>6}")
print("" * 105)
all_rows = []
for s_name, s_cls, s_cfg, interval in strategies:
for symbol in SYMBOLS:
r = await run(symbol, s_name, s_cls, s_cfg.model_copy(), interval)
m = r.metrics
all_rows.append((s_name, symbol, m))
print(f" {s_name:<16} {symbol:<10} {m.total_return_pct:>6.1f}% {m.sharpe_ratio:>6.2f} {m.max_drawdown_pct:>6.1f}% {m.total_trades:>5} {m.win_rate*100:>5.1f}% {m.profit_factor:>6.2f}")
# ── 排名 ──
print("" * 105)
print("\n ■ 按夏普 TOP 5:")
ranked = sorted(all_rows, key=lambda x: x[2].sharpe_ratio, reverse=True)
for i, (s_name, symbol, m) in enumerate(ranked[:5]):
print(f" {i+1}. {symbol} {s_name:<16} 夏普={m.sharpe_ratio:.2f} 收益={m.total_return_pct:+.1f}% 回撤={m.max_drawdown_pct:.1f}% 胜率={m.win_rate*100:.0f}%")
print("\n ■ 各币种最佳:")
for symbol in SYMBOLS:
sym_rows = [(s, m) for s, sym, m in all_rows if sym == symbol]
best = max(sym_rows, key=lambda x: x[1].sharpe_ratio)
print(f" {symbol}: {best[0]:<16} 夏普={best[1].sharpe_ratio:.2f} 收益={best[1].total_return_pct:+.1f}%")
avg_sh = sum(x[2].sharpe_ratio for x in all_rows) / len(all_rows)
print(f"\n 平均夏普: {avg_sh:.2f}")
print("\n" * 105)
if __name__ == "__main__":
asyncio.run(main())