This strategy is a direct port of the classic MetaTrader 5 MACD Sample expert advisor. It uses MACD crossovers filtered by an EMA trend detector. Orders are sized with the standard Volume property, while risk management relies on configurable pip thresholds for the MACD histogram, take profit and trailing stop.
Core logic
Indicators
MovingAverageConvergenceDivergenceSignal with periods (12, 26, 9) provides MACD and signal lines.
ExponentialMovingAverage with period 26 acts as the trend filter.
Entry rules
Long: MACD is below zero, crosses above the signal line, has magnitude above the MACD Open Level, and the EMA is rising.
Short: MACD is above zero, crosses below the signal line, has magnitude above the MACD Open Level, and the EMA is falling.
Exit rules
MACD crosses against the position with magnitude above the MACD Close Level.
Take profit reaches the configured pip distance from the entry price.
Trailing stop (if activated by profit > trailing distance) is hit.
Trailing stop mechanics
Long positions activate the trailing stop once the high price exceeds the entry price by the trailing distance. The stop is then maintained at high − trailing distance.
Short positions activate the trailing stop once the low price moves below the entry price by the trailing distance. The stop is maintained at low + trailing distance.
Parameters
Parameter
Default
Description
FastPeriod
12
Fast EMA period inside MACD.
SlowPeriod
26
Slow EMA period inside MACD.
SignalPeriod
9
Signal EMA period inside MACD.
TrendPeriod
26
Length of the EMA trend filter.
MacdOpenLevelPips
3
Minimum MACD magnitude (in pips) required to open a trade.
MacdCloseLevelPips
2
Minimum MACD magnitude (in pips) required to close a trade on crossover.
TakeProfitPips
50
Take-profit distance expressed in pips.
TrailingStopPips
30
Trailing stop distance expressed in pips. Set to 0 to disable trailing.
CandleType
15-minute time frame
Candle type used for calculations.
Pip conversion
The original expert used MetaTrader's pip normalization (multiplying by 10 for 3/5-digit symbols). The conversion follows the same idea by inspecting Security.PriceStep:
If the price step has 3 or 5 decimal places, the pip size is PriceStep * 10.
Otherwise, the pip size equals PriceStep.
When the price step is unavailable, pip-based thresholds fall back to raw values.
Behavioural notes
Positions are closed before new signals are evaluated, mirroring the MT5 implementation.
LogInfo statements report entries, exits, and trailing stop updates for easier debugging.
Protective orders are not placed automatically; exits are managed inside ProcessCandle to imitate the EA's logic.
Use Volume to define the base trade size. Reversals automatically offset the current exposure by adding Math.Abs(Position) to the order volume.
Differences from the MQL5 version
Processing occurs on finished candles instead of every tick. This avoids repeated signals while maintaining deterministic behaviour.
Trailing stop and take profit checks use candle highs and lows to approximate bid/ask hits from the original EA.
When Security.PriceStep is missing, pip parameters act as absolute price distances and should be tuned manually.
Adjust the pip thresholds and candle type to fit the traded instrument, especially when porting to markets with different tick sizes.
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>
/// MACD strategy with EMA trend filter.
/// Buys when MACD histogram is positive and price is above trend EMA.
/// Sells when MACD histogram is negative and price is below trend EMA.
/// </summary>
public class MacdSampleTrendFilterStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _trendPeriod;
private int _prevSignal;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public int TrendPeriod
{
get => _trendPeriod.Value;
set => _trendPeriod.Value = value;
}
public MacdSampleTrendFilterStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA for MACD", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA for MACD", "Indicators");
_trendPeriod = Param(nameof(TrendPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Trend Period", "Trend EMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevSignal = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevSignal = 0;
var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
var trendEma = new ExponentialMovingAverage { Length = TrendPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, trendEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawIndicator(area, trendEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal trend)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var macdLine = fast - slow;
// Signal: MACD positive + price above trend = bullish; MACD negative + price below trend = bearish
var signal = 0;
if (macdLine > 0 && close > trend)
signal = 1;
else if (macdLine < 0 && close < trend)
signal = -1;
if (signal == _prevSignal)
return;
var oldSignal = _prevSignal;
_prevSignal = signal;
if (signal == 1 && oldSignal <= 0)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (signal == -1 && oldSignal >= 0)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class macd_sample_trend_filter_strategy(Strategy):
def __init__(self):
super(macd_sample_trend_filter_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 12) \
.SetDisplay("Fast Period", "Fast EMA for MACD", "Indicators")
self._slow_period = self.Param("SlowPeriod", 26) \
.SetDisplay("Slow Period", "Slow EMA for MACD", "Indicators")
self._trend_period = self.Param("TrendPeriod", 100) \
.SetDisplay("Trend Period", "Trend EMA period", "Indicators")
self._prev_signal = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def TrendPeriod(self):
return self._trend_period.Value
def OnReseted(self):
super(macd_sample_trend_filter_strategy, self).OnReseted()
self._prev_signal = 0
def OnStarted2(self, time):
super(macd_sample_trend_filter_strategy, self).OnStarted2(time)
self._prev_signal = 0
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
trend_ema = ExponentialMovingAverage()
trend_ema.Length = self.TrendPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, trend_ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawIndicator(area, trend_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value, trend_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
fv = float(fast_value)
sv = float(slow_value)
tv = float(trend_value)
macd_line = fv - sv
signal = 0
if macd_line > 0 and close > tv:
signal = 1
elif macd_line < 0 and close < tv:
signal = -1
if signal == self._prev_signal:
return
old_signal = self._prev_signal
self._prev_signal = signal
if signal == 1 and old_signal <= 0:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif signal == -1 and old_signal >= 0:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return macd_sample_trend_filter_strategy()