Estrategia de Zonas RSI de Bollinger Bands
Un sistema de ruptura multi-banda de Bollinger Bands convertido del asesor experto MetaTrader «Bollinger Bands RSI». La estrategia deriva tres envolventes de Bollinger con períodos idénticos pero diferentes desviaciones para crear bandas «amarilla», «azul» y «roja». Las órdenes se activan cuando el precio revisita zonas configurables alrededor de estas bandas, con confirmación opcional de filtros RSI y Estocástico.
Lógica de la estrategia
- La banda principal (amarilla) utiliza el multiplicador de desviación configurado.
- La banda azul reduce a la mitad la desviación, creando una envolvente más estrecha.
- La banda roja duplica la desviación, produciendo una envolvente exterior amplia.
- Los valores de RSI y Estocástico se evalúan en la vela terminada anterior (
Bar Shift) para coincidir con el comportamiento original del EA. Only One Positioncontrola si las nuevas órdenes están permitidas solo cuando la posición neta está plana o si se permiten operaciones de escala adicionales una vez que el precio regresa a la línea media de Bollinger.
Reglas de entrada
Entradas largas
- El precio en la vela actual cae hasta o por debajo de la zona de entrada larga seleccionada (
Entry Mode):- Punto medio entre amarilla y azul, azul y roja, o una de las bandas individuales.
- Confirmaciones opcionales:
- Filtro RSI: RSI ≤
100 - RSI Lower. - Filtro Estocástico: %K <
100 - Stochastic Lower.
- Filtro RSI: RSI ≤
- Requisitos de posición:
- Si
Only One Positionestá habilitado, la posición neta debe estar plana. - De lo contrario, las órdenes largas adicionales están bloqueadas hasta que la vela cierre por encima de la banda media (amarilla), emulando la lógica de bloqueo del EA.
- Si
Entradas cortas
- El precio en la vela actual sube hasta o por encima de la zona de entrada corta seleccionada (refleja las opciones largas).
- Confirmaciones opcionales:
- Filtro RSI: RSI ≥
RSI Lower. - Filtro Estocástico: %K >
Stochastic Lower.
- Filtro RSI: RSI ≥
- Los requisitos de posición reflejan la lógica larga (posición plana para el modo de una sola operación o estado desbloqueado cuando la vela cierra de vuelta por debajo de la banda media).
Reglas de salida
- El modo de cierre está determinado por
Closure Mode:Middle Line: salir de largos cuando el precio alcanza la banda media de Bollinger; salir de cortos cuando el precio la toca desde arriba.Between Yellow and Blue/Between Blue and Red: salir en los mismos puntos medios usados para las entradas; por defecto en los puntos medios entre azul y rojo cuando el modo de entrada difiere.Yellow Line,Blue Line,Red Line: salir en toques directos de las bandas superiores/inferiores correspondientes.
- Los indicadores de bloqueo para el modo de escala se restablecen automáticamente cuando la vela cierra en el lado opuesto de la banda media, recreando el comportamiento del EA.
Gestión de riesgos
- Los parámetros
Stop LossyTake Profitse expresan en pips y se convierten en distancias de precio absolutas a través dePip Valuecuando se inicializaStartProtection. - Los stops y objetivos son opcionales; deje la distancia en cero para deshabilitar la protección respectiva.
- El volumen de la operación está definido por
Order Volumey se aplica a cada orden de mercado.
Parámetros
| Nombre | Descripción | Predeterminado |
|---|---|---|
Entry Mode |
Elige la zona de Bollinger que activa las entradas. | Entre amarilla y azul |
Closure Mode |
Define la banda o punto medio de toma de ganancias. | Entre azul y roja |
Bands Period |
Longitud de período compartida por todas las bandas de Bollinger. | 140 |
Deviation |
Multiplicador de desviación estándar para la banda amarilla (azul es la mitad, roja es el doble). | 2.0 |
Use RSI Filter |
Activa la lógica de confirmación RSI. | false |
RSI Period |
Período de promediado RSI. | 8 |
RSI Lower |
Umbral de sobrecompra; la sobreventa usa 100 - valor. |
70 |
Use Stochastic Filter |
Activa la lógica de confirmación %K. | true |
Stochastic Period |
Período de retroceso principal %K (suavizado fijo en 3/3 SMA). | 20 |
Stochastic Lower |
Umbral de sobrecompra; la sobreventa usa 100 - valor. |
95 |
Bar Shift |
Número de barras terminadas para mirar hacia atrás en busca de valores de indicadores. | 1 |
Only One Position |
Si está habilitado, abre nuevas operaciones solo cuando no hay ninguna posición activa. | true |
Order Volume |
Volumen enviado con cada orden de mercado. | 1 |
Pip Value |
Valor de precio absoluto de un pip para la conversión de stop/objetivo. | 0.0001 |
Stop Loss |
Distancia de stop protector en pips (0 deshabilita). | 200 |
Take Profit |
Distancia de objetivo protector en pips (0 deshabilita). | 200 |
Candle Type |
Tipo de datos usado para los cálculos (por defecto velas de 1 minuto). | Marco temporal de 1m |
Notas
- La estrategia procesa solo velas completadas, por lo que
Bar Shiftdebe permanecer ≥ 1 para evitar referenciar barras sin terminar. - Los filtros RSI y Estocástico usan la línea %K; la línea %D se calcula pero no se usa, reflejando la implementación original del EA.
- La conversión mantiene comentarios y nombres de señales en inglés y sigue las pautas de la API de alto nivel de StockSharp (pipeline de indicadores basado en Bind, sin acceso manual a buffers).
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 { }
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
BollingerBands,
RelativeStrengthIndex,
StochasticOscillator,
CandleIndicatorValue,
)
from indicator_extensions import *
class bollinger_bands_rsi_zones_strategy(Strategy):
"""Bollinger Bands RSI Zones: three Bollinger bands with RSI and Stochastic filters."""
# Entry modes (integer enum replacement):
# 0 = BetweenYellowAndBlue, 1 = BetweenBlueAndRed,
# 2 = YellowLine, 3 = BlueLine, 4 = RedLine
# Closure modes (integer enum replacement):
# 0 = MiddleLine, 1 = BetweenYellowAndBlue, 2 = BetweenBlueAndRed,
# 3 = YellowLine, 4 = BlueLine, 5 = RedLine
def __init__(self):
super(bollinger_bands_rsi_zones_strategy, self).__init__()
self._entry_mode = self.Param("EntryMode", 0) \
.SetDisplay("Entry Mode", "Bollinger zone used for entries", "Trading")
self._closure_mode = self.Param("ClosureMode", 2) \
.SetDisplay("Closure Mode", "Bollinger zone used for exits", "Trading")
self._bands_period = self.Param("BandsPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Bands Period", "Length of all Bollinger bands", "Indicators")
self._deviation = self.Param("Deviation", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Deviation", "Standard deviation for yellow band", "Indicators")
self._use_rsi_filter = self.Param("UseRsiFilter", False) \
.SetDisplay("Use RSI Filter", "Enable RSI confirmation", "Filters")
self._rsi_period = self.Param("RsiPeriod", 8) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "Length of RSI filter", "Filters")
self._rsi_lower_level = self.Param("RsiLowerLevel", 70.0) \
.SetDisplay("RSI Lower", "Short threshold (long uses 100-threshold)", "Filters")
self._use_stochastic_filter = self.Param("UseStochasticFilter", False) \
.SetDisplay("Use Stochastic Filter", "Enable Stochastic confirmation", "Filters")
self._stochastic_period = self.Param("StochasticPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic Period", "Main %K period", "Filters")
self._stochastic_lower_level = self.Param("StochasticLowerLevel", 95.0) \
.SetDisplay("Stochastic Lower", "Overbought threshold (long uses mirror)", "Filters")
self._bar_shift = self.Param("BarShift", 1) \
.SetGreaterThanZero() \
.SetDisplay("Bar Shift", "Number of finished bars for signals", "Trading")
self._only_one_position = self.Param("OnlyOnePosition", True) \
.SetDisplay("Only One Position", "Restrict to single open position", "Risk")
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Order Volume", "Volume sent with each market order", "Trading")
self._pip_value = self.Param("PipValue", 0.0001) \
.SetGreaterThanZero() \
.SetDisplay("Pip Value", "Monetary value of one pip", "Risk")
self._stop_loss_pips = self.Param("StopLossPips", 200.0) \
.SetDisplay("Stop Loss", "Stop loss distance in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 200.0) \
.SetDisplay("Take Profit", "Take profit distance in pips", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles for analysis", "General")
self._teeth_middle_history = []
self._teeth_upper_history = []
self._teeth_lower_history = []
self._jaws_upper_history = []
self._jaws_lower_history = []
self._lips_upper_history = []
self._lips_lower_history = []
self._rsi_history = []
self._stochastic_history = []
self._long_locked = False
self._short_locked = False
@property
def EntryMode(self):
return int(self._entry_mode.Value)
@property
def ClosureMode(self):
return int(self._closure_mode.Value)
@property
def BandsPeriod(self):
return int(self._bands_period.Value)
@property
def Deviation(self):
return float(self._deviation.Value)
@property
def UseRsiFilter(self):
return self._use_rsi_filter.Value
@property
def RsiPeriod(self):
return int(self._rsi_period.Value)
@property
def RsiLowerLevel(self):
return float(self._rsi_lower_level.Value)
@property
def UseStochasticFilter(self):
return self._use_stochastic_filter.Value
@property
def StochasticPeriod(self):
return int(self._stochastic_period.Value)
@property
def StochasticLowerLevel(self):
return float(self._stochastic_lower_level.Value)
@property
def BarShift(self):
return int(self._bar_shift.Value)
@property
def OnlyOnePosition(self):
return self._only_one_position.Value
@property
def OrderVolume(self):
return float(self._order_volume.Value)
@property
def PipValue(self):
return float(self._pip_value.Value)
@property
def StopLossPips(self):
return float(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return float(self._take_profit_pips.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(bollinger_bands_rsi_zones_strategy, self).OnStarted2(time)
dev = self.Deviation
self._teeth = BollingerBands()
self._teeth.Length = self.BandsPeriod
self._teeth.Width = dev
self._jaws = BollingerBands()
self._jaws.Length = self.BandsPeriod
self._jaws.Width = dev / 2.0
self._lips = BollingerBands()
self._lips.Length = self.BandsPeriod
self._lips.Width = dev * 2.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = self.StochasticPeriod
self._stochastic.D.Length = 3
self._teeth_middle_history = []
self._teeth_upper_history = []
self._teeth_lower_history = []
self._jaws_upper_history = []
self._jaws_lower_history = []
self._lips_upper_history = []
self._lips_lower_history = []
self._rsi_history = []
self._stochastic_history = []
self._long_locked = False
self._short_locked = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self.process_candle).Start()
sec = self.Security
pip_size = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else self.PipValue
if pip_size <= 0:
pip_size = self.PipValue
tp = Unit(self.TakeProfitPips * pip_size, UnitTypes.Absolute) if self.TakeProfitPips > 0 else None
sl = Unit(self.StopLossPips * pip_size, UnitTypes.Absolute) if self.StopLossPips > 0 else None
if tp is not None and sl is not None:
self.StartProtection(takeProfit=tp, stopLoss=sl)
elif tp is not None:
self.StartProtection(takeProfit=tp)
elif sl is not None:
self.StartProtection(stopLoss=sl)
def process_candle(self, candle, rsi_decimal):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
t = candle.ServerTime
teeth_result = process_float(self._teeth, candle.ClosePrice, t, True)
jaws_result = process_float(self._jaws, candle.ClosePrice, t, True)
lips_result = process_float(self._lips, candle.ClosePrice, t, True)
stoch_result = self._stochastic.Process(CandleIndicatorValue(self._stochastic, candle))
if not self._teeth.IsFormed or not self._jaws.IsFormed or not self._lips.IsFormed:
return
teeth_middle = float(teeth_result.MovingAverage) if teeth_result.MovingAverage is not None else 0.0
teeth_upper = float(teeth_result.UpBand) if teeth_result.UpBand is not None else 0.0
teeth_lower = float(teeth_result.LowBand) if teeth_result.LowBand is not None else 0.0
jaws_upper = float(jaws_result.UpBand) if jaws_result.UpBand is not None else 0.0
jaws_lower = float(jaws_result.LowBand) if jaws_result.LowBand is not None else 0.0
lips_upper = float(lips_result.UpBand) if lips_result.UpBand is not None else 0.0
lips_lower = float(lips_result.LowBand) if lips_result.LowBand is not None else 0.0
rsi_value = float(rsi_decimal)
stochastic_k = float(stoch_result.K) if stoch_result.K is not None else 50.0
rsi_ready = not self.UseRsiFilter or self._rsi.IsFormed
stochastic_ready = not self.UseStochasticFilter or self._stochastic.IsFormed
if not rsi_ready or not stochastic_ready:
self._update_history(teeth_middle, teeth_upper, teeth_lower,
jaws_upper, jaws_lower, lips_upper, lips_lower,
rsi_value, stochastic_k)
return
base_teeth = self._try_get_shifted(self._teeth_middle_history)
upper_teeth = self._try_get_shifted(self._teeth_upper_history)
lower_teeth = self._try_get_shifted(self._teeth_lower_history)
u_jaws = self._try_get_shifted(self._jaws_upper_history)
l_jaws = self._try_get_shifted(self._jaws_lower_history)
u_lips = self._try_get_shifted(self._lips_upper_history)
l_lips = self._try_get_shifted(self._lips_lower_history)
if (base_teeth is None or upper_teeth is None or lower_teeth is None or
u_jaws is None or l_jaws is None or u_lips is None or l_lips is None):
self._update_history(teeth_middle, teeth_upper, teeth_lower,
jaws_upper, jaws_lower, lips_upper, lips_lower,
rsi_value, stochastic_k)
return
rsi_shifted = 50.0
if self.UseRsiFilter:
r = self._try_get_shifted(self._rsi_history)
if r is None:
self._update_history(teeth_middle, teeth_upper, teeth_lower,
jaws_upper, jaws_lower, lips_upper, lips_lower,
rsi_value, stochastic_k)
return
rsi_shifted = r
stochastic_shifted = 50.0
if self.UseStochasticFilter:
s = self._try_get_shifted(self._stochastic_history)
if s is None:
self._update_history(teeth_middle, teeth_upper, teeth_lower,
jaws_upper, jaws_lower, lips_upper, lips_lower,
rsi_value, stochastic_k)
return
stochastic_shifted = s
long_entry_price = self._get_long_entry_price(lower_teeth, l_jaws, l_lips)
short_entry_price = self._get_short_entry_price(upper_teeth, u_jaws, u_lips)
exit_long, exit_short = self._get_exit_levels(
short_entry_price, long_entry_price, u_jaws, l_jaws, u_lips, l_lips)
if not self.OnlyOnePosition:
if close >= base_teeth:
self._long_locked = False
if close <= base_teeth:
self._short_locked = False
price_hit_long = float(candle.LowPrice) <= long_entry_price
price_hit_short = float(candle.HighPrice) >= short_entry_price
rsi_long_ok = not self.UseRsiFilter or rsi_shifted <= 100.0 - self.RsiLowerLevel
rsi_short_ok = not self.UseRsiFilter or rsi_shifted >= self.RsiLowerLevel
stoch_long_ok = not self.UseStochasticFilter or stochastic_shifted < 100.0 - self.StochasticLowerLevel
stoch_short_ok = not self.UseStochasticFilter or stochastic_shifted > self.StochasticLowerLevel
can_open_long = self.Position == 0 if self.OnlyOnePosition else self.Position >= 0
can_open_short = self.Position == 0 if self.OnlyOnePosition else self.Position <= 0
if price_hit_short and rsi_short_ok and stoch_short_ok and can_open_short:
if self.OnlyOnePosition or not self._short_locked:
self.SellMarket()
self._short_locked = not self.OnlyOnePosition
if price_hit_long and rsi_long_ok and stoch_long_ok and can_open_long:
if self.OnlyOnePosition or not self._long_locked:
self.BuyMarket()
self._long_locked = not self.OnlyOnePosition
# Exit logic
cm = self.ClosureMode
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if cm == 0: # MiddleLine
if self.Position > 0 and h >= base_teeth:
self.SellMarket()
if self.Position < 0 and lo <= base_teeth:
self.BuyMarket()
elif cm == 1 or cm == 2: # BetweenYellowAndBlue or BetweenBlueAndRed
if self.Position > 0 and h >= exit_long:
self.SellMarket()
if self.Position < 0 and lo <= exit_short:
self.BuyMarket()
elif cm == 3: # YellowLine
if self.Position > 0 and h >= upper_teeth:
self.SellMarket()
if self.Position < 0 and lo <= lower_teeth:
self.BuyMarket()
elif cm == 4: # BlueLine
if self.Position > 0 and h >= u_jaws:
self.SellMarket()
if self.Position < 0 and lo <= l_jaws:
self.BuyMarket()
elif cm == 5: # RedLine
if self.Position > 0 and h >= u_lips:
self.SellMarket()
if self.Position < 0 and lo <= l_lips:
self.BuyMarket()
self._update_history(teeth_middle, teeth_upper, teeth_lower,
jaws_upper, jaws_lower, lips_upper, lips_lower,
rsi_value, stochastic_k)
def _get_long_entry_price(self, lower_teeth, lower_jaws, lower_lips):
em = self.EntryMode
if em == 0: # BetweenYellowAndBlue
return lower_teeth - (lower_teeth - lower_jaws) / 2.0
elif em == 1: # BetweenBlueAndRed
return lower_jaws - (lower_jaws - lower_lips) / 2.0
elif em == 2: # YellowLine
return lower_teeth
elif em == 3: # BlueLine
return lower_jaws
elif em == 4: # RedLine
return lower_lips
return lower_teeth
def _get_short_entry_price(self, upper_teeth, upper_jaws, upper_lips):
em = self.EntryMode
if em == 0: # BetweenYellowAndBlue
return upper_teeth + (upper_jaws - upper_teeth) / 2.0
elif em == 1: # BetweenBlueAndRed
return upper_jaws + (upper_lips - upper_jaws) / 2.0
elif em == 2: # YellowLine
return upper_teeth
elif em == 3: # BlueLine
return upper_jaws
elif em == 4: # RedLine
return upper_lips
return upper_teeth
def _get_exit_levels(self, short_entry_price, long_entry_price,
upper_jaws, lower_jaws, upper_lips, lower_lips):
cm = self.ClosureMode
em = self.EntryMode
if (cm == 1 and em == 0) or (cm == 2 and em == 1):
return (short_entry_price, long_entry_price)
default_long = upper_jaws + (upper_lips - upper_jaws) / 2.0
default_short = lower_jaws - (lower_jaws - lower_lips) / 2.0
return (default_long, default_short)
def _try_get_shifted(self, history):
shift = self.BarShift
if shift <= 0:
return None
if len(history) < shift:
return None
return history[0]
def _update_history(self, teeth_middle, teeth_upper, teeth_lower,
jaws_upper, jaws_lower, lips_upper, lips_lower,
rsi_value, stochastic_k):
shift = self.BarShift
if shift <= 0:
return
self._enqueue(self._teeth_middle_history, teeth_middle)
self._enqueue(self._teeth_upper_history, teeth_upper)
self._enqueue(self._teeth_lower_history, teeth_lower)
self._enqueue(self._jaws_upper_history, jaws_upper)
self._enqueue(self._jaws_lower_history, jaws_lower)
self._enqueue(self._lips_upper_history, lips_upper)
self._enqueue(self._lips_lower_history, lips_lower)
if self._rsi.IsFormed:
self._enqueue(self._rsi_history, rsi_value)
if self._stochastic.IsFormed:
self._enqueue(self._stochastic_history, stochastic_k)
def _enqueue(self, history, value):
history.append(value)
shift = self.BarShift
while len(history) > shift:
history.pop(0)
def OnReseted(self):
super(bollinger_bands_rsi_zones_strategy, self).OnReseted()
self._teeth_middle_history = []
self._teeth_upper_history = []
self._teeth_lower_history = []
self._jaws_upper_history = []
self._jaws_lower_history = []
self._lips_upper_history = []
self._lips_lower_history = []
self._rsi_history = []
self._stochastic_history = []
self._long_locked = False
self._short_locked = False
def CreateClone(self):
return bollinger_bands_rsi_zones_strategy()