Bollinger Bands RSI Zones 策略
本策略源自 MetaTrader 顾问 “Bollinger Bands RSI”。使用相同周期但不同标准差的三组布林带:黄色带为基准,蓝色带的偏差缩小一半,红色带的偏差扩大一倍。价格回落到指定区域时触发交易,并可叠加 RSI 与随机指标过滤器。
策略逻辑
- 黄色带使用
Deviation设置的标准差倍数。 - 蓝色带使用一半倍数,形成较窄的通道;红色带使用两倍倍数,形成更宽的外层通道。
- RSI 与随机指标的数值基于前一根已完成的 K 线(
Bar Shift),以保持与原始 EA 一致。 Only One Position控制是否只在空仓时开新单,或者允许在价格回到布林带中轨后继续加仓。
入场规则
多头
- 当前 K 线价格下探至
Entry Mode指定的买入区域:- 黄色与蓝色之间的中点、蓝色与红色之间的中点,或直接接触其中一条带。
- 可选过滤条件:
- RSI:RSI ≤
100 - RSI Lower。 - 随机指标:%K <
100 - Stochastic Lower。
- RSI:RSI ≤
- 持仓条件:
Only One Position = true时必须在空仓状态下入场。- 允许加仓时,只有当 K 线收盘价高于布林中轨后锁定才会被解除。
空头
- 当前 K 线价格上冲至
Entry Mode指定的卖出区域(与多头对称)。 - 可选过滤条件:
- RSI:RSI ≥
RSI Lower。 - 随机指标:%K >
Stochastic Lower。
- RSI:RSI ≥
- 持仓条件与多头相同(空仓或在收盘价跌破中轨后解除锁定)。
出场规则
Closure Mode决定获利/离场的目标:Middle Line:多头触及布林中轨即平仓,空头触及中轨下侧时平仓。Between Yellow and Blue/Between Blue and Red:使用入场时的相同中点;若入场模式不同,则使用蓝红之间的默认中点。Yellow Line、Blue Line、Red Line:价格触及对应的上/下轨即离场。
- 在加仓模式下,只要收盘价越过中轨,锁定标志会自动清除,允许新的同向交易。
风险控制
Stop Loss与Take Profit以点数表示,在StartProtection初始化时通过Pip Value转换成绝对价格距离。- 若参数为 0,则不设置相应的止损/止盈。
Order Volume定义每次市价单的交易量。
参数表
| 参数 | 说明 | 默认值 |
|---|---|---|
Entry Mode |
触发入场的布林带区域。 | 黄蓝之间 |
Closure Mode |
平仓所用的带或中点。 | 蓝红之间 |
Bands Period |
所有布林带的周期长度。 | 140 |
Deviation |
黄色带的标准差倍数(蓝色为一半,红色为两倍)。 | 2.0 |
Use RSI Filter |
启用 RSI 过滤器。 | false |
RSI Period |
RSI 计算周期。 | 8 |
RSI Lower |
超买阈值(超卖阈值 = 100 - 值)。 |
70 |
Use Stochastic Filter |
启用随机指标过滤。 | true |
Stochastic Period |
%K 主周期(平滑固定为 3/3 SMA)。 | 20 |
Stochastic Lower |
超买阈值(超卖阈值 = 100 - 值)。 |
95 |
Bar Shift |
指标向前回看的完成 K 线数量。 | 1 |
Only One Position |
仅在空仓时开仓。 | true |
Order Volume |
每次下单的数量。 | 1 |
Pip Value |
一个点的绝对价格。 | 0.0001 |
Stop Loss |
止损点数(0 表示关闭)。 | 200 |
Take Profit |
止盈点数(0 表示关闭)。 | 200 |
Candle Type |
使用的 K 线类型(默认 1 分钟)。 | 1 分钟 |
备注
- 策略仅处理收盘完成的 K 线,因此
Bar Shift应保持 ≥ 1,避免引用未完成数据。 - RSI 与随机指标仅使用 %K 线,%D 线虽然计算但未参与决策,与原 EA 保持一致。
- 代码使用 StockSharp 高级 API 通过
Bind订阅指标,未直接访问指标缓冲区,符合仓库的转换要求。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bollinger Bands based strategy with optional RSI and Stochastic filters.
/// Replicates the Bollinger Bands RSI expert advisor logic with configurable entry and exit zones.
/// </summary>
public class BollingerBandsRsiZonesStrategy : Strategy
{
/// <summary>
/// Entry location for Bollinger Bands RSI strategy.
/// </summary>
public enum BollingerBandsRsiEntryModes
{
/// <summary>
/// Midpoint between yellow (primary) and blue (narrow) bands.
/// </summary>
BetweenYellowAndBlue,
/// <summary>
/// Midpoint between blue (narrow) and red (wide) bands.
/// </summary>
BetweenBlueAndRed,
/// <summary>
/// Yellow band itself.
/// </summary>
YellowLine,
/// <summary>
/// Blue band (narrow deviation).
/// </summary>
BlueLine,
/// <summary>
/// Red band (wide deviation).
/// </summary>
RedLine
}
/// <summary>
/// Exit location for Bollinger Bands RSI strategy.
/// </summary>
public enum BollingerBandsRsiClosureModes
{
/// <summary>
/// Exit on the middle Bollinger band.
/// </summary>
MiddleLine,
/// <summary>
/// Exit between yellow and blue bands.
/// </summary>
BetweenYellowAndBlue,
/// <summary>
/// Exit between blue and red bands.
/// </summary>
BetweenBlueAndRed,
/// <summary>
/// Exit on the yellow band.
/// </summary>
YellowLine,
/// <summary>
/// Exit on the blue band.
/// </summary>
BlueLine,
/// <summary>
/// Exit on the red band.
/// </summary>
RedLine
}
private readonly StrategyParam<BollingerBandsRsiEntryModes> _entryMode;
private readonly StrategyParam<BollingerBandsRsiClosureModes> _closureMode;
private readonly StrategyParam<int> _bandsPeriod;
private readonly StrategyParam<decimal> _deviation;
private readonly StrategyParam<bool> _useRsiFilter;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiLowerLevel;
private readonly StrategyParam<bool> _useStochasticFilter;
private readonly StrategyParam<int> _stochasticPeriod;
private readonly StrategyParam<decimal> _stochasticLowerLevel;
private readonly StrategyParam<int> _barShift;
private readonly StrategyParam<bool> _onlyOnePosition;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _pipValue;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<DataType> _candleType;
private BollingerBands _teeth = null!;
private BollingerBands _jaws = null!;
private BollingerBands _lips = null!;
private RelativeStrengthIndex _rsi = null!;
private StochasticOscillator _stochastic = null!;
private readonly List<decimal> _teethMiddleHistory = new();
private readonly List<decimal> _teethUpperHistory = new();
private readonly List<decimal> _teethLowerHistory = new();
private readonly List<decimal> _jawsUpperHistory = new();
private readonly List<decimal> _jawsLowerHistory = new();
private readonly List<decimal> _lipsUpperHistory = new();
private readonly List<decimal> _lipsLowerHistory = new();
private readonly List<decimal> _rsiHistory = new();
private readonly List<decimal> _stochasticHistory = new();
private bool _longLocked;
private bool _shortLocked;
/// <summary>
/// Entry zone selection.
/// </summary>
public BollingerBandsRsiEntryModes EntryMode
{
get => _entryMode.Value;
set => _entryMode.Value = value;
}
/// <summary>
/// Exit zone selection.
/// </summary>
public BollingerBandsRsiClosureModes ClosureMode
{
get => _closureMode.Value;
set => _closureMode.Value = value;
}
/// <summary>
/// Bollinger period for all bands.
/// </summary>
public int BandsPeriod
{
get => _bandsPeriod.Value;
set => _bandsPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for the primary (yellow) band.
/// </summary>
public decimal Deviation
{
get => _deviation.Value;
set => _deviation.Value = value;
}
/// <summary>
/// Enable RSI filter.
/// </summary>
public bool UseRsiFilter
{
get => _useRsiFilter.Value;
set => _useRsiFilter.Value = value;
}
/// <summary>
/// RSI averaging period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI short threshold (long threshold is mirrored from 100).
/// </summary>
public decimal RsiLowerLevel
{
get => _rsiLowerLevel.Value;
set => _rsiLowerLevel.Value = value;
}
/// <summary>
/// Enable Stochastic filter.
/// </summary>
public bool UseStochasticFilter
{
get => _useStochasticFilter.Value;
set => _useStochasticFilter.Value = value;
}
/// <summary>
/// Stochastic main period.
/// </summary>
public int StochasticPeriod
{
get => _stochasticPeriod.Value;
set => _stochasticPeriod.Value = value;
}
/// <summary>
/// Stochastic overbought level (long threshold is mirrored from 100).
/// </summary>
public decimal StochasticLowerLevel
{
get => _stochasticLowerLevel.Value;
set => _stochasticLowerLevel.Value = value;
}
/// <summary>
/// Number of finished bars used for indicator shift.
/// </summary>
public int BarShift
{
get => _barShift.Value;
set => _barShift.Value = value;
}
/// <summary>
/// Allow only one open position at a time.
/// </summary>
public bool OnlyOnePosition
{
get => _onlyOnePosition.Value;
set => _onlyOnePosition.Value = value;
}
/// <summary>
/// Trading volume for new orders.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Value of one pip in price units.
/// </summary>
public decimal PipValue
{
get => _pipValue.Value;
set => _pipValue.Value = value;
}
/// <summary>
/// Stop loss distance in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Candle type for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="BollingerBandsRsiZonesStrategy"/> class.
/// </summary>
public BollingerBandsRsiZonesStrategy()
{
_entryMode = Param(nameof(EntryMode), BollingerBandsRsiEntryModes.BetweenYellowAndBlue)
.SetDisplay("Entry Mode", "Bollinger zone used for entries", "Trading");
_closureMode = Param(nameof(ClosureMode), BollingerBandsRsiClosureModes.BetweenBlueAndRed)
.SetDisplay("Closure Mode", "Bollinger zone used for exits", "Trading");
_bandsPeriod = Param(nameof(BandsPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bands Period", "Length of all Bollinger bands", "Indicators")
;
_deviation = Param(nameof(Deviation), 2m)
.SetGreaterThanZero()
.SetDisplay("Deviation", "Standard deviation for yellow band", "Indicators")
;
_useRsiFilter = Param(nameof(UseRsiFilter), false)
.SetDisplay("Use RSI Filter", "Enable RSI confirmation", "Filters");
_rsiPeriod = Param(nameof(RsiPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Length of RSI filter", "Filters")
;
_rsiLowerLevel = Param(nameof(RsiLowerLevel), 70m)
.SetDisplay("RSI Lower", "Short threshold (long uses 100-threshold)", "Filters")
;
_useStochasticFilter = Param(nameof(UseStochasticFilter), false)
.SetDisplay("Use Stochastic Filter", "Enable Stochastic confirmation", "Filters");
_stochasticPeriod = Param(nameof(StochasticPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Main %K period", "Filters")
;
_stochasticLowerLevel = Param(nameof(StochasticLowerLevel), 95m)
.SetDisplay("Stochastic Lower", "Overbought threshold (long uses mirror)", "Filters")
;
_barShift = Param(nameof(BarShift), 1)
.SetGreaterThanZero()
.SetDisplay("Bar Shift", "Number of finished bars for signals", "Trading");
_onlyOnePosition = Param(nameof(OnlyOnePosition), true)
.SetDisplay("Only One Position", "Restrict to single open position", "Risk");
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume sent with each market order", "Trading");
_pipValue = Param(nameof(PipValue), 0.0001m)
.SetGreaterThanZero()
.SetDisplay("Pip Value", "Monetary value of one pip", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 200m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 200m)
.SetNotNegative()
.SetDisplay("Take Profit", "Take profit distance in pips", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_teethMiddleHistory.Clear();
_teethUpperHistory.Clear();
_teethLowerHistory.Clear();
_jawsUpperHistory.Clear();
_jawsLowerHistory.Clear();
_lipsUpperHistory.Clear();
_lipsLowerHistory.Clear();
_rsiHistory.Clear();
_stochasticHistory.Clear();
_longLocked = false;
_shortLocked = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = OrderVolume;
_teeth = new BollingerBands
{
Length = BandsPeriod,
Width = Deviation
};
_jaws = new BollingerBands
{
Length = BandsPeriod,
Width = Deviation / 2m
};
_lips = new BollingerBands
{
Length = BandsPeriod,
Width = Deviation * 2m
};
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_stochastic = new StochasticOscillator
{
K = { Length = StochasticPeriod },
D = { Length = 3 }
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
var pipSize = Security?.PriceStep ?? PipValue;
var take = TakeProfitPips > 0m ? new Unit(TakeProfitPips * pipSize, UnitTypes.Absolute) : null;
var stop = StopLossPips > 0m ? new Unit(StopLossPips * pipSize, UnitTypes.Absolute) : null;
if (take != null || stop != null)
StartProtection(takeProfit: take, stopLoss: stop);
}
private void ProcessCandle(ICandleMessage candle, decimal rsiDecimal)
{
if (candle.State != CandleStates.Finished)
return;
// Process other indicators manually.
var teethResult = _teeth.Process(new DecimalIndicatorValue(_teeth, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var jawsResult = _jaws.Process(new DecimalIndicatorValue(_jaws, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var lipsResult = _lips.Process(new DecimalIndicatorValue(_lips, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var stochResult = _stochastic.Process(new CandleIndicatorValue(_stochastic, candle) { IsFinal = true });
if (!_teeth.IsFormed || !_jaws.IsFormed || !_lips.IsFormed)
return;
var teethBB = (BollingerBandsValue)teethResult;
var jawsBB = (BollingerBandsValue)jawsResult;
var lipsBB = (BollingerBandsValue)lipsResult;
var teethMiddle = teethBB.MovingAverage ?? 0m;
var teethUpper = teethBB.UpBand ?? 0m;
var teethLower = teethBB.LowBand ?? 0m;
var jawsUpper = jawsBB.UpBand ?? 0m;
var jawsLower = jawsBB.LowBand ?? 0m;
var lipsUpper = lipsBB.UpBand ?? 0m;
var lipsLower = lipsBB.LowBand ?? 0m;
var rsiValue = rsiDecimal;
var stochTyped = (StochasticOscillatorValue)stochResult;
var stochasticK = stochTyped.K ?? 50m;
var rsiReady = !UseRsiFilter || _rsi.IsFormed;
var stochasticReady = !UseStochasticFilter || _stochastic.IsFormed;
if (!rsiReady || !stochasticReady)
{
UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
return;
}
if (!TryGetShifted(_teethMiddleHistory, out var baseTeeth) ||
!TryGetShifted(_teethUpperHistory, out var upperTeeth) ||
!TryGetShifted(_teethLowerHistory, out var lowerTeeth) ||
!TryGetShifted(_jawsUpperHistory, out var upperJaws) ||
!TryGetShifted(_jawsLowerHistory, out var lowerJaws) ||
!TryGetShifted(_lipsUpperHistory, out var upperLips) ||
!TryGetShifted(_lipsLowerHistory, out var lowerLips))
{
UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
return;
}
decimal rsiShifted = 50m;
if (UseRsiFilter)
{
if (!TryGetShifted(_rsiHistory, out rsiShifted))
{
UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
return;
}
}
decimal stochasticShifted = 50m;
if (UseStochasticFilter)
{
if (!TryGetShifted(_stochasticHistory, out stochasticShifted))
{
UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
return;
}
}
// All indicators checked above via IsFormed.
var longEntryPrice = GetLongEntryPrice(lowerTeeth, lowerJaws, lowerLips);
var shortEntryPrice = GetShortEntryPrice(upperTeeth, upperJaws, upperLips);
var (exitLong, exitShort) = GetExitLevels(shortEntryPrice, longEntryPrice, upperJaws, lowerJaws, upperLips, lowerLips);
if (!OnlyOnePosition)
{
if (candle.ClosePrice >= baseTeeth)
_longLocked = false;
if (candle.ClosePrice <= baseTeeth)
_shortLocked = false;
}
var priceHitLong = candle.LowPrice <= longEntryPrice;
var priceHitShort = candle.HighPrice >= shortEntryPrice;
var rsiLongOk = !UseRsiFilter || rsiShifted <= 100m - RsiLowerLevel;
var rsiShortOk = !UseRsiFilter || rsiShifted >= RsiLowerLevel;
var stochLongOk = !UseStochasticFilter || stochasticShifted < 100m - StochasticLowerLevel;
var stochShortOk = !UseStochasticFilter || stochasticShifted > StochasticLowerLevel;
var canOpenLong = OnlyOnePosition ? Position == 0m : Position >= 0m;
var canOpenShort = OnlyOnePosition ? Position == 0m : Position <= 0m;
if (priceHitShort && rsiShortOk && stochShortOk && canOpenShort)
{
if (OnlyOnePosition || !_shortLocked)
{
// Sell when price reaches the selected upper band zone and filters confirm overbought state.
SellMarket();
_shortLocked = !OnlyOnePosition;
}
}
if (priceHitLong && rsiLongOk && stochLongOk && canOpenLong)
{
if (OnlyOnePosition || !_longLocked)
{
// Buy when price reaches the selected lower band zone and filters confirm oversold state.
BuyMarket();
_longLocked = !OnlyOnePosition;
}
}
// Exit logic mirrors the original EA: close longs on selected upper zone, shorts on selected lower zone.
switch (ClosureMode)
{
case BollingerBandsRsiClosureModes.MiddleLine:
if (Position > 0m && candle.HighPrice >= baseTeeth)
SellMarket();
if (Position < 0m && candle.LowPrice <= baseTeeth)
BuyMarket();
break;
case BollingerBandsRsiClosureModes.BetweenYellowAndBlue:
case BollingerBandsRsiClosureModes.BetweenBlueAndRed:
if (Position > 0m && candle.HighPrice >= exitLong)
SellMarket();
if (Position < 0m && candle.LowPrice <= exitShort)
BuyMarket();
break;
case BollingerBandsRsiClosureModes.YellowLine:
if (Position > 0m && candle.HighPrice >= upperTeeth)
SellMarket();
if (Position < 0m && candle.LowPrice <= lowerTeeth)
BuyMarket();
break;
case BollingerBandsRsiClosureModes.BlueLine:
if (Position > 0m && candle.HighPrice >= upperJaws)
SellMarket();
if (Position < 0m && candle.LowPrice <= lowerJaws)
BuyMarket();
break;
case BollingerBandsRsiClosureModes.RedLine:
if (Position > 0m && candle.HighPrice >= upperLips)
SellMarket();
if (Position < 0m && candle.LowPrice <= lowerLips)
BuyMarket();
break;
}
UpdateHistory(teethMiddle, teethUpper, teethLower, jawsUpper, jawsLower, lipsUpper, lipsLower, rsiValue, stochasticK);
}
private decimal GetLongEntryPrice(decimal lowerTeeth, decimal lowerJaws, decimal lowerLips)
{
return EntryMode switch
{
BollingerBandsRsiEntryModes.BetweenYellowAndBlue => lowerTeeth - (lowerTeeth - lowerJaws) / 2m,
BollingerBandsRsiEntryModes.BetweenBlueAndRed => lowerJaws - (lowerJaws - lowerLips) / 2m,
BollingerBandsRsiEntryModes.YellowLine => lowerTeeth,
BollingerBandsRsiEntryModes.BlueLine => lowerJaws,
BollingerBandsRsiEntryModes.RedLine => lowerLips,
_ => lowerTeeth
};
}
private decimal GetShortEntryPrice(decimal upperTeeth, decimal upperJaws, decimal upperLips)
{
return EntryMode switch
{
BollingerBandsRsiEntryModes.BetweenYellowAndBlue => upperTeeth + (upperJaws - upperTeeth) / 2m,
BollingerBandsRsiEntryModes.BetweenBlueAndRed => upperJaws + (upperLips - upperJaws) / 2m,
BollingerBandsRsiEntryModes.YellowLine => upperTeeth,
BollingerBandsRsiEntryModes.BlueLine => upperJaws,
BollingerBandsRsiEntryModes.RedLine => upperLips,
_ => upperTeeth
};
}
private (decimal exitLong, decimal exitShort) GetExitLevels(decimal shortEntryPrice, decimal longEntryPrice, decimal upperJaws, decimal lowerJaws, decimal upperLips, decimal lowerLips)
{
if ((ClosureMode == BollingerBandsRsiClosureModes.BetweenYellowAndBlue && EntryMode == BollingerBandsRsiEntryModes.BetweenYellowAndBlue) ||
(ClosureMode == BollingerBandsRsiClosureModes.BetweenBlueAndRed && EntryMode == BollingerBandsRsiEntryModes.BetweenBlueAndRed))
{
return (shortEntryPrice, longEntryPrice);
}
var defaultLong = upperJaws + (upperLips - upperJaws) / 2m;
var defaultShort = lowerJaws - (lowerJaws - lowerLips) / 2m;
return (defaultLong, defaultShort);
}
private bool TryGetShifted(List<decimal> history, out decimal value)
{
if (BarShift <= 0)
{
value = 0m;
return false;
}
if (history.Count < BarShift)
{
value = 0m;
return false;
}
value = history[0];
return true;
}
private void UpdateHistory(
decimal teethMiddle,
decimal teethUpper,
decimal teethLower,
decimal jawsUpper,
decimal jawsLower,
decimal lipsUpper,
decimal lipsLower,
decimal rsiValue,
decimal stochasticK)
{
if (BarShift <= 0)
return;
Enqueue(_teethMiddleHistory, teethMiddle);
Enqueue(_teethUpperHistory, teethUpper);
Enqueue(_teethLowerHistory, teethLower);
Enqueue(_jawsUpperHistory, jawsUpper);
Enqueue(_jawsLowerHistory, jawsLower);
Enqueue(_lipsUpperHistory, lipsUpper);
Enqueue(_lipsLowerHistory, lipsLower);
if (_rsi.IsFormed)
Enqueue(_rsiHistory, rsiValue);
if (_stochastic.IsFormed)
Enqueue(_stochasticHistory, stochasticK);
}
private void Enqueue(List<decimal> history, decimal value)
{
history.Add(value);
while (history.Count > BarShift)
try { history.RemoveAt(0); } catch { }
}
}
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()