The MACD Signal Strategy ports the MetaTrader expert MACD_signal.mq4 to StockSharp. The original robot measured the
MACD histogram against an ATR-based volatility band and opened a single market order whenever the histogram crossed that
band. This C# version recreates the same momentum breakout logic using StockSharp's high-level API, stores the previous
histogram and ATR readings explicitly, and documents every money-management rule with named parameters and English
comments in the source code.
Unlike the MetaTrader implementation that directly modified tickets, the StockSharp port works with net positions. It
therefore closes the current exposure before flipping direction and updates trailing stops internally instead of relying on
broker-side OrderModify calls.
Trading logic
Subscribe to the configured candle series (CandleType) and process only finished candles to avoid partial-bar
noise.
Feed a MovingAverageConvergenceDivergenceSignal indicator with the chosen fast, slow, and signal EMA lengths. The
histogram value (MACD - signal) is stored every time a bar closes.
Compute the AverageTrueRange on the same candles. The value from the previous bar is multiplied by
ThresholdMultiplier to recreate the rr = ATR * LEVEL threshold from MQL.
Detect a bullish breakout when the current histogram exceeds +threshold while the previous histogram was still below
it. If the account is flat or short and long trading is allowed by Direction, send a market buy order sized by
TradeVolume.
Detect a bearish breakout when the histogram crosses beneath -threshold after being above it on the prior candle. If
the strategy is flat or long and short trading is enabled, issue a market sell order sized by TradeVolume.
Manage open positions every bar:
close longs as soon as the histogram turns negative; close shorts when it turns positive;
monitor the fixed take-profit distance (TakeProfitPoints) against candle highs or lows to emulate the original
MetaTrader take-profit parameter;
update trailing stops once price moves more than TrailingStopPoints away from the entry and exit if the candle revisits
the trailing level. The long stop trails the close as a proxy for the bid price, while the short stop trails the close as
a proxy for the ask price.
The EA refuses to trade when TakeProfitPoints is below the historical 10-point minimum, matching the protective check
present in the MQL code.
Risk management
Single order at a time. The strategy always net-flat before opening a new position, mirroring the original
OrdersTotal() < 1 requirement.
Fixed volume.TradeVolume replaces the Lots input and is also copied to Strategy.Volume so manual UI actions use
the same size.
Fixed take-profit.TakeProfitPoints converts the MQL point distance to the instrument tick size using
Security.PriceStep.
Indicator-based exit. A histogram sign flip triggers an immediate market exit, guaranteeing the EA does not stay in
the market when momentum reverses.
Trailing stop. Once price moves in favour of the trade by more than the configured number of steps, the stop is pulled
inside the profit zone and follows the close price while never moving backwards.
Parameters
Name
Type
Default
Description
TradeVolume
decimal
10
Order size (lots) used for every market entry and copied to Strategy.Volume.
TakeProfitPoints
int
10
Distance to the fixed take-profit target expressed in price steps. Values below 10
disable trading.
TrailingStopPoints
int
25
Distance in price steps for the trailing stop. Set to 0 to disable trailing.
FastPeriod
int
9
Length of the fast EMA inside the MACD indicator.
SlowPeriod
int
15
Length of the slow EMA inside the MACD indicator.
SignalPeriod
int
8
Length of the EMA used to smooth the MACD signal line.
ThresholdMultiplier
decimal
0.004
Multiplier applied to the previous-bar ATR to build the breakout band.
AtrPeriod
int
200
Number of candles used to compute the ATR volatility filter.
CandleType
DataType
30-minute timeframe
Primary timeframe processed by the strategy.
Differences from the original expert advisor
MetaTrader exposes AccountFreeMargin() and refused to trade if the value was too small. StockSharp strategies do not
have the same margin snapshot, so the port omits that check. Portfolio-level risk controls should be handled outside the
strategy when required.
The MQL version adjusted stop orders with OrderModify. StockSharp works with net positions, so the conversion manages
exits internally by monitoring candle highs/lows and the trailing stop variables.
MetaTrader counted "bars" manually and printed a warning when fewer than 100 candles were available. StockSharp relies on
indicator readiness (BindEx) so the strategy stays idle automatically until MACD and ATR have enough data.
The port stores the previous ATR and histogram values explicitly to reproduce the Delta/Delta1 threshold comparison
without violating StockSharp's rule against random indicator indexing.
Usage tips
Keep Security.PriceStep, Security.MinVolume, and Security.VolumeStep accurate so volume conversions and take-profit
calculations remain aligned with the exchange.
Increase ThresholdMultiplier or AtrPeriod when the strategy trades too frequently in choppy markets; decrease them to
make the system more sensitive to volatility breakouts.
Lower TradeVolume when running on leveraged or high-volatility instruments, because the original script assumed large
lot sizes on Forex symbols.
Combine the strategy with higher-timeframe filters through the built-in Direction property if you only want to allow
longs or shorts during specific market regimes.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD Signal ATR: EMA crossover with ATR stops.
/// </summary>
public class MacdSignalAtrStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public MacdSignalAtrStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 12)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 26)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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 ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class macd_signal_atr_strategy(Strategy):
"""
MACD Signal ATR: EMA crossover with ATR-based stops.
"""
def __init__(self):
super(macd_signal_atr_strategy, self).__init__()
self._fast_ema_length = self.Param("FastEmaLength", 12).SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 26).SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_signal_atr_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(macd_signal_atr_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self._fast_ema_length.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
atr = float(atr_val)
if self._prev_fast == 0.0 or self._prev_slow == 0.0 or atr <= 0:
self._prev_fast = fast
self._prev_slow = slow
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fast < slow and self._prev_fast >= self._prev_slow) or close <= self._entry_price - atr * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fast > slow and self._prev_fast <= self._prev_slow) or close >= self._entry_price + atr * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fast > slow and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fast < slow and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fast
self._prev_slow = slow
def CreateClone(self):
return macd_signal_atr_strategy()