Открыть на GitHub

Стратегия MACD Stochastic 2

Стратегия переносит логику советника MetaTrader «MACD Stochastic 2» на высокоуровневый API StockSharp. Используется сочетание трехбарного фильтра по MACD и осциллятора Stochastic для поиска разворотных моментов возле зон перепроданности и перекупленности. Управление рисками реализовано через отдельные стопы и тейк-профиты для лонга и шорта, а также опциональный трейлинг-стоп в пунктах.

Обзор

  • Работает с любым инструментом и таймфреймом, который задается параметром CandleType.
  • MACD анализируется по основной линии, при этом сигнальная линия и гистограмма доступны для отображения на графике.
  • Входы подтверждаются значением %K Stochastic ниже 20 для покупок и выше 80 для продаж.
  • Подсчет пункта имитирует исходную реализацию: берем PriceStep инструмента и умножаем его на 10 для котировок с 3 или 5 знаками после запятой.

Логика торговли

Вход в лонг

  1. Значения основной линии MACD для текущей и двух предыдущих завершенных свечей находятся ниже нуля.
  2. Текущее значение MACD выше предыдущего, а предыдущее меньше значения двух свечей назад (локальный минимум).
  3. %K Stochastic ниже 20 (зона перепроданности).
  4. Открытая короткая позиция закрывается, после чего открывается новая длинная при Position <= 0.

Вход в шорт

  1. Значения основной линии MACD для текущей и двух предыдущих завершенных свечей находятся выше нуля.
  2. Текущее значение MACD ниже предыдущего, а предыдущее выше значения двух свечей назад (локальный максимум).
  3. %K Stochastic выше 80 (зона перекупленности).
  4. Открытая длинная позиция закрывается, после чего открывается новая короткая при Position >= 0.

Управление позицией

  • Жесткий стоп / тейк: Для лонга и шорта задаются отдельные расстояния в пунктах. При нуле соответствующий уровень отключается.
  • Трейлинг-стоп: При включении активируется после прохождения ценой заданного расстояния и сдвигается только при превышении шага трейлинга, что ограничивает частые модификации.
  • Противосигнал: При появлении обратного сигнала текущая позиция закрывается и открывается новая в противоположную сторону с заданным объемом.

Параметры

Параметр По умолчанию Описание
TradeVolume 1 Объем ордера для новых сделок.
StopLossBuyPips 50 Дистанция стоп-лосса для лонга в пунктах (0 — отключить).
StopLossSellPips 50 Дистанция стоп-лосса для шорта в пунктах (0 — отключить).
TakeProfitBuyPips 50 Дистанция тейк-профита для лонга в пунктах (0 — отключить).
TakeProfitSellPips 50 Дистанция тейк-профита для шорта в пунктах (0 — отключить).
TrailingStopPips 0 Дистанция трейлинг-стопа в пунктах. Ноль отключает трейлинг.
TrailingStepPips 5 Минимальный прирост в пунктах для сдвига трейлинг-стопа. При включенном трейлинге должен быть положительным.
MacdFastPeriod 12 Быстрая EMA в расчете MACD.
MacdSlowPeriod 26 Медленная EMA в расчете MACD.
MacdSignalPeriod 9 Период сигнальной линии MACD.
StochasticKPeriod 5 Длина окна для %K Stochastic.
StochasticDPeriod 3 Период сглаживания %D Stochastic.
StochasticSlowing 3 Дополнительное сглаживание %K Stochastic.
CandleType таймфрейм 1ч Тип свечей (таймфрейм) для расчета индикаторов.

Примечания

  • Величина пункта вычисляется как PriceStep, умноженный на 10 для инструментов с 3 или 5 знаками после запятой, что соответствует логике оригинального советника.
  • Пороговые значения Stochastic (20/80) заданы в коде константами по аналогии с исходным файлом.
  • Все решения принимаются после формирования свечи, что соответствует работе советника на закрытии бара.

Использование

  1. Задайте инструмент, CandleType и объем перед запуском.
  2. Настройте стопы, тейк-профиты и трейлинг в соответствии с волатильностью инструмента.
  3. При необходимости используйте оптимизатор StockSharp для подбора параметров MACD и Stochastic.
  4. На графике автоматически отображаются свечи, индикаторы и сделки стратегии (при доступности области).
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>
/// MACD and stochastic based swing strategy.
/// Buys when MACD turns up in negative zone + stochastic is oversold.
/// Sells when MACD turns down in positive zone + stochastic is overbought.
/// </summary>
public class MacdStochastic2Strategy : Strategy
{
	private readonly StrategyParam<decimal> _oversoldThreshold;
	private readonly StrategyParam<decimal> _overboughtThreshold;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _stochasticKPeriod;
	private readonly StrategyParam<int> _stochasticDPeriod;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _macdPrev1;
	private decimal _macdPrev2;
	private int _macdCount;

	public decimal OversoldThreshold { get => _oversoldThreshold.Value; set => _oversoldThreshold.Value = value; }
	public decimal OverboughtThreshold { get => _overboughtThreshold.Value; set => _overboughtThreshold.Value = value; }
	public int MacdFastPeriod { get => _macdFastPeriod.Value; set => _macdFastPeriod.Value = value; }
	public int MacdSlowPeriod { get => _macdSlowPeriod.Value; set => _macdSlowPeriod.Value = value; }
	public int StochasticKPeriod { get => _stochasticKPeriod.Value; set => _stochasticKPeriod.Value = value; }
	public int StochasticDPeriod { get => _stochasticDPeriod.Value; set => _stochasticDPeriod.Value = value; }
	public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
	public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MacdStochastic2Strategy()
	{
		_oversoldThreshold = Param(nameof(OversoldThreshold), 20m)
			.SetDisplay("Oversold", "Stochastic oversold threshold", "Stochastic");

		_overboughtThreshold = Param(nameof(OverboughtThreshold), 80m)
			.SetDisplay("Overbought", "Stochastic overbought threshold", "Stochastic");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MACD Fast", "Fast EMA length for MACD", "MACD");

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

		_stochasticKPeriod = Param(nameof(StochasticKPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic K", "Lookback for %K", "Stochastic");

		_stochasticDPeriod = Param(nameof(StochasticDPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic D", "Smoothing for %D", "Stochastic");

		_stopLoss = Param(nameof(StopLoss), 1000m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 2000m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Take profit in price units", "Risk");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_macdPrev1 = 0m;
		_macdPrev2 = 0m;
		_macdCount = 0;
	}

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

		var macd = new MovingAverageConvergenceDivergence(
			new ExponentialMovingAverage { Length = MacdSlowPeriod },
			new ExponentialMovingAverage { Length = MacdFastPeriod });

		var rsi = new RelativeStrengthIndex { Length = StochasticKPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, (candle, rsiValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				// Process MACD manually
				var macdResult = macd.Process(candle.ClosePrice, candle.CloseTime, true);
				if (!macd.IsFormed)
					return;

				var macdValue = macdResult.ToDecimal();

				_macdCount++;
				if (_macdCount < 3)
				{
					_macdPrev2 = _macdPrev1;
					_macdPrev1 = macdValue;
					return;
				}

				var macd0 = macdValue;
				var macd1 = _macdPrev1;
				var macd2 = _macdPrev2;

				// Buy: MACD in negative zone turning up + stochastic oversold
				var longSignal = macd0 < 0m && macd1 < 0m && macd2 < 0m &&
					macd0 > macd1 && macd1 < macd2 &&
					rsiValue < OversoldThreshold;

				// Sell: MACD in positive zone turning down + stochastic overbought
				var shortSignal = macd0 > 0m && macd1 > 0m && macd2 > 0m &&
					macd0 < macd1 && macd1 > macd2 &&
					rsiValue > OverboughtThreshold;

				if (longSignal && Position <= 0)
					BuyMarket();
				else if (shortSignal && Position >= 0)
					SellMarket();

				_macdPrev2 = _macdPrev1;
				_macdPrev1 = macdValue;
			})
			.Start();

		StartProtection(
			new Unit(TakeProfit, UnitTypes.Absolute),
			new Unit(StopLoss, UnitTypes.Absolute));

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