BB HeikinAshi Entry
Bollinger Bands strategy using Heikin Ashi candles.
The system waits for two or three consecutive bearish Heikin Ashi bars that touch the lower Bollinger Band. A bullish candle closing back above the band triggers a long entry. Shorts work in the opposite direction. Half of the position is closed at the first target and the remainder is protected with a trailing stop.
Details
- Entry Criteria: Reversal of consecutive Heikin Ashi candles around Bollinger Bands.
- Long/Short: Both.
- Exit Criteria: Partial take profit and trailing stop.
- Stops: Yes.
- Default Values:
BollingerLength= 20BollingerWidth= 2CandleType= TimeSpan.FromMinutes(15)
- Filters:
- Category: Reversal
- Direction: Both
- Indicators: Heikin Ashi, Bollinger Bands
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (15m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bollinger Bands + Heikin Ashi entry strategy with partial profit and trailing stop.
/// </summary>
public class BbHeikinAshiEntryStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bollingerLength;
private readonly StrategyParam<decimal> _bollingerWidth;
private BollingerBands _bollinger;
private decimal _haOpenPrev1, _haOpenPrev2, _haOpenPrev3;
private decimal _haClosePrev1, _haClosePrev2, _haClosePrev3;
private decimal _haHighPrev1, _haHighPrev2, _haHighPrev3;
private decimal _haLowPrev1, _haLowPrev2, _haLowPrev3;
private decimal _upperBbPrev1, _upperBbPrev2, _upperBbPrev3;
private decimal _lowerBbPrev1, _lowerBbPrev2, _lowerBbPrev3;
private decimal _prevRawLow;
private decimal _prevRawHigh;
/// <summary>
/// Initialize BB Heikin Ashi Entry strategy.
/// </summary>
public BbHeikinAshiEntryStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_bollingerLength = Param(nameof(BollingerLength), 20)
.SetDisplay("Bollinger Length", "Period of Bollinger Bands", "Bollinger")
.SetOptimize(10, 40, 5);
_bollingerWidth = Param(nameof(BollingerWidth), 2m)
.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Bollinger")
.SetOptimize(1m, 3m, 0.5m);
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Bollinger period.
/// </summary>
public int BollingerLength
{
get => _bollingerLength.Value;
set => _bollingerLength.Value = value;
}
/// <summary>
/// Bollinger width (standard deviation).
/// </summary>
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_haOpenPrev1 = _haOpenPrev2 = _haOpenPrev3 = default;
_haClosePrev1 = _haClosePrev2 = _haClosePrev3 = default;
_haHighPrev1 = _haHighPrev2 = _haHighPrev3 = default;
_haLowPrev1 = _haLowPrev2 = _haLowPrev3 = default;
_upperBbPrev1 = _upperBbPrev2 = _upperBbPrev3 = default;
_lowerBbPrev1 = _lowerBbPrev2 = _lowerBbPrev3 = default;
_prevRawLow = _prevRawHigh = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bollinger = new BollingerBands
{
Length = BollingerLength,
Width = BollingerWidth
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bollinger, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbResult)
{
if (candle.State != CandleStates.Finished)
return;
var haClose = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m;
var haOpen = _haOpenPrev1 == 0
? (candle.OpenPrice + candle.ClosePrice) / 2m
: (_haOpenPrev1 + _haClosePrev1) / 2m;
var haHigh = Math.Max(Math.Max(candle.HighPrice, haOpen), haClose);
var haLow = Math.Min(Math.Min(candle.LowPrice, haOpen), haClose);
if (bbResult is not BollingerBandsValue bbRaw ||
bbRaw.UpBand is not decimal upper ||
bbRaw.LowBand is not decimal lower)
{
Shift(haOpen, haClose, haHigh, haLow, 0m, 0m, candle);
return;
}
if (_haOpenPrev1 != 0)
{
var redCandle1 = _haClosePrev1 < _haOpenPrev1 && _haLowPrev1 <= _lowerBbPrev1;
var redCandle2 = _haClosePrev2 < _haOpenPrev2 && _haLowPrev2 <= _lowerBbPrev2;
var greenConfirmation = haClose > haOpen;
var buySignal = (redCandle1 || redCandle2) && greenConfirmation;
var greenCandle1 = _haClosePrev1 > _haOpenPrev1 && _haHighPrev1 >= _upperBbPrev1;
var greenCandle2 = _haClosePrev2 > _haOpenPrev2 && _haHighPrev2 >= _upperBbPrev2;
var redConfirmation = haClose < haOpen;
var sellSignal = (greenCandle1 || greenCandle2) && redConfirmation;
if (buySignal && Position == 0)
{
BuyMarket();
}
else if (sellSignal && Position == 0)
{
SellMarket();
}
}
Shift(haOpen, haClose, haHigh, haLow, upper, lower, candle);
}
private void Shift(decimal haOpen, decimal haClose, decimal haHigh, decimal haLow, decimal upper, decimal lower, ICandleMessage candle)
{
_haOpenPrev3 = _haOpenPrev2;
_haOpenPrev2 = _haOpenPrev1;
_haOpenPrev1 = haOpen;
_haClosePrev3 = _haClosePrev2;
_haClosePrev2 = _haClosePrev1;
_haClosePrev1 = haClose;
_haHighPrev3 = _haHighPrev2;
_haHighPrev2 = _haHighPrev1;
_haHighPrev1 = haHigh;
_haLowPrev3 = _haLowPrev2;
_haLowPrev2 = _haLowPrev1;
_haLowPrev1 = haLow;
_upperBbPrev3 = _upperBbPrev2;
_upperBbPrev2 = _upperBbPrev1;
_upperBbPrev1 = upper;
_lowerBbPrev3 = _lowerBbPrev2;
_lowerBbPrev2 = _lowerBbPrev1;
_lowerBbPrev1 = lower;
_prevRawLow = candle.LowPrice;
_prevRawHigh = candle.HighPrice;
}
}
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, UnitTypes, Unit
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
class bb_heikin_ashi_entry_strategy(Strategy):
def __init__(self):
super(bb_heikin_ashi_entry_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._bb_length = self.Param("BollingerLength", 20) \
.SetDisplay("Bollinger Length", "Period of Bollinger Bands", "Bollinger")
self._bb_width = self.Param("BollingerWidth", 2.0) \
.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Bollinger")
self._cooldown_bars = self.Param("CooldownBars", 20) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._ha_open_prev1 = 0.0
self._ha_close_prev1 = 0.0
self._ha_high_prev1 = 0.0
self._ha_low_prev1 = 0.0
self._ha_open_prev2 = 0.0
self._ha_close_prev2 = 0.0
self._ha_high_prev2 = 0.0
self._ha_low_prev2 = 0.0
self._upper_bb_prev1 = 0.0
self._lower_bb_prev1 = 0.0
self._upper_bb_prev2 = 0.0
self._lower_bb_prev2 = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
@cooldown_bars.setter
def cooldown_bars(self, value):
self._cooldown_bars.Value = value
def OnReseted(self):
super(bb_heikin_ashi_entry_strategy, self).OnReseted()
self._ha_open_prev1 = self._ha_open_prev2 = 0.0
self._ha_close_prev1 = self._ha_close_prev2 = 0.0
self._ha_high_prev1 = self._ha_high_prev2 = 0.0
self._ha_low_prev1 = self._ha_low_prev2 = 0.0
self._upper_bb_prev1 = self._upper_bb_prev2 = 0.0
self._lower_bb_prev1 = self._lower_bb_prev2 = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(bb_heikin_ashi_entry_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length = self._bb_length.Value
bb.Width = self._bb_width.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bb, self.OnProcess).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bb)
self.DrawOwnTrades(area)
def OnProcess(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
o = float(candle.OpenPrice)
h = float(candle.HighPrice)
l = float(candle.LowPrice)
c = float(candle.ClosePrice)
ha_close = (o + h + l + c) / 4.0
if self._ha_open_prev1 == 0:
ha_open = (o + c) / 2.0
else:
ha_open = (self._ha_open_prev1 + self._ha_close_prev1) / 2.0
ha_high = max(h, ha_open, ha_close)
ha_low = min(l, ha_open, ha_close)
bb = bb_value
upper = bb.UpBand
lower = bb.LowBand
if upper is None or lower is None:
self._shift(ha_open, ha_close, ha_high, ha_low, 0.0, 0.0)
return
upper_v = float(upper)
lower_v = float(lower)
if self._ha_open_prev1 != 0:
red1 = self._ha_close_prev1 < self._ha_open_prev1 and self._ha_low_prev1 <= self._lower_bb_prev1
red2 = self._ha_close_prev2 < self._ha_open_prev2 and self._ha_low_prev2 <= self._lower_bb_prev2
green_confirm = ha_close > ha_open
buy_signal = (red1 or red2) and green_confirm
green1 = self._ha_close_prev1 > self._ha_open_prev1 and self._ha_high_prev1 >= self._upper_bb_prev1
green2 = self._ha_close_prev2 > self._ha_open_prev2 and self._ha_high_prev2 >= self._upper_bb_prev2
red_confirm = ha_close < ha_open
sell_signal = (green1 or green2) and red_confirm
if buy_signal and self.Position == 0:
self.BuyMarket()
elif sell_signal and self.Position == 0:
self.SellMarket()
self._shift(ha_open, ha_close, ha_high, ha_low, upper_v, lower_v)
def _shift(self, ha_open, ha_close, ha_high, ha_low, upper, lower):
self._ha_open_prev2 = self._ha_open_prev1
self._ha_open_prev1 = ha_open
self._ha_close_prev2 = self._ha_close_prev1
self._ha_close_prev1 = ha_close
self._ha_high_prev2 = self._ha_high_prev1
self._ha_high_prev1 = ha_high
self._ha_low_prev2 = self._ha_low_prev1
self._ha_low_prev1 = ha_low
self._upper_bb_prev2 = self._upper_bb_prev1
self._upper_bb_prev1 = upper
self._lower_bb_prev2 = self._lower_bb_prev1
self._lower_bb_prev1 = lower
def CreateClone(self):
return bb_heikin_ashi_entry_strategy()