Открыть на GitHub

Стратегия MACD + стохастический фильтр тренда

Стратегия повторяет работу советника из папки MQL/7604. В оригинале использовался пользовательский индикатор с «зелёной» и «красной» линиями. Набор параметров (15, 3, 3) полностью совпадает с классическим стохастическим осциллятором, поэтому в версии для StockSharp применяется стандартный Stochastic, а подтверждение направления дополнительно контролируется MACD и экспоненциальной скользящей средней.

Алгоритм торгует в обе стороны. Для входа требуется пересечение %K и %D в сторону сделки, бычий или медвежий разворот гистограммы MACD с минимальным удалением от нулевой линии, а также подтверждение тренда по EMA. Управление рисками аналогично MQL-варианту: фиксированный стоп-лосс, тейк-профит и трейлинг-стоп в пунктах, который подтягивает защитный уровень при движении позиции в прибыль.

Индикаторы

  • MovingAverageConvergenceDivergenceSignal (fast = 12, slow = 26, signal = 9). Гистограмма должна пересечь сигнальную линию, оставаясь ниже нуля для покупок и выше нуля для продаж. Параметры MacdOpenLevel и MacdCloseLevel задают минимальное абсолютное значение гистограммы.
  • Stochastic (Length = 15, KPeriod = 3, DPeriod = 3). Линия %K соответствует «зелёному» буферу и должна быть выше %D для покупок (ниже для продаж). То же условие используется для выхода.
  • ExponentialMovingAverage с периодом 26. EMA служит трендовым фильтром: для длинной позиции текущая EMA должна быть выше предыдущей, для короткой — ниже.

Правила входа

  1. Покупка
    • На закрытой свече %K > %D.
    • MACD < 0 и выше сигнальной линии на текущем баре.
    • На предыдущем баре MACD < сигнальной линии (пересечение вверх происходит сейчас).
    • |MACD| > MacdOpenLevel * шаг_цены.
    • EMA растёт (текущее значение больше предыдущего).
  2. Продажа
    • На закрытой свече %K < %D.
    • MACD > 0 и ниже сигнальной линии на текущем баре.
    • На предыдущем баре MACD > сигнальной линии (пересечение вниз происходит сейчас).
    • MACD > MacdOpenLevel * шаг_цены.
    • EMA падает (текущее значение меньше предыдущего).

Если позиция уже открыта, новые заявки не выставляются до её закрытия.

Правила выхода

При активной позиции постоянно контролируются следующие условия:

  • Индикаторный выход
    • Для длинной позиции: %K < %D, MACD > 0, MACD < сигнала, а на предыдущем баре MACD был выше сигнальной линии; дополнительно |MACD| > MacdCloseLevel * шаг_цены.
    • Для короткой позиции: %K > %D, MACD < 0, MACD > сигнала, на предыдущем баре MACD был ниже сигнальной линии; |MACD| > MacdCloseLevel * шаг_цены.
  • Стоп-лосс по параметру StopLossPoints (переводится в цену через PriceStep).
  • Тейк-профит по параметру TakeProfitPoints.
  • Трейлинг-стоп: после движения в прибыль на TrailingStopPoints * PriceStep стоп подтягивается к цене, фиксируя минимум указанного результата.

Параметры

Имя Описание Значение по умолчанию
TradeVolume Объём сделки в лотах 0.1
TakeProfitPoints Тейк-профит в пунктах 10
StopLossPoints Стоп-лосс в пунктах 50
TrailingStopPoints Дистанция трейлинг-стопа 5
MacdOpenLevel Минимальная гистограмма MACD для входа 3
MacdCloseLevel Минимальная гистограмма MACD для выхода 2
MacdFastPeriod Быстрая EMA в MACD 12
MacdSlowPeriod Медленная EMA в MACD 26
MacdSignalPeriod EMA сигнальной линии 9
EmaPeriod Период EMA-фильтра 26
StochasticLength Окно стохастика 15
StochasticKPeriod Сглаживание %K 3
StochasticDPeriod Сглаживание %D 3
CandleType Таймфрейм расчёта 15m

Особенности

  • Расчёты выполняются только на завершённых свечах, что соответствует циклу start() в MQL.
  • Один пункт определяется свойством PriceStep инструмента; при отсутствии шага цена считается равной 1.
  • Используется исключительно высокоуровневый API StockSharp: индикаторы подключаются через BindEx, исторические массивы вручную не создаются, заявки отправляются методами BuyMarket и SellMarket.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MACD strategy with stochastic confirmation and EMA trend filter.
/// Buy when MACD crosses above signal with stochastic K > D and price above EMA.
/// Sell when MACD crosses below signal with stochastic K < D and price below EMA.
/// </summary>
public class MacdStochasticFilterStrategy : Strategy
{
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _stochKLength;
	private readonly StrategyParam<int> _stochDLength;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevMacd;
	private decimal? _prevSignal;

	/// <summary>
	/// Fast EMA period inside MACD.
	/// </summary>
	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period inside MACD.
	/// </summary>
	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	/// <summary>
	/// Signal line period for MACD.
	/// </summary>
	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	/// <summary>
	/// Trend EMA period used as directional filter.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %K length.
	/// </summary>
	public int StochKLength
	{
		get => _stochKLength.Value;
		set => _stochKLength.Value = value;
	}

	/// <summary>
	/// Stochastic %D length.
	/// </summary>
	public int StochDLength
	{
		get => _stochDLength.Value;
		set => _stochDLength.Value = value;
	}

	/// <summary>
	/// Type of candles used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public MacdStochasticFilterStrategy()
	{
		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
			.SetDisplay("MACD Fast", "Fast EMA period for MACD", "Indicators");

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetDisplay("MACD Slow", "Slow EMA period for MACD", "Indicators");

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
			.SetDisplay("MACD Signal", "Signal EMA period for MACD", "Indicators");

		_emaPeriod = Param(nameof(EmaPeriod), 26)
			.SetDisplay("Trend EMA", "EMA period for trend filter", "Indicators");

		_stochKLength = Param(nameof(StochKLength), 14)
			.SetDisplay("Stochastic K", "Look-back length for stochastic K", "Indicators");

		_stochDLength = Param(nameof(StochDLength), 3)
			.SetDisplay("Stochastic D", "Smoothing for D line", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for price data", "General");
	}

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

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

		_prevMacd = null;
		_prevSignal = null;
	}

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

		_prevMacd = null;
		_prevSignal = null;

		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFastPeriod },
				LongMa = { Length = MacdSlowPeriod }
			},
			SignalMa = { Length = MacdSignalPeriod }
		};

		var stochastic = new StochasticOscillator
		{
			K = { Length = StochKLength },
			D = { Length = StochDLength }
		};

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(macd, stochastic, ema, ProcessCandle)
			.Start();
	}

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

		if (!macdValue.IsFinal || !stochValue.IsFinal || !emaValue.IsFinal)
			return;

		if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macdData)
			return;

		if (macdData.Macd is not decimal macd || macdData.Signal is not decimal signal)
			return;

		if (stochValue is not StochasticOscillatorValue stochData)
			return;

		if (stochData.K is not decimal kVal || stochData.D is not decimal dVal)
			return;

		var emaVal = emaValue.ToDecimal();

		if (_prevMacd is not decimal prevMacd || _prevSignal is not decimal prevSignal)
		{
			_prevMacd = macd;
			_prevSignal = signal;
			return;
		}

		// MACD crossover signals
		var macdBullishCross = prevMacd <= prevSignal && macd > signal;
		var macdBearishCross = prevMacd >= prevSignal && macd < signal;

		// Long: MACD bullish cross + stochastic K > D + price above EMA
		if (Position <= 0 && macdBullishCross && kVal > dVal && candle.ClosePrice > emaVal)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Short: MACD bearish cross + stochastic K < D + price below EMA
		else if (Position >= 0 && macdBearishCross && kVal < dVal && candle.ClosePrice < emaVal)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevMacd = macd;
		_prevSignal = signal;
	}
}