Открыть на GitHub

Стратегия Moving Average Crossover Spread

Стратегия переносит MQL4-советник «EA - Moving Average» (файл EA - Moving Average.mq4) в инфраструктуру StockSharp. Она отслеживает пересечения двух экспоненциальных скользящих средних (EMA) и открывает сделку в начале каждой новой свечи, если условия входа выполняются.

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

  • Подписывается на поток свечей, задаваемый параметром CandleType, и рассчитывает на нём быструю и медленную EMA.
  • Анализирует только закрытые свечи: используются значения EMA предыдущей (shift=1) и позапрошлой (shift=2) свечей, что полностью повторяет логику вызовов iMA в оригинальном коде.
  • Покупка выполняется, если на предыдущей свече быстрая EMA расположилась выше медленной, а на позапрошлой — ниже, и при этом угол наклона остаётся восходящим.
  • Продажа выполняется, если на предыдущей свече быстрая EMA опустилась ниже медленной, а на позапрошлой оставалась выше, что соответствует «мертвому кресту».
  • Пока позиция не закрыта и нет активных заявок, новых ордеров не создаётся (OrdersTotal()==0 в терминах MetaTrader).

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

  • Перед подачей рыночного ордера стратегия проверяет текущий спред. Если доступны котировки best bid/ask, разница переводится в «пункты» через PriceStep и сравнивается с порогом MaxSpreadPoints. При превышении лимита сигнал пропускается, что соответствует проверке MarketInfo(..., MODE_SPREAD) в MQL4.
  • После отправки рыночного ордера автоматически активируются защитные уровни:
    • Стоп-лосс выставляется на значение медленной EMA предыдущей свечи плюс-минус StopLossPoints пунктов.
    • Тейк-профит рассчитывается зеркально относительно цены входа: расстояние до цели равно расстоянию до стопа (Ask + (Ask - StopLoss) / Bid - (StopLoss - Bid) из оригинала).
  • Любые расстояния, заданные в пунктах, конвертируются в абсолютные цены через Security.PriceStep, что обеспечивает идентичное поведение с настройками MetaTrader.

Особенности портирования

  • В исходном коде присутствует выбор типа скользящей средней, но значение по умолчанию — EMA (MAMode = 1). Перенос реализует именно этот вариант, чтобы не усложнять интерфейс параметров. При необходимости можно расширить стратегию дополнительными вариантами сглаживания.
  • Объём сделки задаётся параметром TradeVolume и синхронизируется со свойством Strategy.Volume в методе OnStarted.
  • Для соблюдения требований высокоуровневого API не используются дополнительные коллекции: хранится только пара последних значений EMA, необходимых для детекции пересечения.

Параметры

  • CandleType — тип и таймфрейм свечей, используемых в расчётах.
  • FastPeriod — период быстрой EMA (по умолчанию 21).
  • SlowPeriod — период медленной EMA (по умолчанию 84).
  • StopLossPoints — расстояние до стоп-лосса в пунктах относительно медленной EMA предыдущей свечи.
  • MaxSpreadPoints — максимально допустимый спред в пунктах.
  • TradeVolume — объём рыночных ордеров.

Практические рекомендации

  1. Перед запуском выберите нужный инструмент и таймфрейм, чтобы EMA в StockSharp совпадали с расчётами в терминале MetaTrader.
  2. Для корректной работы фильтра по спреду требуется поток Level1-котировок (best bid/ask). При отсутствии котировок фильтр считается пройденным.
  3. Проверьте, что у инструмента задан PriceStep. Без него стратегия не сможет пересчитать пункты в цены и пропустит установку стопов и тейков.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Moving average crossover strategy using two EMAs.
/// Enters long on golden cross, short on death cross.
/// </summary>
public class MovingAverageCrossoverSpreadStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public MovingAverageCrossoverSpreadStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle type", "General");

		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 50)
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

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

		var fastMa = new ExponentialMovingAverage { Length = FastPeriod };
		var slowMa = new ExponentialMovingAverage { Length = SlowPeriod };

		decimal? prevFast = null;
		decimal? prevSlow = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastMa, slowMa, (candle, fastValue, slowValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				if (prevFast.HasValue && prevSlow.HasValue)
				{
					var crossUp = prevFast.Value <= prevSlow.Value && fastValue > slowValue;
					var crossDown = prevFast.Value >= prevSlow.Value && fastValue < slowValue;

					if (crossUp && Position <= 0)
						BuyMarket();
					else if (crossDown && Position >= 0)
						SellMarket();
				}

				prevFast = fastValue;
				prevSlow = slowValue;
			})
			.Start();

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