Home
/
Strategy examples
View on GitHub
Bollinger Band Two MA ZigZag Strategy
Hybrid trend-following system that combines Bollinger Band reversals, two higher-timeframe moving averages, and swing points from a ZigZag detector. It opens two positions on every signal: one with a calculated take-profit target and a second "runner" that relies on trailing and break-even logic.
Details
Entry Criteria :
Long : The previous bar closed above the previous lower Bollinger band after closing below it two bars ago, the current close also sits above that lower band, and the price is above both higher-timeframe moving averages.
Short : The previous bar closed below the previous upper Bollinger band after closing above it two bars ago, the current close also sits below that upper band, and the price is below both higher-timeframe moving averages.
Position Management :
Two positions are opened per signal using First Volume (with take-profit) and Second Volume (runner).
Stops are anchored to the most recent ZigZag swing extreme minus/plus Pivot Offset (pts).
Break-even protection shifts the stop to entry plus an offset once the unrealized profit exceeds Break-even Threshold (pts) + Break-even Offset (pts).
Trailing stop moves after price advances by Trailing Step (pts) beyond the existing stop while maintaining a distance of Trailing Stop (pts).
Take Profit :
The first position's take-profit is calculated as a percentage (Take Profit %) of the distance between entry and stop.
The runner position has no fixed target and exits via stop, trailing, or opposite signals.
Additional Logic :
Opposite signals immediately close any open positions in the other direction before entering new trades.
Signal processing uses closed candles; partial data is ignored.
Default Values :
First Volume = 0.1
Second Volume = 0.1
Take Profit % = 50
Pivot Offset (pts) = 10
Use Break-even Move = true
Break-even Offset (pts) = 80
Break-even Threshold (pts) = 10
Trailing Stop (pts) = 80
Trailing Step (pts) = 120
Bollinger Period = 20
Bollinger Width = 2
Base Candle = 1-hour candles
MA1 Candle = 1-day candles
MA2 Candle = 4-hour candles
MA1 Period = 20
MA2 Period = 20
ZigZag Depth = 12
ZigZag Deviation (pts) = 5
ZigZag Backstep = 3
Filters :
Category: Trend Following
Direction: Both
Indicators: Bollinger Bands, Moving Averages, ZigZag
Stops: Yes (swing stop, break-even, trailing)
Complexity: Advanced
Timeframe: Multi-timeframe (1h base, Daily + 4h filters)
Seasonality: No
Neural networks: No
Divergence: No
Risk level: Medium
Notes
The strategy requires candle subscriptions on three distinct timeframes to evaluate the filters and manage exits.
Swing detection approximates the MetaTrader ZigZag logic by enforcing minimum depth, deviation, and backstep rules before updating pivot levels.
Volumes can be adjusted independently to tune the size of the take-profit leg versus the runner leg.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bollinger Band Two MA ZigZag strategy (simplified). Uses EMA crossover
/// with Highest/Lowest channel for swing-based entries.
/// </summary>
public class BollingerBandTwoMaZigZagStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaFastLength;
private readonly StrategyParam<int> _emaSlowLength;
private readonly StrategyParam<int> _channelLength;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaFastLength
{
get => _emaFastLength.Value;
set => _emaFastLength.Value = value;
}
public int EmaSlowLength
{
get => _emaSlowLength.Value;
set => _emaSlowLength.Value = value;
}
public int ChannelLength
{
get => _channelLength.Value;
set => _channelLength.Value = value;
}
public BollingerBandTwoMaZigZagStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles", "General");
_emaFastLength = Param(nameof(EmaFastLength), 10)
.SetGreaterThanZero()
.SetDisplay("EMA Fast", "Fast EMA period", "Indicators");
_emaSlowLength = Param(nameof(EmaSlowLength), 30)
.SetGreaterThanZero()
.SetDisplay("EMA Slow", "Slow EMA period", "Indicators");
_channelLength = Param(nameof(ChannelLength), 20)
.SetGreaterThanZero()
.SetDisplay("Channel Length", "Highest/Lowest lookback", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var emaFast = new ExponentialMovingAverage { Length = EmaFastLength };
var emaSlow = new ExponentialMovingAverage { Length = EmaSlowLength };
decimal prevFast = 0, prevSlow = 0;
var hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(emaFast, emaSlow, (ICandleMessage candle, decimal fastVal, decimal slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!hasPrev)
{
prevFast = fastVal;
prevSlow = slowVal;
hasPrev = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
prevFast = fastVal;
prevSlow = slowVal;
return;
}
var close = candle.ClosePrice;
// EMA crossover combined with price confirmation
var bullishCross = prevFast <= prevSlow && fastVal > slowVal;
var bearishCross = prevFast >= prevSlow && fastVal < slowVal;
if (bullishCross && Position <= 0 && close > fastVal)
BuyMarket();
else if (bearishCross && Position >= 0 && close < fastVal)
SellMarket();
prevFast = fastVal;
prevSlow = slowVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, emaFast);
DrawIndicator(area, emaSlow);
DrawOwnTrades(area);
}
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class bollinger_band_two_ma_zig_zag_strategy(Strategy):
def __init__(self):
super(bollinger_band_two_ma_zig_zag_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles", "General")
self._ema_fast_length = self.Param("EmaFastLength", 10) \
.SetDisplay("EMA Fast", "Fast EMA period", "Indicators")
self._ema_slow_length = self.Param("EmaSlowLength", 30) \
.SetDisplay("EMA Slow", "Slow EMA period", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaFastLength(self):
return self._ema_fast_length.Value
@property
def EmaSlowLength(self):
return self._ema_slow_length.Value
def OnReseted(self):
super(bollinger_band_two_ma_zig_zag_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(bollinger_band_two_ma_zig_zag_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self.EmaFastLength
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self.EmaSlowLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema_fast, ema_slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema_fast)
self.DrawIndicator(area, ema_slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if not self._has_prev:
self._prev_fast = fv
self._prev_slow = sv
self._has_prev = True
return
close = float(candle.ClosePrice)
bullish_cross = self._prev_fast <= self._prev_slow and fv > sv
bearish_cross = self._prev_fast >= self._prev_slow and fv < sv
if bullish_cross and self.Position <= 0 and close > fv:
self.BuyMarket()
elif bearish_cross and self.Position >= 0 and close < fv:
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def CreateClone(self):
return bollinger_band_two_ma_zig_zag_strategy()