Ver en GitHub

Estrategia ALMA & UT Bot Confluence

La estrategia ALMA & UT Bot Confluence combina un filtro de media móvil Arnaud Legoux con un stop trailing al estilo UT Bot. Se abre una posición larga cuando el precio está por encima de la EMA a largo plazo y la ALMA, el volumen supera su media, el RSI señala momentum, el ADX confirma la fuerza del tendencia, la vela está por debajo de la banda superior de Bollinger y el UT Bot genera una señal de compra. Las entradas cortas ocurren cuando el UT Bot se vuelve bajista y el precio cruza por debajo de la EMA rápida bajo los mismos filtros. Las salidas utilizan el stop trailing del UT Bot o un stop loss y take profit fijos basados en ATR.

Detalles

  • Criterios de entrada:
    • Largo: precio > EMA y ALMA, RSI > 30, ADX > 30, precio < banda superior de Bollinger, señal de compra UT Bot, filtros de volumen y ATR, cooldown.
    • Corto: precio cruza por debajo de la EMA rápida con señal de venta UT Bot y filtros.
  • Largo/Corto: Ambos.
  • Criterios de salida:
    • Stop trailing UT Bot o stop loss/take profit basado en ATR y salida temporal opcional.
  • Stops: ATR o trailing.
  • Valores predeterminados:
    • FastEmaLength = 20
    • EmaLength = 72
    • AtrLength = 14
    • AdxLength = 10
    • RsiLength = 14
    • BbMultiplier = 3.0
    • StopLossAtrMultiplier = 5.0
    • TakeProfitAtrMultiplier = 4.0
    • UtAtrPeriod = 10
    • UtKeyValue = 1
    • VolumeMaLength = 20
    • BaseCooldownBars = 7
    • MinAtr = 0.005
  • Filtros:
    • Categoría: Seguimiento de tendencia con filtro de volatilidad
    • Dirección: Largo/Corto
    • Indicadores: EMA, ALMA, ADX, RSI, Bollinger Bands, UT Bot
    • Stops: ATR o trailing
    • Complejidad: Alto
    • Marco temporal: Cualquiera
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
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;
				}
			}
		}
	}
}