Estratégia Stochastic RSI Cross
Estratégia baseada no cruzamento do Stochastic RSI
Os testes indicam um retorno anual médio de aproximadamente 112%. Funciona melhor no mercado forex.
Stochastic RSI Cross observa as linhas %K e %D do StochRSI. Cruzamentos altistas perto de níveis de sobrevenda acionam compras, cruzamentos baixistas perto da sobrecompra acionam vendas, e cruzamentos opostos saem.
Como o StochRSI oscila rapidamente, os sinais podem ser frequentes. Muitos traders exigem que o cruzamento ocorra perto de um extremo para filtrar o ruído.
Detalhes
- Critérios de entrada: Sinais baseados em RSI, Stochastic.
- Comprado/Vendido: Ambos os direções.
- Critérios de saída: Sinal oposto ou stop.
- Stops: Sim.
- Valores padrão:
RsiPeriod= 14StochPeriod= 14KPeriod= 3DPeriod= 3StopLossPercent= 2mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoria: Tendência
- Direção: Ambos
- Indicadores: RSI, Stochastic
- Stops: Sim
- Complexidade: Básico
- Período: Intradiário (5m)
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Médio
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on sequential RSI normalization and smoothed StochRSI K/D crossover.
/// Buys when %K crosses above %D in oversold zone.
/// Sells when %K crosses below %D in overbought zone.
/// </summary>
public class StochasticRsiCrossStrategy : Strategy
{
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private decimal _prevK;
private decimal _prevD;
private bool _hasPrevValues;
private Order _pendingOrder;
private RelativeStrengthIndex _rsi;
private SimpleMovingAverage _kAverage;
private SimpleMovingAverage _dAverage;
private readonly Queue<decimal> _rsiWindow = new();
/// <summary>
/// K period.
/// </summary>
public int KPeriod
{
get => _kPeriod.Value;
set => _kPeriod.Value = value;
}
/// <summary>
/// D period.
/// </summary>
public int DPeriod
{
get => _dPeriod.Value;
set => _dPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
/// <summary>
/// Initializes a new instance of the <see cref="StochasticRsiCrossStrategy"/>.
/// </summary>
public StochasticRsiCrossStrategy()
{
_kPeriod = Param(nameof(KPeriod), 3).SetGreaterThanZero()
.SetDisplay("K Period", "SMA period of raw StochRSI.", "Indicators")
.SetOptimize(1, 5, 1);
_dPeriod = Param(nameof(DPeriod), 3).SetGreaterThanZero()
.SetDisplay("D Period", "SMA period of formed K values.", "Indicators")
.SetOptimize(3, 5, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetGreaterThanZero()
.SetDisplay("RSI Period", "Period of native close-price RSI.", "Indicators");
_stochPeriod = Param(nameof(StochPeriod), 14).SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Rolling range of formed RSI values.", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection");
OrderRegistering += order => _pendingOrder = order;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, DataType.Level1)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevK = default;
_prevD = default;
_hasPrevValues = default;
_pendingOrder = null;
_rsiWindow.Clear();
_rsi = null;
_kAverage = _dAverage = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_kAverage = new SimpleMovingAverage { Length = KPeriod };
_dAverage = new SimpleMovingAverage { Length = DPeriod };
Indicators.Add(_kAverage);
Indicators.Add(_dAverage);
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawIndicator(area, _kAverage);
DrawIndicator(area, _dAverage);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before the callback, including between finished candles.
}
private static decimal Feed(SimpleMovingAverage average, decimal value, DateTime time)
=> average.Process(new DecimalIndicatorValue(average, value, time) { IsFinal = true }).GetValue<decimal>();
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished || !_rsi.IsFormed)
return;
// Normalize formed RSI readings, not candle High/Low. Warm each stage sequentially.
_rsiWindow.Enqueue(rsiValue);
if (_rsiWindow.Count > StochPeriod)
_rsiWindow.Dequeue();
if (_rsiWindow.Count < StochPeriod)
return;
var low = _rsiWindow.Min();
var high = _rsiWindow.Max();
var raw = high == low ? 50m : 100m * (rsiValue - low) / (high - low);
var k = Feed(_kAverage, raw, candle.OpenTime);
if (!_kAverage.IsFormed)
return;
var d = Feed(_dAverage, k, candle.OpenTime);
if (!_dAverage.IsFormed || !IsFormedAndOnlineAndAllowTrading())
return;
// Rolling decimal sums leave sub-tick residue at equal K/D plateaus. Quantize decisions,
// not the values fed into the next SMA, so equality cannot manufacture or suppress crosses.
k = Math.Round(k, 8);
d = Math.Round(d, 8);
if (!_hasPrevValues)
{
_hasPrevValues = true;
_prevK = k;
_prevD = d;
return;
}
if (_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
{
_prevK = k;
_prevD = d;
return;
}
var crossedUp = _prevK <= _prevD && k > d;
var crossedDown = _prevK >= _prevD && k < d;
if (crossedUp && k < 20m && Position <= 0m)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
}
// %K crosses below %D in overbought zone (> 80) - sell
else if (crossedDown && k > 80m && Position >= 0m)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
}
else if (Position > 0m && crossedDown)
SellMarket(Position);
else if (Position < 0m && crossedUp)
BuyMarket(Math.Abs(Position));
_prevK = k;
_prevD = d;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import RelativeStrengthIndex, SimpleMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class stochastic_rsi_cross_strategy(Strategy):
"""
Sequential RSI normalization and smoothed StochRSI K/D crossover strategy.
Buys when %K crosses above %D in oversold zone.
Sells when %K crosses below %D in overbought zone.
"""
def __init__(self):
super(stochastic_rsi_cross_strategy, self).__init__()
self._k_period = self.Param("KPeriod", 3).SetGreaterThanZero().SetDisplay("K Period", "SMA period of raw StochRSI.", "Indicators")
self._d_period = self.Param("DPeriod", 3).SetGreaterThanZero().SetDisplay("D Period", "SMA period of formed K values.", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero() \
.SetDisplay("RSI Period", "Period of native close-price RSI.", "Indicators")
self._stoch_period = self.Param("StochPeriod", 14).SetGreaterThanZero() \
.SetDisplay("Stochastic Period", "Rolling range of formed RSI values.", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative() \
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection")
self._prev_k = Decimal.Zero
self._prev_d = Decimal.Zero
self._has_prev = False
self._pending_order = None
self._rsi_window = []
self._rsi = None
self._k_average = None
self._d_average = None
self.OrderRegistering += self._track_pending
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(stochastic_rsi_cross_strategy, self).OnReseted()
self._prev_k = Decimal.Zero
self._prev_d = Decimal.Zero
self._has_prev = False
self._pending_order = None
self._rsi_window = []
self._rsi = None
self._k_average = None
self._d_average = None
def OnStarted2(self, time):
super(stochastic_rsi_cross_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self._rsi_period.Value
self._k_average = SimpleMovingAverage()
self._k_average.Length = self._k_period.Value
self._d_average = SimpleMovingAverage()
self._d_average.Length = self._d_period.Value
self.Indicators.Add(self._k_average)
self.Indicators.Add(self._d_average)
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._rsi)
self.DrawIndicator(area, self._k_average)
self.DrawIndicator(area, self._d_average)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native protection runs before this callback, including between signal bars.
pass
@staticmethod
def _feed(average, value, time):
indicator_input = DecimalIndicatorValue(average, value, time)
indicator_input.IsFinal = True
return average.Process(indicator_input).GetValue[Decimal](None)
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished or not self._rsi.IsFormed:
return
self._rsi_window.append(rsi_value)
if len(self._rsi_window) > self._stoch_period.Value:
del self._rsi_window[0]
if len(self._rsi_window) < self._stoch_period.Value:
return
low = min(self._rsi_window)
high = max(self._rsi_window)
raw = Decimal(50) if high == low else Decimal(100) * (rsi_value - low) / (high - low)
k = self._feed(self._k_average, raw, candle.OpenTime)
if not self._k_average.IsFormed:
return
d = self._feed(self._d_average, k, candle.OpenTime)
if not self._d_average.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
# Quantize decisions only; the D SMA receives the unrounded K value.
k = Decimal.Round(k, 8)
d = Decimal.Round(d, 8)
if not self._has_prev:
self._has_prev = True
self._prev_k = k
self._prev_d = d
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
self._prev_k = k
self._prev_d = d
return
crossed_up = self._prev_k <= self._prev_d and k > d
crossed_down = self._prev_k >= self._prev_d and k < d
if crossed_up and k < 20 and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif crossed_down and k > 80 and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
elif self.Position > 0 and crossed_down:
self.SellMarket(self.Position)
elif self.Position < 0 and crossed_up:
self.BuyMarket(Math.Abs(self.Position))
self._prev_k = k
self._prev_d = d
def CreateClone(self):
return stochastic_rsi_cross_strategy()