Открыть на GitHub

Стратегия MACD с подтверждением 1D Stochastic

Стратегия покупает при пересечении линии MACD выше сигнальной с подтверждением от дневного стохастика. Сделка закрывается при достижении стоп-лосса на основе ATR либо при падении цены ниже скользящей EMA, используемой как трейлинг-тейкпрофит.

Детали

  • Условия входа:
    • Лонг: MACD пересекает Signal снизу && DailyK > DailyD && DailyK < 80
  • Лонг/Шорт: Только лонг
  • Стопы: Стоп-лосс по ATR и трейлинг-тейкпрофит по EMA
  • Значения по умолчанию:
    • MacdFastLength = 12
    • MacdSlowLength = 26
    • MacdSignalLength = 9
    • TrailingEmaLength = 20
    • StopLossAtrMultiplier = 3.25m
    • TrailingActivationAtrMultiplier = 4.25m
    • CandleType = TimeSpan.FromHours(1).TimeFrame()
  • Фильтры:
    • Категория: Разворот
    • Направление: Лонг
    • Индикаторы: MACD, Stochastic, ATR, EMA
    • Стопы: Да
    • Сложность: Средняя
    • Таймфрейм: Среднесрочный
    • Сезонность: Нет
    • Нейросети: Нет
    • Дивергенция: Нет
    • Уровень риска: Средний
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that goes long on MACD crossover confirmed by daily Stochastic.
/// Uses ATR based stop loss and trailing EMA take profit.
/// </summary>
public class MacdStochasticConfirmationReversalStrategy : Strategy
{
	private readonly StrategyParam<int> _macdFast;
	private readonly StrategyParam<int> _macdSlow;
	private readonly StrategyParam<int> _macdSignal;
	private readonly StrategyParam<int> _trailingEmaLength;
	private readonly StrategyParam<decimal> _stopLossAtr;
	private readonly StrategyParam<decimal> _trailingActivationAtr;
	private readonly StrategyParam<DataType> _candleType;

	private MovingAverageConvergenceDivergenceSignal _macd;
	private AverageTrueRange _atr;
	private ExponentialMovingAverage _ema;
	private StochasticOscillator _dailyStoch;

	private decimal? _dailyK;
	private decimal? _dailyD;
	private decimal _prevMacd;
	private decimal _prevSignal;
	private bool _hasPrev;

	private decimal _stopLossLevel;
	private decimal _activationLevel;
	private bool _trailingActive;
	private decimal? _takeProfitLevel;
	private decimal _entryPrice;

	/// <summary>
	/// MACD fast EMA length.
	/// </summary>
	public int MacdFastLength { get => _macdFast.Value; set => _macdFast.Value = value; }

	/// <summary>
	/// MACD slow EMA length.
	/// </summary>
	public int MacdSlowLength { get => _macdSlow.Value; set => _macdSlow.Value = value; }

	/// <summary>
	/// MACD signal EMA length.
	/// </summary>
	public int MacdSignalLength { get => _macdSignal.Value; set => _macdSignal.Value = value; }

	/// <summary>
	/// Trailing EMA length.
	/// </summary>
	public int TrailingEmaLength { get => _trailingEmaLength.Value; set => _trailingEmaLength.Value = value; }

	/// <summary>
	/// ATR multiplier for stop loss.
	/// </summary>
	public decimal StopLossAtrMultiplier { get => _stopLossAtr.Value; set => _stopLossAtr.Value = value; }

	/// <summary>
	/// ATR multiplier to activate trailing.
	/// </summary>
	public decimal TrailingActivationAtrMultiplier { get => _trailingActivationAtr.Value; set => _trailingActivationAtr.Value = value; }

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

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public MacdStochasticConfirmationReversalStrategy()
	{
		_macdFast = Param(nameof(MacdFastLength), 12)
			.SetDisplay("MACD Fast", "Fast EMA length", "MACD");

		_macdSlow = Param(nameof(MacdSlowLength), 26)
			.SetDisplay("MACD Slow", "Slow EMA length", "MACD");

		_macdSignal = Param(nameof(MacdSignalLength), 9)
			.SetDisplay("MACD Signal", "Signal EMA length", "MACD");

		_trailingEmaLength = Param(nameof(TrailingEmaLength), 20)
			.SetDisplay("Trailing EMA", "EMA length for trailing take profit", "Strategy");

		_stopLossAtr = Param(nameof(StopLossAtrMultiplier), 3.25m)
			.SetDisplay("ATR Stop", "ATR multiplier for stop loss", "Strategy");

		_trailingActivationAtr = Param(nameof(TrailingActivationAtrMultiplier), 4.25m)
			.SetDisplay("ATR Activate", "ATR multiplier to activate trailing", "Strategy");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Base candle type", "Common");
	}

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

		_macd = null;
		_atr = null;
		_ema = null;
		_dailyStoch = null;

		_dailyK = null;
		_dailyD = null;
		_prevMacd = 0m;
		_prevSignal = 0m;
		_hasPrev = false;

		_stopLossLevel = 0m;
		_activationLevel = 0m;
		_trailingActive = false;
		_takeProfitLevel = null;
		_entryPrice = 0m;
	}

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

		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFastLength },
				LongMa = { Length = MacdSlowLength },
			},
			SignalMa = { Length = MacdSignalLength }
		};

		_atr = new AverageTrueRange { Length = 14 };
		_ema = new EMA { Length = TrailingEmaLength };
		_dailyStoch = new StochasticOscillator();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_macd, _atr, _ema, ProcessCandle)
			.Start();

		var dailySubscription = SubscribeCandles(TimeSpan.FromDays(1).TimeFrame());
		dailySubscription
			.BindEx(_dailyStoch, ProcessDaily)
			.Start();
	}

	private void ProcessDaily(ICandleMessage candle, IIndicatorValue stochValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var stoch = (StochasticOscillatorValue)stochValue;
		if (stoch.K is decimal k && stoch.D is decimal d)
		{
			_dailyK = k;
			_dailyD = d;
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue atrValue, IIndicatorValue emaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

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

		var atr = atrValue.ToDecimal();
		var ema = emaValue.ToDecimal();

		if (!_macd.IsFormed || _dailyK is null || _dailyD is null)
		{
			_prevMacd = macdLine;
			_prevSignal = signalLine;
			_hasPrev = true;
			return;
		}

		var crossUp = _hasPrev && _prevMacd <= _prevSignal && macdLine > signalLine;

		if (crossUp && _dailyK > _dailyD && _dailyK < 80m && Position <= 0)
		{
			BuyMarket();
			_entryPrice = candle.ClosePrice;
			_stopLossLevel = _entryPrice - StopLossAtrMultiplier * atr;
			_activationLevel = _entryPrice + TrailingActivationAtrMultiplier * atr;
			_trailingActive = false;
			_takeProfitLevel = null;
		}

		if (Position > 0)
		{
			if (!_trailingActive && candle.HighPrice > _activationLevel)
				_trailingActive = true;

			if (_trailingActive)
				_takeProfitLevel = ema;

			if ((_takeProfitLevel is decimal tp && candle.ClosePrice < tp) ||
				candle.LowPrice <= _stopLossLevel)
			{
				SellMarket(Position);
				_trailingActive = false;
				_takeProfitLevel = null;
			}
		}

		_prevMacd = macdLine;
		_prevSignal = signalLine;
		_hasPrev = true;
	}
}