Exp RSIOMA V2 — конверсия оригинального советника MetaTrader 5 в инфраструктуру StockSharp. Стратегия использует осциллятор RSIOMA (Relative Strength Index of Moving Average): выбранная цена свечи сглаживается, из сглаженных значений рассчитывается импульс, после чего формируется RSI-подобная величина. Торговые решения принимаются, когда осциллятор меняет направление или выходит из ключевых зон.
Логика торговли
Предобработка цены. Цена свечи, заданная параметром AppliedPrice (по умолчанию закрытие), сглаживается одним из четырёх типов средних (простое, экспоненциальное, сглаженное или линейно взвешенное).
Расчёт импульса. Сглаженное значение сравнивается с ценой MomentumPeriod свечей назад, что даёт текущий импульс.
Вычисление RSIOMA. Положительные и отрицательные импульсы усредняются экспоненциальным фильтром длиной RsiomaLength, формируя значение от 0 до 100.
Оценка сигналов. В зависимости от Mode анализируются последние закрытые свечи:
Breakdown — реагирует на выход RSIOMA за уровни MainTrendLong / MainTrendShort. Возврат из верхней зоны закрывает шорты и позволяет открыть лонг, выход из нижней зоны делает обратное.
Twist — ищет разворотные точки. Покупка происходит, когда наклон осциллятора меняется с нисходящего на восходящий, продажа — при обратном переходе.
CloudTwist — повторяет «облако» индикатора MT5: сделки открываются, когда RSIOMA возвращается из экстремумов внутрь канала, одновременно закрывая противоположные позиции.
Сигналы оцениваются на свече, заданной SignalBar (по умолчанию предыдущая полностью закрытая свеча).
Параметры
Параметр
Описание
Значение по умолчанию
OrderVolume
Базовый объём заявки.
1
CandleType
Тип свечей, используемый стратегией.
4 часа
EnableLongEntries / EnableShortEntries
Разрешение на открытие длинных / коротких позиций.
true
EnableLongExits / EnableShortExits
Разрешение на закрытие длинных / коротких позиций.
true
Mode
Режим генерации сигналов (Breakdown, Twist, CloudTwist).
Breakdown
PriceSmoothing
Тип усреднения, применяемый к цене перед расчётом RSIOMA.
Exponential
RsiomaLength
Длина усреднения RSIOMA.
14
MomentumPeriod
Запаздывание при расчёте импульса.
1
AppliedPrice
Используемая цена свечи (close, open, median, DeMark и т.д.).
Close
MainTrendLong / MainTrendShort
Границы зон перекупленности и перепроданности.
60 / 40
SignalBar
Количество закрытых свечей назад для анализа.
1
Особенности реализации
Реализованы только типы сглаживания, доступные в StockSharp (SMA, EMA, SMMA, WMA). Продвинутые варианты из MT5 (JJMA, VIDYA, AMA и др.) не поддерживаются.
Для инициализации RSI используется первая серия импульсов длиной RsiomaLength, затем применяется стандартное экспоненциальное обновление, как и в оригинальном советнике.
Перед открытием противоположной позиции стратегия всегда закрывает текущую. Параметры EnableLongEntries / EnableShortEntries и EnableLongExits / EnableShortExits позволяют гибко ограничивать направления.
Значение SignalBar = 0 позволяет реагировать на текущую закрытую свечу, большие значения имитируют задержку, доступную в MT5.
Как использовать
Добавьте стратегию в проект StockSharp и назначьте инструмент для торговли.
Настройте подписку на свечи через CandleType (по умолчанию 4-часовые) и при необходимости скорректируйте пороговые уровни под волатильность инструмента.
Выберите режим сигналов: пробой уровней (Breakdown), разворот (Twist) или изменение «облака» (CloudTwist).
Запустите стратегию. Она подпишется на выбранные свечи, рассчитает цепочку RSIOMA и отправит рыночные заявки при выполнении условий.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// RSIOMA strategy using RSI with overbought/oversold levels.
/// Buys when RSI crosses above oversold, sells when crosses below overbought.
/// </summary>
public class ExpRsiomaV2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<decimal> _oversold;
private decimal? _prevRsi;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public decimal Overbought
{
get => _overbought.Value;
set => _overbought.Value = value;
}
public decimal Oversold
{
get => _oversold.Value;
set => _oversold.Value = value;
}
public ExpRsiomaV2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI calculation period", "Indicators");
_overbought = Param(nameof(Overbought), 65m)
.SetDisplay("Overbought", "Overbought RSI level", "Levels");
_oversold = Param(nameof(Oversold), 35m)
.SetDisplay("Oversold", "Oversold RSI level", "Levels");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = null;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
var indArea = CreateChartArea();
if (indArea != null)
{
DrawIndicator(indArea, rsi);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevRsi = rsiValue;
return;
}
if (_prevRsi == null)
{
_prevRsi = rsiValue;
return;
}
// RSI crosses above oversold → buy signal
if (_prevRsi.Value <= Oversold && rsiValue > Oversold && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// RSI crosses below overbought → sell signal
else if (_prevRsi.Value >= Overbought && rsiValue < Overbought && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevRsi = rsiValue;
}
}
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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class exp_rsioma_v2_strategy(Strategy):
def __init__(self):
super(exp_rsioma_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI calculation period", "Indicators")
self._overbought = self.Param("Overbought", 65.0) \
.SetDisplay("Overbought", "Overbought RSI level", "Levels")
self._oversold = self.Param("Oversold", 35.0) \
.SetDisplay("Oversold", "Oversold RSI level", "Levels")
self._prev_rsi = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@property
def Overbought(self):
return self._overbought.Value
@property
def Oversold(self):
return self._oversold.Value
def OnReseted(self):
super(exp_rsioma_v2_strategy, self).OnReseted()
self._prev_rsi = None
def OnStarted2(self, time):
super(exp_rsioma_v2_strategy, self).OnStarted2(time)
self._prev_rsi = None
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
ind_area = self.CreateChartArea()
if ind_area is not None:
self.DrawIndicator(ind_area, rsi)
def _on_process(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rv
return
if self._prev_rsi is None:
self._prev_rsi = rv
return
ob = float(self.Overbought)
os_level = float(self.Oversold)
# RSI crosses above oversold
if self._prev_rsi <= os_level and rv > os_level and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# RSI crosses below overbought
elif self._prev_rsi >= ob and rv < ob and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_rsi = rv
def CreateClone(self):
return exp_rsioma_v2_strategy()