Auf GitHub ansehen

Trendfolger-Regenbogenstrategie

Überblick

Trend Follower Rainbow Strategy ist eine C#-Portierung des MetaTrader 4-Expertenberaters „TrendFollowerRainbowMethodkyast773“. Die Strategie kombiniert mehrere Bestätigungsebenen, um in Richtung starker Trends zu handeln und gleichzeitig bereichsgebundene Zeiträume herauszufiltern. Es basiert auf der Ausrichtung eines Regenbogens exponentieller gleitender Durchschnitte, MACD-Momentum, Laguerre-Oszillator-Schwellenwerten, Geldflussindexwerten und einem schnellen/langsamen EMA-Crossover, um Positionen auszulösen.

Handelslogik

  1. Handelsfenster – Signale werden nur ausgewertet, wenn die aktuelle Schlusszeit der Kerze genau zwischen den konfigurierbaren Start- und Endstunden liegt. Dies ahmt den ursprünglichen Zeitfilter von EA nach, der die ersten und letzten Handelsstunden der Sitzung vermied.
  2. EMA Crossover-Trigger – Ein langer Aufbau erfordert, dass der schnelle EMA (Standardlänge 4) den langsamen EMA (Standardlänge 8) überschreitet. Ein kurzer Aufbau erfordert die umgekehrte Frequenzweiche.
  3. MACD-Bestätigung – Die MACD-Linie und die Signallinie (Standard 5/35/5) müssen beide über Null für Long-Trades oder unter Null für Short-Trades liegen, um die Momentumausrichtung zu bestätigen.
  4. Laguerre-Filter – Der Wert des Laguerre-Filters muss für Long-Trades über 0,15 und für Short-Trades unter 0,75 liegen und damit die ursprünglichen Schwellenwertprüfungen reproduzieren, die für den benutzerdefinierten Indikator durchgeführt wurden.
  5. Regenbogenausrichtung – Fünf Bündel exponentieller gleitender Durchschnitte (vier EMAs pro Bündel) müssen monoton sortiert werden, um die Regenbogenstruktur zu bestätigen. Bundles werden in bullischen Szenarien auf nicht steigende Ordnung und in bärischen Szenarien auf nicht abnehmende Ordnung bewertet.
  6. Money-Flow-Index-Filter – Der Money-Flow-Index (Standardzeitraum 14) muss für Long-Einträge unter 40 und für Short-Einträge über 60 liegen, um einen Handel gegen den volumengesteuerten Fluss zu vermeiden.
  7. Positionsverwaltung – Marktaufträge werden verwendet. Wenn ein entgegengesetztes Signal auftritt, wird das bestehende Engagement geschlossen und eine neue Position in die entgegengesetzte Richtung eröffnet.

Risikomanagement

Die Strategie unterstützt integrierte Schutzmaßnahmen durch den StartProtection-Helfer von StockSharp:

  • Take-Profit- und Stop-Loss-Abstände werden in Preisschritten ausgedrückt, um die punktbasierte Konfiguration von EA widerzuspiegeln.
  • Die Trailing Stop-Distanz verwendet ebenfalls Preisschritte und wird aktiviert, sobald der Schutzblock gestartet wird.

Parameter

Parameter Beschreibung Standard
OrderVolume Basis-Market-Order-Volumen. 1
TakeProfitPoints Nehmen Sie die Gewinndistanz in Preisschritten. 17
StopLossPoints Stop-Loss-Distanz in Preisschritten. 30
TrailingStopPoints Trailing-Stop-Distanz in Preisschritten. 45
TradingStartHour Erste Stunde (einschließlich), die übersprungen wird, bevor Signale ausgewertet werden. 1
TradingEndHour Letzte Stunde (einschließlich), die nach der Auswertung der Signale übersprungen wird. 23
FastEmaLength Länge des schnellen EMA, der im Crossover-Trigger verwendet wird. 4
SlowEmaLength Länge des langsamen EMA, der im Crossover-Trigger verwendet wird. 8
MacdFastLength MACD schneller EMA Länge. 5
MacdSlowLength MACD langsamer EMA Länge. 35
MacdSignalLength MACD Signal EMA Länge. 5
LaguerreGamma Glättungsfaktor des Laguerre-Filters. 0,7
LaguerreBuyThreshold Die Laguerre-Schwelle wurde für Long-Trades nach oben überschritten. 0,15
LaguerreSellThreshold Die Laguerre-Schwelle wurde für Short-Trades nach unten überschritten. 0,75
MfiPeriod Berechnungszeitraum des Geldflussindex. 14
MfiBuyLevel Maximaler MFI-Level, der noch lange Einträge zulässt. 40
MfiSellLevel Mindest-MFI-Level, das noch kurze Einstiege zulässt. 60
RainbowGroup{1..5}Base Basislänge EMA für jedes Regenbogenbündel. Aus jedem Basiswert werden durch Addition von Offsets (0, 2, 4, 6) vier aufeinanderfolgende EMAs erstellt. 5 / 13 / 21 / 34 / 55
CandleType Von der Strategie verwendete primäre Kerzenserie. Standardmäßig werden 5-Minuten-Kerzen verwendet. Zeitrahmen von 5 Minuten

Diagramme

Die Strategie zeichnet automatisch:

  • Preiskerzen für die abonnierte Serie.
  • Schnelle und langsame EMAs zur visuellen Bestätigung von Überkreuzungen.
  • Laguerre-Filterwerte zur Beobachtung von Schwellenwertüberschreitungen.
  • Eigene Trades werden im Diagrammbereich dargestellt.

Notizen

  • Die Rainbow-Logik nähert sich den ursprünglichen benutzerdefinierten RainbowMMA-Indikatoren an, indem sie konfigurierbare EMA-Bundles erstellt. Passen Sie die Basislängen bei Bedarf an eine bestimmte Regenbogenvorlage an.
  • Alle Codekommentare, Protokolle und Dokumentationen werden bei Bedarf auf Englisch bereitgestellt.
  • Die Strategie konzentriert sich ausschließlich auf die C#-Implementierung. In dieser Aufgabe wird kein Python-Port generiert.
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>
/// Trend following strategy that combines EMA crossover, MACD confirmation,
/// Laguerre filter thresholds, rainbow moving average structure and MFI filter.
/// </summary>
public class TrendFollowerRainbowStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<int> _tradingStartHour;
	private readonly StrategyParam<int> _tradingEndHour;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _macdFastLength;
	private readonly StrategyParam<int> _macdSlowLength;
	private readonly StrategyParam<int> _macdSignalLength;
	private readonly StrategyParam<decimal> _laguerreGamma;
	private readonly StrategyParam<decimal> _laguerreBuyThreshold;
	private readonly StrategyParam<decimal> _laguerreSellThreshold;
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _mfiBuyLevel;
	private readonly StrategyParam<decimal> _mfiSellLevel;
	private readonly StrategyParam<int> _rainbowGroup1Base;
	private readonly StrategyParam<int> _rainbowGroup2Base;
	private readonly StrategyParam<int> _rainbowGroup3Base;
	private readonly StrategyParam<int> _rainbowGroup4Base;
	private readonly StrategyParam<int> _rainbowGroup5Base;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _emaFast = null!;
	private ExponentialMovingAverage _emaSlow = null!;
	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private AdaptiveLaguerreFilter _laguerre = null!;
	private MoneyFlowIndex _mfi = null!;
	private ExponentialMovingAverage[][] _rainbowGroups = [];

	private decimal? _previousFastEma;
	private decimal? _previousSlowEma;
	private decimal? _previousLaguerre;
	private decimal _pointValue;

	/// <summary>
	/// Initializes a new instance of the <see cref="TrendFollowerRainbowStrategy"/> class.
	/// </summary>
	public TrendFollowerRainbowStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
		.SetDisplay("Order Volume", "Base order volume", "Trading")
		;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 17m)
		.SetDisplay("Take Profit (pts)", "Distance in price steps for take profit", "Risk Management")
		;

		_stopLossPoints = Param(nameof(StopLossPoints), 30m)
		.SetDisplay("Stop Loss (pts)", "Distance in price steps for stop loss", "Risk Management")
		;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 45m)
		.SetDisplay("Trailing Stop (pts)", "Distance in price steps for trailing stop", "Risk Management")
		;

		_tradingStartHour = Param(nameof(TradingStartHour), 1)
		.SetDisplay("Start Hour", "Hour (0-23) when trading window opens", "Trading Schedule")
		;

		_tradingEndHour = Param(nameof(TradingEndHour), 23)
		.SetDisplay("End Hour", "Hour (0-23) when trading window closes", "Trading Schedule")
		;

		_fastEmaLength = Param(nameof(FastEmaLength), 4)
		.SetRange(2, 20)
		.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicators")
		;

		_slowEmaLength = Param(nameof(SlowEmaLength), 8)
		.SetRange(3, 50)
		.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicators")
		;

		_macdFastLength = Param(nameof(MacdFastLength), 5)
		.SetDisplay("MACD Fast", "Fast EMA length for MACD", "Indicators")
		;

		_macdSlowLength = Param(nameof(MacdSlowLength), 35)
		.SetDisplay("MACD Slow", "Slow EMA length for MACD", "Indicators")
		;

		_macdSignalLength = Param(nameof(MacdSignalLength), 5)
		.SetDisplay("MACD Signal", "Signal EMA length for MACD", "Indicators")
		;

		_laguerreGamma = Param(nameof(LaguerreGamma), 0.7m)
		.SetRange(0.1m, 0.9m)
		.SetDisplay("Laguerre Gamma", "Smoothing factor for Laguerre filter", "Indicators")
		;

		_laguerreBuyThreshold = Param(nameof(LaguerreBuyThreshold), 0.15m)
		.SetDisplay("Laguerre Buy", "Threshold crossed upward for long signals", "Indicators")
		;

		_laguerreSellThreshold = Param(nameof(LaguerreSellThreshold), 0.75m)
		.SetDisplay("Laguerre Sell", "Threshold crossed downward for short signals", "Indicators")
		;

		_mfiPeriod = Param(nameof(MfiPeriod), 14)
		.SetDisplay("MFI Period", "Money Flow Index calculation period", "Indicators")
		;

		_mfiBuyLevel = Param(nameof(MfiBuyLevel), 40m)
		.SetDisplay("MFI Buy", "Upper bound for oversold check", "Indicators")
		;

		_mfiSellLevel = Param(nameof(MfiSellLevel), 60m)
		.SetDisplay("MFI Sell", "Lower bound for overbought check", "Indicators")
		;

		_rainbowGroup1Base = Param(nameof(RainbowGroup1Base), 5)
		.SetDisplay("Rainbow Group 1", "Base length for the fastest rainbow bundle", "Rainbow")
		;

		_rainbowGroup2Base = Param(nameof(RainbowGroup2Base), 13)
		.SetDisplay("Rainbow Group 2", "Base length for the second rainbow bundle", "Rainbow")
		;

		_rainbowGroup3Base = Param(nameof(RainbowGroup3Base), 21)
		.SetDisplay("Rainbow Group 3", "Base length for the middle rainbow bundle", "Rainbow")
		;

		_rainbowGroup4Base = Param(nameof(RainbowGroup4Base), 34)
		.SetDisplay("Rainbow Group 4", "Base length for the fourth rainbow bundle", "Rainbow")
		;

		_rainbowGroup5Base = Param(nameof(RainbowGroup5Base), 55)
		.SetDisplay("Rainbow Group 5", "Base length for the slowest rainbow bundle", "Rainbow")
		;

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
		.SetDisplay("Candle Type", "Primary candle series", "General");
	}

	/// <summary>
	/// Base order volume.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

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

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

	/// <summary>
	/// Trailing stop distance expressed in price steps.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// First hour (0-23) when the strategy can evaluate entries.
	/// </summary>
	public int TradingStartHour
	{
		get => _tradingStartHour.Value;
		set => _tradingStartHour.Value = value;
	}

	/// <summary>
	/// Last hour (0-23) when the strategy can evaluate entries.
	/// </summary>
	public int TradingEndHour
	{
		get => _tradingEndHour.Value;
		set => _tradingEndHour.Value = value;
	}

	/// <summary>
	/// Fast EMA length used for the crossover signal.
	/// </summary>
	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length used for the crossover signal.
	/// </summary>
	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	/// <summary>
	/// MACD fast EMA length.
	/// </summary>
	public int MacdFastLength
	{
		get => _macdFastLength.Value;
		set => _macdFastLength.Value = value;
	}

	/// <summary>
	/// MACD slow EMA length.
	/// </summary>
	public int MacdSlowLength
	{
		get => _macdSlowLength.Value;
		set => _macdSlowLength.Value = value;
	}

	/// <summary>
	/// MACD signal EMA length.
	/// </summary>
	public int MacdSignalLength
	{
		get => _macdSignalLength.Value;
		set => _macdSignalLength.Value = value;
	}

	/// <summary>
	/// Laguerre filter smoothing factor.
	/// </summary>
	public decimal LaguerreGamma
	{
		get => _laguerreGamma.Value;
		set => _laguerreGamma.Value = value;
	}

	/// <summary>
	/// Laguerre threshold that needs to be crossed upward to allow long signals.
	/// </summary>
	public decimal LaguerreBuyThreshold
	{
		get => _laguerreBuyThreshold.Value;
		set => _laguerreBuyThreshold.Value = value;
	}

	/// <summary>
	/// Laguerre threshold that needs to be crossed downward to allow short signals.
	/// </summary>
	public decimal LaguerreSellThreshold
	{
		get => _laguerreSellThreshold.Value;
		set => _laguerreSellThreshold.Value = value;
	}

	/// <summary>
	/// Money Flow Index period.
	/// </summary>
	public int MfiPeriod
	{
		get => _mfiPeriod.Value;
		set => _mfiPeriod.Value = value;
	}

	/// <summary>
	/// Maximum MFI level that still allows long entries.
	/// </summary>
	public decimal MfiBuyLevel
	{
		get => _mfiBuyLevel.Value;
		set => _mfiBuyLevel.Value = value;
	}

	/// <summary>
	/// Minimum MFI level that still allows short entries.
	/// </summary>
	public decimal MfiSellLevel
	{
		get => _mfiSellLevel.Value;
		set => _mfiSellLevel.Value = value;
	}

	/// <summary>
	/// Base period for the fastest rainbow bundle.
	/// </summary>
	public int RainbowGroup1Base
	{
		get => _rainbowGroup1Base.Value;
		set => _rainbowGroup1Base.Value = value;
	}

	/// <summary>
	/// Base period for the second rainbow bundle.
	/// </summary>
	public int RainbowGroup2Base
	{
		get => _rainbowGroup2Base.Value;
		set => _rainbowGroup2Base.Value = value;
	}

	/// <summary>
	/// Base period for the third rainbow bundle.
	/// </summary>
	public int RainbowGroup3Base
	{
		get => _rainbowGroup3Base.Value;
		set => _rainbowGroup3Base.Value = value;
	}

	/// <summary>
	/// Base period for the fourth rainbow bundle.
	/// </summary>
	public int RainbowGroup4Base
	{
		get => _rainbowGroup4Base.Value;
		set => _rainbowGroup4Base.Value = value;
	}

	/// <summary>
	/// Base period for the fifth rainbow bundle.
	/// </summary>
	public int RainbowGroup5Base
	{
		get => _rainbowGroup5Base.Value;
		set => _rainbowGroup5Base.Value = value;
	}

	/// <summary>
	/// Candle type used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

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

		_previousFastEma = null;
		_previousSlowEma = null;
		_previousLaguerre = null;
		_pointValue = 0m;
		_rainbowGroups = [];
	}

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

		_pointValue = Security?.PriceStep ?? 0m;
		Volume = OrderVolume;

		var takeProfit = ToAbsoluteUnit(TakeProfitPoints);
		var stopLoss = ToAbsoluteUnit(StopLossPoints);

		if (takeProfit != null || stopLoss != null)
		{
			StartProtection(
			takeProfit: takeProfit,
			stopLoss: stopLoss,
			isStopTrailing: TrailingStopPoints > 0m,
			useMarketOrders: true);
		}

		_emaFast = new EMA { Length = FastEmaLength };
		_emaSlow = new EMA { Length = SlowEmaLength };
		_macd = new MovingAverageConvergenceDivergenceSignal();
		_macd.Macd.ShortMa.Length = MacdFastLength;
		_macd.Macd.LongMa.Length = MacdSlowLength;
		_macd.SignalMa.Length = MacdSignalLength;
		_laguerre = new AdaptiveLaguerreFilter { Gamma = LaguerreGamma };
		_mfi = new MoneyFlowIndex { Length = MfiPeriod };
		_rainbowGroups = BuildRainbowGroups();

		var indicators = new List<IIndicator>
		{
			_emaFast,
			_emaSlow,
			_macd,
			_laguerre,
			_mfi
		};

		foreach (var group in _rainbowGroups)
		{
			indicators.AddRange(group);
		}

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(indicators.ToArray(), ProcessCandle)
		.Start();

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

	private ExponentialMovingAverage[][] BuildRainbowGroups()
	{
		var offsets = new[] { 0, 2, 4, 6 };

		return new[]
		{
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup1Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup2Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup3Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup4Base + o) }).ToArray(),
			offsets.Select(o => new EMA { Length = Math.Max(1, RainbowGroup5Base + o) }).ToArray()
		};
	}

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

		var hour = candle.CloseTime.Hour;
		if (hour <= TradingStartHour || hour >= TradingEndHour)
		{
			UpdatePreviousValues(values);
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			UpdatePreviousValues(values);
			return;
		}

		var index = 0;

		var hasFast = TryGetDecimal(values[index++], out var fastEma);
		var hasSlow = TryGetDecimal(values[index++], out var slowEma);
		if (!hasFast || !hasSlow)
		{
			UpdatePreviousValues(values, hasFast ? fastEma : null, hasSlow ? slowEma : null);
			return;
		}

		var macdValue = values[index++];
		if (!macdValue.IsFinal || macdValue is not MovingAverageConvergenceDivergenceSignalValue macdData ||
		macdData.Macd is not decimal macdMain || macdData.Signal is not decimal macdSignal)
		{
			UpdatePreviousValues(values, fastEma, slowEma);
			return;
		}

		if (!TryGetDecimal(values[index++], out var laguerre))
		{
			UpdatePreviousValues(values, fastEma, slowEma);
			return;
		}

		if (!TryGetDecimal(values[index++], out var mfi))
		{
			UpdatePreviousValues(values, fastEma, slowEma, laguerre);
			return;
		}

		var rainbowValues = new List<decimal[]>(_rainbowGroups.Length);
		for (var groupIndex = 0; groupIndex < _rainbowGroups.Length; groupIndex++)
		{
			var group = _rainbowGroups[groupIndex];
			var decimals = new decimal[group.Length];

			for (var i = 0; i < group.Length; i++)
			{
				if (!TryGetDecimal(values[index++], out var rainbow))
				{
					UpdatePreviousValues(values, fastEma, slowEma, laguerre);
					return;
				}

				decimals[i] = rainbow;
			}

			rainbowValues.Add(decimals);
		}

		var rainbowBullish = rainbowValues.All(bundle => IsMonotonic(bundle, descending: true));
		var rainbowBearish = rainbowValues.All(bundle => IsMonotonic(bundle, descending: false));

		var emaCrossUp = _previousFastEma is decimal prevFast && _previousSlowEma is decimal prevSlow &&
		prevFast < prevSlow && fastEma > slowEma;

		var emaCrossDown = _previousFastEma is decimal prevFastDown && _previousSlowEma is decimal prevSlowDown &&
		prevFastDown > prevSlowDown && fastEma < slowEma;

		var laguerreBullish = _previousLaguerre is decimal prevLagBull &&
		prevLagBull <= LaguerreBuyThreshold && laguerre > LaguerreBuyThreshold;

		var laguerreBearish = _previousLaguerre is decimal prevLagBear &&
		prevLagBear >= LaguerreSellThreshold && laguerre < LaguerreSellThreshold;

		var macdBullish = macdMain > 0m && macdSignal > 0m;
		var macdBearish = macdMain < 0m && macdSignal < 0m;

		var mfiBullish = mfi < MfiBuyLevel;
		var mfiBearish = mfi > MfiSellLevel;

		if (emaCrossUp && macdBullish && Position <= 0)
		{
			var volume = Volume + Math.Abs(Position);
			BuyMarket(volume);
		}
		else if (emaCrossDown && macdBearish && Position >= 0)
		{
			var volume = Volume + Math.Abs(Position);
			SellMarket(volume);
		}

		_previousFastEma = fastEma;
		_previousSlowEma = slowEma;
		_previousLaguerre = laguerre;
	}

	private void UpdatePreviousValues(IIndicatorValue[] values, decimal? fastEma = null, decimal? slowEma = null, decimal? laguerre = null)
	{
		var index = 0;

		fastEma ??= TryGetDecimal(values[index++], out var fast) ? fast : null;
		slowEma ??= TryGetDecimal(values[index++], out var slow) ? slow : null;
		index++;
		laguerre ??= TryGetDecimal(values[index++], out var lag) ? lag : null;

		_previousFastEma = fastEma ?? _previousFastEma;
		_previousSlowEma = slowEma ?? _previousSlowEma;
		_previousLaguerre = laguerre ?? _previousLaguerre;
	}

	private bool IsMonotonic(decimal[] values, bool descending)
	{
		for (var i = 0; i < values.Length - 1; i++)
		{
			if (descending)
			{
				if (values[i] < values[i + 1])
				return false;
			}
			else
			{
				if (values[i] > values[i + 1])
				return false;
			}
		}

		return true;
	}

	private static bool TryGetDecimal(IIndicatorValue value, out decimal result)
	{
		if (!value.IsFinal)
		{
			result = default;
			return false;
		}

		result = value.ToDecimal();
		return true;
	}

	private Unit ToAbsoluteUnit(decimal points)
	{
		if (points <= 0m || _pointValue <= 0m)
		return null;

		return new Unit(points * _pointValue, UnitTypes.Absolute);
	}
}