85 lines
3.2 KiB
Python
# 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/
| |||
# UT Bot: idea by Yo_adriiiiaan (from an original idea by HPotter); TradingView "UT Bot Alerts" by QuantNomad.
| |||
"""Independent Python reference for UT Bot Alerts, written from the TradingView Pine script's semantics
| |||
(QuantNomad's "UT Bot Alerts"), not translated from the MQL5 code:
| |||
| |||
xATR = ta.atr(c) # Wilder's RMA of the true range, SMA-seeded
| |||
nLoss = a * xATR
| |||
trail = src > trail[1] and src[1] > trail[1] ? max(trail[1], src - nLoss)
| |||
: src < trail[1] and src[1] < trail[1] ? min(trail[1], src + nLoss)
| |||
: src > trail[1] ? src - nLoss : src + nLoss
| |||
buy = src > trail and crossover(src, trail) # crossover: src > trail and src[1] <= trail[1]
| |||
sell = src < trail and crossunder(src, trail)
| |||
| |||
Additions in the MT5 product: optional Heikin Ashi close as src; optional EMA(200) of the close as a trend filter
| |||
(buy only above it, sell only below), EMA seeded with the SMA of the first `period` closes."""
| |||
import numpy as np
| |||
| |||
NAN = np.nan
| |||
| |||
| |||
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 rma(x, n):
| |||
out = np.full(len(x), NAN)
| |||
if len(x) < n:
| |||
return out
| |||
out[n - 1] = x[:n].mean()
| |||
for i in range(n, 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 heikin_close(o, h, l, c):
| |||
return (o + h + l + c) / 4.0
| |||
| |||
| |||
def utbot(o, h, l, c, key=1.0, atr_period=10, heikin=False, use_ema=False, ema_period=200):
| |||
"""Returns (trail, 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 = heikin_close(o, h, l, c) if heikin else c.copy()
| |||
atr = rma(true_range(h, l, c), atr_period)
| |||
e = ema(c, ema_period)
| |||
trail = np.full(n, NAN)
| |||
buy, sell = np.full(n, NAN), np.full(n, NAN)
| |||
for i in range(n):
| |||
if np.isnan(atr[i]):
| |||
continue
| |||
prev = trail[i - 1] if i > 0 and not np.isnan(trail[i - 1]) else 0.0
| |||
s, s1, nl = src[i], (src[i - 1] if i > 0 else src[i]), key * atr[i]
| |||
if s > prev and s1 > prev:
| |||
trail[i] = max(prev, s - nl)
| |||
elif s < prev and s1 < prev:
| |||
trail[i] = min(prev, s + nl)
| |||
else:
| |||
trail[i] = s - nl if s > prev else s + nl
| |||
if i == 0 or i == n - 1 or np.isnan(trail[i - 1]):
| |||
continue
| |||
up = s > trail[i] and s1 <= trail[i - 1]
| |||
dn = s < trail[i] and s1 >= trail[i - 1]
| |||
if use_ema:
| |||
up = up and not np.isnan(e[i]) and c[i] > e[i]
| |||
dn = dn and not np.isnan(e[i]) and c[i] < e[i]
| |||
if up:
| |||
buy[i] = l[i]
| |||
if dn:
| |||
sell[i] = h[i]
| |||
return trail, buy, sell
|