Открыть на GitHub

Стратегия ROC2 VG

Реализует в StockSharp эксперт Exp_ROC2_VG из MetaTrader.
Сравниваются две линии темпа изменения цены с настраиваемыми периодами и типами расчёта.
Длинная позиция открывается, когда первая линия пересекает вторую сверху вниз.
Короткая позиция открывается при обратном пересечении. Параметр Invert меняет линии местами.

Детали

  • Вход в лонг: предыдущий up > предыдущего down И текущий up <= текущего down.
  • Вход в шорт: предыдущий up < предыдущего down И текущий up >= текущего down.
  • Выход: сигнал разворота сразу переворачивает позицию рыночными ордерами.
  • Таймфрейм: настраиваемый тип свечей, по умолчанию 4 часа.
  • Индикаторы: каждая линия может использовать Momentum или варианты ROC:
    • Momentum = цена - предыдущая цена
    • ROC = ((цена / предыдущая) - 1) * 100
    • ROCP = (цена - предыдущая) / предыдущая
    • ROCR = цена / предыдущая
    • ROCR100 = (цена / предыдущая) * 100
  • Значения по умолчанию:
    • RocPeriod1 = 8, RocType1 = Momentum
    • RocPeriod2 = 14, RocType2 = Momentum
    • Invert = false

При смене сигнала стратегия разворачивает позицию.

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 that trades on the crossing of two rate of change lines.
/// </summary>
public class Roc2VgStrategy : Strategy
{
	/// <summary>
	/// Types of rate of change calculation.
	/// </summary>
	public enum RocTypes
	{
		/// <summary>Price - previous price.</summary>
		Momentum,

		/// <summary>((price / previous) - 1) * 100.</summary>
		Roc,

		/// <summary>(price - previous) / previous.</summary>
		RocP,

		/// <summary>price / previous.</summary>
		RocR,

		/// <summary>(price / previous) * 100.</summary>
		RocR100
	}
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rocPeriod1;
	private readonly StrategyParam<RocTypes> _rocType1;
	private readonly StrategyParam<int> _rocPeriod2;
	private readonly StrategyParam<RocTypes> _rocType2;
	private readonly StrategyParam<bool> _invert;

	private decimal? _prevUp;
	private decimal? _prevDn;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int RocPeriod1 { get => _rocPeriod1.Value; set => _rocPeriod1.Value = value; }
	public RocTypes RocType1 { get => _rocType1.Value; set => _rocType1.Value = value; }
	public int RocPeriod2 { get => _rocPeriod2.Value; set => _rocPeriod2.Value = value; }
	public RocTypes RocType2 { get => _rocType2.Value; set => _rocType2.Value = value; }
	public bool Invert { get => _invert.Value; set => _invert.Value = value; }

	public Roc2VgStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles", "General");
		_rocPeriod1 = Param(nameof(RocPeriod1), 8).SetGreaterThanZero()
			.SetDisplay("ROC Period 1", "Length of first ROC", "Indicator");
		_rocType1 = Param(nameof(RocType1), RocTypes.Momentum)
			.SetDisplay("ROC Type 1", "Type of first ROC", "Indicator");
		_rocPeriod2 = Param(nameof(RocPeriod2), 14).SetGreaterThanZero()
			.SetDisplay("ROC Period 2", "Length of second ROC", "Indicator");
		_rocType2 = Param(nameof(RocType2), RocTypes.Momentum)
			.SetDisplay("ROC Type 2", "Type of second ROC", "Indicator");
		_invert = Param(nameof(Invert), false).SetDisplay("Invert", "Swap ROC lines", "General");
	}

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

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

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

		var ind1 = CreateIndicator(RocType1, RocPeriod1);
		var ind2 = CreateIndicator(RocType2, RocPeriod2);

		var sub = SubscribeCandles(CandleType);
		sub.Bind(ind1, ind2, Process).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, sub);
			DrawIndicator(area, ind1);
			DrawIndicator(area, ind2);
			DrawOwnTrades(area);
		}
	}

	private static IIndicator CreateIndicator(RocTypes type, int period)
	{
		return type == RocTypes.Momentum
			? new Momentum { Length = period }
			: new ROC { Length = period };
	}

	private decimal Transform(RocTypes type, decimal value)
	{
		return type switch
		{
			RocTypes.Momentum => value,
			RocTypes.Roc => value * 100m,
			RocTypes.RocP => value,
			RocTypes.RocR => value + 1m,
			RocTypes.RocR100 => (value + 1m) * 100m,
			_ => value
		};
	}

	private void Process(ICandleMessage candle, decimal v1, decimal v2)
	{
		if (candle.State != CandleStates.Finished || !IsFormedAndOnlineAndAllowTrading())
			return;

		var up = Transform(RocType1, Invert ? v2 : v1);
		var dn = Transform(RocType2, Invert ? v1 : v2);

		if (_prevUp is decimal pUp && _prevDn is decimal pDn)
		{
			if (pUp > pDn && up <= dn && Position <= 0)
				BuyMarket();
			else if (pUp < pDn && up >= dn && Position >= 0)
				SellMarket();
		}

		_prevUp = up;
		_prevDn = dn;
	}
}