View on GitHub

Bollinger Bands RSI Zones Strategy

A multi-band Bollinger breakout system converted from the "Bollinger Bands RSI" MetaTrader expert advisor. The strategy derives three Bollinger envelopes with identical periods but different deviations to create "yellow", "blue", and "red" bands. Orders are triggered when price revisits configurable zones around these bands, optionally confirmed by RSI and Stochastic filters.

Strategy Logic

  • The primary (yellow) band uses the configured deviation multiplier.
  • The blue band halves the deviation, creating a narrower envelope.
  • The red band doubles the deviation, producing a wide outer envelope.
  • RSI and Stochastic values are evaluated on the previous finished candle (Bar Shift) to match the original EA behaviour.
  • Only One Position controls whether fresh orders are allowed only when the net position is flat or if additional scaling trades are permitted once price returns to the Bollinger middle line.

Entry Rules

Long entries

  1. Price on the current candle falls to or below the selected long entry zone (Entry Mode):
    • Midpoint between yellow & blue, blue & red, or one of the individual bands.
  2. Optional confirmations:
    • RSI filter: RSI ≤ 100 - RSI Lower.
    • Stochastic filter: %K < 100 - Stochastic Lower.
  3. Position prerequisites:
    • If Only One Position is enabled, the net position must be flat.
    • Otherwise, additional long orders are blocked until the candle closes above the middle (yellow) band, emulating the EA locking logic.

Short entries

  1. Price on the current candle rallies to or above the selected short entry zone (mirrors the long options).
  2. Optional confirmations:
    • RSI filter: RSI ≥ RSI Lower.
    • Stochastic filter: %K > Stochastic Lower.
  3. Position prerequisites mirror the long logic (flat position for single-trade mode or unlocked state once the candle closes back below the middle band).

Exit Rules

  • Closing mode is determined by Closure Mode:
    • Middle Line: exit longs when price reaches the Bollinger middle band; exit shorts when price touches it from above.
    • Between Yellow and Blue / Between Blue and Red: exit at the same midpoints used for entries; defaults to midpoints between blue and red when entry mode differs.
    • Yellow Line, Blue Line, Red Line: exit on direct touches of the corresponding upper/lower bands.
  • Lock flags for scaling mode are reset automatically when the candle closes on the opposite side of the middle band, recreating the EA behaviour.

Risk Management

  • Stop Loss and Take Profit parameters are expressed in pips and converted to absolute price distances through Pip Value when StartProtection is initialised.
  • Stops and targets are optional; leave the distance at zero to disable the respective protection leg.
  • Trade volume is defined by Order Volume and applied to every market order.

Parameters

Name Description Default
Entry Mode Chooses the Bollinger zone that triggers entries. Between yellow & blue
Closure Mode Defines the profit-taking band or midpoint. Between blue & red
Bands Period Period length shared by all Bollinger bands. 140
Deviation Standard deviation multiplier for the yellow band (blue is half, red is double). 2.0
Use RSI Filter Enables RSI confirmation logic. false
RSI Period RSI averaging period. 8
RSI Lower Overbought threshold; oversold uses 100 - value. 70
Use Stochastic Filter Enables %K confirmation logic. true
Stochastic Period Main %K lookback period (smoothing is fixed at 3/3 SMA). 20
Stochastic Lower Overbought threshold; oversold uses 100 - value. 95
Bar Shift Number of finished bars to look back for indicator values. 1
Only One Position If enabled, opens new trades only when no position is active. true
Order Volume Volume submitted with each market order. 1
Pip Value Absolute price value of one pip for stop/target conversion. 0.0001
Stop Loss Protective stop distance in pips (0 disables). 200
Take Profit Protective target distance in pips (0 disables). 200
Candle Type Data type used for calculations (default 1-minute candles). 1m time-frame

Notes

  • The strategy processes only completed candles, so Bar Shift should remain ≥ 1 to avoid referencing unfinished bars.
  • RSI and Stochastic filters use the %K line; the %D line is calculated but not used, mirroring the original EA implementation.
  • The conversion keeps comments and signal names in English and follows the StockSharp high-level API guidelines (Bind-based indicator pipeline, no manual buffer access).
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bollinger Bands based strategy with optional RSI and Stochastic filters.
/// Replicates the Bollinger Bands RSI expert advisor logic with configurable entry and exit zones.
/// </summary>
public class BollingerBandsRsiZonesStrategy : Strategy
{
	/// <summary>
	/// Entry location for Bollinger Bands RSI strategy.
	/// </summary>
	public enum BollingerBandsRsiEntryModes
	{
		/// <summary>
		/// Midpoint between yellow (primary) and blue (narrow) bands.
		/// </summary>
		BetweenYellowAndBlue,

		/// <summary>
		/// Midpoint between blue (narrow) and red (wide) bands.
		/// </summary>
		BetweenBlueAndRed,

		/// <summary>
		/// Yellow band itself.
		/// </summary>
		YellowLine,

		/// <summary>
		/// Blue band (narrow deviation).
		/// </summary>
		BlueLine,

		/// <summary>
		/// Red band (wide deviation).
		/// </summary>
		RedLine
	}

	/// <summary>
	/// Exit location for Bollinger Bands RSI strategy.
	/// </summary>
	public enum BollingerBandsRsiClosureModes
	{
		/// <summary>
		/// Exit on the middle Bollinger band.
		/// </summary>
		MiddleLine,

		/// <summary>
		/// Exit between yellow and blue bands.
		/// </summary>
		BetweenYellowAndBlue,

		/// <summary>
		/// Exit between blue and red bands.
		/// </summary>
		BetweenBlueAndRed,

		/// <summary>
		/// Exit on the yellow band.
		/// </summary>
		YellowLine,

		/// <summary>
		/// Exit on the blue band.
		/// </summary>
		BlueLine,

		/// <summary>
		/// Exit on the red band.
		/// </summary>
		RedLine
	}
	private readonly StrategyParam<BollingerBandsRsiEntryModes> _entryMode;
	private readonly StrategyParam<BollingerBandsRsiClosureModes> _closureMode;
	private readonly StrategyParam<int> _bandsPeriod;
	private readonly StrategyParam<decimal> _deviation;
	private readonly StrategyParam<bool> _useRsiFilter;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiLowerLevel;
	private readonly StrategyParam<bool> _useStochasticFilter;
	private readonly StrategyParam<int> _stochasticPeriod;
	private readonly StrategyParam<decimal> _stochasticLowerLevel;
	private readonly StrategyParam<int> _barShift;
	private readonly StrategyParam<bool> _onlyOnePosition;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<decimal> _pipValue;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<DataType> _candleType;

	private BollingerBands _teeth = null!;
	private BollingerBands _jaws = null!;
	private BollingerBands _lips = null!;
	private RelativeStrengthIndex _rsi = null!;
	private StochasticOscillator _stochastic = null!;

	private readonly List<decimal> _teethMiddleHistory = new();
	private readonly List<decimal> _teethUpperHistory = new();
	private readonly List<decimal> _teethLowerHistory = new();
	private readonly List<decimal> _jawsUpperHistory = new();
	private readonly List<decimal> _jawsLowerHistory = new();
	private readonly List<decimal> _lipsUpperHistory = new();
	private readonly List<decimal> _lipsLowerHistory = new();
	private readonly List<decimal> _rsiHistory = new();
	private readonly List<decimal> _stochasticHistory = new();

	private bool _longLocked;
	private bool _shortLocked;

	/// <summary>
	/// Entry zone selection.
	/// </summary>
	public BollingerBandsRsiEntryModes EntryMode
	{
		get => _entryMode.Value;
		set => _entryMode.Value = value;
	}

	/// <summary>
	/// Exit zone selection.
	/// </summary>
	public BollingerBandsRsiClosureModes ClosureMode
	{
		get => _closureMode.Value;
		set => _closureMode.Value = value;
	}

	/// <summary>
	/// Bollinger period for all bands.
	/// </summary>
	public int BandsPeriod
	{
		get => _bandsPeriod.Value;
		set => _bandsPeriod.Value = value;
	}

	/// <summary>
	/// Standard deviation multiplier for the primary (yellow) band.
	/// </summary>
	public decimal Deviation
	{
		get => _deviation.Value;
		set => _deviation.Value = value;
	}

	/// <summary>
	/// Enable RSI filter.
	/// </summary>
	public bool UseRsiFilter
	{
		get => _useRsiFilter.Value;
		set => _useRsiFilter.Value = value;
	}

	/// <summary>
	/// RSI averaging period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI short threshold (long threshold is mirrored from 100).
	/// </summary>
	public decimal RsiLowerLevel
	{
		get => _rsiLowerLevel.Value;
		set => _rsiLowerLevel.Value = value;
	}

	/// <summary>
	/// Enable Stochastic filter.
	/// </summary>
	public bool UseStochasticFilter
	{
		get => _useStochasticFilter.Value;
		set => _useStochasticFilter.Value = value;
	}

	/// <summary>
	/// Stochastic main period.
	/// </summary>
	public int StochasticPeriod
	{
		get => _stochasticPeriod.Value;
		set => _stochasticPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic overbought level (long threshold is mirrored from 100).
	/// </summary>
	public decimal StochasticLowerLevel
	{
		get => _stochasticLowerLevel.Value;
		set => _stochasticLowerLevel.Value = value;
	}

	/// <summary>
	/// Number of finished bars used for indicator shift.
	/// </summary>
	public int BarShift
	{
		get => _barShift.Value;
		set => _barShift.Value = value;
	}

	/// <summary>
	/// Allow only one open position at a time.
	/// </summary>
	public bool OnlyOnePosition
	{
		get => _onlyOnePosition.Value;
		set => _onlyOnePosition.Value = value;
	}

	/// <summary>
	/// Trading volume for new orders.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Value of one pip in price units.
	/// </summary>
	public decimal PipValue
	{
		get => _pipValue.Value;
		set => _pipValue.Value = value;
	}

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

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

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

	/// <summary>
	/// Initializes a new instance of the <see cref="BollingerBandsRsiZonesStrategy"/> class.
	/// </summary>
	public BollingerBandsRsiZonesStrategy()
	{
		_entryMode = Param(nameof(EntryMode), BollingerBandsRsiEntryModes.BetweenYellowAndBlue)
			.SetDisplay("Entry Mode", "Bollinger zone used for entries", "Trading");

		_closureMode = Param(nameof(ClosureMode), BollingerBandsRsiClosureModes.BetweenBlueAndRed)
			.SetDisplay("Closure Mode", "Bollinger zone used for exits", "Trading");

		_bandsPeriod = Param(nameof(BandsPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bands Period", "Length of all Bollinger bands", "Indicators")
			;

		_deviation = Param(nameof(Deviation), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Deviation", "Standard deviation for yellow band", "Indicators")
			;

		_useRsiFilter = Param(nameof(UseRsiFilter), false)
			.SetDisplay("Use RSI Filter", "Enable RSI confirmation", "Filters");

		_rsiPeriod = Param(nameof(RsiPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "Length of RSI filter", "Filters")
			;

		_rsiLowerLevel = Param(nameof(RsiLowerLevel), 70m)
			.SetDisplay("RSI Lower", "Short threshold (long uses 100-threshold)", "Filters")
			;

		_useStochasticFilter = Param(nameof(UseStochasticFilter), false)
			.SetDisplay("Use Stochastic Filter", "Enable Stochastic confirmation", "Filters");

		_stochasticPeriod = Param(nameof(StochasticPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Period", "Main %K period", "Filters")
			;

		_stochasticLowerLevel = Param(nameof(StochasticLowerLevel), 95m)
			.SetDisplay("Stochastic Lower", "Overbought threshold (long uses mirror)", "Filters")
			;

		_barShift = Param(nameof(BarShift), 1)
			.SetGreaterThanZero()
			.SetDisplay("Bar Shift", "Number of finished bars for signals", "Trading");

		_onlyOnePosition = Param(nameof(OnlyOnePosition), true)
			.SetDisplay("Only One Position", "Restrict to single open position", "Risk");

		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Volume sent with each market order", "Trading");

		_pipValue = Param(nameof(PipValue), 0.0001m)
			.SetGreaterThanZero()
			.SetDisplay("Pip Value", "Monetary value of one pip", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 200m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop loss distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 200m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take profit distance in pips", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles for analysis", "General");
	}

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

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

		_teethMiddleHistory.Clear();
		_teethUpperHistory.Clear();
		_teethLowerHistory.Clear();
		_jawsUpperHistory.Clear();
		_jawsLowerHistory.Clear();
		_lipsUpperHistory.Clear();
		_lipsLowerHistory.Clear();
		_rsiHistory.Clear();
		_stochasticHistory.Clear();
		_longLocked = false;
		_shortLocked = false;
	}

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

		Volume = OrderVolume;

		_teeth = new BollingerBands
		{
			Length = BandsPeriod,
			Width = Deviation
		};

		_jaws = new BollingerBands
		{
			Length = BandsPeriod,
			Width = Deviation / 2m
		};

		_lips = new BollingerBands
		{
			Length = BandsPeriod,
			Width = Deviation * 2m
		};

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		_stochastic = new StochasticOscillator
		{
			K = { Length = StochasticPeriod },
			D = { Length = 3 }
		};

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

		var pipSize = Security?.PriceStep ?? PipValue;
		var take = TakeProfitPips > 0m ? new Unit(TakeProfitPips * pipSize, UnitTypes.Absolute) : null;
		var stop = StopLossPips > 0m ? new Unit(StopLossPips * pipSize, UnitTypes.Absolute) : null;

		if (take != null || stop != null)
			StartProtection(takeProfit: take, stopLoss: stop);
	}

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

		// Process other indicators manually.
		var teethResult = _teeth.Process(new DecimalIndicatorValue(_teeth, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
		var jawsResult = _jaws.Process(new DecimalIndicatorValue(_jaws, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
		var lipsResult = _lips.Process(new DecimalIndicatorValue(_lips, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
		var stochResult = _stochastic.Process(new CandleIndicatorValue(_stochastic, candle) { IsFinal = true });

		if (!_teeth.IsFormed || !_jaws.IsFormed || !_lips.IsFormed)
			return;

		var teethBB = (BollingerBandsValue)teethResult;
		var jawsBB = (BollingerBandsValue)jawsResult;
		var lipsBB = (BollingerBandsValue)lipsResult;

		var teethMiddle = teethBB.MovingAverage ?? 0m;
		var teethUpper = teethBB.UpBand ?? 0m;
		var teethLower = teethBB.LowBand ?? 0m;
		var jawsUpper = jawsBB.UpBand ?? 0m;
		var jawsLower = jawsBB.LowBand ?? 0m;
		var lipsUpper = lipsBB.UpBand ?? 0m;
		var lipsLower = lipsBB.LowBand ?? 0m;

		var rsiValue = rsiDecimal;
		var stochTyped = (StochasticOscillatorValue)stochResult;
		var stochasticK = stochTyped.K ?? 50m;

		var rsiReady = !UseRsiFilter || _rsi.IsFormed;
		var stochasticReady = !UseStochasticFilter || _stochastic.IsFormed;

		if (!rsiReady || !stochasticReady)
		{
			UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
			return;
		}

		if (!TryGetShifted(_teethMiddleHistory, out var baseTeeth) ||
			!TryGetShifted(_teethUpperHistory, out var upperTeeth) ||
			!TryGetShifted(_teethLowerHistory, out var lowerTeeth) ||
			!TryGetShifted(_jawsUpperHistory, out var upperJaws) ||
			!TryGetShifted(_jawsLowerHistory, out var lowerJaws) ||
			!TryGetShifted(_lipsUpperHistory, out var upperLips) ||
			!TryGetShifted(_lipsLowerHistory, out var lowerLips))
		{
			UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
			return;
		}

		decimal rsiShifted = 50m;
		if (UseRsiFilter)
		{
			if (!TryGetShifted(_rsiHistory, out rsiShifted))
			{
				UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
				return;
			}
		}

		decimal stochasticShifted = 50m;
		if (UseStochasticFilter)
		{
			if (!TryGetShifted(_stochasticHistory, out stochasticShifted))
			{
				UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
				return;
			}
		}

		// All indicators checked above via IsFormed.

		var longEntryPrice = GetLongEntryPrice(lowerTeeth, lowerJaws, lowerLips);
		var shortEntryPrice = GetShortEntryPrice(upperTeeth, upperJaws, upperLips);

		var (exitLong, exitShort) = GetExitLevels(shortEntryPrice, longEntryPrice, upperJaws, lowerJaws, upperLips, lowerLips);

		if (!OnlyOnePosition)
		{
			if (candle.ClosePrice >= baseTeeth)
				_longLocked = false;

			if (candle.ClosePrice <= baseTeeth)
				_shortLocked = false;
		}

		var priceHitLong = candle.LowPrice <= longEntryPrice;
		var priceHitShort = candle.HighPrice >= shortEntryPrice;

		var rsiLongOk = !UseRsiFilter || rsiShifted <= 100m - RsiLowerLevel;
		var rsiShortOk = !UseRsiFilter || rsiShifted >= RsiLowerLevel;

		var stochLongOk = !UseStochasticFilter || stochasticShifted < 100m - StochasticLowerLevel;
		var stochShortOk = !UseStochasticFilter || stochasticShifted > StochasticLowerLevel;

		var canOpenLong = OnlyOnePosition ? Position == 0m : Position >= 0m;
		var canOpenShort = OnlyOnePosition ? Position == 0m : Position <= 0m;

		if (priceHitShort && rsiShortOk && stochShortOk && canOpenShort)
		{
			if (OnlyOnePosition || !_shortLocked)
			{
				// Sell when price reaches the selected upper band zone and filters confirm overbought state.
				SellMarket();
				_shortLocked = !OnlyOnePosition;
			}
		}

		if (priceHitLong && rsiLongOk && stochLongOk && canOpenLong)
		{
			if (OnlyOnePosition || !_longLocked)
			{
				// Buy when price reaches the selected lower band zone and filters confirm oversold state.
				BuyMarket();
				_longLocked = !OnlyOnePosition;
			}
		}

		// Exit logic mirrors the original EA: close longs on selected upper zone, shorts on selected lower zone.
		switch (ClosureMode)
		{
			case BollingerBandsRsiClosureModes.MiddleLine:
				if (Position > 0m && candle.HighPrice >= baseTeeth)
					SellMarket();

				if (Position < 0m && candle.LowPrice <= baseTeeth)
					BuyMarket();
				break;

			case BollingerBandsRsiClosureModes.BetweenYellowAndBlue:
			case BollingerBandsRsiClosureModes.BetweenBlueAndRed:
				if (Position > 0m && candle.HighPrice >= exitLong)
					SellMarket();

				if (Position < 0m && candle.LowPrice <= exitShort)
					BuyMarket();
				break;

			case BollingerBandsRsiClosureModes.YellowLine:
				if (Position > 0m && candle.HighPrice >= upperTeeth)
					SellMarket();

				if (Position < 0m && candle.LowPrice <= lowerTeeth)
					BuyMarket();
				break;

			case BollingerBandsRsiClosureModes.BlueLine:
				if (Position > 0m && candle.HighPrice >= upperJaws)
					SellMarket();

				if (Position < 0m && candle.LowPrice <= lowerJaws)
					BuyMarket();
				break;

			case BollingerBandsRsiClosureModes.RedLine:
				if (Position > 0m && candle.HighPrice >= upperLips)
					SellMarket();

				if (Position < 0m && candle.LowPrice <= lowerLips)
					BuyMarket();
				break;
		}

		UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
	}

	private decimal GetLongEntryPrice(decimal lowerTeeth, decimal lowerJaws, decimal lowerLips)
	{
		return EntryMode switch
		{
			BollingerBandsRsiEntryModes.BetweenYellowAndBlue => lowerTeeth - (lowerTeeth - lowerJaws) / 2m,
			BollingerBandsRsiEntryModes.BetweenBlueAndRed => lowerJaws - (lowerJaws - lowerLips) / 2m,
			BollingerBandsRsiEntryModes.YellowLine => lowerTeeth,
			BollingerBandsRsiEntryModes.BlueLine => lowerJaws,
			BollingerBandsRsiEntryModes.RedLine => lowerLips,
			_ => lowerTeeth
		};
	}

	private decimal GetShortEntryPrice(decimal upperTeeth, decimal upperJaws, decimal upperLips)
	{
		return EntryMode switch
		{
			BollingerBandsRsiEntryModes.BetweenYellowAndBlue => upperTeeth + (upperJaws - upperTeeth) / 2m,
			BollingerBandsRsiEntryModes.BetweenBlueAndRed => upperJaws + (upperLips - upperJaws) / 2m,
			BollingerBandsRsiEntryModes.YellowLine => upperTeeth,
			BollingerBandsRsiEntryModes.BlueLine => upperJaws,
			BollingerBandsRsiEntryModes.RedLine => upperLips,
			_ => upperTeeth
		};
	}

	private (decimal exitLong, decimal exitShort) GetExitLevels(decimal shortEntryPrice, decimal longEntryPrice, decimal upperJaws, decimal lowerJaws, decimal upperLips, decimal lowerLips)
	{
		if ((ClosureMode == BollingerBandsRsiClosureModes.BetweenYellowAndBlue && EntryMode == BollingerBandsRsiEntryModes.BetweenYellowAndBlue) ||
			(ClosureMode == BollingerBandsRsiClosureModes.BetweenBlueAndRed && EntryMode == BollingerBandsRsiEntryModes.BetweenBlueAndRed))
		{
			return (shortEntryPrice, longEntryPrice);
		}

		var defaultLong = upperJaws + (upperLips - upperJaws) / 2m;
		var defaultShort = lowerJaws - (lowerJaws - lowerLips) / 2m;
		return (defaultLong, defaultShort);
	}

	private bool TryGetShifted(List<decimal> history, out decimal value)
	{
		if (BarShift <= 0)
		{
			value = 0m;
			return false;
		}

		if (history.Count < BarShift)
		{
			value = 0m;
			return false;
		}

		value = history[0];
		return true;
	}

	private void UpdateHistory(
		decimal teethMiddle,
		decimal teethUpper,
		decimal teethLower,
		decimal jawsUpper,
		decimal jawsLower,
		decimal lipsUpper,
		decimal lipsLower,
		decimal rsiValue,
		decimal stochasticK)
	{
		if (BarShift <= 0)
			return;

		Enqueue(_teethMiddleHistory, teethMiddle);
		Enqueue(_teethUpperHistory, teethUpper);
		Enqueue(_teethLowerHistory, teethLower);
		Enqueue(_jawsUpperHistory, jawsUpper);
		Enqueue(_jawsLowerHistory, jawsLower);
		Enqueue(_lipsUpperHistory, lipsUpper);
		Enqueue(_lipsLowerHistory, lipsLower);

		if (_rsi.IsFormed)
			Enqueue(_rsiHistory, rsiValue);

		if (_stochastic.IsFormed)
			Enqueue(_stochasticHistory, stochasticK);
	}

	private void Enqueue(List<decimal> history, decimal value)
	{
		history.Add(value);

		while (history.Count > BarShift)
			try { history.RemoveAt(0); } catch { }
	}
}