Spectral RVI Crossover Strategy
The Spectral RVI Crossover strategy smooths the Relative Vigor Index and its signal line and trades on their crossovers. It buys when the smoothed RVI crosses above the smoothed signal line and sells when the opposite occurs.
Details
- Entry Criteria: smoothed RVI crossing its smoothed signal line
- Long/Short: Both
- Exit Criteria: opposite crossover
- Stops: No
- Default Values:
RviLength= 14SignalLength= 4SmoothLength= 20
- Filters:
- Category: Oscillator
- Direction: Both
- Indicators: RVI, SMA
- Stops: No
- Complexity: Basic
- Timeframe: 4-hour
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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;
/// <summary>
/// Spectral RVI crossover strategy.
/// Applies smoothing to RVI average and signal and trades on their crossovers.
/// </summary>
public class SpectralRviStrategy : Strategy
{
private readonly StrategyParam<int> _rviLength;
private readonly StrategyParam<int> _smoothLength;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _smoothRvi;
private SimpleMovingAverage _smoothSig;
private decimal? _prevSmRvi;
private decimal? _prevSmSig;
public int RviLength { get => _rviLength.Value; set => _rviLength.Value = value; }
public int SmoothLength { get => _smoothLength.Value; set => _smoothLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public SpectralRviStrategy()
{
_rviLength = Param(nameof(RviLength), 14)
.SetGreaterThanZero()
.SetDisplay("RVI Length", "Length for RVI", "General");
_smoothLength = Param(nameof(SmoothLength), 10)
.SetGreaterThanZero()
.SetDisplay("Smooth Length", "Smoothing length", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_smoothRvi = null;
_smoothSig = null;
_prevSmRvi = null;
_prevSmSig = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevSmRvi = null;
_prevSmSig = null;
_smoothRvi = new SimpleMovingAverage { Length = SmoothLength };
_smoothSig = new SimpleMovingAverage { Length = SmoothLength };
var rvi = new RelativeVigorIndex();
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(rvi, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rvi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rviVal)
{
if (candle.State != CandleStates.Finished)
return;
if (rviVal is not IRelativeVigorIndexValue rviTyped)
return;
if (rviTyped.Average is not decimal avg || rviTyped.Signal is not decimal sig)
return;
var t = candle.CloseTime;
var smRviResult = _smoothRvi.Process(avg, t, true);
var smSigResult = _smoothSig.Process(sig, t, true);
if (!_smoothRvi.IsFormed || !_smoothSig.IsFormed)
return;
var smRvi = smRviResult.ToDecimal();
var smSig = smSigResult.ToDecimal();
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevSmRvi = smRvi;
_prevSmSig = smSig;
return;
}
if (_prevSmRvi is decimal prevR && _prevSmSig is decimal prevS)
{
if (prevR <= prevS && smRvi > smSig && Position <= 0)
BuyMarket();
else if (prevR >= prevS && smRvi < smSig && Position >= 0)
SellMarket();
}
_prevSmRvi = smRvi;
_prevSmSig = smSig;
}
}
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 RelativeVigorIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class spectral_rvi_crossover_strategy(Strategy):
def __init__(self):
super(spectral_rvi_crossover_strategy, self).__init__()
self._rvi_length = self.Param("RviLength", 14) \
.SetDisplay("RVI Length", "Length for RVI", "General")
self._smooth_length = self.Param("SmoothLength", 10) \
.SetDisplay("Smooth Length", "Smoothing length", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._smooth_rvi = None
self._smooth_sig = None
self._prev_sm_rvi = None
self._prev_sm_sig = None
@property
def rvi_length(self):
return self._rvi_length.Value
@property
def smooth_length(self):
return self._smooth_length.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(spectral_rvi_crossover_strategy, self).OnReseted()
self._smooth_rvi = None
self._smooth_sig = None
self._prev_sm_rvi = None
self._prev_sm_sig = None
def OnStarted2(self, time):
super(spectral_rvi_crossover_strategy, self).OnStarted2(time)
self._prev_sm_rvi = None
self._prev_sm_sig = None
self._smooth_rvi = SimpleMovingAverage()
self._smooth_rvi.Length = self.smooth_length
self._smooth_sig = SimpleMovingAverage()
self._smooth_sig.Length = self.smooth_length
rvi = RelativeVigorIndex()
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(rvi, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rvi)
self.DrawOwnTrades(area)
def process_candle(self, candle, rvi_val):
if candle.State != CandleStates.Finished:
return
avg = rvi_val.Average
sig = rvi_val.Signal
if avg is None or sig is None:
return
avg = float(avg)
sig = float(sig)
t = candle.CloseTime
sm_rvi_result = process_float(self._smooth_rvi, avg, t, True)
sm_sig_result = process_float(self._smooth_sig, sig, t, True)
if not self._smooth_rvi.IsFormed or not self._smooth_sig.IsFormed:
return
sm_rvi = float(sm_rvi_result)
sm_sig = float(sm_sig_result)
if self._prev_sm_rvi is not None and self._prev_sm_sig is not None:
if self._prev_sm_rvi <= self._prev_sm_sig and sm_rvi > sm_sig and self.Position <= 0:
self.BuyMarket()
elif self._prev_sm_rvi >= self._prev_sm_sig and sm_rvi < sm_sig and self.Position >= 0:
self.SellMarket()
self._prev_sm_rvi = sm_rvi
self._prev_sm_sig = sm_sig
def CreateClone(self):
return spectral_rvi_crossover_strategy()