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) |
|:--------:|
|  |
+
### **Overlap** (31)
@@ -752,8 +748,8 @@ df.ta.cdl(["doji", "inside"], append=True)
| _Simple Moving Averages_ (SMA) and _Bollinger Bands_ (BBANDS) |
|:--------:|
|  |
-
+
### **Performance** (4)
@@ -811,6 +807,8 @@ Use parameter: cumulative=**True** for cumulative results.
|:--------:|
|  |
+
+
### **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.
|:--------:|
|  |
+
+
### **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