def natr(high, low, close, length=None, mamode=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: Normalized Average True Range (NATR)""" # 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.lower() if mamode else "ema" scalar = float(scalar) if scalar else 100 drift = get_drift(drift) offset = get_offset(offset) # Calculate Result natr = scalar / close natr *= atr(high=high, low=low, close=close, length=length, mamode=mamode, drift=drift, offset=offset, **kwargs) # Offset if offset != 0: natr = natr.shift(offset) # Handle fills if "fillna" in kwargs: natr.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: natr.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it natr.name = f"NATR_{length}" natr.category = "volatility" return natr
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 if isinstance(mamode, str) else "ema" offset = get_offset(offset) # Calculate Result pv_diff = close.diff(drift) * volume efi = ma(mamode, pv_diff, length=length) # Offset if offset != 0: efi = efi.shift(offset) # Handle fills if "fillna" in kwargs: efi.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: efi.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it efi.name = f"EFI_{length}" efi.category = "volume" return efi
def pvt(close, volume, drift=None, offset=None, **kwargs): """Indicator: Price-Volume Trend (PVT)""" # Validate arguments close = verify_series(close) volume = verify_series(volume) drift = get_drift(drift) offset = get_offset(offset) # Calculate Result pv = roc(close=close, length=drift) * volume pvt = pv.cumsum() # Offset if offset != 0: pvt = pvt.shift(offset) # Handle fills if "fillna" in kwargs: pvt.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: pvt.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it pvt.name = f"PVT" pvt.category = "volume" return pvt
def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: Relative Strength Index (RSI)""" # Validate arguments close = verify_series(close) length = int(length) if length and length > 0 else 14 scalar = float(scalar) if scalar else 100 drift = get_drift(drift) offset = get_offset(offset) # Calculate Result negative = close.diff(drift) positive = negative.copy() positive[positive < 0] = 0 # Make negatives 0 for the postive series negative[negative > 0] = 0 # Make postives 0 for the negative series positive_avg = positive.ewm(com=length, adjust=False).mean() negative_avg = negative.ewm(com=length, adjust=False).mean().abs() rsi = scalar * positive_avg / (positive_avg + negative_avg) # Offset if offset != 0: rsi = rsi.shift(offset) # Handle fills if 'fillna' in kwargs: rsi.fillna(kwargs['fillna'], inplace=True) if 'fill_method' in kwargs: rsi.fillna(method=kwargs['fill_method'], inplace=True) # Name and Categorize it rsi.name = f"RSI_{length}" rsi.category = 'momentum' signal_indicators = kwargs.pop('signal_indicators', False) if signal_indicators: signalsdf = concat( [ DataFrame( {rsi.name: rsi} ), signals( indicator=rsi, xa=kwargs.pop('xa', 80), xb=kwargs.pop('xb', 20), 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 rsi
def cfo(close, length=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: Chande Forcast Oscillator (CFO)""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 0 else 9 scalar = float(scalar) if scalar else 100 drift = get_drift(drift) offset = get_offset(offset) # Finding linear regression of Series cfo = scalar * (close - linreg(close, length=length, tsf=True)) cfo /= close # Offset if offset != 0: cfo = cfo.shift(offset) # Handle fills if "fillna" in kwargs: cfo.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: cfo.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it cfo.name = f"CFO_{length}" cfo.category = "momentum" return cfo
def pdist(open_, high, low, close, drift=None, offset=None, **kwargs): """Indicator: Price Distance (PDIST)""" # Validate Arguments open_ = verify_series(open_) high = verify_series(high) low = verify_series(low) close = verify_series(close) drift = get_drift(drift) offset = get_offset(offset) # Calculate Result pdist = 2 * non_zero_range(high, low) pdist += non_zero_range(open_, close.shift(drift)).abs() pdist -= non_zero_range(close, open_).abs() # Offset if offset != 0: pdist = pdist.shift(offset) # Handle fills if "fillna" in kwargs: pdist.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: pdist.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category pdist.name = "PDIST" pdist.category = "volatility" return pdist
def brar(open_, high, low, close, length=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: BRAR (BRAR)""" # 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 26 scalar = float(scalar) if scalar else 100 high_open_range = non_zero_range(high, open_) open_low_range = non_zero_range(open_, low) drift = get_drift(drift) offset = get_offset(offset) # Calculate Result hcy = non_zero_range(high, close.shift(drift)) cyl = non_zero_range(close.shift(drift), low) hcy[hcy < 0] = 0 # Zero negative values cyl[cyl < 0] = 0 # "" ar = scalar * high_open_range.rolling(length).sum() ar /= open_low_range.rolling(length).sum() br = scalar * hcy.rolling(length).sum() br /= cyl.rolling(length).sum() # Offset if offset != 0: ar = ar.shift(offset) br = ar.shift(offset) # Handle fills if "fillna" in kwargs: ar.fillna(kwargs["fillna"], inplace=True) br.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ar.fillna(method=kwargs["fill_method"], inplace=True) br.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = f"_{length}" ar.name = f"AR{_props}" br.name = f"BR{_props}" ar.category = br.category = "momentum" # Prepare DataFrame to return brardf = DataFrame({ar.name: ar, br.name: br}) brardf.name = f"BRAR{_props}" brardf.category = "momentum" return brardf
def 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 mamode = mamode if isinstance(mamode, str) 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) lower = ma(mamode, _lower, length=length) mid = ma(mamode, close, length=length) upper = ma(mamode, _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 true_range(high, low, close, drift=None, offset=None, **kwargs): """Indicator: True Range""" # Validate arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) drift = get_drift(drift) offset = get_offset(offset) # Calculate Result high_low_range = non_zero_range(high, low) prev_close = close.shift(drift) ranges = [high_low_range, high - prev_close, prev_close - low] true_range = concat(ranges, axis=1) true_range = true_range.abs().max(axis=1) true_range.iloc[:drift] = npNaN # Offset if offset != 0: true_range = true_range.shift(offset) # Handle fills if "fillna" in kwargs: true_range.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: true_range.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it true_range.name = f"TRUERANGE_{drift}" true_range.category = "volatility" return true_range
def adx(high, low, close, length=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: ADX""" # Validate Arguments high = verify_series(high) low = verify_series(low) close = verify_series(close) length = length if length and length > 0 else 14 scalar = float(scalar) if scalar else 100 drift = get_drift(drift) offset = get_offset(offset) # Calculate Result atr_ = atr(high=high, low=low, close=close, length=length) up = high - high.shift(drift) # high.diff(drift) dn = low.shift(drift) - low # low.diff(-drift).shift(drift) pos = ((up > dn) & (up > 0)) * up neg = ((dn > up) & (dn > 0)) * dn pos = pos.apply(zero) neg = neg.apply(zero) k = scalar / atr_ dmp = k * rma(close=pos, length=length) dmn = k * rma(close=neg, length=length) dx = scalar * (dmp - dmn).abs() / (dmp + dmn) adx = rma(close=dx, length=length) # Offset if offset != 0: dmp = dmp.shift(offset) dmn = dmn.shift(offset) adx = adx.shift(offset) # Handle fills if "fillna" in kwargs: adx.fillna(kwargs["fillna"], inplace=True) dmp.fillna(kwargs["fillna"], inplace=True) dmn.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: adx.fillna(method=kwargs["fill_method"], inplace=True) dmp.fillna(method=kwargs["fill_method"], inplace=True) dmn.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it adx.name = f"ADX_{length}" dmp.name = f"DMP_{length}" dmn.name = f"DMN_{length}" adx.category = dmp.category = dmn.category = "trend" # Prepare DataFrame to return data = {adx.name: adx, dmp.name: dmp, dmn.name: dmn} adxdf = DataFrame(data) adxdf.name = f"ADX_{length}" adxdf.category = "trend" return adxdf
def vhf(close, length=None, drift=None, offset=None, **kwargs): """Indicator: Vertical Horizontal Filter (VHF)""" # Validate arguments length = int(length) if length and length > 0 else 28 close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) if close is None: return # Calculate Result hcp = close.rolling(length).max() lcp = close.rolling(length).min() diff = npFabs(close.diff(drift)) vhf = npFabs(non_zero_range(hcp, lcp)) / diff.rolling(length).sum() # Offset if offset != 0: vhf = vhf.shift(offset) # Handle fills if "fillna" in kwargs: vhf.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: vhf.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it vhf.name = f"VHF_{length}" vhf.category = "trend" return vhf
def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: Relative Strength Index (RSI)""" # Validate arguments length = int(length) if length and length > 0 else 14 scalar = float(scalar) if scalar else 100 close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) if close is None: return # Calculate Result negative = close.diff(drift) positive = negative.copy() positive[positive < 0] = 0 # Make negatives 0 for the postive series negative[negative > 0] = 0 # Make postives 0 for the negative series positive_avg = rma(positive, length=length) negative_avg = rma(negative, length=length) rsi = scalar * positive_avg / (positive_avg + negative_avg.abs()) # Offset if offset != 0: rsi = rsi.shift(offset) # Handle fills if "fillna" in kwargs: rsi.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: rsi.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it rsi.name = f"RSI_{length}" rsi.category = "momentum" signal_indicators = kwargs.pop("signal_indicators", False) if signal_indicators: signalsdf = concat( [ DataFrame({rsi.name: rsi}), signals( indicator=rsi, xa=kwargs.pop("xa", 80), xb=kwargs.pop("xb", 20), 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 rsi
def kvo(high, low, close, volume, fast=None, slow=None, signal=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: Klinger Volume Oscillator (KVO)""" # Validate arguments fast = int(fast) if fast and fast > 0 else 34 slow = int(slow) if slow and slow > 0 else 55 signal = int(signal) if signal and signal > 0 else 13 mamode = mamode.lower() if mamode and isinstance(mamode, str) else "ema" _length = max(fast, slow, signal) high = verify_series(high, _length) low = verify_series(low, _length) close = verify_series(close, _length) volume = verify_series(volume, _length) drift = get_drift(drift) offset = get_offset(offset) if high is None or low is None or close is None or volume is None: return # Calculate Result signed_volume = volume * signed_series(hlc3(high, low, close), 1) sv = signed_volume.loc[signed_volume.first_valid_index():, ] kvo = ma(mamode, sv, length=fast) - ma(mamode, sv, length=slow) kvo_signal = ma(mamode, kvo.loc[kvo.first_valid_index():, ], length=signal) # Offset if offset != 0: kvo = kvo.shift(offset) kvo_signal = kvo_signal.shift(offset) # Handle fills if "fillna" in kwargs: kvo.fillna(kwargs["fillna"], inplace=True) kvo_signal.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: kvo.fillna(method=kwargs["fill_method"], inplace=True) kvo_signal.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _props = f"_{fast}_{slow}_{signal}" kvo.name = f"KVO{_props}" kvo_signal.name = f"KVOs{_props}" kvo.category = kvo_signal.category = "volume" # Prepare DataFrame to return data = {kvo.name: kvo, kvo_signal.name: kvo_signal} df = DataFrame(data) df.name = f"KVO{_props}" df.category = kvo.category return df
def mfi(high, low, close, volume, length=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Money Flow Index (MFI)""" # Validate arguments length = int(length) if length and length > 0 else 14 high = verify_series(high, length) low = verify_series(low, length) close = verify_series(close, length) volume = verify_series(volume, length) drift = get_drift(drift) 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 or volume is None: return # Calculate Result if Imports["talib"] and mode_tal: from talib import MFI mfi = MFI(high, low, close, volume, length) else: typical_price = hlc3(high=high, low=low, close=close) raw_money_flow = typical_price * volume tdf = DataFrame({"diff": 0, "rmf": raw_money_flow, "+mf": 0, "-mf": 0}) tdf.loc[(typical_price.diff(drift) > 0), "diff"] = 1 tdf.loc[tdf["diff"] == 1, "+mf"] = raw_money_flow tdf.loc[(typical_price.diff(drift) < 0), "diff"] = -1 tdf.loc[tdf["diff"] == -1, "-mf"] = raw_money_flow psum = tdf["+mf"].rolling(length).sum() nsum = tdf["-mf"].rolling(length).sum() tdf["mr"] = psum / nsum mfi = 100 * psum / (psum + nsum) tdf["mfi"] = mfi # Offset if offset != 0: mfi = mfi.shift(offset) # Handle fills if "fillna" in kwargs: mfi.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: mfi.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it mfi.name = f"MFI_{length}" mfi.category = "volume" return mfi
def kvo(high, low, close, volume, fast=None, slow=None, length_sig=None, mamode=None, drift=None, offset=None, **kwargs): """Indicator: Klinger Volume Oscillator (KVO)""" # Validate arguments fast = int(fast) if fast and fast > 0 else 34 slow = int(slow) if slow and slow > 0 else 55 length_sig = int(length_sig) if length_sig and length_sig > 0 else 13 mamode = mamode.lower() if mamode and isinstance(mamode, str) else "ema" _length = max(fast, slow, length_sig) high = verify_series(high, _length) low = verify_series(low, _length) close = verify_series(close, _length) volume = verify_series(volume, _length) drift = get_drift(drift) offset = get_offset(offset) if high is None or low is None or close is None or volume is None: return # Calculate Result mom = hlc3(high, low, close).diff(drift) trend = npWhere(mom > 0, 1, 0) + npWhere(mom < 0, -1, 0) dm = non_zero_range(high, low) m = high.size cm = [0] * m for i in range(1, m): cm[i] = (cm[i - 1] + dm[i]) if trend[i] == trend[i - 1] else (dm[i - 1] + dm[i]) vf = 100 * volume * trend * abs(2 * dm / cm - 1) kvo = ma(mamode, vf, length=fast) - ma(mamode, vf, length=slow) kvo_signal = ma(mamode, kvo, length=length_sig) # Offset if offset != 0: kvo = kvo.shift(offset) kvo_signal = kvo_signal.shift(offset) # Handle fills if "fillna" in kwargs: kvo.fillna(kwargs["fillna"], inplace=True) kvo_signal.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: kvo.fillna(method=kwargs["fill_method"], inplace=True) kvo_signal.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it kvo.name = f"KVO_{fast}_{slow}" kvo_signal.name = f"KVOSig_{length_sig}" kvo.category = kvo_signal.category = "volume" # Prepare DataFrame to return data = {kvo.name: kvo, kvo_signal.name: kvo_signal} kvoandsig = DataFrame(data) kvoandsig.name = f"KVO_{fast}_{slow}_{length_sig}" kvoandsig.category = kvo.category return kvoandsig
def dm(high, low, length=None, mamode=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: DM""" # Validate Arguments length = int(length) if length and length > 0 else 14 mamode = mamode.lower() if mamode and isinstance(mamode, str) else "rma" high = verify_series(high) low = verify_series(low) drift = get_drift(drift) offset = get_offset(offset) mode_tal = bool(talib) if isinstance(talib, bool) else True if high is None or low is None: return if Imports["talib"] and mode_tal: from talib import MINUS_DM, PLUS_DM pos = PLUS_DM(high, low, length) neg = MINUS_DM(high, low, length) else: up = high - high.shift(drift) dn = low.shift(drift) - low pos_ = ((up > dn) & (up > 0)) * up neg_ = ((dn > up) & (dn > 0)) * dn pos_ = pos_.apply(zero) neg_ = neg_.apply(zero) # Not the same values as TA Lib's -+DM (Good First Issue) pos = ma(mamode, pos_, length=length) neg = ma(mamode, neg_, length=length) # Offset if offset != 0: pos = pos.shift(offset) neg = neg.shift(offset) _params = f"_{length}" data = { f"DMP{_params}": pos, f"DMN{_params}": neg, } dmdf = DataFrame(data) # print(dmdf.head(20)) # print() dmdf.name = f"DM{_params}" dmdf.category = "trend" return dmdf
def kama(close, length=None, fast=None, slow=None, drift=None, offset=None, **kwargs): """Indicator: Kaufman's Adaptive Moving Average (KAMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 fast = int(fast) if fast and fast > 0 else 2 slow = int(slow) if slow and slow > 0 else 30 close = verify_series(close, max(fast, slow, length)) drift = get_drift(drift) offset = get_offset(offset) if close is None: return # Calculate Result def weight(length: int) -> float: return 2 / (length + 1) fr = weight(fast) sr = weight(slow) abs_diff = non_zero_range(close, close.shift(length)).abs() peer_diff = non_zero_range(close, close.shift(drift)).abs() peer_diff_sum = peer_diff.rolling(length).sum() er = abs_diff / peer_diff_sum x = er * (fr - sr) + sr sc = x * x m = close.size result = [npNaN for _ in range(0, length - 1)] + [0] for i in range(length, m): result.append(sc.iloc[i] * close.iloc[i] + (1 - sc.iloc[i]) * result[i - 1]) kama = Series(result, index=close.index) # Offset if offset != 0: kama = kama.shift(offset) # Handle fills if "fillna" in kwargs: kama.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: kama.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category kama.name = f"KAMA_{length}_{fast}_{slow}" kama.category = "overlap" return kama
def er(close, length=None, drift=None, offset=None, **kwargs): """Indicator: Efficiency Ratio (ER)""" # Validate arguments length = int(length) if length and length > 0 else 10 close = verify_series(close, length) offset = get_offset(offset) drift = get_drift(drift) if close is None: return # Calculate Result abs_diff = close.diff(length).abs() abs_volatility = close.diff(drift).abs() er = abs_diff er /= abs_volatility.rolling(window=length).sum() # Offset if offset != 0: er = er.shift(offset) # Handle fills if "fillna" in kwargs: er.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: er.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it er.name = f"ER_{length}" er.category = "momentum" signal_indicators = kwargs.pop("signal_indicators", False) if signal_indicators: signalsdf = concat( [ DataFrame({er.name: er}), signals( indicator=er, xa=kwargs.pop("xa", 80), xb=kwargs.pop("xb", 20), 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 er
def decreasing(close, length=None, strict=None, asint=None, percent=None, drift=None, offset=None, **kwargs): """Indicator: Decreasing""" # Validate Arguments length = int(length) if length and length > 0 else 1 strict = strict if isinstance(strict, bool) else False asint = asint if isinstance(asint, bool) else True close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) percent = float(percent) if is_percent(percent) else False if close is None: return # Calculate Result close_ = (1 - 0.01 * percent) * close if percent else close if strict: # Returns value as float64? Have to cast to bool decreasing = close < close_.shift(drift) for x in range(3, length + 1): decreasing = decreasing & (close.shift(x - (drift + 1)) < close_.shift(x - drift)) decreasing.fillna(0, inplace=True) decreasing = decreasing.astype(bool) else: decreasing = close_.diff(length) < 0 if asint: decreasing = decreasing.astype(int) # Offset if offset != 0: decreasing = decreasing.shift(offset) # Handle fills if "fillna" in kwargs: decreasing.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: decreasing.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it _percent = f"_{0.01 * percent}" if percent else '' _props = f"{'S' if strict else ''}DEC{'p' if percent else ''}" decreasing.name = f"{_props}_{length}{_percent}" decreasing.category = "trend" return decreasing
def natr(high, low, close, length=None, scalar=None, mamode=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Normalized Average True Range (NATR)""" # Validate arguments length = int(length) if length and length > 0 else 14 mamode = mamode if isinstance(mamode, str) else "ema" scalar = float(scalar) if scalar else 100 high = verify_series(high, length) low = verify_series(low, length) close = verify_series(close, length) drift = get_drift(drift) 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 NATR natr = NATR(high, low, close, length) else: natr = scalar / close natr *= atr(high=high, low=low, close=close, length=length, mamode=mamode, drift=drift, offset=offset, **kwargs) # Offset if offset != 0: natr = natr.shift(offset) # Handle fills if "fillna" in kwargs: natr.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: natr.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it natr.name = f"NATR_{length}" natr.category = "volatility" return natr
def tsignals(trend, asbool=None, trend_reset=0, trade_offset=None, drift=None, offset=None, **kwargs): """Indicator: Trend Signals""" # Validate Arguments trend = verify_series(trend) asbool = bool(asbool) if isinstance(asbool, bool) else False trend_reset = int(trend_reset) if trend_reset and isinstance( trend_reset, int) else 0 if trade_offset != 0: trade_offset = int(trade_offset) if trade_offset and isinstance( trade_offset, int) else 0 drift = get_drift(drift) offset = get_offset(offset) # Calculate Result trends = trend.astype(int) trades = trends.diff(drift).shift(trade_offset).fillna(0).astype(int) entries = (trades > 0).astype(int) exits = (trades < 0).abs().astype(int) if asbool: trends = trends.astype(bool) entries = entries.astype(bool) exits = exits.astype(bool) data = { f"TS_Trends": trends, f"TS_Trades": trades, f"TS_Entries": entries, f"TS_Exits": exits, } df = DataFrame(data, index=trends.index) # Offset if offset != 0: df = df.shift(offset) # Handle fills if "fillna" in kwargs: df.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: df.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category df.name = f"TS" df.category = "trend" return df
def kst(close, roc1=None, roc2=None, roc3=None, roc4=None, sma1=None, sma2=None, sma3=None, sma4=None, signal=None, drift=None, offset=None, **kwargs): """Indicator: 'Know Sure Thing' (KST)""" # Validate arguments close = verify_series(close) roc1 = int(roc1) if roc1 and roc1 > 0 else 10 roc2 = int(roc2) if roc2 and roc2 > 0 else 15 roc3 = int(roc3) if roc3 and roc3 > 0 else 20 roc4 = int(roc4) if roc4 and roc4 > 0 else 30 sma1 = int(sma1) if sma1 and sma1 > 0 else 10 sma2 = int(sma2) if sma2 and sma2 > 0 else 10 sma3 = int(sma3) if sma3 and sma3 > 0 else 10 sma4 = int(sma4) if sma4 and sma4 > 0 else 15 signal = int(signal) if signal and signal > 0 else 9 drift = get_drift(drift) offset = get_offset(offset) # Calculate Result rocma1 = roc(close, roc1).rolling(sma1).mean() rocma2 = roc(close, roc2).rolling(sma2).mean() rocma3 = roc(close, roc3).rolling(sma3).mean() rocma4 = roc(close, roc4).rolling(sma4).mean() kst = 100 * (rocma1 + 2 * rocma2 + 3 * rocma3 + 4 * rocma4) kst_signal = kst.rolling(signal).mean() # Offset if offset != 0: kst = kst.shift(offset) kst_signal = kst_signal.shift(offset) # Handle fills if "fillna" in kwargs: kst.fillna(kwargs["fillna"], inplace=True) kst_signal.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: kst.fillna(method=kwargs["fill_method"], inplace=True) kst_signal.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it kst.name = f"KST_{roc1}_{roc2}_{roc3}_{roc4}_{sma1}_{sma2}_{sma3}_{sma4}" kst_signal.name = f"KSTs_{signal}" kst.category = kst_signal.category = "momentum" # Prepare DataFrame to return data = {kst.name: kst, kst_signal.name: kst_signal} kstdf = DataFrame(data) kstdf.name = f"KST_{roc1}_{roc2}_{roc3}_{roc4}_{sma1}_{sma2}_{sma3}_{sma4}_{signal}" kstdf.category = "momentum" return kstdf
def chop(high, low, close, length=None, atr_length=None, ln=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: Choppiness Index (CHOP)""" # Validate Arguments length = int(length) if length and length > 0 else 14 atr_length = int( atr_length) if atr_length is not None and atr_length > 0 else 1 ln = bool(ln) if isinstance(ln, bool) else False scalar = float(scalar) if scalar else 100 high = verify_series(high, length) low = verify_series(low, length) close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) if high is None or low is None or close is None: return # Calculate Result diff = high.rolling(length).max() - low.rolling(length).min() atr_ = atr(high=high, low=low, close=close, length=atr_length) atr_sum = atr_.rolling(length).sum() chop = scalar if ln: chop *= (npLn(atr_sum) - npLn(diff)) / npLn(length) else: chop *= (npLog10(atr_sum) - npLog10(diff)) / npLog10(length) # Offset if offset != 0: chop = chop.shift(offset) # Handle fills if "fillna" in kwargs: chop.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: chop.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it chop.name = f"CHOP{'ln' if ln else ''}_{length}_{atr_length}_{scalar}" chop.category = "trend" return chop
def mfi(high, low, close, volume, length=None, drift=None, offset=None, **kwargs): """Indicator: Money Flow Index (MFI)""" # 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 drift = get_drift(drift) offset = get_offset(offset) # Calculate Result typical_price = hlc3(high=high, low=low, close=close) raw_money_flow = typical_price * volume tdf = DataFrame({"diff": 0, "rmf": raw_money_flow, "+mf": 0, "-mf": 0}) tdf.loc[(typical_price.diff(drift) > 0), "diff"] = 1 tdf.loc[tdf["diff"] == 1, "+mf"] = raw_money_flow tdf.loc[(typical_price.diff(drift) < 0), "diff"] = -1 tdf.loc[tdf["diff"] == -1, "-mf"] = raw_money_flow psum = tdf["+mf"].rolling(length).sum() nsum = tdf["-mf"].rolling(length).sum() tdf["mr"] = psum / nsum mfi = 100 * psum / (psum + nsum) tdf["mfi"] = mfi # Offset if offset != 0: mfi = mfi.shift(offset) # Handle fills if "fillna" in kwargs: mfi.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: mfi.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it mfi.name = f"MFI_{length}" mfi.category = "volume" return mfi
def trix(close, length=None, signal=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: Trix (TRIX)""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 0 else 30 signal = int(signal) if signal and signal > 0 else 9 scalar = float(scalar) if scalar else 100 min_periods = int( kwargs['min_periods']) if 'min_periods' in kwargs and kwargs[ 'min_periods'] is not None else length drift = get_drift(drift) offset = get_offset(offset) # Calculate Result ema1 = ema(close=close, length=length, **kwargs) ema2 = ema(close=ema1, length=length, **kwargs) ema3 = ema(close=ema2, length=length, **kwargs) trix = scalar * ema3.pct_change(drift) trix_signal = trix.rolling(signal).mean() # Offset if offset != 0: trix = trix.shift(offset) trix_signal = trix_signal.shift(offset) # Handle fills if 'fillna' in kwargs: trix.fillna(kwargs['fillna'], inplace=True) trix_signal.fillna(kwargs['fillna'], inplace=True) if 'fill_method' in kwargs: trix.fillna(method=kwargs['fill_method'], inplace=True) trix_signal.fillna(method=kwargs['fill_method'], inplace=True) # Name & Category trix.name = f"TRIX_{length}_{signal}" trix_signal.name = f"TRIXs_{length}_{signal}" trix.category = trix_signal.category = "momentum" # Prepare DataFrame to return df = DataFrame({trix.name: trix, trix_signal.name: trix_signal}) df.name = f"TRIX_{length}_{signal}" df.category = "momentum" return df
def vortex(high, low, close, length=None, drift=None, offset=None, **kwargs): """Indicator: Vortex""" # Validate arguments length = length if length and length > 0 else 14 min_periods = int( kwargs["min_periods"]) if "min_periods" in kwargs and kwargs[ "min_periods"] is not None else length _length = max(length, min_periods) high = verify_series(high, _length) low = verify_series(low, _length) close = verify_series(close, _length) drift = get_drift(drift) offset = get_offset(offset) if high is None or low is None or close is None: return # Calculate Result tr = true_range(high=high, low=low, close=close) tr_sum = tr.rolling(length, min_periods=min_periods).sum() vmp = (high - low.shift(drift)).abs() vmm = (low - high.shift(drift)).abs() vip = vmp.rolling(length, min_periods=min_periods).sum() / tr_sum vim = vmm.rolling(length, min_periods=min_periods).sum() / tr_sum # Offset if offset != 0: vip = vip.shift(offset) vim = vim.shift(offset) # Handle fills if "fillna" in kwargs: vip.fillna(kwargs["fillna"], inplace=True) vim.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: vip.fillna(method=kwargs["fill_method"], inplace=True) vim.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it vip.name = f"VTXP_{length}" vim.name = f"VTXM_{length}" vip.category = vim.category = "trend" # Prepare DataFrame to return data = {vip.name: vip, vim.name: vim} vtxdf = DataFrame(data) vtxdf.name = f"VTX_{length}" vtxdf.category = "trend" return vtxdf
def cmo(close, length=None, scalar=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Chande Momentum Oscillator (CMO)""" # Validate Arguments length = int(length) if length and length > 0 else 14 scalar = float(scalar) if scalar else 100 close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) mode_tal = bool(talib) if isinstance(talib, bool) else True if close is None: return # Calculate Result if Imports["talib"] and mode_tal: from talib import CMO cmo = CMO(close, length) else: mom = close.diff(drift) positive = mom.copy().clip(lower=0) negative = mom.copy().clip(upper=0).abs() if mode_tal: pos_ = rma(positive, length) neg_ = rma(negative, length) else: pos_ = positive.rolling(length).sum() neg_ = negative.rolling(length).sum() cmo = scalar * (pos_ - neg_) / (pos_ + neg_) # Offset if offset != 0: cmo = cmo.shift(offset) # Handle fills if "fillna" in kwargs: cmo.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: cmo.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it cmo.name = f"CMO_{length}" cmo.category = "momentum" return cmo
def atr(high, low, close, length=None, mamode=None, talib=None, drift=None, offset=None, **kwargs): """Indicator: Average True Range (ATR)""" # Validate arguments length = int(length) if length and length > 0 else 14 mamode = mamode.lower() if mamode and isinstance(mamode, str) else "rma" high = verify_series(high, length) low = verify_series(low, length) close = verify_series(close, length) drift = get_drift(drift) 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 ATR atr = ATR(high, low, close, length) else: tr = true_range(high=high, low=low, close=close, drift=drift) atr = ma(mamode, 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{mamode[0]}_{length}{'p' if percentage else ''}" atr.category = "volatility" return atr
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 vidya(close, length=None, drift=None, offset=None, **kwargs): """Indicator: Variable Index Dynamic Average (VIDYA)""" # Validate Arguments length = int(length) if length and length > 0 else 14 close = verify_series(close, length) drift = get_drift(drift) offset = get_offset(offset) if close is None: return def _cmo(source: Series, n: int, d: int): """Chande Momentum Oscillator (CMO) Patch For some reason: from pandas_ta.momentum import cmo causes pandas_ta.momentum.coppock to not be able to import it's wma like from pandas_ta.overlap import wma? Weird Circular TypeError!?! """ mom = source.diff(d) positive = mom.copy().clip(lower=0) negative = mom.copy().clip(upper=0).abs() pos_sum = positive.rolling(n).sum() neg_sum = negative.rolling(n).sum() return (pos_sum - neg_sum) / (pos_sum + neg_sum) # Calculate Result m = close.size alpha = 2 / (length + 1) abs_cmo = _cmo(close, length, drift).abs() vidya = Series(0, index=close.index) for i in range(length, m): vidya.iloc[i] = alpha * abs_cmo.iloc[i] * close.iloc[i] + vidya.iloc[ i - 1] * (1 - alpha * abs_cmo.iloc[i]) vidya.replace({0: npNaN}, inplace=True) # Offset if offset != 0: vidya = vidya.shift(offset) # Handle fills if "fillna" in kwargs: vidya.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: vidya.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category vidya.name = f"VIDYA_{length}" vidya.category = "overlap" return vidya