Ver en GitHub

Estrategia de Envolventes de Tendencia

Estrategia de seguimiento de tendencia basada en el indicador TrendEnvelopes. Combina una EMA con bandas basadas en ATR para detectar rupturas. Las posiciones largas se abren cuando el precio rompe por encima de la banda superior y aparece una señal de compra. Las posiciones cortas se abren en rupturas por debajo de la banda inferior con una señal de venta. Las bandas opuestas activan el cierre de posiciones.

Detalles

  • Criterios de entrada:
    • Largo: el precio cierra por encima del envolvente superior y genera una señal de compra
    • Corto: el precio cierra por debajo del envolvente inferior y genera una señal de venta
  • Largo/Corto: Ambos
  • Criterios de salida: Señal de tendencia opuesta
  • Stops: Sí (take profit y stop loss)
  • Valores predeterminados:
    • MaPeriod = 14
    • Deviation = 0.2m
    • AtrPeriod = 15
    • AtrSensitivity = 0.5m
    • TakeProfit = 2000 puntos
    • StopLoss = 1000 puntos
    • CandleType = TimeSpan.FromHours(4).TimeFrame()
  • Filtros:
    • Categoría: Seguimiento de tendencia
    • Dirección: Ambos
    • Indicadores: EMA, ATR
    • Stops: Sí
    • Complejidad: Intermedio
    • Marco temporal: 4h
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on TrendEnvelopes indicator with ATR-based signals.
/// </summary>
public class TrendEnvelopesStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<decimal> _deviation;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrSensitivity;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _buyEntry;
	private readonly StrategyParam<bool> _sellEntry;
	private readonly StrategyParam<bool> _buyExit;
	private readonly StrategyParam<bool> _sellExit;
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _stopLoss;

	private ExponentialMovingAverage _ma;
	private AverageTrueRange _atr;

	private decimal _prevSmax;
	private decimal _prevSmin;
	private int _prevTrend;
	private bool _initialized;

	/// <summary>
	/// Moving average period.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Percentage deviation for envelopes.
	/// </summary>
	public decimal Deviation
	{
		get => _deviation.Value;
		set => _deviation.Value = value;
	}

	/// <summary>
	/// ATR period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// ATR shift sensitivity.
	/// </summary>
	public decimal AtrSensitivity
	{
		get => _atrSensitivity.Value;
		set => _atrSensitivity.Value = value;
	}

	/// <summary>
	/// Candle type for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Whether long entries are allowed.
	/// </summary>
	public bool BuyEntry
	{
		get => _buyEntry.Value;
		set => _buyEntry.Value = value;
	}

	/// <summary>
	/// Whether short entries are allowed.
	/// </summary>
	public bool SellEntry
	{
		get => _sellEntry.Value;
		set => _sellEntry.Value = value;
	}

	/// <summary>
	/// Whether long positions can be closed.
	/// </summary>
	public bool BuyExit
	{
		get => _buyExit.Value;
		set => _buyExit.Value = value;
	}

	/// <summary>
	/// Whether short positions can be closed.
	/// </summary>
	public bool SellExit
	{
		get => _sellExit.Value;
		set => _sellExit.Value = value;
	}

	/// <summary>
	/// Take profit in points.
	/// </summary>
	public int TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Stop loss in points.
	/// </summary>
	public int StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public TrendEnvelopesStrategy()
	{
		_maPeriod = Param(nameof(MaPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("MA Period", "Moving average length", "Indicator");

		_deviation = Param(nameof(Deviation), 0.2m)
		.SetGreaterThanZero()
		.SetDisplay("Deviation", "Percent offset for envelopes", "Indicator");

		_atrPeriod = Param(nameof(AtrPeriod), 15)
		.SetGreaterThanZero()
		.SetDisplay("ATR Period", "ATR calculation length", "Indicator");

		_atrSensitivity = Param(nameof(AtrSensitivity), 0.5m)
		.SetGreaterThanZero()
		.SetDisplay("ATR Sensitivity", "Multiplier for signal shift", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe for strategy", "General");

		_buyEntry = Param(nameof(BuyEntry), true)
		.SetDisplay("Enable Long Entry", "Allow opening long positions", "Trading");

		_sellEntry = Param(nameof(SellEntry), true)
		.SetDisplay("Enable Short Entry", "Allow opening short positions", "Trading");

		_buyExit = Param(nameof(BuyExit), true)
		.SetDisplay("Enable Long Exit", "Allow closing long positions", "Trading");

		_sellExit = Param(nameof(SellExit), true)
		.SetDisplay("Enable Short Exit", "Allow closing short positions", "Trading");

		_takeProfit = Param(nameof(TakeProfit), 2000)
		.SetGreaterThanZero()
		.SetDisplay("Take Profit", "Target in points", "Protection");

		_stopLoss = Param(nameof(StopLoss), 1000)
		.SetGreaterThanZero()
		.SetDisplay("Stop Loss", "Loss limit in points", "Protection");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_ma = default;
		_atr = default;
		_prevSmax = 0;
		_prevSmin = 0;
		_prevTrend = 0;
		_initialized = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_ma = new ExponentialMovingAverage { Length = MaPeriod };
		_atr = new AverageTrueRange { Length = AtrPeriod };

		Indicators.Add(_ma);
		Indicators.Add(_atr);

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(ProcessCandle)
		.Start();

		var step = Security.PriceStep ?? 1m;
		StartProtection(
		takeProfit: new Unit(TakeProfit * step, UnitTypes.Absolute),
		stopLoss: new Unit(StopLoss * step, UnitTypes.Absolute));

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _ma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var maResult = _ma.Process(candle.ClosePrice, candle.OpenTime, true);
		var atrResult = _atr.Process(candle);

		if (!maResult.IsFormed || !atrResult.IsFormed)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var maValue = maResult.ToDecimal();
		var atrValue = atrResult.ToDecimal();

		var smax = (1m + Deviation / 100m) * maValue;
		var smin = (1m - Deviation / 100m) * maValue;
		var trend = _prevTrend;

		if (_initialized)
		{
			if (candle.ClosePrice > _prevSmax)
			trend = 1;
			if (candle.ClosePrice < _prevSmin)
			trend = -1;
		}

		decimal? upSignal = null;
		decimal? downSignal = null;
		var upTrend = false;
		var downTrend = false;

		if (!_initialized)
		{
			_prevSmax = smax;
			_prevSmin = smin;
			_prevTrend = 0;
			_initialized = true;
			return;
		}

		if (trend > 0)
		{
			if (smin < _prevSmin)
			smin = _prevSmin;

			upTrend = true;

			if (_prevTrend <= 0)
			upSignal = smin - AtrSensitivity * atrValue;
		}
		else if (trend < 0)
		{
			if (smax > _prevSmax)
			smax = _prevSmax;

			downTrend = true;

			if (_prevTrend >= 0)
			downSignal = smax + AtrSensitivity * atrValue;
		}

		_prevSmax = smax;
		_prevSmin = smin;
		_prevTrend = trend;

		if (BuyExit && downTrend && Position > 0)
			SellMarket();

		if (SellExit && upTrend && Position < 0)
			BuyMarket();

		if (BuyEntry && upSignal.HasValue && Position <= 0)
			BuyMarket();

		if (SellEntry && downSignal.HasValue && Position >= 0)
			SellMarket();
	}
}