def ao(high, low, fast=None, slow=None, offset=None, **kwargs): """Indicator: Awesome Oscillator (AO)""" # Validate Arguments high = verify_series(high) low = verify_series(low) fast = int(fast) if fast and fast > 0 else 5 slow = int(slow) if slow and slow > 0 else 34 if slow < fast: fast, slow = slow, fast offset = get_offset(offset) # Calculate Result median_price = 0.5 * (high + low) fast_sma = sma(median_price, fast) slow_sma = sma(median_price, slow) ao = fast_sma - slow_sma # Offset if offset != 0: ao = ao.shift(offset) # Handle fills if "fillna" in kwargs: ao.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ao.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it ao.name = f"AO_{fast}_{slow}" ao.category = "momentum" return ao
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 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 apo(close, fast=None, slow=None, offset=None, **kwargs): """Indicator: Absolute Price Oscillator (APO)""" # 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 if slow < fast: fast, slow = slow, fast offset = get_offset(offset) # Calculate Result 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 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 stoch(high, low, close, k=None, d=None, smooth_k=None, offset=None, **kwargs): """Indicator: Stochastic Oscillator (STOCH)""" # Validate arguments k = k if k and k > 0 else 14 d = d if d and d > 0 else 3 smooth_k = smooth_k if smooth_k and smooth_k > 0 else 3 _length = max(k, d, smooth_k) 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(k).min() highest_high = high.rolling(k).max() stoch = 100 * (close - lowest_low) stoch /= non_zero_range(highest_high, lowest_low) stoch_k = sma(stoch, length=smooth_k) stoch_d = sma(stoch_k, length=d) # Offset if offset != 0: stoch_k = stoch_k.shift(offset) stoch_d = stoch_d.shift(offset) # Handle fills if "fillna" in kwargs: stoch_k.fillna(kwargs["fillna"], inplace=True) stoch_d.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: stoch_k.fillna(method=kwargs["fill_method"], inplace=True) stoch_d.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _name = "STOCH" _props = f"_{k}_{d}_{smooth_k}" stoch_k.name = f"{_name}k{_props}" stoch_d.name = f"{_name}d{_props}" stoch_k.category = stoch_d.category = "momentum" # Prepare DataFrame to return data = {stoch_k.name: stoch_k, stoch_d.name: stoch_d} df = DataFrame(data) df.name = f"{_name}{_props}" df.category = stoch_k.category return df
def stochrsi(close, length=None, rsi_length=None, k=None, d=None, offset=None, **kwargs): """Indicator: Stochastic RSI Oscillator (STOCHRSI)""" # Validate arguments length = length if length and length > 0 else 14 rsi_length = rsi_length if rsi_length and rsi_length > 0 else 14 k = k if k and k > 0 else 3 d = d if d and d > 0 else 3 close = verify_series(close, max(length, rsi_length, k, d)) offset = get_offset(offset) if close is None: return # Calculate Result rsi_ = rsi(close, length=rsi_length) lowest_rsi = rsi_.rolling(length).min() highest_rsi = rsi_.rolling(length).max() stoch = 100 * (rsi_ - lowest_rsi) stoch /= non_zero_range(highest_rsi, lowest_rsi) stochrsi_k = sma(stoch, length=k) stochrsi_d = sma(stochrsi_k, length=d) # Offset if offset != 0: stochrsi_k = stochrsi_k.shift(offset) stochrsi_d = stochrsi_d.shift(offset) # Handle fills if "fillna" in kwargs: stochrsi_k.fillna(kwargs["fillna"], inplace=True) stochrsi_d.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: stochrsi_k.fillna(method=kwargs["fill_method"], inplace=True) stochrsi_d.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _name = "STOCHRSI" _props = f"_{length}_{rsi_length}_{k}_{d}" stochrsi_k.name = f"{_name}k{_props}" stochrsi_d.name = f"{_name}d{_props}" stochrsi_k.category = stochrsi_d.category = "momentum" # Prepare DataFrame to return data = {stochrsi_k.name: stochrsi_k, stochrsi_d.name: stochrsi_d} df = DataFrame(data) df.name = f"{_name}{_props}" df.category = stochrsi_k.category return df
def cci(high, low, close, length=None, c=None, offset=None, **kwargs): """Indicator: Commodity Channel Index (CCI)""" # Validate Arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) length = int(length) if length and length > 0 else 14 c = float(c) if c and c > 0 else 0.015 offset = get_offset(offset) # Calculate Result typical_price = hlc3(high=high, low=low, close=close) mean_typical_price = sma(typical_price, length=length) mad_typical_price = mad(typical_price, length=length) cci = typical_price - mean_typical_price cci /= c * mad_typical_price # Offset if offset != 0: cci = cci.shift(offset) # Handle fills if "fillna" in kwargs: cci.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: cci.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it cci.name = f"CCI_{length}_{c}" cci.category = "momentum" return cci
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) # Handle fills if "fillna" in kwargs: zscore.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: zscore.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category zscore.name = f"Z_{length}" zscore.category = "statistics" return zscore
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 dpo(close, length=None, centered=True, offset=None, **kwargs): """Indicator: Detrend Price Oscillator (DPO)""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 0 else 20 offset = get_offset(offset) # Calculate Result t = int(0.5 * length) + 1 ma = sma(close, length) dpo = close - ma.shift(t) if centered: dpo = (close.shift(t) - ma).shift(-t) # Offset if offset != 0: dpo = dpo.shift(offset) # Handle fills if "fillna" in kwargs: dpo.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: dpo.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it dpo.name = f"DPO_{length}" dpo.category = "trend" return dpo
def ni(close, length=None, centered=False, offset=None, **kwargs): """ Example indicator ni """ # Validate Arguments length = int(length) if length and length > 0 else 20 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result t = int(0.5 * length) + 1 ma = sma(close, length) ni = close - ma.shift(t) if centered: ni = (close.shift(t) - ma).shift(-t) # Offset if offset != 0: ni = ni.shift(offset) # Handle fills if "fillna" in kwargs: ni.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ni.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it ni.name = f"ni_{length}" ni.category = "trend" return ni
def aberration(high, low, close, length=None, atr_length=None, offset=None, **kwargs): """Indicator: Aberration (ABER)""" # Validate arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) length = int(length) if length and length > 0 else 5 atr_length = int(atr_length) if atr_length and atr_length > 0 else 15 offset = get_offset(offset) # Calculate Result atr_ = atr(high=high, low=low, close=close, length=atr_length) jg = hlc3(high=high, low=low, close=close) zg = sma(jg, length) sg = zg + atr_ xg = zg - atr_ # Offset if offset != 0: zg = zg.shift(offset) sg = sg.shift(offset) xg = xg.shift(offset) atr_ = atr_.shift(offset) # Handle fills if "fillna" in kwargs: zg.fillna(kwargs["fillna"], inplace=True) sg.fillna(kwargs["fillna"], inplace=True) xg.fillna(kwargs["fillna"], inplace=True) atr_.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: zg.fillna(method=kwargs["fill_method"], inplace=True) sg.fillna(method=kwargs["fill_method"], inplace=True) xg.fillna(method=kwargs["fill_method"], inplace=True) atr_.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = f"_{length}_{atr_length}" zg.name = f"ABER_ZG{_props}" sg.name = f"ABER_SG{_props}" xg.name = f"ABER_XG{_props}" atr_.name = f"ABER_ATR{_props}" zg.category = sg.category = "volatility" xg.category = atr_.category = zg.category # Prepare DataFrame to return data = {zg.name: zg, sg.name: sg, xg.name: xg, atr_.name: atr_} aberdf = DataFrame(data) aberdf.name = f"ABER{_props}" aberdf.category = zg.category return aberdf
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 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 cdl_doji( open_, high, low, close, length=None, factor=None, scalar=None, asint=True, offset=None, **kwargs, ): """Candle Type: Doji""" # Validate Arguments open_ = verify_series(open_) high = verify_series(high) low = verify_series(low) close = verify_series(close) length = int(length) if length and length > 0 else 10 factor = float(factor) if is_percent(factor) else 10 scalar = float(scalar) if scalar else 100 offset = get_offset(offset) naive = kwargs.pop("naive", False) # Calculate Result body = real_body(open_, close).abs() hl_range = high_low_range(high, low).abs() hl_range_avg = sma(hl_range, length) doji = body < 0.01 * factor * hl_range_avg if naive: doji.iloc[:length] = body < 0.01 * factor * hl_range if asint: doji = scalar * doji.astype(int) # Offset if offset != 0: doji = doji.shift(offset) # Handle fills if "fillna" in kwargs: doji.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: doji.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it doji.name = f"CDL_DOJI_{length}_{0.01 * factor}" doji.category = "candles" return doji
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 cci(high, low, close, length=None, c=None, talib=None, offset=None, **kwargs): """Indicator: Commodity Channel Index (CCI)""" # Validate Arguments length = int(length) if length and length > 0 else 14 c = float(c) if c and c > 0 else 0.015 high = verify_series(high, length) low = verify_series(low, length) close = verify_series(close, length) offset = get_offset(offset) mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None or close is None: return # Calculate Result if Imports["talib"] and mode_tal: from talib import CCI cci = CCI(high, low, close, length) else: typical_price = hlc3(high=high, low=low, close=close) mean_typical_price = sma(typical_price, length=length) mad_typical_price = mad(typical_price, length=length) cci = typical_price - mean_typical_price cci /= c * mad_typical_price # Offset if offset != 0: cci = cci.shift(offset) # Handle fills if "fillna" in kwargs: cci.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: cci.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it cci.name = f"CCI_{length}_{c}" cci.category = "momentum" return cci
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) - 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 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 zscore(close, length=None, std=None, offset=None, **kwargs): """Indicator: Z Score""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 1 else 30 std = float(std) if std and std > 1 else 1 offset = get_offset(offset) # 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 ui(close, length=None, scalar=None, offset=None, **kwargs): """Indicator: Ulcer Index (UI)""" # Validate arguments length = int(length) if length and length > 0 else 14 scalar = float(scalar) if scalar and scalar > 0 else 100 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # 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 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 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 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 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
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 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 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