This commit is contained in:
2026-09-17 00:56:05 +08:00
parent 33693adb66
commit c7d39938a2
14 changed files with 1005 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
"""仅用已收盘日线计算指标,避免把盘中未完成的日线混入信号。"""
from dataclasses import dataclass
from datetime import date, datetime
from decimal import Decimal, ROUND_CEILING
import math
from statistics import fmean, pstdev
from .config import ETFConfig
@dataclass(frozen=True)
class Indicators:
day: str
ma60: float
atr: float
lower: float
middle: float
upper: float
grid: float
def calculate(rows: list[dict], today: date, cfg: ETFConfig) -> Indicators:
"""MA60 + Wilder ATR + BOLL总体标准差格距向上取整到 0.001 元。"""
bars = {}
for row in rows:
day = datetime.strptime(str(row['date']), '%Y%m%d').date()
if day >= today:
continue
if day in bars:
raise ValueError('日线包含重复日期')
high, low, close = (float(row[key]) for key in ('high', 'low', 'close'))
if not all(math.isfinite(v) and v > 0 for v in (high, low, close)) or not low <= close <= high:
raise ValueError('日线价格无效')
bars[day] = (high, low, close)
days = sorted(bars)
if len(days) < max(60, cfg.atr_period + 1, cfg.boll_period):
raise ValueError('已收盘日线不足,至少需要 60 根且能计算 ATR')
# 长期停牌或历史缓存未补齐时不使用过期信号;春节等长假允许 15 个自然日。
if (today - days[-1]).days > 15:
raise ValueError('最近日线超过 15 个自然日,需补齐行情')
values = [bars[d] for d in days]
closes = [v[2] for v in values]
tr = [max(h - l, abs(h - closes[i - 1]), abs(l - closes[i - 1]))
for i, (h, l, _) in enumerate(values) if i > 0]
n = cfg.atr_period
atr = fmean(tr[:n])
for value in tr[n:]:
atr = (atr * (n - 1) + value) / n
ma = fmean(closes[-60:])
window = closes[-cfg.boll_period:]
middle, width = fmean(window), cfg.boll_std * pstdev(window)
raw_grid = max(atr * cfg.atr_multiplier, ma * cfg.min_grid_pct / 100, 0.001)
grid = float(Decimal(str(raw_grid)).quantize(Decimal('0.001'), rounding=ROUND_CEILING))
return Indicators(days[-1].strftime('%Y%m%d'), ma, atr, middle - width, middle, middle + width, grid)