mirror of
https://github.com/wassname/pandas-ta.git
synced 2026-08-11 11:22:48 +08:00
Merge branch 'indicator-mama' into development
This commit is contained in:
@@ -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** |
|
||||
| - | - |
|
||||
| ☐ | 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 |
|
||||
|
||||
<br/>
|
||||
|
||||
+13
-7
@@ -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
@@ -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"],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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"))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user