def downside_deviation(returns: Series, benchmark_rate: float = 0.0, tf: str = "years") -> float: """Downside Deviation for the Sortino ratio. Benchmark rate is assumed to be annualized. Adjusted according for the number of periods per year seen in the data. Args: close (pd.Series): Series of 'close's benchmark_rate (float): Benchmark Rate to use. Default: 0.0 tf (str): Time Frame options: 'days', 'weeks', 'months', and 'years'. Default: 'years' >>> result = ta.downside_deviation(returns, benchmark_rate=0.0, tf="years") """ # For both de-annualizing the benchmark rate and annualizing result returns = verify_series(returns) days_per_year = returns.shape[0] / total_time(returns, tf) adjusted_benchmark_rate = ((1 + benchmark_rate)**(1 / days_per_year)) - 1 downside = adjusted_benchmark_rate - returns downside_sum_of_squares = (downside[downside > 0]**2).sum() downside_deviation = npSqrt(downside_sum_of_squares / (returns.shape[0] - 1)) return downside_deviation * npSqrt(days_per_year)
def ssf(close, length=None, poles=None, offset=None, **kwargs): """Indicator: Ehler's Super Smoother Filter (SSF)""" # Validate Arguments length = int(length) if length and length > 0 else 10 poles = int(poles) if poles in [2, 3] else 2 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result m = close.size ssf = close.copy() if poles == 3: x = npPi / length # x = PI / n a0 = npExp(-x) # e^(-x) b0 = 2 * a0 * npCos(npSqrt(3) * x) # 2e^(-x)*cos(3^(.5) * x) c0 = a0 * a0 # e^(-2x) c4 = c0 * c0 # e^(-4x) c3 = -c0 * (1 + b0) # -e^(-2x) * (1 + 2e^(-x)*cos(3^(.5) * x)) c2 = c0 + b0 # e^(-2x) + 2e^(-x)*cos(3^(.5) * x) c1 = 1 - c2 - c3 - c4 for i in range(0, m): ssf.iloc[i] = c1 * close.iloc[i] + c2 * ssf.iloc[ i - 1] + c3 * ssf.iloc[i - 2] + c4 * ssf.iloc[i - 3] else: # poles == 2 x = npPi * npSqrt(2) / length # x = PI * 2^(.5) / n a0 = npExp(-x) # e^(-x) a1 = -a0 * a0 # -e^(-2x) b1 = 2 * a0 * npCos(x) # 2e^(-x)*cos(x) c1 = 1 - a1 - b1 # e^(-2x) - 2e^(-x)*cos(x) + 1 for i in range(0, m): ssf.iloc[i] = c1 * close.iloc[i] + b1 * ssf.iloc[ i - 1] + a1 * ssf.iloc[i - 2] # Offset if offset != 0: ssf = ssf.shift(offset) # Handle fills if "fillna" in kwargs: ssf.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ssf.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category ssf.name = f"SSF_{length}_{poles}" ssf.category = "overlap" return ssf
def downside_deviation(returns: Series, benchmark_rate: float = 0.0, log: bool = False, tf: str = "years") -> float: """Downside Deviation for the Sortino ratio. Benchmark rate is assumed to be annualized. Adjusted according for the number of periods per year seen in the data.""" # For both de-annualizing the benchmark rate and annualizing result returns = verify_series(returns) days_per_year = returns.shape[0] / total_time(returns, tf) adjusted_benchmark_rate = ((1 + benchmark_rate) ** (1 / days_per_year)) - 1 downside = adjusted_benchmark_rate - returns downside_sum_of_squares = (downside[downside > 0] ** 2).sum() downside_deviation = npSqrt(downside_sum_of_squares / (returns.shape[0] - 1)) return downside_deviation * npSqrt(days_per_year)
def _linear_regression_np(x: Series, y: Series) -> dict: """Simple Linear Regression in Numpy for two 1d arrays for environments without the sklearn package.""" result = {"a": npNaN, "b": npNaN, "r": npNaN, "t": npNaN, "line": npNaN} x_sum = x.sum() y_sum = y.sum() if int(x_sum) != 0: # 1st row, 2nd col value corr(x, y) r = npCorrcoef(x, y)[0, 1] m = x.size r_mix = m * (x * y).sum() - x_sum * y_sum b = r_mix // (m * (x * x).sum() - x_sum * x_sum) a = y.mean() - b * x.mean() line = a + b * x _np_err = seterr() seterr(divide="ignore", invalid="ignore") result = { "a": a, "b": b, "r": r, "t": r / npSqrt((1 - r * r) / (m - 2)), "line": line, } seterr(divide=_np_err["divide"], invalid=_np_err["invalid"]) return result
def hma(close, length=None, offset=None, **kwargs): """Indicator: Hull Moving Average (HMA)""" # Validate Arguments length = int(length) if length and length > 0 else 10 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # Calculate Result half_length = int(length / 2) sqrt_length = int(npSqrt(length)) wmaf = wma(close=close, length=half_length) wmas = wma(close=close, length=length) hma = wma(close=2 * wmaf - wmas, length=sqrt_length) # Offset if offset != 0: hma = hma.shift(offset) # Handle fills if "fillna" in kwargs: hma.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: hma.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category hma.name = f"HMA_{length}" hma.category = "overlap" return hma
def sharpe_ratio(close: Series, benchmark_rate: float = 0.0, log: bool = False, use_cagr: bool = False, period: int = RATE["TRADING_DAYS_PER_YEAR"]) -> float: """Sharpe Ratio of a series. Args: close (pd.Series): Series of 'close's benchmark_rate (float): Benchmark Rate to use. Default: 0.0 log (bool): If True, calculates log_return. Otherwise it returns percent_return. Default: False use_cagr (bool): Use cagr - benchmark_rate instead. Default: False period (int, float): Period to use to calculate Mean Annual Return and Annual Standard Deviation. Default: RATE["TRADING_DAYS_PER_YEAR"] (currently 252) >>> result = ta.sharpe_ratio(close, benchmark_rate=0.0, log=False) """ close = verify_series(close) returns = percent_return(close=close) if not log else log_return( close=close) if use_cagr: return cagr(close) / volatility(close, returns, log=log) else: period_mu = period * returns.mean() period_std = npSqrt(period) * returns.std() return (period_mu - benchmark_rate) / period_std
def optimal_leverage(close: Series, benchmark_rate: float = 0.0, period: Tuple[float, int] = RATE["TRADING_DAYS_PER_YEAR"], log: bool = False, capital: float = 1., **kwargs) -> float: """Optimal Leverage of a series. NOTE: Incomplete. Do NOT use. Args: close (pd.Series): Series of 'close's benchmark_rate (float): Benchmark Rate to use. Default: 0.0 period (int, float): Period to use to calculate Mean Annual Return and Annual Standard Deviation. Default: None or the default sharpe_ratio.period() log (bool): If True, calculates log_return. Otherwise it returns percent_return. Default: False >>> result = ta.optimal_leverage(close, benchmark_rate=0.0, log=False) """ close = verify_series(close) use_cagr = kwargs.pop("use_cagr", False) returns = percent_return(close=close) if not log else log_return( close=close) # sharpe = sharpe_ratio(close, benchmark_rate=benchmark_rate, log=log, use_cagr=use_cagr, period=period) period_mu = period * returns.mean() period_std = npSqrt(period) * returns.std() mean_excess_return = period_mu - benchmark_rate # sharpe = mean_excess_return / period_std opt_leverage = (period_std**-2) * mean_excess_return amount = int(capital * opt_leverage) return amount
def volatility(close: Series, tf:str = "years", returns:bool = False, log: bool = False, **kwargs) -> float: """Volatility of a series. Default: 'years'""" close = verify_series(close) if not returns: returns = percent_return(close=close) if not log else log_return(close=close) factor = returns.shape[0] / total_time(returns, tf) if kwargs.pop("nearest_day", False) and tf.lower() == "years": factor = int(factor + 1) return returns.std() * npSqrt(factor)
def _linear_regression_sklearn(x, y): """Simple Linear Regression in Scikit Learn for two 1d arrays for environments with the sklearn package.""" from sklearn.linear_model import LinearRegression regression = LinearRegression().fit(DataFrame(x), y=y) r = regression.score(DataFrame(x), y=y) a, b = regression.intercept_, regression.coef_[0] return { "a": a, "b": b, "r": r, "t": r / npSqrt((1 - r * r) / (x.size - 2)), "line": a + b * x }
def _linear_regression_sklearn(x: Series, y: Series) -> dict: """Simple Linear Regression in Scikit Learn for two 1d arrays for environments with the sklearn package.""" from sklearn.linear_model import LinearRegression X = DataFrame(x) lr = LinearRegression().fit(X, y=y) r = lr.score(X, y=y) a, b = lr.intercept_, lr.coef_[0] result = { "a": a, "b": b, "r": r, "t": r / npSqrt((1 - r * r) / (x.size - 2)), "line": a + b * x } return result
def _linear_regression_np(x: Series, y: Series) -> dict: """Simple Linear Regression in Numpy for two 1d arrays for environments without the sklearn package.""" m = x.size x_sum = x.sum() y_sum = y.sum() # 1st row, 2nd col value corr(x, y) r = npCorrcoef(x, y)[0,1] r_mixture = m * (x * y).sum() - x_sum * y_sum b = r_mixture / (m * (x * x).sum() - x_sum * x_sum) a = y.mean() - b * x.mean() line = a + b * x # seterr(divide="ignore", invalid="ignore") return { "a": a, "b": b, "r": r, "t": r / npSqrt((1 - r * r) / (m - 2)), "line": line }
def linear_regression(series): y_sum = series.sum() xy_sum = (x * series).sum() m = (length * xy_sum - x_sum * y_sum) / divisor if slope: return m b = (y_sum * x2_sum - x_sum * xy_sum) / divisor if intercept: return b if angle: theta = npAtan(m) if degrees: theta *= 180 / npPi return theta if r: y2_sum = (series * series).sum() rn = length * xy_sum - x_sum * y_sum rd = npSqrt(divisor * (length * y2_sum - y_sum * y_sum)) return rn / rd return m * length + b if tsf else m * (length - 1) + b
def ebsw(close, length=None, bars=None, offset=None, **kwargs): """Indicator: Even Better SineWave (EBSW)""" # Validate arguments length = int(length) if length and length > 38 else 40 bars = int(bars) if bars and bars > 0 else 10 close = verify_series(close, length) offset = get_offset(offset) if close is None: return # variables alpha1 = HP = 0 # alpha and HighPass a1 = b1 = c1 = c2 = c3 = 0 Filt = Pwr = Wave = 0 lastClose = lastHP = 0 FilterHist = [0, 0] # Filter history # Calculate Result m = close.size result = [npNaN for _ in range(0, length - 1)] + [0] for i in range(length, m): # HighPass filter cyclic components whose periods are shorter than Duration input alpha1 = (1 - npSin(360 / length)) / npCos(360 / length) HP = 0.5 * (1 + alpha1) * (close[i] - lastClose) + alpha1 * lastHP # Smooth with a Super Smoother Filter from equation 3-3 a1 = npExp(-npSqrt(2) * npPi / bars) b1 = 2 * a1 * npCos(npSqrt(2) * 180 / bars) c2 = b1 c3 = -1 * a1 * a1 c1 = 1 - c2 - c3 Filt = c1 * (HP + lastHP) / 2 + c2 * FilterHist[1] + c3 * FilterHist[0] # Filt = float("{:.8f}".format(float(Filt))) # to fix for small scientific notations, the big ones fail # 3 Bar average of Wave amplitude and power Wave = (Filt + FilterHist[1] + FilterHist[0]) / 3 Pwr = (Filt * Filt + FilterHist[1] * FilterHist[1] + FilterHist[0] * FilterHist[0]) / 3 # Normalize the Average Wave to Square Root of the Average Power Wave = Wave / npSqrt(Pwr) # update storage, result FilterHist.append(Filt) # append new Filt value FilterHist.pop( 0) # remove first element of list (left) -> updating/trim lastHP = HP lastClose = close[i] result.append(Wave) ebsw = Series(result, index=close.index) # Offset if offset != 0: ebsw = ebsw.shift(offset) # Handle fills if "fillna" in kwargs: ebsw.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: ebsw.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it ebsw.name = f"EBSW_{length}_{bars}" ebsw.category = "cycles" return ebsw
def hwc(close, na=None, nb=None, nc=None, nd=None, scalar=None, channel_eval=None, offset=None, **kwargs): """Indicator: Holt-Winter Channel""" # Validate Arguments na = float(na) if na and na > 0 else 0.2 nb = float(nb) if nb and nb > 0 else 0.1 nc = float(nc) if nc and nc > 0 else 0.1 nd = float(nd) if nd and nd > 0 else 0.1 scalar = float(scalar) if scalar and scalar > 0 else 1 channel_eval = bool( channel_eval) if channel_eval and channel_eval else False close = verify_series(close) offset = get_offset(offset) # Calculate Result last_a = last_v = last_var = 0 last_f = last_price = last_result = close[0] lower, result, upper = [], [], [] chan_pct_width, chan_width = [], [] m = close.size for i in range(m): F = (1.0 - na) * (last_f + last_v + 0.5 * last_a) + na * close[i] V = (1.0 - nb) * (last_v + last_a) + nb * (F - last_f) A = (1.0 - nc) * last_a + nc * (V - last_v) result.append((F + V + 0.5 * A)) var = (1.0 - nd) * last_var + nd * (last_price - last_result) * ( last_price - last_result) stddev = npSqrt(last_var) upper.append(result[i] + scalar * stddev) lower.append(result[i] - scalar * stddev) if channel_eval: # channel width chan_width.append(upper[i] - lower[i]) # channel percentage price position chan_pct_width.append( (close[i] - lower[i]) / (upper[i] - lower[i])) # print('channel_eval (width|percentageWidth):', chan_width[i], chan_pct_width[i]) # update values last_price = close[i] last_a = A last_f = F last_v = V last_var = var last_result = result[i] # Aggregate hwc = Series(result, index=close.index) hwc_upper = Series(upper, index=close.index) hwc_lower = Series(lower, index=close.index) if channel_eval: hwc_width = Series(chan_width, index=close.index) hwc_pctwidth = Series(chan_pct_width, index=close.index) # Offset if offset != 0: hwc = hwc.shift(offset) hwc_upper = hwc_upper.shift(offset) hwc_lower = hwc_lower.shift(offset) if channel_eval: hwc_width = hwc_width.shift(offset) hwc_pctwidth = hwc_pctwidth.shift(offset) # Handle fills if "fillna" in kwargs: hwc.fillna(kwargs["fillna"], inplace=True) hwc_upper.fillna(kwargs["fillna"], inplace=True) hwc_lower.fillna(kwargs["fillna"], inplace=True) if channel_eval: hwc_width.fillna(kwargs["fillna"], inplace=True) hwc_pctwidth.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: hwc.fillna(method=kwargs["fill_method"], inplace=True) hwc_upper.fillna(method=kwargs["fill_method"], inplace=True) hwc_lower.fillna(method=kwargs["fill_method"], inplace=True) if channel_eval: hwc_width.fillna(method=kwargs["fill_method"], inplace=True) hwc_pctwidth.fillna(method=kwargs["fill_method"], inplace=True) # Name and Categorize it # suffix = f'{str(na).replace(".", "")}-{str(nb).replace(".", "")}-{str(nc).replace(".", "")}' hwc.name = 'HW-MID' hwc_upper.name = "HW-UPPER" hwc_lower.name = "HW-LOWER" hwc.category = hwc_upper.category = hwc_lower.category = "volatility" if channel_eval: hwc_width.name = 'HW-WIDTH' hwc_pctwidth.name = 'HW-PCTW' # Prepare DataFrame to return if channel_eval: data = { hwc.name: hwc, hwc_upper.name: hwc_upper, hwc_lower.name: hwc_lower, hwc_width.name: hwc_width, hwc_pctwidth.name: hwc_pctwidth } df = DataFrame(data) df.name = "hwc" df.category = hwc.category else: data = { hwc.name: hwc, hwc_upper.name: hwc_upper, hwc_lower.name: hwc_lower } df = DataFrame(data) df.name = "hwc" df.category = hwc.category return df
def jma(close, length=None, phase=None, offset=None, **kwargs): """Indicator: Jurik Moving Average (JMA)""" # Validate Arguments _length = int(length) if length and length > 0 else 7 phase = float(phase) if phase and phase != 0 else 0 close = verify_series(close, _length) offset = get_offset(offset) if close is None: return # Define base variables jma = npZeroslike(close) volty = npZeroslike(close) v_sum = npZeroslike(close) kv = det0 = det1 = ma2 = 0.0 jma[0] = ma1 = uBand = lBand = close[0] # Static variables sum_length = 10 length = 0.5 * (_length - 1) pr = 0.5 if phase < -100 else 2.5 if phase > 100 else 1.5 + phase * 0.01 length1 = max((npLog(npSqrt(length)) / npLog(2.0)) + 2.0, 0) pow1 = max(length1 - 2.0, 0.5) length2 = length1 * npSqrt(length) bet = length2 / (length2 + 1) beta = 0.45 * (_length - 1) / (0.45 * (_length - 1) + 2.0) m = close.shape[0] for i in range(1, m): price = close[i] # Price volatility del1 = price - uBand del2 = price - lBand volty[i] = max(abs(del1),abs(del2)) if abs(del1)!=abs(del2) else 0 # Relative price volatility factor v_sum[i] = v_sum[i - 1] + (volty[i] - volty[max(i - sum_length, 0)]) / sum_length avg_volty = npAverage(v_sum[max(i - 65, 0):i + 1]) d_volty = 0 if avg_volty ==0 else volty[i] / avg_volty r_volty = max(1.0, min(npPower(length1, 1 / pow1), d_volty)) # Jurik volatility bands pow2 = npPower(r_volty, pow1) kv = npPower(bet, npSqrt(pow2)) uBand = price if (del1 > 0) else price - (kv * del1) lBand = price if (del2 < 0) else price - (kv * del2) # Jurik Dynamic Factor power = npPower(r_volty, pow1) alpha = npPower(beta, power) # 1st stage - prelimimary smoothing by adaptive EMA ma1 = ((1 - alpha) * price) + (alpha * ma1) # 2nd stage - one more prelimimary smoothing by Kalman filter det0 = ((price - ma1) * (1 - beta)) + (beta * det0) ma2 = ma1 + pr * det0 # 3rd stage - final smoothing by unique Jurik adaptive filter det1 = ((ma2 - jma[i - 1]) * (1 - alpha) * (1 - alpha)) + (alpha * alpha * det1) jma[i] = jma[i-1] + det1 # Remove initial lookback data and convert to pandas frame jma[0:_length - 1] = npNaN jma = Series(jma, index=close.index) # Offset if offset != 0: jma = jma.shift(offset) # Handle fills if "fillna" in kwargs: jma.fillna(kwargs["fillna"], inplace=True) if "fill_method" in kwargs: jma.fillna(method=kwargs["fill_method"], inplace=True) # Name & Category jma.name = f"JMA_{_length}_{phase}" jma.category = "overlap" return jma