Стратегия представляет собой перенос советника MetaTrader 5 Exp_SR-RateIndicator на платформу StockSharp. В коде используетс
я высокоуровневый API и собственная реализация осциллятора SR Rate. Индикатор измеряет положение сглаженной взвешенной цены внут
ри динамического диапазона поддержки/сопротивления и выдаёт пять цветовых состояний, подсвечивающих экстремальные зоны.
Алгоритм обрабатывает только завершённые свечи выбранного таймфрейма. Когда цвет осциллятора переходит в бычий или медвежий экст
ремум, стратегия закрывает противоположную позицию и при необходимости открывает сделку в сторону сигнала. Стоп-лосс и тейк-проф
ит рассчитываются в тех же пунктах, что и в оригинальном советнике, и автоматически переводятся в абсолютные цены через шаг цен
ы инструмента.
Осциллятор SR Rate
Расчёт индикатора выполняется по следующему алгоритму:
Для каждой свечи максимумы, минимумы и взвешенные цены закрытия сглаживаются односторонними гауссовыми весами длиной шесть.
Внутри заданного окна определяется максимум сглаженных максимумов и минимум сглаженных минимумов, формируя динамический кан
ал.
Текущее сглаженное взвешенное закрытие нормируется относительно полученного диапазона и проецируется в интервал [-100, 100].
Значение осциллятора сопоставляется с цветовым кодом: 0 — сильный медвежий сигнал, 1 — мягкий медвежий, 2 — нейтральный, 3
— мягкий бычий, 4 — сильный бычий.
Цвет 4 означает достижение верхней границы диапазона, цвет 0 — тест его нижней границы.
Правила торговли
Подписаться на свечи выбранного типа и вычислять SR Rate на каждой закрытой свече.
Смещать оценку сигнала на SignalBar закрытых свечей назад (по умолчанию на одну свечу) для полного соответствия оригиналу.
Если смещённый цвет становится 4, а предыдущий цвет меньше 4:
Закрыть имеющиеся короткие позиции, если разрешено закрытие шортов.
Открыть длинную позицию при разрешённых входах и отсутствии активных сделок.
Если смещённый цвет становится 0, а предыдущий больше 0:
Закрыть длинные позиции, если разрешено закрытие лонгов.
Открыть короткую позицию при разрешённых входах и отсутствии активных сделок.
Одновременно поддерживается только одна позиция; новые сигналы игнорируются до её закрытия.
Стоп-лосс и тейк-профит задаются в пунктах и переводятся в абсолютные цены через шаг цены инструмента.
Параметры
Имя
Описание
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class sr_rate_indicator_strategy(Strategy):
def __init__(self):
super(sr_rate_indicator_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(sr_rate_indicator_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(sr_rate_indicator_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return sr_rate_indicator_strategy()