Стратегия пересечения Stochastic RSI
Метод преобразует классический индекс относительной силы в Stochastic RSI и сглаживает его, получая линии %K и %D. Когда %K пересекает %D внутри заранее заданных зон, это подразумевает краткосрочный разворот импульса. Торговля разрешается только тогда, когда тройная структура EMA подтверждает направление глобального тренда, что позволяет отсекать ложные сигналы.
После появления пересечения цена закрытия должна находиться выше или ниже быстрой EMA в зависимости от направления сигнала. Это защищает от входа против тенденции и концентрирует внимание на моментах, когда импульс совпадает с направлением. Параметры сглаживания и длину RSI можно настраивать, изменяя чувствительность системы к всплескам волатильности.
Для оценки риска используется показатель среднего истинного диапазона. Множители текущего ATR задают ориентиры стоп‑лосса и тейк‑профита, динамически расширяясь в периоды высокой активности и сужаясь при спокойном рынке. Скрипт не отправляет защитные ордера автоматически, но рассчитанные уровни помогают вручную управлять позицией или могут подключаться к дополнительным модулям риска.
Детали
- Условия входа:
- Покупка:
%Kпересекает%Dснизу вверх,%Kв диапазоне[10,60], EMA указывают бычий тренд, цена выше EMA1. - Продажа:
%Kпересекает%Dсверху вниз,%Kв диапазоне[40,95], EMA указывают медвежий тренд, цена ниже EMA1.
- Покупка:
- Длинные/короткие: Оба направления.
- Условия выхода: Не реализованы.
- Стопы: Предлагаются множители ATR, но не ставятся автоматически.
- Значения по умолчанию:
SmoothK= 3,SmoothD= 3.RsiLength= 14,StochLength= 14.Ema1Length= 20,Ema2Length= 50,Ema3Length= 100.AtrLength= 14,AtrLossMultiplier= 1.5,AtrProfitMultiplier= 2.0.
- Фильтры:
- Категория: Импульсная
- Направление: Оба
- Индикаторы: Несколько
- Стопы: Опционально
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Да
- Уровень риска: Средний
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Stoch RSI Crossover Strategy.
/// RSI is turned into a Stochastic RSI over StochLength bars and smoothed into %K (SmoothK) and %D (SmoothD).
/// A long opens when %K crosses above %D with %K in [10, 60], EMA1 > EMA2 > EMA3 and the close above EMA1.
/// A short opens when %K crosses below %D with %K in [40, 95], EMA1 < EMA2 < EMA3 and the close below EMA1.
/// There is no built-in exit: an opposite signal reverses the position. The ATR multipliers only describe suggested levels.
/// </summary>
public class StochRsiCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _smoothK;
private readonly StrategyParam<int> _smoothD;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _stochLength;
private readonly StrategyParam<int> _ema1Length;
private readonly StrategyParam<int> _ema2Length;
private readonly StrategyParam<int> _ema3Length;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrLossMultiplier;
private readonly StrategyParam<decimal> _atrProfitMultiplier;
private readonly StrategyParam<DataType> _candleType;
private Highest _rsiHigh;
private Lowest _rsiLow;
private SimpleMovingAverage _kMa;
private SimpleMovingAverage _dMa;
private decimal? _prevK;
private decimal? _prevD;
/// <summary>
/// %K smoothing.
/// </summary>
public int SmoothK
{
get => _smoothK.Value;
set => _smoothK.Value = value;
}
/// <summary>
/// %D smoothing.
/// </summary>
public int SmoothD
{
get => _smoothD.Value;
set => _smoothD.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Stochastic period applied to RSI.
/// </summary>
public int StochLength
{
get => _stochLength.Value;
set => _stochLength.Value = value;
}
/// <summary>
/// Fast EMA period.
/// </summary>
public int Ema1Length
{
get => _ema1Length.Value;
set => _ema1Length.Value = value;
}
/// <summary>
/// Medium EMA period.
/// </summary>
public int Ema2Length
{
get => _ema2Length.Value;
set => _ema2Length.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int Ema3Length
{
get => _ema3Length.Value;
set => _ema3Length.Value = value;
}
/// <summary>
/// ATR period of the suggested levels.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiplier of the suggested stop loss.
/// </summary>
public decimal AtrLossMultiplier
{
get => _atrLossMultiplier.Value;
set => _atrLossMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier of the suggested profit target.
/// </summary>
public decimal AtrProfitMultiplier
{
get => _atrProfitMultiplier.Value;
set => _atrProfitMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public StochRsiCrossoverStrategy()
{
_smoothK = Param(nameof(SmoothK), 3)
.SetGreaterThanZero()
.SetDisplay("Smooth K", "%K smoothing", "Stoch RSI");
_smoothD = Param(nameof(SmoothD), 3)
.SetGreaterThanZero()
.SetDisplay("Smooth D", "%D smoothing", "Stoch RSI");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Stoch RSI");
_stochLength = Param(nameof(StochLength), 14)
.SetGreaterThanZero()
.SetDisplay("Stoch Length", "Stochastic period applied to RSI", "Stoch RSI");
_ema1Length = Param(nameof(Ema1Length), 20)
.SetGreaterThanZero()
.SetDisplay("EMA1 Length", "Fast EMA period", "Trend");
_ema2Length = Param(nameof(Ema2Length), 50)
.SetGreaterThanZero()
.SetDisplay("EMA2 Length", "Medium EMA period", "Trend");
_ema3Length = Param(nameof(Ema3Length), 100)
.SetGreaterThanZero()
.SetDisplay("EMA3 Length", "Slow EMA period", "Trend");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period of the suggested levels", "Risk");
_atrLossMultiplier = Param(nameof(AtrLossMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Loss Multiplier", "ATR multiplier of the suggested stop loss", "Risk");
_atrProfitMultiplier = Param(nameof(AtrProfitMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("ATR Profit Multiplier", "ATR multiplier of the suggested profit target", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevK = null;
_prevD = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevK = null;
_prevD = null;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var ema1 = new ExponentialMovingAverage { Length = Ema1Length };
var ema2 = new ExponentialMovingAverage { Length = Ema2Length };
var ema3 = new ExponentialMovingAverage { Length = Ema3Length };
_rsiHigh = new Highest { Length = StochLength };
_rsiLow = new Lowest { Length = StochLength };
_kMa = new SimpleMovingAverage { Length = SmoothK };
_dMa = new SimpleMovingAverage { Length = SmoothD };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(rsi, ema1, ema2, ema3, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema1);
DrawIndicator(area, ema2);
DrawIndicator(area, ema3);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rsiValue, IIndicatorValue ema1Value, IIndicatorValue ema2Value, IIndicatorValue ema3Value)
{
if (candle.State != CandleStates.Finished)
return;
if (!rsiValue.IsFormed)
return;
var rsi = rsiValue.GetValue<decimal>();
var high = _rsiHigh.Process(rsi, candle.OpenTime, true).ToDecimal();
var low = _rsiLow.Process(rsi, candle.OpenTime, true).ToDecimal();
if (!_rsiHigh.IsFormed || !_rsiLow.IsFormed)
return;
var stoch = high - low > 0m ? 100m * (rsi - low) / (high - low) : 0m;
var kValue = _kMa.Process(stoch, candle.OpenTime, true);
if (!_kMa.IsFormed)
return;
var k = kValue.ToDecimal();
var dValue = _dMa.Process(k, candle.OpenTime, true);
if (!_dMa.IsFormed)
return;
var d = dValue.ToDecimal();
var prevK = _prevK;
var prevD = _prevD;
_prevK = k;
_prevD = d;
if (prevK is not decimal pk || prevD is not decimal pd)
return;
if (!ema1Value.IsFormed || !ema2Value.IsFormed || !ema3Value.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ema1 = ema1Value.GetValue<decimal>();
var ema2 = ema2Value.GetValue<decimal>();
var ema3 = ema3Value.GetValue<decimal>();
var close = candle.ClosePrice;
var crossUp = pk <= pd && k > d;
var crossDown = pk >= pd && k < d;
var longSignal = crossUp && k >= 10m && k <= 60m && ema1 > ema2 && ema2 > ema3 && close > ema1;
var shortSignal = crossDown && k >= 40m && k <= 95m && ema1 < ema2 && ema2 < ema3 && close < ema1;
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage, SimpleMovingAverage, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class stoch_rsi_crossover_strategy(Strategy):
"""
Stoch RSI Crossover Strategy.
RSI is turned into a Stochastic RSI over StochLength bars and smoothed into %K (SmoothK) and %D (SmoothD).
A long opens when %K crosses above %D with %K in [10, 60], EMA1 > EMA2 > EMA3 and the close above EMA1.
A short opens when %K crosses below %D with %K in [40, 95], EMA1 < EMA2 < EMA3 and the close below EMA1.
There is no built-in exit: an opposite signal reverses the position. The ATR multipliers only describe suggested levels.
"""
def __init__(self):
super(stoch_rsi_crossover_strategy, self).__init__()
self._smooth_k = self.Param("SmoothK", 3).SetGreaterThanZero().SetDisplay("Smooth K", "%K smoothing", "Stoch RSI")
self._smooth_d = self.Param("SmoothD", 3).SetGreaterThanZero().SetDisplay("Smooth D", "%D smoothing", "Stoch RSI")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI period", "Stoch RSI")
self._stoch_length = self.Param("StochLength", 14).SetGreaterThanZero().SetDisplay("Stoch Length", "Stochastic period applied to RSI", "Stoch RSI")
self._ema1_length = self.Param("Ema1Length", 20).SetGreaterThanZero().SetDisplay("EMA1 Length", "Fast EMA period", "Trend")
self._ema2_length = self.Param("Ema2Length", 50).SetGreaterThanZero().SetDisplay("EMA2 Length", "Medium EMA period", "Trend")
self._ema3_length = self.Param("Ema3Length", 100).SetGreaterThanZero().SetDisplay("EMA3 Length", "Slow EMA period", "Trend")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period of the suggested levels", "Risk")
self._atr_loss_multiplier = self.Param("AtrLossMultiplier", 1.5).SetGreaterThanZero().SetDisplay("ATR Loss Multiplier", "ATR multiplier of the suggested stop loss", "Risk")
self._atr_profit_multiplier = self.Param("AtrProfitMultiplier", 2.0).SetGreaterThanZero().SetDisplay("ATR Profit Multiplier", "ATR multiplier of the suggested profit target", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi_high = None
self._rsi_low = None
self._k_ma = None
self._d_ma = None
self._prev_k = None
self._prev_d = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(stoch_rsi_crossover_strategy, self).OnReseted()
self._prev_k = None
self._prev_d = None
def OnStarted2(self, time):
super(stoch_rsi_crossover_strategy, self).OnStarted2(time)
self._prev_k = None
self._prev_d = None
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
ema1 = ExponentialMovingAverage()
ema1.Length = self._ema1_length.Value
ema2 = ExponentialMovingAverage()
ema2.Length = self._ema2_length.Value
ema3 = ExponentialMovingAverage()
ema3.Length = self._ema3_length.Value
self._rsi_high = Highest()
self._rsi_high.Length = self._stoch_length.Value
self._rsi_low = Lowest()
self._rsi_low.Length = self._stoch_length.Value
self._k_ma = SimpleMovingAverage()
self._k_ma.Length = self._smooth_k.Value
self._d_ma = SimpleMovingAverage()
self._d_ma.Length = self._smooth_d.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(rsi, ema1, ema2, ema3, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema1)
self.DrawIndicator(area, ema2)
self.DrawIndicator(area, ema3)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_value, ema1_value, ema2_value, ema3_value):
if candle.State != CandleStates.Finished:
return
if not rsi_value.IsFormed:
return
rsi_dec = rsi_value.GetValue[Decimal](None)
rsi = float(rsi_dec)
high = float(process_float(self._rsi_high, rsi_dec, candle.OpenTime, True).GetValue[Decimal](None))
low = float(process_float(self._rsi_low, rsi_dec, candle.OpenTime, True).GetValue[Decimal](None))
if not self._rsi_high.IsFormed or not self._rsi_low.IsFormed:
return
stoch = 100.0 * (rsi - low) / (high - low) if high - low > 0 else 0.0
k_value = process_float(self._k_ma, stoch, candle.OpenTime, True)
if not self._k_ma.IsFormed:
return
k_dec = k_value.GetValue[Decimal](None)
k = float(k_dec)
d_value = process_float(self._d_ma, k_dec, candle.OpenTime, True)
if not self._d_ma.IsFormed:
return
d = float(d_value.GetValue[Decimal](None))
prev_k = self._prev_k
prev_d = self._prev_d
self._prev_k = k
self._prev_d = d
if prev_k is None or prev_d is None:
return
if not ema1_value.IsFormed or not ema2_value.IsFormed or not ema3_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ema1 = float(ema1_value.GetValue[Decimal](None))
ema2 = float(ema2_value.GetValue[Decimal](None))
ema3 = float(ema3_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
cross_up = prev_k <= prev_d and k > d
cross_down = prev_k >= prev_d and k < d
long_signal = cross_up and 10.0 <= k <= 60.0 and ema1 > ema2 and ema2 > ema3 and close > ema1
short_signal = cross_down and 40.0 <= k <= 95.0 and ema1 < ema2 and ema2 < ema3 and close < ema1
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return stoch_rsi_crossover_strategy()