Открыть на GitHub

Стратегия Exp Blau CSI

Данная стратегия представляет собой перенос советника MetaTrader 5 Exp_BlauCSI на платформу StockSharp. В основе лежит индикатор Blau Candle Stochastic Index (CSI), рассчитываемый по выбранному потоку свечей. Стратегия может работать по пробоям уровня ноль либо по разворотам индикатора и поддерживает фиксированные уровни стоп-лосса и тейк-профита в шагах цены.

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

Blau CSI сравнивает моментум цены с диапазоном High-Low последних свечей. Обе серии проходят через три последовательных сглаживания.

  • Режим Breakdown — открывает длинную позицию при пробое уровня ноль вниз и закрывает все короткие позиции, если предыдущее значение было положительным. При пробое вверх открывает шорт и закрывает лонги, если предыдущее значение было отрицательным.
  • Режим Twist — открывает длинную позицию при смене направления вверх (значение растёт после снижения) и закрывает шорт. При смене направления вниз открывает короткую позицию и закрывает лонг. Предыдущая свеча всегда используется для выхода из противоположных позиций.

Сигналы рассчитываются по заданному сдвигу Signal Bar, что гарантирует использование полностью сформированных свечей.

Параметры

Параметр Описание
Entry Mode Выбор логики Breakdown или Twist.
Smoothing Method Тип сглаживания в расчёте Blau CSI (Simple, Exponential, Smoothed, LinearWeighted, Jurik).
Momentum Length Количество свечей для расчёта моментума и диапазона.
First/Second/Third Smoothing Периоды трёх стадий сглаживания.
Smoothing Phase Параметр фазы для Jurik (игнорируется другими методами).
Momentum Price / Reference Price Типы цен для ведущего и запаздывающего значений (close, open, high, low, median, typical, weighted, simple, quarter, trend-following, Demark).
Signal Bar Сдвиг по истории при оценке индикатора. Значение 1 означает предыдущую закрытую свечу.
Stop Loss (pts) Размер стоп-лосса в шагах цены (0 отключает).
Take Profit (pts) Размер тейк-профита в шагах цены (0 отключает).
Allow Long/Short Entries Разрешение на открытие длинных/коротких позиций.
Allow Long/Short Exits Разрешение на закрытие длинных/коротких позиций по сигналам.
Candle Type Тип подписки на данные (по умолчанию свечи 4H).
Start Date / End Date Ограничение по датам торговли.
Order Volume Объём рыночных заявок.

Управление риском

При открытии позиции рассчитываются уровни стоп-лосса и тейк-профита на основе PriceStep. Если инструмент не предоставляет шаг цены, защитные уровни отключаются. Трейлинг-стоп не используется — уровень остаётся неизменным до закрытия позиции сигналом или достижением цели.

Рекомендации по использованию

  1. Подключите стратегию к инструменту, предоставляющему свечи выбранного типа.
  2. Настройте режим сигналов и параметры сглаживания в соответствии с торговой идеей.
  3. Убедитесь, что инструмент имеет корректный PriceStep, если задействованы стоп-лосс/тейк-профит.
  4. Ограничьте период торговли с помощью Start Date и End Date, если это необходимо.

Отличия от версии MT5

  • Используются индикаторы и торговые методы StockSharp вместо функций MetaTrader.
  • Управление размером позиции упрощено: объём берётся из параметра Order Volume.
  • Реализованы только доступные в StockSharp типы сглаживания (Simple, Exponential, Smoothed, LinearWeighted, Jurik). Остальные варианты MT5 автоматически заменяются на экспоненциальное сглаживание.
  • Флаги разрешения на вход и выход, а также логика стопов соответствуют оригинальному советнику.

Стратегию можно запускать в StockSharp Designer, Shell, Runner или в любом приложении, использующем библиотеку StockSharp.

using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;



/// <summary>
/// Blau Candle Stochastic Index strategy converted from MetaTrader 5.
/// </summary>
public class ExpBlauCsiStrategy : Strategy
{
	/// <summary>
	/// Available entry modes for the Blau CSI strategy.
	/// </summary>
	public enum BlauCsiEntryModes
	{
		/// <summary>
		/// Use zero level breakdowns as signals.
		/// </summary>
		Breakdown,

		/// <summary>
		/// Use direction changes (twists) as signals.
		/// </summary>
		Twist
	}

	/// <summary>
	/// Applied price constants supported by the Blau CSI indicator.
	/// </summary>
	public enum BlauCsiAppliedPrices
	{
		/// <summary>
		/// Close price.
		/// </summary>
		Close = 1,

		/// <summary>
		/// Open price.
		/// </summary>
		Open = 2,

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

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

		/// <summary>
		/// Median price = (High + Low) / 2.
		/// </summary>
		Median = 5,

		/// <summary>
		/// Typical price = (High + Low + Close) / 3.
		/// </summary>
		Typical = 6,

		/// <summary>
		/// Weighted close price = (2 * Close + High + Low) / 4.
		/// </summary>
		Weighted = 7,

		/// <summary>
		/// Average of open and close.
		/// </summary>
		Simple = 8,

		/// <summary>
		/// Average of open, high, low, and close.
		/// </summary>
		Quarter = 9,

		/// <summary>
		/// Trend-following price variant 0.
		/// </summary>
		TrendFollow0 = 10,

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

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

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

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

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

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

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

	private readonly StrategyParam<BlauCsiEntryModes> _entryMode;
	private readonly StrategyParam<BlauCsiSmoothMethods> _smoothMethod;
	private readonly StrategyParam<int> _momentumLength;
	private readonly StrategyParam<int> _firstSmoothLength;
	private readonly StrategyParam<int> _secondSmoothLength;
	private readonly StrategyParam<int> _thirdSmoothLength;
	private readonly StrategyParam<int> _smoothingPhase;
	private readonly StrategyParam<BlauCsiAppliedPrices> _firstPrice;
	private readonly StrategyParam<BlauCsiAppliedPrices> _secondPrice;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<bool> _allowLongEntries;
	private readonly StrategyParam<bool> _allowShortEntries;
	private readonly StrategyParam<bool> _allowLongExits;
	private readonly StrategyParam<bool> _allowShortExits;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<DateTimeOffset> _startDate;
	private readonly StrategyParam<DateTimeOffset> _endDate;

	private BlauCsiIndicator _blauCsi = null!;
	private readonly List<decimal> _indicatorValues = new();
	private decimal? _stopPrice;
	private decimal? _takePrice;

	/// <summary>
	/// Initializes a new instance of the <see cref="ExpBlauCsiStrategy"/> class.
	/// </summary>
	public ExpBlauCsiStrategy()
	{
		_entryMode = Param(nameof(EntryMode), BlauCsiEntryModes.Breakdown)
			.SetDisplay("Entry Mode", "Zero cross or direction change logic", "Parameters");

		_smoothMethod = Param(nameof(SmoothingMethod), BlauCsiSmoothMethods.Exponential)
			.SetDisplay("Smoothing Method", "Moving average type used inside Blau CSI", "Indicator");

		_momentumLength = Param(nameof(MomentumLength), 1)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Length", "Number of bars for momentum calculation", "Indicator")
			
			.SetOptimize(1, 20, 1);

		_firstSmoothLength = Param(nameof(FirstSmoothingLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("First Smoothing", "Depth of first smoothing stage", "Indicator")

			.SetOptimize(5, 60, 5);

		_secondSmoothLength = Param(nameof(SecondSmoothingLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Second Smoothing", "Depth of second smoothing stage", "Indicator")

			.SetOptimize(2, 30, 1);

		_thirdSmoothLength = Param(nameof(ThirdSmoothingLength), 2)
			.SetGreaterThanZero()
			.SetDisplay("Third Smoothing", "Depth of third smoothing stage", "Indicator")

			.SetOptimize(2, 20, 1);

		_smoothingPhase = Param(nameof(SmoothingPhase), 15)
			.SetDisplay("Smoothing Phase", "Phase parameter used by Jurik smoothing", "Indicator");

		_firstPrice = Param(nameof(FirstPrice), BlauCsiAppliedPrices.Close)
			.SetDisplay("Momentum Price", "Price constant for the leading value", "Indicator");

		_secondPrice = Param(nameof(SecondPrice), BlauCsiAppliedPrices.Open)
			.SetDisplay("Reference Price", "Price constant for the lagging value", "Indicator");

		_signalBar = Param(nameof(SignalBar), 1)
			.SetNotNegative()
			.SetDisplay("Signal Bar", "Offset in bars used to confirm a signal", "Trading");

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

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

		_allowLongEntries = Param(nameof(AllowLongEntries), true)
			.SetDisplay("Allow Long Entries", "Enable opening long positions", "Trading");

		_allowShortEntries = Param(nameof(AllowShortEntries), true)
			.SetDisplay("Allow Short Entries", "Enable opening short positions", "Trading");

		_allowLongExits = Param(nameof(AllowLongExits), true)
			.SetDisplay("Allow Long Exits", "Enable closing long positions", "Trading");

		_allowShortExits = Param(nameof(AllowShortExits), true)
			.SetDisplay("Allow Short Exits", "Enable closing short positions", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used for indicator calculations", "General");

		_startDate = Param(nameof(StartDate), new DateTimeOffset(2018, 1, 1, 0, 0, 0, TimeSpan.Zero))
			.SetDisplay("Start Date", "Backtest start date", "General");

		_endDate = Param(nameof(EndDate), new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero))
			.SetDisplay("End Date", "Backtest end date", "General");

		Volume = 1m;
	}

	/// <summary>
	/// Entry mode determining how Blau CSI generates signals.
	/// </summary>
	public BlauCsiEntryModes EntryMode
	{
		get => _entryMode.Value;
		set => _entryMode.Value = value;
	}

	/// <summary>
	/// Smoothing method used inside Blau CSI calculation.
	/// </summary>
	public BlauCsiSmoothMethods SmoothingMethod
	{
		get => _smoothMethod.Value;
		set => _smoothMethod.Value = value;
	}

	/// <summary>
	/// Momentum length controlling the lookback for price difference and range.
	/// </summary>
	public int MomentumLength
	{
		get => _momentumLength.Value;
		set => _momentumLength.Value = value;
	}

	/// <summary>
	/// First smoothing depth.
	/// </summary>
	public int FirstSmoothingLength
	{
		get => _firstSmoothLength.Value;
		set => _firstSmoothLength.Value = value;
	}

	/// <summary>
	/// Second smoothing depth.
	/// </summary>
	public int SecondSmoothingLength
	{
		get => _secondSmoothLength.Value;
		set => _secondSmoothLength.Value = value;
	}

	/// <summary>
	/// Third smoothing depth.
	/// </summary>
	public int ThirdSmoothingLength
	{
		get => _thirdSmoothLength.Value;
		set => _thirdSmoothLength.Value = value;
	}

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

	/// <summary>
	/// Price constant for the leading momentum value.
	/// </summary>
	public ExpBlauCsiStrategy.BlauCsiAppliedPrices FirstPrice
	{
		get => _firstPrice.Value;
		set => _firstPrice.Value = value;
	}

	/// <summary>
	/// Price constant for the lagging momentum value.
	/// </summary>
	public ExpBlauCsiStrategy.BlauCsiAppliedPrices SecondPrice
	{
		get => _secondPrice.Value;
		set => _secondPrice.Value = value;
	}

	/// <summary>
	/// Offset in bars used when checking the Blau CSI buffer.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

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

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

	/// <summary>
	/// Toggle controlling long entries.
	/// </summary>
	public bool AllowLongEntries
	{
		get => _allowLongEntries.Value;
		set => _allowLongEntries.Value = value;
	}

	/// <summary>
	/// Toggle controlling short entries.
	/// </summary>
	public bool AllowShortEntries
	{
		get => _allowShortEntries.Value;
		set => _allowShortEntries.Value = value;
	}

	/// <summary>
	/// Toggle controlling long exits.
	/// </summary>
	public bool AllowLongExits
	{
		get => _allowLongExits.Value;
		set => _allowLongExits.Value = value;
	}

	/// <summary>
	/// Toggle controlling short exits.
	/// </summary>
	public bool AllowShortExits
	{
		get => _allowShortExits.Value;
		set => _allowShortExits.Value = value;
	}

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

	/// <summary>
	/// Start date filter for trading.
	/// </summary>
	public DateTimeOffset StartDate
	{
		get => _startDate.Value;
		set => _startDate.Value = value;
	}

	/// <summary>
	/// End date filter for trading.
	/// </summary>
	public DateTimeOffset EndDate
	{
		get => _endDate.Value;
		set => _endDate.Value = value;
	}

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

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

		_indicatorValues.Clear();
		_stopPrice = null;
		_takePrice = null;
	}

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

		_blauCsi = new BlauCsiIndicator
		{
			SmoothMethod = SmoothingMethod,
			MomentumLength = MomentumLength,
			FirstSmoothingLength = FirstSmoothingLength,
			SecondSmoothingLength = SecondSmoothingLength,
			ThirdSmoothingLength = ThirdSmoothingLength,
			Phase = SmoothingPhase,
			FirstPrice = FirstPrice,
			SecondPrice = SecondPrice
		};

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

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

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

		var result = _blauCsi.Process(new CandleIndicatorValue(_blauCsi, candle));
		var indicatorValue = result.IsEmpty ? 0m : result.GetValue<decimal>();

		if (HandleStops(candle))
			return;

		var time = candle.OpenTime;
		var inRange = time >= StartDate && time <= EndDate;

		if (!inRange)
		{
			if (Position > 0)
			{
				SellMarket();
				ResetTargets();
			}
			else if (Position < 0)
			{
				BuyMarket();
				ResetTargets();
			}

			return;
		}

		StoreIndicatorValue(indicatorValue);

		var (openLong, openShort, closeLong, closeShort) = EvaluateSignals();

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

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

		if (openLong && AllowLongEntries && Position <= 0)
		{
			BuyMarket();
			SetTargets(candle.ClosePrice, true);
		}
		else if (openShort && AllowShortEntries && Position >= 0)
		{
			SellMarket();
			SetTargets(candle.ClosePrice, false);
		}
	}

	private (bool openLong, bool openShort, bool closeLong, bool closeShort) EvaluateSignals()
	{
		var required = EntryMode == BlauCsiEntryModes.Twist ? 3 : 2;
		var count = _indicatorValues.Count;

		if (SignalBar < 0)
			return (false, false, false, false);

		var signalIndex = count - 1 - SignalBar;
		if (signalIndex < required - 1)
			return (false, false, false, false);

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

		if (EntryMode == BlauCsiEntryModes.Breakdown)
		{
			var current = _indicatorValues[signalIndex];
			var previous = _indicatorValues[signalIndex - 1];

			if (previous > 0m)
			{
				if (current <= 0m)
					openLong = true;

				closeShort = true;
			}

			if (previous < 0m)
			{
				if (current >= 0m)
					openShort = true;

				closeLong = true;
			}
		}
		else
		{
			var current = _indicatorValues[signalIndex];
			var previous = _indicatorValues[signalIndex - 1];
			var older = _indicatorValues[signalIndex - 2];

			if (previous < older)
			{
				if (current >= previous)
					openLong = true;

				closeShort = true;
			}

			if (previous > older)
			{
				if (current <= previous)
					openShort = true;

				closeLong = true;
			}
		}

		return (openLong, openShort, closeLong, closeShort);
	}

	private bool HandleStops(ICandleMessage candle)
	{
		var triggered = false;

		if (Position > 0)
		{
			if (_stopPrice != null && candle.LowPrice <= _stopPrice)
			{
				SellMarket();
				triggered = true;
			}
			else if (_takePrice != null && candle.HighPrice >= _takePrice)
			{
				SellMarket();
				triggered = true;
			}
		}
		else if (Position < 0)
		{
			if (_stopPrice != null && candle.HighPrice >= _stopPrice)
			{
				BuyMarket();
				triggered = true;
			}
			else if (_takePrice != null && candle.LowPrice <= _takePrice)
			{
				BuyMarket();
				triggered = true;
			}
		}

		if (triggered)
			ResetTargets();

		return triggered;
	}

	private void SetTargets(decimal entryPrice, bool isLong)
	{
		var step = Security?.PriceStep ?? 0m;

		if (step <= 0m)
		{
			_stopPrice = null;
			_takePrice = null;
			return;
		}

		_stopPrice = StopLossPoints > 0
			? isLong ? entryPrice - StopLossPoints * step : entryPrice + StopLossPoints * step
			: null;

		_takePrice = TakeProfitPoints > 0
			? isLong ? entryPrice + TakeProfitPoints * step : entryPrice - TakeProfitPoints * step
			: null;
	}

	private void ResetTargets()
	{
		_stopPrice = null;
		_takePrice = null;
	}

	private void StoreIndicatorValue(decimal value)
	{
		_indicatorValues.Add(value);

		var keep = SignalBar + (EntryMode == BlauCsiEntryModes.Twist ? 3 : 2) + 5;
		if (keep < 10)
			keep = 10;

		if (_indicatorValues.Count > keep)
			_indicatorValues.RemoveRange(0, _indicatorValues.Count - keep);
	}
}

/// <summary>
/// Blau Candle Stochastic Index implementation.
/// </summary>
public class BlauCsiIndicator : BaseIndicator
{
	private readonly List<ICandleMessage> _window = new();
	private IIndicator _momentumStage1;
	private IIndicator _momentumStage2;
	private IIndicator _momentumStage3;
	private IIndicator _rangeStage1;
	private IIndicator _rangeStage2;
	private IIndicator _rangeStage3;

	/// <summary>
	/// Selected smoothing method.
	/// </summary>
	public ExpBlauCsiStrategy.BlauCsiSmoothMethods SmoothMethod { get; set; } = ExpBlauCsiStrategy.BlauCsiSmoothMethods.Exponential;

	/// <summary>
	/// Momentum length.
	/// </summary>
	public int MomentumLength { get; set; } = 1;

	/// <summary>
	/// First smoothing length.
	/// </summary>
	public int FirstSmoothingLength { get; set; } = 20;

	/// <summary>
	/// Second smoothing length.
	/// </summary>
	public int SecondSmoothingLength { get; set; } = 5;

	/// <summary>
	/// Third smoothing length.
	/// </summary>
	public int ThirdSmoothingLength { get; set; } = 3;

	/// <summary>
	/// Phase parameter for Jurik average.
	/// </summary>
	public int Phase { get; set; } = 15;

	/// <summary>
	/// Price constant used for the leading price.
	/// </summary>
	public ExpBlauCsiStrategy.BlauCsiAppliedPrices FirstPrice { get; set; } = ExpBlauCsiStrategy.BlauCsiAppliedPrices.Close;

	/// <summary>
	/// Price constant used for the lagging price.
	/// </summary>
	public ExpBlauCsiStrategy.BlauCsiAppliedPrices SecondPrice { get; set; } = ExpBlauCsiStrategy.BlauCsiAppliedPrices.Open;

	/// <inheritdoc />
	protected override IIndicatorValue OnProcess(IIndicatorValue input)
	{
		ICandleMessage candle = null;
		if (input is CandleIndicatorValue civ)
			candle = civ.GetValue<ICandleMessage>(default);
		if (candle == null || candle.State != CandleStates.Finished)
			return new DecimalIndicatorValue(this, default, input.Time);

		if (_momentumStage1 == null)
			Initialize();

		_window.Add(candle);
		while (_window.Count > Math.Max(MomentumLength, 1))
			_window.RemoveAt(0);

		if (_window.Count < Math.Max(MomentumLength, 1))
		{
			IsFormed = false;
			return new DecimalIndicatorValue(this, default, input.Time);
		}

		var currentPrice = GetPrice(candle, FirstPrice);
		var pastCandle = _window[0];
		var pastPrice = GetPrice(pastCandle, SecondPrice);

		var min = decimal.MaxValue;
		var max = decimal.MinValue;

		foreach (var item in _window)
		{
			if (item.LowPrice < min)
				min = item.LowPrice;

			if (item.HighPrice > max)
				max = item.HighPrice;
		}

		var range = max - min;
		var momentum = currentPrice - pastPrice;

		var time = input.Time;
		var m1 = _momentumStage1!.Process(new DecimalIndicatorValue(_momentumStage1, momentum, time)).ToDecimal();
		var r1 = _rangeStage1!.Process(new DecimalIndicatorValue(_rangeStage1, range, time)).ToDecimal();

		var m2 = _momentumStage2!.Process(new DecimalIndicatorValue(_momentumStage2, m1, time)).ToDecimal();
		var r2 = _rangeStage2!.Process(new DecimalIndicatorValue(_rangeStage2, r1, time)).ToDecimal();

		var m3 = _momentumStage3!.Process(new DecimalIndicatorValue(_momentumStage3, m2, time)).ToDecimal();
		var r3 = _rangeStage3!.Process(new DecimalIndicatorValue(_rangeStage3, r2, time)).ToDecimal();

		decimal value;
		if (r3 != 0m)
			value = 100m * m3 / r3;
		else
			value = 0m;

		IsFormed = _momentumStage3.IsFormed && _rangeStage3.IsFormed;
		return new DecimalIndicatorValue(this, value, input.Time);
	}

	private void Initialize()
	{
		_momentumStage1 = CreateSmoother(FirstSmoothingLength);
		_momentumStage2 = CreateSmoother(SecondSmoothingLength);
		_momentumStage3 = CreateSmoother(ThirdSmoothingLength);

		_rangeStage1 = CreateSmoother(FirstSmoothingLength);
		_rangeStage2 = CreateSmoother(SecondSmoothingLength);
		_rangeStage3 = CreateSmoother(ThirdSmoothingLength);
	}

	private IIndicator CreateSmoother(int length)
	{
		return SmoothMethod switch
		{
			ExpBlauCsiStrategy.BlauCsiSmoothMethods.Simple => new SimpleMovingAverage { Length = length },
			ExpBlauCsiStrategy.BlauCsiSmoothMethods.Smoothed => new SmoothedMovingAverage { Length = length },
			ExpBlauCsiStrategy.BlauCsiSmoothMethods.LinearWeighted => new WeightedMovingAverage { Length = length },
			ExpBlauCsiStrategy.BlauCsiSmoothMethods.Jurik => new JurikMovingAverage { Length = length, Phase = Phase },
			_ => new ExponentialMovingAverage { Length = length }
		};
	}

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

	private static decimal GetDemarkPrice(ICandleMessage candle)
	{
		var sum = candle.HighPrice + candle.LowPrice + candle.ClosePrice;

		if (candle.ClosePrice < candle.OpenPrice)
			sum = (sum + candle.LowPrice) / 2m;
		else if (candle.ClosePrice > candle.OpenPrice)
			sum = (sum + candle.HighPrice) / 2m;
		else
			sum = (sum + candle.ClosePrice) / 2m;

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