Auf GitHub ansehen

Bollinger Bands RSI Zonen-Strategie

Ein Multi-Band-Bollinger-Breakout-System, konvertiert aus dem MetaTrader-Experten «Bollinger Bands RSI». Die Strategie leitet drei Bollinger-Hüllen mit identischen Perioden, aber unterschiedlichen Abweichungen ab, um «gelbe», «blaue» und «rote» Bänder zu erstellen. Orders werden ausgelöst, wenn der Preis konfigurierbare Zonen rund um diese Bänder erneut testet, optional bestätigt durch RSI- und Stochastik-Filter.

Strategielogik

  • Das primäre (gelbe) Band verwendet den konfigurierten Abweichungsmultiplikator.
  • Das blaue Band halbiert die Abweichung und erstellt eine engere Hülle.
  • Das rote Band verdoppelt die Abweichung und produziert eine breite äußere Hülle.
  • RSI- und Stochastik-Werte werden auf der vorherigen fertigen Kerze (Bar Shift) ausgewertet, um das ursprüngliche EA-Verhalten zu replizieren.
  • Only One Position kontrolliert, ob neue Orders nur erlaubt sind, wenn die Nettoposition flat ist, oder ob zusätzliche Skalierungsgeschäfte erlaubt sind, sobald der Preis zur Bollinger-Mittellinie zurückkehrt.

Einstiegskriterien

Long-Einstiege

  1. Der Preis auf der aktuellen Kerze fällt auf oder unter die ausgewählte Long-Einstiegszone (Entry Mode):
    • Mittelpunkt zwischen gelb und blau, blau und rot, oder eines der einzelnen Bänder.
  2. Optionale Bestätigungen:
    • RSI-Filter: RSI ≤ 100 - RSI Lower.
    • Stochastik-Filter: %K < 100 - Stochastic Lower.
  3. Positionsvoraussetzungen:
    • Wenn Only One Position aktiviert ist, muss die Nettoposition flat sein.
    • Andernfalls werden zusätzliche Long-Orders blockiert, bis die Kerze über dem mittleren (gelben) Band schließt, was die EA-Sperrlogik emuliert.

Short-Einstiege

  1. Der Preis auf der aktuellen Kerze steigt auf oder über die ausgewählte Short-Einstiegszone (spiegelt die Long-Optionen).
  2. Optionale Bestätigungen:
    • RSI-Filter: RSI ≥ RSI Lower.
    • Stochastik-Filter: %K > Stochastic Lower.
  3. Positionsvoraussetzungen spiegeln die Long-Logik (flat Position für Einzelhandels-Modus oder entsperrter Zustand, sobald die Kerze unter dem mittleren Band schließt).

Ausstiegskriterien

  • Der Schließmodus wird durch Closure Mode bestimmt:
    • Middle Line: Longs aussteigen, wenn der Preis das Bollinger-Mittelband erreicht; Shorts aussteigen, wenn der Preis es von oben berührt.
    • Between Yellow and Blue / Between Blue and Red: Aussteigen an denselben Mittelpunkten, die für Einstiege verwendet werden; standardmäßig an Mittelpunkten zwischen blau und rot, wenn der Eintrittsmodus abweicht.
    • Yellow Line, Blue Line, Red Line: Aussteigen bei direkten Berührungen der entsprechenden oberen/unteren Bänder.
  • Sperr-Flags für den Skalierungsmodus werden automatisch zurückgesetzt, wenn die Kerze auf der anderen Seite des mittleren Bandes schließt, und recreaten das EA-Verhalten.

Risikomanagement

  • Stop Loss- und Take Profit-Parameter werden in Pips ausgedrückt und über Pip Value in absolute Preisabstände umgewandelt, wenn StartProtection initialisiert wird.
  • Stops und Ziele sind optional; lassen Sie den Abstand bei null, um den entsprechenden Schutz zu deaktivieren.
  • Das Handelsvolumen wird durch Order Volume definiert und auf jede Marktorder angewendet.

Parameter

Name Beschreibung Standard
Entry Mode Wählt die Bollinger-Zone, die Einstiege auslöst. Zwischen gelb und blau
Closure Mode Definiert das gewinnerzielende Band oder den Mittelpunkt. Zwischen blau und rot
Bands Period Periodenlänge, die alle Bollinger Bands teilen. 140
Deviation Standardabweichungsmultiplikator für das gelbe Band (blau ist halb, rot ist doppelt). 2.0
Use RSI Filter Aktiviert die RSI-Bestätigungslogik. false
RSI Period RSI-Mittelungsperiode. 8
RSI Lower Überkauft-Schwelle; überverkauft verwendet 100 - Wert. 70
Use Stochastic Filter Aktiviert die %K-Bestätigungslogik. true
Stochastic Period Haupt-%K-Rückblickperiode (Glättung fest auf 3/3 SMA). 20
Stochastic Lower Überkauft-Schwelle; überverkauft verwendet 100 - Wert. 95
Bar Shift Anzahl der fertigen Bars für Indikatorwerte. 1
Only One Position Wenn aktiviert, werden neue Trades nur geöffnet, wenn keine Position aktiv ist. true
Order Volume Volumen, das mit jeder Marktorder gesendet wird. 1
Pip Value Absoluter Preiswert eines Pips für die Stop/Ziel-Konvertierung. 0.0001
Stop Loss Schützender Stop-Abstand in Pips (0 deaktiviert). 200
Take Profit Schützender Ziel-Abstand in Pips (0 deaktiviert). 200
Candle Type Datentyp für Berechnungen (Standard-1-Minuten-Kerzen). 1m Zeitrahmen

Hinweise

  • Die Strategie verarbeitet nur abgeschlossene Kerzen, daher sollte Bar Shift ≥ 1 bleiben, um Referenzen auf unfertige Bars zu vermeiden.
  • RSI- und Stochastik-Filter verwenden die %K-Linie; die %D-Linie wird berechnet, aber nicht verwendet, was die ursprüngliche EA-Implementierung widerspiegelt.
  • Die Konvertierung hält Kommentare und Signalnamen auf Englisch und folgt den StockSharp-High-Level-API-Richtlinien (Bind-basierte Indikator-Pipeline, kein manueller Pufferzugriff).
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 { }
	}
}