Открыть на GitHub

Стратегия Three EMA

Общее описание

Стратегия повторяет эксперта MetaTrader "ThreeEMA", который использует три экспоненциальные скользящие средние (EMA) разных периодов. Сигналы формируются при строгом упорядочении средних на закрытии свечи: когда быстрая EMA находится выше средней, а средняя выше медленной, стратегия открывает или удерживает длинную позицию. Если порядок обращается (быстрая ниже средней, а средняя ниже медленной), открывается короткая позиция. Защитные стоп‑лосс и тейк‑профит берутся из оригинального MQL и задаются в пунктах относительно шага цены инструмента.

Поведение оригинального MQL

В MQL-версии создавались три индикатора EMA с периодами FastPeriod, MediumPeriod, SlowPeriod. На каждой закрытой свече выполнялись проверки:

  • Открытие лонга / закрытие шорта при условии FastEMA > MediumEMA > SlowEMA.
  • Открытие шорта / закрытие лонга при условии FastEMA < MediumEMA < SlowEMA.
  • Стоп‑лосс и тейк‑профит задавались фиксированными расстояниями в пунктах от цены открытия.

Заявки отправлялись по рынку, объём рассчитывался модулем фиксированных лотов, трейлинг отключался.

Особенности реализации в StockSharp

  • Используется высокоуровневый API подписки на свечи. Три индикатора ExponentialMovingAverage привязаны к одной подписке, поэтому на каждую завершённую свечу поступают готовые значения EMA.
  • Проверки выполняются только на полностью сформированных свечах, что исключает внутрибрасковый шум.
  • При появлении направленного стека индикаторов стратегия отменяет активные заявки, закрывает противоположную позицию (если она есть) и открывает новую рыночную сделку в требуемом направлении.
  • Метод StartProtection переводит заданные в пунктах стоп‑лосс и тейк‑профит в абсолютное изменение цены с учётом PriceStep, полностью повторяя защитную логику оригинального советника.
  • При наличии графика отображаются свечи и все три EMA, что помогает визуально контролировать сигналы.

Параметры

Имя Значение по умолчанию Описание
CandleType Таймфрейм 1 минута Таймфрейм подписки, на которой рассчитываются EMA.
FastPeriod 5 Период быстрой EMA. Должен быть меньше MediumPeriod.
MediumPeriod 12 Период средней EMA. Должен находиться между быстрым и медленным значениями.
SlowPeriod 24 Период медленной EMA. Должен быть максимальным.
StopLossPoints 400 Расстояние защитного стоп‑лосса в пунктах (конвертируется в цену через PriceStep). Ноль отключает защиту.
TakeProfitPoints 900 Расстояние тейк‑профита в пунктах (конвертируется в цену через PriceStep). Ноль отключает защиту.

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

  1. Перед запуском задайте требуемый объём через свойство Volume, так как оригинальный эксперт работал с фиксированными лотами.
  2. Следите, чтобы периоды EMA шли в строгом порядке возрастания — при нарушении порядка OnStarted выбросит исключение, аналогично проверкам из MQL.
  3. Логика всегда переворачивает позицию при смене порядка EMA, поэтому стратегия практически всегда находится в рынке, пока стек индикаторов меняет направление.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class ThreeEmaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ThreeEmaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}