Открыть на GitHub

Стратегия RAVIiAO (StockSharp)

Обзор

Стратегия RAVIiAO переносит советник MetaTrader 4 «RAVIiAO» на высокоуровневый API StockSharp. Система ждёт закрытия новой свечи, оценивает наклон осциллятора RAVI и одновременно анализирует осциллятор Acceleration/Deceleration (AC) Билла Уильямса. Если оба индикатора подтверждают однонаправленный тренд, позиция открывается по рынку без задержек. Параметры оригинала — периоды средних, порог, дистанции стопов и объём заявки — сохранены, поэтому поведение стратегии полностью соответствует MQL-версии.

Последовательность работы

  1. Подписка на свечи — стратегия подписывается на настраиваемый таймфрейм (по умолчанию 30-минутные свечи).
  2. Расчёт индикаторов — после закрытия свечи обновляются две скользящие средние для построения RAVI, а те же данные подаются в связку «Awesome Oscillator + SMA(5)», что даёт значение AC.
  3. Буферизация значений — последняя завершённая свеча сохраняется как «бар 1», предыдущая — как «бар 2», что напрямую соответствует вызовам iCustom(...,1) и iCustom(...,2) в MetaTrader.
  4. Решение об открытии — длинная позиция появляется при выполнении условий AC[1] > AC[2] > 0 и RAVI[1] > RAVI[2] > Threshold. Для короткой позиции условия зеркальные.
  5. Риск-контроль — сразу после входа фиксируются уровни стоп-лосса и тейк-профита в пунктах (StopLossPoints * PriceStep). Каждый бар проверяется по минимуму/максимуму на предмет пробоя защитных уровней.
  6. Сброс состояния — при срабатывании защитных уровней позиция закрывается рыночной заявкой, внутренние буферы очищаются и стратегия готова к следующему сигналу.

Торговые правила

  • Покупка
    • Значение AC на предыдущем баре выше, чем на более раннем баре, и оба значения положительные.
    • Значение RAVI на предыдущем баре выше порога и выше, чем на баре №2.
    • На момент сигнала позиция отсутствует.
  • Продажа
    • Значение AC на предыдущем баре ниже, чем на баре №2, и оба значения отрицательные.
    • Значение RAVI на предыдущем баре ниже отрицательного порога и ниже значения на баре №2.
    • В момент появления сигнала позиция отсутствует.
  • Выход
    • Стоп-лосс и тейк-профит задаются в пунктах и пересчитываются в цены через PriceStep.
    • Пробои определяются по экстремумам свечи (минимум для стопа в лонге, максимум для стопа в шорте и т. д.), после чего позиция закрывается рыночной заявкой, что имитирует MT4-логику SL/TP.

Параметры

Имя Описание
CandleType Таймфрейм свечей для расчётов (по умолчанию 30 минут).
FastLength Период быстрой средней в осцилляторе RAVI.
SlowLength Период медленной средней в осцилляторе RAVI.
Threshold Минимальная абсолютная величина RAVI для подтверждения тренда.
StopLossPoints Дистанция стоп-лосса в пунктах (умножается на PriceStep).
TakeProfitPoints Дистанция тейк-профита в пунктах.
TradeVolume Объём рыночной заявки при каждом входе.

Особенности переноса

  • Внутренние буферы хранят значения двух последних баров, поэтому решение на свече n использует метрики предыдущего бара, аналогично AC[1] и RAVI[1] в MetaTrader.
  • Индикатор AC построен как разница между Awesome Oscillator и его 5-периодной SMA, что повторяет расчёт из MT4.
  • Вместо регистрации стоп-заявок используется контроль экстремумов свечей, что сохраняет поведение SL/TP и лучше вписывается в архитектуру StockSharp.

Рекомендации по применению

  • Убедитесь, что у инструмента корректно задан шаг цены (PriceStep), иначе дистанции защитных уровней будут отличаться от MT4.
  • Для адаптации к другим рынкам оптимизируйте Threshold, FastLength и SlowLength.
  • Дополните стратегию риск-параметрами уровня портфеля или коннектора, чтобы повысить устойчивость в реальной торговле.
using System;
using System.Linq;
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>
/// Port of the MetaTrader 4 expert advisor "RAVIiAO" that combines the RAVI oscillator and the Acceleration/Deceleration oscillator.
/// </summary>
public class RaviIaoStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<decimal> _threshold;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;

	private SMA _aoAverage;

	private decimal? _prevRavi;
	private decimal? _prevPrevRavi;
	private decimal? _prevAc;
	private decimal? _prevPrevAc;

	/// <summary>
	/// Type of candles used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Fast moving average length for the RAVI oscillator.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Slow moving average length for the RAVI oscillator.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	/// <summary>
	/// Threshold for bullish or bearish confirmation of the RAVI oscillator (percentage value).
	/// </summary>
	public decimal Threshold
	{
		get => _threshold.Value;
		set => _threshold.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in absolute price units.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in absolute price units.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="RaviIaoStrategy"/>.
	/// </summary>
	public RaviIaoStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(10).TimeFrame())
			.SetDisplay("Candle Type", "Time-frame for analysis", "General");

		_fastLength = Param(nameof(FastLength), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast Length", "Fast SMA period inside RAVI", "RAVI");

		_slowLength = Param(nameof(SlowLength), 72)
			.SetGreaterThanZero()
			.SetDisplay("Slow Length", "Slow SMA period inside RAVI", "RAVI");

		_threshold = Param(nameof(Threshold), 0.3m)
			.SetDisplay("RAVI Threshold", "Minimum absolute RAVI value to confirm the trend", "Signals");

		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price units", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price units", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return new[] { (Security, CandleType) };
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevRavi = null;
		_prevPrevRavi = null;
		_prevAc = null;
		_prevPrevAc = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		var fastMa = new SMA { Length = FastLength };
		var slowMa = new SMA { Length = SlowLength };
		var ao = new AwesomeOscillator();
		_aoAverage = new SMA { Length = 5 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(new IIndicator[] { fastMa, slowMa, ao }, ProcessCandle)
			.Start();

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

		// Use StartProtection for SL/TP
		var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
		var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
		StartProtection(tp, sl);

		base.OnStarted2(time);
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var fastVal = values[0];
		var slowVal = values[1];
		var aoVal = values[2];

		if (fastVal.IsEmpty || slowVal.IsEmpty || aoVal.IsEmpty)
			return;

		var fastValue = fastVal.ToDecimal();
		var slowValue = slowVal.ToDecimal();
		var aoValue = aoVal.ToDecimal();

		// Compute AC = AO - SMA(AO)
		var aoAvgResult = _aoAverage.Process(aoVal);
		if (aoAvgResult.IsEmpty)
			return;

		var aoAvgValue = aoAvgResult.ToDecimal();
		var ac = aoValue - aoAvgValue;

		if (slowValue == 0m)
		{
			UpdateHistory(null, ac);
			return;
		}

		var ravi = 100m * (fastValue - slowValue) / slowValue;

		if (_prevRavi is decimal prevRavi &&
			_prevPrevRavi is decimal prevPrevRavi &&
			_prevAc is decimal prevAc &&
			_prevPrevAc is decimal prevPrevAc &&
			Position == 0 &&
			IsFormedAndOnlineAndAllowTrading())
		{
			var bullish = prevAc > prevPrevAc && prevPrevAc > 0m && prevRavi > prevPrevRavi && prevRavi > Threshold;
			var bearish = prevAc < prevPrevAc && prevPrevAc < 0m && prevRavi < prevPrevRavi && prevRavi < -Threshold;

			if (bullish)
			{
				BuyMarket(Volume);
			}
			else if (bearish)
			{
				SellMarket(Volume);
			}
		}

		UpdateHistory(ravi, ac);
	}

	private void UpdateHistory(decimal? ravi, decimal ac)
	{
		_prevPrevRavi = _prevRavi;
		_prevRavi = ravi;
		_prevPrevAc = _prevAc;
		_prevAc = ac;
	}
}