Replicates the behaviour of the TradingView "UT Bot Alerts" script.
Timeframe
N/A
Direction
Long Only
Stoploss
N/A
Trailing Stop
No
ROI
0m: 1000.0%
Interface Version
N/A
Startup Candles
N/A
Indicators
2
freqtrade/freqtrade-strategies
freqtrade/freqtrade-strategies
Strategy 001_custom_exit author@: Gerald Lonlas, froggleston github@: https://github.com/freqtrade/freqtrade-strategies
freqtrade/freqtrade-strategies
Strategy 001 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# -*- coding: utf-8 -*-
"""Freqtrade implementation of the TradingView "UT Bot Alerts" indicator."""
from __future__ import annotations
from typing import Dict
import numpy as np
from pandas import DataFrame, Series
from freqtrade.strategy.interface import IStrategy
from freqtrade.vendor.qtpylib import indicators as qtpylib
class UtBotStrategy(IStrategy):
"""Replicates the behaviour of the TradingView "UT Bot Alerts" script."""
timeframe: str = "15m"
process_only_new_candles: bool = True
startup_candle_count: int = 200
minimal_roi = {"0": 10.0}
stoploss: float = -0.2
trailing_stop: bool = False
can_short: bool = True
position_adjustment_enable: bool = False
order_types: Dict[str, str] = {
"entry": "limit",
"exit": "limit",
"entry_long": "limit",
"entry_short": "limit",
"exit_long": "limit",
"exit_short": "limit",
"force_entry": "market",
"force_exit": "market",
"force_entry_long": "market",
"force_entry_short": "market",
"force_exit_long": "market",
"force_exit_short": "market",
"stoploss": "market",
"emergency_exit": "market",
"stoploss_on_exchange": False,
"stoploss_on_exchange_interval": 60,
"stoploss_on_exchange_limit_ratio": 0.99,
}
order_time_in_force: Dict[str, str] = {
"entry": "gtc",
"exit": "gtc",
"entry_long": "gtc",
"entry_short": "gtc",
"exit_long": "gtc",
"exit_short": "gtc",
"force_entry": "gtc",
"force_exit": "gtc",
"force_entry_long": "gtc",
"force_entry_short": "gtc",
"force_exit_long": "gtc",
"force_exit_short": "gtc",
"stoploss": "gtc",
"emergency_exit": "gtc",
}
stoploss_on_exchange: bool = False
stoploss_on_exchange_interval: int = 60
stoploss_on_exchange_limit_ratio: float = 0.99
# --- Parameters taken from the TradingView script ---
atr_period: int = 10
key_value: float = 1.0
use_heikin_ashi_source: bool = False
plot_config = {
"main_plot": {
"ut_trailing_stop": {"color": "orange"},
},
}
def __init__(self, config: dict) -> None:
super().__init__(config)
config.pop("order_time_in_force", None)
self.order_time_in_force = self.order_time_in_force.copy()
# --- Helper methods -------------------------------------------------
@staticmethod
def _atr(dataframe: DataFrame, period: int) -> Series:
high_low = (dataframe["high"] - dataframe["low"]).abs()
high_close = (dataframe["high"] - dataframe["close"].shift()).abs()
low_close = (dataframe["low"] - dataframe["close"].shift()).abs()
tr = np.maximum.reduce([high_low.values, high_close.values, low_close.values])
tr_series = Series(tr, index=dataframe.index, name="tr")
return tr_series.ewm(alpha=1.0 / period, adjust=False).mean()
@staticmethod
def _crossover(a: Series, b: Series) -> Series:
return (a > b) & (a.shift(1) <= b.shift(1))
@staticmethod
def _crossunder(a: Series, b: Series) -> Series:
return (a < b) & (a.shift(1) >= b.shift(1))
@staticmethod
def _ut_trailing_stop(src: Series, nloss: Series) -> Series:
values = np.zeros(len(src))
for i, (price, loss) in enumerate(zip(src.values, nloss.values)):
prev_stop = values[i - 1] if i > 0 else 0.0
prev_src = src.iloc[i - 1] if i > 0 else src.iloc[i]
if not np.isfinite(price):
values[i] = prev_stop
continue
if not np.isfinite(loss):
loss = 0.0
if price > prev_stop and prev_src > prev_stop:
stop_val = max(prev_stop, price - loss)
elif price < prev_stop and prev_src < prev_stop:
stop_val = min(prev_stop, price + loss)
elif price > prev_stop:
stop_val = price - loss
else:
stop_val = price + loss
values[i] = stop_val
return Series(values, index=src.index, name="ut_trailing_stop")
# --- Indicator population ------------------------------------------
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
if self.use_heikin_ashi_source:
ha = qtpylib.heikinashi(df)
source = ha["close"]
else:
source = df["close"]
atr = self._atr(df, self.atr_period)
nloss = self.key_value * atr
df["ut_source"] = source
df["ut_atr"] = atr
df["ut_nloss"] = nloss
df["ut_trailing_stop"] = self._ut_trailing_stop(source, nloss)
df["ut_crossover"] = self._crossover(source, df["ut_trailing_stop"])
df["ut_crossunder"] = self._crossunder(source, df["ut_trailing_stop"])
df["ut_buy"] = (source > df["ut_trailing_stop"]) & df["ut_crossover"]
df["ut_sell"] = (source < df["ut_trailing_stop"]) & df["ut_crossunder"]
return df
# --- Entry / Exit rules --------------------------------------------
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
df.loc[df["ut_buy"], "enter_long"] = 1
df.loc[df["ut_sell"], "enter_short"] = 1
return df
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
df.loc[df["ut_sell"], "exit_long"] = 1
df.loc[df["ut_buy"], "exit_short"] = 1
return df