MACD Crossover Strategy
Strategy based on MACD crossover within specified zone.
MACD Crossover Strategy waits for the MACD line to cross the signal line while the MACD value stays between lower and upper thresholds. Opposite crossover closes the existing position. No stop-loss is applied.
Details
- Entry Criteria: MACD crossover within zone.
- Long/Short: Both directions.
- Exit Criteria: Opposite crossover.
- Stops: No.
- Default Values:
FastLength= 12SlowLength= 26SignalLength= 9LowerThreshold= -0.5mUpperThreshold= 0.5mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: MACD
- Stops: No
- Complexity: Basic
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// MACD crossover strategy within predefined zone.
/// Goes long when MACD crosses above signal line inside the zone and short on opposite condition.
/// Closes positions on opposite crossovers regardless of zone.
/// </summary>
public class MacdCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<decimal> _lowerThreshold;
private readonly StrategyParam<decimal> _upperThreshold;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private bool _prevIsMacdAboveSignal;
private int _barsFromSignal;
/// <summary>
/// Fast EMA period for MACD.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA period for MACD.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Signal line period for MACD.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// Lower bound of important MACD zone.
/// </summary>
public decimal LowerThreshold
{
get => _lowerThreshold.Value;
set => _lowerThreshold.Value = value;
}
/// <summary>
/// Upper bound of important MACD zone.
/// </summary>
public decimal UpperThreshold
{
get => _upperThreshold.Value;
set => _upperThreshold.Value = value;
}
/// <summary>
/// Minimum bars between executed signals.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="MacdCrossoverStrategy"/>.
/// </summary>
public MacdCrossoverStrategy()
{
_fastLength = Param(nameof(FastLength), 12)
.SetGreaterThanZero()
.SetDisplay("Fast Length", "Fast EMA period", "MACD Settings")
.SetOptimize(8, 16, 2);
_slowLength = Param(nameof(SlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("Slow Length", "Slow EMA period", "MACD Settings")
.SetOptimize(20, 32, 2);
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "Signal line period", "MACD Settings")
.SetOptimize(5, 13, 2);
_lowerThreshold = Param(nameof(LowerThreshold), -100m)
.SetDisplay("Lower Threshold", "Lower bound for MACD zone", "MACD Zone")
.SetOptimize(-1m, 0m, 0.1m);
_upperThreshold = Param(nameof(UpperThreshold), 100m)
.SetDisplay("Upper Threshold", "Upper bound for MACD zone", "MACD Zone")
.SetOptimize(0m, 1m, 0.1m);
_signalCooldownBars = Param(nameof(SignalCooldownBars), 3)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Minimum bars between trade signals", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(10).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();
_prevIsMacdAboveSignal = false;
_barsFromSignal = int.MaxValue;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = FastLength },
LongMa = { Length = SlowLength },
},
SignalMa = { Length = SignalLength }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, macd);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (macdValue is not IMovingAverageConvergenceDivergenceSignalValue macdTyped)
return;
var macd = macdTyped.Macd ?? 0m;
var signal = macdTyped.Signal ?? 0m;
var isMacdAboveSignal = macd > signal;
var crossUp = isMacdAboveSignal && !_prevIsMacdAboveSignal;
var crossDown = !isMacdAboveSignal && _prevIsMacdAboveSignal;
var inZone = macd >= LowerThreshold && macd <= UpperThreshold;
_barsFromSignal++;
if (_barsFromSignal >= SignalCooldownBars)
{
if (crossUp)
{
if (Position < 0)
{
BuyMarket();
_barsFromSignal = 0;
}
else if (inZone && Position == 0)
{
BuyMarket();
_barsFromSignal = 0;
}
}
else if (crossDown)
{
if (Position > 0)
{
SellMarket();
_barsFromSignal = 0;
}
else if (inZone && Position == 0)
{
SellMarket();
_barsFromSignal = 0;
}
}
}
_prevIsMacdAboveSignal = isMacdAboveSignal;
}
}
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 MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class macd_crossover_strategy(Strategy):
"""
MACD crossover within predefined zone. Goes long on cross above signal in zone.
"""
def __init__(self):
super(macd_crossover_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12).SetDisplay("Fast Length", "Fast EMA period", "MACD")
self._slow_length = self.Param("SlowLength", 26).SetDisplay("Slow Length", "Slow EMA period", "MACD")
self._signal_length = self.Param("SignalLength", 9).SetDisplay("Signal Length", "Signal line period", "MACD")
self._lower_threshold = self.Param("LowerThreshold", -100.0).SetDisplay("Lower Threshold", "Lower bound for MACD zone", "Zone")
self._upper_threshold = self.Param("UpperThreshold", 100.0).SetDisplay("Upper Threshold", "Upper bound for MACD zone", "Zone")
self._cooldown_bars = self.Param("SignalCooldownBars", 3).SetDisplay("Cooldown Bars", "Bars between signals", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(10))).SetDisplay("Candle Type", "Timeframe", "General")
self._prev_above = False
self._bars_from_signal = 9999
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_crossover_strategy, self).OnReseted()
self._prev_above = False
self._bars_from_signal = 9999
def OnStarted2(self, time):
super(macd_crossover_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._fast_length.Value
macd.Macd.LongMa.Length = self._slow_length.Value
macd.SignalMa.Length = self._signal_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, self._process_candle).Start()
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
typed_val = macd_value
macd_line = typed_val.Macd
signal_line = typed_val.Signal
if macd_line is None or signal_line is None:
return
macd_f = float(macd_line)
signal_f = float(signal_line)
is_above = macd_f > signal_f
cross_up = is_above and not self._prev_above
cross_down = not is_above and self._prev_above
in_zone = macd_f >= self._lower_threshold.Value and macd_f <= self._upper_threshold.Value
self._bars_from_signal += 1
if self._bars_from_signal >= self._cooldown_bars.Value:
if cross_up:
if self.Position < 0:
self.BuyMarket()
self._bars_from_signal = 0
elif in_zone and self.Position == 0:
self.BuyMarket()
self._bars_from_signal = 0
elif cross_down:
if self.Position > 0:
self.SellMarket()
self._bars_from_signal = 0
elif in_zone and self.Position == 0:
self.SellMarket()
self._bars_from_signal = 0
self._prev_above = is_above
def CreateClone(self):
return macd_crossover_strategy()