def efi(close, volume, length=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: Elder's Force Index (EFI)""" # Validate arguments length = int(length) if length and length > 0 else 13 mamode = mamode if isinstance(mamode, str) else "ema" close = verify_series(close, length) volume = verify_series(volume, length) drift = get_drift(drift) offset = get_offset(offset) if close is None or volume is None: return # Calculate Result pv_diff = close.diff(drift) * volume efi = ma(mamode, pv_diff, length=length) # Offset if offset != 0: efi = efi.shift(offset) # Handle fills if "fillna" in kwargs: efi.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: efi.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it efi.name = f"EFI_{length}" efi.category = "volume" return efi
def massi(high, low, fast=None, slow=None, offset=None, **kwargs): """Indicator: Mass Index (MASSI)""" # Validate arguments high = verify_series(high) low = verify_series(low) high_low_range = non_zero_range(high, low) fast = int(fast) if fast and fast > 0 else 9 slow = int(slow) if slow and slow > 0 else 25 if slow < fast: fast, slow = slow, fast min_periods = int( kwargs["min_periods"]) if "min_periods" in kwargs and kwargs[ "min_periods"] is not None else fast offset = get_offset(offset) # Calculate Result hl_ema1 = ema(close=high_low_range, length=fast, **kwargs) hl_ema2 = ema(close=hl_ema1, length=fast, **kwargs) hl_ratio = hl_ema1 / hl_ema2 massi = hl_ratio.rolling(slow, min_periods=slow).sum() # Offset if offset != 0: massi = massi.shift(offset) # Handle fills if "fillna" in kwargs: massi.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: massi.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it massi.name = f"MASSI_{fast}_{slow}" massi.category = "volatility" return massi
def bias(close, length=None, mamode=None, offset=None, **kwargs): """Indicator: Bias (BIAS)""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 0 else 26 mamode = mamode.lower() if mamode else None offset = get_offset(offset) # Calculate Result if mamode is None or mamode == "sma": ma = sma(close, length=length, **kwargs) if mamode == "ema": ma = ema(close, length=length, **kwargs) if mamode == "hma": ma = hma(close, length=length, **kwargs) if mamode == "rma": ma = rma(close, length=length, **kwargs) if mamode == "wma": ma = wma(close, length=length, **kwargs) bias = (close / ma) - 1 # Offset if offset != 0: bias = bias.shift(offset) # Handle fills if "fillna" in kwargs: bias.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: bias.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it bias.name = f"BIAS_{ma.name}" bias.category = "momentum" return bias
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 willr(high, low, close, length=None, offset=None, **kwargs): """Indicator: William's Percent R (WILLR)""" # Validate arguments length = int(length) if length and length > 0 else 14 min_periods = int( kwargs["min_periods"]) if "min_periods" in kwargs and kwargs[ "min_periods"] is not None else length _length = max(length, min_periods) high = verify_series(high, _length) low = verify_series(low, _length) close = verify_series(close, _length) offset = get_offset(offset) if high is None or low is None or close is None: return # Calculate Result lowest_low = low.rolling(length, min_periods=min_periods).min() highest_high = high.rolling(length, min_periods=min_periods).max() willr = 100 * ((close - lowest_low) / (highest_high - lowest_low) - 1) # Offset if offset != 0: willr = willr.shift(offset) # Handle fills if "fillna" in kwargs: willr.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: willr.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it willr.name = f"WILLR_{length}" willr.category = "momentum" return willr
def ui(close, length=None, scalar=None, offset=None, **kwargs): """Indicator: Ulcer Index (UI)""" # Validate arguments close = verify_series(close) length = int(length) if length and length > 0 else 14 scalar = float(scalar) if scalar and scalar > 0 else 100 offset = get_offset(offset) # Calculate Result highest_close = close.rolling(length).max() downside = scalar * (close - highest_close) downside /= highest_close d2 = downside * downside everget = kwargs.pop("everget", False) if everget: # Everget uses SMA instead of SUM for calculation ui = (sma(d2, length) / length).apply(npsqrt) else: ui = (d2.rolling(length).sum() / length).apply(npsqrt) # Offset if offset != 0: ui = ui.shift(offset) # Handle fills if "fillna" in kwargs: ui.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ui.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it ui.name = f"UI{'' if not everget else 'e'}_{length}" ui.category = "volatility" return ui
def variance(close, length=None, ddof=1, offset=None, **kwargs): """Indicator: Variance""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 1 else 30 ddof = int(ddof) if ddof >= 0 and ddof < length else 1 min_periods = int( kwargs["min_periods"]) if "min_periods" in kwargs and kwargs[ "min_periods"] is not None else length offset = get_offset(offset) # Calculate Result variance = close.rolling(length, min_periods=min_periods).var(ddof) # Offset if offset != 0: variance = variance.shift(offset) # Name & Category variance.name = f"VAR_{length}" variance.category = "statistics" return variance
def eom(high, low, close, volume, length=None, divisor=None, drift=None, offset=None, **kwargs): """Indicator: Ease of Movement (EOM)""" # Validate arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) volume = verify_series(volume) length = int(length) if length and length > 0 else 14 divisor = divisor if divisor and divisor > 0 else 100000000 drift = get_drift(drift) offset = get_offset(offset) # Calculate Result high_low_range = non_zero_range(high, low) distance = hl2(high=high, low=low) distance -= hl2(high=high.shift(drift), low=low.shift(drift)) box_ratio = volume / divisor box_ratio /= high_low_range eom = distance / box_ratio eom = sma(eom, length=length) # Offset if offset != 0: eom = eom.shift(offset) # Handle fills if "fillna" in kwargs: eom.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: eom.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it eom.name = f"EOM_{length}_{divisor}" eom.category = "volume" return eom
def midprice(high, low, length=None, offset=None, **kwargs): """Indicator: Midprice""" # Validate arguments length = int(length) if length and length > 0 else 2 min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length _length = max(length, min_periods) high = verify_series(high, _length) low = verify_series(low, _length) offset = get_offset(offset) if high is None or low is None: return # Calculate Result if Imports["talib"]: from talib import MIDPRICE midprice = MIDPRICE(high, low, length) else: lowest_low = low.rolling(length, min_periods=min_periods).min() highest_high = high.rolling(length, min_periods=min_periods).max() midprice = 0.5 * (lowest_low + highest_high) # Offset if offset != 0: midprice = midprice.shift(offset) # Handle fills if "fillna" in kwargs: midprice.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: midprice.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it midprice.name = f"MIDPRICE_{length}" midprice.category = "overlap" return midprice
def sinwma(close, length=None, offset=None, **kwargs): """Indicator: Sine Weighted Moving Average (SINWMA) by Everget of TradingView""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 0 else 14 offset = get_offset(offset) # Calculate Result sines = Series( [sin((i + 1) * pi / (length + 1)) for i in range(0, length)]) w = sines / sines.sum() sinwma = close.rolling(length, min_periods=length).apply(weights(w), raw=True) # Offset if offset != 0: sinwma = sinwma.shift(offset) # Name & Category sinwma.name = f"SINWMA_{length}" sinwma.category = "overlap" return sinwma
def hma(close, length=None, offset=None, **kwargs): """Indicator: Hull Moving Average (HMA)""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 0 else 10 offset = get_offset(offset) # Calculate Result half_length = int(length / 2) sqrt_length = int(sqrt(length)) wmaf = wma(close=close, length=half_length) wmas = wma(close=close, length=length) hma = wma(close=2 * wmaf - wmas, length=sqrt_length) # Offset if offset != 0: hma = hma.shift(offset) # Name & Category hma.name = f"HMA_{length}" hma.category = "overlap" return hma
def pwma(close, length=None, asc=None, offset=None, **kwargs): """Indicator: Pascals Weighted Moving Average (PWMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 asc = asc if asc else True close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result triangle = pascals_triangle(n=length - 1, weighted=True) pwma = close.rolling(length, min_periods=length).apply(weights(triangle), raw=True) # Offset if offset != 0: pwma = pwma.shift(offset) # Name & Category pwma.name = f"PWMA_{length}" pwma.category = "overlap" return pwma
def apo(close, fast=None, slow=None, offset=None, **kwargs): """Indicator: Absolute Price Oscillator (APO)""" # Validate Arguments fast = int(fast) if fast and fast > 0 else 12 slow = int(slow) if slow and slow > 0 else 26 if slow < fast: fast, slow = slow, fast close = verify_series(close, max(fast, slow)) offset = get_offset(offset) if close is None: return # Calculate Result if Imports["talib"]: from talib import APO apo = APO(close, fast, slow) else: fastma = sma(close, length=fast) slowma = sma(close, length=slow) apo = fastma - slowma # Offset if offset != 0: apo = apo.shift(offset) # Handle fills if "fillna" in kwargs: apo.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: apo.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it apo.name = f"APO_{fast}_{slow}" apo.category = "momentum" return apo
def kurtosis(close, length=None, offset=None, **kwargs): """Indicator: Kurtosis""" # Validate Arguments length = int(length) if length and length > 0 else 30 min_periods = int( kwargs["min_periods"]) if "min_periods" in kwargs and kwargs[ "min_periods"] is not None else length close = verify_series(close, max(length, min_periods)) offset = get_offset(offset) if close is None: return # Calculate Result kurtosis = close.rolling(length, min_periods=min_periods).kurt() # Offset if offset != 0: kurtosis = kurtosis.shift(offset) # Name & Category kurtosis.name = f"KURT_{length}" kurtosis.category = "statistics" return kurtosis
def mcgd(close, length=None, offset=None, c=None, **kwargs): """Indicator: McGinley Dynamic Indicator""" # Validate arguments length = int(length) if length and length > 0 else 10 c = float(c) if c and 0 < c <= 1 else 1 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result close = close.copy() def mcg_(series): denom = (c * length * (series.iloc[1] / series.iloc[0]) ** 4) series.iloc[1] = (series.iloc[0] + ((series.iloc[1] - series.iloc[0]) / denom)) return series.iloc[1] mcg_cell = close[0:].rolling(2, min_periods=2).apply(mcg_, raw=False) mcg_ds = close[:1].append(mcg_cell[1:]) # Offset if offset != 0: mcg_ds = mcg_ds.shift(offset) # Handle fills if "fillna" in kwargs: mcg_ds.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: mcg_ds.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category mcg_ds.name = f"MCGD_{length}" mcg_ds.category = "overlap" return mcg_ds
def percent_return(close, length=None, cumulative=False, offset=None, **kwargs): """Indicator: Percent Return""" # Validate Arguments length = int(length) if length and length > 0 else 1 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result pct_return = close.pct_change(length) if cumulative: pct_return = pct_return.cumsum() # Offset if offset != 0: pct_return = pct_return.shift(offset) # Name & Category pct_return.name = f"{'CUM' if cumulative else ''}PCTRET_{length}" pct_return.category = "performance" return pct_return
def true_range(high, low, close, drift=None, offset=None, **kwargs): """Indicator: True Range""" # Validate arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) drift = get_drift(drift) offset = get_offset(offset) # Calculate Result if Imports["talib"]: from talib import TRANGE true_range = TRANGE(high, low, close) else: high_low_range = non_zero_range(high, low) prev_close = close.shift(drift) ranges = [high_low_range, high - prev_close, prev_close - low] true_range = concat(ranges, axis=1) true_range = true_range.abs().max(axis=1) true_range.iloc[:drift] = npNaN # Offset if offset != 0: true_range = true_range.shift(offset) # Handle fills if "fillna" in kwargs: true_range.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: true_range.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it true_range.name = f"TRUERANGE_{drift}" true_range.category = "volatility" return true_range
def zscore(close, length=None, std=None, offset=None, **kwargs): """Indicator: Z Score""" # Validate Arguments length = int(length) if length and length > 1 else 30 std = float(std) if std and std > 1 else 1 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result std *= stdev(close=close, length=length, **kwargs) mean = sma(close=close, length=length, **kwargs) zscore = (close - mean) / std # Offset if offset != 0: zscore = zscore.shift(offset) # Name & Category zscore.name = f"Z_{length}" zscore.category = "statistics" return zscore
def pdist(open_, high, low, close, drift=None, offset=None, **kwargs): """Indicator: Price Distance (PDIST)""" # Validate Arguments open_ = verify_series(open_) high = verify_series(high) low = verify_series(low) close = verify_series(close) drift = get_drift(drift) offset = get_offset(offset) # Calculate Result pdist = 2 * non_zero_range(high, low) pdist += non_zero_range(open_, close.shift(drift)).abs() pdist -= non_zero_range(close, open_).abs() # Offset if offset != 0: pdist = pdist.shift(offset) # Name & Category pdist.name = "PDIST" pdist.category = "volatility" return pdist
def adosc(high, low, close, volume, open_=None, fast=None, slow=None, offset=None, **kwargs): """Indicator: Accumulation/Distribution Oscillator""" # Validate Arguments fast = int(fast) if fast and fast > 0 else 3 slow = int(slow) if slow and slow > 0 else 10 _length = max(fast, slow) high = verify_series(high, _length) low = verify_series(low, _length) close = verify_series(close, _length) volume = verify_series(volume, _length) offset = get_offset(offset) if "length" in kwargs: kwargs.pop("length") if high is None or low is None or close is None or volume is None: return # Calculate Result ad_ = ad(high=high, low=low, close=close, volume=volume, open_=open_) fast_ad = ema(close=ad_, length=fast, **kwargs) slow_ad = ema(close=ad_, length=slow, **kwargs) adosc = fast_ad - slow_ad # Offset if offset != 0: adosc = adosc.shift(offset) # Handle fills if "fillna" in kwargs: adosc.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: adosc.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it adosc.name = f"ADOSC_{fast}_{slow}" adosc.category = "volume" return adosc
def tema(close, length=None, offset=None, **kwargs): """Indicator: Triple Exponential Moving Average (TEMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result ema1 = ema(close=close, length=length, **kwargs) ema2 = ema(close=ema1, length=length, **kwargs) ema3 = ema(close=ema2, length=length, **kwargs) tema = 3 * (ema1 - ema2) + ema3 # Offset if offset != 0: tema = tema.shift(offset) # Name & Category tema.name = f"TEMA_{length}" tema.category = "overlap" return tema
def vwap(high, low, close, volume, anchor=None, offset=None, **kwargs): """Indicator: Volume Weighted Average Price (VWAP)""" # Validate Arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) volume = verify_series(volume) anchor = anchor.upper() if anchor and isinstance(anchor, str) and len(anchor) >= 1 else "D" offset = get_offset(offset) typical_price = hlc3(high=high, low=low, close=close) if not is_datetime_ordered(volume): print(f"[!] VWAP volume series is not datetime ordered. Results may not be as expected.") if not is_datetime_ordered(typical_price): print(f"[!] VWAP price series is not datetime ordered. Results may not be as expected.") # Calculate Result wp = typical_price * volume vwap = wp.groupby(wp.index.to_period(anchor)).cumsum() vwap /= volume.groupby(volume.index.to_period(anchor)).cumsum() # Offset if offset != 0: vwap = vwap.shift(offset) # Handle fills if "fillna" in kwargs: vwap.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: vwap.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category vwap.name = f"VWAP_{anchor}" vwap.category = "overlap" return vwap
def hlc3(high, low, close, offset=None, **kwargs): """Indicator: HLC3""" # Validate Arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) offset = get_offset(offset) # Calculate Result if Imports["talib"]: from talib import TYPPRICE hlc3 = TYPPRICE(high, low, close) else: hlc3 = (high + low + close) / 3.0 # Offset if offset != 0: hlc3 = hlc3.shift(offset) # Name & Category hlc3.name = "HLC3" hlc3.category = "overlap" return hlc3
def squeeze(high, low, close, bb_length=None, bb_std=None, kc_length=None, kc_scalar=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs): """Indicator: Squeeze Momentum (SQZ)""" # Validate arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) offset = get_offset(offset) bb_length = int(bb_length) if bb_length and bb_length > 0 else 20 bb_std = float(bb_std) if bb_std and bb_std > 0 else 2. kc_length = int(kc_length) if kc_length and kc_length > 0 else 20 kc_scalar = float(kc_scalar) if kc_scalar and kc_scalar > 0 else 1.5 mom_length = int(mom_length) if mom_length and mom_length > 0 else 12 mom_smooth = int(mom_smooth) if mom_smooth and mom_smooth > 0 else 6 use_tr = kwargs.setdefault("tr", True) asint = kwargs.pop("asint", True) mamode = kwargs.pop("mamode", "sma").lower() lazybear = kwargs.pop("lazybear", False) detailed = kwargs.pop("detailed", False) def simplify_columns(df, n=3): df.columns = df.columns.str.lower() return [c.split('_')[0][n - 1:n] for c in df.columns] # Calculate Result bbd = bbands(close, length=bb_length, std=bb_std, mamode=mamode) kch = kc(high, low, close, length=kc_length, scalar=kc_scalar, mamode=mamode, tr=use_tr) # Simplify KC and BBAND column names for dynamic access bbd.columns = simplify_columns(bbd) kch.columns = simplify_columns(kch) if lazybear: highest_high = high.rolling(kc_length).max() lowest_low = low.rolling(kc_length).min() avg_ = 0.25 * (highest_high + lowest_low) + 0.5 * kch.b squeeze = linreg(close - avg_, length=kc_length) else: momo = mom(close, length=mom_length) if mamode == "ema": squeeze = ema(momo, length=mom_smooth) else: squeeze = sma(momo, length=mom_smooth) # Classify Squeezes squeeze_on = (bbd.l > kch.l) & (bbd.u < kch.u) squeeze_off = (bbd.l < kch.l) & (bbd.u > kch.u) no_squeeze = ~squeeze_on & ~squeeze_off # Offset if offset != 0: squeeze = squeeze.shift(offset) squeeze_on = squeeze_on.shift(offset) squeeze_off = squeeze_off.shift(offset) no_squeeze = no_squeeze.shift(offset) # Handle fills if "fillna" in kwargs: squeeze.fillna(kwargs["fillna"], inplace=True) squeeze_on.fillna(kwargs["fillna"], inplace=True) squeeze_off.fillna(kwargs["fillna"], inplace=True) no_squeeze.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: squeeze.fillna(method=kwargs["fill_method"], inplace=True) squeeze_on.fillna(method=kwargs["fill_method"], inplace=True) squeeze_off.fillna(method=kwargs["fill_method"], inplace=True) no_squeeze.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = "" if use_tr else "hlr" _props += f"_{bb_length}_{bb_std}_{kc_length}_{kc_scalar}" _props += "_LB" if lazybear else "" squeeze.name = f"SQZ{_props}" data = { squeeze.name: squeeze, f"SQZ_ON": squeeze_on.astype(int) if asint else squeeze_on, f"SQZ_OFF": squeeze_off.astype(int) if asint else squeeze_off, f"SQZ_NO": no_squeeze.astype(int) if asint else no_squeeze } df = DataFrame(data) df.name = squeeze.name df.category = squeeze.category = "momentum" # Detailed Squeeze Series if detailed: pos_squeeze = squeeze[squeeze >= 0] neg_squeeze = squeeze[squeeze < 0] pos_inc, pos_dec = unsigned_differences(pos_squeeze, asint=True) neg_inc, neg_dec = unsigned_differences(neg_squeeze, asint=True) pos_inc *= squeeze pos_dec *= squeeze neg_dec *= squeeze neg_inc *= squeeze pos_inc.replace(0, npNaN, inplace=True) pos_dec.replace(0, npNaN, inplace=True) neg_dec.replace(0, npNaN, inplace=True) neg_inc.replace(0, npNaN, inplace=True) sqz_inc = squeeze * increasing(squeeze) sqz_dec = squeeze * decreasing(squeeze) sqz_inc.replace(0, npNaN, inplace=True) sqz_dec.replace(0, npNaN, inplace=True) # Handle fills if "fillna" in kwargs: sqz_inc.fillna(kwargs["fillna"], inplace=True) sqz_dec.fillna(kwargs["fillna"], inplace=True) pos_inc.fillna(kwargs["fillna"], inplace=True) pos_dec.fillna(kwargs["fillna"], inplace=True) neg_dec.fillna(kwargs["fillna"], inplace=True) neg_inc.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: sqz_inc.fillna(method=kwargs["fill_method"], inplace=True) sqz_dec.fillna(method=kwargs["fill_method"], inplace=True) pos_inc.fillna(method=kwargs["fill_method"], inplace=True) pos_dec.fillna(method=kwargs["fill_method"], inplace=True) neg_dec.fillna(method=kwargs["fill_method"], inplace=True) neg_inc.fillna(method=kwargs["fill_method"], inplace=True) df[f"SQZ_INC"] = sqz_inc df[f"SQZ_DEC"] = sqz_dec df[f"SQZ_PINC"] = pos_inc df[f"SQZ_PDEC"] = pos_dec df[f"SQZ_NDEC"] = neg_dec df[f"SQZ_NINC"] = neg_inc return df
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 brar(open_, high, low, close, length=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: BRAR (BRAR)""" # Validate Arguments length = int(length) if length and length > 0 else 26 scalar = float(scalar) if scalar else 100 high_open_range = non_zero_range(high, open_) open_low_range = non_zero_range(open_, low) open_ = verify_series(open_, length) high = verify_series(high, length) low = verify_series(low, length) close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) if open_ is None or high is None or low is None or close is None: return # Calculate Result hcy = non_zero_range(high, close.shift(drift)) cyl = non_zero_range(close.shift(drift), low) hcy[hcy < 0] = 0 # Zero negative values cyl[cyl < 0] = 0 # "" ar = scalar * high_open_range.rolling(length).sum() ar /= open_low_range.rolling(length).sum() br = scalar * hcy.rolling(length).sum() br /= cyl.rolling(length).sum() # Offset if offset != 0: ar = ar.shift(offset) br = ar.shift(offset) # Handle fills if "fillna" in kwargs: ar.fillna(kwargs["fillna"], inplace=True) br.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ar.fillna(method=kwargs["fill_method"], inplace=True) br.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = f"_{length}" ar.name = f"AR{_props}" br.name = f"BR{_props}" ar.category = br.category = "momentum" # Prepare DataFrame to return brardf = DataFrame({ar.name: ar, br.name: br}) brardf.name = f"BRAR{_props}" brardf.category = "momentum" return brardf
def linreg(close, length=None, offset=None, **kwargs): """Indicator: Linear Regression""" # Validate arguments close = verify_series(close) length = int(length) if length and length > 0 else 14 min_periods = int( kwargs["min_periods"]) if "min_periods" in kwargs and kwargs[ "min_periods"] is not None else length offset = get_offset(offset) angle = kwargs.pop("angle", False) intercept = kwargs.pop("intercept", False) degrees = kwargs.pop("degrees", False) r = kwargs.pop("r", False) slope = kwargs.pop("slope", False) tsf = kwargs.pop("tsf", False) # Calculate Result x = range(1, length + 1) # [1, 2, ..., n] from 1 to n keeps Sum(xy) low x_sum = 0.5 * length * (length + 1) x2_sum = x_sum * (2 * length + 1) / 3 divisor = length * x2_sum - x_sum * x_sum def linear_regression(series): y_sum = series.sum() xy_sum = (x * series).sum() m = (length * xy_sum - x_sum * y_sum) / divisor if slope: return m b = (y_sum * x2_sum - x_sum * xy_sum) / divisor if intercept: return b if angle: theta = math.atan(m) if degrees: theta *= 180 / math.pi return theta if r: y2_sum = (series * series).sum() rn = length * xy_sum - x_sum * y_sum rd = math.sqrt(divisor * (length * y2_sum - y_sum * y_sum)) return rn / rd return m * length + b if tsf else m * (length - 1) + b linreg = close.rolling(length, min_periods=length).apply(linear_regression, raw=False) # Offset if offset != 0: linreg = linreg.shift(offset) # Handle fills if "fillna" in kwargs: linreg.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: linreg.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it linreg.name = f"LR" if slope: linreg.name += "m" if intercept: linreg.name += "b" if angle: linreg.name += "a" if r: linreg.name += "r" linreg.name += f"_{length}" linreg.category = "overlap" return linreg
def ichimoku(high, low, close, tenkan=None, kijun=None, senkou=None, include_chikou=True, offset=None, **kwargs): """Indicator: Ichimoku Kinkō Hyō (Ichimoku)""" tenkan = int(tenkan) if tenkan and tenkan > 0 else 9 kijun = int(kijun) if kijun and kijun > 0 else 26 senkou = int(senkou) if senkou and senkou > 0 else 52 _length = max(tenkan, kijun, senkou) high = verify_series(high, _length) low = verify_series(low, _length) close = verify_series(close, _length) offset = get_offset(offset) if not kwargs.get("lookahead", True): include_chikou = False if high is None or low is None or close is None: return None, None # Calculate Result tenkan_sen = midprice(high=high, low=low, length=tenkan) kijun_sen = midprice(high=high, low=low, length=kijun) span_a = 0.5 * (tenkan_sen + kijun_sen) span_b = midprice(high=high, low=low, length=senkou) # Copy Span A and B values before their shift _span_a = span_a[-kijun:].copy() _span_b = span_b[-kijun:].copy() span_a = span_a.shift(kijun) span_b = span_b.shift(kijun) chikou_span = close.shift(-kijun) # Offset if offset != 0: tenkan_sen = tenkan_sen.shift(offset) kijun_sen = kijun_sen.shift(offset) span_a = span_a.shift(offset) span_b = span_b.shift(offset) chikou_span = chikou_span.shift(offset) # Handle fills if "fillna" in kwargs: span_a.fillna(kwargs["fillna"], inplace=True) span_b.fillna(kwargs["fillna"], inplace=True) chikou_span.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: span_a.fillna(method=kwargs["fill_method"], inplace=True) span_b.fillna(method=kwargs["fill_method"], inplace=True) chikou_span.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it span_a.name = f"ISA_{tenkan}" span_b.name = f"ISB_{kijun}" tenkan_sen.name = f"ITS_{tenkan}" kijun_sen.name = f"IKS_{kijun}" chikou_span.name = f"ICS_{kijun}" chikou_span.category = kijun_sen.category = tenkan_sen.category = "trend" span_b.category = span_a.category = chikou_span # Prepare Ichimoku DataFrame data = { span_a.name: span_a, span_b.name: span_b, tenkan_sen.name: tenkan_sen, kijun_sen.name: kijun_sen, } if include_chikou: data[chikou_span.name] = chikou_span ichimokudf = DataFrame(data) ichimokudf.name = f"ICHIMOKU_{tenkan}_{kijun}_{senkou}" ichimokudf.category = "overlap" # Prepare Span DataFrame last = close.index[-1] if close.index.dtype == "int64": ext_index = RangeIndex(start=last + 1, stop=last + kijun + 1) spandf = DataFrame(index=ext_index, columns=[span_a.name, span_b.name]) _span_a.index = _span_b.index = ext_index else: df_freq = close.index.value_counts().mode()[0] tdelta = Timedelta(df_freq, unit="d") new_dt = date_range(start=last + tdelta, periods=kijun, freq="B") spandf = DataFrame(index=new_dt, columns=[span_a.name, span_b.name]) _span_a.index = _span_b.index = new_dt spandf[span_a.name] = _span_a spandf[span_b.name] = _span_b spandf.name = f"ICHISPAN_{tenkan}_{kijun}" spandf.category = "overlap" return ichimokudf, spandf
def adx(high, low, close, length=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: ADX""" # Validate Arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) length = length if length and length > 0 else 14 scalar = float(scalar) if scalar else 100 drift = get_drift(drift) offset = get_offset(offset) # Calculate Result atr_ = atr(high=high, low=low, close=close, length=length) up = high - high.shift(drift) # high.diff(drift) dn = low.shift(drift) - low # low.diff(-drift).shift(drift) pos = ((up > dn) & (up > 0)) * up neg = ((dn > up) & (dn > 0)) * dn pos = pos.apply(zero) neg = neg.apply(zero) k = scalar / atr_ dmp = k * rma(close=pos, length=length) dmn = k * rma(close=neg, length=length) dx = scalar * (dmp - dmn).abs() / (dmp + dmn) adx = rma(close=dx, length=length) # Offset if offset != 0: dmp = dmp.shift(offset) dmn = dmn.shift(offset) adx = adx.shift(offset) # Handle fills if "fillna" in kwargs: adx.fillna(kwargs["fillna"], inplace=True) dmp.fillna(kwargs["fillna"], inplace=True) dmn.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: adx.fillna(method=kwargs["fill_method"], inplace=True) dmp.fillna(method=kwargs["fill_method"], inplace=True) dmn.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it adx.name = f"ADX_{length}" dmp.name = f"DMP_{length}" dmn.name = f"DMN_{length}" adx.category = dmp.category = dmn.category = "trend" # Prepare DataFrame to return data = {adx.name: adx, dmp.name: dmp, dmn.name: dmn} adxdf = DataFrame(data) adxdf.name = f"ADX_{length}" adxdf.category = "trend" return adxdf
def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs): """Indicator: Moving Average, Convergence/Divergence (MACD)""" # Validate arguments close = verify_series(close) fast = int(fast) if fast and fast > 0 else 12 slow = int(slow) if slow and slow > 0 else 26 signal = int(signal) if signal and signal > 0 else 9 if slow < fast: fast, slow = slow, fast offset = get_offset(offset) # Calculate Result fastma = ema(close, length=fast) slowma = ema(close, length=slow) macd = fastma - slowma signalma = ema(close=macd, length=signal) histogram = macd - signalma # Offset if offset != 0: macd = macd.shift(offset) histogram = histogram.shift(offset) signalma = signalma.shift(offset) # Handle fills if "fillna" in kwargs: macd.fillna(kwargs["fillna"], inplace=True) histogram.fillna(kwargs["fillna"], inplace=True) signalma.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: macd.fillna(method=kwargs["fill_method"], inplace=True) histogram.fillna(method=kwargs["fill_method"], inplace=True) signalma.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = f"_{fast}_{slow}_{signal}" macd.name = f"MACD{_props}" histogram.name = f"MACDh{_props}" signalma.name = f"MACDs{_props}" macd.category = histogram.category = signalma.category = "momentum" # Prepare DataFrame to return data = {macd.name: macd, histogram.name: histogram, signalma.name: signalma} df = DataFrame(data) df.name = f"MACD{_props}" df.category = macd.category signal_indicators = kwargs.pop("signal_indicators", False) if signal_indicators: signalsdf = concat( [ df, signals( indicator=histogram, xa=kwargs.pop("xa", 0), xb=kwargs.pop("xb", None), xserie=kwargs.pop("xserie", None), xserie_a=kwargs.pop("xserie_a", None), xserie_b=kwargs.pop("xserie_b", None), cross_values=kwargs.pop("cross_values", True), cross_series=kwargs.pop("cross_series", True), offset=offset, ), signals( indicator=macd, xa=kwargs.pop("xa", 0), xb=kwargs.pop("xb", None), xserie=kwargs.pop("xserie", None), xserie_a=kwargs.pop("xserie_a", None), xserie_b=kwargs.pop("xserie_b", None), cross_values=kwargs.pop("cross_values", False), cross_series=kwargs.pop("cross_series", True), offset=offset, ), ], axis=1, ) return signalsdf else: return df