Ver en GitHub

Estrategia Blau C-Momentum

Descripción general

Esta estrategia es un port de StockSharp del asesor experto de MetaTrader Exp_BlauCMomentum. Opera en un único instrumento usando velas de un marco temporal configurable e interpreta el Momentum triple suavizado de Blau en uno de dos modos:

  • Modo Breakdown – reacciona al cruce de la línea de momentum por el nivel cero.
  • Modo Twist – reacciona a los cambios en la dirección de la pendiente del momentum suavizado.

El indicador se calcula en un marco temporal externo y puede opcionalmente usar diferentes precios aplicados para el cálculo del momentum. Las posiciones se abren con órdenes de mercado y pueden protegerse usando módulos integrados de stop-loss y take-profit.

Cómo funciona

  1. Suscribirse a velas del marco temporal seleccionado.
  2. Calcular Blau C-Momentum:
    • El momentum crudo es la diferencia entre dos precios aplicados separados por MomentumLength barras.
    • El momentum crudo se suaviza tres veces por el método de media móvil elegido y se escala a pasos de precio (×100/Point).
  3. Almacenar el historial del indicador suavizado para los desplazamientos de barra definidos por SignalBar.
  4. Generar señales:
    • Breakdown – si la barra anterior estaba por encima de cero y la barra de señal está por debajo o igual a cero, abrir/invertir largo; si la barra anterior estaba por debajo de cero y la barra de señal está por encima o igual a cero, abrir/invertir corto. Los indicadores de salida opcionales cierran el lado opuesto cuando la barra anterior cruza la línea cero.
    • Twist – comparar dos barras anteriores; cuando el momentum acelera hacia arriba (anterior < más antiguo) y la barra de señal confirma, abrir/invertir largo; cuando el momentum acelera hacia abajo (anterior > más antiguo) y la barra de señal confirma, abrir/invertir corto. Los indicadores de salida opcionales cierran el lado opuesto en la misma condición.
  5. Usar MoneyManagement y MarginModes para dimensionar la posición. Los valores negativos significan volumen fijo; los valores positivos arriesgan o asignan una fracción del valor de la cartera. Un bloqueo de tiempo simple previene reentradas inmediatas dentro de la misma vela.

Parámetros

Grupo Nombre Descripción
Trading MoneyManagement Porcentaje del capital para el dimensionamiento de posición. Valor negativo = volumen fijo.
Trading MarginModes Interpretación de la gestión monetaria (FreeMarginShare, BalanceShare, FreeMarginRisk, BalanceRisk). Los modos de riesgo usan la distancia de stop-loss y StepPrice.
Riesgo StopLossPoints Distancia de stop-loss en pasos de precio del instrumento (poner 0 para deshabilitar).
Riesgo TakeProfitPoints Distancia de take-profit en pasos de precio del instrumento (poner 0 para deshabilitar).
Trading SlippagePoints Deslizamiento permitido (mantenido por compatibilidad, no usado para colocación de órdenes).
Trading EnableLongEntry, EnableShortEntry Permitir la apertura de posiciones largas/cortas.
Trading EnableLongExit, EnableShortExit Permitir el cierre de posiciones existentes según el indicador.
Lógica EntryModes Breakdown o Twist.
Datos CandleType Marco temporal usado para los cálculos del indicador (predeterminado 4h).
Indicador SmoothingMethod Método de media móvil: Simple, Exponential, Smoothed, LinearWeighted, Jurik, TripleExponential, Adaptive.
Indicador MomentumLength Profundidad de promediado del momentum crudo (barras entre los dos valores de precio).
Indicador FirstSmoothLength, SecondSmoothLength, ThirdSmoothLength Longitudes de las tres etapas de suavizado.
Indicador Phase Parámetro de fase de Jurik (usado cuando el método de suavizado es Jurik).
Indicador PriceForClose, PriceForOpen Precios aplicados usados para el momentum (ver comentarios del código para las fórmulas).
Lógica SignalBar Índice de barra usado para señales (0 = barra cerrada actual, 1 = barra anterior, etc.).

Notas de uso

  • Adjunte la estrategia a un instrumento y configure la serie de velas. El marco temporal de trading es el mismo que el marco temporal del indicador.
  • El módulo de protección de la API de alto nivel se activa automáticamente cuando los valores de stop/take profit son positivos.
  • Los modos de margen son aproximaciones porque StockSharp no expone el balance/margen libre al estilo MetaTrader. Los modos basados en riesgo dependen de StopLossPoints y Security.StepPrice.
  • Los métodos de suavizado avanzados de la biblioteca original (Parabolic, VIDYA, JurX) se mapean a los indicadores StockSharp disponibles más cercanos (TripleExponential ≈ T3, Adaptive ≈ KAMA).
  • El parámetro de deslizamiento se conserva por completitud, pero se usan órdenes de mercado, por lo que el valor es informativo.

Primeros pasos

  1. Configure la conexión, la cartera y el instrumento en su entorno StockSharp.
  2. Cree una instancia de BlauCMomentumStrategy, asigne Security, Portfolio y los parámetros deseados.
  3. Llame a Start(); la estrategia se suscribirá a las velas, calculará el indicador y operará automáticamente.
  4. Monitoree los registros para obtener información sobre posiciones abiertas/cerradas y estados del indicador.

Descargo de responsabilidad de riesgo

Esta estrategia se proporciona con fines educativos. Siempre valide el rendimiento con pruebas históricas y prospectivas antes de ejecutarla en una cuenta en vivo. Ajuste la configuración de riesgo para que coincida con su capital y las condiciones del 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;
		}
	}
}