在 GitHub 上查看

Exp Blau CSI 策略

该策略是 MetaTrader 5 专家顾问 Exp_BlauCSI 的 C# 版本,利用 Blau Candle Stochastic Index(CSI)对所选 K 线序列进行分析。策略可以基于指标穿越零轴或趋势转折进行交易,并支持以最小价格步长定义的止损和止盈距离。

交易逻辑

Blau CSI 将价格动量与最近 K 线的高低价范围进行比较,两者均通过三层移动平均进行平滑处理。

  • Breakdown 模式:当指标向下穿越零轴时开多,并在前一数值大于零时关闭所有空头;当指标向上穿越零轴时开空,并在前一数值小于零时关闭所有多头。
  • Twist 模式:当指标出现由跌转涨的拐点时开多,并关闭空头;当指标由涨转跌时开空,并关闭多头。上一柱的方向始终用于管理已有头寸。

所有信号均基于可配置的历史柱(Signal Bar)进行确认,以确保使用完整收盘的 K 线。

参数

参数 说明
Entry Mode 选择 BreakdownTwist 交易逻辑。
Smoothing Method Blau CSI 内部的平滑方式(Simple、Exponential、Smoothed、LinearWeighted、Jurik)。
Momentum Length 计算动量和范围时使用的柱数。
First/Second/Third Smoothing 三层平滑的周期长度。
Smoothing Phase Jurik 平滑的相位参数(其他方法忽略)。
Momentum Price / Reference Price 动量领先值与滞后值所使用的价格常量(收盘、开盘、高、低、中价、典型价、加权价、均值价、四分价、趋势价、Demark 等)。
Signal Bar 评估指标时向前回溯的柱数,默认 1 表示上一根已收盘 K 线。
Stop Loss (pts) 止损距离,单位为价格步长(0 表示禁用)。
Take Profit (pts) 止盈距离,单位为价格步长(0 表示禁用)。
Allow Long/Short Entries 控制是否允许开多/开空。
Allow Long/Short Exits 控制是否允许平多/平空信号。
Candle Type 订阅的数据类型(默认 4 小时 K 线)。
Start Date / End Date 限制策略参与交易的日期范围。
Order Volume 市价单的下单量。

风险控制

开仓时根据交易品种的 PriceStep 计算止损和止盈价位。如果品种没有提供价格步长,策略会自动禁用止损止盈。策略不包含追踪逻辑,头寸在平仓或达到目标前始终保持初始保护水平。

使用说明

  1. 将策略连接到能提供所需 Candle Type 数据的标的。
  2. 按需求设置指标模式和平滑参数。
  3. 如需启用止损/止盈,请确保标的具有有效的 PriceStep
  4. 通过 Start DateEnd Date 可限制策略在指定日期范围内运行。

与 MT5 原版的差异

  • 使用 StockSharp 指标与策略 API,替代了 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;
	}
}