Auf GitHub ansehen

Blau C-Momentum Strategy

Overview

This strategy is a StockSharp port of the MetaTrader expert advisor Exp_BlauCMomentum. It trades on a single instrument using candles from a configurable timeframe and interprets Blau's triple-smoothed momentum in one of two modes:

  • Breakdown mode – reacts to the momentum line crossing the zero level.
  • Twist mode – reacts to changes in the direction of the smoothed momentum slope.

The indicator is calculated on an external timeframe and can optionally use different applied prices for the momentum calculation. Positions are opened with market orders and can be protected using built-in stop-loss and take-profit modules.

How it works

  1. Subscribe to candles of the selected timeframe.
  2. Compute Blau C-Momentum:
    • Raw momentum is the difference between two applied prices separated by MomentumLength bars.
    • The raw momentum is smoothed three times by the chosen moving-average method and scaled to price steps (×100/Point).
  3. Store the smoothed indicator history for bar shifts defined by SignalBar.
  4. Generate signals:
    • Breakdown – if the previous bar was above zero and the signal bar is below or equal to zero, open/flip long; if the previous bar was below zero and the signal bar is above or equal to zero, open/flip short. Optional exit flags close the opposite side when the previous bar crosses the zero line.
    • Twist – compare two previous bars; when momentum accelerates upward (previous < older) and the signal bar confirms, open/flip long; when momentum accelerates downward (previous > older) and the signal bar confirms, open/flip short. Optional exit flags close the opposite side on the same condition.
  5. Use MoneyManagement and MarginModes to size the position. Negative values mean fixed volume; positive values risk or allocate a fraction of the portfolio value. A simple time lock prevents immediate re-entries within the same candle.

Parameters

Group Name Description
Trading MoneyManagement Share of capital for position sizing. Negative value = fixed volume.
Trading MarginModes Interpretation of money management (FreeMarginShare, BalanceShare, FreeMarginRisk, BalanceRisk). Risk modes use stop-loss distance and StepPrice.
Risk StopLossPoints Stop-loss distance in instrument price steps (set 0 to disable).
Risk TakeProfitPoints Take-profit distance in instrument price steps (set 0 to disable).
Trading SlippagePoints Allowed slippage (kept for compatibility, not used for order placement).
Trading EnableLongEntry, EnableShortEntry Allow opening long/short positions.
Trading EnableLongExit, EnableShortExit Allow closing existing positions according to the indicator.
Logic EntryModes Breakdown or Twist.
Data CandleType Timeframe used for indicator calculations (default 4h).
Indicator SmoothingMethod Moving-average method: Simple, Exponential, Smoothed, LinearWeighted, Jurik, TripleExponential, Adaptive.
Indicator MomentumLength Raw momentum averaging depth (bars between the two price values).
Indicator FirstSmoothLength, SecondSmoothLength, ThirdSmoothLength Lengths of the three smoothing stages.
Indicator Phase Jurik phase parameter (used when smoothing method is Jurik).
Indicator PriceForClose, PriceForOpen Applied prices used for momentum (see code comments for formulas).
Logic SignalBar Bar index used for signals (0 = current closed bar, 1 = previous bar, etc.).

Usage notes

  • Attach the strategy to a security and configure the candle series. The trading timeframe is the same as the indicator timeframe.
  • The high-level API protection module is enabled automatically when stop/take profit values are positive.
  • Margin modes are approximations because StockSharp does not expose MetaTrader-style balance/free margin. Risk-based modes rely on StopLossPoints and Security.StepPrice.
  • Advanced smoothing methods from the original library (Parabolic, VIDYA, JurX) are mapped to the closest available StockSharp indicators (TripleExponential ≈ T3, Adaptive ≈ KAMA).
  • Slippage parameter is preserved for completeness but market orders are used, so the value is informational.

Getting started

  1. Configure connection, portfolio, and security in your StockSharp environment.
  2. Create an instance of BlauCMomentumStrategy, assign Security, Portfolio, and desired parameters.
  3. Call Start(); the strategy will subscribe to candles, calculate the indicator, and trade automatically.
  4. Monitor logs for information about opened/closed positions and indicator states.

Risk disclaimer

This strategy is provided for educational purposes. Always validate performance with historical and forward tests before running it on a live account. Adjust risk settings to match your capital and market conditions.

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