Files
Rekey edc50e8809 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 等分析脚本
2026-06-13 19:30:25 +08:00

245 lines
7.2 KiB
Python
Raw Permalink 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.
"""
增量指标 — 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)