Color Zerolag RVI Strategy
This strategy uses the Relative Vigor Index and its signal line. It buys when the main RVI line crosses below the signal line and sells when the main line crosses above the signal line.
Details
- Entry Criteria: Cross of RVI and signal line
- Long/Short: Both
- Exit Criteria: Opposite signal
- Stops: No
- Default Values:
RviLength= 14SignalLength= 9BuyOpen= trueSellOpen= trueBuyClose= trueSellClose= trueCandleType= 4 hours
- Filters:
- Category: Oscillator
- Direction: Both
- Indicators: RVI, SMA
- Stops: No
- Complexity: Basic
- Timeframe: Intraday (H4)
- 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>
/// Trades the documented inverse RVI/signal-line cross, with separate entry/exit permissions.
/// </summary>
public class ColorZerolagRviStrategy : Strategy
{
private readonly StrategyParam<int> _rviLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _sellClose;
private decimal? _prevRvi;
private decimal? _prevSignal;
public int RviLength { get => _rviLength.Value; set => _rviLength.Value = value; }
public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public bool BuyOpen { get => _buyOpen.Value; set => _buyOpen.Value = value; }
public bool BuyClose { get => _buyClose.Value; set => _buyClose.Value = value; }
public bool SellOpen { get => _sellOpen.Value; set => _sellOpen.Value = value; }
public bool SellClose { get => _sellClose.Value; set => _sellClose.Value = value; }
public ColorZerolagRviStrategy()
{
_rviLength = Param(nameof(RviLength), 14)
.SetGreaterThanZero()
.SetDisplay("RVI Length", "RVI calculation period", "Indicator");
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "RVI signal line period", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_buyOpen = Param(nameof(BuyOpen), true)
.SetDisplay("Buy Open", "Allow long entries", "Trading");
_buyClose = Param(nameof(BuyClose), true)
.SetDisplay("Buy Close", "Close longs on a sell signal", "Trading");
_sellOpen = Param(nameof(SellOpen), true)
.SetDisplay("Sell Open", "Allow short entries", "Trading");
_sellClose = Param(nameof(SellClose), true)
.SetDisplay("Sell Close", "Close shorts on a buy signal", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRvi = null;
_prevSignal = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rvi = new RelativeVigorIndex();
rvi.Average.Length = RviLength;
rvi.Signal.Length = SignalLength;
SubscribeCandles(CandleType)
.BindEx(rvi, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rviValue)
{
if (candle.State != CandleStates.Finished)
return;
var value = (IRelativeVigorIndexValue)rviValue;
if (value.Average is not decimal rvi || value.Signal is not decimal signal)
return;
if (_prevRvi is null || _prevSignal is null)
{
_prevRvi = rvi;
_prevSignal = signal;
return;
}
var crossUp = _prevRvi < _prevSignal && rvi > signal;
var crossDown = _prevRvi > _prevSignal && rvi < signal;
// Downward RVI crossover is the documented buy signal; upward is sell.
// A disabled opposite-position close also suppresses opening the reverse
// side, preventing an accidental netting trade.
if (crossDown)
{
var position = Position;
if (position < 0 && SellClose)
BuyMarket(-position);
if (BuyOpen && (position == 0 || (position < 0 && SellClose)))
BuyMarket(Volume);
}
else if (crossUp)
{
var position = Position;
if (position > 0 && BuyClose)
SellMarket(position);
if (SellOpen && (position == 0 || (position > 0 && BuyClose)))
SellMarket(Volume);
}
_prevRvi = rvi;
_prevSignal = signal;
}
}
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 color_zerolag_rvi_strategy(Strategy):
def __init__(self):
super(color_zerolag_rvi_strategy, self).__init__()
self._rvi_length = self.Param("RviLength", 14) \
.SetDisplay("RVI Length", "RVI calculation period", "Indicator")
self._signal_length = self.Param("SignalLength", 9) \
.SetDisplay("Signal Length", "RVI signal line period", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._buy_open = self.Param("BuyOpen", True).SetDisplay("Buy Open", "Allow long entries", "Trading")
self._buy_close = self.Param("BuyClose", True).SetDisplay("Buy Close", "Close longs on a sell signal", "Trading")
self._sell_open = self.Param("SellOpen", True).SetDisplay("Sell Open", "Allow short entries", "Trading")
self._sell_close = self.Param("SellClose", True).SetDisplay("Sell Close", "Close shorts on a buy signal", "Trading")
self._prev_rvi = None
self._prev_signal = None
@property
def RviLength(self):
return self._rvi_length.Value
@RviLength.setter
def RviLength(self, value):
self._rvi_length.Value = value
@property
def SignalLength(self):
return self._signal_length.Value
@SignalLength.setter
def SignalLength(self, value):
self._signal_length.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BuyOpen(self):
return self._buy_open.Value
@BuyOpen.setter
def BuyOpen(self, value):
self._buy_open.Value = value
@property
def BuyClose(self):
return self._buy_close.Value
@BuyClose.setter
def BuyClose(self, value):
self._buy_close.Value = value
@property
def SellOpen(self):
return self._sell_open.Value
@SellOpen.setter
def SellOpen(self, value):
self._sell_open.Value = value
@property
def SellClose(self):
return self._sell_close.Value
@SellClose.setter
def SellClose(self, value):
self._sell_close.Value = value
def OnStarted2(self, time):
super(color_zerolag_rvi_strategy, self).OnStarted2(time)
rvi = RelativeVigorIndex()
rvi.Average.Length = self.RviLength
rvi.Signal.Length = self.SignalLength
self.SubscribeCandles(self.CandleType) \
.BindEx(rvi, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, rvi_value):
if candle.State != CandleStates.Finished:
return
avg_raw = rvi_value.Average
sig_raw = rvi_value.Signal
if avg_raw is None or sig_raw is None:
return
rvi_val = float(avg_raw)
signal_val = float(sig_raw)
if self._prev_rvi is None or self._prev_signal is None:
self._prev_rvi = rvi_val
self._prev_signal = signal_val
return
cross_up = self._prev_rvi < self._prev_signal and rvi_val > signal_val
cross_down = self._prev_rvi > self._prev_signal and rvi_val < signal_val
# Downward RVI crossover is the documented *buy* signal.
# Closure and entry are independently configurable; do not net against
# an opposite position when its close permission is disabled.
if cross_down:
current_position = self.Position
if current_position < 0 and self.SellClose:
self.BuyMarket(-current_position)
if self.BuyOpen and (current_position == 0 or (current_position < 0 and self.SellClose)):
self.BuyMarket(self.Volume)
elif cross_up:
current_position = self.Position
if current_position > 0 and self.BuyClose:
self.SellMarket(current_position)
if self.SellOpen and (current_position == 0 or (current_position > 0 and self.BuyClose)):
self.SellMarket(self.Volume)
self._prev_rvi = rvi_val
self._prev_signal = signal_val
def OnReseted(self):
super(color_zerolag_rvi_strategy, self).OnReseted()
self._prev_rvi = None
self._prev_signal = None
def CreateClone(self):
return color_zerolag_rvi_strategy()