From 8b0cf4e574390738ee33f7ff334dd5f5109b7819 Mon Sep 17 00:00:00 2001 From: TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> Date: Wed, 1 Apr 2026 18:37:11 +0900 Subject: feat(strategy): add technical indicators library (ATR, ADX, RSI, MACD, Bollinger, Stochastic, OBV) --- .../strategies/indicators/volatility.py | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 services/strategy-engine/strategies/indicators/volatility.py (limited to 'services/strategy-engine/strategies/indicators/volatility.py') diff --git a/services/strategy-engine/strategies/indicators/volatility.py b/services/strategy-engine/strategies/indicators/volatility.py new file mode 100644 index 0000000..d47eb86 --- /dev/null +++ b/services/strategy-engine/strategies/indicators/volatility.py @@ -0,0 +1,69 @@ +"""Volatility indicators: ATR, Bollinger Bands, Keltner Channels.""" +import pandas as pd +import numpy as np + + +def atr( + highs: pd.Series, + lows: pd.Series, + closes: pd.Series, + period: int = 14, +) -> pd.Series: + """Average True Range using Wilder's smoothing.""" + high = highs.values + low = lows.values + close = closes.values + n = len(close) + + tr = np.zeros(n) + tr[0] = high[0] - low[0] + for i in range(1, n): + tr[i] = max( + high[i] - low[i], + abs(high[i] - close[i - 1]), + abs(low[i] - close[i - 1]), + ) + + atr_vals = np.full(n, np.nan) + if n >= period: + atr_vals[period - 1] = np.mean(tr[:period]) + for i in range(period, n): + atr_vals[i] = (atr_vals[i - 1] * (period - 1) + tr[i]) / period + + return pd.Series(atr_vals, index=closes.index if hasattr(closes, 'index') else None) + + +def bollinger_bands( + closes: pd.Series, + period: int = 20, + num_std: float = 2.0, +) -> tuple[pd.Series, pd.Series, pd.Series]: + """Bollinger Bands. + + Returns: (upper_band, middle_band, lower_band) + """ + middle = closes.rolling(window=period).mean() + std = closes.rolling(window=period).std() + upper = middle + num_std * std + lower = middle - num_std * std + return upper, middle, lower + + +def keltner_channels( + highs: pd.Series, + lows: pd.Series, + closes: pd.Series, + ema_period: int = 20, + atr_period: int = 14, + atr_multiplier: float = 2.0, +) -> tuple[pd.Series, pd.Series, pd.Series]: + """Keltner Channels. + + Returns: (upper_channel, middle_ema, lower_channel) + """ + from strategies.indicators.trend import ema as calc_ema + middle = calc_ema(closes, ema_period) + atr_vals = atr(highs, lows, closes, atr_period) + upper = middle + atr_multiplier * atr_vals + lower = middle - atr_multiplier * atr_vals + return upper, middle, lower -- cgit v1.2.3