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);
		}
	}
}