"""순수 TA 지표 — sma / rsi_series / highest_high.""" from __future__ import annotations def sma(values: list[float], period: int) -> float | None: if period <= 0 or len(values) < period: return None return sum(values[-period:]) / period def highest_high(highs: list[float], period: int) -> float | None: if period <= 0 or len(highs) < period: return None return max(highs[-period:]) def rsi_series(closes: list[float], period: int = 14) -> list[float]: """Wilder RSI. 반환 리스트는 closes[period:]에 1:1 정렬. 부족하면 [].""" if len(closes) <= period: return [] deltas = [closes[i] - closes[i - 1] for i in range(1, len(closes))] gains = [d if d > 0 else 0.0 for d in deltas] losses = [-d if d < 0 else 0.0 for d in deltas] def _rsi(ag: float, al: float) -> float: if al == 0: return 100.0 rs = ag / al return 100.0 - 100.0 / (1.0 + rs) avg_gain = sum(gains[:period]) / period avg_loss = sum(losses[:period]) / period out = [_rsi(avg_gain, avg_loss)] for i in range(period, len(deltas)): avg_gain = (avg_gain * (period - 1) + gains[i]) / period avg_loss = (avg_loss * (period - 1) + losses[i]) / period out.append(_rsi(avg_gain, avg_loss)) return out