Открыть на GitHub

Стратегия Fast Slow RVI Crossover

Обзор

Стратегия повторяет эксперта MetaTrader _HPCS_FastSlowRVIsCrossOver_MT4_EA_V01_WE. Торговые решения принимаются, когда основная линия индикатора Relative Vigor Index (RVI) пересекает свою сигнальную линию в пределах заданного торгового окна. На одну свечу допускается только один вход. Поддерживаются стоп-лосс, тейк-профит и трейлинг-стоп, задаваемые в пунктах.

Логика торговли

  1. Формируются стандартные свечи согласно параметру Candle Type.
  2. Рассчитывается RVI с периодом RVI Period и сигнальная линия в виде простого скользящего среднего по четырём барам.
  3. При пробое сигнальной линии снизу вверх закрываются короткие позиции и открывается/наращивается длинная.
  4. При пробое сверху вниз закрываются длинные позиции и открывается/наращивается короткая.
  5. Сигналы вне интервала Start TimeStop Time игнорируются.
  6. Защитные ордера выставляются через механизм StartProtection, который также управляет трейлинг-стопом.
  7. Чтобы исключить дублирование сделок, с каждой свечи обрабатывается не более одного сигнала.

Параметры

Название Описание
RVI Period Количество баров в расчёте индикатора RVI.
Take Profit (pips) Дистанция тейк-профита в пунктах. Ноль отключает тейк-профит.
Stop Loss (pips) Дистанция стоп-лосса в пунктах. Ноль отключает стоп-лосс.
Trailing Stop (pips) Дистанция трейлинг-стопа в пунктах. Ноль отключает трейлинг.
Trailing Step (pips) Минимальный благоприятный сдвиг для очередного подтягивания трейлинг-стопа. Работает только при активном трейлинге.
Volume Объём заявки при входе в позицию.
Candle Type Тип свечей или таймфрейм для анализа.
Start Time Начало дневного торгового окна (включительно).
Stop Time Конец дневного торгового окна (исключительно).

Примечания

  • Размер пункта автоматически масштабируется исходя из минимального шага цены инструмента (для пяти- и трёхзнаковых котировок применяется множитель ×10).
  • StartProtection вызывается один раз в OnStarted, что включает штатное сопровождение позиций и трейлинг-стоп.
  • Все комментарии в коде написаны на английском языке, как требует руководство проекта.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Fast and slow Relative Vigor Index crossover strategy.
/// Opens a long position when the RVI average line crosses above the signal line,
/// and opens a short position on the opposite crossover.
/// </summary>
public class FastSlowRviCrossoverStrategy : Strategy
{
	private readonly StrategyParam<int> _rviPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _previousAverage;
	private decimal? _previousSignal;

	public int RviPeriod
	{
		get => _rviPeriod.Value;
		set => _rviPeriod.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public FastSlowRviCrossoverStrategy()
	{
		_rviPeriod = Param(nameof(RviPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("RVI Period", "Period for the Relative Vigor Index", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candles used for analysis", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_previousAverage = null;
		_previousSignal = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_previousAverage = null;
		_previousSignal = null;

		var rvi = new ExponentialMovingAverage { Length = RviPeriod };
		var signal = new ExponentialMovingAverage { Length = RviPeriod * 2 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rvi, signal, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, rvi);
			DrawIndicator(area, signal);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal avgValue, decimal sigValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_previousAverage.HasValue && _previousSignal.HasValue)
		{
			var longSignal = _previousAverage.Value <= _previousSignal.Value && avgValue > sigValue;
			var shortSignal = _previousAverage.Value >= _previousSignal.Value && avgValue < sigValue;

			if (longSignal && Position <= 0)
			{
				BuyMarket();
			}
			else if (shortSignal && Position >= 0)
			{
				SellMarket();
			}
		}

		_previousAverage = avgValue;
		_previousSignal = sigValue;
	}
}