Открыть на GitHub

Стратегия STO M5xM15xM30

Обзор

Данная стратегия представляет собой конверсию советника MetaTrader 4 «STO_m5xm15xm30» на платформу StockSharp. Логика основана на согласованной работе трёх стохастических осцилляторов на таймфреймах M5, M15 и M30. Код использует высокоуровневый API, поэтому избавлен от ручной обработки заявок и полностью поддерживает оптимизацию параметров через StrategyParam.

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

  1. Мульти-таймфреймовое подтверждение
    • Основной стохастик (по умолчанию M5) должен сформировать бычье пересечение (%K выше %D).
    • Стохастики на M15 и M30 уже должны находиться в бычьей фазе (%K выше %D).
    • Для продаж условия зеркальны.
  2. Фильтр смещения
    • Используется значение стохастика на ShiftBars баров назад. Для покупки историческое значение %K должно быть ниже %D, что подтверждает свежий разворот. Для продажи — наоборот.
  3. Фильтр цены
    • Текущая свеча должна закрыться выше предыдущей (для покупок) или ниже (для продаж). Это точное соответствие проверке Close[0] > Close[1] в MQL.
  4. Вход
    • При отсутствии позиции и выполнении бычьих условий отправляется рыночная заявка на покупку с объёмом TradeVolume.
    • Если открыта противоположная позиция, она сначала закрывается, после чего открывается новая в соответствии с сигналом.
  5. Выход
    • Дополнительный стохастик M5 с периодом ExitKPeriod анализирует предыдущую свечу (shift = 1). Лонг закрывается при падении %K ниже %D, шорт — при подъёме %K выше %D.
    • После закрытия позиции повторный вход на той же свече не выполняется, что повторяет логику оригинального советника.

Подписки и индикаторы

  • Свечи основного таймфрейма (CandleType, по умолчанию M5).
  • Свечи подтверждения на M15 (MiddleCandleType).
  • Свечи подтверждения на M30 (SlowCandleType).
  • Все стохастики используют сглаживание %K = 3, %D = 3, как в исходном коде.

Параметры

Параметр Значение по умолчанию Описание
CandleType Свечи M5 Рабочий таймфрейм.
MiddleCandleType Свечи M15 Первое подтверждение.
SlowCandleType Свечи M30 Второе подтверждение.
FastKPeriod 5 Период %K основного стохастика.
MiddleKPeriod 5 Период %K на M15.
SlowKPeriod 5 Период %K на M30.
ExitKPeriod 5 Период %K выходного стохастика.
ShiftBars 3 Количество баров между текущим и контрольным пересечением.
TakeProfitPoints 30 Размер тейк-профита в пунктах.
StopLossPoints 10 Размер стоп-лосса в пунктах.
TradeVolume 0.1 Объём новой позиции.

Все параметры доступны для оптимизации в Designer и сохраняются в настройках стратегии.

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

Метод StartProtection() автоматически преобразует параметры TP и SL из советника в защитные заявки StockSharp. Чтобы отключить защиту, установите значения в ноль.

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

  • Индикаторы подключаются через SubscribeCandles().BindEx(...), что полностью соответствует рекомендациям AGENTS.md.
  • Класс StochasticShiftBuffer эмулирует аргумент shift из MQL и позволяет обходиться без прямых вызовов GetValue.
  • Обработка сигналов выполняется один раз на закрытии свечи. Сначала проверяется необходимость выхода, затем — условия для нового входа.
  • В коде добавлены подробные англоязычные комментарии, поясняющие соответствие между MQL-логикой и реализацией StockSharp.

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

  1. Добавьте стратегию в схему StockSharp или в проект Designer.
  2. Назначьте инструмент и убедитесь в наличии истории свечей M5, M15 и M30.
  3. При необходимости скорректируйте параметры (объём, периоды стохастика, уровни TP/SL).
  4. Запустите стратегию. Защитные ордера будут размещаться автоматически при каждой сделке.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// STO M5xM15xM30: RSI momentum with dual EMA confirmation.
/// Uses RSI crossover of 50 level with fast/slow EMA alignment.
/// </summary>
public class StoM5xM15xM30Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevRsi;
	private decimal _entryPrice;

	public StoM5xM15xM30Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

		_fastLength = Param(nameof(FastLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 30)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for stops.", "Indicators");
	}

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

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevRsi = 0;
		_entryPrice = 0;
	}

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

		_prevRsi = 0;
		_entryPrice = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var fast = new ExponentialMovingAverage { Length = FastLength };
		var slow = new ExponentialMovingAverage { Length = SlowLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, fast, slow, atr, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal fastVal, decimal slowVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevRsi == 0 || atrVal <= 0)
		{
			_prevRsi = rsiVal;
			return;
		}

		var close = candle.ClosePrice;

		// Exit management
		if (Position > 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 3m)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (rsiVal < 40 && fastVal < slowVal)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 3m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (rsiVal > 60 && fastVal > slowVal)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry: RSI crosses 50 with EMA alignment
		if (Position == 0)
		{
			if (_prevRsi <= 50 && rsiVal > 50 && fastVal > slowVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (_prevRsi >= 50 && rsiVal < 50 && fastVal < slowVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevRsi = rsiVal;
	}
}