def ssf(close, length=None, poles=None, offset=None, **kwargs): """Indicator: Ehler's Super Smoother Filter (SSF)""" # Validate Arguments length = int(length) if length and length > 0 else 10 poles = int(poles) if poles in [2, 3] else 2 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result m = close.size ssf = close.copy() if poles == 3: x = npPi / length # x = PI / n a0 = npExp(-x) # e^(-x) b0 = 2 * a0 * npCos(npSqrt(3) * x) # 2e^(-x)*cos(3^(.5) * x) c0 = a0 * a0 # e^(-2x) c4 = c0 * c0 # e^(-4x) c3 = -c0 * (1 + b0) # -e^(-2x) * (1 + 2e^(-x)*cos(3^(.5) * x)) c2 = c0 + b0 # e^(-2x) + 2e^(-x)*cos(3^(.5) * x) c1 = 1 - c2 - c3 - c4 for i in range(0, m): ssf.iloc[i] = c1 * close.iloc[i] + c2 * ssf.iloc[ i - 1] + c3 * ssf.iloc[i - 2] + c4 * ssf.iloc[i - 3] else: # poles == 2 x = npPi * npSqrt(2) / length # x = PI * 2^(.5) / n a0 = npExp(-x) # e^(-x) a1 = -a0 * a0 # -e^(-2x) b1 = 2 * a0 * npCos(x) # 2e^(-x)*cos(x) c1 = 1 - a1 - b1 # e^(-2x) - 2e^(-x)*cos(x) + 1 for i in range(0, m): ssf.iloc[i] = c1 * close.iloc[i] + b1 * ssf.iloc[ i - 1] + a1 * ssf.iloc[i - 2] # Offset if offset != 0: ssf = ssf.shift(offset) # Handle fills if "fillna" in kwargs: ssf.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ssf.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category ssf.name = f"SSF_{length}_{poles}" ssf.category = "overlap" return ssf
def log_geometric_mean(series: Series) -> float: """Returns the Logarithmic Geometric Mean""" n = series.size if n < 2: return 0 else: series = series.fillna(0) + 1 if npAll(series > 0): return npExp(npLog(series).sum() / n) - 1 return 0
def alma(close, length=None, sigma=None, distribution_offset=None, offset=None, **kwargs): """Indicator: Arnaud Legoux Moving Average (ALMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 sigma = float(sigma) if sigma and sigma > 0 else 6.0 distribution_offset = float( distribution_offset ) if distribution_offset and distribution_offset > 0 else 0.85 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Pre-Calculations m = distribution_offset * (length - 1) s = length / sigma wtd = list(range(length)) for i in range(0, length): wtd[i] = npExp(-1 * ((i - m) * (i - m)) / (2 * s * s)) # Calculate Result result = [npNaN for _ in range(0, length - 1)] + [0] for i in range(length, close.size): window_sum = 0 cum_sum = 0 for j in range(0, length): # wtd = math.exp(-1 * ((j - m) * (j - m)) / (2 * s * s)) # moved to pre-calc for efficiency window_sum = window_sum + wtd[j] * close.iloc[i - j] cum_sum = cum_sum + wtd[j] almean = window_sum / cum_sum result.append(npNaN) if i == length else result.append(almean) alma = Series(result, index=close.index) # Offset if offset != 0: alma = alma.shift(offset) # Handle fills if "fillna" in kwargs: alma.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: alma.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category alma.name = f"ALMA_{length}_{sigma}_{distribution_offset}" alma.category = "overlap" return alma
def erf(x): """Error Function erf(x) The algorithm comes from Handbook of Mathematical Functions, formula 7.1.26. Source: https://stackoverflow.com/questions/457408/is-there-an-easily-available-implementation-of-erf-for-python """ # save the sign of x sign = 1 if x >= 0 else -1 x = abs(x) # constants a1 = 0.254829592 a2 = -0.284496736 a3 = 1.421413741 a4 = -1.453152027 a5 = 1.061405429 p = 0.3275911 # A&S formula 7.1.26 t = 1.0 / (1.0 + p * x) y = 1.0 - (((( (a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * npExp(-x * x) return sign * y # erf(-x) = -erf(x)
def decay(close, kind=None, length=None, mode=None, offset=None, **kwargs): """Indicator: Decay""" # Validate Arguments length = int(length) if length and length > 0 else 5 mode = mode.lower() if isinstance(mode, str) else "linear" close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result _mode = "L" if mode == "exp" or kind == "exponential": _mode = "EXP" diff = close.shift(1) - npExp(-length) else: # "linear" diff = close.shift(1) - (1 / length) diff[0] = close[0] tdf = DataFrame({"close": close, "diff": diff, "0": 0}) ld = tdf.max(axis=1) # Offset if offset != 0: ld = ld.shift(offset) # Handle fills if "fillna" in kwargs: ld.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ld.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it ld.name = f"{_mode}DECAY_{length}" ld.category = "trend" return ld
def ebsw(close, length=None, bars=None, offset=None, **kwargs): """Indicator: Even Better SineWave (EBSW)""" # Validate arguments length = int(length) if length and length > 38 else 40 bars = int(bars) if bars and bars > 0 else 10 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # variables alpha1 = HP = 0 # alpha and HighPass a1 = b1 = c1 = c2 = c3 = 0 Filt = Pwr = Wave = 0 lastClose = lastHP = 0 FilterHist = [0, 0] # Filter history # Calculate Result m = close.size result = [npNaN for _ in range(0, length - 1)] + [0] for i in range(length, m): # HighPass filter cyclic components whose periods are shorter than Duration input alpha1 = (1 - npSin(360 / length)) / npCos(360 / length) HP = 0.5 * (1 + alpha1) * (close[i] - lastClose) + alpha1 * lastHP # Smooth with a Super Smoother Filter from equation 3-3 a1 = npExp(-npSqrt(2) * npPi / bars) b1 = 2 * a1 * npCos(npSqrt(2) * 180 / bars) c2 = b1 c3 = -1 * a1 * a1 c1 = 1 - c2 - c3 Filt = c1 * (HP + lastHP) / 2 + c2 * FilterHist[1] + c3 * FilterHist[0] # Filt = float("{:.8f}".format(float(Filt))) # to fix for small scientific notations, the big ones fail # 3 Bar average of Wave amplitude and power Wave = (Filt + FilterHist[1] + FilterHist[0]) / 3 Pwr = (Filt * Filt + FilterHist[1] * FilterHist[1] + FilterHist[0] * FilterHist[0]) / 3 # Normalize the Average Wave to Square Root of the Average Power Wave = Wave / npSqrt(Pwr) # update storage, result FilterHist.append(Filt) # append new Filt value FilterHist.pop( 0) # remove first element of list (left) -> updating/trim lastHP = HP lastClose = close[i] result.append(Wave) ebsw = Series(result, index=close.index) # Offset if offset != 0: ebsw = ebsw.shift(offset) # Handle fills if "fillna" in kwargs: ebsw.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ebsw.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it ebsw.name = f"EBSW_{length}_{bars}" ebsw.category = "cycles" return ebsw
def __call__(self, x): return 1.0 / (1.0 + npExp(-x))