Открыть на GitHub

Стратегия Cronex RSI

Стратегия Cronex RSI переносит советник Exp_CronexRSI.mq5 на высокоуровневый API StockSharp. Базовый RSI проходит через две последовательные скользящие средние, что формирует «быструю» и «медленную» кривые. Торговые решения принимаются по их пересечению, а дополнительные флаги позволяют включать и отключать открытия/закрытия позиций так же, как в оригинальном MQL5 коде.

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

  1. Рассчитать RSI по выбранному типу цены и заданному периоду.
  2. Сгладить результат быстрым скользящим средним, затем повторно сгладить медленным средним.
  3. С учётом параметра SignalShift анализировать пересечения:
    • Если на предыдущей подтверждённой свече быстрая линия находилась выше медленной, а на текущей опускается ниже, стратегия закрывает короткие позиции и при разрешении открывает длинную.
    • Если быстрая линия была ниже медленной и на подтверждённой свече поднимается выше, закрываются длинные позиции и может быть открыт шорт.
  4. При смене направления позиции стратегия сначала ликвидирует старое плечо, затем открывает новое объёмом TradeVolume, добавляя недостающий объём для полного разворота.

По умолчанию используется SignalShift = 1, что обеспечивает подтверждение сигнала одной полностью завершённой свечой — аналогично Exp_CronexRSI. Значение 0 заставляет реагировать на пересечение сразу после закрытия текущей свечи.

Параметры

Параметр Описание
RsiPeriod Период расчёта RSI.
FastPeriod Длина первой (быстрой) скользящей средней.
SlowPeriod Длина второй (медленной) скользящей средней.
SignalShift Количество закрытых свечей для подтверждения сигнала (0 — без задержки).
SmoothingMethod Тип скользящей средней для обоих этапов сглаживания (простая, экспоненциальная, сглаженная, линейно-взвешенная, объёмно-взвешенная).
AppliedPrice Тип цены для расчёта RSI (close, open, high, low, median, typical, weighted).
CandleType Тип свечей, используемых в стратегии.
TradeVolume Базовый объём при открытии новых позиций.
EnableLongEntry / EnableShortEntry Разрешение на открытие длинных / коротких позиций.
EnableLongExit / EnableShortExit Разрешение на закрытие длинных / коротких позиций противоположными сигналами.

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

  • Для сглаживания применяются стандартные индикаторы StockSharp. Опция VolumeWeighted выступает практичной заменой для VIDYA/AMA, присутствовавших в библиотеке Cronex.
  • Логика выбора цены полностью повторяет MQL5-версию и позволяет гибко переключаться между различными вариантами входных данных.
  • Индикаторные значения прокачиваются через DecimalIndicatorValue, что сохраняет совместимость с пайплайном StockSharp и избавляет от прямого чтения внутреннего состояния индикаторов.
  • При изменении SignalShift стратегия автоматически пересоздаёт внутреннюю историю, чтобы условия пересечения всегда анализировались по правильному числу прошедших баров.

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

  1. Присоедините стратегию к портфелю и инструменту в дизайнере StockSharp или программно.
  2. Настройте таймфрейм свечей, тип сглаживания и флаги торговли согласно требованиям конкретной версии Cronex RSI.
  3. Запустите стратегию: она подпишется на поток свечей, пересчитает RSI и две средние, после чего будет отправлять рыночные заявки при подтверждённых пересечениях.
  4. Для проверки поведения воспользуйтесь встроенными средствами визуализации: на графике будут отображены свечи, индикаторы и совершённые сделки.
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 CronexRsiStrategy : 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 CronexRsiStrategy()
	{
		_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;
	}
}