[ENH] Added Schaff Trend Cycle (STC)

This request adds an extended version of STC, which has internally two EMAs, but can be feeded with any other two types of MAs and alternativly also with any oscillator type.
This commit is contained in:
rengel8
2021-04-08 20:23:51 +02:00
parent 84cefa9b22
commit 9d8a55f242
5 changed files with 183 additions and 1 deletions
+1
View File
@@ -596,6 +596,7 @@ help(ta.yf)
* _Relative Strength Index_: **rsi**
* _Relative Strength Xtra_: **rsx**
* _Relative Vigor Index_: **rvgi**
* _Schaff Trend Cycle_: **stc**
* _Slope_: **slope**
* _SMI Ergodic_ **smi**
* _Squeeze_: **squeeze**
+1 -1
View File
@@ -47,7 +47,7 @@ Category = {
"ao", "apo", "bias", "bop", "brar", "cci", "cfo", "cg", "cmo",
"coppock", "er", "eri", "fisher", "inertia", "kdj", "kst", "macd",
"mom", "pgo", "ppo", "psl", "pvo", "qqe", "roc", "rsi", "rsx", "rvgi",
"slope", "smi", "squeeze", "stoch", "stochrsi", "td_seq", "trix", "tsi", "uo",
"slope", "smi", "squeeze", "stc", "stoch", "stochrsi", "td_seq", "trix", "tsi", "uo",
"willr"
],
# Overlap
+5
View File
@@ -1043,6 +1043,11 @@ 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):
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)
return self._post_process(result, **kwargs)
def stoch(self, fast_k=None, slow_k=None, slow_d=None, offset=None, **kwargs):
high = self._get_column(kwargs.pop("high", "high"))
low = self._get_column(kwargs.pop("low", "low"))
+1
View File
@@ -29,6 +29,7 @@ from .rvgi import rvgi
from .slope import slope
from .smi import smi
from .squeeze import squeeze
from .stc import stc
from .stoch import stoch
from .stochrsi import stochrsi
from .td_seq import td_seq
+175
View File
@@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series, concat
from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, verify_series, signals
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):
"""Indicator: Schaff Trend Cycle (STC)"""
# Validate arguments
close = verify_series(close) # close
tclen = int(tclen) if tclen and tclen > 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
fast, slow = slow, fast
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.
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)
# Calculate Result based on external feeded series
XMAC = ma1 - ma2
# invoke shared calculation
collect = schaff_tc(close, XMAC, tclen, factor)
elif isinstance(osc, Series):
osc = verify_series(osc)
# Calculate Result based on feeded oscillator (should be ranging around 0 x-axis)
XMAC = osc
# invoke shared calculation
collect = schaff_tc(close, XMAC, tclen, factor)
else:
# Calculate Result .. (traditionel/full)
# MACD line
fastma = ema(close, length=fast)
slowma = ema(close, length=slow)
XMAC = fastma - slowma
# invoke shared calculation
collect = schaff_tc(close, XMAC, tclen, factor)
# Resulting Series
stc = Series(collect[0], index=close.index)
macd = Series(XMAC, index=close.index)
stoch = Series(collect[1], index=close.index)
# Offset
if offset != 0:
stc = stc.shift(offset)
macd = macd.shift(offset)
stoch = stoch.shift(offset)
# Handle fills
if "fillna" in kwargs:
stc.fillna(kwargs["fillna"], inplace=True)
macd.fillna(kwargs["fillna"], inplace=True)
stoch.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
stc.fillna(method=kwargs["fill_method"], inplace=True)
macd.fillna(method=kwargs["fill_method"], inplace=True)
stoch.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
_props = f"_{tclen}_{fast}_{slow}_{factor}"
stc.name = f"STC{_props}"
macd.name = f"STCmacd{_props}"
stoch.name = f"STCstoch{_props}"
stc.category = macd.category = stoch.category ="momentum"
# Prepare DataFrame to return
data = {stc.name: stc, macd.name: macd, stoch.name: stoch}
df = DataFrame(data)
df.name = f"STC{_props}"
df.category = stc.category
return df
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.
Feed external moving averages:
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)
extMa2 = df.ta.ema(close=df["close"], length=ma2_interval, append=True)
stc = ta.stc(close=df["close"], tclen=stc_tclen, ma1=extMa1, ma2=extMa2, factor=stc_factor)
The same goes for osc=, which allows the input of an externally calculated oscillator, overriding ma1 & ma2.
Sources:
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
STCstoch = Intermediate Stochastic of MACD/Osc.
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
tclen (int): SchaffTC Signal-Line length. Default: 10 (adjust to the half of cycle)
fast (int): The short period. Default: 12
slow (int): The long period. Default: 26
factor (float): smoothing factor for last stoch. calculation. Default: 0.5
offset (int): How many periods to offset the result. Default: 0
Kwargs:
ma1: 1st moving average provided externally (mandatory in conjuction with ma2)
ma2: 2nd moving average provided externally (mandatory in conjuction with ma1)
osc: an externally feeded osillator
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
Returns:
pd.DataFrame: stc, macd, stoch
"""