feat: 新增2h/6h时间框架支持,策略重构为增量指标计算
- 数据层: build_aggregates_sql 新增 2h/6h 聚合视图,默认起始时间调整为 2017-05 - 模型层: KlineInterval 类型扩展 2h/6h,DataService 新增对应表名和毫秒映射 - 指标层: 新增 incremental.py 增量指标模块 (EmaInc/AtrInc/RsiInc/BbInc),O(1) per bar - 策略重构: long_short.py 和 regime_all.py 从批量 ema/atr 迁移至增量指标,避免每 bar 重复全量计算 - regime 探测器: RegimeDetector3 改为增量 EMA200,detect() 接口简化 - 回测扩展: regime_timeframe_comparison 从 4h/1d 扩展至 2h/4h/6h/1d - 新增示例: multi_strategy_report, vol_break_compare/periods, intraday_explore, top3_trades 等分析脚本
This commit is contained in:
@@ -23,50 +23,55 @@ from engine.common.base import BaseStrategy, Signal, StrategyConfig
|
||||
from engine.common.models import Kline
|
||||
from engine.common.config import config
|
||||
from engine.backtest import BacktestConfig
|
||||
from engine.indicators import ema, atr
|
||||
from engine.indicators.incremental import EmaInc, AtrInc
|
||||
from engine.data import DataService
|
||||
from engine.example.long_short import LongShortEngine
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════
|
||||
# 3法判定器
|
||||
# 3法判定器(增量 EMA200,O(1) per bar)
|
||||
# ════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
class RegimeDetector3:
|
||||
"""牛熊判定器,内部维护增量 EMA(200),避免每次从头重算"""
|
||||
|
||||
def __init__(self):
|
||||
self._ath = 0.0
|
||||
self._e200 = EmaInc(200)
|
||||
|
||||
def update_ath(self, price: float):
|
||||
def update(self, price: float):
|
||||
"""每根 bar 调一次:更新 ATH + EMA(200)"""
|
||||
if price > self._ath:
|
||||
self._ath = price
|
||||
self._e200.update(price)
|
||||
|
||||
def ema200_slope(self, closes: list[float], idx: int) -> str:
|
||||
if idx < 210: return "unknown"
|
||||
e200 = ema(closes, 200)
|
||||
def _ema200_slope(self, idx: int) -> str:
|
||||
if idx < 220: return "unknown"
|
||||
e200 = self._e200
|
||||
if e200[idx - 20] == 0: return "unknown"
|
||||
slope = (e200[idx] - e200[idx - 20]) / e200[idx - 20]
|
||||
if slope > 0.002: return "bull"
|
||||
if slope < -0.002: return "bear"
|
||||
return "sideways"
|
||||
|
||||
def price_vs_ema200(self, closes: list[float], idx: int) -> str:
|
||||
def _price_vs_ema200(self, price: float, idx: int) -> str:
|
||||
if idx < 210: return "unknown"
|
||||
e200 = ema(closes, 200)
|
||||
if e200[idx] == 0: return "unknown"
|
||||
return "bull" if closes[idx] > e200[idx] else "bear"
|
||||
e = self._e200[idx]
|
||||
if e == 0: return "unknown"
|
||||
return "bull" if price > e else "bear"
|
||||
|
||||
def ath_drawdown(self, closes: list[float], idx: int) -> str:
|
||||
def _ath_drawdown(self, price: float) -> str:
|
||||
if self._ath == 0: return "unknown"
|
||||
dd = (closes[idx] - self._ath) / self._ath
|
||||
dd = (price - self._ath) / self._ath
|
||||
if dd > -0.15: return "bull"
|
||||
if dd < -0.35: return "bear"
|
||||
return "sideways"
|
||||
|
||||
def detect(self, closes: list[float], idx: int) -> str:
|
||||
r1 = self.ema200_slope(closes, idx)
|
||||
r2 = self.price_vs_ema200(closes, idx)
|
||||
r3 = self.ath_drawdown(closes, idx)
|
||||
def detect(self, price: float, idx: int) -> str:
|
||||
r1 = self._ema200_slope(idx)
|
||||
r2 = self._price_vs_ema200(price, idx)
|
||||
r3 = self._ath_drawdown(price)
|
||||
b = sum(1 for r in [r1, r2, r3] if r == "bull")
|
||||
br = sum(1 for r in [r1, r2, r3] if r == "bear")
|
||||
if b >= 2: return "bull"
|
||||
@@ -84,7 +89,7 @@ class RegimeEmaConfig(StrategyConfig):
|
||||
|
||||
|
||||
class RegimeEmaStrategy(BaseStrategy):
|
||||
"""按市场状态自适应做多/做空"""
|
||||
"""按市场状态自适应做多/做空 — 全部指标增量计算,O(1) per bar"""
|
||||
|
||||
strategy_type = "regime_ema"
|
||||
|
||||
@@ -93,19 +98,28 @@ class RegimeEmaStrategy(BaseStrategy):
|
||||
self.cfg = c
|
||||
self._c: list[float] = []; self._h: list[float] = []; self._l: list[float] = []
|
||||
self._detector = RegimeDetector3()
|
||||
self._ema_fast = EmaInc(c.fast)
|
||||
self._ema_slow = EmaInc(c.slow)
|
||||
self._atr = AtrInc(14)
|
||||
self._side: str = ""; self._hp: float = 0.0; self._lp: float = float('inf')
|
||||
|
||||
async def on_kline(self, k: Kline) -> Optional[Signal]:
|
||||
self._c.append(k.close); self._h.append(k.high); self._l.append(k.low)
|
||||
self._detector.update_ath(k.close)
|
||||
|
||||
# 增量更新所有指标(O(1) each)
|
||||
self._detector.update(k.close)
|
||||
self._ema_fast.update(k.close)
|
||||
self._ema_slow.update(k.close)
|
||||
self._atr.update(k.high, k.low, k.close)
|
||||
|
||||
n = len(self._c)
|
||||
if n < 220: return None
|
||||
|
||||
regime = self._detector.detect(self._c, n - 1)
|
||||
regime = self._detector.detect(k.close, n - 1)
|
||||
|
||||
f = ema(self._c, self.cfg.fast); s = ema(self._c, self.cfg.slow)
|
||||
a = atr(self._h, self._l, self._c, 14)
|
||||
cf, cs, ca, pf, ps = f[-1], s[-1], a[-1], f[-2], s[-2]
|
||||
cf, cs = self._ema_fast[-1], self._ema_slow[-1]
|
||||
ca = self._atr[-1]
|
||||
pf, ps = self._ema_fast[-2], self._ema_slow[-2]
|
||||
if cf == 0 or cs == 0 or ca == 0: return None
|
||||
golden = pf <= ps and cf > cs; death = pf >= ps and cf < cs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user