Открыть на GitHub

Стратегия EMA LWMA RSI

Обзор

EMA LWMA RSI Strategy переносит советник MetaTrader «EMA LWMA RSI» на платформу StockSharp. Стратегия сравнивает две скользящие средние, использующие одинаковый тип цены и при необходимости смещение вперёд, а также подтверждает импульс фильтром RSI. Алгоритм реагирует только на полностью сформированные свечи выбранного таймфрейма и работает с одной совокупной позицией: перед открытием новой сделки в противоположную сторону он закрывает текущую позицию. Расстояния стоп-лосса и тейк-профита задаются в пунктах и автоматически масштабируются относительно шага цены инструмента.

Логика торговли

  1. Рассчитываются экспоненциальная (EMA) и линейно-взвешенная (LWMA) скользящие средние с отдельными периодами, но общим типом цены. Если MaShift больше нуля, обе средние сдвигаются вперёд на указанное количество баров, полностью повторяя параметр shift из MetaTrader.
  2. RSI рассчитывается по собственному типу цены. Классический порог 50 отделяет бычий импульс от медвежьего.
  3. После закрытия свечи:
    • Покупка активируется, когда EMA переходит выше LWMA (предыдущее значение EMA было больше LWMA, а текущее стало ниже) и значение RSI находится выше 50.
    • Продажа активируется, когда EMA переходит ниже LWMA (предыдущее значение EMA было ниже LWMA, а текущее стало выше) и RSI опускается ниже 50.
  4. Сигналы выставляют внутренние флаги ожидания. Перед разворотом стратегия сначала вызывает ClosePosition(), чтобы ликвидировать открытую позицию. После подтверждения сделки незамедлительно отправляется рыночный ордер в требуемом направлении. Такой механизм соответствует оригинальному советнику, который ждал подтверждения транзакции.
  5. Защитные ордера запускаются через StartProtection. Если стоп-лосс или тейк-профит отключён (значение равно нулю), соответствующая нога пропускается — поведение полностью повторяет MQL.

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

  • Выбор типа цены повторяет перечисление MetaTrader (Close, Open, High, Low, Median, Typical, Weighted, Average). Взвешенная цена вычисляется как (High + Low + 2 * Close) / 4, что соответствует PRICE_WEIGHTED.
  • Размер «пункта» определяется автоматически: при 3- и 5-знаковых форекс-символах шаг цены умножается на 10, чтобы один pip соответствовал 10 пунктам котировки.
  • Для подписки на свечи используется высокоуровневый API StockSharp. Смещение реализовано через индикаторы Shift, без ручного доступа к буферам.
  • Логика удерживает булевы флаги ожидающих покупок/продаж. Они предотвращают повторные заявки, пока предыдущий ордер в работе, и очищаются после сделок или когда позиция уже соответствует сигналу.
  • На ценовой панели отображаются свечи и обе скользящие, а RSI рисуется на отдельной области графика.

Параметры

Параметр Тип Значение по умолчанию Описание
CandleType DataType Таймфрейм 1 час Серия свечей, которую анализирует стратегия.
StopLossPips int 150 Дистанция стоп-лосса в пунктах. 0 — отключить.
TakeProfitPips int 150 Дистанция тейк-профита в пунктах. 0 — отключить.
EmaPeriod int 28 Период экспоненциальной средней.
LwmaPeriod int 8 Период линейно-взвешенной средней.
MaShift int 0 Смещение (в барах), применяемое к обеим средним.
RsiPeriod int 14 Период усреднения RSI.
MaAppliedPrice AppliedPriceType Weighted Тип цены для EMA и LWMA.
RsiAppliedPrice AppliedPriceType Weighted Тип цены для RSI.

Использование

  1. Привяжите стратегию к нужному инструменту и задайте CandleType, совпадающий с таймфреймом оригинального советника.
  2. При необходимости скорректируйте защитные дистанции в пунктах и параметры индикаторов под условия брокера.
  3. После запуска подписки включите торговлю. Стратегия всегда поддерживает только одну позицию и использует ClosePosition() перед сменой направления.

Python-версия стратегии пока отсутствует.

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 EmaLwmaRsiStrategy : 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 EmaLwmaRsiStrategy()
	{
		_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;
	}
}