This strategy trades against extreme RVI values. It operates on the Relative Vigor Index (RVI) and opens positions when the indicator leaves overbought or oversold zones or when the RVI crosses its signal line. Two signal modes are supported:
Levels – reacts to RVI crossing predefined upper or lower thresholds.
Cross – reacts to RVI crossing its signal line.
The logic is contrarian: if RVI was above the high level and then falls back, a long position is opened. If RVI was below the low level and then rises back, a short position is opened.
Parameters
Name
Description
RviPeriod
RVI calculation period.
HighLevel
Upper threshold for the RVI.
LowLevel
Lower threshold for the RVI.
Mode
Signal generation mode (Levels or Cross).
EnableBuyOpen
Allow opening long positions.
EnableSellOpen
Allow opening short positions.
EnableBuyClose
Allow closing long positions.
EnableSellClose
Allow closing short positions.
CandleType
Candle time frame.
How It Works
RVI and its simple moving average are calculated on every finished candle.
Depending on the selected mode, the strategy checks for:
the RVI leaving an extreme level, or
the RVI crossing its signal line.
On a long signal the strategy closes short positions and opens a long one. On a short signal it closes long positions and opens a short one.
The default time frame is four hours.
Notes
Orders are executed with market orders.
Stop-loss and take-profit management can be added separately if required.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// RVI histogram reversal strategy.
/// Opens long when RVI Average crosses above Signal.
/// Opens short when RVI Average crosses below Signal.
/// </summary>
public class RviHistogramReversalStrategy : Strategy
{
private readonly StrategyParam<int> _rviPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevAvg;
private decimal? _prevSig;
public int RviPeriod { get => _rviPeriod.Value; set => _rviPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public RviHistogramReversalStrategy()
{
_rviPeriod = Param(nameof(RviPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RVI Period", "Period of RVI indicator", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_prevAvg = null;
_prevSig = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rvi = new RelativeVigorIndex();
rvi.Average.Length = RviPeriod;
rvi.Signal.Length = RviPeriod;
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 value)
{
if (candle.State != CandleStates.Finished)
return;
var rviVal = value as IRelativeVigorIndexValue;
if (rviVal?.Average is not decimal avg || rviVal?.Signal is not decimal sig)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevAvg = avg;
_prevSig = sig;
return;
}
if (_prevAvg is decimal pa && _prevSig is decimal ps)
{
// Average crosses above Signal - buy
if (pa <= ps && avg > sig && Position <= 0)
BuyMarket();
// Average crosses below Signal - sell
else if (pa >= ps && avg < sig && Position >= 0)
SellMarket();
}
_prevAvg = avg;
_prevSig = sig;
}
}
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_histogram_reversal_strategy(Strategy):
def __init__(self):
super(rvi_histogram_reversal_strategy, self).__init__()
self._rvi_period = self.Param("RviPeriod", 14).SetDisplay("RVI Period", "Period of RVI indicator", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Type of candles", "General")
self._prev_avg = None
self._prev_sig = None
@property
def rvi_period(self): return self._rvi_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(rvi_histogram_reversal_strategy, self).OnReseted()
self._prev_avg = None
self._prev_sig = None
def OnStarted2(self, time):
super(rvi_histogram_reversal_strategy, self).OnStarted2(time)
rvi = RelativeVigorIndex()
rvi.Average.Length = self.rvi_period
rvi.Signal.Length = self.rvi_period
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, value):
if candle.State != CandleStates.Finished: return
avg = value.Average
sig = value.Signal
if avg is None or sig is None: return
avg = float(avg)
sig = float(sig)
if self._prev_avg is not None and self._prev_sig is not None:
if self._prev_avg <= self._prev_sig and avg > sig and self.Position <= 0:
self.BuyMarket()
elif self._prev_avg >= self._prev_sig and avg < sig and self.Position >= 0:
self.SellMarket()
self._prev_avg = avg
self._prev_sig = sig
def CreateClone(self): return rvi_histogram_reversal_strategy()