MACD Breakout
The MACD Breakout strategy watches the MACD for sudden expansions. When readings jump beyond their normal range, price often starts a new move.
Testing indicates an average annual return of about 94%. It performs best in the stocks market.
A position opens once the indicator pierces a band derived from recent data and a deviation multiplier. Long and short trades are possible with a stop attached.
This system fits momentum traders seeking early breakouts. Trades close as the MACD falls back toward the mean. Defaults start with FastEmaPeriod = 12.
Details
- Entry Criteria: Indicator exceeds average by deviation multiplier.
- Long/Short: Both directions.
- Exit Criteria: Indicator reverts to average.
- Stops: Yes.
- Default Values:
FastEmaPeriod= 12SlowEmaPeriod= 26SignalPeriod= 9SmaPeriod= 20DeviationMultiplier= 2.0mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Breakout
- Direction: Both
- Indicators: MACD
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Short-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>
/// MACD Breakout Strategy that enters positions when MACD Histogram breaks out of its normal range.
/// </summary>
public class MacdBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd;
private SimpleMovingAverage _macdHistSma;
private StandardDeviation _macdHistStdDev;
private decimal _prevMacdHistValue;
private decimal _prevMacdHistSmaValue;
/// <summary>
/// MACD Fast EMA period.
/// </summary>
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
/// <summary>
/// MACD Slow EMA period.
/// </summary>
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
/// <summary>
/// MACD Signal line period.
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Period for MACD Histogram moving average.
/// </summary>
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for breakout threshold.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MacdBreakoutStrategy()
{
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Fast EMA Period", "Period for MACD fast EMA", "MACD Settings")
.SetOptimize(8, 20, 4);
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Slow EMA Period", "Period for MACD slow EMA", "MACD Settings")
.SetOptimize(20, 40, 4);
_signalPeriod = Param(nameof(SignalPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Signal Period", "Period for MACD signal line", "MACD Settings")
.SetOptimize(5, 15, 2);
_smaPeriod = Param(nameof(SmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "Period for MACD Histogram moving average", "Indicator Settings")
.SetOptimize(10, 30, 5);
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Deviation Multiplier", "Standard deviation multiplier for breakout threshold", "Breakout Settings")
.SetOptimize(1.0m, 3.0m, 0.5m);
_stopLossPercent = Param(nameof(StopLossPercent), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
.SetOptimize(1.0m, 4.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMacdHistSmaValue = default;
_prevMacdHistValue = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
// Initialize indicators
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = FastEmaPeriod },
LongMa = { Length = SlowEmaPeriod },
},
SignalMa = { Length = SignalPeriod }
};
_macdHistSma = new SMA { Length = SmaPeriod };
_macdHistStdDev = new StandardDeviation { Length = SmaPeriod };
// Create subscription and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, ProcessCandle)
.Start();
// Enable position protection
StartProtection(
new Unit(StopLossPercent, UnitTypes.Percent),
new Unit(StopLossPercent * 1.5m, UnitTypes.Percent));
// Setup chart visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _macd);
DrawOwnTrades(area);
}
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready for trading
if (!IsFormedAndOnlineAndAllowTrading())
return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
// Extract the histogram value (MACD Line - Signal Line)
if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal signal)
{
return;
}
// Process indicators for MACD histogram
var macdHistSmaValue = _macdHistSma.Process(new DecimalIndicatorValue(_macdHistSma, macd, candle.ServerTime)).ToDecimal();
var macdHistStdDevValue = _macdHistStdDev.Process(new DecimalIndicatorValue(_macdHistStdDev, macd, candle.ServerTime)).ToDecimal();
// Store previous values on first call
if (_prevMacdHistValue == 0 && _prevMacdHistSmaValue == 0)
{
_prevMacdHistValue = macd;
_prevMacdHistSmaValue = macdHistSmaValue;
return;
}
// Calculate breakout thresholds
var upperThreshold = macdHistSmaValue + DeviationMultiplier * macdHistStdDevValue;
var lowerThreshold = macdHistSmaValue - DeviationMultiplier * macdHistStdDevValue;
// Trading logic
if (macd > upperThreshold && Position <= 0)
{
// MACD Histogram broke above upper threshold - buy signal (long)
BuyMarket(Volume);
LogInfo($"Buy signal: MACD Hist({macd}) > Upper Threshold({upperThreshold})");
}
else if (macd < lowerThreshold && Position >= 0)
{
// MACD Histogram broke below lower threshold - sell signal (short)
SellMarket(Volume + Math.Abs(Position));
LogInfo($"Sell signal: MACD Hist({macd}) < Lower Threshold({lowerThreshold})");
}
// Exit conditions
else if (Position > 0 && macd < macdHistSmaValue)
{
// Exit long position when MACD Histogram returns below its mean
SellMarket(Math.Abs(Position));
LogInfo($"Exit long: MACD Hist({macd}) < SMA({macdHistSmaValue})");
}
else if (Position < 0 && macd > macdHistSmaValue)
{
// Exit short position when MACD Histogram returns above its mean
BuyMarket(Math.Abs(Position));
LogInfo($"Exit short: MACD Hist({macd}) > SMA({macdHistSmaValue})");
}
// Update previous values
_prevMacdHistValue = macd;
_prevMacdHistSmaValue = macdHistSmaValue;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class macd_breakout_strategy(Strategy):
"""
MACD Breakout: enters when MACD histogram breaks out of its normal range.
"""
def __init__(self):
super(macd_breakout_strategy, self).__init__()
self._fast_ema = self.Param("FastEmaPeriod", 12).SetDisplay("Fast EMA", "Fast EMA period", "MACD")
self._slow_ema = self.Param("SlowEmaPeriod", 26).SetDisplay("Slow EMA", "Slow EMA period", "MACD")
self._signal_period = self.Param("SignalPeriod", 9).SetDisplay("Signal Period", "Signal line period", "MACD")
self._sma_period = self.Param("SmaPeriod", 20).SetDisplay("SMA Period", "Histogram SMA period", "Indicators")
self._dev_mult = self.Param("DeviationMultiplier", 2.0).SetDisplay("Dev Mult", "Stddev multiplier", "Breakout")
self._sl_pct = self.Param("StopLossPercent", 2.0).SetDisplay("SL %", "Stop loss percent", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._macd_hist_sma = None
self._macd_hist_stddev = None
self._prev_macd_hist_value = 0.0
self._prev_macd_hist_sma_value = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_breakout_strategy, self).OnReseted()
self._prev_macd_hist_value = 0.0
self._prev_macd_hist_sma_value = 0.0
def OnStarted2(self, time):
super(macd_breakout_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._fast_ema.Value
macd.Macd.LongMa.Length = self._slow_ema.Value
macd.SignalMa.Length = self._signal_period.Value
self._macd_hist_sma = SimpleMovingAverage()
self._macd_hist_sma.Length = self._sma_period.Value
self._macd_hist_stddev = StandardDeviation()
self._macd_hist_stddev.Length = self._sma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, self._process_candle).Start()
sl = self._sl_pct.Value
self.StartProtection(Unit(sl, UnitTypes.Percent), Unit(sl * 1.5, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, macd)
self.DrawOwnTrades(area)
def _process_candle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
typed_val = macd_value
if typed_val.Macd is None or typed_val.Signal is None:
return
macd_val = float(typed_val.Macd)
macd_hist_sma_value = float(process_float(self._macd_hist_sma, macd_val, candle.ServerTime, True))
macd_hist_stddev_value = float(process_float(self._macd_hist_stddev, macd_val, candle.ServerTime, True))
# Store previous values on first call
if self._prev_macd_hist_value == 0.0 and self._prev_macd_hist_sma_value == 0.0:
self._prev_macd_hist_value = macd_val
self._prev_macd_hist_sma_value = macd_hist_sma_value
return
# Calculate breakout thresholds
dm = float(self._dev_mult.Value)
upper_threshold = macd_hist_sma_value + dm * macd_hist_stddev_value
lower_threshold = macd_hist_sma_value - dm * macd_hist_stddev_value
# Trading logic
if macd_val > upper_threshold and self.Position <= 0:
self.BuyMarket(self.Volume)
elif macd_val < lower_threshold and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
elif self.Position > 0 and macd_val < macd_hist_sma_value:
self.SellMarket(Math.Abs(self.Position))
elif self.Position < 0 and macd_val > macd_hist_sma_value:
self.BuyMarket(Math.Abs(self.Position))
self._prev_macd_hist_value = macd_val
self._prev_macd_hist_sma_value = macd_hist_sma_value
def CreateClone(self):
return macd_breakout_strategy()