Ver en GitHub

Blau SM Stochastic Strategy

Overview

This strategy is a C# conversion of the original MetaTrader 5 expert Exp_BlauSMStochastic built around the Blau SM Stochastic oscillator. The indicator measures the distance between price and the recent trading range, applies multiple smoothing stages and compares the result with a smoothed reference line. The strategy works on completed candles (default 4-hour timeframe) and allows trading in both directions.

Indicator Logic

  1. Compute the highest high and lowest low over LookbackLength bars.
  2. Build a detrended price series: sm = price - (HH + LL) / 2 where price is the applied price type.
  3. Smooth the detrended series sequentially by three moving averages with lengths FirstSmoothingLength, SecondSmoothingLength and ThirdSmoothingLength using the selected SmoothMethod (SMA, EMA, SMMA or LWMA).
  4. Smooth the half-range (HH - LL) / 2 with the same triple sequence to normalize volatility.
  5. Form the main oscillator line as 100 * smoothed(sm) / smoothed(range).
  6. Smooth the main line with SignalLength to obtain the signal line.

The parameter Phase is kept for compatibility with the MQL version but is not used by the simplified smoothing engine.

Trading Modes

  • Breakdown: monitors zero crossings of the main line. A cross from positive to non-positive opens a long and closes shorts. A cross from negative to non-negative opens a short and closes longs.
  • Twist: tracks momentum twists. If the main line forms a local trough (value rises after declining), a long entry is triggered, while a local peak (value falls after rising) triggers a short. Opposite positions are closed accordingly.
  • CloudTwist: observes crossings between the main line and the signal line. A downward cross of the main line through the signal line opens a long and exits shorts, while an upward cross opens a short and exits longs.

Entry and exit switches (EnableLongEntry, EnableShortEntry, EnableLongExit, EnableShortExit) allow disabling specific operations while keeping indicator calculations intact.

Risk Management

TakeProfitPoints and StopLossPoints convert to absolute price distances using the instrument price step and are passed to the built-in protective block via StartProtection. Set them to zero to disable the corresponding limit.

Parameters

  • CandleType (DataType, default: 4-hour time frame) – timeframe used for candle subscription and indicator calculations.
  • Mode (BlauSmStochasticModes, default: Twist) – selects the signal generation mode (Breakdown, Twist, CloudTwist).
  • SignalBar (int, default: 1) – number of bars to shift indicator values when evaluating signals, reproducing the original SignalBar logic.
  • LookbackLength (int, default: 5) – bars used to compute highest and lowest values.
  • FirstSmoothingLength (int, default: 20) – length of the first smoothing stage.
  • SecondSmoothingLength (int, default: 5) – length of the second smoothing stage.
  • ThirdSmoothingLength (int, default: 3) – length of the third smoothing stage.
  • SignalLength (int, default: 3) – smoothing length of the signal line.
  • SmoothMethod (BlauSmSmoothMethods, default: EMA) – moving average family applied to all smoothing stages (SMA, EMA, SMMA, LWMA).
  • PriceType (BlauSmAppliedPrices, default: Close) – applied price used to feed the oscillator (close, open, high, low, median, typical, weighted, simple, quarter, trend-follow variants, Demark).
  • EnableLongEntry (bool, default: true) – allow opening long positions.
  • EnableShortEntry (bool, default: true) – allow opening short positions.
  • EnableLongExit (bool, default: true) – allow closing long positions.
  • EnableShortExit (bool, default: true) – allow closing short positions.
  • TakeProfitPoints (int, default: 2000) – fixed take-profit distance expressed in instrument points.
  • StopLossPoints (int, default: 1000) – fixed stop-loss distance expressed in instrument points.

Notes

  • The smoothing engine currently supports classic moving averages (SMA, EMA, SMMA, LWMA). Exotic modes from the MQL library (JMA, JurX, etc.) are not available in StockSharp and are therefore not included.
  • Phase is preserved as a parameter for completeness; adjust it for documentation purposes only.
  • Works on any symbol supported by StockSharp. Adjust candle type, smoothing lengths and stops to match instrument volatility.
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;
	}
	}
}