Открыть на GitHub

Laguerre ROC

Стратегия использует осциллятор Laguerre Rate of Change для поиска разворотных моментов.

Осциллятор вычисляет изменение цены и пропускает его через четырёхэтапный фильтр Лагерра. Полученный показатель нормализован в диапазоне 0–1 и сравнивается с порогами:

  • Up Level — зона перекупленности, значения выше неё означают сильный рост.
  • Down Level — зона перепроданности, значения ниже неё означают сильное падение.

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

  1. Если осциллятор выходит из перекупленности (предыдущее значение > Up Level, текущее < Up Level), открывается длинная позиция.
  2. Если осциллятор выходит из перепроданности (предыдущее значение < Down Level, текущее > Down Level), открывается короткая позиция.
  3. Длинная позиция закрывается, когда показатель падает ниже нейтрального уровня 0.5.
  4. Короткая позиция закрывается, когда показатель поднимается выше 0.5.

Параметры:

  • Period — глубина расчёта изменения цены.
  • Gamma — коэффициент сглаживания фильтра Лагерра.
  • Up Level — порог перекупленности.
  • Down Level — порог перепроданности.
  • Candle Type — тип используемых свечей.

Пример показывает, как реализовать собственную логику индикатора в стратегии StockSharp с использованием стандартного индикатора изменения цены и ручного фильтра Лагерра.

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>
/// Strategy based on the Laguerre rate of change oscillator.
/// </summary>
public class LaguerreRocStrategy : Strategy
{
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<decimal> _gamma;
	private readonly StrategyParam<decimal> _upLevel;
	private readonly StrategyParam<decimal> _downLevel;
	private readonly StrategyParam<DataType> _candleType;

	private RateOfChange _roc;
	private decimal _l0, _l1, _l2, _l3;
	private bool _isFirst = true;
	private int _prevColor = 2;

	public int Period { get => _period.Value; set => _period.Value = value; }
	public decimal Gamma { get => _gamma.Value; set => _gamma.Value = value; }
	public decimal UpLevel { get => _upLevel.Value; set => _upLevel.Value = value; }
	public decimal DownLevel { get => _downLevel.Value; set => _downLevel.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public LaguerreRocStrategy()
	{
		_period = Param(nameof(Period), 5)
			.SetGreaterThanZero()
			.SetDisplay("Period", "Rate of change lookback", "Indicators");

		_gamma = Param(nameof(Gamma), 0.5m)
			.SetDisplay("Gamma", "Laguerre smoothing factor", "Indicators");

		_upLevel = Param(nameof(UpLevel), 0.75m)
			.SetDisplay("Up Level", "Overbought threshold", "Indicators");

		_downLevel = Param(nameof(DownLevel), 0.25m)
			.SetDisplay("Down Level", "Oversold threshold", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_isFirst = true;
		_prevColor = 2;
		_l0 = _l1 = _l2 = _l3 = 0m;
		_roc = default;
	}

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

		_roc = new RateOfChange { Length = Period };

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

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

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

		decimal l0, l1, l2, l3;

		if (_isFirst)
		{
			l0 = l1 = l2 = l3 = rocValue;
			_isFirst = false;
		}
		else
		{
			l0 = (1 - Gamma) * rocValue + Gamma * _l0;
			l1 = -Gamma * l0 + _l0 + Gamma * _l1;
			l2 = -Gamma * l1 + _l1 + Gamma * _l2;
			l3 = -Gamma * l2 + _l2 + Gamma * _l3;
		}

		var cu = 0m;
		var cd = 0m;

		if (l0 >= l1) cu += l0 - l1; else cd += l1 - l0;
		if (l1 >= l2) cu += l1 - l2; else cd += l2 - l1;
		if (l2 >= l3) cu += l2 - l3; else cd += l3 - l2;

		var denom = cu + cd;
		var lroc = denom != 0m ? cu / denom : 0m;

		int color = 2;
		if (lroc > UpLevel) color = 4;
		else if (lroc > 0.5m) color = 3;
		if (lroc < DownLevel) color = 0;
		else if (lroc < 0.5m) color = 1;

		if (_prevColor > 2 && color <= 2 && Position < 0)
			BuyMarket();
		if (_prevColor < 2 && color >= 2 && Position > 0)
			SellMarket();

		if (_prevColor == 4 && color < 4 && Position <= 0)
			BuyMarket();
		if (_prevColor == 0 && color > 0 && Position >= 0)
			SellMarket();

		_prevColor = color;
		_l0 = l0; _l1 = l1; _l2 = l2; _l3 = l3;
	}
}