summaryrefslogtreecommitdiff
path: root/services/strategy-engine/strategies/indicators/volatility.py
diff options
context:
space:
mode:
Diffstat (limited to 'services/strategy-engine/strategies/indicators/volatility.py')
-rw-r--r--services/strategy-engine/strategies/indicators/volatility.py69
1 files changed, 69 insertions, 0 deletions
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