Открыть на GitHub

Simple MACD

Стратегия Simple MACD — это перенос советника MQL5 Simple_MACD.mq5 в инфраструктуру StockSharp. Алгоритм отслеживает наклон основной линии MACD на закрытых свечах и накапливает позицию в направлении текущего тренда.

Обзор

  • Рынки: валюты, акции, фьючерсы и любые инструменты с регулярными свечными данными.
  • Базовый индикатор: MACD с экспоненциальными средними 12 и 26 и сигнальной EMA 9.
  • Методика: анализ изменения основной линии MACD между двумя последними завершёнными барами. Рост линии => покупки, падение => продажи.
  • Типы ордеров: только рыночные. Объём заявки включает ликвидацию противоположной позиции и добавление заданного объёма, как в оригинальном советнике.

Особенности конверсии

  • В MQL5 сравнивались значения MACD(1) и MACD(2) на открытии новой свечи. В StockSharp сравнение выполняется после закрытия свечи, поэтому сигнал появляется до начала следующего бара.
  • Проверка хеджингового режима и расчёт допустимого объёма в MQL5 заменены на управление позицией через TradeVolume и методы BuyMarket/SellMarket.
  • StockSharp хранит чистую позицию, поэтому дополнительный обход всех открытых позиций не требуется.

Торговые правила

Вход и наращивание позиции

  1. Для каждой закрытой свечи рассчитывается значение основной линии MACD.
  2. Хранятся два последних значения индикатора.
  3. Если MACD(1) > MACD(2):
    • Покупается объём TradeVolume + max(0, -Position) (закрываются шорты и добавляется новая длинная позиция).
  4. Если MACD(1) < MACD(2):
    • Продаётся объём TradeVolume + max(0, Position) (закрываются лонги и открывается новая короткая позиция).
  5. При равенстве значений новых ордеров нет.

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

  • Позиция наращивается при каждом сигнале, пока наклон MACD сохраняется в ту же сторону.
  • При смене направления сначала закрывается существующая позиция, затем формируется новая в противоположную сторону.
  • В стратегии нет встроенных стопов и тейков — контроль риска выполняется внешними средствами.

Дополнительные защиты

  • Сделки возможны только после формирования индикатора MACD.
  • Обрабатываются только завершённые свечи (CandleStates.Finished).
  • В журнал записываются значения MACD, по которым был принят торговый сигнал.

Параметры

Параметр Значение по умолчанию Описание
FastPeriod 12 Период быстрой EMA в MACD.
SlowPeriod 26 Период медленной EMA в MACD.
SignalPeriod 9 Период сигнальной EMA, сохранённый для совместимости с оригиналом.
TradeVolume 0.1 Объём, добавляемый к позиции при каждом сигнале.
CandleType Таймфрейм 1 минута Тип свечей, на основе которых рассчитывается MACD.

Все параметры доступны для оптимизации.

Визуализация

  • При наличии чартов создаётся область с ценовыми свечами, где отображается MACD и сделки стратегии.
  • Это помогает увидеть, как часто стратегия наращивает позицию при устойчивых трендах.

Рекомендации по использованию

  • Лучше применять на трендовых участках рынка. На флэте стратегия может часто разворачиваться и генерировать лишние сделки.
  • Используйте внешние ограничения по риску, так как стоп-лосс не предусмотрен.
  • Рекомендуется оптимизировать параметры MACD и торговый объём под конкретный инструмент и таймфрейм.

Состав папки

  • CS/SimpleMacdStrategy.cs — реализация стратегии на C#.
  • README.md, README_ru.md, README_zh.md — документация на трёх языках.
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>
/// Simple MACD slope-following strategy converted from MQL5 Simple_MACD.mq5.
/// The strategy evaluates the MACD main line on completed candles and builds positions accordingly.
/// </summary>
public class SimpleMacdStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private MovingAverageConvergenceDivergence _macd = null!;
	private decimal? _previousMacdValue;
	private decimal? _prePreviousMacdValue;
	private int? _previousSlope;

	/// <summary>
	/// Fast EMA period used for the MACD main line.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period used for the MACD main line.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Signal EMA period used by the MACD indicator.
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	/// <summary>
	/// Trading volume applied when new orders are sent.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Candle type used to feed the MACD indicator.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize Simple MACD strategy with default parameters.
	/// </summary>
	public SimpleMacdStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetDisplay("MACD Fast Period", "Fast EMA length for MACD calculation", "Indicators")
			
			.SetOptimize(6, 18, 2);

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetDisplay("MACD Slow Period", "Slow EMA length for MACD calculation", "Indicators")
			
			.SetOptimize(20, 40, 2);

		_signalPeriod = Param(nameof(SignalPeriod), 9)
			.SetDisplay("MACD Signal Period", "Signal EMA length maintained for compatibility", "Indicators")
			
			.SetOptimize(6, 18, 1);

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetDisplay("Trade Volume", "Order volume used for each signal", "Risk")
			
			.SetOptimize(0.1m, 1m, 0.1m);

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

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

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

		_previousMacdValue = null;
		_prePreviousMacdValue = null;
		_previousSlope = null;
	}

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

		// Configure MACD indicator to match the source MQL strategy settings.
		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = FastPeriod },
			LongMa = { Length = SlowPeriod },
		};

		// Subscribe to candle data and bind the MACD indicator.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_macd, ProcessCandle)
			.Start();

		// Prepare visual elements when charts are available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _macd);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
	{
		// React only to completed candles to avoid premature decisions.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure the indicator produced a valid value.
		if (!_macd.IsFormed || !macdValue.IsFinal)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var macdLine = macdValue.ToDecimal();

		// Accumulate historical MACD values for slope calculations.
		if (_previousMacdValue is null)
		{
			_previousMacdValue = macdLine;
			return;
		}

		if (_prePreviousMacdValue is null)
		{
			_prePreviousMacdValue = _previousMacdValue;
			_previousMacdValue = macdLine;
			return;
		}

		var macdPrev = _previousMacdValue.Value;
		var macdPrevPrev = _prePreviousMacdValue.Value;

		var currentSlope = macdPrev > macdPrevPrev ? 1 : macdPrev < macdPrevPrev ? -1 : 0;

		if (currentSlope == 0)
		{
			_prePreviousMacdValue = _previousMacdValue;
			_previousMacdValue = macdLine;
			return;
		}

		if (_previousSlope == currentSlope)
		{
			_prePreviousMacdValue = _previousMacdValue;
			_previousMacdValue = macdLine;
			return;
		}

		if (currentSlope > 0)
		{
			// Close shorts and open (or add to) longs when the MACD slope turns positive.
			var volumeToBuy = TradeVolume + Math.Max(0m, -Position);
			if (volumeToBuy > 0m)
			{
				BuyMarket(volumeToBuy);
				LogInfo($"Bullish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Buying {volumeToBuy}.");
			}
		}
		else
		{
			// Close longs and open (or add to) shorts when the MACD slope turns negative.
			var volumeToSell = TradeVolume + Math.Max(0m, Position);
			if (volumeToSell > 0m)
			{
				SellMarket(volumeToSell);
				LogInfo($"Bearish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Selling {volumeToSell}.");
			}
		}

		// Update stored values so the next candle compares the two previous MACD readings.
		_previousSlope = currentSlope;
		_prePreviousMacdValue = _previousMacdValue;
		_previousMacdValue = macdLine;
	}
}