Открыть на GitHub

Стратегия MACD Signal ATR

Обзор

Стратегия MACD Signal — это порт советника MetaTrader MACD_signal.mq4 на платформу StockSharp. В исходном коде сравнивался гистограммный столбик MACD с волатильностным диапазоном на основе ATR: когда столбик пробивал границу диапазона, открывалась единственная рыночная позиция. C#-версия воспроизводит тот же импульсный подход на высокоуровневом API StockSharp, хранит предыдущие значения гистограммы и ATR в приватных полях и снабжена подробными англоязычными комментариями.

В отличие от MetaTrader, где ордера модифицировались напрямую, StockSharp работает с неттинговым учётом. Поэтому стратегия сначала закрывает текущую позицию перед сменой направления и управляет трейлинг-стопом внутри кода, не полагаясь на внешние OrderModify.

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

  1. Подписаться на свечную серию, указанную параметром CandleType, и обрабатывать только закрытые свечи.
  2. Подать цены закрытия в индикатор MovingAverageConvergenceDivergenceSignal с заданными периодами EMA. Значение гистограммы (MACD - signal) фиксируется на каждой завершённой свече.
  3. Рассчитать AverageTrueRange на тех же свечах. Значение предыдущей свечи умножается на ThresholdMultiplier, что соответствует формуле rr = ATR * LEVEL из MQL.
  4. Сформировать бычий сигнал, когда текущая гистограмма выше +threshold, а предыдущая была ниже. Если счёт пуст или находится в шорте, а свойство Direction разрешает покупки, отправить рыночный ордер объёмом TradeVolume.
  5. Сформировать медвежий сигнал, когда гистограмма уходит ниже -threshold, оставаясь выше на прошлой свече. Если счёт пуст или в лонге и разрешены продажи, отправить рыночный ордер на TradeVolume.
  6. Управлять открытой позицией на каждом баре:
    • закрывать лонг при переходе гистограммы в отрицательную зону и шорт — при переходе в положительную;
    • контролировать фиксированный тейк-профит TakeProfitPoints, сравнивая его с максимумом/минимумом свечи;
    • активировать трейлинг-стоп после прохождения цены на TrailingStopPoints в прибыльную сторону и закрывать позицию, если свеча возвращается к пересчитанному уровню. Для лонга используется цена закрытия как аналог бид-цены, для шорта — как аналог аск-цены.
  7. Если TakeProfitPoints меньше исторического порога в 10 пунктов, стратегия не торгует, повторяя защитную проверку из MQL.

Управление рисками

  • Одна позиция за раз. Стратегия всегда выходит в ноль перед открытием нового направления, что копирует условие OrdersTotal() < 1.
  • Фиксированный объём. Параметр TradeVolume заменяет mql-переменную Lots и также назначается в Strategy.Volume, чтобы ручные действия в интерфейсе использовали тот же размер.
  • Фиксированный тейк-профит. TakeProfitPoints переводит шаги цены в реальные величины через Security.PriceStep.
  • Выход по индикатору. Смена знака гистограммы приводит к мгновенному рыночному закрытию позиции.
  • Трейлинг-стоп. После достаточного движения стоп подтягивается внутрь зоны прибыли и никогда не отступает назад.

Параметры

Имя Тип Значение по умолчанию Описание
TradeVolume decimal 10 Объём каждой рыночной заявки; дополнительно назначается в Strategy.Volume.
TakeProfitPoints int 10 Расстояние до фиксированного тейк-профита в шагах цены. Значения ниже 10 блокируют
торговлю.
TrailingStopPoints int 25 Шаги цены для трейлинг-стопа. 0 отключает сопровождение.
FastPeriod int 9 Период быстрой EMA в MACD.
SlowPeriod int 15 Период медленной EMA в MACD.
SignalPeriod int 8 Период сглаживания сигнальной линии MACD.
ThresholdMultiplier decimal 0.004 Множитель для ATR предыдущей свечи, формирующий границу прорыва.
AtrPeriod int 200 Количество свечей для расчёта ATR.
CandleType DataType таймфрейм 30 минут Основной используемый временной интервал.

Отличия от оригинального советника

  • В MetaTrader использовалась проверка AccountFreeMargin(). В StockSharp аналогичной информации нет, поэтому проверка опущена, а контроль риска рекомендуется вынести на уровень портфеля.
  • В MQL стоп-заявки обновлялись через OrderModify. В портированной версии управление выходами выполняется внутри стратегии по данным свечи и внутренним переменным трейлинга.
  • MQL следил за количеством исторических баров (Bars < 100). В StockSharp индикаторы через BindEx автоматически ждут накопления истории, поэтому отдельная проверка не нужна.
  • Предыдущие значения ATR и гистограммы сохраняются в полях, что позволяет повторить сравнение Delta и Delta1, не нарушая запрета на прямой доступ к индикаторам по индексу.

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

  • Проверьте корректность Security.PriceStep, Security.MinVolume и Security.VolumeStep, чтобы переводы пунктов и объёма соответствовали биржевым требованиям.
  • Увеличивайте ThresholdMultiplier или AtrPeriod, если стратегия слишком активно торгует во флэте; уменьшайте их, чтобы быстрее реагировать на расширение волатильности.
  • Для высокорискованных инструментов уменьшайте TradeVolume, поскольку оригинальный советник был рассчитан на крупные форекс-лоты.
  • Используйте свойство Direction совместно с фильтрами старшего таймфрейма, если нужно разрешать только длинные или только короткие сделки в определённых рыночных условиях.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MACD Signal ATR: EMA crossover with ATR stops.
/// </summary>
public class MacdSignalAtrStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public MacdSignalAtrStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_fastEmaLength = Param(nameof(FastEmaLength), 12)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
		_slowEmaLength = Param(nameof(SlowEmaLength), 26)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
	public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }

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

		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fastEma, slowEma, atr, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow) { _entryPrice = close; BuyMarket(); }
			else if (fastVal < slowVal && _prevFast >= _prevSlow) { _entryPrice = close; SellMarket(); }
		}
		_prevFast = fastVal; _prevSlow = slowVal;
	}
}