RVI Diff Reversal Strategy
The strategy trades based on the smoothed difference between the Relative Vigor Index (RVI) and its signal line. It detects points where this difference stops falling and begins to rise to enter long, and vice versa for short positions.
Details
- Entry Criteria: Slope reversal of the smoothed RVI difference
- Long/Short: Both
- Exit Criteria: Opposite signal
- Stops: No
- Default Values:
RviLength= 12SmoothingLength= 13CandleType= 6-hour candles
- Filters:
- Category: Oscillator
- Direction: Both
- Indicators: RVI, SMA, EMA
- Stops: No
- Complexity: Basic
- Timeframe: 6H
- 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>
/// Strategy based on the smoothed difference between RVI average and signal.
/// Buy when smoothed diff turns up, sell when it turns down.
/// </summary>
public class RviDiffReversalStrategy : Strategy
{
private readonly StrategyParam<int> _rviLength;
private readonly StrategyParam<int> _smoothingLength;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevDiff;
private decimal? _prevPrevDiff;
public int RviLength { get => _rviLength.Value; set => _rviLength.Value = value; }
public int SmoothingLength { get => _smoothingLength.Value; set => _smoothingLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public RviDiffReversalStrategy()
{
_rviLength = Param(nameof(RviLength), 12)
.SetGreaterThanZero()
.SetDisplay("RVI Length", "Length of RVI", "General");
_smoothingLength = Param(nameof(SmoothingLength), 13)
.SetGreaterThanZero()
.SetDisplay("Smoothing Length", "Length of EMA smoothing", "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();
_prevDiff = null;
_prevPrevDiff = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevDiff = null;
_prevPrevDiff = null;
var rvi = new RelativeVigorIndex();
rvi.Average.Length = RviLength;
rvi.Signal.Length = SmoothingLength;
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 current = avg - sig;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevPrevDiff = _prevDiff;
_prevDiff = current;
return;
}
if (_prevDiff.HasValue && _prevPrevDiff.HasValue)
{
var wasFalling = _prevPrevDiff > _prevDiff;
var wasRising = _prevPrevDiff < _prevDiff;
if (wasFalling && current > _prevDiff && Position <= 0)
BuyMarket();
else if (wasRising && current < _prevDiff && Position >= 0)
SellMarket();
}
_prevPrevDiff = _prevDiff;
_prevDiff = current;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class rvi_diff_reversal_strategy(Strategy):
def __init__(self):
super(rvi_diff_reversal_strategy, self).__init__()
self._rvi_length = self.Param("RviLength", 12) \
.SetDisplay("RVI Length", "Length of RVI", "General")
self._smoothing_length = self.Param("SmoothingLength", 13) \
.SetDisplay("Smoothing Length", "Length of EMA smoothing", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_diff = None
self._prev_prev_diff = None
@property
def rvi_length(self):
return self._rvi_length.Value
@property
def smoothing_length(self):
return self._smoothing_length.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(rvi_diff_reversal_strategy, self).OnReseted()
self._prev_diff = None
self._prev_prev_diff = None
def OnStarted2(self, time):
super(rvi_diff_reversal_strategy, self).OnStarted2(time)
self._prev_diff = None
self._prev_prev_diff = None
rvi = RelativeVigorIndex()
rvi.Average.Length = int(self.rvi_length)
rvi.Signal.Length = int(self.smoothing_length)
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
if not rvi_val.IsFormed:
return
avg = rvi_val.Average
sig = rvi_val.Signal
if avg is None or sig is None:
return
avg = float(avg)
sig = float(sig)
current = avg - sig
if self._prev_diff is not None and self._prev_prev_diff is not None:
was_falling = self._prev_prev_diff > self._prev_diff
was_rising = self._prev_prev_diff < self._prev_diff
if was_falling and current > self._prev_diff and self.Position <= 0:
self.BuyMarket()
elif was_rising and current < self._prev_diff and self.Position >= 0:
self.SellMarket()
self._prev_prev_diff = self._prev_diff
self._prev_diff = current
def CreateClone(self):
return rvi_diff_reversal_strategy()