diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index bfb07df..4275b1d 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -49,7 +49,7 @@ Category = { ], # Overlap "overlap": [ - "dema", "ema", "fwma", "hilo", "hl2", "hlc3", "hma", "ichimoku", + "alma", "dema", "ema", "fwma", "hilo", "hl2", "hlc3", "hma", "ichimoku", "kama", "linreg", "mcgd", "midpoint", "midprice", "ohlc4", "pwma", "rma", "sinwma", "sma", "ssf", "supertrend", "swma", "t3", "tema", "trima", "vidya", "vwap", "vwma", "wcp", "wma", "zlma" diff --git a/pandas_ta/core.py b/pandas_ta/core.py index fe82a07..6f73c03 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -955,6 +955,11 @@ class AnalysisIndicators(BasePandasObject): return self._post_process(result, **kwargs) # Overlap + def alma(self, length=None, sigma=None, distribution_offset=None, offset=None, **kwargs): + close = self._get_column(kwargs.pop("close", "close")) + result = alma(close=close, length=length, sigma=sigma, distribution_offset=distribution_offset, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def dema(self, length=None, offset=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) result = dema(close=close, length=length, offset=offset, **kwargs) diff --git a/pandas_ta/overlap/__init__.py b/pandas_ta/overlap/__init__.py index 71725ae..c212759 100644 --- a/pandas_ta/overlap/__init__.py +++ b/pandas_ta/overlap/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from .alma import alma from .dema import dema from .ema import ema from .fwma import fwma diff --git a/pandas_ta/overlap/alma.py b/pandas_ta/overlap/alma.py new file mode 100644 index 0000000..2d8af98 --- /dev/null +++ b/pandas_ta/overlap/alma.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +from numpy import NaN as npNaN +from pandas import Series +from pandas_ta.utils import get_offset, verify_series +import math + + +def alma(close, length=None, sigma=None, distribution_offset=None, offset=None, **kwargs): + """Indicator: Arnaud Legoux Moving Average (ALMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + sigma = float(sigma) if sigma and sigma > 0 else 6.0 + distribution_offset = float(distribution_offset) if distribution_offset and distribution_offset > 0 else 0.85 + offset = get_offset(offset) + + # Pre-Calculations + m = (distribution_offset * (length - 1)) + s = length / sigma + wtd = list(range(length)) + for j in range(0, length): + wtd[j] = math.exp(-1 * ((j - m) * (j - m)) / (2 * s * s)) + + # Calculate Result + result = [npNaN for _ in range(0, length - 1)] + [0] + for i in range(length, close.size): + window_sum = 0 + cum_sum = 0 + for j in range(0, length): + # wtd = math.exp(-1 * ((j - m) * (j - m)) / (2 * s * s)) # moved to pre-calc for efficiency + window_sum = window_sum + wtd[j] * close[i - j] + cum_sum = cum_sum + wtd[j] + almean = window_sum / cum_sum + if i == length: + result.append(npNaN) # additional one bar NaN as pre-roll + else: + result.append(almean) + + alma = Series(result, index=close.index) + + # Offset + if offset != 0: + alma = alma.shift(offset) + + # Handle fills + if "fillna" in kwargs: + alma.fillna(kwargs["fillna"], inplace=True) + if "fill_method" in kwargs: + alma.fillna(method=kwargs["fill_method"], inplace=True) + + # Name & Category + alma.name = f"ALMA_{length}" + alma.category = "overlap" + + return alma + + +alma.__doc__ = \ +"""Arnaud Legoux Moving Average (ALMA) + +The ALMA moving average uses the curve of the Normal (Gauss) distribution, which can be shifted +from 0 to 1. This allows regulating the smoothness and high sensitivity of the indicator. +Sigma is another parameter that is responsible for the shape of the curve coefficients. This moving average +reduces lag of the data in conjunction with smoothing to reduce noise. + +Implemented for Pandas TA by rengel8 based on the source provided below. + +Sources: + https://www.prorealcode.com/prorealtime-indicators/alma-arnaud-legoux-moving-average/ + +Calculation: + refer to provided source + +Args: + close (pd.Series): Series of 'close's + length (int): It's period, window size. Default: 10 + sigma (float): Smoothing value. Default 6.0 + distribution_offset (float): Value to offset the distribution min 0 (smoother), max 1 (more responsive). Default 0.85 + offset (int): How many periods to offset the result. Default: 0 + +Kwargs: + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + +Returns: + pd.Series: New feature generated. +"""