Estratégia de Zonas RSI de Bollinger Bands
Um sistema de rompimento multi-banda de Bollinger Bands convertido do expert MetaTrader «Bollinger Bands RSI». A estratégia deriva três envelopes de Bollinger com períodos idênticos mas desvios diferentes para criar bandas «amarela», «azul» e «vermelha». Ordens são acionadas quando o preço revisita zonas configuráveis ao redor dessas bandas, com confirmação opcional por filtros RSI e Estocástico.
Lógica da estratégia
- A banda principal (amarela) usa o multiplicador de desvio configurado.
- A banda azul reduz pela metade o desvio, criando um envelope mais estreito.
- A banda vermelha dobra o desvio, produzindo um envelope externo amplo.
- Os valores de RSI e Estocástico são avaliados na vela finalizada anterior (
Bar Shift) para corresponder ao comportamento original do EA. Only One Positioncontrola se novas ordens são permitidas apenas quando a posição líquida está zerada ou se operações de escala adicionais são permitidas quando o preço retorna à linha média de Bollinger.
Critérios de entrada
Entradas compradas
- O preço na vela atual cai para ou abaixo da zona de entrada comprada selecionada (
Entry Mode):- Ponto médio entre amarela e azul, azul e vermelha, ou uma das bandas individuais.
- Confirmações opcionais:
- Filtro RSI: RSI ≤
100 - RSI Lower. - Filtro Estocástico: %K <
100 - Stochastic Lower.
- Filtro RSI: RSI ≤
- Pré-requisitos de posição:
- Se
Only One Positionestiver habilitado, a posição líquida deve estar zerada. - Caso contrário, ordens compradas adicionais são bloqueadas até que a vela feche acima da banda média (amarela), emulando a lógica de bloqueio do EA.
- Se
Entradas vendidas
- O preço na vela atual sobe para ou acima da zona de entrada vendida selecionada (espelha as opções compradas).
- Confirmações opcionais:
- Filtro RSI: RSI ≥
RSI Lower. - Filtro Estocástico: %K >
Stochastic Lower.
- Filtro RSI: RSI ≥
- Os pré-requisitos de posição espelham a lógica comprada (posição zerada para modo de operação única ou estado desbloqueado quando a vela fecha de volta abaixo da banda média).
Critérios de saída
- O modo de fechamento é determinado por
Closure Mode:Middle Line: sair de posições compradas quando o preço atinge a banda média de Bollinger; sair de vendidas quando o preço a toca por cima.Between Yellow and Blue/Between Blue and Red: sair nos mesmos pontos médios usados para entradas; padrão para pontos médios entre azul e vermelho quando o modo de entrada difere.Yellow Line,Blue Line,Red Line: sair em toques diretos das bandas superiores/inferiores correspondentes.
- Os indicadores de bloqueio para o modo de escala são redefinidos automaticamente quando a vela fecha no lado oposto da banda média, recriando o comportamento do EA.
Gestão de riscos
- Os parâmetros
Stop LosseTake Profitsão expressos em pips e convertidos em distâncias de preço absolutas através dePip ValuequandoStartProtectioné inicializado. - Stops e alvos são opcionais; deixe a distância em zero para desabilitar a proteção respectiva.
- O volume de negociação é definido por
Order Volumee aplicado a cada ordem a mercado.
Parâmetros
| Nome | Descrição | Padrão |
|---|---|---|
Entry Mode |
Escolhe a zona de Bollinger que aciona as entradas. | Entre amarela e azul |
Closure Mode |
Define a banda ou ponto médio de tomada de lucro. | Entre azul e vermelha |
Bands Period |
Comprimento de período compartilhado por todas as Bollinger Bands. | 140 |
Deviation |
Multiplicador de desvio padrão para a banda amarela (azul é metade, vermelha é o dobro). | 2.0 |
Use RSI Filter |
Ativa a lógica de confirmação RSI. | false |
RSI Period |
Período de média do RSI. | 8 |
RSI Lower |
Limite de sobrecompra; sobrevenda usa 100 - valor. |
70 |
Use Stochastic Filter |
Ativa a lógica de confirmação %K. | true |
Stochastic Period |
Período principal de retrocesso %K (suavização fixa em 3/3 SMA). | 20 |
Stochastic Lower |
Limite de sobrecompra; sobrevenda usa 100 - valor. |
95 |
Bar Shift |
Número de barras finalizadas para olhar para trás em busca de valores de indicadores. | 1 |
Only One Position |
Se habilitado, abre novas operações apenas quando não há posição ativa. | true |
Order Volume |
Volume enviado com cada ordem a mercado. | 1 |
Pip Value |
Valor de preço absoluto de um pip para conversão de stop/alvo. | 0.0001 |
Stop Loss |
Distância de stop protetor em pips (0 desabilita). | 200 |
Take Profit |
Distância de alvo protetor em pips (0 desabilita). | 200 |
Candle Type |
Tipo de dados usado para cálculos (velas de 1 minuto por padrão). | Período de 1m |
Notas
- A estratégia processa apenas velas concluídas, portanto
Bar Shiftdeve permanecer ≥ 1 para evitar referenciar barras inacabadas. - Os filtros RSI e Estocástico usam a linha %K; a linha %D é calculada mas não usada, refletindo a implementação original do EA.
- A conversão mantém comentários e nomes de sinais em inglês e segue as diretrizes da API de alto nível do StockSharp (pipeline de indicadores baseado em Bind, sem acesso 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()