Открыть на GitHub

Стратегия Blau C-Momentum

Описание

Стратегия представляет собой порт советника MetaTrader Exp_BlauCMomentum на платформу StockSharp. Алгоритм использует свечи выбранного таймфрейма и рассчитывает тройное сглаженное значение Blau C-Momentum в одном из двух режимов:

  • Breakdown (пробой нуля) — реагирует на пересечение индикатором уровня 0.
  • Twist (поворот) — реагирует на изменение наклона сглаженного момента.

Индикатор может строиться по различным типам цен. Ордера открываются по рынку, при необходимости автоматически выставляются стоп-лосс и тейк-профит.

Логика работы

  1. Подписаться на свечи заданного таймфрейма.
  2. Рассчитать Blau C-Momentum:
    • Базовый момент — разница между двумя прикладными ценами, разделёнными MomentumLength барами.
    • Результат проходит три последовательных сглаживания выбранным типом скользящей и масштабируется к размеру шага цены.
  3. Сохранить историю индикатора, чтобы использовать смещения SignalBar.
  4. Сформировать сигналы:
    • Breakdown — если предыдущий бар был выше нуля, а сигнальный бар ≤0 — открываем/переворачиваемся в лонг; если предыдущий бар был ниже нуля, а сигнальный ≥0 — открываем/переворачиваемся в шорт. Флаги закрытия (EnableLongExit, EnableShortExit) закрывают противоположные позиции при тех же условиях.
    • Twist — сравниваем два предыдущих бара. Ускорение вверх и подтверждение сигнального бара инициирует покупку, ускорение вниз — продажу.
  5. Объём сделки определяется параметрами MoneyManagement и MarginModes. Отрицательное значение — фиксированный объём. Введена временная блокировка повторных входов до следующей свечи.

Параметры

  • MoneyManagement — доля капитала (или фиксированный лот при отрицательном значении).
  • MarginModes — режим интерпретации money management (FreeMarginShare, BalanceShare, FreeMarginRisk, BalanceRisk). Режимы риска используют StopLossPoints и Security.StepPrice.
  • StopLossPoints, TakeProfitPoints — расстояние до стоп-лосса/тейк-профита в шагах цены.
  • SlippagePoints — допустимое проскальзывание (для совместимости, реальные заявки — рыночные).
  • EnableLongEntry, EnableShortEntry — разрешение на открытие длинных/коротких позиций.
  • EnableLongExit, EnableShortExit — разрешение на закрытие позиций по сигналам.
  • EntryModes — режим работы (Breakdown или Twist).
  • CandleType — таймфрейм для расчёта индикатора.
  • SmoothingMethod — метод сглаживания (Simple, Exponential, Smoothed, LinearWeighted, Jurik, TripleExponential, Adaptive).
  • MomentumLength, FirstSmoothLength, SecondSmoothLength, ThirdSmoothLength, Phase — параметры расчёта момента и сглаживания.
  • PriceForClose, PriceForOpen — используемые прикладные цены.
  • SignalBar — смещение по барам для генерации сигналов (0 — текущая закрытая свеча).

Использование

  1. Настройте подключение, портфель и инструмент в StockSharp.
  2. Создайте экземпляр BlauCMomentumStrategy, укажите Security, Portfolio и нужные параметры.
  3. Вызовите Start() — стратегия подпишется на свечи, начнёт расчёт индикатора и выставление ордеров.
  4. При ненулевых StopLossPoints/TakeProfitPoints автоматически включается защита позиций через высокоуровневый API.

Особенности

  • Параметры money-management адаптированы под инфраструктуру StockSharp, поэтому расчёт свободной маржи приближен.
  • Сложные методы сглаживания из оригинальной библиотеки (Parabolic, VIDYA, JurX) заменены ближайшими по смыслу индикаторами (TripleExponential ≈ Tillson T3, Adaptive ≈ KAMA).
  • Значение SlippagePoints сохранено для справки, но заявки отправляются по рынку.

Предупреждение о рисках

Стратегия предоставляется «как есть» и предназначена для обучения. Перед использованием на реальном счёте протестируйте алгоритм на исторических данных и в демо-режиме, а также адаптируйте параметры под собственные ограничения по риску.

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