Merge branch 'indicator-mama' into development

This commit is contained in:
Kevin Johnson
2022-04-13 12:05:24 -07:00
8 changed files with 232 additions and 19 deletions
+5 -3
View File
@@ -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)
<br/>
### **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** |
| - | - |
| &#9744; | Candlesticks |
| &#9744; | Indicators: ```ht_dcperiod```, ```ht_dcphase```, ```ht_phasor```, ```ht_sine```, ```ht_trendline```, ```ht_trendmode```, ```mama``` |
| &#9744; | Indicators: ```ht_dcperiod```, ```ht_dcphase```, ```ht_phasor```, ```ht_sine```, ```ht_trendline```, ```ht_trendmode``` |
| &#9744; | **Numpy**/**Numba**_-ify_ base indicators |
<br/>
+13 -7
View File
@@ -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)
+4 -4
View File
@@ -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"],
+1
View File
@@ -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
+179
View File
@@ -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
+5
View File
@@ -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)
+20
View File
@@ -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)
+5 -5
View File
@@ -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"))