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
+3
View File
@@ -17,6 +17,7 @@ from .trend import sma, ema, macd, macd_signal, macd_histogram, adx
from .momentum import rsi, stoch, stoch_k, stoch_d
from .volatility import bollinger, bollinger_upper, bollinger_mid, bollinger_lower, atr
from .volume import obv, vwap
from .incremental import EmaInc, AtrInc, RsiInc, BbInc
__all__ = [
# 趋势
@@ -27,4 +28,6 @@ __all__ = [
"bollinger", "bollinger_upper", "bollinger_mid", "bollinger_lower", "atr",
# 成交量
"obv", "vwap",
# 增量
"EmaInc", "AtrInc", "RsiInc", "BbInc",
]
+244
View File
@@ -0,0 +1,244 @@
"""
增量指标 — O(1) 每 bar 更新,避免每次从头重算整条序列
策略在 on_kline 中对每根 bar 调用 update(),内部只计算增量值,
对外暴露 values 属性(完整序列,支持索引回溯),兼顾性能与易用性。
用法:
from engine.indicators.incremental import EmaInc, AtrInc
e200 = EmaInc(200)
for price in prices:
e200.update(price)
print(e200[-1]) # 最新 EMA 值
print(e200[-20]) # 20 根前的 EMA 值(斜率计算用)
"""
from typing import Optional
class EmaInc:
"""增量 EMA
内部维护完整序列,update() 为 O(1)values 为 list[float] 可直接索引。
"""
def __init__(self, period: int):
self.period = period
self.k = 2.0 / (period + 1)
self._values: list[float] = []
self._warm: list[float] = []
self._ready = False
def update(self, price: float) -> float:
"""输入新价格,返回最新 EMA 值(不足周期时返回 0)"""
if not self._ready:
self._warm.append(price)
self._values.append(0.0)
if len(self._warm) == self.period:
self._values[-1] = sum(self._warm) / self.period
self._warm.clear()
self._ready = True
return self._values[-1]
return 0.0
val = price * self.k + self._values[-1] * (1 - self.k)
self._values.append(val)
return val
@property
def values(self) -> list[float]:
return self._values
@property
def current(self) -> float:
return self._values[-1] if self._values else 0.0
def __getitem__(self, idx: int) -> float:
return self._values[idx]
def __len__(self) -> int:
return len(self._values)
class AtrInc:
"""增量 ATRWilder 平滑)
内部维护完整序列,update() 为 O(1)values 为 list[float] 可直接索引。
"""
def __init__(self, period: int = 14):
self.period = period
self._values: list[float] = []
self._tr_buffer: list[float] = []
self._prev_close: Optional[float] = None
self._ready = False
def update(self, high: float, low: float, close: float) -> float:
"""输入新 bar 的 HLC,返回最新 ATR 值(不足周期时返回 0)"""
# 第一根 bar:记录收盘价,无法计算 TR
if self._prev_close is None:
self._prev_close = close
self._values.append(0.0)
return 0.0
tr = max(high - low, abs(high - self._prev_close), abs(low - self._prev_close))
self._prev_close = close
if not self._ready:
self._tr_buffer.append(tr)
self._values.append(0.0)
if len(self._tr_buffer) == self.period:
atr_val = sum(self._tr_buffer) / self.period
self._values[-1] = atr_val
self._tr_buffer.clear()
self._ready = True
return atr_val
return 0.0
# Wilder 平滑
atr_val = (self._values[-1] * (self.period - 1) + tr) / self.period
self._values.append(atr_val)
return atr_val
@property
def values(self) -> list[float]:
return self._values
@property
def current(self) -> float:
return self._values[-1] if self._values else 0.0
def __getitem__(self, idx: int) -> float:
return self._values[idx]
def __len__(self) -> int:
return len(self._values)
class RsiInc:
"""增量 RSIWilder 平滑)
内部维护完整序列,update() 为 O(1)。
"""
def __init__(self, period: int = 14):
self.period = period
self._values: list[float] = []
self._prev_price: Optional[float] = None
self._avg_gain: float = 0.0
self._avg_loss: float = 0.0
self._changes: list[float] = []
self._ready = False
def update(self, price: float) -> float:
if self._prev_price is None:
self._prev_price = price
self._values.append(0.0)
return 0.0
change = price - self._prev_price
self._prev_price = price
if not self._ready:
self._changes.append(change)
self._values.append(0.0)
if len(self._changes) == self.period:
gains = [max(c, 0.0) for c in self._changes]
losses = [abs(min(c, 0.0)) for c in self._changes]
self._avg_gain = sum(gains) / self.period
self._avg_loss = sum(losses) / self.period
self._changes.clear()
self._ready = True
rs = self._avg_gain / self._avg_loss if self._avg_loss > 0 else float("inf")
rsi = 100.0 - (100.0 / (1.0 + rs)) if self._avg_loss > 0 else 100.0
self._values[-1] = rsi
return rsi
return 0.0
gain = max(change, 0.0)
loss = abs(min(change, 0.0))
self._avg_gain = (self._avg_gain * (self.period - 1) + gain) / self.period
self._avg_loss = (self._avg_loss * (self.period - 1) + loss) / self.period
if self._avg_loss == 0:
rsi = 100.0
else:
rs = self._avg_gain / self._avg_loss
rsi = 100.0 - (100.0 / (1.0 + rs))
self._values.append(rsi)
return rsi
@property
def values(self) -> list[float]:
return self._values
@property
def current(self) -> float:
return self._values[-1] if self._values else 0.0
def __getitem__(self, idx: int) -> float:
return self._values[idx]
def __len__(self) -> int:
return len(self._values)
class BbInc:
"""增量布林带
内部维护完整序列,update() 返回 (upper, mid, lower) 三元组。
"""
def __init__(self, period: int = 20, std: float = 2.0):
self.period = period
self.std = std
self._upper: list[float] = []
self._mid: list[float] = []
self._lower: list[float] = []
self._window: list[float] = []
self._window_sum: float = 0.0
self._window_sum_sq: float = 0.0
def update(self, price: float) -> tuple[float, float, float]:
self._window.append(price)
self._window_sum += price
self._window_sum_sq += price * price
if len(self._window) < self.period:
self._upper.append(0.0)
self._mid.append(0.0)
self._lower.append(0.0)
return 0.0, 0.0, 0.0
if len(self._window) > self.period:
old = self._window.pop(0)
self._window_sum -= old
self._window_sum_sq -= old * old
mean = self._window_sum / self.period
variance = (self._window_sum_sq / self.period) - (mean * mean)
stdev = max(variance, 0.0) ** 0.5
upper = mean + self.std * stdev
lower = mean - self.std * stdev
self._upper.append(upper)
self._mid.append(mean)
self._lower.append(lower)
return upper, mean, lower
@property
def upper(self) -> list[float]:
return self._upper
@property
def mid(self) -> list[float]:
return self._mid
@property
def lower(self) -> list[float]:
return self._lower
def __len__(self) -> int:
return len(self._mid)