56 lines
2.2 KiB
Python
56 lines
2.2 KiB
Python
"""仅用已收盘日线计算指标,避免把盘中未完成的日线混入信号。"""
|
||
|
||
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)
|