Открыть на GitHub

Стратегия SR Rate Indicator

Обзор

Стратегия представляет собой перенос советника MetaTrader 5 Exp_SR-RateIndicator на платформу StockSharp. В коде используетс я высокоуровневый API и собственная реализация осциллятора SR Rate. Индикатор измеряет положение сглаженной взвешенной цены внут ри динамического диапазона поддержки/сопротивления и выдаёт пять цветовых состояний, подсвечивающих экстремальные зоны.

Алгоритм обрабатывает только завершённые свечи выбранного таймфрейма. Когда цвет осциллятора переходит в бычий или медвежий экст ремум, стратегия закрывает противоположную позицию и при необходимости открывает сделку в сторону сигнала. Стоп-лосс и тейк-проф ит рассчитываются в тех же пунктах, что и в оригинальном советнике, и автоматически переводятся в абсолютные цены через шаг цен ы инструмента.

Осциллятор SR Rate

Расчёт индикатора выполняется по следующему алгоритму:

  1. Для каждой свечи максимумы, минимумы и взвешенные цены закрытия сглаживаются односторонними гауссовыми весами длиной шесть.
  2. Внутри заданного окна определяется максимум сглаженных максимумов и минимум сглаженных минимумов, формируя динамический кан ал.
  3. Текущее сглаженное взвешенное закрытие нормируется относительно полученного диапазона и проецируется в интервал [-100, 100].
  4. Значение осциллятора сопоставляется с цветовым кодом: 0 — сильный медвежий сигнал, 1 — мягкий медвежий, 2 — нейтральный, 3 — мягкий бычий, 4 — сильный бычий.

Цвет 4 означает достижение верхней границы диапазона, цвет 0 — тест его нижней границы.

Правила торговли

  1. Подписаться на свечи выбранного типа и вычислять SR Rate на каждой закрытой свече.
  2. Смещать оценку сигнала на SignalBar закрытых свечей назад (по умолчанию на одну свечу) для полного соответствия оригиналу.
  3. Если смещённый цвет становится 4, а предыдущий цвет меньше 4:
    • Закрыть имеющиеся короткие позиции, если разрешено закрытие шортов.
    • Открыть длинную позицию при разрешённых входах и отсутствии активных сделок.
  4. Если смещённый цвет становится 0, а предыдущий больше 0:
    • Закрыть длинные позиции, если разрешено закрытие лонгов.
    • Открыть короткую позицию при разрешённых входах и отсутствии активных сделок.
  5. Одновременно поддерживается только одна позиция; новые сигналы игнорируются до её закрытия.
  6. Стоп-лосс и тейк-профит задаются в пунктах и переводятся в абсолютные цены через шаг цены инструмента.

Параметры

Имя Описание
OrderVolume Объём каждой рыночной заявки.
EnableLongEntries Разрешение на открытие длинных позиций.
EnableShortEntries Разрешение на открытие коротких позиций.
EnableLongExits Закрывать лонги при появлении сильного медвежьего цвета.
EnableShortExits Закрывать шорты при появлении сильного бычьего цвета.
StopLossPoints Дистанция стоп-лосса в пунктах (конвертируется в абсолютное значение).
TakeProfitPoints Дистанция тейк-профита в пунктах (конвертируется в абсолютное значение).
SlippagePoints Допустимое проскальзывание при закрытии позиций. Параметр сохранён для совместимости; высокоуровневый API не управляет проскальзыванием напрямую.
CandleType Тип и таймфрейм свечей для расчёта.
SignalBar Количество закрытых свечей, на которое смещается сигнал (по умолчанию 1).
WindowSize Длина окна нормализации SR Rate.
HighLevel Порог верхнего экстремума (по умолчанию +20).
LowLevel Порог нижнего экстремума (по умолчанию -20).

Примечания

  • Стратегия работает с любыми инструментами, предоставляющими стандартные OHLC-свечи.
  • Сигналы анализируются только после закрытия свечи, как и в MetaTrader.
  • Контроль проскальзывания в исходном советнике зависел от настроек исполнения. В StockSharp рыночные заявки следуют правилам биржи, поэтому параметр SlippagePoints носит информационный характер.
  • Индикатор хранит только минимально необходимую историю, что исключает лишние затраты памяти.
  • По требованиям проекта версия на 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 SrRateIndicatorStrategy : 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 SrRateIndicatorStrategy()
	{
		_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;
	}
}