Dual MACD
Strategy that combines two MACD indicators. The slower MACD crossing zero enters trades when the faster MACD histogram aligns. The position closes when the fast MACD turns against it or the stop/take profit triggers.
Testing indicates an average annual return of about 65%. It performs best in the stocks market.
Details
- Entry Criteria: Slow MACD histogram crossing zero with fast MACD confirmation.
- Long/Short: Both directions.
- Exit Criteria: Fast MACD reversal or stop/target.
- Stops: Yes.
- Default Values:
Macd1FastLength= 34Macd1SlowLength= 144Macd1SignalLength= 9Macd2FastLength= 100Macd2SlowLength= 200Macd2SignalLength= 50StopLossPercent= 1.0mTakeProfitPercent= 1.5mCandleType= TimeSpan.FromMinutes(15)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: MACD
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (15m)
- 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>
/// Dual MACD strategy.
/// MACD 1 is the fast MACD and MACD 2 the slow one; a histogram is the MACD line minus its signal line. When the slow histogram
/// crosses above zero while the fast histogram is positive the strategy goes long, and when it crosses below zero while the fast
/// histogram is negative it goes short, reversing an opposite position. A long closes when the fast histogram turns negative and
/// a short when it turns positive. Percent stop loss and take profit protect the position.
/// </summary>
public class DualMacdStrategy : Strategy
{
private readonly StrategyParam<int> _macd1FastLength;
private readonly StrategyParam<int> _macd1SlowLength;
private readonly StrategyParam<int> _macd1SignalLength;
private readonly StrategyParam<int> _macd2FastLength;
private readonly StrategyParam<int> _macd2SlowLength;
private readonly StrategyParam<int> _macd2SignalLength;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevSlowHist;
/// <summary>
/// Fast period of the fast MACD.
/// </summary>
public int Macd1FastLength
{
get => _macd1FastLength.Value;
set => _macd1FastLength.Value = value;
}
/// <summary>
/// Slow period of the fast MACD.
/// </summary>
public int Macd1SlowLength
{
get => _macd1SlowLength.Value;
set => _macd1SlowLength.Value = value;
}
/// <summary>
/// Signal period of the fast MACD.
/// </summary>
public int Macd1SignalLength
{
get => _macd1SignalLength.Value;
set => _macd1SignalLength.Value = value;
}
/// <summary>
/// Fast period of the slow MACD.
/// </summary>
public int Macd2FastLength
{
get => _macd2FastLength.Value;
set => _macd2FastLength.Value = value;
}
/// <summary>
/// Slow period of the slow MACD.
/// </summary>
public int Macd2SlowLength
{
get => _macd2SlowLength.Value;
set => _macd2SlowLength.Value = value;
}
/// <summary>
/// Signal period of the slow MACD.
/// </summary>
public int Macd2SignalLength
{
get => _macd2SignalLength.Value;
set => _macd2SignalLength.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Take profit percentage from entry price.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DualMacdStrategy()
{
_macd1FastLength = Param(nameof(Macd1FastLength), 34)
.SetGreaterThanZero()
.SetDisplay("MACD1 Fast", "Fast period of the fast MACD", "MACD 1");
_macd1SlowLength = Param(nameof(Macd1SlowLength), 144)
.SetGreaterThanZero()
.SetDisplay("MACD1 Slow", "Slow period of the fast MACD", "MACD 1");
_macd1SignalLength = Param(nameof(Macd1SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("MACD1 Signal", "Signal period of the fast MACD", "MACD 1");
_macd2FastLength = Param(nameof(Macd2FastLength), 100)
.SetGreaterThanZero()
.SetDisplay("MACD2 Fast", "Fast period of the slow MACD", "MACD 2");
_macd2SlowLength = Param(nameof(Macd2SlowLength), 200)
.SetGreaterThanZero()
.SetDisplay("MACD2 Slow", "Slow period of the slow MACD", "MACD 2");
_macd2SignalLength = Param(nameof(Macd2SignalLength), 50)
.SetGreaterThanZero()
.SetDisplay("MACD2 Signal", "Signal period of the slow MACD", "MACD 2");
_stopLossPercent = Param(nameof(StopLossPercent), 1.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 1.5m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_prevSlowHist = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevSlowHist = null;
var fastMacd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = Macd1FastLength },
LongMa = { Length = Macd1SlowLength },
},
SignalMa = { Length = Macd1SignalLength }
};
var slowMacd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = Macd2FastLength },
LongMa = { Length = Macd2SlowLength },
},
SignalMa = { Length = Macd2SignalLength }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(fastMacd, slowMacd, ProcessCandle)
.Start();
var take = TakeProfitPercent > 0 ? new Unit(TakeProfitPercent, UnitTypes.Percent) : new Unit();
var stop = StopLossPercent > 0 ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit();
StartProtection(take, stop, useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, fastMacd);
DrawIndicator(oscillators, slowMacd);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastValue, IIndicatorValue slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!fastValue.IsFormed || fastValue is not MovingAverageConvergenceDivergenceSignalValue { Macd: decimal fastMacd, Signal: decimal fastSignal })
return;
if (!slowValue.IsFormed || slowValue is not MovingAverageConvergenceDivergenceSignalValue { Macd: decimal slowMacd, Signal: decimal slowSignal })
return;
var fastHist = fastMacd - fastSignal;
var slowHist = slowMacd - slowSignal;
var prevSlowHist = _prevSlowHist;
_prevSlowHist = slowHist;
if (prevSlowHist is not decimal prev)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var crossUp = prev <= 0 && slowHist > 0;
var crossDown = prev >= 0 && slowHist < 0;
if (crossUp && fastHist > 0 && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (crossDown && fastHist < 0 && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && fastHist < 0)
SellMarket(Position);
else if (Position < 0 && fastHist > 0)
BuyMarket(-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, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class dual_macd_strategy(Strategy):
"""
Dual MACD strategy.
MACD 1 is the fast MACD and MACD 2 the slow one; a histogram is the MACD line minus its signal line. When the slow histogram
crosses above zero while the fast histogram is positive the strategy goes long, and when it crosses below zero while the fast
histogram is negative it goes short, reversing an opposite position. A long closes when the fast histogram turns negative and
a short when it turns positive. Percent stop loss and take profit protect the position.
"""
def __init__(self):
super(dual_macd_strategy, self).__init__()
self._macd1_fast_length = self.Param("Macd1FastLength", 34).SetGreaterThanZero().SetDisplay("MACD1 Fast", "Fast period of the fast MACD", "MACD 1")
self._macd1_slow_length = self.Param("Macd1SlowLength", 144).SetGreaterThanZero().SetDisplay("MACD1 Slow", "Slow period of the fast MACD", "MACD 1")
self._macd1_signal_length = self.Param("Macd1SignalLength", 9).SetGreaterThanZero().SetDisplay("MACD1 Signal", "Signal period of the fast MACD", "MACD 1")
self._macd2_fast_length = self.Param("Macd2FastLength", 100).SetGreaterThanZero().SetDisplay("MACD2 Fast", "Fast period of the slow MACD", "MACD 2")
self._macd2_slow_length = self.Param("Macd2SlowLength", 200).SetGreaterThanZero().SetDisplay("MACD2 Slow", "Slow period of the slow MACD", "MACD 2")
self._macd2_signal_length = self.Param("Macd2SignalLength", 50).SetGreaterThanZero().SetDisplay("MACD2 Signal", "Signal period of the slow MACD", "MACD 2")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 1.5).SetNotNegative().SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_slow_hist = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(dual_macd_strategy, self).OnReseted()
self._prev_slow_hist = None
def OnStarted2(self, time):
super(dual_macd_strategy, self).OnStarted2(time)
self._prev_slow_hist = None
fast_macd = MovingAverageConvergenceDivergenceSignal()
fast_macd.Macd.ShortMa.Length = self._macd1_fast_length.Value
fast_macd.Macd.LongMa.Length = self._macd1_slow_length.Value
fast_macd.SignalMa.Length = self._macd1_signal_length.Value
slow_macd = MovingAverageConvergenceDivergenceSignal()
slow_macd.Macd.ShortMa.Length = self._macd2_fast_length.Value
slow_macd.Macd.LongMa.Length = self._macd2_slow_length.Value
slow_macd.SignalMa.Length = self._macd2_signal_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(fast_macd, slow_macd, self._process_candle).Start()
take_percent = Decimal(self._take_profit_percent.Value)
stop_percent = Decimal(self._stop_loss_percent.Value)
take = Unit(take_percent, UnitTypes.Percent) if take_percent > 0 else Unit()
stop = Unit(stop_percent, UnitTypes.Percent) if stop_percent > 0 else Unit()
self.StartProtection(take, stop, useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, fast_macd)
self.DrawIndicator(oscillators, slow_macd)
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
if not fast_value.IsFormed or fast_value.Macd is None or fast_value.Signal is None:
return
if not slow_value.IsFormed or slow_value.Macd is None or slow_value.Signal is None:
return
fast_hist = fast_value.Macd - fast_value.Signal
slow_hist = slow_value.Macd - slow_value.Signal
prev = self._prev_slow_hist
self._prev_slow_hist = slow_hist
if prev is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
cross_up = prev <= 0 and slow_hist > 0
cross_down = prev >= 0 and slow_hist < 0
if cross_up and fast_hist > 0 and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif cross_down and fast_hist < 0 and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and fast_hist < 0:
self.SellMarket(self.Position)
elif self.Position < 0 and fast_hist > 0:
self.BuyMarket(-self.Position)
def CreateClone(self):
return dual_macd_strategy()