Ver en GitHub

Estrategia Multi-Factor

La Estrategia Multi-Factor combina MACD, RSI y dos medias móviles para operar con confirmación de tendencia. Las operaciones largas ocurren cuando la línea MACD está por encima de su señal, el RSI está por debajo de 70, el precio está por encima de la SMA de 50 períodos y la SMA de 50 está por encima de la SMA de 200. Las operaciones cortas usan condiciones opuestas.

Los stops y objetivos se basan en múltiplos del ATR.

Detalles

  • Criterios de entrada:
    • Largo: MACD > Signal && RSI < 70 && Close > SMA50 && SMA50 > SMA200.
    • Corto: MACD < Signal && RSI > 30 && Close < SMA50 && SMA50 < SMA200.
  • Largo/Corto: Ambas direcciones.
  • Criterios de salida: Stop loss y take profit basados en ATR.
  • Stops: Sí.
  • Valores predeterminados:
    • FastLength = 12
    • SlowLength = 26
    • SignalLength = 9
    • RsiLength = 14
    • AtrLength = 14
    • StopAtrMultiplier = 2
    • ProfitAtrMultiplier = 3
    • CandleType = TimeSpan.FromMinutes(15)
  • Filtros:
    • Categoría: Seguimiento de tendencia
    • Dirección: Ambos
    • Indicadores: MACD, RSI, SMA, ATR
    • Stops: Sí
    • Complejidad: Básico
    • Marco temporal: Medio plazo
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// Multi-factor strategy combining MACD, RSI, ATR, and trend filters.
/// Opens long positions only on bullish MACD crossovers confirmed by RSI and long-term trend.
/// </summary>
public class MultiFactorStrategy : Strategy
{
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _signalLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _stopAtrMultiplier;
	private readonly StrategyParam<decimal> _profitAtrMultiplier;
	private readonly StrategyParam<int> _signalCooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevDiff;
	private bool _hasPrevDiff;
	private int _cooldownRemaining;
	private MovingAverageConvergenceDivergenceSignal _macd;
	private RelativeStrengthIndex _rsi;
	private AverageTrueRange _atr;
	private SMA _sma50;
	private SMA _sma200;

	public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
	public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
	public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
	public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
	public decimal StopAtrMultiplier { get => _stopAtrMultiplier.Value; set => _stopAtrMultiplier.Value = value; }
	public decimal ProfitAtrMultiplier { get => _profitAtrMultiplier.Value; set => _profitAtrMultiplier.Value = value; }
	public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MultiFactorStrategy()
	{
		_fastLength = Param(nameof(FastLength), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast", "MACD fast EMA length", "MACD");

		_slowLength = Param(nameof(SlowLength), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD Slow", "MACD slow EMA length", "MACD");

		_signalLength = Param(nameof(SignalLength), 9)
			.SetGreaterThanZero()
			.SetDisplay("MACD Signal", "MACD signal EMA length", "MACD");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "RSI period", "RSI");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Length", "ATR period", "ATR");

		_stopAtrMultiplier = Param(nameof(StopAtrMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Stop ATR Mult", "ATR multiplier for stop", "Risk");

		_profitAtrMultiplier = Param(nameof(ProfitAtrMultiplier), 3m)
			.SetGreaterThanZero()
			.SetDisplay("Profit ATR Mult", "ATR multiplier for take profit", "Risk");

		_signalCooldownBars = Param(nameof(SignalCooldownBars), 50)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait after entries and exits", "Trading");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevDiff = 0m;
		_hasPrevDiff = false;
		_cooldownRemaining = 0;
		_macd = null;
		_rsi = null;
		_atr = null;
		_sma50 = null;
		_sma200 = null;
	}

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

		_macd = new()
		{
			Macd =
			{
				ShortMa = { Length = FastLength },
				LongMa = { Length = SlowLength },
			},
			SignalMa = { Length = SignalLength }
		};

		_rsi = new() { Length = RsiLength };
		_atr = new() { Length = AtrLength };
		_sma50 = new() { Length = 50 };
		_sma200 = new() { Length = 200 };
		_prevDiff = 0m;
		_hasPrevDiff = false;
		_cooldownRemaining = 0;

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

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

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

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

		var macdValue = _macd.Process(candle);
		var rsiValue = _rsi.Process(candle);
		var atrValue = _atr.Process(candle);
		var sma50Value = _sma50.Process(new DecimalIndicatorValue(_sma50, candle.ClosePrice, candle.ServerTime) { IsFinal = true });
		var sma200Value = _sma200.Process(new DecimalIndicatorValue(_sma200, candle.ClosePrice, candle.ServerTime) { IsFinal = true });

		if (!_macd.IsFormed || !_rsi.IsFormed || !_atr.IsFormed || !_sma50.IsFormed || !_sma200.IsFormed)
			return;

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var macdData = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
		if (macdData.Macd is not decimal macdLine || macdData.Signal is not decimal signalLine)
			return;

		var diff = macdLine - signalLine;
		var rsi = rsiValue.ToDecimal();
		var atr = atrValue.ToDecimal();
		var sma50 = sma50Value.ToDecimal();
		var sma200 = sma200Value.ToDecimal();

		if (!_hasPrevDiff)
		{
			_prevDiff = diff;
			_hasPrevDiff = true;
			return;
		}

		if (_cooldownRemaining == 0 && Position == 0)
		{
			var bullishCross = _prevDiff <= 0m && diff > 0m;
			var bearishCross = _prevDiff >= 0m && diff < 0m;

			if (bullishCross && candle.ClosePrice > sma50)
			{
				BuyMarket();
				_cooldownRemaining = SignalCooldownBars;
			}
			else if (bearishCross && candle.ClosePrice < sma50)
			{
				SellMarket();
				_cooldownRemaining = SignalCooldownBars;
			}
		}

		_prevDiff = diff;
	}
}