This strategy reproduces the behavior of Yury Reshetov's "MACDSimple" MetaTrader expert advisor inside the StockSharp framework. It works with a single security and evaluates classic MACD signals that are modified by two offset parameters. The algorithm processes completed candles only, ensuring that all trading decisions are made on confirmed data and avoiding intrabar noise.
Indicators and Calculations
MACD (Moving Average Convergence Divergence) – the MACD line and signal line are calculated with custom periods:
Fast EMA period = SignalPeriod + DF
Slow EMA period = SignalPeriod + DS + DF
Signal line period = SignalPeriod
The offsets DF and DS follow the original expert inputs and allow the trader to stretch or compress the MACD components while keeping their relationship intact.
Parameters
Name
Description
Default
Volume
Order size used for every market entry.
2
DF
Offset added to the fast MACD EMA length. Must be zero or positive.
1
DS
Additional offset applied to the slow MACD EMA length. Must be zero or positive.
2
SignalPeriod
Base period from which the fast and slow EMA lengths are derived.
10
CandleType
Timeframe of the candles used for analysis and trading.
30-minute time frame
Trading Logic
Position Handling
On every finished candle the strategy updates the MACD indicator and ignores the bar if the indicator is not fully formed yet.
If a long position is open and the MACD line drops below zero, the strategy closes the entire long position at market price.
If a short position is open and the MACD line rises above zero, the strategy closes the entire short position at market price.
After closing a position on a given bar the algorithm stops processing that bar, mirroring the behavior of the original expert advisor.
Entry Rules
The algorithm verifies that both the MACD line and the signal line share the same sign (both positive or both negative). Mixed signs produce no trades.
When both lines are positive, a long position is opened if the MACD line is above the signal line.
When both lines are negative, a short position is opened if the MACD line is below the signal line.
Market orders are sized with the configured Volume parameter. Only one position can exist at a time.
Exit Rules
Exits are driven solely by the MACD line crossing the zero level against the open position, as described in the position handling section. No partial exits, stop losses, or take profits are implemented by default.
Additional Notes
The strategy trades only when IsFormedAndOnlineAndAllowTrading() is satisfied, ensuring that live data is available and trading is enabled before entering new positions.
No automatic risk management is built in. Users can add custom protections such as StartProtection() or combine the strategy with portfolio-level risk controls if desired.
Because the MACD parameters are derived from a single base period plus offsets, adjusting SignalPeriod, DF, or DS affects all components simultaneously, preserving the relative spacing intended by the original expert advisor.
Implementation Details
The indicator binding uses StockSharp's high-level SubscribeCandles().Bind() API, keeping the implementation concise and event-driven.
The conversion follows the rule set described in AGENTS.md: tabs are used for indentation, indicator values are consumed directly from the binding callback, and trading functions BuyMarket/SellMarket manage entries and exits.
The strategy structure is ready for extension (for example, adding filters or risk logic) while staying faithful to the original MetaTrader expert logic.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD-based strategy adapted from Yury Reshetov's MACDSimple expert advisor.
/// </summary>
public class MacdSimpleReshetovStrategy : Strategy
{
private readonly StrategyParam<int> _df;
private readonly StrategyParam<int> _ds;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd;
public int Df { get => _df.Value; set => _df.Value = value; }
public int Ds { get => _ds.Value; set => _ds.Value = value; }
public int SignalPeriod { get => _signalPeriod.Value; set => _signalPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MacdSimpleReshetovStrategy()
{
_df = Param(nameof(Df), 1)
.SetNotNegative()
.SetDisplay("DF", "Offset for the fast EMA", "Indicators");
_ds = Param(nameof(Ds), 2)
.SetNotNegative()
.SetDisplay("DS", "Offset for the slow EMA", "Indicators");
_signalPeriod = Param(nameof(SignalPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Signal Period", "Signal line period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// The MQL version derives MACD periods from the signal period with DF and DS offsets.
var fastPeriod = SignalPeriod + Df;
var slowPeriod = SignalPeriod + Ds + Df;
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = fastPeriod }, LongMa = { Length = slowPeriod } },
SignalMa = { Length = SignalPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var result = _macd.Process(candle);
if (!_macd.IsFormed)
return;
var macdValue = result as MovingAverageConvergenceDivergenceSignalValue;
if (macdValue == null)
return;
var macdLine = macdValue.Macd ?? 0m;
var signalLine = macdValue.Signal ?? 0m;
// Manage existing positions before evaluating new signals.
if (Position > 0)
{
if (macdLine < 0m)
SellMarket(Position);
return;
}
if (Position < 0)
{
if (macdLine > 0m)
BuyMarket(-Position);
return;
}
// Enter only when MACD and signal lines share the same sign.
if (macdLine * signalLine <= 0m)
return;
if (macdLine > 0m && macdLine > signalLine)
{
BuyMarket(Volume);
}
else if (macdLine < 0m && macdLine < signalLine)
{
SellMarket(Volume);
}
}
}
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 MovingAverageConvergenceDivergenceSignal, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class macd_simple_reshetov_strategy(Strategy):
def __init__(self):
super(macd_simple_reshetov_strategy, self).__init__()
self._df = self.Param("Df", 1)
self._ds = self.Param("Ds", 2)
self._signal_period = self.Param("SignalPeriod", 10)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._macd = None
@property
def Df(self):
return self._df.Value
@property
def Ds(self):
return self._ds.Value
@property
def SignalPeriod(self):
return self._signal_period.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(macd_simple_reshetov_strategy, self).OnStarted2(time)
fast_period = self.SignalPeriod + self.Df
slow_period = self.SignalPeriod + self.Ds + self.Df
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = fast_period
self._macd.Macd.LongMa.Length = slow_period
self._macd.SignalMa.Length = self.SignalPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ = CandleIndicatorValue(self._macd, candle)
civ.IsFinal = True
result = self._macd.Process(civ)
if not self._macd.IsFormed:
return
try:
macd_line = float(result.Macd) if result.Macd is not None else 0.0
signal_line = float(result.Signal) if result.Signal is not None else 0.0
except:
return
pos = float(self.Position)
if pos > 0:
if macd_line < 0:
self.SellMarket(pos)
return
if pos < 0:
if macd_line > 0:
self.BuyMarket(abs(pos))
return
if macd_line * signal_line <= 0:
return
if macd_line > 0 and macd_line > signal_line:
self.BuyMarket(float(self.Volume))
elif macd_line < 0 and macd_line < signal_line:
self.SellMarket(float(self.Volume))
def OnReseted(self):
super(macd_simple_reshetov_strategy, self).OnReseted()
self._macd = None
def CreateClone(self):
return macd_simple_reshetov_strategy()