Открыть на 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.
  • Перед открытием новой позиции противоположная экспозиция закрывается (аналог CLOSEORDEROPENORDER).

Параметры

Имя Описание
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;
	}
}