This strategy ports the MetaTrader 5 expert adviser Simple EA MA plus MACD to the StockSharp high-level API. It searches for a breakout from a “signal bar” that satisfies two conditions: a shifted moving average sits below/above the bar’s highs, and the MACD histogram has just crossed the zero line. When the next candle closes beyond the signal bar’s extremum the strategy enters in the breakout direction.
The implementation keeps the original behaviour of the EA:
Signal detection – on every finished candle the strategy inspects the previous bar. A configurable moving average (default LWMA) calculated on the chosen applied price must be lower than both the previous and current candle highs for longs (higher for shorts). Simultaneously the MACD main line must have crossed zero between the two preceding bars.
Signal confirmation – once a signal bar is stored, the strategy waits for the next completed candle. A close above the stored high triggers a long breakout; a close below the stored low triggers a short breakout. If price invalidates the signal by closing back inside the signal bar, the setup is cancelled.
Position management – newly opened trades inherit stop-loss, take-profit and trailing-stop distances expressed in pips. Protective levels are converted to absolute prices using the security PriceStep. Instruments with three or five decimals receive the classic forex adjustment (step × 10) to mimic MetaTrader pip definitions.
Risk management
Stop-loss / take-profit – optional distances defined in pips are evaluated on every candle close. When the market prints beyond the corresponding level the strategy exits with a market order.
Trailing stop – when profit exceeds TrailingStopPips + TrailingStepPips, a trailing reference is moved behind the best price reached. If price pulls back to the trailing level the position is closed. A trailing step of zero re-arms the stop on every new extreme.
Flatten on reversal – if an opposite breakout appears while an opposite position is open, the strategy sends a single market order large enough to close the existing exposure and open the new trade in one shot.
Implementation notes
The moving average supports the same smoothing methods and applied price options as MetaTrader (Simple, Exponential, Smoothed, LinearWeighted and Close/Open/High/Low/Median/Typical/Weighted prices).
MaShift reproduces the horizontal offset of the MetaTrader indicator by reading values from earlier bars before evaluating the breakout rules.
MACD uses the built-in MovingAverageConvergenceDivergence indicator. Only the histogram (difference between fast and slow EMAs) is required; the signal line period is retained to stay faithful to the EA settings.
Candle subscriptions and indicator processing rely exclusively on the StockSharp high-level API. No manual tick handling or indicator buffers are used.
Parameters
Parameter
Default
Description
Volume
1
Order size for every breakout entry.
TakeProfitPips
50
Profit target distance expressed in pips (converted to absolute price using the security price step). Set to 0 to disable.
StopLossPips
50
Protective stop distance in pips. Set to 0 to disable.
TrailingStopPips
5
Trailing stop distance in pips that is locked in once price advances sufficiently.
TrailingStepPips
5
Minimum additional progress (in pips) before the trailing stop is advanced again.
MaPeriod
100
Length of the moving average used to validate the signal bar.
MaShift
0
Horizontal shift applied to the moving average, emulating the MetaTrader ma_shift parameter.
MaMethod
LinearWeighted
Moving average smoothing method (Simple, Exponential, Smoothed, LinearWeighted).
MaAppliedPrice
Weighted
Price source fed into the moving average (Close, Open, High, Low, Median, Typical, Weighted).
MacdFastPeriod
12
Fast EMA period used in the MACD calculation.
MacdSlowPeriod
26
Slow EMA period used in the MACD calculation.
MacdSignalPeriod
9
Signal line smoothing period retained for parity with the original EA.
MacdAppliedPrice
Weighted
Applied price used when feeding values into MACD.
CandleType
1 hour time frame
Primary candle series analysed for signals and trade management.
Usage tips
Tune the pip-based protections to match the tick size of the selected instrument; incorrect PriceStep values on the connector side will distort pip conversions.
For highly volatile markets consider increasing TrailingStepPips to reduce premature exits, or decrease it to tighten trailing behaviour.
Because trades are executed on closed candles, the breakout must persist until the bar completes; enabling smaller timeframes increases trading frequency but may introduce more noise.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simple EA MA plus MACD strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class SimpleEaMaPlusMacdStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public SimpleEaMaPlusMacdStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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
from datatype_extensions import *
from indicator_extensions import *
class simple_ea_ma_plus_macd_strategy(Strategy):
def __init__(self):
super(simple_ea_ma_plus_macd_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._sl_points = self.Param("StopLossPoints", 200).SetNotNegative().SetDisplay("Stop Loss", "SL in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 400).SetNotNegative().SetDisplay("Take Profit", "TP in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle type", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(simple_ea_ma_plus_macd_strategy, self).OnReseted()
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
def OnStarted2(self, time):
super(simple_ea_ma_plus_macd_strategy, self).OnStarted2(time)
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
self._step = 1.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
self._step = float(self.Security.PriceStep)
fast = ExponentialMovingAverage()
fast.Length = self._fast_period.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast, slow, self.OnProcess).Start()
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if self._prev_fast == 0 or self._prev_slow == 0:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = self._step
# Manage SL/TP
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# Crossover signals
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return simple_ea_ma_plus_macd_strategy()