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:
Rekey
2026-06-13 19:30:25 +08:00
parent b5cdb41993
commit edc50e8809
20 changed files with 484544 additions and 34 deletions
+36 -22
View File
@@ -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法判定器(增量 EMA200O(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