MACD Not So Sample Strategy
Overview
The MACD Not So Sample strategy is a conversion of the MetaTrader expert advisor MACD_Not_So_Sample. The original robot trades
a 4-hour EURUSD chart using MACD crossovers confirmed by an EMA trend filter, combined with large take-profit levels and a
trailing stop. The StockSharp version keeps the same structure: the MACD histogram must be negative and cross above its signal
line for a long entry, while a positive histogram crossing below the signal produces a short entry. A trend EMA must confirm the
direction before any position is opened.
All money-management features are implemented in StockSharp: the strategy sets a configurable take-profit target, manages a
trailing stop once price travels far enough, and closes trades when the MACD crosses in the opposite direction with sufficient
strength. The port uses StockSharp indicators and high-level candle subscriptions so all calculations happen on finalized H4
candles, mirroring the MetaTrader behaviour.
Trading logic
- Subscribe to the timeframe defined by
CandleType (defaults to 4-hour candles) and process only finished candles.
- Feed a
MovingAverageConvergenceDivergenceSignal indicator with the configured FastPeriod, SlowPeriod, and
SignalPeriod. The indicator provides both the MACD line and the signal line.
- Calculate an EMA trend filter with length
TrendPeriod. Its slope determines whether long or short entries are allowed.
- Convert the pip-based thresholds (
MacdOpenLevelPips, MacdCloseLevelPips, TakeProfitPips, TrailingStopPips) to absolute
price distances using the instrument’s pip size.
- When no position exists:
- Open a long position if the MACD is below zero, the current value is above the signal value, the previous MACD was below
the previous signal, the EMA is rising, and the MACD magnitude exceeds
MacdOpenLevelPips.
- Open a short position if the MACD is above zero, the current value is below the signal value, the previous MACD was above
the previous signal, the EMA is falling, and the MACD magnitude exceeds
MacdOpenLevelPips.
- While holding a long position:
- Close the trade when the MACD becomes positive, crosses below the signal, and its magnitude exceeds
MacdCloseLevelPips.
- Exit early if price reaches the configured take-profit or if the trailing stop level is breached.
- While holding a short position:
- Close the trade when the MACD turns negative, crosses above the signal, and its magnitude exceeds
MacdCloseLevelPips.
- Exit early if price hits the take-profit target or the trailing stop.
- The trailing stop activates only after price moves beyond the threshold by
TrailingStopPips and then locks in profit by
following subsequent candle extremes.
Parameters
| Name |
Type |
Default |
Description |
FastPeriod |
int |
47 |
Fast EMA length used inside the MACD calculation. |
SlowPeriod |
int |
166 |
Slow EMA length used inside the MACD calculation. |
SignalPeriod |
int |
11 |
EMA length of the MACD signal line. |
TrendPeriod |
int |
8 |
Length of the EMA trend filter. |
MacdOpenLevelPips |
decimal |
1 |
Minimum MACD magnitude (in pips) required to open a position. |
MacdCloseLevelPips |
decimal |
3 |
Minimum MACD magnitude (in pips) required to close a position. |
TakeProfitPips |
decimal |
550 |
Take-profit distance measured in pips. |
TrailingStopPips |
decimal |
19 |
Trailing-stop distance measured in pips. A value of 0 disables trailing. |
TradeVolume |
decimal |
1 |
Net volume used for market entries. |
CandleType |
DataType |
4-hour time frame |
Candle series processed by the strategy. |
RequiredSecurityCode |
string |
EURUSD |
Security code that must match the selected instrument, mimicking the MetaTrader check. |
- MetaTrader manages individual orders and magic numbers. StockSharp works with net positions, so the conversion closes the
current exposure and opens a new one instead of juggling multiple tickets.
- The original code used
AccountFreeMargin to size positions dynamically. The StockSharp port exposes a simple TradeVolume
parameter and documents that users should configure position sizing externally.
- Stop-loss adjustments use StockSharp’s candle extremes rather than modifying existing orders. Exits still occur on the first
candle that violates the trailing stop, producing behaviour very close to the MetaTrader logic.
- All indicator calculations rely on StockSharp indicator classes bound through
SubscribeCandles, without direct calls to
iMACD or iMA functions.
Usage notes
- Assign the desired instrument before starting the strategy. If the instrument code does not match
RequiredSecurityCode the
strategy stops immediately to prevent accidental deployment on the wrong market.
TradeVolume is copied into Strategy.Volume during OnStarted, so helper methods (BuyMarket, SellMarket) always use the
configured size.
- Trailing stops only become active after price advances beyond the configured distance; until then the strategy will rely on the
MACD crossover and take-profit target for exits.
- Adding the strategy to a chart draws candles, both indicators, and executed trades so the crossover logic can be validated
visually.
Indicators
MovingAverageConvergenceDivergenceSignal (MACD line and signal line).
ExponentialMovingAverage (trend filter).
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD Not So Sample: Dual EMA crossover with RSI confirmation and ATR stops.
/// </summary>
public class MacdNotSoSampleStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public MacdNotSoSampleStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 12)
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 26)
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI 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 RsiLength { get => _rsiLength.Value; set => _rsiLength.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 rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, rsi, 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 rsiVal, 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 && rsiVal > 50) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 50) { _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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class macd_not_so_sample_strategy(Strategy):
def __init__(self):
super(macd_not_so_sample_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 12) \
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 26) \
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(macd_not_so_sample_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow and rv > 50:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv < 50:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(macd_not_so_sample_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return macd_not_so_sample_strategy()