Auf GitHub ansehen

Brandy-Strategie (C#)

Übersicht

Die Brandy-Strategie ist eine direkte Portierung des MetaTrader 5-Expert Advisors Brandy (barabashkakvn's edition). Sie kombiniert zwei konfigurierbare gleitende Durchschnitte und bewertet deren relative Positionen auf abgeschlossenen Kerzen, um zu entscheiden, ob eine Long- oder Short-Position eröffnet werden soll. Die ursprüngliche Logik erzwingt auch optionale Stop-Loss-, Take-Profit- und Trailing-Stop-Kontrollen in Pips. Diese C#-Version reproduziert diese Verhaltensweisen originalgetreu auf der StockSharp-High-Level-Strategie-API.

Die Strategie berechnet einen "schnellen" gleitenden Durchschnitt auf dem Eröffnungspreisstream und einen "langsamen" gleitenden Durchschnitt auf dem Schlusskursstream. Beide Indikatoren haben unabhängige Parameter für Periode, Glättungsmethode, Preisquelle, Signalbalken-Referenz und Verschiebung. Signale werden generiert, wenn die MA-Werte des vorherigen Balkens auf derselben Seite der jeweiligen Signalwerte liegen. Die Schutzlogik prüft den eröffnungsbasierten gleitenden Durchschnitt bei jeder Kerze und verlässt den Trade sofort, wenn die Trendbedingung nicht mehr erfüllt ist. Zusätzliches Risikomanagement wird mit optionalen Stop-Loss-, Take-Profit- und Trailing-Stop-Abständen implementiert, alle in Pips gemessen und in absolute Preise umgerechnet durch die Verwendung der Tick-Größe des Instruments mit einer Fünf-Dezimalstellen-Pip-Anpassung.

Handelslogik

  1. Bei jeder abgeschlossenen Kerze aktualisiert die Strategie die eröffnungs- und schlusspreisbasierten gleitenden Durchschnitte unter Verwendung der konfigurierten Glättungsmethode und des angewendeten Preises. Historische MA-Werte werden gepuffert, damit der Code das iMA-Verschiebungsverhalten des ursprünglichen Expert Advisors emulieren kann.
  2. Wenn keine aktive Position vorhanden ist, wird ein Long-Trade eröffnet, wenn:
    • Der vorherige eröffnungsbasierte MA-Wert größer als der konfigurierte Signalwert (möglicherweise verschoben) ist;
    • Der vorherige schlussbasierte MA-Wert ebenfalls größer als seine Signalreferenz ist (zu beachten ist, dass der ursprüngliche EA für diese Prüfung gegen den eröffnungsbasierten Indikator vergleicht, und die Portierung behält diese Eigenart für die Kompatibilität).
  3. Ein Short-Trade wird eröffnet, wenn beide gleitenden Durchschnitte unter ihren jeweiligen Signalreferenzen liegen.
  4. Während eine Position aktiv ist, bewertet die Strategie Ausstiege bei jeder abgeschlossenen Kerze in folgender Reihenfolge:
    • Trendumkehr: wenn der eröffnungsbasierte MA unter den Signalwert fällt (für Longs) oder darüber steigt (für Shorts), wird die Position sofort zum Marktpreis geschlossen.
    • Trailing-Stop-Aktualisierung: wenn aktiviert und die Bewegung zugunsten des Trades Trailing Stop + Trailing Step überschreitet (in absolute Preise umgerechnet), wird das Stop-Niveau eng gezogen, um eine Distanz von Trailing Stop vom letzten Schluss beizubehalten.
    • Take-Profit: wenn der Kerzenbereich das Gewinnziel berührt, wird der Trade zum Marktpreis geschlossen.
    • Stop-Loss: wenn der Kerzenbereich das Schutz-Stop-Niveau verletzt, wird der Trade geschlossen.
  5. Das gesamte Volumen ist fest und wird durch den Parameter TradeVolume bestimmt. Der Standardwert repliziert die 0,1-Lot-Einstellung der MT5-Version.

Parameterreferenz

Parameter Beschreibung
TradeVolume Marktordergröße in Lots.
StopLossPips Abstand des Schutz-Stops, gemessen in Pips (0 deaktiviert ihn).
TakeProfitPips Abstand des Gewinnziels in Pips (0 deaktiviert es).
TrailingStopPips Trailing-Stop-Abstand in Pips. Erfordert, dass TrailingStepPips positiv ist.
TrailingStepPips Zusätzliche Pip-Bewegung erforderlich, bevor der Trailing Stop vorgerückt wird. Muss ungleich null sein, wenn der Trailing Stop aktiv ist.
MaClosePeriod, MaOpenPeriod Gleitende Durchschnittslängen für die Schluss- und Eröffnungsserien.
MaCloseShift, MaOpenShift Auf die MA-Puffer angewendete Vorwärtsverschiebungen (Anzahl der Balken).
MaCloseSignalBar, MaOpenSignalBar Als Vergleichsreferenzen verwendete Balkenindizes. Null entspricht dem neuesten Wert, eins bezieht sich auf den vorherigen Balken, usw.
MaCloseMethod, MaOpenMethod Glättungsmethoden für gleitende Durchschnitte (SMA, EMA, SMMA, LWMA).
MaCloseAppliedPrice, MaOpenAppliedPrice Kerzenpreisquelle für jeden Indikator (Schluss, Eröffnung, Hoch, Tief, Median, Typisch, Gewichtet).
CandleType Zeitrahmen der von der Datenquelle angeforderten Kerzen.

Implementierungshinweise

  • Die Pip-Größe wird aus Security.PriceStep berechnet und mit 10 multipliziert, wenn das Instrument 3 oder 5 Dezimalstellen aufweist, was die MetaTrader-Anpassung zwischen Punkten und Pips widerspiegelt.
  • Die Indikatorhistorie wird mit begrenzten Warteschlangen aufbewahrt, damit die Strategie iMA-Aufrufe mit beliebigen Signalbalken-Indizes und positiven Verschiebungen reproduzieren kann, ohne sich auf verbotene Indikatorzugriffe zu stützen.
  • Die Abschlussbedingung für den schlusspreisbasierten gleitenden Durchschnitt vergleicht absichtlich gegen den Eröffnungs-MA-Puffer, weil der ursprüngliche Quellcode iMAGet(handle_iMAOpen, MaClose_SignalBar) aufrief. Diese Portierung behält das Verhalten bei, um die Kompatibilität mit alten Konfigurationen zu erhalten.
  • Stops und Trailing-Logik werden auf abgeschlossenen Kerzen ausgeführt und approximieren die vom Expert Advisor vorgenommenen Ordermodifikationen unter Einhaltung der StockSharp-High-Level-API.

Nutzungstipps

  • Konfigurieren Sie den CandleType-Parameter entsprechend dem vom ursprünglichen EA verwendeten Zeitrahmen (typischerweise ein einzelner Instrument-Zeitrahmen).
  • Lassen Sie TrailingStopPips auf null, wenn kein Trailing-Verhalten gewünscht wird; stellen Sie andernfalls sicher, dass TrailingStepPips strikt positiv ist, um den von der Strategie erzwungenen Initialisierungsfehler zu vermeiden.
  • Stellen Sie beim Backtesting in StockSharp sicher, dass PriceStep und Decimals des Instruments die beabsichtigte Pip-Definition widerspiegeln, damit Risikoabstände korrekt umgerechnet werden.
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>
/// Conversion of the Brandy Expert Advisor from MetaTrader 5.
/// Combines two configurable moving averages to generate entries and manages positions with trailing exits.
/// </summary>
public class BrandyStrategy : Strategy
{
	/// <summary>
	/// Supported moving average smoothing methods.
	/// </summary>
	public enum MovingAverageMethods
	{
		/// <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>
	/// Price sources that can be fed into the moving averages.
	/// </summary>
	public enum AppliedPriceTypes
	{
		/// <summary>
		/// Candle close price.
		/// </summary>
		Close,

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

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

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

		/// <summary>
		/// Median price of the candle (high + low) / 2.
		/// </summary>
		Median,

		/// <summary>
		/// Typical price (high + low + close) / 3.
		/// </summary>
		Typical,

		/// <summary>
		/// Weighted price (high + low + 2 * close) / 4.
		/// </summary>
		Weighted
	}
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<int> _maClosePeriod;
	private readonly StrategyParam<int> _maCloseShift;
	private readonly StrategyParam<MovingAverageMethods> _maCloseMethod;
	private readonly StrategyParam<AppliedPriceTypes> _maCloseAppliedPrice;
	private readonly StrategyParam<int> _maCloseSignalBar;
	private readonly StrategyParam<int> _maOpenPeriod;
	private readonly StrategyParam<int> _maOpenShift;
	private readonly StrategyParam<MovingAverageMethods> _maOpenMethod;
	private readonly StrategyParam<AppliedPriceTypes> _maOpenAppliedPrice;
	private readonly StrategyParam<int> _maOpenSignalBar;
	private readonly StrategyParam<DataType> _candleType;

	private DecimalLengthIndicator _maOpenIndicator;
	private DecimalLengthIndicator _maCloseIndicator;
	private decimal _pipSize;
	private readonly List<decimal> _maOpenValues = [];
	private readonly List<decimal> _maCloseValues = [];
	private int _maxOpenQueueSize;
	private int _maxCloseQueueSize;
	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;

	/// <summary>
	/// Trading volume per order.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set
		{
			_tradeVolume.Value = value;
			Volume = value;
		}
	}

	/// <summary>
	/// Stop loss distance expressed in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance expressed in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Step that defines how far the price must move before the trailing stop is advanced.
	/// </summary>
	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Period of the moving average calculated on the close series.
	/// </summary>
	public int MaClosePeriod
	{
		get => _maClosePeriod.Value;
		set => _maClosePeriod.Value = value;
	}

	/// <summary>
	/// Displacement applied to the moving average calculated on closes.
	/// </summary>
	public int MaCloseShift
	{
		get => _maCloseShift.Value;
		set => _maCloseShift.Value = value;
	}

	/// <summary>
	/// Moving average smoothing method for the close series.
	/// </summary>
	public MovingAverageMethods MaCloseMethod
	{
		get => _maCloseMethod.Value;
		set => _maCloseMethod.Value = value;
	}

	/// <summary>
	/// Price source used by the close moving average.
	/// </summary>
	public AppliedPriceTypes MaCloseAppliedPrice
	{
		get => _maCloseAppliedPrice.Value;
		set => _maCloseAppliedPrice.Value = value;
	}

	/// <summary>
	/// Bar index used as a signal reference for the close moving average.
	/// </summary>
	public int MaCloseSignalBar
	{
		get => _maCloseSignalBar.Value;
		set => _maCloseSignalBar.Value = value;
	}

	/// <summary>
	/// Period of the moving average calculated on the open series.
	/// </summary>
	public int MaOpenPeriod
	{
		get => _maOpenPeriod.Value;
		set => _maOpenPeriod.Value = value;
	}

	/// <summary>
	/// Displacement applied to the moving average calculated on opens.
	/// </summary>
	public int MaOpenShift
	{
		get => _maOpenShift.Value;
		set => _maOpenShift.Value = value;
	}

	/// <summary>
	/// Moving average smoothing method for the open series.
	/// </summary>
	public MovingAverageMethods MaOpenMethod
	{
		get => _maOpenMethod.Value;
		set => _maOpenMethod.Value = value;
	}

	/// <summary>
	/// Price source used by the open moving average.
	/// </summary>
	public AppliedPriceTypes MaOpenAppliedPrice
	{
		get => _maOpenAppliedPrice.Value;
		set => _maOpenAppliedPrice.Value = value;
	}

	/// <summary>
	/// Bar index used as a signal reference for the open moving average.
	/// </summary>
	public int MaOpenSignalBar
	{
		get => _maOpenSignalBar.Value;
		set => _maOpenSignalBar.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the <see cref="BrandyStrategy"/> class.
	/// </summary>
	public BrandyStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
		.SetGreaterThanZero()
		.SetDisplay("Trade Volume", "Order size in lots", "General");

		_stopLossPips = Param(nameof(StopLossPips), 50m)
		.SetNotNegative()
		.SetDisplay("Stop Loss (pips)", "Protective stop distance", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 150m)
		.SetNotNegative()
		.SetDisplay("Take Profit (pips)", "Profit target distance", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 5m)
		.SetNotNegative()
		.SetDisplay("Trailing Stop (pips)", "Distance for trailing stop", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
		.SetNotNegative()
		.SetDisplay("Trailing Step (pips)", "Additional move required before trailing", "Risk");

		_maClosePeriod = Param(nameof(MaClosePeriod), 20)
		.SetGreaterThanZero()
		.SetDisplay("MA Close Period", "Length of MA calculated on close", "Indicators");

		_maCloseShift = Param(nameof(MaCloseShift), 0)
		.SetNotNegative()
		.SetDisplay("MA Close Shift", "Forward shift applied to close MA", "Indicators");

		_maCloseMethod = Param(nameof(MaCloseMethod), MovingAverageMethods.Ema)
		.SetDisplay("MA Close Method", "Smoothing method for close MA", "Indicators");

		_maCloseAppliedPrice = Param(nameof(MaCloseAppliedPrice), AppliedPriceTypes.Close)
		.SetDisplay("MA Close Price", "Price source for close MA", "Indicators");

		_maCloseSignalBar = Param(nameof(MaCloseSignalBar), 0)
		.SetNotNegative()
		.SetDisplay("MA Close Signal Bar", "Reference bar index for close MA", "Indicators");

		_maOpenPeriod = Param(nameof(MaOpenPeriod), 70)
		.SetGreaterThanZero()
		.SetDisplay("MA Open Period", "Length of MA calculated on open", "Indicators");

		_maOpenShift = Param(nameof(MaOpenShift), 0)
		.SetNotNegative()
		.SetDisplay("MA Open Shift", "Forward shift applied to open MA", "Indicators");

		_maOpenMethod = Param(nameof(MaOpenMethod), MovingAverageMethods.Ema)
		.SetDisplay("MA Open Method", "Smoothing method for open MA", "Indicators");

		_maOpenAppliedPrice = Param(nameof(MaOpenAppliedPrice), AppliedPriceTypes.Close)
		.SetDisplay("MA Open Price", "Price source for open MA", "Indicators");

		_maOpenSignalBar = Param(nameof(MaOpenSignalBar), 0)
		.SetNotNegative()
		.SetDisplay("MA Open Signal Bar", "Reference bar index for open MA", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Time frame of input candles", "General");

		Volume = _tradeVolume.Value;
	}

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

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

		_maOpenIndicator = null;
		_maCloseIndicator = null;
		_maOpenValues.Clear();
		_maCloseValues.Clear();
		_pipSize = 0m;
		_maxOpenQueueSize = 0;
		_maxCloseQueueSize = 0;
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
	}

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

		if (TrailingStopPips > 0m && TrailingStepPips <= 0m)
		throw new InvalidOperationException("Trailing step must be greater than zero when trailing stop is enabled.");

		_maOpenIndicator = CreateMovingAverage(MaOpenMethod, MaOpenPeriod);
		_maCloseIndicator = CreateMovingAverage(MaCloseMethod, MaClosePeriod);

		UpdatePipSize();
		UpdateQueueSizes();

		Volume = _tradeVolume.Value;

		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;

		if (_maOpenIndicator == null || _maCloseIndicator == null)
		return;

		var openSource = GetAppliedPrice(candle, MaOpenAppliedPrice);
		var closeSource = GetAppliedPrice(candle, MaCloseAppliedPrice);

		var maOpenResult = _maOpenIndicator!.Process(new DecimalIndicatorValue(_maOpenIndicator, openSource, candle.OpenTime) { IsFinal = true });
		var maCloseResult = _maCloseIndicator!.Process(new DecimalIndicatorValue(_maCloseIndicator, closeSource, candle.OpenTime) { IsFinal = true });

		if (maOpenResult.IsEmpty || maCloseResult.IsEmpty || !_maOpenIndicator.IsFormed || !_maCloseIndicator.IsFormed)
			return;

		var maOpen = maOpenResult.ToDecimal();
		var maClose = maCloseResult.ToDecimal();

		EnqueueValue(_maOpenValues, maOpen, _maxOpenQueueSize);
		EnqueueValue(_maCloseValues, maClose, _maxCloseQueueSize);

		var maOpenPrev = GetQueueValue(_maOpenValues, 1 + MaOpenShift);
		var maOpenSignal = GetQueueValue(_maOpenValues, MaOpenSignalBar + MaOpenShift);
		var maClosePrev = GetQueueValue(_maCloseValues, 1 + MaCloseShift);
		var maCloseSignal = GetQueueValue(_maCloseValues, MaCloseSignalBar + MaCloseShift);

		if (maOpenPrev is null || maOpenSignal is null || maClosePrev is null || maCloseSignal is null)
		return;

		var longSignal = maOpenPrev > maOpenSignal && maClosePrev > maCloseSignal;
		var shortSignal = maOpenPrev < maOpenSignal && maClosePrev < maCloseSignal;

		if (Position == 0)
		{
			if (longSignal)
			{
				OpenLong(candle.ClosePrice);
			}
			else if (shortSignal)
			{
				OpenShort(candle.ClosePrice);
			}
		}
		else
		{
			ManageOpenPosition(candle, maOpenPrev.Value, maOpenSignal.Value);
		}
	}

	private void OpenLong(decimal price)
	{
		var volume = Volume;
		if (volume <= 0m)
		return;

		_entryPrice = price;
		_stopPrice = StopLossPips > 0m ? price - StopLossPips * _pipSize : null;
		_takePrice = TakeProfitPips > 0m ? price + TakeProfitPips * _pipSize : null;

		BuyMarket(volume);
	}

	private void OpenShort(decimal price)
	{
		var volume = Volume;
		if (volume <= 0m)
		return;

		_entryPrice = price;
		_stopPrice = StopLossPips > 0m ? price + StopLossPips * _pipSize : null;
		_takePrice = TakeProfitPips > 0m ? price - TakeProfitPips * _pipSize : null;

		SellMarket(volume);
	}

	private void ManageOpenPosition(ICandleMessage candle, decimal maOpenPrev, decimal maOpenSignal)
	{
		var position = Position;

		if (position > 0)
		{
			if (maOpenPrev < maOpenSignal)
			{
				SellMarket(position);
				ResetPositionState();
				return;
			}

			UpdateTrailingForLong(candle);

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				SellMarket(position);
				ResetPositionState();
				return;
			}

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket(position);
				ResetPositionState();
			}
		}
		else if (position < 0)
		{
			if (maOpenPrev > maOpenSignal)
			{
				BuyMarket(-position);
				ResetPositionState();
				return;
			}

			UpdateTrailingForShort(candle);

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				BuyMarket(-position);
				ResetPositionState();
				return;
			}

			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				BuyMarket(-position);
				ResetPositionState();
			}
		}
		else
		{
			ResetPositionState();
		}
	}

	private void UpdateTrailingForLong(ICandleMessage candle)
	{
		if (TrailingStopPips <= 0m || TrailingStepPips <= 0m || _entryPrice is null)
		return;

		var trailingStop = TrailingStopPips * _pipSize;
		var trailingStep = TrailingStepPips * _pipSize;

		if (trailingStop <= 0m)
		return;

		var currentPrice = candle.ClosePrice;
		var entryPrice = _entryPrice.Value;

		if (currentPrice - entryPrice <= trailingStop + trailingStep)
		return;

		var threshold = currentPrice - (trailingStop + trailingStep);

		if (_stopPrice.HasValue && _stopPrice.Value >= threshold)
		return;

		var newStop = currentPrice - trailingStop;
		if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
		_stopPrice = newStop;
	}

	private void UpdateTrailingForShort(ICandleMessage candle)
	{
		if (TrailingStopPips <= 0m || TrailingStepPips <= 0m || _entryPrice is null)
		return;

		var trailingStop = TrailingStopPips * _pipSize;
		var trailingStep = TrailingStepPips * _pipSize;

		if (trailingStop <= 0m)
		return;

		var currentPrice = candle.ClosePrice;
		var entryPrice = _entryPrice.Value;

		if (entryPrice - currentPrice <= trailingStop + trailingStep)
		return;

		var threshold = currentPrice + trailingStop + trailingStep;

		if (_stopPrice.HasValue && _stopPrice.Value <= threshold)
		return;

		var newStop = currentPrice + trailingStop;
		if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
		_stopPrice = newStop;
	}

	private void ResetPositionState()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
	}

	private void UpdatePipSize()
	{
		var step = Security?.PriceStep ?? 1m;
		_pipSize = step;
	}

	private void UpdateQueueSizes()
	{
		var shiftOpen = Math.Max(0, MaOpenShift);
		var shiftClose = Math.Max(0, MaCloseShift);
		var openDepth = Math.Max(Math.Max(1 + shiftOpen, MaOpenSignalBar + shiftOpen), MaCloseSignalBar + shiftOpen);
		var closeDepth = Math.Max(1 + shiftClose, 1);

		_maxOpenQueueSize = Math.Max(2, openDepth + 2);
		_maxCloseQueueSize = Math.Max(2, closeDepth + 2);
	}

	private static void EnqueueValue(List<decimal> queue, decimal value, int maxSize)
	{
		queue.Add(value);

		while (queue.Count > maxSize)
			queue.RemoveAt(0);
	}

	private static decimal? GetQueueValue(List<decimal> queue, int indexFromCurrent)
	{
		if (indexFromCurrent < 0)
			return null;

		if (queue.Count <= indexFromCurrent)
			return null;

		var targetIndex = queue.Count - 1 - indexFromCurrent;

		return targetIndex >= 0 && targetIndex < queue.Count
			? queue[targetIndex]
			: null;
	}

	private static DecimalLengthIndicator CreateMovingAverage(MovingAverageMethods method, int length)
	{
		return method switch
		{
		MovingAverageMethods.Sma => new SimpleMovingAverage { Length = length },
		MovingAverageMethods.Ema => new ExponentialMovingAverage { Length = length },
		MovingAverageMethods.Smma => new SmoothedMovingAverage { Length = length },
		MovingAverageMethods.Lwma => new WeightedMovingAverage { Length = length },
		_ => new ExponentialMovingAverage { Length = length }
		};
	}

	private static decimal GetAppliedPrice(ICandleMessage candle, AppliedPriceTypes priceType)
	{
		return priceType switch
		{
			AppliedPriceTypes.Close => candle.ClosePrice,
			AppliedPriceTypes.Open => candle.OpenPrice,
			AppliedPriceTypes.High => candle.HighPrice,
			AppliedPriceTypes.Low => candle.LowPrice,
			AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			AppliedPriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			AppliedPriceTypes.Weighted => (candle.HighPrice + candle.LowPrice + candle.ClosePrice + candle.ClosePrice) / 4m,
			_ => candle.ClosePrice
		};
	}
}