This strategy is a StockSharp port of the MetaTrader 4 expert AG.mq4. The robot operates with two Moving Average Convergence Divergence (MACD) calculations that use different parameter sets. The primary MACD produces entry triggers, while the secondary (scaled) MACD acts as a directional filter to avoid counter-trend trades and to control exits. The logic mirrors the original MQL4 expert by evaluating only closed candles and by reusing the signal line sign checks that gated the original orders.
Trading Logic
Indicators
Primary MACD: fast EMA = FastEmaLength, slow EMA = SlowEmaLength, signal SMA = SignalSmaLength.
Secondary MACD: fast EMA = SlowEmaLength * 2, slow EMA = FastEmaLength * 2, signal SMA = SignalSmaLength * 2.
Long entry
Primary MACD main line is above its signal line.
Primary MACD signal line is negative (below the waterline).
Secondary MACD main line is above its signal line.
Secondary MACD signal line is negative.
Short entry
Primary MACD main line is below its signal line.
Primary MACD signal line is positive.
Secondary MACD main line is below its signal line.
Secondary MACD signal line is positive.
Exit rules
Close long positions when the secondary MACD turns bearish while the primary signal line stays above zero.
Close short positions when the secondary MACD turns bullish while the primary signal line stays below zero.
The strategy only reacts to finished candles and ignores unfinished bars to avoid repainting.
Position Management
All orders are market orders with the fixed volume defined by OrderVolume.
MaxOpenOrders mirrors the original ORDER input and caps the total number of active orders plus open positions. Set it to 0 to remove the cap.
StartProtection() is enabled once the strategy starts so the StockSharp risk manager can monitor open exposure.
Parameters
Name
Description
OrderVolume
Base lot size for new trades.
FastEmaLength
Fast EMA period of the primary MACD.
SlowEmaLength
Slow EMA period of the primary MACD.
SignalSmaLength
Signal smoothing period for both MACDs.
MaxOpenOrders
Maximum number of combined active orders and open positions. Set 0 for unlimited.
CandleType
Time frame used to build candles for both indicators.
Notes
The secondary MACD keeps the same fast/slow order as in the original EA, even if the fast period becomes larger than the slow one, to preserve the author's calculations.
The strategy does not place pending orders; it opens or closes at market as soon as the conditions appear.
No additional stop-loss or take-profit levels are added because the original expert relied exclusively on signal reversals.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// AG MACD Dual strategy - MACD histogram crossover with EMA trend filter.
/// Buys when MACD histogram crosses above zero while price is above EMA.
/// Sells when MACD histogram crosses below zero while price is below EMA.
/// </summary>
public class AgMacdDualStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevMacd;
private decimal _prevSignal;
private bool _hasPrev;
private decimal _currentEma;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AgMacdDualStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 50)
.SetDisplay("EMA Period", "EMA trend filter", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevMacd = 0m; _prevSignal = 0m; _hasPrev = false; _currentEma = 0m; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var macd = new MovingAverageConvergenceDivergenceSignal();
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ProcessMacd)
.Bind(ema, ProcessEma)
.Start();
}
private void ProcessEma(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished)
return;
_currentEma = ema;
}
private void ProcessMacd(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!value.IsFinal || value.IsEmpty)
return;
var macdVal = value as MovingAverageConvergenceDivergenceSignalValue;
if (macdVal == null)
return;
var macdLine = macdVal.Macd;
var signalLine = macdVal.Signal;
if (macdLine == null || signalLine == null)
return;
var histogram = macdLine.Value - signalLine.Value;
if (!_hasPrev)
{
_prevMacd = macdLine.Value;
_prevSignal = signalLine.Value;
_hasPrev = true;
return;
}
var prevHist = _prevMacd - _prevSignal;
var close = candle.ClosePrice;
if (prevHist <= 0 && histogram > 0 && close > _currentEma && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (prevHist >= 0 && histogram < 0 && close < _currentEma && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevMacd = macdLine.Value;
_prevSignal = signalLine.Value;
}
}
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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ag_macd_dual_strategy(Strategy):
def __init__(self):
super(ag_macd_dual_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 50).SetDisplay("EMA Period", "EMA trend filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_macd = 0.0; self._prev_signal = 0.0; self._has_prev = False; self._current_ema = 0.0
@property
def ema_period(self): return self._ema_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(ag_macd_dual_strategy, self).OnReseted()
self._prev_macd = 0.0; self._prev_signal = 0.0; self._has_prev = False; self._current_ema = 0.0
def OnStarted2(self, time):
super(ag_macd_dual_strategy, self).OnStarted2(time)
self._has_prev = False
macd = MovingAverageConvergenceDivergenceSignal()
ema = ExponentialMovingAverage(); ema.Length = self.ema_period
sub = self.SubscribeCandles(self.candle_type)
sub.BindEx(macd, self.process_macd).Bind(ema, self.process_ema).Start()
def process_ema(self, candle, ema):
if candle.State != CandleStates.Finished: return
self._current_ema = float(ema)
def process_macd(self, candle, value):
if candle.State != CandleStates.Finished: return
if not value.IsFinal or value.IsEmpty: return
if value.Macd is None or value.Signal is None: return
ml = float(value.Macd); sl = float(value.Signal); hist = ml - sl
if not self._has_prev: self._prev_macd = ml; self._prev_signal = sl; self._has_prev = True; return
prev_hist = self._prev_macd - self._prev_signal; close = float(candle.ClosePrice)
if prev_hist <= 0 and hist > 0 and close > self._current_ema and self.Position <= 0:
if self.Position < 0: self.BuyMarket()
self.BuyMarket()
elif prev_hist >= 0 and hist < 0 and close < self._current_ema and self.Position >= 0:
if self.Position > 0: self.SellMarket()
self.SellMarket()
self._prev_macd = ml; self._prev_signal = sl
def CreateClone(self): return ag_macd_dual_strategy()