Estrategia de Cruce de Stochastic RSI
Este método convierte el clásico Relative Strength Index en un Stochastic RSI, luego suaviza el resultado en líneas %K y %D. Cuando %K cruza %D dentro de zonas cuidadosamente elegidas, el movimiento implica un cambio de corto plazo en el momentum. El algoritmo solo opera cuando una estructura EMA de tres capas confirma la dirección de la tendencia más amplia, ayudando a filtrar las falsas señales.
Una vez que aparece un cruce, el precio de cierre también debe situarse por encima o por debajo de la EMA rápida dependiendo de la señal. Esto protege contra actuar en oscilaciones que ocurren contra la tendencia prevaleciente y mantiene la atención en los momentos cuando el momentum se alinea con la dirección. Los traders pueden ajustar los períodos de suavizado y la longitud del RSI para afinar cómo reacciona el sistema a los picos de volatilidad.
El riesgo se referencia a través de una lectura de Average True Range. Los multiplicadores del ATR actual proponen stops de pérdida y objetivos de ganancia, proporcionando un nivel dinámico que se expande en mercados volátiles y se contrae cuando la actividad se calma. Aunque el script no envía automáticamente órdenes protectoras, estos niveles calculados ayudan a la gestión manual o pueden vincularse a módulos de riesgo adicionales.
Detalles
- Criterios de entrada:
- Largo:
%K cruza por encima de %D, %K en [10,60], EMAs alineadas alcistamente, precio por encima de EMA1.
- Corto:
%K cruza por debajo de %D, %K en [40,95], EMAs alineadas bajistamente, precio por debajo de EMA1.
- Largo/Corto: Ambos lados.
- Criterios de salida: Ninguno integrado.
- Stops: Múltiplos de ATR sugeridos pero no colocados automáticamente.
- Valores predeterminados:
SmoothK = 3, SmoothD = 3.
RsiLength = 14, StochLength = 14.
Ema1Length = 20, Ema2Length = 50, Ema3Length = 100.
AtrLength = 14, AtrLossMultiplier = 1.5, AtrProfitMultiplier = 2.0.
- Filtros:
- Categoría: Momentum
- Dirección: Ambos
- Indicadores: Múltiples
- Stops: Opcional
- Complejidad: Moderado
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: Sí
- Nivel de riesgo: Medio
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>
/// Stochastic RSI Crossover Strategy with EMA trend filter.
/// Uses RSI crossovers with triple EMA alignment for trend confirmation.
/// Buys when RSI crosses above oversold in bullish EMA alignment.
/// Sells when RSI crosses below overbought in bearish EMA alignment.
/// </summary>
public class StochRsiCrossoverStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _rsiOversold;
private readonly StrategyParam<int> _rsiOverbought;
private readonly StrategyParam<int> _ema1Length;
private readonly StrategyParam<int> _ema2Length;
private readonly StrategyParam<int> _ema3Length;
private readonly StrategyParam<int> _cooldownBars;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ema1;
private ExponentialMovingAverage _ema2;
private ExponentialMovingAverage _ema3;
private decimal _prevRsi;
private int _cooldownRemaining;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
public int RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
public int Ema1Length
{
get => _ema1Length.Value;
set => _ema1Length.Value = value;
}
public int Ema2Length
{
get => _ema2Length.Value;
set => _ema2Length.Value = value;
}
public int Ema3Length
{
get => _ema3Length.Value;
set => _ema3Length.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public StochRsiCrossoverStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_rsiOversold = Param(nameof(RsiOversold), 40)
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI");
_rsiOverbought = Param(nameof(RsiOverbought), 60)
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI");
_ema1Length = Param(nameof(Ema1Length), 8)
.SetGreaterThanZero()
.SetDisplay("EMA 1 Length", "Fast EMA length", "Moving Averages");
_ema2Length = Param(nameof(Ema2Length), 14)
.SetGreaterThanZero()
.SetDisplay("EMA 2 Length", "Medium EMA length", "Moving Averages");
_ema3Length = Param(nameof(Ema3Length), 50)
.SetGreaterThanZero()
.SetDisplay("EMA 3 Length", "Slow EMA length", "Moving Averages");
_cooldownBars = Param(nameof(CooldownBars), 15)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_ema1 = null;
_ema2 = null;
_ema3 = null;
_prevRsi = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_ema1 = new ExponentialMovingAverage { Length = Ema1Length };
_ema2 = new ExponentialMovingAverage { Length = Ema2Length };
_ema3 = new ExponentialMovingAverage { Length = Ema3Length };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _ema1, _ema2, _ema3, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema1);
DrawIndicator(area, _ema2);
DrawIndicator(area, _ema3);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal rsiVal, decimal ema1Val, decimal ema2Val, decimal ema3Val)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_ema1.IsFormed || !_ema2.IsFormed || !_ema3.IsFormed)
{
_prevRsi = rsiVal;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevRsi = rsiVal;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevRsi = rsiVal;
return;
}
if (_prevRsi == 0)
{
_prevRsi = rsiVal;
return;
}
// EMA alignment (relaxed - only fast vs slow)
var bullishEma = ema1Val > ema3Val;
var bearishEma = ema1Val < ema3Val;
// RSI crossovers
var rsiCrossUpOversold = rsiVal > RsiOversold && _prevRsi <= RsiOversold;
var rsiCrossDownOverbought = rsiVal < RsiOverbought && _prevRsi >= RsiOverbought;
// Buy: RSI crosses above oversold + bullish EMA
if (rsiCrossUpOversold && bullishEma && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: RSI crosses below overbought + bearish EMA
else if (rsiCrossDownOverbought && bearishEma && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: RSI overbought or EMA bearish cross
else if (Position > 0 && (rsiVal > RsiOverbought || ema1Val < ema2Val))
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: RSI oversold or EMA bullish cross
else if (Position < 0 && (rsiVal < RsiOversold || ema1Val > ema2Val))
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevRsi = rsiVal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class stoch_rsi_crossover_strategy(Strategy):
"""Stochastic RSI Crossover Strategy."""
def __init__(self):
super(stoch_rsi_crossover_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period", "RSI")
self._rsi_oversold = self.Param("RsiOversold", 40) \
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI")
self._rsi_overbought = self.Param("RsiOverbought", 60) \
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI")
self._ema1_length = self.Param("Ema1Length", 8) \
.SetDisplay("EMA 1 Length", "Fast EMA length", "Moving Averages")
self._ema2_length = self.Param("Ema2Length", 14) \
.SetDisplay("EMA 2 Length", "Medium EMA length", "Moving Averages")
self._ema3_length = self.Param("Ema3Length", 50) \
.SetDisplay("EMA 3 Length", "Slow EMA length", "Moving Averages")
self._cooldown_bars = self.Param("CooldownBars", 15) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._rsi = None
self._ema1 = None
self._ema2 = None
self._ema3 = None
self._prev_rsi = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(stoch_rsi_crossover_strategy, self).OnReseted()
self._rsi = None
self._ema1 = None
self._ema2 = None
self._ema3 = None
self._prev_rsi = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(stoch_rsi_crossover_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._ema1 = ExponentialMovingAverage()
self._ema1.Length = int(self._ema1_length.Value)
self._ema2 = ExponentialMovingAverage()
self._ema2.Length = int(self._ema2_length.Value)
self._ema3 = ExponentialMovingAverage()
self._ema3.Length = int(self._ema3_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._ema1, self._ema2, self._ema3, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema1)
self.DrawIndicator(area, self._ema2)
self.DrawIndicator(area, self._ema3)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_val, ema1_val, ema2_val, ema3_val):
if candle.State != CandleStates.Finished:
return
if not self._rsi.IsFormed or not self._ema1.IsFormed or not self._ema2.IsFormed or not self._ema3.IsFormed:
self._prev_rsi = float(rsi_val)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = float(rsi_val)
return
rsi = float(rsi_val)
ema1 = float(ema1_val)
ema2 = float(ema2_val)
ema3 = float(ema3_val)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_rsi = rsi
return
if self._prev_rsi == 0.0:
self._prev_rsi = rsi
return
rsi_os = int(self._rsi_oversold.Value)
rsi_ob = int(self._rsi_overbought.Value)
cooldown = int(self._cooldown_bars.Value)
bullish_ema = ema1 > ema3
bearish_ema = ema1 < ema3
rsi_cross_up_oversold = rsi > rsi_os and self._prev_rsi <= rsi_os
rsi_cross_down_overbought = rsi < rsi_ob and self._prev_rsi >= rsi_ob
if rsi_cross_up_oversold and bullish_ema and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif rsi_cross_down_overbought and bearish_ema and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and (rsi > rsi_ob or ema1 < ema2):
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and (rsi < rsi_os or ema1 > ema2):
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_rsi = rsi
def CreateClone(self):
return stoch_rsi_crossover_strategy()