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 offset = get_offset(offset) if "length" in kwargs: kwargs.pop("length") # 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 kc(high, low, close, length=None, scalar=None, mamode=None, offset=None, **kwargs): """Indicator: Keltner Channels (KC)""" # Validate arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) length = int(length) if length and length > 0 else 20 scalar = float(scalar) if scalar and scalar > 0 else 2 mamode = mamode.lower() if mamode else None offset = get_offset(offset) # Calculate Result use_tr = kwargs.pop("tr", True) if use_tr: range_ = true_range(high, low, close) else: range_ = high_low_range(high, low) _mode = "" if mamode == "sma": basis = sma(close, length) band = sma(range_, length=length) _mode += "s" elif mamode is None or mamode == "ema": basis = ema(close, length=length) band = ema(range_, length=length) lower = basis - scalar * band upper = basis + scalar * band # Offset if offset != 0: lower = lower.shift(offset) basis = basis.shift(offset) upper = upper.shift(offset) # Handle fills if "fillna" in kwargs: lower.fillna(kwargs["fillna"], inplace=True) basis.fillna(kwargs["fillna"], inplace=True) upper.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: lower.fillna(method=kwargs["fill_method"], inplace=True) basis.fillna(method=kwargs["fill_method"], inplace=True) upper.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = f"{_mode if len(_mode) else ''}_{length}_{scalar}" lower.name = f"KCL{_props}" basis.name = f"KCB{_props}" upper.name = f"KCU{_props}" basis.category = upper.category = lower.category = "volatility" # Prepare DataFrame to return data = {lower.name: lower, basis.name: basis, upper.name: upper} kcdf = DataFrame(data) kcdf.name = f"KC{_props}" kcdf.category = basis.category return kcdf
def adosc(high, low, close, volume, open_=None, fast=None, slow=None, offset=None, **kwargs): """Indicator: Accumulation/Distribution Oscillator""" # Validate Arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) volume = verify_series(volume) fast = int(fast) if fast and fast > 0 else 3 slow = int(slow) if slow and slow > 0 else 10 offset = get_offset(offset) if "length" in kwargs: kwargs.pop("length") # 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 pvo(volume, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs): """Indicator: Percentage Volume Oscillator (PVO)""" # Validate Arguments 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 scalar = float(scalar) if scalar else 100 if slow < fast: fast, slow = slow, fast volume = verify_series(volume, max(fast, slow, signal)) offset = get_offset(offset) if volume is None: return # Calculate Result fastma = ema(volume, length=fast) slowma = ema(volume, length=slow) pvo = scalar * (fastma - slowma) pvo /= slowma signalma = ema(pvo, length=signal) histogram = pvo - signalma # Offset if offset != 0: pvo = pvo.shift(offset) histogram = histogram.shift(offset) signalma = signalma.shift(offset) # Handle fills if "fillna" in kwargs: pvo.fillna(kwargs["fillna"], inplace=True) histogram.fillna(kwargs["fillna"], inplace=True) signalma.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: pvo.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}" pvo.name = f"PVO{_props}" histogram.name = f"PVOh{_props}" signalma.name = f"PVOs{_props}" pvo.category = histogram.category = signalma.category = "momentum" # data = {pvo.name: pvo, histogram.name: histogram, signalma.name: signalma} df = DataFrame(data) df.name = pvo.name df.category = pvo.category return df
def amat(close=None, fast=None, slow=None, mamode=None, lookback=None, offset=None, **kwargs): """Indicator: Archer Moving Averages Trends (AMAT)""" # Validate Arguments close = verify_series(close) fast = int(fast) if fast and fast > 0 else 8 slow = int(slow) if slow and slow > 0 else 21 lookback = int(lookback) if lookback and lookback > 0 else 2 mamode = mamode.lower() if mamode else "ema" offset = get_offset(offset) # Calculate Result if mamode == "hma": fast_ma = hma(close=close, length=fast, **kwargs) slow_ma = hma(close=close, length=slow, **kwargs) elif mamode == "linreg": fast_ma = linreg(close=close, length=fast, **kwargs) slow_ma = linreg(close=close, length=slow, **kwargs) elif mamode == "rma": fast_ma = rma(close=close, length=fast, **kwargs) slow_ma = rma(close=close, length=slow, **kwargs) elif mamode == "sma": fast_ma = sma(close=close, length=fast, **kwargs) slow_ma = sma(close=close, length=slow, **kwargs) elif mamode == "wma": fast_ma = wma(close=close, length=fast, **kwargs) slow_ma = wma(close=close, length=slow, **kwargs) else: # "ema" fast_ma = ema(close=close, length=fast, **kwargs) slow_ma = ema(close=close, length=slow, **kwargs) mas_long = long_run(fast_ma, slow_ma, length=lookback) mas_short = short_run(fast_ma, slow_ma, length=lookback) # Offset if offset != 0: mas_long = mas_long.shift(offset) mas_short = mas_short.shift(offset) # # Handle fills if "fillna" in kwargs: mas_long.fillna(kwargs["fillna"], inplace=True) mas_short.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: mas_long.fillna(method=kwargs["fill_method"], inplace=True) mas_short.fillna(method=kwargs["fill_method"], inplace=True) # Prepare DataFrame to return amatdf = DataFrame({ f"AMAT_{mas_long.name}": mas_long, f"AMAT_{mas_short.name}": mas_short }) # Name and Categorize it amatdf.name = f"AMAT_{mamode.upper()}_{fast}_{slow}_{lookback}" amatdf.category = "trend" return amatdf
def adosc(high, low, close, volume, open_=None, fast=None, slow=None, talib=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") mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None or close is None or volume is None: return # Calculate Result if Imports["talib"] and mode_tal: from talib import ADOSC adosc = ADOSC(high, low, close, volume, fast, slow) else: 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 ppo(close, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs): """Indicator: Percentage Price Oscillator (PPO)""" # 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 scalar = float(scalar) if scalar else 100 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 fastma = sma(close, length=fast) slowma = sma(close, length=slow) ppo = scalar * (fastma - slowma) ppo /= slowma signalma = ema(ppo, length=signal) histogram = ppo - signalma # Offset if offset != 0: ppo = ppo.shift(offset) histogram = histogram.shift(offset) signalma = signalma.shift(offset) # Handle fills if "fillna" in kwargs: ppo.fillna(kwargs["fillna"], inplace=True) histogram.fillna(kwargs["fillna"], inplace=True) signalma.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ppo.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}" ppo.name = f"PPO{_props}" histogram.name = f"PPOh{_props}" signalma.name = f"PPOs{_props}" ppo.category = histogram.category = signalma.category = "momentum" # Prepare DataFrame to return data = {ppo.name: ppo, histogram.name: histogram, signalma.name: signalma} df = DataFrame(data) df.name = f"PPO{_props}" df.category = ppo.category return df
def pgo(high, low, close, length=None, offset=None, **kwargs): """Indicator: Pretty Good Oscillator (PGO)""" # Validate arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) length = int(length) if length and length > 0 else 14 offset = get_offset(offset) # Calculate Result pgo = close - sma(close, length) pgo /= ema(atr(high, low, close, length), length) # Offset if offset != 0: pgo = pgo.shift(offset) # Handle fills if "fillna" in kwargs: pgo.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: pgo.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it pgo.name = f"PGO_{length}" pgo.category = "momentum" return pgo
def tsi(close, fast=None, slow=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: True Strength Index (TSI)""" # Validate Arguments fast = int(fast) if fast and fast > 0 else 13 slow = int(slow) if slow and slow > 0 else 25 # if slow < fast: # fast, slow = slow, fast scalar = float(scalar) if scalar else 100 close = verify_series(close, max(fast, slow)) drift = get_drift(drift) offset = get_offset(offset) if "length" in kwargs: kwargs.pop("length") if close is None: return # Calculate Result diff = close.diff(drift) slow_ema = ema(close=diff, length=slow, **kwargs) fast_slow_ema = ema(close=slow_ema, length=fast, **kwargs) abs_diff = diff.abs() abs_slow_ema = ema(close=abs_diff, length=slow, **kwargs) abs_fast_slow_ema = ema(close=abs_slow_ema, length=fast, **kwargs) tsi = scalar * fast_slow_ema / abs_fast_slow_ema # Offset if offset != 0: tsi = tsi.shift(offset) # Handle fills if "fillna" in kwargs: tsi.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: tsi.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it tsi.name = f"TSI_{fast}_{slow}" tsi.category = "momentum" return tsi
def fisher(high, low, length=None, signal=None, offset=None, **kwargs): """Indicator: Fisher Transform (FISHT)""" # Validate Arguments high = verify_series(high) low = verify_series(low) length = int(length) if length and length > 0 else 9 signal = int(signal) if signal and signal > 0 else 5 offset = get_offset(offset) # Calculate Result m = high.size hl2_ = hl2(high, low) max_high = hl2_.rolling(length).max() min_low = hl2_.rolling(length).min() hl2_range = max_high - min_low hl2_range[hl2_range < 1e-5] = 0.001 position = (hl2_ - min_low) / hl2_range v = 0 fish = 0 result = [npNaN for _ in range(0, length - 1)] for i in range(length - 1, m): v = 0.66 * (position[i] - 0.5) + 0.67 * v if v > 0.99: v = 0.999 if v < -0.99: v = -0.999 fish = 0.5 * (fish + nplog((1 + v) / (1 - v))) result.append(fish) fisher = Series(result, index=high.index) signalma = ema(fisher, length=signal) # Offset if offset != 0: fisher = fisher.shift(offset) signalma = signalma.shift(offset) # Handle fills if "fillna" in kwargs: fisher.fillna(kwargs["fillna"], inplace=True) signalma.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: fisher.fillna(method=kwargs["fill_method"], inplace=True) signalma.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = f"_{length}_{signal}" fisher.name = f"FISHERT{_props}" signalma.name = f"FISHERTs{_props}" fisher.category = signalma.category = "momentum" # Prepare DataFrame to return data = {fisher.name: fisher, signalma.name: signalma} df = DataFrame(data) df.name = f"FISHERT{_props}" df.category = fisher.category return df
def rvi_(source, length, scalar, mamode, drift): """RVI""" std = stdev(source, length) pos, neg = unsigned_differences(source, amount=drift) pos_std = pos * std neg_std = neg * std if mamode == 'sma': pos_avg = sma(pos_std, length) neg_avg = sma(neg_std, length) else: # 'ema' pos_avg = ema(pos_std, length) neg_avg = ema(neg_std, length) result = scalar * pos_avg result /= pos_avg + neg_avg return result
def smi(close, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs): """Indicator: SMI Ergodic Indicator (SMIIO)""" # Validate arguments close = verify_series(close) fast = int(fast) if fast and fast > 0 else 5 slow = int(slow) if slow and slow > 0 else 20 signal = int(signal) if signal and signal > 0 else 5 if slow < fast: fast, slow = slow, fast scalar = float(scalar) if scalar else 1 offset = get_offset(offset) # Calculate Result smi = tsi(close, fast=fast, slow=slow, scalar=scalar) signalma = ema(smi, signal) osc = smi - signalma # Offset if offset != 0: smi = smi.shift(offset) signalma = signalma.shift(offset) osc = osc.shift(offset) # Handle fills if "fillna" in kwargs: smi.fillna(kwargs["fillna"], inplace=True) signalma.fillna(kwargs["fillna"], inplace=True) osc.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: smi.fillna(method=kwargs["fill_method"], inplace=True) signalma.fillna(method=kwargs["fill_method"], inplace=True) osc.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _scalar = f"_{scalar}" if scalar != 1 else "" _props = f"_{fast}_{slow}_{signal}{_scalar}" smi.name = f"SMI{_props}" signalma.name = f"SMIs{_props}" osc.name = f"SMIo{_props}" smi.category = signalma.category = osc.category = "momentum" # Prepare DataFrame to return data = {smi.name: smi, signalma.name: signalma, osc.name: osc} df = DataFrame(data) df.name = f"SMI{_props}" df.category = smi.category return df
def bbands(close, length=None, std=None, mamode=None, offset=None, **kwargs): """Indicator: Bollinger Bands (BBANDS)""" # Validate arguments close = verify_series(close) length = int(length) if length and length > 0 else 5 min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length std = float(std) if std and std > 0 else 2. mamode = mamode.lower() if mamode else "sma" offset = get_offset(offset) # Calculate Result standard_deviation = stdev(close=close, length=length) deviations = std * standard_deviation if mamode is None or mamode == "sma": mid = sma(close=close, length=length) elif mamode == "ema": mid = ema(close=close, length=length, **kwargs) lower = mid - deviations upper = mid + deviations # Offset if offset != 0: lower = lower.shift(offset) mid = mid.shift(offset) upper = upper.shift(offset) # Handle fills if "fillna" in kwargs: lower.fillna(kwargs["fillna"], inplace=True) mid.fillna(kwargs["fillna"], inplace=True) upper.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: lower.fillna(method=kwargs["fill_method"], inplace=True) mid.fillna(method=kwargs["fill_method"], inplace=True) upper.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it lower.name = f"BBL_{length}_{std}" mid.name = f"BBM_{length}_{std}" upper.name = f"BBU_{length}_{std}" mid.category = upper.category = lower.category = "volatility" # Prepare DataFrame to return data = {lower.name: lower, mid.name: mid, upper.name: upper} bbandsdf = DataFrame(data) bbandsdf.name = f"BBANDS_{length}_{std}" bbandsdf.category = "volatility" return bbandsdf
def atr(high, low, close, length=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: Average True Range (ATR)""" # Validate arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) length = int(length) if length and length > 0 else 14 mamode = mamode = mamode.lower() if mamode else "rma" drift = get_drift(drift) offset = get_offset(offset) # Calculate Result _mode = "" tr = true_range(high=high, low=low, close=close, drift=drift) if mamode == "ema": atr, _mode = ema(tr, length=length), "ema" elif mamode == "sma": atr, _mode = sma(tr, length=length), "sma" elif mamode == "wma": atr, _mode = wma(tr, length=length), "wma" else: # "rma" atr = rma(tr, length=length) percentage = kwargs.pop("percent", False) if percentage: atr *= 100 / close # Offset if offset != 0: atr = atr.shift(offset) # Handle fills if "fillna" in kwargs: atr.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: atr.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it atr.name = f"ATR{_mode}_{length}{'p' if percentage else ''}" atr.category = "volatility" return atr
def eri(high, low, close, length=None, offset=None, **kwargs): """Indicator: Elder Ray Index (ERI)""" # Validate arguments length = int(length) if length and length > 0 else 13 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 ema_ = ema(close, length) bull = high - ema_ bear = low - ema_ # Offset if offset != 0: bull = bull.shift(offset) bear = bear.shift(offset) # Handle fills if "fillna" in kwargs: bull.fillna(kwargs["fillna"], inplace=True) bear.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: bull.fillna(method=kwargs["fill_method"], inplace=True) bear.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it bull.name = f"BULLP_{length}" bear.name = f"BEARP_{length}" bull.category = bear.category = "momentum" # Prepare DataFrame to return data = {bull.name: bull, bear.name: bear} df = DataFrame(data) df.name = f"ERI_{length}" df.category = bull.category return df
def qstick(open_, close, length=None, offset=None, **kwargs): """Indicator: Q Stick""" # Validate Arguments length = int(length) if length and length > 0 else 10 ma = kwargs.pop("ma", "sma") open_ = verify_series(open_, length) close = verify_series(close, length) offset = get_offset(offset) if open_ is None or close is None: return # Calculate Result diff = non_zero_range(close, open_) if ma == "dema": qstick = dema(diff, length=length, **kwargs) elif ma == "ema": qstick = ema(diff, length=length, **kwargs) elif ma == "hma": qstick = hma(diff, length=length) elif ma == "rma": qstick = rma(diff, length=length) else: # "sma" qstick = sma(diff, length=length) # Offset if offset != 0: qstick = qstick.shift(offset) # Handle fills if "fillna" in kwargs: qstick.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: qstick.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it qstick.name = f"QS_{length}" qstick.category = "trend" return qstick
def efi(close, volume, length=None, drift=None, mamode=None, offset=None, **kwargs): """Indicator: Elder's Force Index (EFI)""" # Validate arguments close = verify_series(close) volume = verify_series(volume) length = int(length) if length and length > 0 else 13 drift = get_drift(drift) mamode = mamode.lower() if mamode else None offset = get_offset(offset) # Calculate Result pv_diff = close.diff(drift) * volume if mamode == "sma": efi = sma(pv_diff, length) else: efi = ema(pv_diff, 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 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 == "ema": ma = ema(close, length=length, **kwargs) elif mamode == "hma": ma = hma(close, length=length, **kwargs) elif mamode == "rma": ma = rma(close, length=length, **kwargs) elif mamode == "wma": ma = wma(close, length=length, **kwargs) else: # "sma" ma = sma(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 stc(close, tclength=None, fast=None, slow=None, factor=None, offset=None, **kwargs): """Indicator: Schaff Trend Cycle (STC)""" # Validate arguments 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 fast, slow = slow, fast _length = max(tclength, fast, slow) close = verify_series(close, _length) offset = get_offset(offset) 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, _length) ma2 = verify_series(ma2, _length) if ma1 is None or ma2 is None: return # Calculate Result based on external feeded series xmacd = ma1 - ma2 # invoke shared calculation pff, pf = schaff_tc(close, xmacd, tclength, factor) elif isinstance(osc, Series): 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 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) xmacd = fastma - slowma # invoke shared calculation pff, pf = schaff_tc(close, xmacd, tclength, factor) # Resulting Series stc = Series(pff, index=close.index) macd = Series(xmacd, index=close.index) stoch = Series(pf, 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"_{tclength}_{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
def accbands(high, low, close, length=None, c=None, drift=None, mamode=None, offset=None, **kwargs): """Indicator: Acceleration Bands (ACCBANDS)""" # Validate arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) high_low_range = non_zero_range(high, low) length = int(length) if length and length > 0 else 20 c = float(c) if c and c > 0 else 4 min_periods = int( kwargs["min_periods"]) if "min_periods" in kwargs and kwargs[ "min_periods"] is not None else length mamode = mamode.lower() if mamode else "sma" drift = get_drift(drift) offset = get_offset(offset) # Calculate Result hl_ratio = high_low_range / (high + low) hl_ratio *= c _lower = low * (1 - hl_ratio) _upper = high * (1 + hl_ratio) if mamode == "ema": lower = ema(_lower, length=length) mid = ema(close, length=length) upper = ema(_upper, length=length) else: # "sma" lower = sma(_lower, length=length) mid = sma(close, length=length) upper = sma(_upper, length=length) # Offset if offset != 0: lower = lower.shift(offset) mid = mid.shift(offset) upper = upper.shift(offset) # Handle fills if "fillna" in kwargs: lower.fillna(kwargs["fillna"], inplace=True) mid.fillna(kwargs["fillna"], inplace=True) upper.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: lower.fillna(method=kwargs["fill_method"], inplace=True) mid.fillna(method=kwargs["fill_method"], inplace=True) upper.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it lower.name = f"ACCBL_{length}" mid.name = f"ACCBM_{length}" upper.name = f"ACCBU_{length}" mid.category = upper.category = lower.category = "volatility" # Prepare DataFrame to return data = {lower.name: lower, mid.name: mid, upper.name: upper} accbandsdf = DataFrame(data) accbandsdf.name = f"ACCBANDS_{length}" accbandsdf.category = mid.category return accbandsdf
def aobv(close, volume, fast=None, slow=None, mamode=None, max_lookback=None, min_lookback=None, offset=None, **kwargs): """Indicator: Archer On Balance Volume (AOBV)""" # Validate arguments close = verify_series(close) volume = verify_series(volume) offset = get_offset(offset) fast = int(fast) if fast and fast > 0 else 4 slow = int(slow) if slow and slow > 0 else 12 max_lookback = int( max_lookback) if max_lookback and max_lookback > 0 else 2 min_lookback = int( min_lookback) if min_lookback and min_lookback > 0 else 2 if slow < fast: fast, slow = slow, fast mamode = mamode.upper() if mamode else None run_length = kwargs.pop("run_length", 2) # Calculate Result obv_ = obv(close=close, volume=volume, **kwargs) if mamode is None or mamode == "EMA": mamode = "EMA" maf = ema(close=obv_, length=fast, **kwargs) mas = ema(close=obv_, length=slow, **kwargs) elif mamode == "HMA": maf = hma(close=obv_, length=fast, **kwargs) mas = hma(close=obv_, length=slow, **kwargs) elif mamode == "LINREG": maf = linreg(close=obv_, length=fast, **kwargs) mas = linreg(close=obv_, length=slow, **kwargs) elif mamode == "SMA": maf = sma(close=obv_, length=fast, **kwargs) mas = sma(close=obv_, length=slow, **kwargs) elif mamode == "WMA": maf = wma(close=obv_, length=fast, **kwargs) mas = wma(close=obv_, length=slow, **kwargs) # When MAs are long and short obv_long = long_run(maf, mas, length=run_length) obv_short = short_run(maf, mas, length=run_length) # Offset if offset != 0: obv_ = obv_.shift(offset) maf = maf.shift(offset) mas = mas.shift(offset) obv_long = obv_long.shift(offset) obv_short = obv_short.shift(offset) # # Handle fills if "fillna" in kwargs: obv_.fillna(kwargs["fillna"], inplace=True) maf.fillna(kwargs["fillna"], inplace=True) mas.fillna(kwargs["fillna"], inplace=True) obv_long.fillna(kwargs["fillna"], inplace=True) obv_short.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: obv_.fillna(method=kwargs["fill_method"], inplace=True) maf.fillna(method=kwargs["fill_method"], inplace=True) mas.fillna(method=kwargs["fill_method"], inplace=True) obv_long.fillna(method=kwargs["fill_method"], inplace=True) obv_short.fillna(method=kwargs["fill_method"], inplace=True) # Prepare DataFrame to return data = { obv_.name: obv_, f"OBV_min_{min_lookback}": obv_.rolling(min_lookback).min(), f"OBV_max_{max_lookback}": obv_.rolling(max_lookback).max(), f"OBV_{maf.name}": maf, f"OBV_{mas.name}": mas, f"AOBV_LR_{run_length}": obv_long, f"AOBV_SR_{run_length}": obv_short, } aobvdf = DataFrame(data) # Name and Categorize it aobvdf.name = ( f"AOBV_{mamode}_{fast}_{slow}_{min_lookback}_{max_lookback}_{run_length}" ) aobvdf.category = "volume" return aobvdf
def macd(close, fast=None, slow=None, signal=None, talib=None, offset=None, **kwargs): """Indicator: Moving Average, Convergence/Divergence (MACD)""" # Validate arguments 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 close = verify_series(close, max(fast, slow, signal)) offset = get_offset(offset) mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return as_mode = kwargs.setdefault("asmode", False) # Calculate Result if Imports["talib"] and mode_tal: from talib import MACD macd, signalma, histogram = MACD(close, fast, slow, signal) else: fastma = ema(close, length=fast) slowma = ema(close, length=slow) macd = fastma - slowma signalma = ema(close=macd.loc[macd.first_valid_index():, ], length=signal) histogram = macd - signalma if as_mode: macd = macd - signalma signalma = ema(close=macd.loc[macd.first_valid_index():, ], 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 _asmode = "AS" if as_mode else "" _props = f"_{fast}_{slow}_{signal}" macd.name = f"MACD{_asmode}{_props}" histogram.name = f"MACD{_asmode}h{_props}" signalma.name = f"MACD{_asmode}s{_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{_asmode}{_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
def thermo(high, low, length=None, long=None, short=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: Elders Thermometer (THERMO)""" # Validate arguments high = verify_series(high) low = verify_series(low) drift = get_drift(drift) offset = get_offset(offset) length = int(length) if length and length > 0 else 20 long = float(long) if long and long > 0 else 2 short = float(short) if short and short > 0 else 0.5 mamode = mamode.lower() if mamode else "ema" asint = kwargs.pop("asint", True) # Calculate Result thermoL = (low.shift(drift) - low).abs() thermoH = (high - high.shift(drift)).abs() thermo = thermoL thermo = thermo.where(thermoH < thermoL, thermoH) thermo.index = high.index if mamode == "sma": thermo_ma = sma(thermo, length) if mamode == "hma": thermo_ma = hma(thermo, length) else: # "ema" thermo_ma = ema(thermo, length) # Create signals thermo_long = thermo < (thermo_ma * long) thermo_short = thermo > (thermo_ma * short) # Binary output, useful for signals if asint: thermo_long = thermo_long.astype(int) thermo_short = thermo_short.astype(int) # Offset if offset != 0: thermo = thermo.shift(offset) thermo_ma = thermo_ma.shift(offset) therthermo_longmo_ma = thermo_ma.shift(offset) thermo_short = thermo_ma.shift(offset) # Handle fills if "fillna" in kwargs: thermo.fillna(kwargs["fillna"], inplace=True) thermo_ma.fillna(kwargs["fillna"], inplace=True) thermo_long.fillna(kwargs["fillna"], inplace=True) thermo_short.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: thermo.fillna(method=kwargs["fill_method"], inplace=True) thermo_ma.fillna(method=kwargs["fill_method"], inplace=True) thermo_long.fillna(method=kwargs["fill_method"], inplace=True) thermo_short.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = f"_{length}_{long}_{short}" thermo.name = f"THERMO{_props}" thermo_ma.name = f"THERMOma{_props}" thermo_long.name = f"THERMOl{_props}" thermo_short.name = f"THERMOs{_props}" thermo.category = thermo_ma.category = thermo_long.category = thermo_short.category = "volatility" # Prepare Dataframe to return data = { thermo.name: thermo, thermo_ma.name: thermo_ma, thermo_long.name: thermo_long, thermo_short.name: thermo_short } df = DataFrame(data) df.name = f"THERMO{_props}" df.category = thermo.category return df
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
def tsi(close, fast=None, slow=None, signal=None, scalar=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: True Strength Index (TSI)""" # Validate Arguments fast = int(fast) if fast and fast > 0 else 13 slow = int(slow) if slow and slow > 0 else 25 signal = int(signal) if signal and signal > 0 else 13 # if slow < fast: # fast, slow = slow, fast scalar = float(scalar) if scalar else 100 close = verify_series(close, max(fast, slow)) drift = get_drift(drift) offset = get_offset(offset) mamode = mamode if isinstance(mamode, str) else "ema" if "length" in kwargs: kwargs.pop("length") if close is None: return # Calculate Result diff = close.diff(drift) slow_ema = ema(close=diff, length=slow, **kwargs) fast_slow_ema = ema(close=slow_ema, length=fast, **kwargs) abs_diff = diff.abs() abs_slow_ema = ema(close=abs_diff, length=slow, **kwargs) abs_fast_slow_ema = ema(close=abs_slow_ema, length=fast, **kwargs) tsi = scalar * fast_slow_ema / abs_fast_slow_ema tsi_signal = ma(mamode, tsi, length=signal) # Offset if offset != 0: tsi = tsi.shift(offset) tsi_signal = tsi_signal.shift(offset) # Handle fills if "fillna" in kwargs: tsi.fillna(kwargs["fillna"], inplace=True) tsi_signal.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: tsi.fillna(method=kwargs["fill_method"], inplace=True) tsi_signal.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it tsi.name = f"TSI_{fast}_{slow}_{signal}" tsi_signal.name = f"TSIs_{fast}_{slow}_{signal}" tsi.category = tsi_signal.category = "momentum" # Prepare DataFrame to return df = DataFrame({tsi.name: tsi, tsi_signal.name: tsi_signal}) df.name = f"TSI_{fast}_{slow}_{signal}" df.category = "momentum" return df
def squeeze_pro(high, low, close, bb_length=None, bb_std=None, kc_length=None, kc_scalar_wide=None, kc_scalar_normal=None, kc_scalar_narrow=None, mom_length=None, mom_smooth=None, use_tr=None, mamode=None, offset=None, **kwargs): """Indicator: Squeeze Momentum (SQZ) PRO""" # Validate arguments 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.0 kc_length = int(kc_length) if kc_length and kc_length > 0 else 20 kc_scalar_wide = float( kc_scalar_wide) if kc_scalar_wide and kc_scalar_wide > 0 else 2 kc_scalar_normal = float( kc_scalar_normal) if kc_scalar_normal and kc_scalar_normal > 0 else 1.5 kc_scalar_narrow = float( kc_scalar_narrow) if kc_scalar_narrow and kc_scalar_narrow > 0 else 1 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 _length = max(bb_length, kc_length, mom_length, mom_smooth) high = verify_series(high, _length) low = verify_series(low, _length) close = verify_series(close, _length) offset = get_offset(offset) valid_kc_scaler = kc_scalar_wide > kc_scalar_normal and kc_scalar_normal > kc_scalar_narrow if not valid_kc_scaler: return if high is None or low is None or close is None: return use_tr = kwargs.setdefault("tr", True) asint = kwargs.pop("asint", True) detailed = kwargs.pop("detailed", False) mamode = mamode if isinstance(mamode, str) else "sma" 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_wide = kc(high, low, close, length=kc_length, scalar=kc_scalar_wide, mamode=mamode, tr=use_tr) kch_normal = kc(high, low, close, length=kc_length, scalar=kc_scalar_normal, mamode=mamode, tr=use_tr) kch_narrow = kc(high, low, close, length=kc_length, scalar=kc_scalar_narrow, mamode=mamode, tr=use_tr) # Simplify KC and BBAND column names for dynamic access bbd.columns = simplify_columns(bbd) kch_wide.columns = simplify_columns(kch_wide) kch_normal.columns = simplify_columns(kch_normal) kch_narrow.columns = simplify_columns(kch_narrow) momo = mom(close, length=mom_length) if mamode.lower() == "ema": squeeze = ema(momo, length=mom_smooth) else: # "sma" squeeze = sma(momo, length=mom_smooth) # Classify Squeezes squeeze_on_wide = (bbd.l > kch_wide.l) & (bbd.u < kch_wide.u) squeeze_on_normal = (bbd.l > kch_normal.l) & (bbd.u < kch_normal.u) squeeze_on_narrow = (bbd.l > kch_narrow.l) & (bbd.u < kch_narrow.u) squeeze_off_wide = (bbd.l < kch_wide.l) & (bbd.u > kch_wide.u) no_squeeze = ~squeeze_on_wide & ~squeeze_off_wide # Offset if offset != 0: squeeze = squeeze.shift(offset) squeeze_on_wide = squeeze_on_wide.shift(offset) squeeze_on_normal = squeeze_on_normal.shift(offset) squeeze_on_narrow = squeeze_on_narrow.shift(offset) squeeze_off_wide = squeeze_off_wide.shift(offset) no_squeeze = no_squeeze.shift(offset) # Handle fills if "fillna" in kwargs: squeeze.fillna(kwargs["fillna"], inplace=True) squeeze_on_wide.fillna(kwargs["fillna"], inplace=True) squeeze_on_normal.fillna(kwargs["fillna"], inplace=True) squeeze_on_narrow.fillna(kwargs["fillna"], inplace=True) squeeze_off_wide.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_wide.fillna(method=kwargs["fill_method"], inplace=True) squeeze_on_normal.fillna(method=kwargs["fill_method"], inplace=True) squeeze_on_narrow.fillna(method=kwargs["fill_method"], inplace=True) squeeze_off_wide.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_wide}_{kc_scalar_normal}_{kc_scalar_narrow}" squeeze.name = f"SQZPRO{_props}" data = { squeeze.name: squeeze, f"SQZPRO_ON_WIDE": squeeze_on_wide.astype(int) if asint else squeeze_on_wide, f"SQZPRO_ON_NORMAL": squeeze_on_normal.astype(int) if asint else squeeze_on_normal, f"SQZPRO_ON_NARROW": squeeze_on_narrow.astype(int) if asint else squeeze_on_narrow, f"SQZPRO_OFF": squeeze_off_wide.astype(int) if asint else squeeze_off_wide, f"SQZPRO_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"SQZPRO_INC"] = sqz_inc df[f"SQZPRO_DEC"] = sqz_dec df[f"SQZPRO_PINC"] = pos_inc df[f"SQZPRO_PDEC"] = pos_dec df[f"SQZPRO_NDEC"] = neg_dec df[f"SQZPRO_NINC"] = neg_inc return df
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 qqe(close, length=None, smooth=None, factor=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: Quantitative Qualitative Estimation (QQE)""" # Validate arguments close = verify_series(close) length = int(length) if length and length > 0 else 14 smooth = int(smooth) if smooth and smooth > 0 else 5 factor = float(factor) if factor else 4.236 mamode = mamode.lower() if mamode else "ema" drift = get_drift(drift) offset = get_offset(offset) # Calculate Result rsi_ = rsi(close, length) if mamode == "hma": rsi_ma, _mode = hma(rsi_, length=smooth), "h" elif mamode == "rma": rsi_ma, _mode = rma(rsi_, length=smooth), "r" elif mamode == "sma": rsi_ma, _mode = sma(rsi_, length=smooth), "s" elif mamode == "wma": rsi_ma, _mode = wma(rsi_, length=smooth), "w" else: # "ema" rsi_ma, _mode = ema(rsi_, length=smooth), "" # RSI MA True Range rsi_ma_tr = rsi_ma.diff(drift).abs() # Double Smooth the RSI MA True Range using Wilder's Length with a default # width of 4.236. wilders_length = 2 * length - 1 smoothed_rsi_tr_ma = ema(rsi_ma_tr, length=wilders_length) dar = factor * ema(smoothed_rsi_tr_ma, length=wilders_length) # Create the Upper and Lower Bands around RSI MA. upperband = rsi_ma + dar lowerband = rsi_ma - dar m = close.size long = Series(0, index=close.index) short = Series(0, index=close.index) trend = Series(1, index=close.index) qqe = Series(rsi_ma.iloc[0], index=close.index) qqe_long = Series(npNaN, index=close.index) qqe_short = Series(npNaN, index=close.index) for i in range(1, m): c_rsi, p_rsi = rsi_ma.iloc[i], rsi_ma.iloc[i - 1] c_long, p_long = long.iloc[i - 1], long.iloc[i - 2] c_short, p_short = short.iloc[i - 1], short.iloc[i - 2] # Long Line if p_rsi > c_long and c_rsi > c_long: long.iloc[i] = npMaximum(c_long, lowerband.iloc[i]) else: long.iloc[i] = lowerband.iloc[i] # Short Line if p_rsi < c_short and c_rsi < c_short: short.iloc[i] = npMinimum(c_short, upperband.iloc[i]) else: short.iloc[i] = upperband.iloc[i] # Trend & QQE Calculation # Long: Current RSI_MA value Crosses the Prior Short Line Value # Short: Current RSI_MA Crosses the Prior Long Line Value if (c_rsi > c_short and p_rsi < p_short) or (c_rsi <= c_short and p_rsi >= p_short): trend.iloc[i] = 1 qqe.iloc[i] = qqe_long.iloc[i] = long.iloc[i] elif (c_rsi > c_long and p_rsi < p_long) or (c_rsi <= c_long and p_rsi >= p_long): trend.iloc[i] = -1 qqe.iloc[i] = qqe_short.iloc[i] = short.iloc[i] else: trend.iloc[i] = trend.iloc[i - 1] if trend.iloc[i] == 1: qqe.iloc[i] = qqe_long.iloc[i] = long.iloc[i] else: qqe.iloc[i] = qqe_short.iloc[i] = short.iloc[i] # Offset if offset != 0: rsi_ma = rsi_ma.shift(offset) qqe = qqe.shift(offset) long = long.shift(offset) short = short.shift(offset) # Handle fills if "fillna" in kwargs: rsi_ma.fillna(kwargs["fillna"], inplace=True) qqe.fillna(kwargs["fillna"], inplace=True) long.fillna(kwargs["fillna"], inplace=True) short.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: rsi_ma.fillna(method=kwargs["fill_method"], inplace=True) qqe.fillna(method=kwargs["fill_method"], inplace=True) long.fillna(method=kwargs["fill_method"], inplace=True) short.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = f"{_mode}_{length}_{smooth}_{factor}" qqe.name = f"QQE{_props}" rsi_ma.name = f"QQE{_props}_RSI{_mode.upper()}MA" long.name = f"QQEl{_props}" short.name = f"QQEs{_props}" qqe.category = rsi_ma.category = "momentum" long.category = short.category = qqe.category # Prepare DataFrame to return data = { qqe.name: qqe, rsi_ma.name: rsi_ma, # long.name: long, short.name: short long.name: qqe_long, short.name: qqe_short } df = DataFrame(data) df.name = f"QQE{_props}" df.category = qqe.category return df