Стратегия торгует против экстремальных значений индикатора RVI (Relative Vigor Index). Позиции открываются, когда RVI выходит из зоны перекупленности или перепроданности, либо когда линия RVI пересекает сигнальную линию. Поддерживаются два режима сигналов:
Levels – реагирует на пересечение верхнего или нижнего порога RVI.
Cross – реагирует на пересечение RVI и его сигнальной линии.
Логика контртрендовая: если RVI был выше верхнего уровня и затем опустился ниже, открывается длинная позиция. Если RVI был ниже нижнего уровня и затем поднялся выше, открывается короткая позиция.
Параметры
Название
Описание
RviPeriod
Период расчёта RVI.
HighLevel
Верхний порог RVI.
LowLevel
Нижний порог RVI.
Mode
Режим сигналов (Levels или Cross).
EnableBuyOpen
Разрешить открытие длинных позиций.
EnableSellOpen
Разрешить открытие коротких позиций.
EnableBuyClose
Разрешить закрытие длинных позиций.
EnableSellClose
Разрешить закрытие коротких позиций.
CandleType
Таймфрейм свечей.
Как работает
На каждой завершённой свече вычисляются RVI и его скользящая средняя.
В зависимости от выбранного режима проверяется:
выход RVI из экстремальной зоны, либо
пересечение RVI с сигнальной линией.
При сигнале на покупку закрываются короткие позиции и открывается длинная. При сигнале на продажу закрываются длинные позиции и открывается короткая.
Таймфрейм по умолчанию — четыре часа.
Примечания
Используются рыночные заявки.
При необходимости можно дополнить стратегию управлением стоп-лоссом и тейк-профитом.
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()