Stockbit Research research Playground @hansputera
indicators.py — 3.5 KB Download
""" Technical indicators computed from OHLCV data. Pure Python — no external dependencies. """ from typing import List, Optional, Tuple def sma(closes: List[float], period: int = 20) -> List[Optional[float]]: """Simple Moving Average.""" result: List[Optional[float]] = [None] * len(closes) for i in range(period - 1, len(closes)): result[i] = sum(closes[i - period + 1: i + 1]) / period return result def ema(closes: List[float], period: int = 20) -> List[Optional[float]]: """Exponential Moving Average.""" if len(closes) < period: return [None] * len(closes) result: List[Optional[float]] = [None] * len(closes) k = 2.0 / (period + 1) result[period - 1] = sum(closes[:period]) / period for i in range(period, len(closes)): result[i] = closes[i] * k + result[i - 1] * (1 - k) return result def rsi(closes: List[float], period: int = 14) -> List[Optional[float]]: """Relative Strength Index.""" if len(closes) < period + 1: return [None] * len(closes) result: List[Optional[float]] = [None] * len(closes) gains = [] losses = [] for i in range(1, len(closes)): delta = closes[i] - closes[i - 1] gains.append(max(delta, 0)) losses.append(max(-delta, 0)) avg_gain = sum(gains[:period]) / period avg_loss = sum(losses[:period]) / period if avg_loss == 0: result[period] = 100.0 else: result[period] = 100.0 - (100.0 / (1.0 + avg_gain / avg_loss)) for i in range(period, len(gains)): avg_gain = (avg_gain * (period - 1) + gains[i]) / period avg_loss = (avg_loss * (period - 1) + losses[i]) / period if avg_loss == 0: result[i + 1] = 100.0 else: result[i + 1] = 100.0 - (100.0 / (1.0 + avg_gain / avg_loss)) return result def macd( closes: List[float], fast: int = 12, slow: int = 26, signal: int = 9, ) -> Tuple[List[Optional[float]], List[Optional[float]], List[Optional[float]]]: """MACD line, Signal line, Histogram.""" ema_fast = ema(closes, fast) ema_slow = ema(closes, slow) n = len(closes) macd_line: List[Optional[float]] = [None] * n for i in range(n): if ema_fast[i] is not None and ema_slow[i] is not None: macd_line[i] = ema_fast[i] - ema_slow[i] signal_line: List[Optional[float]] = [None] * n macd_vals = [v for v in macd_line if v is not None] sig = ema(macd_vals, signal) j = 0 for i in range(n): if macd_line[i] is not None: signal_line[i] = sig[j] if j < len(sig) else None j += 1 histogram: List[Optional[float]] = [None] * n for i in range(n): if macd_line[i] is not None and signal_line[i] is not None: histogram[i] = macd_line[i] - signal_line[i] return macd_line, signal_line, histogram def bollinger( closes: List[float], period: int = 20, std_dev: float = 2.0, ) -> Tuple[List[Optional[float]], List[Optional[float]], List[Optional[float]]]: """Upper, Middle (SMA), Lower Bollinger Bands.""" n = len(closes) middle = sma(closes, period) upper: List[Optional[float]] = [None] * n lower: List[Optional[float]] = [None] * n for i in range(period - 1, n): window = closes[i - period + 1: i + 1] mean = middle[i] variance = sum((x - mean) ** 2 for x in window) / period std = variance ** 0.5 upper[i] = mean + std_dev * std lower[i] = mean - std_dev * std return upper, middle, lower