MACD + Stochastic Trend Filter Strategy
This strategy recreates the behaviour of the MetaTrader expert advisor from folder MQL/7604. The original script relied on a custom oscillator that produced green and red buffers. In practice the numbers (15, 3, 3) match a classical stochastic oscillator, therefore the StockSharp port uses the built-in Stochastic indicator for the signal confirmation while MACD and an EMA trend filter manage direction.
The strategy trades both long and short. It waits for a stochastic crossover in the direction of the trade, requires the MACD histogram to cross its signal line with enough distance from zero, and demands that the EMA slope agrees with the entry. Risk management mirrors the MQL version: a fixed stop-loss, take-profit, and a point-based trailing stop that tightens the protective level as soon as the trade moves into profit.
Indicators
- MovingAverageConvergenceDivergenceSignal with parameters
fast = 12, slow = 26, signal = 9. The MACD histogram must cross its signal line while staying below zero for long setups and above zero for short setups. Additional thresholds (MacdOpenLevel, MacdCloseLevel) enforce a minimal absolute distance from the zero line.
- Stochastic oscillator with
(Length = 15, KPeriod = 3, DPeriod = 3). The %K line plays the role of the "green" buffer and must be above %D for long trades (below for short trades). The same crossover is used to exit positions.
- ExponentialMovingAverage with period
26. The EMA provides a directional filter: for a long trade the current EMA value must be above the previous bar's EMA, and conversely for a short trade.
Entry Logic
- Long setup
- Stochastic %K > %D on the current closed candle.
- MACD histogram < 0 and > signal line on the current bar.
- MACD histogram < signal line on the previous bar (i.e., bullish crossover now).
|MACD| > MacdOpenLevel * price_step.
- EMA rising (current EMA > previous EMA).
- Short setup
- Stochastic %K < %D on the current candle.
- MACD histogram > 0 and < signal line on the current bar.
- MACD histogram > signal line on the previous bar (bearish crossover now).
MACD > MacdOpenLevel * price_step.
- EMA falling (current EMA < previous EMA).
If the account already holds a position, no new orders are generated until the open trade is closed.
Exit Logic
While a position is open the strategy continuously enforces:
- Indicator exit
- Long positions close when
%K < %D, MACD > 0, MACD < signal, previous MACD was above its signal, and the absolute histogram exceeds MacdCloseLevel * price_step.
- Short positions close when
%K > %D, MACD < 0, MACD > signal, previous MACD was below its signal, and |MACD| > MacdCloseLevel * price_step.
- Stop-loss: configured by
StopLossPoints, converted into price units via the instrument's PriceStep.
- Take-profit:
TakeProfitPoints multiplied by PriceStep.
- Trailing stop: once the profit exceeds
TrailingStopPoints * PriceStep, the stop level is raised (for longs) or lowered (for shorts) so that the trade always locks in at least that amount of profit.
Parameters
| Name |
Description |
Default |
TradeVolume |
Order size in lots |
0.1 |
TakeProfitPoints |
Take-profit distance in points |
10 |
StopLossPoints |
Stop-loss distance in points |
50 |
TrailingStopPoints |
Trailing stop distance in points |
5 |
MacdOpenLevel |
Minimal absolute MACD value for entries |
3 |
MacdCloseLevel |
Minimal absolute MACD value for exits |
2 |
MacdFastPeriod |
Fast EMA length inside MACD |
12 |
MacdSlowPeriod |
Slow EMA length inside MACD |
26 |
MacdSignalPeriod |
MACD signal EMA length |
9 |
EmaPeriod |
EMA period for the trend filter |
26 |
StochasticLength |
Stochastic look-back window |
15 |
StochasticKPeriod |
%K smoothing |
3 |
StochasticDPeriod |
%D smoothing |
3 |
CandleType |
Time-frame used for calculations |
15m |
Notes
- All calculations use finished candles only, matching the
start() loop in the original EA.
- The
PriceStep supplied by the instrument defines one point. When the security does not expose a step the strategy falls back to 1.
- The code relies purely on StockSharp's high-level API: indicators are bound through
SubscribeCandles().BindEx(...), no manual history buffers are created, and orders use BuyMarket/SellMarket like in the MQL version.
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 stochastic confirmation and EMA trend filter.
/// Buy when MACD crosses above signal with stochastic K > D and price above EMA.
/// Sell when MACD crosses below signal with stochastic K < D and price below EMA.
/// </summary>
public class MacdStochasticFilterStrategy : Strategy
{
private readonly StrategyParam<int> _macdFastPeriod;
private readonly StrategyParam<int> _macdSlowPeriod;
private readonly StrategyParam<int> _macdSignalPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _stochKLength;
private readonly StrategyParam<int> _stochDLength;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevMacd;
private decimal? _prevSignal;
/// <summary>
/// Fast EMA period inside MACD.
/// </summary>
public int MacdFastPeriod
{
get => _macdFastPeriod.Value;
set => _macdFastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period inside MACD.
/// </summary>
public int MacdSlowPeriod
{
get => _macdSlowPeriod.Value;
set => _macdSlowPeriod.Value = value;
}
/// <summary>
/// Signal line period for MACD.
/// </summary>
public int MacdSignalPeriod
{
get => _macdSignalPeriod.Value;
set => _macdSignalPeriod.Value = value;
}
/// <summary>
/// Trend EMA period used as directional filter.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Stochastic %K length.
/// </summary>
public int StochKLength
{
get => _stochKLength.Value;
set => _stochKLength.Value = value;
}
/// <summary>
/// Stochastic %D length.
/// </summary>
public int StochDLength
{
get => _stochDLength.Value;
set => _stochDLength.Value = value;
}
/// <summary>
/// Type of candles used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public MacdStochasticFilterStrategy()
{
_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
.SetDisplay("MACD Fast", "Fast EMA period for MACD", "Indicators");
_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
.SetDisplay("MACD Slow", "Slow EMA period for MACD", "Indicators");
_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
.SetDisplay("MACD Signal", "Signal EMA period for MACD", "Indicators");
_emaPeriod = Param(nameof(EmaPeriod), 26)
.SetDisplay("Trend EMA", "EMA period for trend filter", "Indicators");
_stochKLength = Param(nameof(StochKLength), 14)
.SetDisplay("Stochastic K", "Look-back length for stochastic K", "Indicators");
_stochDLength = Param(nameof(StochDLength), 3)
.SetDisplay("Stochastic D", "Smoothing for D line", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for price data", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMacd = null;
_prevSignal = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMacd = null;
_prevSignal = null;
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFastPeriod },
LongMa = { Length = MacdSlowPeriod }
},
SignalMa = { Length = MacdSignalPeriod }
};
var stochastic = new StochasticOscillator
{
K = { Length = StochKLength },
D = { Length = StochDLength }
};
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, stochastic, ema, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue stochValue, IIndicatorValue emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!macdValue.IsFinal || !stochValue.IsFinal || !emaValue.IsFinal)
return;
if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macdData)
return;
if (macdData.Macd is not decimal macd || macdData.Signal is not decimal signal)
return;
if (stochValue is not StochasticOscillatorValue stochData)
return;
if (stochData.K is not decimal kVal || stochData.D is not decimal dVal)
return;
var emaVal = emaValue.ToDecimal();
if (_prevMacd is not decimal prevMacd || _prevSignal is not decimal prevSignal)
{
_prevMacd = macd;
_prevSignal = signal;
return;
}
// MACD crossover signals
var macdBullishCross = prevMacd <= prevSignal && macd > signal;
var macdBearishCross = prevMacd >= prevSignal && macd < signal;
// Long: MACD bullish cross + stochastic K > D + price above EMA
if (Position <= 0 && macdBullishCross && kVal > dVal && candle.ClosePrice > emaVal)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Short: MACD bearish cross + stochastic K < D + price below EMA
else if (Position >= 0 && macdBearishCross && kVal < dVal && candle.ClosePrice < emaVal)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevMacd = macd;
_prevSignal = signal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import (
ExponentialMovingAverage,
MovingAverageConvergenceDivergenceSignal,
StochasticOscillator
)
from StockSharp.Algo.Strategies import Strategy
class macd_stochastic_filter_strategy(Strategy):
"""MACD strategy with stochastic confirmation and EMA trend filter.
Buy when MACD crosses above signal with stochastic K > D and price above EMA.
Sell when MACD crosses below signal with stochastic K < D and price below EMA."""
def __init__(self):
super(macd_stochastic_filter_strategy, self).__init__()
self._macd_fast_period = self.Param("MacdFastPeriod", 12) \
.SetDisplay("MACD Fast", "Fast EMA period for MACD", "Indicators")
self._macd_slow_period = self.Param("MacdSlowPeriod", 26) \
.SetDisplay("MACD Slow", "Slow EMA period for MACD", "Indicators")
self._macd_signal_period = self.Param("MacdSignalPeriod", 9) \
.SetDisplay("MACD Signal", "Signal EMA period for MACD", "Indicators")
self._ema_period = self.Param("EmaPeriod", 26) \
.SetDisplay("Trend EMA", "EMA period for trend filter", "Indicators")
self._stoch_k_length = self.Param("StochKLength", 14) \
.SetDisplay("Stochastic K", "Look-back length for stochastic K", "Indicators")
self._stoch_d_length = self.Param("StochDLength", 3) \
.SetDisplay("Stochastic D", "Smoothing for D line", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for price data", "General")
self._prev_macd = None
self._prev_signal = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MacdFastPeriod(self):
return self._macd_fast_period.Value
@property
def MacdSlowPeriod(self):
return self._macd_slow_period.Value
@property
def MacdSignalPeriod(self):
return self._macd_signal_period.Value
@property
def EmaPeriod(self):
return self._ema_period.Value
@property
def StochKLength(self):
return self._stoch_k_length.Value
@property
def StochDLength(self):
return self._stoch_d_length.Value
def OnReseted(self):
super(macd_stochastic_filter_strategy, self).OnReseted()
self._prev_macd = None
self._prev_signal = None
def OnStarted2(self, time):
super(macd_stochastic_filter_strategy, self).OnStarted2(time)
self._prev_macd = None
self._prev_signal = None
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self.MacdFastPeriod
macd.Macd.LongMa.Length = self.MacdSlowPeriod
macd.SignalMa.Length = self.MacdSignalPeriod
stochastic = StochasticOscillator()
stochastic.K.Length = self.StochKLength
stochastic.D.Length = self.StochDLength
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(macd, stochastic, ema, self._process_candle).Start()
def _process_candle(self, candle, macd_value, stoch_value, ema_value):
if candle.State != CandleStates.Finished:
return
if not macd_value.IsFinal or not stoch_value.IsFinal or not ema_value.IsFinal:
return
macd_raw = macd_value.Macd if hasattr(macd_value, 'Macd') else None
signal_raw = macd_value.Signal if hasattr(macd_value, 'Signal') else None
if macd_raw is None or signal_raw is None:
return
macd_main = float(macd_raw)
signal_line = float(signal_raw)
k_raw = stoch_value.K if hasattr(stoch_value, 'K') else None
d_raw = stoch_value.D if hasattr(stoch_value, 'D') else None
if k_raw is None or d_raw is None:
return
k_val = float(k_raw)
d_val = float(d_raw)
ema_val = float(ema_value)
if self._prev_macd is None or self._prev_signal is None:
self._prev_macd = macd_main
self._prev_signal = signal_line
return
prev_macd = self._prev_macd
prev_signal = self._prev_signal
# MACD crossover signals
macd_bullish_cross = prev_macd <= prev_signal and macd_main > signal_line
macd_bearish_cross = prev_macd >= prev_signal and macd_main < signal_line
close = float(candle.ClosePrice)
# Long: MACD bullish cross + stochastic K > D + price above EMA
if self.Position <= 0 and macd_bullish_cross and k_val > d_val and close > ema_val:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Short: MACD bearish cross + stochastic K < D + price below EMA
elif self.Position >= 0 and macd_bearish_cross and k_val < d_val and close < ema_val:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_macd = macd_main
self._prev_signal = signal_line
def CreateClone(self):
return macd_stochastic_filter_strategy()