Adx Bollinger Strategy
Strategy based on ADX and Bollinger Bands indicators. Enters long when ADX > 25 and price breaks above upper Bollinger band Enters short when ADX > 25 and price breaks below lower Bollinger band
Testing indicates an average annual return of about 115%. It performs best in the stocks market.
Bollinger band breaches filtered with ADX ensure price is breaking out with force. The system trades in the direction of the breakout.
Suited for high-volatility environments. An ATR-based stop reduces downside risk.
Details
- Entry Criteria:
- Long:
Close < LowerBand && ADX > 25 - Short:
Close > UpperBand && ADX > 25
- Long:
- Long/Short: Both
- Exit Criteria: Price returns to middle band
- Stops: ATR-based using
AtrMultiplier - Default Values:
AdxPeriod= 14BollingerPeriod= 20BollingerDeviation= 2.0mAtrPeriod= 14AtrMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: ADX, Bollinger Bands
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- 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>
/// Strategy based on ADX and Bollinger Bands indicators.
/// Enters long when ADX > 25 and price breaks above upper Bollinger band
/// Enters short when ADX > 25 and price breaks below lower Bollinger band
/// </summary>
public class AdxBollingerStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// ADX period
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands period
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands deviation
/// </summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>
/// ATR period for stop-loss calculation
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for stop-loss
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type for strategy calculation
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor
/// </summary>
public AdxBollingerStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period for ADX indicator", "Indicators")
.SetOptimize(10, 20, 2);
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Period for Bollinger Bands", "Indicators")
.SetOptimize(15, 30, 5);
_bollingerDeviation = Param(nameof(BollingerDeviation), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Deviation multiplier for Bollinger Bands", "Indicators")
.SetOptimize(1.5m, 2.5m, 0.5m);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR indicator for stop-loss", "Risk Management")
.SetOptimize(10, 20, 2);
_atrMultiplier = Param(nameof(AtrMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Multiplier for ATR-based stop-loss", "Risk Management")
.SetOptimize(1.5m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var bollinger = new BollingerBands
{
Length = BollingerPeriod,
Width = BollingerDeviation
};
var atr = new AverageTrueRange { Length = AtrPeriod };
// Subscribe to candles and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, bollinger, atr, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
// Create a separate area for ADX
var adxArea = CreateChartArea();
if (adxArea != null)
{
DrawIndicator(adxArea, adx);
}
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue, IIndicatorValue bollingerValue, IIndicatorValue atrValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Get additional values from Bollinger Bands
var bollingerValueTyped = (BollingerBandsValue)bollingerValue;
var upperBand = bollingerValueTyped.UpBand;
var lowerBand = bollingerValueTyped.LowBand;
var middleBand = (upperBand - lowerBand) / 2 + lowerBand;
// Current price (close of the candle)
var price = candle.ClosePrice;
// Stop-loss size based on ATR
var stopSize = atrValue.ToDecimal() * AtrMultiplier;
var adxValueTyped = (AverageDirectionalIndexValue)adxValue;
// Trading logic
if (adxValueTyped.MovingAverage > 25) // Strong trend
{
if (price > upperBand && Position <= 0)
{
// Buy signal: price above upper Bollinger band with strong trend
BuyMarket(Volume + Math.Abs(Position));
// Set stop-loss
var stopPrice = price - stopSize;
RegisterOrder(CreateOrder(Sides.Sell, stopPrice, Math.Abs(Position + Volume).Max(Volume)));
}
else if (price < lowerBand && Position >= 0)
{
// Sell signal: price below lower Bollinger band with strong trend
SellMarket(Volume + Math.Abs(Position));
// Set stop-loss
var stopPrice = price + stopSize;
RegisterOrder(CreateOrder(Sides.Buy, stopPrice, Math.Abs(Position + Volume).Max(Volume)));
}
}
// Exit conditions
else if (adxValueTyped.MovingAverage < 20)
{
// Trend is weakening - close any position
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
}
// Also exit when price returns to middle band
else if (price < middleBand && Position > 0)
{
// Exit long position when price returns to middle band
SellMarket(Position);
}
else if (price > middleBand && Position < 0)
{
// Exit short position when price returns to middle band
BuyMarket(Math.Abs(Position));
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Indicators import AverageDirectionalIndex, BollingerBands, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class adx_bollinger_strategy(Strategy):
"""
Strategy based on ADX and Bollinger Bands indicators.
Enters long when ADX > 25 and price breaks above upper Bollinger band
Enters short when ADX > 25 and price breaks below lower Bollinger band
"""
def __init__(self):
super(adx_bollinger_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.SetDisplay("ADX Period", "Period for ADX indicator", "Indicators")
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetDisplay("Bollinger Period", "Period for Bollinger Bands", "Indicators")
self._bollinger_deviation = self.Param("BollingerDeviation", 2.0) \
.SetDisplay("Bollinger Deviation", "Deviation multiplier for Bollinger Bands", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR indicator for stop-loss", "Risk Management")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0) \
.SetDisplay("ATR Multiplier", "Multiplier for ATR-based stop-loss", "Risk Management")
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Timeframe for strategy", "General")
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adx_bollinger_strategy, self).OnReseted()
def OnStarted2(self, time):
super(adx_bollinger_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
bollinger = BollingerBands()
bollinger.Length = self._bollinger_period.Value
bollinger.Width = self._bollinger_deviation.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, bollinger, atr, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
adx_area = self.CreateChartArea()
if adx_area is not None:
self.DrawIndicator(adx_area, adx)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, adx_value, bollinger_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None:
return
adx_ma = adx_value.MovingAverage
if adx_ma is None:
return
adx_ma_f = float(adx_ma)
upper_band = float(bollinger_value.UpBand)
lower_band = float(bollinger_value.LowBand)
middle_band = (upper_band - lower_band) / 2.0 + lower_band
price = float(candle.ClosePrice)
stop_size = float(atr_value) * float(self._atr_multiplier.Value)
# Trading logic
if adx_ma_f > 25: # Strong trend
if price > upper_band and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
stop_price = price - stop_size
stop_vol = max(abs(self.Position + self.Volume), self.Volume)
self.RegisterOrder(self.CreateOrder(Sides.Sell, stop_price, stop_vol))
elif price < lower_band and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
stop_price = price + stop_size
stop_vol = max(abs(self.Position + self.Volume), self.Volume)
self.RegisterOrder(self.CreateOrder(Sides.Buy, stop_price, stop_vol))
elif adx_ma_f < 20:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
elif price < middle_band and self.Position > 0:
self.SellMarket(self.Position)
elif price > middle_band and self.Position < 0:
self.BuyMarket(abs(self.Position))
def CreateClone(self):
return adx_bollinger_strategy()