Ver en GitHub

Estrategia de Canal de Redondeo de MA Exponencial

Esta estrategia redondea una media móvil a un paso de tick fijo y construye un canal basado en ATR a su alrededor. Cuando la vela anterior cierra por encima de la banda superior, la estrategia abre una posición larga. Cuando la vela anterior cierra por debajo de la banda inferior, abre una posición corta. Las señales opuestas cierran las posiciones existentes. El stop loss y el take profit se definen en ticks y se gestionan automáticamente.

Detalles

  • Criterios de entrada:
    • Largo: El cierre anterior está por encima de la banda superior redondeada.
    • Corto: El cierre anterior está por debajo de la banda inferior redondeada.
  • Criterios de salida:
    • Largo: El cierre anterior está por debajo de la banda inferior.
    • Corto: El cierre anterior está por encima de la banda superior.
  • Indicadores:
    • Media Móvil Exponencial.
    • Average True Range para el ancho del canal.
  • Stops: Sí, stop loss y take profit fijos en ticks.
  • Valores predeterminados:
    • MA period = 12.
    • ATR period = 12.
    • ATR factor = 1.
    • MA round = 500 ticks.
    • Stop loss = 1000 ticks.
    • Take profit = 2000 ticks.
    • Timeframe = 4 horas.

Filtros

  • Categoría: Seguimiento de tendencia
  • Dirección: Ambos
  • Indicadores: Múltiples
  • Stops: Sí
  • Complejidad: Moderado
  • Marco temporal: Medio plazo
  • Estacionalidad: No
  • Redes neuronales: No
  • Divergencia: No
  • Nivel de riesgo: Moderado
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>
/// Moving average rounding channel strategy.
/// Opens long positions when price closes above the rounded upper channel
/// and opens short positions when price closes below the rounded lower channel.
/// </summary>
public class ExpMaRoundingChannelStrategy : Strategy
{
	private readonly StrategyParam<int> _maLength;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrFactor;
	private readonly StrategyParam<decimal> _roundStep;
	private readonly StrategyParam<DataType> _candleType;

	private AverageTrueRange _atr;
	private decimal _prevUpper;
	private decimal _prevLower;
	private decimal _prevClose;

	public int MaLength { get => _maLength.Value; set => _maLength.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal AtrFactor { get => _atrFactor.Value; set => _atrFactor.Value = value; }
	public decimal RoundStep { get => _roundStep.Value; set => _roundStep.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public ExpMaRoundingChannelStrategy()
	{
		_maLength = Param(nameof(MaLength), 12)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Length of moving average", "Indicator");

		_atrPeriod = Param(nameof(AtrPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for channel width", "Indicator");

		_atrFactor = Param(nameof(AtrFactor), 2m)
			.SetDisplay("ATR Factor", "Multiplier for ATR channel", "Indicator");

		_roundStep = Param(nameof(RoundStep), 50m)
			.SetDisplay("Round Step", "Rounding step for the moving average", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for calculation", "General");
	}

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

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

		_prevClose = 0;
		_prevUpper = 0;
		_prevLower = 0;

		var ema = new ExponentialMovingAverage { Length = MaLength };
		_atr = new AverageTrueRange { Length = AtrPeriod };

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

		StartProtection(
			takeProfit: new Unit(3, UnitTypes.Percent),
			stopLoss: new Unit(2, UnitTypes.Percent));

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_atr = default;
		_prevUpper = default;
		_prevLower = default;
		_prevClose = default;
	}

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

		var atrResult = _atr.Process(candle);
		if (!atrResult.IsFormed)
			return;

		var atrValue = atrResult.ToDecimal();

		// Round the MA value
		var step = RoundStep;
		var roundedMa = step > 0 ? Math.Round(maValue / step) * step : maValue;
		var upper = roundedMa + atrValue * AtrFactor;
		var lower = roundedMa - atrValue * AtrFactor;

		if (_prevClose != 0m)
		{
			var breakUp = _prevClose <= _prevUpper && candle.ClosePrice > upper;
			var breakDown = _prevClose >= _prevLower && candle.ClosePrice < lower;

			if (breakUp && Position == 0)
				BuyMarket();
			else if (breakDown && Position == 0)
				SellMarket();
		}

		_prevUpper = upper;
		_prevLower = lower;
		_prevClose = candle.ClosePrice;
	}
}