Открыть на GitHub

Стратегия EMA WMA Risk

Обзор

  • Конвертация эксперта MetaTrader 4 «EMA WMA» автора Владимира Хлыстова.
  • Ищет разворотные сигналы по взаимному положению экспоненциальной (EMA) и взвешенной (WMA) скользящих средних, рассчитанных по ценам открытия свечей.
  • Автоматически подвязывает стоп-лосс и тейк-профит через StartProtection, что повторяет логику MT4.
  • Поддерживает риск-ориентированный расчет объёма, эквивалентный параметру risk, с возможностью задать фиксированный объём.

Логика оригинального советника

  • Эксперт работает на любом инструменте и таймфрейме, вычисляя сигналы один раз при появлении новой свечи (TimeBar).
  • Индикаторы используют PRICE_OPEN, поэтому реагируют на цену открытия бара.
  • Если EMA опускается ниже WMA при условии, что на предыдущей свече была выше, закрываются продажи и открывается покупка с заданными стопом и целью.
  • Если EMA поднимается выше WMA после нахождения ниже, закрываются покупки и открывается продажа.
  • Параметр risk определяет размер лота от свободной маржи и размера стоп-лосса.

Правила в StockSharp

  1. Подписка на выбранный тип свечей (CandleType, по умолчанию 30 минут). Обрабатываются только завершённые свечи.
  2. Значения открытия подаются в EMA и WMA вручную, ожидаем формирования обоих индикаторов.
  3. Бычий сигнал: предыдущая EMA > предыдущей WMA и текущая EMA < текущей WMA.
    • Закрываем шорты и открываем лонг с объёмом по правилам риска.
  4. Медвежий сигнал: предыдущая EMA < предыдущей WMA и текущая EMA > текущей WMA.
    • Закрываем лонги и открываем шорт.
  5. StartProtection оформляет рыночные защитные приказы, поэтому новая сделка сразу получает стоп и тейк в шагах цены.

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

  • RiskPercent имитирует параметр risk. Объём вычисляется по текущей стоимости портфеля, величине стопа и биржевым шагам цены/цены шага.
  • При отсутствии метаданных (нет PriceStep или StepPrice) используется абсолютное значение стопа.
  • Если RiskPercent равен нулю, нужно задать положительный OrderVolume.
  • Перед открытием новой позиции противоположная экспозиция закрывается (аналог CLOSEORDER → OPENORDER).

Параметры

Имя Описание
EmaPeriod Период экспоненциальной скользящей средней (28 по умолчанию).
WmaPeriod Период взвешенной скользящей средней (8 по умолчанию).
StopLossPoints Дистанция стоп-лосса в шагах цены (по умолчанию 50).
TakeProfitPoints Дистанция тейк-профита в шагах цены (по умолчанию 50).
RiskPercent Доля капитала под риск на сделку (по умолчанию 10%).
OrderVolume Фиксированный объём ордера; 0 — использовать расчёт по риску.
CandleType Тип/таймфрейм свечей для расчётов.

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

  • Значения EMA и WMA обновляются через DecimalIndicatorValue, чтобы гарантировать использование цены открытия, как в MT4.
  • Работаем по закрытым свечам: сигнал может появляться на бар позже относительно MT4, но исключает подсмотр в будущее.
  • Стоп и тейк задаются в шагах цены, что соответствует переменной Point из MetaTrader.
  • При наличии графической области отображаются свечи, обе скользящие и точки сделок.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// EMA WMA Crossover Risk: Dual EMA crossover with ATR stops.
/// </summary>
public class EmaWmaRiskStrategy : 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 EmaWmaRiskStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 21)
			.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;
	}

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

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

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