Открыть на GitHub

Стратегия LCS MACD Trader

Стратегия представляет собой портирование советника "LCS-MACD-Trader" для MetaTrader 4 на StockSharp. Торговля ведётся по пересечениям MACD ниже/выше нулевой линии с опциональным подтверждением индикатором Стохастик. Реализованы исходные торговые сессии и логика трейлинга/перевода в безубыток, характерная для MetaTrader.

Логика работы

  • Покупка выполняется, когда линия MACD пересекает сигнал снизу в отрицательной зоне. При активном фильтре требуется, чтобы за указанное количество свечей %D находилась выше %K и на текущей свече вновь опустилась ниже.
  • Продажа выполняется, когда линия MACD пересекает сигнал сверху в положительной зоне. При включённом фильтре %D должна недавно быть ниже %K и на текущей свече подняться обратно выше неё.
  • Сделки разрешены только внутри трёх настраиваемых внутридневных окон, соответствующих настройкам оригинального советника.
  • Значения стопов, тейк-профита, перевода в безубыток и трейлинга задаются в пунктах и автоматически переводятся в цену согласно шагу инструмента.
  • StockSharp использует неттинг: одновременное удержание разнонаправленных позиций не поддерживается. Наращивание в сторону сигнала разрешено вплоть до лимита MaxOrders, противоположные сигналы ожидают закрытия текущей позиции правилами управления риском.

Параметры

Имя Описание По умолчанию
CandleType Тип свечей для расчётов. Таймфрейм 15 минут
FastEmaPeriod Период быстрой EMA в MACD. 12
SlowEmaPeriod Период медленной EMA в MACD. 26
SignalPeriod Период сигнальной линии MACD. 9
UseStochasticFilter Требовать подтверждение стохастиком. true
BarsToCheckStochastic Количество свечей, в течение которых должна наблюдаться противоположная связь %K/%D. 5
StochasticKPeriod Период расчёта %K. 5
StochasticDPeriod Период сглаживания %D. 3
StochasticSlowing Дополнительное сглаживание %K. 3
TradeVolume Объём сделки. 0.1
TakeProfitPips Тейк-профит в пунктах. 100
StopLossPips Стоп-лосс в пунктах. 100
MaxOrders Максимальное количество добавлений к позиции. 5
EnableTrailing Включить трейлинг стоп. false
TrailingActivationPips Прибыль для активации трейлинга (в пунктах). 50
TrailingDistancePips Расстояние трейлинг-стопа (в пунктах). 25
BreakEvenActivationPips Прибыль для перевода в безубыток. 25
BreakEvenOffsetPips Добавочные пункты при установке стопа в безубыток. 1
Session1Start/End, Session2Start/End, Session3Start/End Внутридневные торговые окна. 08:15-08:35, 13:45-14:42, 22:15-22:45

Примечания

  • Стратегия рассчитана на неттинговый режим работы. Как и в оригинальном советнике, закрытие позиций происходит за счёт стопов, перевода в безубыток и трейлинга, но без одновременных разнонаправленных сделок.
  • Конвертация пунктов автоматически учитывает шаг цены инструмента и корректно масштабирует значения для пятизначных валютных пар.
  • Проверка условий трейлинга и безубытка выполняется на закрытых свечах, при этом используются максимум и минимум свечи для имитации тикового исполнения MetaTrader.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// LCS MACD Trader: MACD zero-line cross with RSI filter and ATR stops.
/// </summary>
public class LcsMacdTraderStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevMacd;
	private decimal _entryPrice;

	public LcsMacdTraderStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).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");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI 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 RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

		_prevMacd = 0;
		_entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevMacd = 0;
		_entryPrice = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, rsi, 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 rsiVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var macd = fastVal - slowVal;

		if (_prevMacd == 0 || atrVal <= 0)
		{
			_prevMacd = macd;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || macd < 0)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || macd > 0)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (macd > 0 && _prevMacd <= 0 && rsiVal > 45)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (macd < 0 && _prevMacd >= 0 && rsiVal < 55)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevMacd = macd;
	}
}