Backtest UT Bot + RSI Strategy
Combines a UT Bot trend detector with RSI levels. Enters long on a bullish UT Bot reversal when RSI is oversold and short on a bearish reversal when RSI is overbought.
Details
- Entry Criteria:
- Long: UT Bot turns up and RSI <
RSI Oversold. - Short: UT Bot turns down and RSI >
RSI Overbought.
- Long: UT Bot turns up and RSI <
- Long/Short: Both directions.
- Exit Criteria:
- Take profit or stop loss percentages.
- Stops: Take Profit & Stop Loss.
- Default Values:
RSI Length= 14RSI Overbought= 60RSI Oversold= 40ATR Length= 10UT Bot Factor= 1.0Take Profit %= 3.0Stop Loss %= 1.5
- Filters:
- Category: Trend Following
- Direction: Both
- Indicators: Multiple
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// UT Bot trend strategy combined with EMA filter.
/// Opens long on trend reversal up below EMA and
/// short on trend reversal down above EMA.
/// Uses StandardDeviation instead of ATR for volatility measurement.
/// </summary>
public class BacktestUtBotRsiStrategy : Strategy
{
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _stdLength;
private readonly StrategyParam<decimal> _factor;
private readonly StrategyParam<DataType> _candleType;
private decimal? _trail;
private int _dir;
private int _prevDir;
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
public int StdLength { get => _stdLength.Value; set => _stdLength.Value = value; }
public decimal Factor { get => _factor.Value; set => _factor.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BacktestUtBotRsiStrategy()
{
_emaLength = Param(nameof(EmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period for trend filter", "Parameters");
_stdLength = Param(nameof(StdLength), 10)
.SetGreaterThanZero()
.SetDisplay("StdDev Length", "StdDev period", "Parameters");
_factor = Param(nameof(Factor), 1.0m)
.SetGreaterThanZero()
.SetDisplay("UT Bot Factor", "Volatility multiplier", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trail = null;
_dir = 0;
_prevDir = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var stdDev = new StandardDeviation { Length = StdLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(stdDev, ema, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal stdValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (stdValue <= 0)
return;
var upperBand = candle.ClosePrice + Factor * stdValue;
var lowerBand = candle.ClosePrice - Factor * stdValue;
if (_trail is null)
{
_trail = lowerBand;
_dir = 0;
}
else if (candle.ClosePrice > _trail)
{
_trail = Math.Max(_trail.Value, lowerBand);
_dir = 1;
}
else if (candle.ClosePrice < _trail)
{
_trail = Math.Min(_trail.Value, upperBand);
_dir = -1;
}
var trendUp = _dir == 1 && _prevDir == -1;
var trendDown = _dir == -1 && _prevDir == 1;
if (trendUp && candle.ClosePrice < emaValue && Position <= 0)
BuyMarket();
else if (trendDown && candle.ClosePrice > emaValue && Position >= 0)
SellMarket();
_prevDir = _dir;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class backtest_ut_bot_rsi_strategy(Strategy):
def __init__(self):
super(backtest_ut_bot_rsi_strategy, self).__init__()
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "EMA period for trend filter", "Parameters")
self._std_length = self.Param("StdLength", 10) \
.SetDisplay("StdDev Length", "StdDev period", "Parameters")
self._factor = self.Param("Factor", 1.0) \
.SetDisplay("UT Bot Factor", "Volatility multiplier", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._trail = None
self._dir = 0
self._prev_dir = 0
@property
def ema_length(self):
return self._ema_length.Value
@property
def std_length(self):
return self._std_length.Value
@property
def factor(self):
return self._factor.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(backtest_ut_bot_rsi_strategy, self).OnReseted()
self._trail = None
self._dir = 0
self._prev_dir = 0
def OnStarted2(self, time):
super(backtest_ut_bot_rsi_strategy, self).OnStarted2(time)
std_dev = StandardDeviation()
std_dev.Length = self.std_length
ema = ExponentialMovingAverage()
ema.Length = self.ema_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(std_dev, ema, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def on_process(self, candle, std_value, ema_value):
if candle.State != CandleStates.Finished:
return
if std_value <= 0:
return
upper_band = candle.ClosePrice + self.factor * std_value
lower_band = candle.ClosePrice - self.factor * std_value
if self._trail is None:
self._trail = lower_band
self._dir = 0
elif candle.ClosePrice > self._trail:
self._trail = max(self._trail, lower_band)
self._dir = 1
elif candle.ClosePrice < self._trail:
self._trail = min(self._trail, upper_band)
self._dir = -1
trend_up = self._dir == 1 and self._prev_dir == -1
trend_down = self._dir == -1 and self._prev_dir == 1
if trend_up and candle.ClosePrice < ema_value and self.Position <= 0:
self.BuyMarket()
elif trend_down and candle.ClosePrice > ema_value and self.Position >= 0:
self.SellMarket()
self._prev_dir = self._dir
def CreateClone(self):
return backtest_ut_bot_rsi_strategy()