diff --git a/README.md b/README.md index 3e2c26b..399895b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever * [Indicators by Category](#indicators-by-category) * [Candles](#candles-63) * [Cycles](#cycles-1) - * [Momentum](#momentum-37) + * [Momentum](#momentum-38) * [Overlap](#overlap-31) * [Performance](#performance-4) * [Statistics](#statistics-9) @@ -97,7 +97,7 @@ $ pip install pandas_ta Latest Version -------------- -Best choice! Version: *0.2.68b* +Best choice! Version: *0.2.69b* ```sh $ pip install -U git+https://github.com/twopirllc/pandas-ta ``` @@ -567,10 +567,6 @@ help(ta.yf) # **Indicators** (_by Category_) ### **Candles** (63) - -_Candle Patterns_: ```ta.cdl_pattern``` or ```ta.cdl``` - - Patterns that are **not bold**, require TA-Lib to be installed: ```pip install TA-Lib``` * 2crows @@ -657,12 +653,11 @@ df.ta.cdl(["doji", "inside"], append=True) ### **Cycles** (1) - * _Even Better Sinewave_: **ebsw** +
-### **Momentum** (37) - +### **Momentum** (38) * _Awesome Oscillator_: **ao** * _Absolute Price Oscillator_: **apo** * _Bias_: **bias** @@ -708,6 +703,7 @@ df.ta.cdl(["doji", "inside"], append=True) | _Moving Average Convergence Divergence_ (MACD) | |:--------:| | ![Example MACD](/images/SPY_MACD.png) | +
### **Overlap** (31) @@ -752,8 +748,8 @@ df.ta.cdl(["doji", "inside"], append=True) | _Simple Moving Averages_ (SMA) and _Bollinger Bands_ (BBANDS) | |:--------:| | ![Example Chart](/images/TA_Chart.png) | -
+
### **Performance** (4) @@ -811,6 +807,8 @@ Use parameter: cumulative=**True** for cumulative results. |:--------:| | ![Example ADX](/images/SPY_ADX.png) | +
+ ### **Utility** (5) * _Above_: **above** @@ -819,6 +817,8 @@ Use parameter: cumulative=**True** for cumulative results. * _Below Value_: **below_value** * _Cross_: **cross** +
+ ### **Volatility** (13) * _Aberration_: **aberration** @@ -839,6 +839,8 @@ Use parameter: cumulative=**True** for cumulative results. |:--------:| | ![Example ATR](/images/SPY_ATR.png) | +
+ ### **Volume** (14) * _Accumulation/Distribution Index_: **ad** @@ -899,12 +901,17 @@ result = ta.cagr(df.close) ## **Breaking Indicators** * _Trend Return_ (**trend_return**) when given a trend Series like ```close > sma(close, 50)``` it now returns by default log and cumulative log returns of the trend as well as the Trends, Trades, Trade Entries and Trade Exits of that trend. Now compatible with [**vectorbt**](https://github.com/polakowo/vectorbt) by setting ```asbool=True``` to get boolean Trade Entries and Exits. See: ```help(ta.trend_return)``` +
+ ## **New Indicators** * _Arnaud Legoux Moving Average_ (**alma**) uses the curve of the Normal (Gauss) distribution to allow regulating the smoothness and high sensitivity of the indicator. See: ```help(ta.alma)``` trading account, or fund. See: ```help(ta.drawdown)``` * _Candle Patterns_ (**cdl_pattern**) If TA Lib is installed, then all those Candle Patterns are available. See the list and examples above on how to call the patterns. See: ```help(ta.cdl_pattern)``` * _Even Better Sinewave_ (**ebsw**) measures market cycles and uses a low pass filter to remove noise. See: ```help(ta.ebsw)``` +* _Schaff Trend Cycle_ (**stc**) is an evolution of the popular MACD incorportating two +cascaded stochastic calculations with additional smoothing. See: ```help(ta.stc)``` * _Tom DeMark's Sequential_ (**td_seq**) attempts to identify a price point where an uptrend or a downtrend exhausts itself and reverses. Currently exlcuded from ```df.ta.strategy()``` for performance reasons. See: ```help(ta.td_seq)``` +
## **Updated Indicators** diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 3b51b01..20b896f 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -1039,9 +1039,9 @@ class AnalysisIndicators(BasePandasObject): result = squeeze(high=high, low=low, close=close, bb_length=bb_length, bb_std=bb_std, kc_length=kc_length, kc_scalar=kc_scalar, mom_length=mom_length, mom_smooth=mom_smooth, use_tr=use_tr, offset=offset, **kwargs) return self._post_process(result, **kwargs) - def stc(self, ma1=None, ma2=None, osc=None, tclen=None, fast=None, slow=None, factor=None, offset=None, **kwargs): + def stc(self, ma1=None, ma2=None, osc=None, tclength=None, fast=None, slow=None, factor=None, offset=None, **kwargs): close = self._get_column(kwargs.pop("close", "close")) - result = stc(close=close, ma1=ma1, ma2=ma2, osc=osc, tclen=tclen, fast=fast, slow=slow, factor=factor, offset=offset, **kwargs) + result = stc(close=close, ma1=ma1, ma2=ma2, osc=osc, tclength=tclength, fast=fast, slow=slow, factor=factor, offset=offset, **kwargs) return self._post_process(result, **kwargs) def stoch(self, fast_k=None, slow_k=None, slow_d=None, offset=None, **kwargs): diff --git a/pandas_ta/momentum/stc.py b/pandas_ta/momentum/stc.py index dd7af63..34201e2 100644 --- a/pandas_ta/momentum/stc.py +++ b/pandas_ta/momentum/stc.py @@ -1,96 +1,64 @@ # -*- coding: utf-8 -*- -from pandas import DataFrame, Series, concat +from pandas import DataFrame, Series from pandas_ta.overlap import ema -from pandas_ta.utils import get_offset, verify_series, signals +from pandas_ta.utils import get_offset, non_zero_range, verify_series -def schaff_tc(close, XMAC, tclen, factor): - # ACTUAL Calculation part, which is shared between operation modes - # 1St : Stochastic of MACD - Value1 = XMAC.rolling(tclen).min() # min value in interval tclen - Value2 = XMAC.rolling(tclen).max() - Value1 # max value in interval tclen - - # ... : %Fast K of MACD - Frac1 = list(XMAC) - Frac1[0] = 0 - PF = list(XMAC) - PF[0] = 0 - for i in range(1, len(XMAC)): - if Value1[i] > 0: - Frac1[i] = ((XMAC[i] - Value1[i]) / Value2[i]) * 100 - else: - Frac1[i] = Frac1[i - 1] - # Smoothed Calculation for % Fast D of MACD - PF[i] = round(PF[i - 1] + (factor * (Frac1[i] - PF[i - 1])), 8) - - PF = Series(PF, index=close.index) - - # 2nd : Stochastic of smoothed Percent Fast D, 'PF', above - Value3 = PF.rolling(tclen).min() # min value in interval tclen - Value4 = PF.rolling(tclen).max() - Value3 # max value in interval tclen - # ... : % of Fast K of PF - Frac2 = list(XMAC) - Frac2[0] = 0 - PFF = list(XMAC) - PFF[0] = 0 - for i in range(1, len(XMAC)): - if Value4[i] > 0: - Frac2[i] = ((PF[i] - Value3[i]) / Value4[i]) * 100 - else: - Frac2[i] = Frac2[i - 1] - # Smoothed Calculation for % Fast D of MACD - PFF[i] = round(PFF[i - 1] + (factor * (Frac2[i] - PFF[i - 1])), 8) - - return [PFF, PF] - - -def stc(close, tclen=None, fast=None, slow=None, factor=None, offset=None, **kwargs): +def stc(close, tclength=None, fast=None, slow=None, factor=None, offset=None, **kwargs): """Indicator: Schaff Trend Cycle (STC)""" # Validate arguments - close = verify_series(close) # close - tclen = int(tclen) if tclen and tclen > 0 else 10 + tclength = int(tclength) if tclength and tclength > 0 else 10 fast = int(fast) if fast and fast > 0 else 12 slow = int(slow) if slow and slow > 0 else 26 factor = float(factor) if factor and factor > 0 else 0.5 - if slow < fast: # mandatory condition, but might be confusing + if slow < fast: # mandatory condition, but might be confusing fast, slow = slow, fast + _length = max(tclength, fast, slow) + close = verify_series(close, _length) offset = get_offset(offset) - # kwargs allows for three more series (ma1, ma2 and osc) which can be passed here - # ma1 and ma2 input negate internal ema calculations, osc substitutes both ma's. + if close is None: return + + # kwargs allows for three more series (ma1, ma2 and osc) which can be passed + # here ma1 and ma2 input negate internal ema calculations, osc substitutes + # both ma's. ma1 = kwargs.pop("ma1", False) ma2 = kwargs.pop("ma2", False) osc = kwargs.pop("osc", False) # 3 different modes of calculation.. if isinstance(ma1, Series) and isinstance(ma2, Series) and not osc: - ma1 = verify_series(ma1) - ma2 = verify_series(ma2) + ma1 = verify_series(ma1, _length) + ma2 = verify_series(ma2, _length) + + if ma1 is None or ma2 is None: return # Calculate Result based on external feeded series - XMAC = ma1 - ma2 + xmacd = ma1 - ma2 # invoke shared calculation - collect = schaff_tc(close, XMAC, tclen, factor) + pff, pf = schaff_tc(close, xmacd, tclength, factor) elif isinstance(osc, Series): - osc = verify_series(osc) - # Calculate Result based on feeded oscillator (should be ranging around 0 x-axis) - XMAC = osc + osc = verify_series(osc, _length) + if osc is None: return + # Calculate Result based on feeded oscillator + # (should be ranging around 0 x-axis) + xmacd = osc # invoke shared calculation - collect = schaff_tc(close, XMAC, tclen, factor) + pff, pf = schaff_tc(close, xmacd, tclength, factor) else: # Calculate Result .. (traditionel/full) # MACD line fastma = ema(close, length=fast) slowma = ema(close, length=slow) - XMAC = fastma - slowma + xmacd = fastma - slowma # invoke shared calculation - collect = schaff_tc(close, XMAC, tclen, factor) + pff, pf = schaff_tc(close, xmacd, tclength, factor) # Resulting Series - stc = Series(collect[0], index=close.index) - macd = Series(XMAC, index=close.index) - stoch = Series(collect[1], index=close.index) + stc = Series(pff, index=close.index) + macd = Series(xmacd, index=close.index) + stoch = Series(pf, index=close.index) # Offset if offset != 0: @@ -109,7 +77,7 @@ def stc(close, tclen=None, fast=None, slow=None, factor=None, offset=None, **kwa stoch.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it - _props = f"_{tclen}_{fast}_{slow}_{factor}" + _props = f"_{tclength}_{fast}_{slow}_{factor}" stc.name = f"STC{_props}" macd.name = f"STCmacd{_props}" stoch.name = f"STCstoch{_props}" @@ -127,15 +95,17 @@ def stc(close, tclen=None, fast=None, slow=None, factor=None, offset=None, **kwa stc.__doc__ = \ """Schaff Trend Cycle (STC) -The Schaff Trend Cycle is an evolution of the popular MACD incorportating two cascaded -stochastic calculations with additional smoothing. -The STC returns also the beginning MACD result as well as the result after the first stochastic -including its smoothing. This implementation has been extended for Pandas TA to -also allow for separatly feeding any other two moving Averages (as ma1 and ma2) or to skip this -to feed an oscillator (osc), based on which the Schaff Trend Cycle should be calculated. +The Schaff Trend Cycle is an evolution of the popular MACD incorportating two +cascaded stochastic calculations with additional smoothing. + +The STC returns also the beginning MACD result as well as the result after the +first stochastic including its smoothing. This implementation has been extended +for Pandas TA to also allow for separatly feeding any other two moving Averages +(as ma1 and ma2) or to skip this to feed an oscillator (osc), based on which the +Schaff Trend Cycle should be calculated. Feed external moving averages: -Internally calculation.. +Internally calculation.. stc = ta.stc(close=df["close"], tclen=stc_tclen, fast=ma1_interval, slow=ma2_interval, factor=stc_factor) becomes.. extMa1 = df.ta.zlma(close=df["close"], length=ma1_interval, append=True) @@ -146,14 +116,14 @@ The same goes for osc=, which allows the input of an externally calculated oscil Sources: - Implemented by rengel8 based on work found here: + Implemented by rengel8 based on work found here: https://www.prorealcode.com/prorealtime-indicators/schaff-trend-cycle2/ - -Calculation: - STCmacd = Moving Average Convergance/Divergance or Oscillator + +Calculation: + STCmacd = Moving Average Convergance/Divergance or Oscillator STCstoch = Intermediate Stochastic of MACD/Osc. - 2nd Stochastic including filtering with results in the - STC = Schaff Trend Cycle + 2nd Stochastic including filtering with results in the + STC = Schaff Trend Cycle Args: close (pd.Series): Series of 'close's, used for indexing Series, mandatory @@ -173,3 +143,41 @@ Kwargs: Returns: pd.DataFrame: stc, macd, stoch """ + + +def schaff_tc(close, xmacd, tclength, factor): + # ACTUAL Calculation part, which is shared between operation modes + # 1St : Stochastic of MACD + lowest_xmacd = xmacd.rolling(tclength).min() # min value in interval tclen + xmacd_range = non_zero_range(xmacd.rolling(tclength).max(), lowest_xmacd) + m = len(xmacd) + + # %Fast K of MACD + stoch1, pf = list(xmacd), list(xmacd) + stoch1[0], pf[0] = 0, 0 + for i in range(1, m): + if lowest_xmacd[i] > 0: + stoch1[i] = 100 * ((xmacd[i] - lowest_xmacd[i]) / xmacd_range[i]) + else: + stoch1[i] = stoch1[i - 1] + # Smoothed Calculation for % Fast D of MACD + pf[i] = round(pf[i - 1] + (factor * (stoch1[i] - pf[i - 1])), 8) + + pf = Series(pf, index=close.index) + + # 2nd : Stochastic of smoothed Percent Fast D, 'PF', above + lowest_pf = pf.rolling(tclength).min() + pf_range = non_zero_range(pf.rolling(tclength).max(), lowest_pf) + + # % of Fast K of PF + stoch2, pff = list(xmacd), list(xmacd) + stoch2[0], pff[0] = 0, 0 + for i in range(1, m): + if pf_range[i] > 0: + stoch2[i] = 100 * ((pf[i] - lowest_pf[i]) / pf_range[i]) + else: + stoch2[i] = stoch2[i - 1] + # Smoothed Calculation for % Fast D of PF + pff[i] = round(pff[i - 1] + (factor * (stoch2[i] - pff[i - 1])), 8) + + return [pff, pf] \ No newline at end of file diff --git a/pandas_ta/utils/_metrics.py b/pandas_ta/utils/_metrics.py index 739fb67..c101248 100644 --- a/pandas_ta/utils/_metrics.py +++ b/pandas_ta/utils/_metrics.py @@ -32,7 +32,8 @@ def calmar_ratio(close: Series, method: str = "percent", years: int = 3) -> floa Args: close (pd.Series): Series of 'close's - method (str): Max DD calculation options: 'dollar', 'percent', 'log'. Default: 'dollar' + method (str): Max DD calculation options: 'dollar', 'percent', 'log'. + Default: 'dollar' years (int): The positive number of years to use. Default: 3 >>> result = ta.calmar_ratio(close, method="percent", years=3) @@ -56,7 +57,8 @@ def downside_deviation(returns: Series, benchmark_rate: float = 0.0, tf: str = " Args: close (pd.Series): Series of 'close's benchmark_rate (float): Benchmark Rate to use. Default: 0.0 - tf (str): Time Frame options: 'days', 'weeks', 'months', and 'years'. Default: 'years' + tf (str): Time Frame options: 'days', 'weeks', 'months', and 'years'. + Default: 'years' >>> result = ta.downside_deviation(returns, benchmark_rate=0.0, tf="years") """ @@ -106,8 +108,10 @@ def max_drawdown(close: Series, method:str = None, all:bool = False) -> float: Args: close (pd.Series): Series of 'close's - method (str): Max DD calculation options: 'dollar', 'percent', 'log'. Default: 'dollar' - all (bool): If True, it returns all three methods as a dict. Default: False + method (str): Max DD calculation options: 'dollar', 'percent', 'log'. + Default: 'dollar' + all (bool): If True, it returns all three methods as a dict. + Default: False >>> result = ta.max_drawdown(close, method="dollar", all=False) """ @@ -136,8 +140,11 @@ def optimal_leverage( Args: close (pd.Series): Series of 'close's benchmark_rate (float): Benchmark Rate to use. Default: 0.0 - period (int, float): Period to use to calculate Mean Annual Return and Annual Standard Deviation. Default: None or the default sharpe_ratio.period() - log (bool): If True, calculates log_return. Otherwise it returns percent_return. Default: False + period (int, float): Period to use to calculate Mean Annual Return and + Annual Standard Deviation. + Default: None or the default sharpe_ratio.period() + log (bool): If True, calculates log_return. Otherwise it returns + percent_return. Default: False >>> result = ta.optimal_leverage(close, benchmark_rate=0.0, log=False) """ @@ -181,9 +188,12 @@ def sharpe_ratio(close: Series, benchmark_rate: float = 0.0, log: bool = False, Args: close (pd.Series): Series of 'close's benchmark_rate (float): Benchmark Rate to use. Default: 0.0 - log (bool): If True, calculates log_return. Otherwise it returns percent_return. Default: False + log (bool): If True, calculates log_return. Otherwise it returns + percent_return. Default: False use_cagr (bool): Use cagr - benchmark_rate instead. Default: False - period (int, float): Period to use to calculate Mean Annual Return and Annual Standard Deviation. Default: RATE["TRADING_DAYS_PER_YEAR"] (currently 252) + period (int, float): Period to use to calculate Mean Annual Return and + Annual Standard Deviation. + Default: RATE["TRADING_DAYS_PER_YEAR"] (currently 252) >>> result = ta.sharpe_ratio(close, benchmark_rate=0.0, log=False) """ @@ -204,7 +214,8 @@ def sortino_ratio(close: Series, benchmark_rate: float = 0.0, log: bool = False) Args: close (pd.Series): Series of 'close's benchmark_rate (float): Benchmark Rate to use. Default: 0.0 - log (bool): If True, calculates log_return. Otherwise it returns percent_return. Default: False + log (bool): If True, calculates log_return. Otherwise it returns + percent_return. Default: False >>> result = ta.sortino_ratio(close, benchmark_rate=0.0, log=False) """ @@ -221,9 +232,13 @@ def volatility(close: Series, tf: str = "years", returns: bool = False, log: boo Args: close (pd.Series): Series of 'close's - tf (str): Time Frame options: 'days', 'weeks', 'months', and 'years'. Default: 'years' - returns (bool): If True, then it replace the close Series with the user defined Series; typically user generated returns or percent returns or log returns. Default: False - log (bool): If True, calculates log_return. Otherwise it calculates percent_return. Default: False + tf (str): Time Frame options: 'days', 'weeks', 'months', and 'years'. + Default: 'years' + returns (bool): If True, then it replace the close Series with the user + defined Series; typically user generated returns or percent returns + or log returns. Default: False + log (bool): If True, calculates log_return. Otherwise it calculates + percent_return. Default: False >>> result = ta.volatility(close, tf="years", returns=False, log=False, **kwargs) """ diff --git a/setup.py b/setup.py index 3f1ec22..09c68f5 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( "pandas_ta.volatility", "pandas_ta.volume" ], - version=".".join(("0", "2", "68b")), + version=".".join(("0", "2", "69b")), description=long_description, long_description=long_description, author="Kevin Johnson", diff --git a/tests/test_ext_indicator_momentum.py b/tests/test_ext_indicator_momentum.py index d5b5c95..a251853 100644 --- a/tests/test_ext_indicator_momentum.py +++ b/tests/test_ext_indicator_momentum.py @@ -197,6 +197,11 @@ class TestMomentumExtension(TestCase): ["SQZ_ON", "SQZ_OFF", "SQZ_NO", "SQZhlr_20_2.0_20_1.5"] ) + def test_stc_ext(self): + self.data.ta.stc(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(list(self.data.columns[-3:]), ["STC_10_12_26_0.5", "STCmacd_10_12_26_0.5", "STCstoch_10_12_26_0.5"]) + def test_stoch_ext(self): self.data.ta.stoch(append=True) self.assertIsInstance(self.data, DataFrame) diff --git a/tests/test_indicator_momentum.py b/tests/test_indicator_momentum.py index 7589ac3..7a9505a 100644 --- a/tests/test_indicator_momentum.py +++ b/tests/test_indicator_momentum.py @@ -349,6 +349,11 @@ class TestMomentum(TestCase): self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, "SQZhlr_20_2.0_20_1.5_LB") + def test_stc(self): + result = pandas_ta.stc(self.close) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "STC_10_12_26_0.5") + # @skip def test_stoch(self): # TV Correlation