ALMA & UT Bot Confluence Strategy
The ALMA & UT Bot Confluence strategy combines an Arnaud Legoux Moving Average filter with a UT Bot style trailing stop. A long position opens when price is above both the long-term EMA and ALMA, volume exceeds its average, RSI signals momentum, ADX confirms trend strength, the candle is below the upper Bollinger Band and the UT Bot generates a buy signal. Short entries occur when the UT Bot turns bearish and price crosses below the fast EMA under the same filters. Exits use either the UT Bot trailing stop or fixed ATR-based stop loss and take profit.
Details
- Entry Criteria:
- Long: price > EMA & ALMA, RSI > 30, ADX > 30, price < Bollinger upper band, UT Bot buy signal, volume and ATR filters, cooldown.
- Short: price crosses below fast EMA with UT Bot sell signal and filters.
- Long/Short: Both.
- Exit Criteria:
- UT Bot trailing stop or ATR-based stop loss/take profit and optional time exit.
- Stops: ATR or UT Bot trailing.
- Default Values:
FastEmaLength= 20EmaLength= 72AtrLength= 14AdxLength= 10RsiLength= 14BbMultiplier= 3.0StopLossAtrMultiplier= 5.0TakeProfitAtrMultiplier= 4.0UtAtrPeriod= 10UtKeyValue= 1VolumeMaLength= 20BaseCooldownBars= 7MinAtr= 0.005
- Filters:
- Category: Trend following with volatility filter
- Direction: Long/Short
- Indicators: EMA, ALMA, ADX, RSI, Bollinger Bands, UT Bot
- Stops: ATR or trailing
- Complexity: High
- Timeframe: Any
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
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>
/// Strategy that combines ALMA filter with UT Bot trailing stop.
/// Enters long when UT Bot gives a buy signal above EMA.
/// Short entries occur on UT Bot sell signals below EMA.
/// Exits are handled by UT Bot trailing stop or ATR-based stop/target.
/// </summary>
public class AlmaUtBotConfluenceStrategy : Strategy
{
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _stopLossAtrMultiplier;
private readonly StrategyParam<decimal> _takeProfitAtrMultiplier;
private readonly StrategyParam<int> _utKeyValue;
private readonly StrategyParam<int> _utAtrPeriod;
private readonly StrategyParam<int> _baseCooldownBars;
private readonly StrategyParam<bool> _useUtExit;
private readonly StrategyParam<DataType> _candleType;
private decimal _xAtrTrailingStop;
private decimal _prevSrc;
private decimal _prevStop;
private int _barIndex;
private int? _lastTradeIndex;
private decimal _entryPrice;
/// <summary>
/// Length for long-term EMA (default: 72).
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// ATR length (default: 14).
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiplier for stop loss (default: 5.0).
/// </summary>
public decimal StopLossAtrMultiplier
{
get => _stopLossAtrMultiplier.Value;
set => _stopLossAtrMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier for take profit (default: 4.0).
/// </summary>
public decimal TakeProfitAtrMultiplier
{
get => _takeProfitAtrMultiplier.Value;
set => _takeProfitAtrMultiplier.Value = value;
}
/// <summary>
/// UT Bot key value (default: 2).
/// </summary>
public int UtKeyValue
{
get => _utKeyValue.Value;
set => _utKeyValue.Value = value;
}
/// <summary>
/// ATR period for UT Bot (default: 10).
/// </summary>
public int UtAtrPeriod
{
get => _utAtrPeriod.Value;
set => _utAtrPeriod.Value = value;
}
/// <summary>
/// Cooldown in bars between trades (default: 15).
/// </summary>
public int BaseCooldownBars
{
get => _baseCooldownBars.Value;
set => _baseCooldownBars.Value = value;
}
/// <summary>
/// Use UT Bot trailing stop for exits (default: true).
/// </summary>
public bool UseUtExit
{
get => _useUtExit.Value;
set => _useUtExit.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes parameters.
/// </summary>
public AlmaUtBotConfluenceStrategy()
{
_emaLength = Param(nameof(EmaLength), 50)
.SetDisplay("EMA Length", "Length for long-term EMA", "Main");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period", "Main");
_stopLossAtrMultiplier = Param(nameof(StopLossAtrMultiplier), 5m)
.SetDisplay("Stop Loss ATR Mult", "ATR multiplier for stop loss", "Risk");
_takeProfitAtrMultiplier = Param(nameof(TakeProfitAtrMultiplier), 4m)
.SetDisplay("Take Profit ATR Mult", "ATR multiplier for take profit", "Risk");
_utKeyValue = Param(nameof(UtKeyValue), 1)
.SetDisplay("UT Key", "UT Bot key value", "UT Bot");
_utAtrPeriod = Param(nameof(UtAtrPeriod), 10)
.SetDisplay("UT ATR Period", "ATR period for UT Bot", "UT Bot");
_baseCooldownBars = Param(nameof(BaseCooldownBars), 30)
.SetDisplay("Base Cooldown", "Cooldown in bars between trades", "Filters");
_useUtExit = Param(nameof(UseUtExit), true)
.SetDisplay("Use UT Exit", "Use UT Bot trailing stop for exits", "Exit");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "Main");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_xAtrTrailingStop = default;
_prevSrc = default;
_prevStop = default;
_barIndex = default;
_lastTradeIndex = default;
_entryPrice = default;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var atrUt = new AverageTrueRange { Length = UtAtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(new IIndicator[] { ema, atr, atrUt }, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal[] values)
{
if (candle.State != CandleStates.Finished)
return;
var emaValue = values[0];
var atrValue = values[1];
var atrUtValue = values[2];
var src = candle.ClosePrice;
var nLoss = UtKeyValue * atrUtValue;
if (_barIndex == 0)
_xAtrTrailingStop = src + nLoss;
if (src > _prevStop && _prevSrc > _prevStop)
_xAtrTrailingStop = Math.Max(_prevStop, src - nLoss);
else if (src < _prevStop && _prevSrc < _prevStop)
_xAtrTrailingStop = Math.Min(_prevStop, src + nLoss);
else
_xAtrTrailingStop = src > _prevStop ? src - nLoss : src + nLoss;
var buyUt = src > _xAtrTrailingStop && _prevSrc <= _prevStop;
var sellUt = src < _xAtrTrailingStop && _prevSrc >= _prevStop;
var cooldownOk = _lastTradeIndex is null || _barIndex - _lastTradeIndex >= BaseCooldownBars;
var buyCondition = buyUt && src > emaValue && cooldownOk;
var sellCondition = sellUt && src < emaValue && cooldownOk;
if (buyCondition && Position <= 0)
{
BuyMarket();
_lastTradeIndex = _barIndex;
_entryPrice = src;
}
else if (sellCondition && Position >= 0)
{
SellMarket();
_lastTradeIndex = _barIndex;
_entryPrice = src;
}
else
{
ManageExit(candle, atrValue, src);
}
_prevSrc = src;
_prevStop = _xAtrTrailingStop;
_barIndex++;
}
private void ManageExit(ICandleMessage candle, decimal atr, decimal src)
{
if (UseUtExit)
{
if (Position > 0 && src < _xAtrTrailingStop && _prevSrc >= _prevStop)
{
SellMarket();
_lastTradeIndex = _barIndex;
}
else if (Position < 0 && src > _xAtrTrailingStop && _prevSrc <= _prevStop)
{
BuyMarket();
_lastTradeIndex = _barIndex;
}
}
else if (Position != 0)
{
var stopLoss = atr * StopLossAtrMultiplier;
var takeProfit = atr * TakeProfitAtrMultiplier;
if (Position > 0)
{
if (candle.ClosePrice <= _entryPrice - stopLoss || candle.ClosePrice >= _entryPrice + takeProfit)
{
SellMarket();
_lastTradeIndex = _barIndex;
}
}
else
{
if (candle.ClosePrice >= _entryPrice + stopLoss || candle.ClosePrice <= _entryPrice - takeProfit)
{
BuyMarket();
_lastTradeIndex = _barIndex;
}
}
}
}
}
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 CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class alma_ut_bot_confluence_strategy(Strategy):
"""
Strategy that combines ALMA filter with UT Bot trailing stop.
Enters long when UT Bot gives a buy signal above EMA.
Short entries occur on UT Bot sell signals below EMA.
Exits are handled by UT Bot trailing stop or ATR-based stop/target.
"""
def __init__(self):
super(alma_ut_bot_confluence_strategy, self).__init__()
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "Length for long-term EMA", "Main")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period", "Main")
self._stop_loss_atr_mult = self.Param("StopLossAtrMultiplier", 5.0) \
.SetDisplay("Stop Loss ATR Mult", "ATR multiplier for stop loss", "Risk")
self._take_profit_atr_mult = self.Param("TakeProfitAtrMultiplier", 4.0) \
.SetDisplay("Take Profit ATR Mult", "ATR multiplier for take profit", "Risk")
self._ut_key_value = self.Param("UtKeyValue", 1) \
.SetDisplay("UT Key", "UT Bot key value", "UT Bot")
self._ut_atr_period = self.Param("UtAtrPeriod", 10) \
.SetDisplay("UT ATR Period", "ATR period for UT Bot", "UT Bot")
self._base_cooldown_bars = self.Param("BaseCooldownBars", 30) \
.SetDisplay("Base Cooldown", "Cooldown in bars between trades", "Filters")
self._use_ut_exit = self.Param("UseUtExit", True) \
.SetDisplay("Use UT Exit", "Use UT Bot trailing stop for exits", "Exit")
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles", "Main")
self._x_atr_trailing_stop = 0.0
self._prev_src = 0.0
self._prev_stop = 0.0
self._bar_index = 0
self._last_trade_index = None
self._entry_price = 0.0
@property
def EmaLength(self): return self._ema_length.Value
@EmaLength.setter
def EmaLength(self, v): self._ema_length.Value = v
@property
def AtrLength(self): return self._atr_length.Value
@AtrLength.setter
def AtrLength(self, v): self._atr_length.Value = v
@property
def StopLossAtrMultiplier(self): return self._stop_loss_atr_mult.Value
@StopLossAtrMultiplier.setter
def StopLossAtrMultiplier(self, v): self._stop_loss_atr_mult.Value = v
@property
def TakeProfitAtrMultiplier(self): return self._take_profit_atr_mult.Value
@TakeProfitAtrMultiplier.setter
def TakeProfitAtrMultiplier(self, v): self._take_profit_atr_mult.Value = v
@property
def UtKeyValue(self): return self._ut_key_value.Value
@UtKeyValue.setter
def UtKeyValue(self, v): self._ut_key_value.Value = v
@property
def UtAtrPeriod(self): return self._ut_atr_period.Value
@UtAtrPeriod.setter
def UtAtrPeriod(self, v): self._ut_atr_period.Value = v
@property
def BaseCooldownBars(self): return self._base_cooldown_bars.Value
@BaseCooldownBars.setter
def BaseCooldownBars(self, v): self._base_cooldown_bars.Value = v
@property
def UseUtExit(self): return self._use_ut_exit.Value
@UseUtExit.setter
def UseUtExit(self, v): self._use_ut_exit.Value = v
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, v): self._candle_type.Value = v
def OnReseted(self):
super(alma_ut_bot_confluence_strategy, self).OnReseted()
self._x_atr_trailing_stop = 0.0
self._prev_src = 0.0
self._prev_stop = 0.0
self._bar_index = 0
self._last_trade_index = None
self._entry_price = 0.0
def OnStarted2(self, time):
super(alma_ut_bot_confluence_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
atr = AverageTrueRange()
atr.Length = self.AtrLength
atr_ut = AverageTrueRange()
atr_ut.Length = self.UtAtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, atr, atr_ut, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, ema_value, atr_value, atr_ut_value):
if candle.State != CandleStates.Finished:
return
src = float(candle.ClosePrice)
n_loss = self.UtKeyValue * atr_ut_value
if self._bar_index == 0:
self._x_atr_trailing_stop = src + n_loss
if src > self._prev_stop and self._prev_src > self._prev_stop:
self._x_atr_trailing_stop = max(self._prev_stop, src - n_loss)
elif src < self._prev_stop and self._prev_src < self._prev_stop:
self._x_atr_trailing_stop = min(self._prev_stop, src + n_loss)
else:
self._x_atr_trailing_stop = src - n_loss if src > self._prev_stop else src + n_loss
buy_ut = src > self._x_atr_trailing_stop and self._prev_src <= self._prev_stop
sell_ut = src < self._x_atr_trailing_stop and self._prev_src >= self._prev_stop
cooldown_ok = self._last_trade_index is None or self._bar_index - self._last_trade_index >= self.BaseCooldownBars
buy_condition = buy_ut and src > ema_value and cooldown_ok
sell_condition = sell_ut and src < ema_value and cooldown_ok
if buy_condition and self.Position <= 0:
self.BuyMarket()
self._last_trade_index = self._bar_index
self._entry_price = src
elif sell_condition and self.Position >= 0:
self.SellMarket()
self._last_trade_index = self._bar_index
self._entry_price = src
else:
self._manage_exit(candle, atr_value, src)
self._prev_src = src
self._prev_stop = self._x_atr_trailing_stop
self._bar_index += 1
def _manage_exit(self, candle, atr, src):
if self.UseUtExit:
if self.Position > 0 and src < self._x_atr_trailing_stop and self._prev_src >= self._prev_stop:
self.SellMarket()
self._last_trade_index = self._bar_index
elif self.Position < 0 and src > self._x_atr_trailing_stop and self._prev_src <= self._prev_stop:
self.BuyMarket()
self._last_trade_index = self._bar_index
elif self.Position != 0:
stop_loss = atr * self.StopLossAtrMultiplier
take_profit = atr * self.TakeProfitAtrMultiplier
close = float(candle.ClosePrice)
if self.Position > 0:
if close <= self._entry_price - stop_loss or close >= self._entry_price + take_profit:
self.SellMarket()
self._last_trade_index = self._bar_index
else:
if close >= self._entry_price + stop_loss or close <= self._entry_price - take_profit:
self.BuyMarket()
self._last_trade_index = self._bar_index
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return alma_ut_bot_confluence_strategy()