Ver no GitHub

Estratégia Blau C-Momentum

Visão geral

Esta estratégia é um port do StockSharp do consultor especialista de MetaTrader Exp_BlauCMomentum. Opera em um único instrumento usando candles de um período configurável e interpreta o Momentum triplicemente suavizado de Blau em um de dois modos:

  • Modo Breakdown – reage ao cruzamento da linha de momentum pelo nível zero.
  • Modo Twist – reage a mudanças na direção da inclinação do momentum suavizado.

O indicador é calculado em um período externo e pode opcionalmente usar preços aplicados diferentes para o cálculo do momentum. As posições são abertas com ordens de mercado e podem ser protegidas usando módulos integrados de stop-loss e take-profit.

Como funciona

  1. Inscrever-se em candles do período selecionado.
  2. Calcular Blau C-Momentum:
    • O momentum bruto é a diferença entre dois preços aplicados separados por MomentumLength barras.
    • O momentum bruto é suavizado três vezes pelo método de média móvel escolhido e escalado para passos de preço (×100/Point).
  3. Armazenar o histórico do indicador suavizado para os deslocamentos de barra definidos por SignalBar.
  4. Gerar sinais:
    • Breakdown – se a barra anterior estava acima de zero e a barra de sinal está abaixo ou igual a zero, abrir/inverter comprado; se a barra anterior estava abaixo de zero e a barra de sinal está acima ou igual a zero, abrir/inverter vendido. Os sinalizadores de saída opcionais fecham o lado oposto quando a barra anterior cruza a linha zero.
    • Twist – comparar duas barras anteriores; quando o momentum acelera para cima (anterior < mais antigo) e a barra de sinal confirma, abrir/inverter comprado; quando o momentum acelera para baixo (anterior > mais antigo) e a barra de sinal confirma, abrir/inverter vendido. Os sinalizadores de saída opcionais fecham o lado oposto na mesma condição.
  5. Usar MoneyManagement e MarginModes para dimensionar a posição. Valores negativos significam volume fixo; valores positivos arriscam ou alocam uma fração do valor da carteira. Um bloqueio de tempo simples impede reentradas imediatas dentro do mesmo candle.

Parâmetros

Grupo Nome Descrição
Negociação MoneyManagement Participação do capital para dimensionamento de posição. Valor negativo = volume fixo.
Negociação MarginModes Interpretação do gerenciamento de dinheiro (FreeMarginShare, BalanceShare, FreeMarginRisk, BalanceRisk). Os modos de risco usam distância de stop-loss e StepPrice.
Risco StopLossPoints Distância de stop-loss em passos de preço do instrumento (definir 0 para desabilitar).
Risco TakeProfitPoints Distância de take-profit em passos de preço do instrumento (definir 0 para desabilitar).
Negociação SlippagePoints Slippage permitido (mantido por compatibilidade, não usado para colocação de ordens).
Negociação EnableLongEntry, EnableShortEntry Permitir abertura de posições compradas/vendidas.
Negociação EnableLongExit, EnableShortExit Permitir fechamento de posições existentes de acordo com o indicador.
Lógica EntryModes Breakdown ou Twist.
Dados CandleType Período usado para cálculos do indicador (padrão 4h).
Indicador SmoothingMethod Método de média móvel: Simple, Exponential, Smoothed, LinearWeighted, Jurik, TripleExponential, Adaptive.
Indicador MomentumLength Profundidade de médias do momentum bruto (barras entre os dois valores de preço).
Indicador FirstSmoothLength, SecondSmoothLength, ThirdSmoothLength Comprimentos dos três estágios de suavização.
Indicador Phase Parâmetro de fase do Jurik (usado quando o método de suavização é Jurik).
Indicador PriceForClose, PriceForOpen Preços aplicados usados para o momentum (veja comentários do código para fórmulas).
Lógica SignalBar Índice de barra usado para sinais (0 = barra fechada atual, 1 = barra anterior, etc.).

Notas de uso

  • Anexe a estratégia a um instrumento e configure a série de candles. O período de negociação é o mesmo que o período do indicador.
  • O módulo de proteção da API de alto nível é habilitado automaticamente quando os valores de stop/take profit são positivos.
  • Os modos de margem são aproximações porque o StockSharp não expõe balanço/margem livre ao estilo MetaTrader. Os modos baseados em risco dependem de StopLossPoints e Security.StepPrice.
  • Os métodos de suavização avançados da biblioteca original (Parabolic, VIDYA, JurX) são mapeados para os indicadores StockSharp disponíveis mais próximos (TripleExponential ≈ T3, Adaptive ≈ KAMA).
  • O parâmetro de slippage é preservado para completude, mas ordens de mercado são usadas, então o valor é informativo.

Primeiros passos

  1. Configure a conexão, a carteira e o instrumento em seu ambiente StockSharp.
  2. Crie uma instância de BlauCMomentumStrategy, atribua Security, Portfolio e os parâmetros desejados.
  3. Chame Start(); a estratégia assinará candles, calculará o indicador e negociará automaticamente.
  4. Monitore os logs para informações sobre posições abertas/fechadas e estados do indicador.

Aviso de risco

Esta estratégia é fornecida para fins educacionais. Sempre valide o desempenho com testes históricos e prospectivos antes de executá-la em uma conta ao vivo. Ajuste as configurações de risco para corresponder ao seu capital e às condições do mercado.

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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Blau C-Momentum strategy converted from the MetaTrader expert advisor.
/// The strategy processes Blau's triple smoothed momentum and reacts either to zero breakouts or twists.
/// </summary>
public class BlauCMomentumStrategy : Strategy
{
	private readonly StrategyParam<decimal> _moneyManagement;
	private readonly StrategyParam<MarginModes> _marginMode;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<int> _slippagePoints;
	private readonly StrategyParam<bool> _enableLongEntry;
	private readonly StrategyParam<bool> _enableShortEntry;
	private readonly StrategyParam<bool> _enableLongExit;
	private readonly StrategyParam<bool> _enableShortExit;
	private readonly StrategyParam<EntryModes> _entryMode;
	private readonly StrategyParam<DataType> _candleType;

	private readonly StrategyParam<SmoothMethods> _smoothingMethod;
	private readonly StrategyParam<int> _momentumLength;
	private readonly StrategyParam<int> _firstSmoothLength;
	private readonly StrategyParam<int> _secondSmoothLength;
	private readonly StrategyParam<int> _thirdSmoothLength;
	private readonly StrategyParam<int> _phase;
	private readonly StrategyParam<AppliedPrices> _priceForClose;
	private readonly StrategyParam<AppliedPrices> _priceForOpen;
	private readonly StrategyParam<int> _signalBar;

	private BlauMomentumCalculator _momentum;
	private readonly List<decimal> _indicatorHistory = new();
	private TimeSpan _candleSpan;
	private DateTimeOffset? _longTradeBlockUntil;
	private DateTimeOffset? _shortTradeBlockUntil;

	/// <summary>
	/// Initializes a new instance of the <see cref="BlauCMomentumStrategy"/> class.
	/// </summary>
	public BlauCMomentumStrategy()
	{
		_moneyManagement = Param(nameof(MoneyManagement), 0.1m)
			.SetDisplay("Money Management", "Fraction of capital used to size positions (negative value = fixed volume)", "Trading")
			;

		_marginMode = Param(nameof(MarginMode), MarginModes.FreeMarginShare)
			.SetDisplay("Margin Mode", "Interpretation of money management parameter", "Trading");

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
			.SetDisplay("Stop Loss", "Stop loss distance in price steps", "Risk")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
			.SetDisplay("Take Profit", "Take profit distance in price steps", "Risk")
			;

		_slippagePoints = Param(nameof(SlippagePoints), 10)
			.SetDisplay("Max Slippage", "Maximum slippage allowed in points", "Trading");

		_enableLongEntry = Param(nameof(EnableLongEntry), true)
			.SetDisplay("Enable Long Entry", "Allow opening long positions", "Trading");

		_enableShortEntry = Param(nameof(EnableShortEntry), true)
			.SetDisplay("Enable Short Entry", "Allow opening short positions", "Trading");

		_enableLongExit = Param(nameof(EnableLongExit), true)
			.SetDisplay("Enable Long Exit", "Allow closing long positions", "Trading");

		_enableShortExit = Param(nameof(EnableShortExit), true)
			.SetDisplay("Enable Short Exit", "Allow closing short positions", "Trading");

		_entryMode = Param(nameof(EntryMode), EntryModes.Twist)
			.SetDisplay("Entry Mode", "Choose between zero breakout or twist logic", "Logic");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Indicator Timeframe", "Candle type used for indicator calculations", "Data");

		_smoothingMethod = Param(nameof(SmoothingMethod), SmoothMethods.Exponential)
			.SetDisplay("Smoothing Method", "Smoothing method applied to the momentum", "Indicator")
			;

		_momentumLength = Param(nameof(MomentumLength), 1)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Length", "Depth of raw momentum calculation", "Indicator")
			;

		_firstSmoothLength = Param(nameof(FirstSmoothLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("First Smooth", "Length of the first smoothing stage", "Indicator")
			;

		_secondSmoothLength = Param(nameof(SecondSmoothLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Second Smooth", "Length of the second smoothing stage", "Indicator")
			;

		_thirdSmoothLength = Param(nameof(ThirdSmoothLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Third Smooth", "Length of the third smoothing stage", "Indicator")
			;

		_phase = Param(nameof(Phase), 15)
			.SetDisplay("Phase", "Phase parameter used by Jurik-style moving averages", "Indicator");

		_priceForClose = Param(nameof(PriceForClose), AppliedPrices.Close)
			.SetDisplay("Close Price Source", "Applied price used as the reference close", "Indicator");

		_priceForOpen = Param(nameof(PriceForOpen), AppliedPrices.Open)
			.SetDisplay("Open Price Source", "Applied price used for the entry reference", "Indicator");

		_signalBar = Param(nameof(SignalBar), 1)
			.SetDisplay("Signal Bar", "Bar index used for generating entry signals", "Logic")
			;
	}

	/// <summary>
	/// Fraction of capital (or fixed lot size) used for trading.
	/// </summary>
	public decimal MoneyManagement
	{
		get => _moneyManagement.Value;
		set => _moneyManagement.Value = value;
	}

	/// <summary>
	/// Interpretation of the money management parameter.
	/// </summary>
	public MarginModes MarginMode
	{
		get => _marginMode.Value;
		set => _marginMode.Value = value;
	}

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

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

	/// <summary>
	/// Maximum tolerated slippage in points.
	/// </summary>
	public int SlippagePoints
	{
		get => _slippagePoints.Value;
		set => _slippagePoints.Value = value;
	}

	/// <summary>
	/// Enable opening long positions.
	/// </summary>
	public bool EnableLongEntry
	{
		get => _enableLongEntry.Value;
		set => _enableLongEntry.Value = value;
	}

	/// <summary>
	/// Enable opening short positions.
	/// </summary>
	public bool EnableShortEntry
	{
		get => _enableShortEntry.Value;
		set => _enableShortEntry.Value = value;
	}

	/// <summary>
	/// Enable closing long positions on indicator signals.
	/// </summary>
	public bool EnableLongExit
	{
		get => _enableLongExit.Value;
		set => _enableLongExit.Value = value;
	}

	/// <summary>
	/// Enable closing short positions on indicator signals.
	/// </summary>
	public bool EnableShortExit
	{
		get => _enableShortExit.Value;
		set => _enableShortExit.Value = value;
	}

	/// <summary>
	/// Entry logic: zero-line breakdown or twist detection.
	/// </summary>
	public EntryModes EntryMode
	{
		get => _entryMode.Value;
		set => _entryMode.Value = value;
	}

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

	/// <summary>
	/// Smoothing method applied to Blau momentum.
	/// </summary>
	public SmoothMethods SmoothingMethod
	{
		get => _smoothingMethod.Value;
		set => _smoothingMethod.Value = value;
	}

	/// <summary>
	/// Momentum averaging depth.
	/// </summary>
	public int MomentumLength
	{
		get => _momentumLength.Value;
		set => _momentumLength.Value = value;
	}

	/// <summary>
	/// First smoothing stage length.
	/// </summary>
	public int FirstSmoothLength
	{
		get => _firstSmoothLength.Value;
		set => _firstSmoothLength.Value = value;
	}

	/// <summary>
	/// Second smoothing stage length.
	/// </summary>
	public int SecondSmoothLength
	{
		get => _secondSmoothLength.Value;
		set => _secondSmoothLength.Value = value;
	}

	/// <summary>
	/// Third smoothing stage length.
	/// </summary>
	public int ThirdSmoothLength
	{
		get => _thirdSmoothLength.Value;
		set => _thirdSmoothLength.Value = value;
	}

	/// <summary>
	/// Phase parameter used by Jurik-styled smoothing.
	/// </summary>
	public int Phase
	{
		get => _phase.Value;
		set => _phase.Value = value;
	}

	/// <summary>
	/// Applied price for the "closing" component.
	/// </summary>
	public AppliedPrices PriceForClose
	{
		get => _priceForClose.Value;
		set => _priceForClose.Value = value;
	}

	/// <summary>
	/// Applied price for the "opening" component.
	/// </summary>
	public AppliedPrices PriceForOpen
	{
		get => _priceForOpen.Value;
		set => _priceForOpen.Value = value;
	}

	/// <summary>
	/// Index of the bar used for generating entry signals.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

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

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

		_indicatorHistory.Clear();
		_momentum = null;
		_longTradeBlockUntil = null;
		_shortTradeBlockUntil = null;
		_candleSpan = TimeSpan.Zero;
	}

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

		_indicatorHistory.Clear();
		_momentum = new BlauMomentumCalculator(
			SmoothingMethod,
			MomentumLength,
			FirstSmoothLength,
			SecondSmoothLength,
			ThirdSmoothLength,
			Phase,
			PriceForClose,
			PriceForOpen
		);

		_candleSpan = CandleType.Arg is TimeSpan frame ? frame : TimeSpan.Zero;

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

		var step = Security?.PriceStep ?? 0m;
		var takeProfitUnit = TakeProfitPoints > 0 && step > 0m ? new Unit(TakeProfitPoints * step, UnitTypes.Absolute) : null;
		var stopLossUnit = StopLossPoints > 0 && step > 0m ? new Unit(StopLossPoints * step, UnitTypes.Absolute) : null;
		StartProtection(takeProfitUnit, stopLossUnit);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished || _momentum is null)
			return;

		var step = Security?.PriceStep ?? 1m;
		var indicatorValue = _momentum.Process(candle, step);
		if (indicatorValue is null)
			return;

		_indicatorHistory.Add(indicatorValue.Value);

		var requiredHistory = Math.Max(SignalBar + 3, 5);
		if (_indicatorHistory.Count > requiredHistory)
			_indicatorHistory.RemoveRange(0, _indicatorHistory.Count - requiredHistory);

		// indicators are checked via history availability below

		var current = GetHistoryValue(SignalBar);
		var previous = GetHistoryValue(SignalBar + 1);

		if (current is null || previous is null)
			return;

		var closeShort = false;
		var closeLong = false;
		var openLong = false;
		var openShort = false;

		switch (EntryMode)
		{
			case EntryModes.Breakdown:
			{
				if (previous.Value > 0m)
				{
					if (EnableLongEntry && current.Value <= 0m)
					{
						openLong = true;
					}

					if (EnableShortExit)
					{
						closeShort = true;
					}
				}

				if (previous.Value < 0m)
				{
					if (EnableShortEntry && current.Value >= 0m)
					{
						openShort = true;
					}

					if (EnableLongExit)
					{
						closeLong = true;
					}
				}
				break;
			}
			case EntryModes.Twist:
			{
				var older = GetHistoryValue(SignalBar + 2);
				if (older is null)
					return;

				if (previous.Value < older.Value)
				{
					if (EnableLongEntry && current.Value >= previous.Value)
					{
						openLong = true;
					}

					if (EnableShortExit)
					{
						closeShort = true;
					}
				}

				if (previous.Value > older.Value)
				{
					if (EnableShortEntry && current.Value <= previous.Value)
					{
						openShort = true;
					}

					if (EnableLongExit)
					{
						closeLong = true;
					}
				}
				break;
			}
		}

		if (closeLong && Position > 0m)
		{
			SellMarket();
		}

		if (closeShort && Position < 0m)
		{
			BuyMarket();
		}

		if (openLong && Position <= 0m && CanEnterLong(candle.OpenTime))
		{
			BuyMarket();
			SetLongBlock(candle.OpenTime);
		}

		if (openShort && Position >= 0m && CanEnterShort(candle.OpenTime))
		{
			SellMarket();
			SetShortBlock(candle.OpenTime);
		}
	}

	private decimal? GetHistoryValue(int shift)
	{
		if (shift < 0)
			return null;

		var index = _indicatorHistory.Count - shift - 1;
		if (index < 0 || index >= _indicatorHistory.Count)
			return null;

		return _indicatorHistory[index];
	}

	private bool CanEnterLong(DateTimeOffset signalTime)
	{
		return !_longTradeBlockUntil.HasValue || signalTime >= _longTradeBlockUntil.Value;
	}

	private bool CanEnterShort(DateTimeOffset signalTime)
	{
		return !_shortTradeBlockUntil.HasValue || signalTime >= _shortTradeBlockUntil.Value;
	}

	private void SetLongBlock(DateTimeOffset signalTime)
	{
		_longTradeBlockUntil = _candleSpan != TimeSpan.Zero ? signalTime + _candleSpan : signalTime;
	}

	private void SetShortBlock(DateTimeOffset signalTime)
	{
		_shortTradeBlockUntil = _candleSpan != TimeSpan.Zero ? signalTime + _candleSpan : signalTime;
	}

	private decimal CalculateTradeVolume(decimal price)
	{
		if (price <= 0m)
			return 0m;

		var step = Security?.VolumeStep ?? 1m;
		var minVolume = Security?.MinVolume ?? step;
		var capital = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
		var moneyManagement = MoneyManagement;
		decimal volume;

		if (moneyManagement < 0m)
		{
			volume = Math.Abs(moneyManagement);
		}
		else
		{
			if (capital <= 0m)
				return minVolume;

			switch (MarginMode)
			{
				case MarginModes.FreeMarginShare:
				case MarginModes.BalanceShare:
				{
					var budget = capital * moneyManagement;
					volume = budget / price;
					break;
				}
				case MarginModes.FreeMarginRisk:
				case MarginModes.BalanceRisk:
				{
					var riskCapital = capital * moneyManagement;
					var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 1m;
					var stopLoss = StopLossPoints > 0 ? StopLossPoints * stepPrice : price;
					volume = stopLoss > 0m ? riskCapital / stopLoss : riskCapital / price;
					break;
				}
				default:
				{
					var budget = capital * moneyManagement;
					volume = budget / price;
					break;
				}
			}
		}

		if (step > 0m && volume > 0m)
		{
			volume = Math.Floor(volume / step) * step;
		}

		if (volume < minVolume)
			volume = minVolume;

		return volume;
	}

	/// <summary>
	/// Entry mode replication.
	/// </summary>
	public enum EntryModes
	{
		/// <summary>
		/// Entry when the indicator breaks zero.
		/// </summary>
		Breakdown,

		/// <summary>
		/// Entry when the indicator changes direction (twist).
		/// </summary>
		Twist
	}

	/// <summary>
	/// Applied price selection.
	/// </summary>
	public enum AppliedPrices
	{
		/// <summary>
		/// Closing price.
		/// </summary>
		Close = 1,

		/// <summary>
		/// Opening price.
		/// </summary>
		Open,

		/// <summary>
		/// High price.
		/// </summary>
		High,

		/// <summary>
		/// Low price.
		/// </summary>
		Low,

		/// <summary>
		/// Median price (HL/2).
		/// </summary>
		Median,

		/// <summary>
		/// Typical price (HLC/3).
		/// </summary>
		Typical,

		/// <summary>
		/// Weighted close (HLCC/4).
		/// </summary>
		Weighted,

		/// <summary>
		/// Simple price (OC/2).
		/// </summary>
		Simple,

		/// <summary>
		/// Quarted price (HLOC/4).
		/// </summary>
		Quarter,

		/// <summary>
		/// Trend-following price variant 1.
		/// </summary>
		TrendFollow1,

		/// <summary>
		/// Trend-following price variant 2.
		/// </summary>
		TrendFollow2,

		/// <summary>
		/// Demark price.
		/// </summary>
		Demark
	}

	/// <summary>
	/// Money management interpretation.
	/// </summary>
	public enum MarginModes
	{
		/// <summary>
		/// Use a fraction of account capital (approximation of free margin share).
		/// </summary>
		FreeMarginShare = 0,

		/// <summary>
		/// Use a fraction of balance (treated equally to free margin share in this port).
		/// </summary>
		BalanceShare = 1,

		/// <summary>
		/// Risk a fraction of capital with stop-loss distance.
		/// </summary>
		FreeMarginRisk = 2,

		/// <summary>
		/// Risk a fraction of balance with stop-loss distance.
		/// </summary>
		BalanceRisk = 3
	}

	/// <summary>
	/// Smoothing methods available for Blau momentum.
	/// </summary>
	public enum SmoothMethods
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Simple,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Exponential,

		/// <summary>
		/// Smoothed moving average (RMA/SMMA).
		/// </summary>
		Smoothed,

		/// <summary>
		/// Linear weighted moving average.
		/// </summary>
		LinearWeighted,

		/// <summary>
		/// Jurik moving average.
		/// </summary>
		Jurik,

		/// <summary>
		/// Triple exponential moving average (approximation of T3).
		/// </summary>
		TripleExponential,

		/// <summary>
		/// Kaufman adaptive moving average.
		/// </summary>
		Adaptive
	}

	private sealed class BlauMomentumCalculator
	{
		private readonly SmoothMethods _method;
		private readonly int _momentumLength;
		private readonly int _firstLength;
		private readonly int _secondLength;
		private readonly int _thirdLength;
		private readonly int _phase;
		private readonly AppliedPrices _price1;
		private readonly AppliedPrices _price2;

		private readonly List<decimal> _priceBuffer = new();
		private readonly DecimalLengthIndicator _ma1;
		private readonly DecimalLengthIndicator _ma2;
		private readonly DecimalLengthIndicator _ma3;

		public BlauMomentumCalculator(
		SmoothMethods method,
		int momentumLength,
		int firstLength,
		int secondLength,
		int thirdLength,
		int phase,
		AppliedPrices price1,
		AppliedPrices price2)
		{
			_method = method;
			_momentumLength = Math.Max(1, momentumLength);
			_firstLength = Math.Max(1, firstLength);
			_secondLength = Math.Max(1, secondLength);
			_thirdLength = Math.Max(1, thirdLength);
			_phase = phase;
			_price1 = price1;
			_price2 = price2;

			_ma1 = CreateMovingAverage(method, _firstLength, _phase);
			_ma2 = CreateMovingAverage(method, _secondLength, _phase);
			_ma3 = CreateMovingAverage(method, _thirdLength, _phase);
		}

		public decimal? Process(ICandleMessage candle, decimal point)
		{
			var value1 = GetAppliedPrice(_price1, candle);
			var value2 = GetAppliedPrice(_price2, candle);

			_priceBuffer.Add(value2);
			if (_priceBuffer.Count > _momentumLength)
				try { _priceBuffer.RemoveAt(0); } catch { }

			if (_priceBuffer.Count < _momentumLength)
				return null;

			var reference = _priceBuffer[0];
			var momentum = value1 - reference;
			var time = candle.OpenTime;

			var smooth1Result = _ma1.Process(new DecimalIndicatorValue(_ma1, momentum, time) { IsFinal = true });
			if (!_ma1.IsFormed)
				return null;
			var smooth1 = smooth1Result.ToDecimal();

			var smooth2Result = _ma2.Process(new DecimalIndicatorValue(_ma2, smooth1, time) { IsFinal = true });
			if (!_ma2.IsFormed)
				return null;
			var smooth2 = smooth2Result.ToDecimal();

			var smooth3Result = _ma3.Process(new DecimalIndicatorValue(_ma3, smooth2, time) { IsFinal = true });
			if (!_ma3.IsFormed)
				return null;
			var smooth3 = smooth3Result.ToDecimal();

			return point > 0m ? smooth3 * 100m / point : smooth3;
		}

		public void Reset()
		{
			_priceBuffer.Clear();
			_ma1.Reset();
			_ma2.Reset();
			_ma3.Reset();
		}

		private static DecimalLengthIndicator CreateMovingAverage(SmoothMethods method, int length, int phase)
		{
			return method switch
			{
				SmoothMethods.Simple => new SMA { Length = length },
				SmoothMethods.Exponential => new EMA { Length = length },
				SmoothMethods.Smoothed => new SmoothedMovingAverage { Length = length },
				SmoothMethods.LinearWeighted => new WeightedMovingAverage { Length = length },
				SmoothMethods.Jurik => new JurikMovingAverage { Length = length, Phase = phase },
				SmoothMethods.TripleExponential => new TripleExponentialMovingAverage { Length = length },
				SmoothMethods.Adaptive => new KaufmanAdaptiveMovingAverage { Length = length },
				_ => new EMA { Length = length }
			};
		}

		private static decimal GetAppliedPrice(AppliedPrices price, ICandleMessage candle)
		{
			return price switch
			{
				AppliedPrices.Close => candle.ClosePrice,
				AppliedPrices.Open => candle.OpenPrice,
				AppliedPrices.High => candle.HighPrice,
				AppliedPrices.Low => candle.LowPrice,
				AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
				AppliedPrices.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
				AppliedPrices.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
				AppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
				AppliedPrices.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
				AppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice : candle.ClosePrice < candle.OpenPrice ? candle.LowPrice : candle.ClosePrice,
				AppliedPrices.TrendFollow2 => candle.ClosePrice > candle.OpenPrice ? (candle.HighPrice + candle.ClosePrice) / 2m : candle.ClosePrice < candle.OpenPrice ? (candle.LowPrice + candle.ClosePrice) / 2m : candle.ClosePrice,
				AppliedPrices.Demark => CalculateDemarkPrice(candle),
				_ => candle.ClosePrice
			};
		}

		private static decimal CalculateDemarkPrice(ICandleMessage candle)
		{
			var res = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
			if (candle.ClosePrice < candle.OpenPrice)
				res = (res + candle.LowPrice) / 2m;
			else if (candle.ClosePrice > candle.OpenPrice)
				res = (res + candle.HighPrice) / 2m;
			else
				res = (res + candle.ClosePrice) / 2m;

			return ((res - candle.LowPrice) + (res - candle.HighPrice)) / 2m;
		}
	}
}