在 GitHub 上查看

Bollinger Bands RSI Zones 策略

本策略源自 MetaTrader 顾问 “Bollinger Bands RSI”。使用相同周期但不同标准差的三组布林带:黄色带为基准,蓝色带的偏差缩小一半,红色带的偏差扩大一倍。价格回落到指定区域时触发交易,并可叠加 RSI 与随机指标过滤器。

策略逻辑

  • 黄色带使用 Deviation 设置的标准差倍数。
  • 蓝色带使用一半倍数,形成较窄的通道;红色带使用两倍倍数,形成更宽的外层通道。
  • RSI 与随机指标的数值基于前一根已完成的 K 线(Bar Shift),以保持与原始 EA 一致。
  • Only One Position 控制是否只在空仓时开新单,或者允许在价格回到布林带中轨后继续加仓。

入场规则

多头

  1. 当前 K 线价格下探至 Entry Mode 指定的买入区域:
    • 黄色与蓝色之间的中点、蓝色与红色之间的中点,或直接接触其中一条带。
  2. 可选过滤条件:
    • RSI:RSI ≤ 100 - RSI Lower
    • 随机指标:%K < 100 - Stochastic Lower
  3. 持仓条件:
    • Only One Position = true 时必须在空仓状态下入场。
    • 允许加仓时,只有当 K 线收盘价高于布林中轨后锁定才会被解除。

空头

  1. 当前 K 线价格上冲至 Entry Mode 指定的卖出区域(与多头对称)。
  2. 可选过滤条件:
    • RSI:RSI ≥ RSI Lower
    • 随机指标:%K > Stochastic Lower
  3. 持仓条件与多头相同(空仓或在收盘价跌破中轨后解除锁定)。

出场规则

  • Closure Mode 决定获利/离场的目标:
    • Middle Line:多头触及布林中轨即平仓,空头触及中轨下侧时平仓。
    • Between Yellow and Blue / Between Blue and Red:使用入场时的相同中点;若入场模式不同,则使用蓝红之间的默认中点。
    • Yellow LineBlue LineRed Line:价格触及对应的上/下轨即离场。
  • 在加仓模式下,只要收盘价越过中轨,锁定标志会自动清除,允许新的同向交易。

风险控制

  • Stop LossTake Profit 以点数表示,在 StartProtection 初始化时通过 Pip Value 转换成绝对价格距离。
  • 若参数为 0,则不设置相应的止损/止盈。
  • Order Volume 定义每次市价单的交易量。

参数表

参数 说明 默认值
Entry Mode 触发入场的布林带区域。 黄蓝之间
Closure Mode 平仓所用的带或中点。 蓝红之间
Bands Period 所有布林带的周期长度。 140
Deviation 黄色带的标准差倍数(蓝色为一半,红色为两倍)。 2.0
Use RSI Filter 启用 RSI 过滤器。 false
RSI Period RSI 计算周期。 8
RSI Lower 超买阈值(超卖阈值 = 100 - 值)。 70
Use Stochastic Filter 启用随机指标过滤。 true
Stochastic Period %K 主周期(平滑固定为 3/3 SMA)。 20
Stochastic Lower 超买阈值(超卖阈值 = 100 - 值)。 95
Bar Shift 指标向前回看的完成 K 线数量。 1
Only One Position 仅在空仓时开仓。 true
Order Volume 每次下单的数量。 1
Pip Value 一个点的绝对价格。 0.0001
Stop Loss 止损点数(0 表示关闭)。 200
Take Profit 止盈点数(0 表示关闭)。 200
Candle Type 使用的 K 线类型(默认 1 分钟)。 1 分钟

备注

  • 策略仅处理收盘完成的 K 线,因此 Bar Shift 应保持 ≥ 1,避免引用未完成数据。
  • RSI 与随机指标仅使用 %K 线,%D 线虽然计算但未参与决策,与原 EA 保持一致。
  • 代码使用 StockSharp 高级 API 通过 Bind 订阅指标,未直接访问指标缓冲区,符合仓库的转换要求。
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 { }
	}
}