From cefa083ed90ca55bde3a7ed0c2015de4996c33c6 Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Sun, 25 Jul 2021 15:31:32 -0700 Subject: [PATCH] ENH #342 jma indicator MAINT jma ext TST DOC --- README.md | 10 ++- pandas_ta/__init__.py | 6 +- pandas_ta/core.py | 5 ++ pandas_ta/overlap/__init__.py | 1 + pandas_ta/overlap/jma.py | 114 ++++++++++++++---------- setup.py | 2 +- tests/test_ext_indicator_overlap_ext.py | 5 ++ tests/test_indicator_overlap.py | 5 ++ 8 files changed, 91 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 459cca5..50b2f69 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever * [Candles](#candles-64) * [Cycles](#cycles-1) * [Momentum](#momentum-41) - * [Overlap](#overlap-32) + * [Overlap](#overlap-33) * [Performance](#performance-3) * [Statistics](#statistics-11) * [Trend](#trend-18) @@ -112,7 +112,7 @@ $ pip install pandas_ta Latest Version -------------- -Best choice! Version: *0.3.10b* +Best choice! Version: *0.3.11b* * Includes all fixes and updates between **pypi** and what is covered in this README. ```sh $ pip install -U git+https://github.com/twopirllc/pandas-ta @@ -214,7 +214,7 @@ Thanks for using **Pandas TA**! _Thank you for your contributions!_ - +
@@ -723,7 +723,7 @@ df = df.ta.cdl_pattern(name=["doji", "inside"])
-### **Overlap** (32) +### **Overlap** (33) * _Arnaud Legoux Moving Average_: **alma** * _Double Exponential Moving Average_: **dema** @@ -738,6 +738,7 @@ df = df.ta.cdl_pattern(name=["doji", "inside"]) * _Ichimoku Kinkō Hyō_: **ichimoku** * Returns two DataFrames. For more information: ```help(ta.ichimoku)```. * ```lookahead=False``` drops the Chikou Span Column to prevent potential data leak. +* _Jurik Moving Average_: **jma** * _Kaufman's Adaptive Moving Average_: **kama** * _Linear Regression_: **linreg** * _McGinley Dynamic_: **mcgd** @@ -968,6 +969,7 @@ trading account, or fund. See ```help(ta.drawdown)``` * _Cross Signals_ (**xsignals**) was created by Kevin Johnson. It is a wrapper of Trade Signals that returns Trends, Trades, Entries and Exits. Cross Signals are commonly used for **bbands**, **rsi**, **zscore** crossing some value either above or below two values at different times. See ```help(ta.xsignals)``` * _Directional Movement_ (**dm**) developed by J. Welles Wilder in 1978 attempts to determine which direction the price of an asset is moving. See ```help(ta.dm)``` * _Even Better Sinewave_ (**ebsw**) measures market cycles and uses a low pass filter to remove noise. See: ```help(ta.ebsw)``` +* _Jurik Moving Average_ (**jma**) attempts to eliminate noise to see the "true" underlying activity.. See: ```help(ta.jma)``` * _Klinger Volume Oscillator_ (**kvo**) was developed by Stephen J. Klinger. It is designed to predict price reversals in a market by comparing volume to price.. See ```help(ta.kvo)``` * _Schaff Trend Cycle_ (**stc**) is an evolution of the popular MACD incorportating two cascaded stochastic calculations with additional smoothing. See ```help(ta.stc)``` * _Squeeze Pro_ (**squeeze_pro**) is an extended version of "TTM Squeeze" from John Carter. See ```help(ta.squeeze_pro)``` diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index d3a5d31..0e740b8 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -55,9 +55,9 @@ Category = { # Overlap "overlap": [ "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" + "jma", "kama", "linreg", "mcgd", "midpoint", "midprice", "ohlc4", + "pwma", "rma", "sinwma", "sma", "ssf", "supertrend", "swma", "t3", + "tema", "trima", "vidya", "vwap", "vwma", "wcp", "wma", "zlma" ], # Performance "performance": ["log_return", "percent_return"], diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 82eb52c..fdee64a 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1194,6 +1194,11 @@ class AnalysisIndicators(BasePandasObject): result = hwma(close=close, na=na, nb=nb, nc=nc, offset=offset, **kwargs) return self._post_process(result, **kwargs) + def jma(self, length=None, phase=None, offset=None, **kwargs): + close = self._get_column(kwargs.pop("close", "close")) + result = jma(close=close, length=length, phase=phase, offset=offset, **kwargs) + return self._post_process(result, **kwargs) + def kama(self, length=None, fast=None, slow=None, offset=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) result = kama(close=close, length=length, fast=fast, slow=slow, offset=offset, **kwargs) diff --git a/pandas_ta/overlap/__init__.py b/pandas_ta/overlap/__init__.py index 4c098ff..7a0f5ec 100644 --- a/pandas_ta/overlap/__init__.py +++ b/pandas_ta/overlap/__init__.py @@ -9,6 +9,7 @@ from .hlc3 import hlc3 from .hma import hma from .hwma import hwma from .ichimoku import ichimoku +from .jma import jma from .kama import kama from .linreg import linreg from .ma import ma diff --git a/pandas_ta/overlap/jma.py b/pandas_ta/overlap/jma.py index 3fd8be1..be116e3 100644 --- a/pandas_ta/overlap/jma.py +++ b/pandas_ta/overlap/jma.py @@ -1,67 +1,79 @@ # -*- coding: utf-8 -*- -from pandas_ta.utils import get_offset, verify_series -from pandas import Series -import numpy as np +from numpy import average as npAverage from numpy import nan as npNaN -import math +from numpy import log as npLog +from numpy import power as npPower +from numpy import sqrt as npSqrt +from numpy import zeros_like as npZeroslike +from pandas import Series +from pandas_ta.utils import get_offset, verify_series -def jma(close, length=None, phase=0, offset=None, **kwargs): - """ - Indicator: Jurik Moving Average (JMA) - Implementation of: https://c.mql5.com/forextsd/forum/164/jurik_1.pdf - Jurik Volty from: https://www.prorealcode.com/prorealtime-indicators/jurik-volatility-bands/ - """ + +def jma(close, length=None, phase=None, offset=None, **kwargs): + """Indicator: Jurik Moving Average (JMA)""" # Validate Arguments - length = int(length) if length and length > 0 else 7 - close = verify_series(close, length) + _length = int(length) if length and length > 0 else 7 + phase = float(phase) if phase and phase != 0 else 0 + close = verify_series(close, _length) offset = get_offset(offset) if close is None: return # Define base variables - jma = np.zeros_like(close) - Volty = np.zeros_like(close) - vSum = np.zeros_like(close) - Kv = det0 = det1 = ma2 = 0.0 + jma = npZeroslike(close) + volty = npZeroslike(close) + v_sum = npZeroslike(close) + + kv = det0 = det1 = ma2 = 0.0 jma[0] = ma1 = uBand = lBand = close[0] + # Static variables - SumLen = 10 - len = 0.5*(length-1) - PR = 0.5 if phase<-100 else 2.5 if phase>100 else phase*0.01+1.5 - len1 = max((math.log(math.sqrt(len))/math.log(2.0))+2.0, 0) - pow1 = max(len1-2.0, 0.5) - len2 = math.sqrt(len)*len1 - bet = len2/(len2+1) - beta = 0.45*(length-1)/(0.45*(length-1)+2.0) - for i in range(1, close.shape[0]): + sum_length = 10 + length = 0.5 * (_length - 1) + pr = 0.5 if phase < -100 else 2.5 if phase > 100 else 1.5 + phase * 0.01 + length1 = max((npLog(npSqrt(length)) / npLog(2.0)) + 2.0, 0) + pow1 = max(length1 - 2.0, 0.5) + length2 = length1 * npSqrt(length) + bet = length2 / (length2 + 1) + beta = 0.45 * (length - 1) / (0.45 * (length - 1) + 2.0) + + m = close.shape[0] + for i in range(1, m): price = close[i] + # Price volatility - del1 = price-uBand - del2 = price-lBand - Volty[i] = max(abs(del1),abs(del2)) if abs(del1)!=abs(del2) else 0 + del1 = price - uBand + del2 = price - lBand + volty[i] = max(abs(del1),abs(del2)) if abs(del1)!=abs(del2) else 0 + # Relative price volatility factor - vSum[i] = vSum[i-1] + (Volty[i]-Volty[max(i-SumLen,0)])/SumLen - avgVolty = np.average(vSum[max(i-65,0):i+1]) - dVolty = 0 if avgVolty==0 else Volty[i]/avgVolty - rVolty = max(1.0, min(math.pow(len1, 1/pow1), dVolty)) + v_sum[i] = v_sum[i - 1] + (volty[i] - volty[max(i - sum_length, 0)]) / sum_length + avg_volty = npAverage(v_sum[max(i - 65, 0):i + 1]) + d_volty = 0 if avg_volty ==0 else volty[i] / avg_volty + r_volty = max(1.0, min(npPower(length1, 1 / pow1), d_volty)) + # Jurik volatility bands - pow2 = math.pow(rVolty, pow1) - Kv = math.pow(bet, math.sqrt(pow2)) - uBand = price if (del1 > 0) else price - (Kv*del1) - lBand = price if (del2 < 0) else price - (Kv*del2) + pow2 = npPower(r_volty, pow1) + kv = npPower(bet, npSqrt(pow2)) + uBand = price if (del1 > 0) else price - (kv * del1) + lBand = price if (del2 < 0) else price - (kv * del2) + # Jurik Dynamic Factor - power = math.pow(rVolty, pow1) - alpha = math.pow(beta, power) + power = npPower(r_volty, pow1) + alpha = npPower(beta, power) + # 1st stage - prelimimary smoothing by adaptive EMA - ma1 = ((1-alpha)*price)+(alpha*ma1) # + ma1 = ((1 - alpha) * price) + (alpha * ma1) + # 2nd stage - one more prelimimary smoothing by Kalman filter - det0 = ((price-ma1)*(1-beta))+(beta*det0) - ma2 = ma1+PR*det0 + det0 = ((price - ma1) * (1 - beta)) + (beta * det0) + ma2 = ma1 + pr * det0 + # 3rd stage - final smoothing by unique Jurik adaptive filter - det1 = ((ma2-jma[i-1])*(1-alpha)*(1-alpha))+(alpha*alpha*det1) + det1 = ((ma2 - jma[i - 1]) * (1 - alpha) * (1 - alpha)) + (alpha * alpha * det1) jma[i] = jma[i-1] + det1 # Remove initial lookback data and convert to pandas frame - jma[0:length-1] = npNaN + jma[0:_length - 1] = npNaN jma = Series(jma, index=close.index) # Offset @@ -75,27 +87,31 @@ def jma(close, length=None, phase=0, offset=None, **kwargs): jma.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category - jma.name = f"JMA_{length}" + jma.name = f"JMA_{_length}_{phase}" jma.category = "overlap" return jma jma.__doc__ = \ -""" Jurik Moving Average Average (JMA) +"""Jurik Moving Average Average (JMA) + +Mark Jurik's Moving Average (JMA) attempts to eliminate noise to see the "true" +underlying activity. It has extremely low lag, is very smooth and is responsive +to market gaps. Sources: - Implementation of: https://c.mql5.com/forextsd/forum/164/jurik_1.pdf + https://c.mql5.com/forextsd/forum/164/jurik_1.pdf + https://www.prorealcode.com/prorealtime-indicators/jurik-volatility-bands/ Calculation: Default Inputs: - length=7 - phase=0 + length=7, phase=0 Args: close (pd.Series): Series of 'close's length (int): Period of calculation. Default: 7 - phase (float): how heavy/light the average is [-100, 100] Default: 0 + phase (float): How heavy/light the average is [-100, 100]. Default: 0 offset (int): How many lengths to offset the result. Default: 0 Kwargs: diff --git a/setup.py b/setup.py index a9a9e78..590d956 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( "pandas_ta.volatility", "pandas_ta.volume" ], - version=".".join(("0", "3", "10b")), + version=".".join(("0", "3", "11b")), description=long_description, long_description=long_description, author="Kevin Johnson", diff --git a/tests/test_ext_indicator_overlap_ext.py b/tests/test_ext_indicator_overlap_ext.py index f56ba41..e6bdebc 100644 --- a/tests/test_ext_indicator_overlap_ext.py +++ b/tests/test_ext_indicator_overlap_ext.py @@ -63,6 +63,11 @@ class TestOverlapExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], "HWMA_0.2_0.1_0.1") + def test_jma_ext(self): + self.data.ta.jma(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], "JMA_7_0") + def test_kama_ext(self): self.data.ta.kama(append=True) self.assertIsInstance(self.data, DataFrame) diff --git a/tests/test_indicator_overlap.py b/tests/test_indicator_overlap.py index 726449b..5848969 100644 --- a/tests/test_indicator_overlap.py +++ b/tests/test_indicator_overlap.py @@ -139,6 +139,11 @@ class TestOverlap(TestCase): self.assertIsInstance(result, Series) self.assertEqual(result.name, "KAMA_10_2_30") + def test_jma(self): + result = pandas_ta.jma(self.close) + self.assertIsInstance(result, Series) + self.assertEqual(result.name, "JMA_7_0") + def test_ichimoku(self): ichimoku, span = pandas_ta.ichimoku(self.high, self.low, self.close) self.assertIsInstance(ichimoku, DataFrame)