# SPDX-License-Identifier: MPL-2.0 # This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not # distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/ # SuperTrend: method by Olivier Seban; TradingView "SuperTrend" script by KivancOzbilgic. """Independent Python reference for SuperTrend Signal Alerts, written from the TradingView Pine script's semantics (KivancOzbilgic's "SuperTrend"), not translated from the MQL5 code: src = hl2 atr = changeATR ? ta.atr(Periods) : ta.sma(ta.tr, Periods) # ta.tr: first bar = high - low up = src - Multiplier * atr; up1 = nz(up[1], up) up := close[1] > up1 ? max(up, up1) : up dn = src + Multiplier * atr; dn1 = nz(dn[1], dn) dn := close[1] < dn1 ? min(dn, dn1) : dn trend = nz(trend[1], 1) trend := trend == -1 and close > dn1 ? 1 : trend == 1 and close < up1 ? -1 : trend line = trend == 1 ? up : dn buy = trend == 1 and trend[1] == -1; sell = trend == -1 and trend[1] == 1 ta.atr is Wilder's RMA seeded with the SMA of the first `period` true ranges (as on TradingView). Additions in the MT5 product: optional EMA trend filter (EMA of the close, SMA-seeded: buy only close > EMA, sell only close < EMA) and RSI filter (ta.rsi: RMA of gains/losses seeded with the SMA of the first `period` changes; buy only RSI > 50, sell only RSI < 50).""" import numpy as np NAN = np.nan def above(a, b): """Strict a > b ignoring floating-point noise (a band can tie a close exactly in real arithmetic); same as MQL5.""" return a - b > 1e-10 * abs(b) def below(a, b): return b - a > 1e-10 * abs(b) def true_range(h, l, c): tr = h - l tr[1:] = np.maximum(tr[1:], np.maximum(np.abs(h[1:] - c[:-1]), np.abs(l[1:] - c[:-1]))) return tr def sma(x, n): """Plain SMA. Each window is summed left to right (like Pine's ta.sma and the MQL5 loop); a running cumsum difference would carry ~1e-13 noise, and SuperTrend compares close against bands that can tie exactly.""" out = np.full(len(x), NAN) for i in range(n - 1, len(x)): s = 0.0 for v in x[i - n + 1:i + 1]: s += v out[i] = s / n return out def rma(x, n): """RMA seeded with the SMA of x[:n]; x may carry leading NaN (then seeded at the first full window).""" out = np.full(len(x), NAN) first = int(np.argmax(~np.isnan(x))) if (~np.isnan(x)).any() else len(x) s = first + n - 1 if s >= len(x): return out out[s] = x[first:first + n].mean() for i in range(s + 1, len(x)): out[i] = (out[i - 1] * (n - 1) + x[i]) / n return out def ema(x, n): out = np.full(len(x), NAN) if len(x) < n: return out out[n - 1] = x[:n].mean() k = 2.0 / (n + 1) for i in range(n, len(x)): out[i] = out[i - 1] + k * (x[i] - out[i - 1]) return out def rsi(c, n): ch = np.concatenate(([NAN], np.diff(c))) up, dn = rma(np.where(np.isnan(ch), NAN, np.maximum(ch, 0)), n), rma(np.where(np.isnan(ch), NAN, np.maximum(-ch, 0)), n) out = np.full(len(c), NAN) for i in range(len(c)): if np.isnan(up[i]): continue out[i] = 100.0 if dn[i] == 0 else 0.0 if up[i] == 0 else 100.0 - 100.0 / (1.0 + up[i] / dn[i]) return out def supertrend(o, h, l, c, period=10, mult=3.0, rma_atr=True, use_ema=False, ema_period=200, use_rsi=False, rsi_period=14): """Returns (line, buy, sell) arrays; buy/sell hold the arrow price (low/high) or NaN. The last bar is treated as still forming: no signal on it.""" n = len(c) src = (h + l) / 2.0 tr = true_range(h, l, c) atr = rma(tr, period) if rma_atr else sma(tr, period) e, r = ema(c, ema_period), rsi(c, rsi_period) up, dn, trend = np.full(n, NAN), np.full(n, NAN), np.full(n, NAN) line, buy, sell = np.full(n, NAN), np.full(n, NAN), np.full(n, NAN) for i in range(n): if np.isnan(atr[i]): continue u, d = src[i] - mult * atr[i], src[i] + mult * atr[i] u1 = up[i - 1] if i > 0 and not np.isnan(up[i - 1]) else u # nz(up[1], up) d1 = dn[i - 1] if i > 0 and not np.isnan(dn[i - 1]) else d if i > 0 and above(c[i - 1], u1): u = max(u, u1) if i > 0 and below(c[i - 1], d1): d = min(d, d1) t_prev = trend[i - 1] if i > 0 and not np.isnan(trend[i - 1]) else 1.0 t = t_prev if t == -1 and above(c[i], d1): t = 1.0 elif t == 1 and below(c[i], u1): t = -1.0 up[i], dn[i], trend[i] = u, d, t line[i] = u if t == 1 else d if i == 0 or i == n - 1 or np.isnan(trend[i - 1]): continue b, s = t == 1 and trend[i - 1] == -1, t == -1 and trend[i - 1] == 1 if use_ema: b = b and not np.isnan(e[i]) and c[i] > e[i] s = s and not np.isnan(e[i]) and c[i] < e[i] if use_rsi: b = b and not np.isnan(r[i]) and r[i] > 50 s = s and not np.isnan(r[i]) and r[i] < 50 if b: buy[i] = l[i] if s: sell[i] = h[i] return line, buy, sell