在 GitHub 上查看

Blau SM Stochastic 策略

概述

该策略是将 MetaTrader 5 专家 Exp_BlauSMStochastic 迁移到 C# 的版本。核心使用 Blau SM Stochastic 振荡指标,通过测量价格相对于最近区间的位置并进行多次平滑来生成信号。策略仅在完全收盘的K线(默认 4 小时时间框架)上运作,可同时进行多头和空头交易。

指标逻辑

  1. 在最近 LookbackLength 根K线中计算最高价和最低价。
  2. 构建去趋势序列:sm = price - (HH + LL) / 2,其中 price 为所选价格类型。
  3. 使用 SmoothMethod 指定的移动平均(SMA、EMA、SMMA、LWMA)依次对 sm 进行三次平滑,周期分别为 FirstSmoothingLengthSecondSmoothingLengthThirdSmoothingLength
  4. 将半区间 (HH - LL) / 2 通过相同的三次平滑序列以归一化波动率。
  5. 主振荡线定义为 100 * smoothed(sm) / smoothed(range)
  6. 使用 SignalLength 对主线再次平滑得到信号线。

参数 Phase 仅用于与原始 MQL 版本保持一致,在当前简化的平滑实现中未参与计算。

交易模式

  • Breakdown:监控主线穿越零值。当主线从正区间进入非正区间时开多并平空;从负区间进入非负区间时开空并平多。
  • Twist:监控动量拐点。主线在下行后出现上拐(局部低点)时开多并平空;主线在上行后出现下拐(局部高点)时开空并平多。
  • CloudTwist:观察主线与信号线的交叉。主线自上而下穿越信号线时开多并平空;自下而上穿越时开空并平多。

EnableLongEntryEnableShortEntryEnableLongExitEnableShortExit 四个开关可分别控制开仓或平仓动作,而不会影响指标计算。

风险管理

TakeProfitPointsStopLossPoints 会根据标的的价格步长转换为绝对价格距离,并通过 StartProtection 启动保护模块。设为 0 即关闭对应的止盈或止损。

参数说明

  • CandleType (DataType,默认 4 小时):用于订阅和计算的K线类型。
  • Mode (BlauSmStochasticModes,默认 Twist):信号生成模式(Breakdown、Twist、CloudTwist)。
  • SignalBar (int,默认 1):在评估信号时向后取值的K线数量,对应原始参数 SignalBar
  • LookbackLength (int,默认 5):计算最高价、最低价所用的回溯长度。
  • FirstSmoothingLength (int,默认 20):第一阶段平滑周期。
  • SecondSmoothingLength (int,默认 5):第二阶段平滑周期。
  • ThirdSmoothingLength (int,默认 3):第三阶段平滑周期。
  • SignalLength (int,默认 3):信号线的平滑周期。
  • SmoothMethod (BlauSmSmoothMethods,默认 EMA):所有平滑阶段使用的均线类型(SMA、EMA、SMMA、LWMA)。
  • PriceType (BlauSmAppliedPrices,默认 Close):振荡器使用的价格类型(收盘、开盘、最高、最低、中位、典型、加权、简单、四分位、TrendFollow 两种及 Demark)。
  • EnableLongEntry (bool,默认 true):允许开多。
  • EnableShortEntry (bool,默认 true):允许开空。
  • EnableLongExit (bool,默认 true):允许平多。
  • EnableShortExit (bool,默认 true):允许平空。
  • TakeProfitPoints (int,默认 2000):以点数表示的固定止盈距离。
  • StopLossPoints (int,默认 1000):以点数表示的固定止损距离。

说明

  • 当前平滑引擎仅支持经典均线(SMA、EMA、SMMA、LWMA),MQL 库中的 JMA、JurX 等特殊算法在 StockSharp 中不可用。
  • Phase 参数仅为兼容保留,调节不会影响结果。
  • 策略可用于任意 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 SM Stochastic based strategy converted from the MQL5 Expert Advisor.
/// </summary>
public class BlauSmStochasticStrategy : Strategy
{
	/// <summary>
	/// Signal generation modes.
	/// </summary>
	public enum BlauSmStochasticModes
	{
		/// <summary>
		/// Uses histogram zero crossings.
		/// </summary>
		Breakdown,

		/// <summary>
		/// Uses momentum twists.
		/// </summary>
		Twist,

		/// <summary>
		/// Uses crossings between main and signal lines.
		/// </summary>
		CloudTwist
	}

	/// <summary>
	/// Moving average options used in the oscillator smoothing stages.
	/// </summary>
	public enum BlauSmSmoothMethods
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Sma,

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

		/// <summary>
		/// Smoothed moving average.
		/// </summary>
		Smma,

		/// <summary>
		/// Linear weighted moving average.
		/// </summary>
		Lwma
	}

	/// <summary>
	/// Applied price options for oscillator input.
	/// </summary>
	public enum BlauSmAppliedPrices
	{
		/// <summary>
		/// Close price.
		/// </summary>
		Close,

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

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

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

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

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

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

		/// <summary>
		/// (Open + Close) / 2.
		/// </summary>
		Simple,

		/// <summary>
		/// (Open + Close + High + Low) / 4.
		/// </summary>
		Quarter,

		/// <summary>
		/// Trend-follow price using highs and lows.
		/// </summary>
		TrendFollow0,

		/// <summary>
		/// Average between close and extreme price in trend direction.
		/// </summary>
		TrendFollow1,

		/// <summary>
		/// Demark price variant.
		/// </summary>
		Demark
	}
	private readonly StrategyParam<BlauSmStochasticModes> _mode;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<int> _lookbackLength;
	private readonly StrategyParam<int> _firstSmoothingLength;
	private readonly StrategyParam<int> _secondSmoothingLength;
	private readonly StrategyParam<int> _thirdSmoothingLength;
	private readonly StrategyParam<int> _signalLength;
	private readonly StrategyParam<BlauSmSmoothMethods> _smoothMethod;
	private readonly StrategyParam<int> _phase;
	private readonly StrategyParam<BlauSmAppliedPrices> _priceType;
	private readonly StrategyParam<bool> _enableLongEntry;
	private readonly StrategyParam<bool> _enableShortEntry;
	private readonly StrategyParam<bool> _enableLongExit;
	private readonly StrategyParam<bool> _enableShortExit;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _mainHistory = new();
	private readonly List<decimal> _signalHistory = new();
	private BlauSmStochasticIndicator _indicator;
	private decimal _entryPrice;

	/// <summary>
	/// Initializes a new instance of the <see cref="BlauSmStochasticStrategy"/> class.
	/// </summary>
	public BlauSmStochasticStrategy()
	{
		_mode = Param(nameof(Mode), BlauSmStochasticModes.Twist)
			.SetDisplay("Mode", "Signal generation mode", "Parameters");

		_signalBar = Param(nameof(SignalBar), 1)
			.SetNotNegative()
			.SetDisplay("Signal Bar Shift", "Number of bars to shift indicator values", "Parameters");

		_lookbackLength = Param(nameof(LookbackLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Lookback Length", "Bars used to compute highest and lowest prices", "Indicator")
			
			.SetOptimize(5, 25, 5);

		_firstSmoothingLength = Param(nameof(FirstSmoothingLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("First Smoothing", "Length of the first smoothing stage", "Indicator")
			
			.SetOptimize(10, 40, 5);

		_secondSmoothingLength = Param(nameof(SecondSmoothingLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Second Smoothing", "Length of the second smoothing stage", "Indicator")
			
			.SetOptimize(3, 15, 2);

		_thirdSmoothingLength = Param(nameof(ThirdSmoothingLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Third Smoothing", "Length of the third smoothing stage", "Indicator")
			
			.SetOptimize(2, 10, 1);

		_signalLength = Param(nameof(SignalLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Signal Smoothing", "Length of the signal line smoothing", "Indicator")
			
			.SetOptimize(2, 12, 1);

		_smoothMethod = Param(nameof(SmoothMethod), BlauSmSmoothMethods.Ema)
			.SetDisplay("Smoothing Method", "Moving average type used for all smoothing stages", "Indicator");

		_phase = Param(nameof(Phase), 15)
			.SetDisplay("Phase", "Compatibility parameter kept from the original indicator", "Indicator");

		_priceType = Param(nameof(PriceType), BlauSmAppliedPrices.Close)
			.SetDisplay("Applied Price", "Price input used in oscillator calculations", "Indicator");

		_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 existing long positions", "Trading");

		_enableShortExit = Param(nameof(EnableShortExit), true)
			.SetDisplay("Enable Short Exit", "Allow closing existing short positions", "Trading");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
			.SetNotNegative()
			.SetDisplay("Take Profit (points)", "Take-profit distance expressed in instrument points", "Risk")
			
			.SetOptimize(0, 4000, 500);

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
			.SetNotNegative()
			.SetDisplay("Stop Loss (points)", "Stop-loss distance expressed in instrument points", "Risk")
			
			.SetOptimize(0, 4000, 500);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for indicator calculations", "General");
	}

	/// <summary>
	/// Strategy operating mode.
	/// </summary>
	public BlauSmStochasticModes Mode
	{
		get => _mode.Value;
		set => _mode.Value = value;
	}

	/// <summary>
	/// Number of bars to shift indicator values before evaluation.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Length used to search for highs and lows.
	/// </summary>
	public int LookbackLength
	{
		get => _lookbackLength.Value;
		set => _lookbackLength.Value = value;
	}

	/// <summary>
	/// First smoothing stage length.
	/// </summary>
	public int FirstSmoothingLength
	{
		get => _firstSmoothingLength.Value;
		set => _firstSmoothingLength.Value = value;
	}

	/// <summary>
	/// Second smoothing stage length.
	/// </summary>
	public int SecondSmoothingLength
	{
		get => _secondSmoothingLength.Value;
		set => _secondSmoothingLength.Value = value;
	}

	/// <summary>
	/// Third smoothing stage length.
	/// </summary>
	public int ThirdSmoothingLength
	{
		get => _thirdSmoothingLength.Value;
		set => _thirdSmoothingLength.Value = value;
	}

	/// <summary>
	/// Signal line smoothing length.
	/// </summary>
	public int SignalLength
	{
		get => _signalLength.Value;
		set => _signalLength.Value = value;
	}

	/// <summary>
	/// Moving average method.
	/// </summary>
	public BlauSmSmoothMethods SmoothMethod
	{
		get => _smoothMethod.Value;
		set => _smoothMethod.Value = value;
	}

	/// <summary>
	/// Phase parameter kept for compatibility.
	/// </summary>
	public int Phase
	{
		get => _phase.Value;
		set => _phase.Value = value;
	}

	/// <summary>
	/// Applied price type.
	/// </summary>
	public BlauSmAppliedPrices PriceType
	{
		get => _priceType.Value;
		set => _priceType.Value = value;
	}

	/// <summary>
	/// Allow opening long positions.
	/// </summary>
	public bool EnableLongEntry
	{
		get => _enableLongEntry.Value;
		set => _enableLongEntry.Value = value;
	}

	/// <summary>
	/// Allow opening short positions.
	/// </summary>
	public bool EnableShortEntry
	{
		get => _enableShortEntry.Value;
		set => _enableShortEntry.Value = value;
	}

	/// <summary>
	/// Allow closing existing long positions.
	/// </summary>
	public bool EnableLongExit
	{
		get => _enableLongExit.Value;
		set => _enableLongExit.Value = value;
	}

	/// <summary>
	/// Allow closing existing short positions.
	/// </summary>
	public bool EnableShortExit
	{
		get => _enableShortExit.Value;
		set => _enableShortExit.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in points.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in points.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

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

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

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

		_mainHistory.Clear();
		_signalHistory.Clear();
		_indicator = null;
		_entryPrice = 0m;
	}

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

		_indicator = new BlauSmStochasticIndicator
		{
			LookbackLength = LookbackLength,
			FirstSmoothingLength = FirstSmoothingLength,
			SecondSmoothingLength = SecondSmoothingLength,
			ThirdSmoothingLength = ThirdSmoothingLength,
			SignalLength = SignalLength,
			SmoothMethod = SmoothMethod,
			Phase = Phase,
			PriceType = PriceType,
		};

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

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

	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);
		if (trade?.Trade == null) return;
		if (Position != 0m && _entryPrice == 0m)
			_entryPrice = trade.Trade.Price;
		if (Position == 0m)
			_entryPrice = 0m;
	}

	private void HandleProtectiveLevels(ICandleMessage candle)
	{
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0m)
		{
			if (StopLossPoints > 0 && candle.LowPrice <= _entryPrice - StopLossPoints * step)
			{
				SellMarket(Position);
				return;
			}
			if (TakeProfitPoints > 0 && candle.HighPrice >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket(Position);
				return;
			}
		}
		else if (Position < 0m)
		{
			var abs = Math.Abs(Position);
			if (StopLossPoints > 0 && candle.HighPrice >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket(abs);
				return;
			}
			if (TakeProfitPoints > 0 && candle.LowPrice <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket(abs);
				return;
			}
		}
	}

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

		HandleProtectiveLevels(candle);

		var indicatorValue = _indicator.Process(candle);

		if (!_indicator.IsFormed)
			return;

		var main = indicatorValue.ToDecimal();
		// Get signal from indicator's stored value
		var signal = _indicator.LastSignal;

		UpdateHistory(main, signal);

		var hasCurrent = TryGetMain(SignalBar, out var currentMain);
		var hasPrevious = TryGetMain(SignalBar + 1, out var previousMain);

		var hasCurrentSignal = TryGetSignal(SignalBar, out var currentSignal);
		var hasPreviousSignal = TryGetSignal(SignalBar + 1, out var previousSignal);

		if (!hasCurrent || !hasPrevious)
			return;

		var buyEntry = false;
		var sellEntry = false;
		var buyExit = false;
		var sellExit = false;

		switch (Mode)
		{
			case BlauSmStochasticModes.Breakdown:
			{
				// Detect histogram sign change through zero.
				if (previousMain > 0m && currentMain <= 0m)
				{
					if (EnableLongEntry)
						buyEntry = true;
					if (EnableShortExit)
						sellExit = true;
				}

				if (previousMain < 0m && currentMain >= 0m)
				{
					if (EnableShortEntry)
						sellEntry = true;
					if (EnableLongExit)
						buyExit = true;
				}
				break;
			}
			case BlauSmStochasticModes.Twist:
			{
				if (!TryGetMain(SignalBar + 2, out var olderMain))
					return;

				// Identify twists in momentum slope.
				if (previousMain < olderMain && currentMain > previousMain)
				{
					if (EnableLongEntry)
						buyEntry = true;
					if (EnableShortExit)
						sellExit = true;
				}

				if (previousMain > olderMain && currentMain < previousMain)
				{
					if (EnableShortEntry)
						sellEntry = true;
					if (EnableLongExit)
						buyExit = true;
				}
				break;
			}
			case BlauSmStochasticModes.CloudTwist:
			{
				if (!hasCurrentSignal || !hasPreviousSignal)
					return;

				// Watch for crossings between main and smoothed signal lines.
				if (previousMain > previousSignal && currentMain <= currentSignal)
				{
					if (EnableLongEntry)
						buyEntry = true;
					if (EnableShortExit)
						sellExit = true;
				}

				if (previousMain < previousSignal && currentMain >= currentSignal)
				{
					if (EnableShortEntry)
						sellEntry = true;
					if (EnableLongExit)
						buyExit = true;
				}
				break;
			}
		}

		// Close positions before opening opposite trades.
		if (buyExit && Position > 0)
			SellMarket(Position);

		if (sellExit && Position < 0)
			BuyMarket(-Position);

		if (buyEntry && Position <= 0)
			BuyMarket(Volume + Math.Abs(Position));

		if (sellEntry && Position >= 0)
			SellMarket(Volume + Math.Abs(Position));
	}

	private void UpdateHistory(decimal main, decimal signal)
	{
		_mainHistory.Add(main);
		_signalHistory.Add(signal);

		var max = SignalBar + 5;
		while (_mainHistory.Count > max)
			_mainHistory.RemoveAt(0);

		while (_signalHistory.Count > max)
			_signalHistory.RemoveAt(0);
	}

	private bool TryGetMain(int shift, out decimal value)
	{
		var index = _mainHistory.Count - 1 - shift;
		if (index < 0)
		{
			value = 0m;
			return false;
		}

		value = _mainHistory[index];
		return true;
	}

	private bool TryGetSignal(int shift, out decimal value)
	{
		var index = _signalHistory.Count - 1 - shift;
		if (index < 0)
		{
			value = 0m;
			return false;
		}

		value = _signalHistory[index];
		return true;
	}

	/// <summary>
	/// Custom indicator implementing the Blau SM Stochastic oscillator.
	/// </summary>
	public class BlauSmStochasticIndicator : BaseIndicator
	{
	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();

	private IIndicator _smooth1;
	private IIndicator _smooth2;
	private IIndicator _smooth3;
	private IIndicator _halfSmooth1;
	private IIndicator _halfSmooth2;
	private IIndicator _halfSmooth3;
	private IIndicator _signalSmooth;

	/// <summary>
	/// Bars used to search for highest and lowest values.
	/// </summary>
	public int LookbackLength { get; set; } = 5;

	/// <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>
	/// Signal line smoothing length.
	/// </summary>
	public int SignalLength { get; set; } = 3;

	/// <summary>
	/// Moving average type used throughout the calculations.
	/// </summary>
	public BlauSmSmoothMethods SmoothMethod { get; set; } = BlauSmSmoothMethods.Ema;

	/// <summary>
	/// Phase parameter kept for compatibility with the original code.
	/// </summary>
	public int Phase { get; set; } = 15;

	/// <summary>
	/// Applied price type.
	/// </summary>
	public BlauSmAppliedPrices PriceType { get; set; } = BlauSmAppliedPrices.Close;

	/// <summary>
	/// Last computed signal line value.
	/// </summary>
	public decimal LastSignal { get; private set; }

	/// <inheritdoc />
	protected override IIndicatorValue OnProcess(IIndicatorValue input)
	{
		IsFormed = false;

		if (!input.IsFinal)
			return new DecimalIndicatorValue(this, default, input.Time);

		var candle = input.GetValue<ICandleMessage>();
		if (candle == null)
			return new DecimalIndicatorValue(this, default, input.Time);

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		while (_highs.Count > LookbackLength)
			_highs.RemoveAt(0);

		while (_lows.Count > LookbackLength)
			_lows.RemoveAt(0);

		EnsureIndicators();
		_lastInputTime = input.Time;

		if (_highs.Count < LookbackLength || _lows.Count < LookbackLength)
			return new DecimalIndicatorValue(this, default, input.Time);

		var highest = GetMaximum(_highs);
		var lowest = GetMinimum(_lows);
		var price = GetAppliedPrice(candle, PriceType);

		var sm = price - 0.5m * (lowest + highest);
		var half = 0.5m * (highest - lowest);

		var sm1 = ProcessStage(_smooth1, sm);
		if (sm1 is null)
			return new DecimalIndicatorValue(this, default, input.Time);

		var sm2 = ProcessStage(_smooth2, sm1.Value);
		if (sm2 is null)
			return new DecimalIndicatorValue(this, default, input.Time);

		var sm3 = ProcessStage(_smooth3, sm2.Value);
		if (sm3 is null)
			return new DecimalIndicatorValue(this, default, input.Time);

		var half1 = ProcessStage(_halfSmooth1, half);
		if (half1 is null)
			return new DecimalIndicatorValue(this, default, input.Time);

		var half2 = ProcessStage(_halfSmooth2, half1.Value);
		if (half2 is null)
			return new DecimalIndicatorValue(this, default, input.Time);

		var half3 = ProcessStage(_halfSmooth3, half2.Value);
		if (half3 is null || half3.Value == 0m)
			return new DecimalIndicatorValue(this, default, input.Time);

		var main = 100m * sm3.Value / half3.Value;

		var signal = ProcessStage(_signalSmooth, main);
		if (signal is null)
			return new DecimalIndicatorValue(this, default, input.Time);

		IsFormed = true;
		LastSignal = signal.Value;

		return new DecimalIndicatorValue(this, main, input.Time) { IsFinal = input.IsFinal };
	}

	private void EnsureIndicators()
	{
		if (_smooth1 != null)
			return;

		_smooth1 = CreateAverage(FirstSmoothingLength);
		_smooth2 = CreateAverage(SecondSmoothingLength);
		_smooth3 = CreateAverage(ThirdSmoothingLength);
		_halfSmooth1 = CreateAverage(FirstSmoothingLength);
		_halfSmooth2 = CreateAverage(SecondSmoothingLength);
		_halfSmooth3 = CreateAverage(ThirdSmoothingLength);
		_signalSmooth = CreateAverage(SignalLength);
	}

	private static decimal GetMaximum(List<decimal> values)
	{
		var max = decimal.MinValue;
		foreach (var value in values)
		{
			if (value > max)
				max = value;
		}
		return max;
	}

	private static decimal GetMinimum(List<decimal> values)
	{
		var min = decimal.MaxValue;
		foreach (var value in values)
		{
			if (value < min)
				min = value;
		}
		return min;
	}

	private static decimal GetAppliedPrice(ICandleMessage candle, BlauSmAppliedPrices priceType)
	{
		return priceType switch
		{
			BlauSmAppliedPrices.Close => candle.ClosePrice,
			BlauSmAppliedPrices.Open => candle.OpenPrice,
			BlauSmAppliedPrices.High => candle.HighPrice,
			BlauSmAppliedPrices.Low => candle.LowPrice,
			BlauSmAppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			BlauSmAppliedPrices.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
			BlauSmAppliedPrices.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
			BlauSmAppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
			BlauSmAppliedPrices.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
			BlauSmAppliedPrices.TrendFollow0 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice : candle.ClosePrice < candle.OpenPrice ? candle.LowPrice : candle.ClosePrice,
			BlauSmAppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice ? (candle.HighPrice + candle.ClosePrice) / 2m : candle.ClosePrice < candle.OpenPrice ? (candle.LowPrice + candle.ClosePrice) / 2m : candle.ClosePrice,
			BlauSmAppliedPrices.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;
	}

	private IIndicator CreateAverage(int length)
	{
		return SmoothMethod switch
		{
			BlauSmSmoothMethods.Sma => new SimpleMovingAverage { Length = length },
			BlauSmSmoothMethods.Ema => new ExponentialMovingAverage { Length = length },
			BlauSmSmoothMethods.Smma => new SmoothedMovingAverage { Length = length },
			BlauSmSmoothMethods.Lwma => new WeightedMovingAverage { Length = length },
			_ => new ExponentialMovingAverage { Length = length },
		};
	}

	private DateTime _lastInputTime;

	private decimal? ProcessStage(IIndicator indicator, decimal value)
	{
		var result = indicator.Process(new DecimalIndicatorValue(indicator, value, _lastInputTime) { IsFinal = true });
		return indicator.IsFormed ? result.ToDecimal() : null;
	}

	/// <inheritdoc />
	public override void Reset()
	{
		base.Reset();

		_highs.Clear();
		_lows.Clear();
		_smooth1 = null;
		_smooth2 = null;
		_smooth3 = null;
		_halfSmooth1 = null;
		_halfSmooth2 = null;
		_halfSmooth3 = null;
		_signalSmooth = null;
		LastSignal = 0m;
		IsFormed = false;
	}
	}
}