Стратегия RVI Diff Reversal
Стратегия торгует по сглаженной разнице между Relative Vigor Index (RVI) и его сигнальной линией. Вход в длинную позицию происходит, когда эта разница перестаёт снижаться и начинает расти, и наоборот для короткой позиции.
Детали
- Критерий входа: изменение наклона сглаженной разницы RVI
- Длинные/Короткие: обе стороны
- Критерий выхода: противоположный сигнал
- Стопы: нет
- Значения по умолчанию:
RviLength= 12SmoothingLength= 13CandleType= 6-часовые свечи
- Фильтры:
- Категория: осциллятор
- Направление: обе
- Индикаторы: RVI, SMA, EMA
- Стопы: нет
- Сложность: базовая
- Таймфрейм: 6H
- Сезонность: нет
- Нейросети: нет
- Дивергенция: нет
- Уровень риска: средний
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()