GitHub で見る

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 = 12
    • SmoothingLength = 13
    • CandleType = 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;
	}
}