diff --git a/README.md b/README.md
index b9407d7..c1b8d8d 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ _Pandas Technical Analysis_ (**Pandas TA**) is a free, Open Source, and easy to
* [Candles](#candles-64)
* [Cycles](#cycles-2)
* [Momentum](#momentum-42)
- * [Overlap](#overlap-36)
+ * [Overlap](#overlap-37)
* [Performance](#performance-3)
* [Statistics](#statistics-11)
* [Transform](#transform-3)
@@ -913,7 +913,7 @@ Back to [Contents](#contents)
-### **Overlap** (36)
+### **Overlap** (37)
* _Bill Williams Alligator_: **alligator**
* _Arnaud Legoux Moving Average_: **alma**
@@ -932,6 +932,8 @@ Back to [Contents](#contents)
* _Jurik Moving Average_: **jma**
* _Kaufman's Adaptive Moving Average_: **kama**
* _Linear Regression_: **linreg**
+* _Ehler's MESA Adapative Moving Average_: **mama**
+ * Includes: **fama**
* _McGinley Dynamic_: **mcgd**
* _Midpoint_: **midpoint**
* _Midprice_: **midprice**
@@ -1254,7 +1256,7 @@ TODO
| **Status** | **Remaining TA Lib Indicators** |
| - | - |
| ☐ | Candlesticks |
-| ☐ | Indicators: ```ht_dcperiod```, ```ht_dcphase```, ```ht_phasor```, ```ht_sine```, ```ht_trendline```, ```ht_trendmode```, ```mama``` |
+| ☐ | Indicators: ```ht_dcperiod```, ```ht_dcphase```, ```ht_phasor```, ```ht_sine```, ```ht_trendline```, ```ht_trendmode``` |
| ☐ | **Numpy**/**Numba**_-ify_ base indicators |
diff --git a/pandas_ta/core.py b/pandas_ta/core.py
index 4c1e8a7..9861e4f 100644
--- a/pandas_ta/core.py
+++ b/pandas_ta/core.py
@@ -364,13 +364,14 @@ class AnalysisIndicators(object):
# Add prefix/suffix and append to the dataframe
self._add_prefix_suffix(result=result, **kwargs)
- if "append" in kwargs and kwargs["append"]:
- # Default: Appends result to DataFrame
- self._append(result=result, **kwargs)
- if "append" in kwargs and kwargs["append"] is None:
- # Issue 388 - No appending, just print to stdout
- # No DatetimeIndex could break execution.
- print(result)
+ if "append" in kwargs and isinstance(kwargs["append"], bool):
+ if not kwargs["append"]:
+ # Issue 388 - No appending, just print to stdout
+ # No DatetimeIndex could break execution.
+ print(result)
+ else:
+ # Default: Appends result to DataFrame
+ self._append(result=result, **kwargs)
return result
def _study_mode(self, *args: Args) -> Tuple:
@@ -1232,6 +1233,11 @@ class AnalysisIndicators(object):
result = linreg(close=close, length=length, offset=offset, adjust=adjust, **kwargs)
return self._post_process(result, **kwargs)
+ def mama(self, fastlimit=None, slowlimit=None, prenan: Int = None, offset: Int = None, **kwargs: DictLike):
+ close = self._get_column(kwargs.pop("close", "close"))
+ result = mama(close=close, fastlimit=fastlimit, slowlimit=slowlimit, prenan=prenan, offset=offset, **kwargs)
+ return self._post_process(result, **kwargs)
+
def mcgd(self, length=None, offset: Int = None, **kwargs: DictLike):
close = self._get_column(kwargs.pop("close", "close"))
result = mcgd(close=close, length=length, offset=offset, **kwargs)
diff --git a/pandas_ta/maps.py b/pandas_ta/maps.py
index cb2caf7..f04d49e 100644
--- a/pandas_ta/maps.py
+++ b/pandas_ta/maps.py
@@ -56,10 +56,10 @@ Category: Dict[str, ListStr] = {
# Overlap
"overlap": [
"alligator", "alma", "dema", "ema", "fwma", "hilo", "hl2", "hlc3",
- "hma", "hwma", "ichimoku", "jma", "kama", "linreg", "mcgd", "midpoint",
- "midprice", "ohlc4", "pwma", "rma", "sinwma", "sma", "smma", "ssf",
- "ssf3", "supertrend", "swma", "t3", "tema", "trima", "vidya", "vwap",
- "vwma", "wcp", "wma", "zlma"
+ "hma", "hwma", "ichimoku", "jma", "kama", "linreg", "mama",
+ "mcgd", "midpoint", "midprice", "ohlc4", "pwma", "rma", "sinwma",
+ "sma", "smma", "ssf", "ssf3", "supertrend", "swma", "t3", "tema",
+ "trima", "vidya", "vwap", "vwma", "wcp", "wma", "zlma"
],
# Performance
"performance": ["log_return", "percent_return"],
diff --git a/pandas_ta/overlap/__init__.py b/pandas_ta/overlap/__init__.py
index af83e17..2adc09f 100644
--- a/pandas_ta/overlap/__init__.py
+++ b/pandas_ta/overlap/__init__.py
@@ -13,6 +13,7 @@ from .ichimoku import ichimoku
from .jma import jma
from .kama import kama
from .linreg import linreg
+from .mama import mama
from .mcgd import mcgd
from .midpoint import midpoint
from .midprice import midprice
diff --git a/pandas_ta/overlap/mama.py b/pandas_ta/overlap/mama.py
new file mode 100644
index 0000000..3e676cb
--- /dev/null
+++ b/pandas_ta/overlap/mama.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+from numpy import arctan, nan, zeros_like
+from pandas import DataFrame, Series
+from pandas_ta._typing import Array, DictLike, Int, IntFloat
+from pandas_ta.maps import Imports
+from pandas_ta.utils import v_offset, v_pos_default, v_series, v_talib
+
+
+try:
+ from numba import njit
+except ImportError:
+ def njit(_): return _
+
+
+@njit
+def np_mama(
+ x: Array, fastlimit: IntFloat, slowlimit: IntFloat, prenan: Int
+):
+ """Ehler's Mother of Adaptive Moving Averages
+ http://traders.com/documentation/feedbk_docs/2014/01/traderstips.html
+ """
+ a1, a2 = 0.0962, 0.5769
+ p_w, smp_w, smp_w_c = 0.2, 0.33, 0.67 # smp_w + smp_w_c = 1
+
+ sm = zeros_like(x)
+ dt, smp, q1, q2 = sm.copy(), sm.copy(), sm.copy(), sm.copy()
+ i1, i2, jI, jQ = sm.copy(), sm.copy(), sm.copy(), sm.copy()
+ re, im, alpha = sm.copy(), sm.copy(), sm.copy()
+ period, phase, mama, fama = sm.copy(), sm.copy(), sm.copy(), sm.copy()
+
+ n = x.size
+ # Ehler's starts from 6, TV-LB starts at 3, TALib 32
+ for i in range(3, n):
+ w_period = .075 * period[i - 1] + .54
+
+ # Smoother and Detrend the Smoother
+ sm[i] = 0.4 * x[i] + 0.3 * x[i - 1] + 0.2 * x[i - 2] + 0.1 * x[i - 3]
+ dt[i] = w_period * (a1 * sm[i] + a2 * sm[i - 2] - a2 * sm[i - 4] - a1 * sm[i - 6])
+
+ # Quadrature(Detrender) and In Phase Component
+ q1[i] = w_period * (a1 * dt[i] + a2 * dt[i - 2] - a2 * dt[i - 4] - a1 * dt[i - 6])
+ i1[i] = dt[i - 3]
+
+ # Phase advance I1 and Q1 by 90 degrees
+ jI[i] = w_period * (a1 * i1[i] + a2 * i1[i - 2] - a2 * i1[i - 4] - a1 * i1[i - 6])
+ jQ[i] = w_period * (a1 * q1[i] + a2 * q1[i - 2] - a2 * q1[i - 4] - a1 * q1[i - 6])
+
+ # Phasor Addition for 3 Bar Averaging
+ i2[i] = i1[i] - jQ[i]
+ q2[i] = q1[i] + jI[i]
+
+ # Smooth I and Q components
+ i2[i] = p_w * i2[i] + (1 - p_w) * i2[i - 1]
+ q2[i] = p_w * q2[i] + (1 - p_w) * q2[i - 1]
+
+ # Homodyne Discriminator
+ re[i] = i2[i] * i2[i - 1] + q2[i] * q2[i - 1]
+ im[i] = i2[i] * q2[i - 1] + q2[i] * i2[i - 1]
+
+ re[i] = p_w * re[i] + (1 - p_w) * re[i - 1]
+ im[i] = p_w * im[i] + (1 - p_w) * im[i - 1]
+
+ if im[i] != 0.0 and re[i] != 0.0:
+ period[i] = 360 / arctan(im[i] / re[i])
+ else:
+ period[i] = 0
+
+ if period[i] > 1.5 * period[i - 1]:
+ period[i] = 1.5 * period[i - 1]
+ if period[i] < 0.67 * period[i - 1]:
+ period[i] = 0.67 * period[i - 1]
+ if period[i] < 6:
+ period[i] = 6
+ if period[i] > 50:
+ period[i] = 50
+
+ period[i] = p_w * period[i] + (1 - p_w) * period[i - 1]
+ smp[i] = smp_w * period[i] + smp_w_c * smp[i - 1]
+
+ if i1[i] != 0.0:
+ phase[i] = arctan(q1[i] / i1[i])
+
+ dphase = phase[i - 1] - phase[i]
+ if dphase < 1:
+ dphase = 1
+
+ alpha[i] = fastlimit / dphase
+ if alpha[i] > fastlimit:
+ alpha[i] = fastlimit
+ if alpha[i] < slowlimit:
+ alpha[i] = slowlimit
+
+ mama[i] = alpha[i] * x[i] + (1 - alpha[i]) * mama[i - 1]
+ fama[i] = 0.5 * alpha[i] * mama[i] + (1 - 0.5 * alpha[i]) * fama[i - 1]
+
+ mama[:prenan], fama[:prenan] = nan, nan
+ return mama, fama
+
+
+def mama(
+ close: Series, fastlimit: IntFloat = None, slowlimit: IntFloat = None,
+ prenan: Int = None, talib: bool = None,
+ offset: Int = None, **kwargs: DictLike
+) -> Series:
+ """Ehler's MESA Adapative Moving Average (MAMA)
+
+ Ehler's MESA Adapative Moving Average (MAMA) aka the Mother of All Moving
+ Averages attempts to adapt to the source's dynamic nature. The adapation
+ is based on the rate change of phase as measured by the Hilbert
+ Transform Discriminator. The advantage of this method of adaptation is
+ that it features a fast attack average and a slow decay average so that
+ the composite average rapidly adjusts to price changes and holds
+ the average value until the next change occurs. This indicator also
+ includes FAMA.
+
+ Sources:
+ Ehler's Mother of Adaptive Moving Averages:
+ http://traders.com/documentation/feedbk_docs/2014/01/traderstips.html
+ https://www.tradingview.com/script/foQxLbU3-Ehlers-MESA-Adaptive-Moving-Average-LazyBear/
+
+ Args:
+ close (pd.Series): Series of 'close's
+ fastlimit (float): Fast limit. Default: 0.5
+ slowlimit (float): Slow limit. Default: 0.05
+ prenan (int): Prenans to apply. TV-LB 3, Ehler's 6, TALib 32
+ Default: 3
+ talib (bool): If TA Lib is installed and talib is True, Returns
+ the TA Lib version. Default: True
+ 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.DataFrame: MAMA and FAMA columns.
+ """
+ # Validate
+ close = v_series(close)
+
+ if close is None:
+ return
+
+ fastlimit = v_pos_default(fastlimit, 0.5)
+ slowlimit = v_pos_default(slowlimit, 0.05)
+ prenan = v_pos_default(prenan, 3)
+ mode_tal = v_talib(talib)
+ offset = v_offset(offset)
+
+ # Calculate
+ np_close = close.values
+ if Imports["talib"] and mode_tal:
+ from talib import MAMA
+ mama, fama = MAMA(np_close, fastlimit, slowlimit)
+ else:
+ mama, fama = np_mama(np_close, fastlimit, slowlimit, prenan)
+
+ # Name and Category
+ _props = f"_{fastlimit}_{slowlimit}"
+ df = DataFrame({
+ f"MAMA{_props}": mama,
+ f"FAMA{_props}": fama,
+ }, index=close.index)
+
+ df.name = f"MAMA{_props}"
+ df.category = "overlap"
+
+ # Offset
+ if offset != 0:
+ df = df.shift(offset)
+
+ # Fill
+ if "fillna" in kwargs:
+ df.fillna(kwargs["fillna"], inplace=True)
+
+ if "fill_method" in kwargs:
+ df.fillna(method=kwargs["fill_method"], inplace=True)
+
+ return df
\ No newline at end of file
diff --git a/tests/test_ext_indicator_overlap_ext.py b/tests/test_ext_indicator_overlap_ext.py
index 70f7ac1..d9c5461 100644
--- a/tests/test_ext_indicator_overlap_ext.py
+++ b/tests/test_ext_indicator_overlap_ext.py
@@ -88,6 +88,11 @@ class TestOverlapExtension(TestCase):
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "LR_14")
+ def test_mama_ext(self):
+ self.data.ta.mama(append=True)
+ self.assertIsInstance(self.data, DataFrame)
+ self.assertEqual(list(self.data.columns[-2:]), ["MAMA_0.5_0.05", "FAMA_0.5_0.05"])
+
def test_mcgd_ext(self):
self.data.ta.mcgd(append=True)
self.assertIsInstance(self.data, DataFrame)
diff --git a/tests/test_indicator_overlap.py b/tests/test_indicator_overlap.py
index d33ec29..9c0968f 100644
--- a/tests/test_indicator_overlap.py
+++ b/tests/test_indicator_overlap.py
@@ -271,6 +271,26 @@ class TestOverlap(TestCase):
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "FWMA_15")
+ def test_mama(self):
+ """Overlap: MAMA/FAMA"""
+ result = pandas_ta.mama(self.close, talib=False)
+ self.assertIsInstance(result, DataFrame)
+ self.assertEqual(result.name, "MAMA_0.5_0.05")
+
+ try:
+ expected = tal.MAMA(self.close)
+ pdt.assert_series_equal(result, expected, check_names=False)
+ except AssertionError:
+ try:
+ corr = pandas_ta.utils.df_error_analysis(result, expected)
+ self.assertGreater(corr, CORRELATION_THRESHOLD)
+ except Exception as ex:
+ error_analysis(result, CORRELATION, ex)
+
+ result = pandas_ta.mama(self.close)
+ self.assertIsInstance(result, DataFrame)
+ self.assertEqual(result.name, "MAMA_0.5_0.05")
+
def test_mcgd(self):
"""Overlap: MCGD"""
result = pandas_ta.mcgd(self.close)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index aab59df..d1ee359 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -42,19 +42,19 @@ class TestUtilities(TestCase):
del self.utils
def test__add_prefix_suffix(self):
- result = self.data.ta.hl2(append=False, prefix="pre")
+ result = self.data.ta.hl2(prefix="pre")
self.assertEqual(result.name, "pre_HL2")
- result = self.data.ta.hl2(append=False, suffix="suf")
+ result = self.data.ta.hl2(suffix="suf")
self.assertEqual(result.name, "HL2_suf")
- result = self.data.ta.hl2(append=False, prefix="pre", suffix="suf")
+ result = self.data.ta.hl2(prefix="pre", suffix="suf")
self.assertEqual(result.name, "pre_HL2_suf")
- result = self.data.ta.hl2(append=False, prefix=1, suffix=2)
+ result = self.data.ta.hl2(prefix=1, suffix=2)
self.assertEqual(result.name, "1_HL2_2")
- result = self.data.ta.macd(append=False, prefix="pre", suffix="suf")
+ result = self.data.ta.macd(prefix="pre", suffix="suf")
for col in result.columns:
self.assertTrue(col.startswith("pre_") and col.endswith("_suf"))