Auf GitHub ansehen

Laguerre ROC Strategy

This strategy uses the Laguerre rate-of-change oscillator to capture trend reversals.

The Laguerre ROC oscillator smooths rate of change through a four-stage Laguerre filter. Values are normalized between 0 and 1. Two thresholds define overbought and oversold zones:

  • Up Level – values above this level indicate strong upward momentum.
  • Down Level – values below this level indicate strong downward momentum.

Trading logic:

  1. When the oscillator falls from the overbought zone (previous value above Up Level and current value below) the strategy enters a long position.
  2. When the oscillator rises from the oversold zone (previous value below Down Level and current value above) the strategy enters a short position.
  3. If a long position is open and the oscillator turns bearish (previous value below the neutral level of 0.5) the position is closed.
  4. If a short position is open and the oscillator turns bullish (previous value above 0.5) the position is closed.

Parameters:

  • Period – lookback length for the rate-of-change calculation.
  • Gamma – smoothing factor for the Laguerre filter.
  • Up Level – overbought threshold.
  • Down Level – oversold threshold.
  • Candle Type – timeframe used for candle data.

The example demonstrates how custom indicator logic can be recreated within a high-level StockSharp strategy using built-in rate-of-change and manual Laguerre filtering.

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;
	}
}