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:
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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:
|
||||
"""增量 ATR(Wilder 平滑)
|
||||
|
||||
内部维护完整序列,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:
|
||||
"""增量 RSI(Wilder 平滑)
|
||||
|
||||
内部维护完整序列,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)
|
||||
Reference in New Issue
Block a user