diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 00f695fe..d32862b2 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1207,6 +1207,11 @@ def hma(self, length=None, offset=None, **kwargs: DictLike): result = hma(close=close, length=length, offset=offset, **kwargs) return self._post_process(result, **kwargs) + def ehma(self, length=None, offset=None, **kwargs: DictLike): + close = self._get_column(kwargs.pop("close", "close")) + result = ehma(close=close, length=length, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def hwma(self, na=None, nb=None, nc=None, offset=None, **kwargs: DictLike): close = self._get_column(kwargs.pop("close", "close")) result = hwma(close=close, na=na, nb=nb, nc=nc, offset=offset, **kwargs) diff --git a/pandas_ta/ma.py b/pandas_ta/ma.py index 573a9517..fd790ea3 100644 --- a/pandas_ta/ma.py +++ b/pandas_ta/ma.py @@ -4,6 +4,7 @@ from pandas_ta.overlap.dema import dema from pandas_ta.overlap.ema import ema from pandas_ta.overlap.fwma import fwma +from pandas_ta.overlap.ehma import ehma from pandas_ta.overlap.hma import hma from pandas_ta.overlap.linreg import linreg from pandas_ta.overlap.midpoint import midpoint @@ -25,7 +26,7 @@ def ma(name: str = None, source: Series = None, **kwargs: DictLike) -> Series: """Simple MA Utility for easier MA selection Available MAs: - dema, ema, fwma, hma, linreg, midpoint, pwma, rma, sinwma, sma, ssf, + dema, ema, fwma, hma, ehma, linreg, midpoint, pwma, rma, sinwma, sma, ssf, swma, t3, tema, trima, vidya, wma Examples: @@ -44,7 +45,7 @@ def ma(name: str = None, source: Series = None, **kwargs: DictLike) -> Series: pd.Series: New feature generated. """ _mas = [ - "dema", "ema", "fwma", "hma", "linreg", "midpoint", "pwma", "rma", + "dema", "ema", "fwma", "hma", "ehma", "linreg", "midpoint", "pwma", "rma", "sinwma", "sma", "ssf", "swma", "t3", "tema", "trima", "vidya", "wma" ] if name is None and source is None: @@ -57,6 +58,7 @@ def ma(name: str = None, source: Series = None, **kwargs: DictLike) -> Series: if name == "dema": return dema(source, **kwargs) elif name == "fwma": return fwma(source, **kwargs) elif name == "hma": return hma(source, **kwargs) + elif name == "ehma": return ehma(source, **kwargs) elif name == "linreg": return linreg(source, **kwargs) elif name == "midpoint": return midpoint(source, **kwargs) elif name == "pwma": return pwma(source, **kwargs) diff --git a/pandas_ta/maps.py b/pandas_ta/maps.py index d369c37c..20d04019 100644 --- a/pandas_ta/maps.py +++ b/pandas_ta/maps.py @@ -57,7 +57,7 @@ # Overlap "overlap": [ "alligator", "alma", "dema", "ema", "fwma", "hilo", "hl2", "hlc3", - "hma", "hwma", "ichimoku", "jma", "kama", "linreg", "mama", + "hma", "ehma", "hwma", "ichimoku", "jma", "kama", "linreg", "mama", "mcgd", "midpoint", "midprice", "ohlc4", "pivots", "pwma", "rma", "sinwma", "sma", "smma", "ssf", "ssf3", "supertrend", "swma", "t3", "tema", "trima", "vidya", "wcp", "wma", "zlma" diff --git a/pandas_ta/overlap/__init__.py b/pandas_ta/overlap/__init__.py index d68ac52d..3c2c9b32 100644 --- a/pandas_ta/overlap/__init__.py +++ b/pandas_ta/overlap/__init__.py @@ -8,6 +8,7 @@ from .hl2 import hl2 from .hlc3 import hlc3 from .hma import hma +from .ehma import ehma from .hwma import hwma from .ichimoku import ichimoku from .jma import jma @@ -47,6 +48,7 @@ "hl2", "hlc3", "hma", + "ehma", "hwma", "ichimoku", "jma", diff --git a/pandas_ta/overlap/ehma.py b/pandas_ta/overlap/ehma.py new file mode 100644 index 00000000..14b4a49d --- /dev/null +++ b/pandas_ta/overlap/ehma.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from numpy import sqrt +from pandas import Series +from pandas_ta._typing import DictLike, Int +from pandas_ta.utils import v_offset, v_pos_default, v_series +from .ema import ema + + + +def ehma( + close: Series, length: Int = None, + offset: Int = None, **kwargs: DictLike +) -> Series: + """Hull Moving Average (EHMA) + + The Hull Exponential Moving Average attempts to reduce or remove lag + in moving averages. + + Sources: + https://alanhull.com/hull-moving-average + + Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + offset (int): How many periods to offset the result. Default: 0 + + Kwargs: + fillna (value, optional): pd.DataFrame.fillna(value) + + Returns: + pd.Series: New feature generated. + """ + # Validate + length = v_pos_default(length, 10) + close = v_series(close, length + 2) + + if close is None: + return + + offset = v_offset(offset) + + # Calculate + half_length = int(length / 2) + sqrt_length = int(sqrt(length)) + + emaf = ema(close=close, length=half_length) + emas = ema(close=close, length=length) + ehma = ema(close=2 * emaf - emas, length=sqrt_length) + + # Offset + if offset != 0: + ehma = ehma.shift(offset) + + # Fill + if "fillna" in kwargs: + ehma.fillna(kwargs["fillna"], inplace=True) + + # Name and Category + ehma.name = f"EHMA_{length}" + ehma.category = "overlap" + + return ehma diff --git a/pandas_ta/overlap/zlma.py b/pandas_ta/overlap/zlma.py index dc04e1f6..6e6834cf 100644 --- a/pandas_ta/overlap/zlma.py +++ b/pandas_ta/overlap/zlma.py @@ -10,6 +10,7 @@ from .ema import ema from .fwma import fwma from .hma import hma +from .ehma import ehma from .linreg import linreg from .midpoint import midpoint from .pwma import pwma @@ -60,7 +61,7 @@ def zlma( mamode = v_mamode(mamode, "ema") supported_mas = [ - "dema", "ema", "fwma", "hma", "linreg", "midpoint", "pwma", "rma", + "dema", "ema", "fwma", "hma", "ehma", "linreg", "midpoint", "pwma", "rma", "sinwma", "sma", "ssf", "swma", "t3", "tema", "trima", "vidya", "wma" ] diff --git a/tests/test_indicator_overlap.py b/tests/test_indicator_overlap.py index c3b16d16..c37fd794 100644 --- a/tests/test_indicator_overlap.py +++ b/tests/test_indicator_overlap.py @@ -122,6 +122,12 @@ def test_hma(df): assert result.name == "HMA_10" +def test_ehma(df): + result = ta.ehma(df.close) + assert isinstance(result, Series) + assert result.name == "EHMA_10" + + def test_hwma(df): result = ta.hwma(df.close) assert isinstance(result, Series)