Auf GitHub ansehen

Simple MACD

Simple MACD replicates the logic of the MQL5 advisor Simple_MACD.mq5 in StockSharp. The strategy follows the slope of the MACD main line calculated on completed candles and keeps adding to the position whenever the slope remains in the same direction.

Overview

  • Market: any instrument with candle data and continuous trading hours.
  • Core indicator: Moving Average Convergence Divergence (MACD) using exponential moving averages 12/26 and signal 9.
  • Approach: momentum-following. The strategy compares two most recent completed MACD readings and goes long when the line rises, or short when the line falls.
  • Order type: market orders only. Every signal aggregates the amount required to close the opposite position and adds the configured trade volume on top, mirroring the original expert advisor.

Conversion Notes

  • The MQL5 bot triggered once per new bar by comparing MACD(1) and MACD(2) (previous two completed bars). In StockSharp, the same comparison is executed when a candle finishes, before the next bar starts.
  • The MQL version relied on explicit position enumeration and manual volume checks. The StockSharp version aggregates volume automatically with BuyMarket/SellMarket calls and the strategy TradeVolume parameter.
  • Hedging checks from the MQL code are not required because StockSharp tracks the net position directly.

Trading Rules

Entry and Scaling

  1. Compute the MACD main line on each finished candle.
  2. Store the last two MACD values and compare them:
    • If MACD(1) > MACD(2) the slope is bullish. The strategy buys a volume equal to TradeVolume + max(0, -Position) to close shorts and add new longs.
    • If MACD(1) < MACD(2) the slope is bearish. The strategy sells TradeVolume + max(0, Position) to close longs and add new shorts.
  3. If both values are equal no new orders are submitted.

Position Management

  • The strategy keeps stacking orders in the current direction as long as the MACD slope does not change sign, just like the original advisor which submitted a buy or sell on every qualifying bar.
  • Opposite signals flatten any open exposure before building the new position.
  • No stop-loss or take-profit levels are embedded; risk control relies on external money-management rules or manual supervision.

Additional Safeguards

  • Trading is skipped until the MACD indicator becomes fully formed.
  • Only completed candles (CandleStates.Finished) are processed, preventing premature actions on partial data.
  • Log messages trace every trade and show the two MACD values used to make the decision for easier backtesting analysis.

Parameters

Parameter Default Description
FastPeriod 12 Fast EMA length for the MACD calculation.
SlowPeriod 26 Slow EMA length for the MACD calculation.
SignalPeriod 9 Signal EMA period retained for compatibility with the original settings.
TradeVolume 0.1 Volume added on each signal before accounting for position reversal.
CandleType 1 minute time frame Candle type used to feed the indicator. Adjustable to any desired timeframe.

All parameters are exposed as strategy parameters and marked as optimizable where meaningful.

Visualization

  • The strategy automatically creates a chart area (when available) with the price candles and overlays the MACD indicator output.
  • Own trades are drawn on the chart to show how frequently the strategy scales positions in trending conditions.
  • Apply on trending instruments where momentum persists for several bars; range-bound markets will cause frequent reversals and whipsaw trades.
  • Combine with portfolio-level risk management since the base logic has no intrinsic stop mechanism.
  • Consider optimizing the TradeVolume and MACD periods for the target instrument and timeframe.

Files

  • CS/SimpleMacdStrategy.cs – StockSharp implementation of the strategy logic.
  • README.md, README_ru.md, README_zh.md – detailed documentation in three languages.
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>
/// Simple MACD slope-following strategy converted from MQL5 Simple_MACD.mq5.
/// The strategy evaluates the MACD main line on completed candles and builds positions accordingly.
/// </summary>
public class SimpleMacdStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private MovingAverageConvergenceDivergence _macd = null!;
	private decimal? _previousMacdValue;
	private decimal? _prePreviousMacdValue;
	private int? _previousSlope;

	/// <summary>
	/// Fast EMA period used for the MACD main line.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period used for the MACD main line.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Signal EMA period used by the MACD indicator.
	/// </summary>
	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	/// <summary>
	/// Trading volume applied when new orders are sent.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Candle type used to feed the MACD indicator.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize Simple MACD strategy with default parameters.
	/// </summary>
	public SimpleMacdStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetDisplay("MACD Fast Period", "Fast EMA length for MACD calculation", "Indicators")
			
			.SetOptimize(6, 18, 2);

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetDisplay("MACD Slow Period", "Slow EMA length for MACD calculation", "Indicators")
			
			.SetOptimize(20, 40, 2);

		_signalPeriod = Param(nameof(SignalPeriod), 9)
			.SetDisplay("MACD Signal Period", "Signal EMA length maintained for compatibility", "Indicators")
			
			.SetOptimize(6, 18, 1);

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetDisplay("Trade Volume", "Order volume used for each signal", "Risk")
			
			.SetOptimize(0.1m, 1m, 0.1m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for MACD calculations", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousMacdValue = null;
		_prePreviousMacdValue = null;
		_previousSlope = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Configure MACD indicator to match the source MQL strategy settings.
		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = FastPeriod },
			LongMa = { Length = SlowPeriod },
		};

		// Subscribe to candle data and bind the MACD indicator.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_macd, ProcessCandle)
			.Start();

		// Prepare visual elements when charts are available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _macd);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
	{
		// React only to completed candles to avoid premature decisions.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure the indicator produced a valid value.
		if (!_macd.IsFormed || !macdValue.IsFinal)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var macdLine = macdValue.ToDecimal();

		// Accumulate historical MACD values for slope calculations.
		if (_previousMacdValue is null)
		{
			_previousMacdValue = macdLine;
			return;
		}

		if (_prePreviousMacdValue is null)
		{
			_prePreviousMacdValue = _previousMacdValue;
			_previousMacdValue = macdLine;
			return;
		}

		var macdPrev = _previousMacdValue.Value;
		var macdPrevPrev = _prePreviousMacdValue.Value;

		var currentSlope = macdPrev > macdPrevPrev ? 1 : macdPrev < macdPrevPrev ? -1 : 0;

		if (currentSlope == 0)
		{
			_prePreviousMacdValue = _previousMacdValue;
			_previousMacdValue = macdLine;
			return;
		}

		if (_previousSlope == currentSlope)
		{
			_prePreviousMacdValue = _previousMacdValue;
			_previousMacdValue = macdLine;
			return;
		}

		if (currentSlope > 0)
		{
			// Close shorts and open (or add to) longs when the MACD slope turns positive.
			var volumeToBuy = TradeVolume + Math.Max(0m, -Position);
			if (volumeToBuy > 0m)
			{
				BuyMarket(volumeToBuy);
				LogInfo($"Bullish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Buying {volumeToBuy}.");
			}
		}
		else
		{
			// Close longs and open (or add to) shorts when the MACD slope turns negative.
			var volumeToSell = TradeVolume + Math.Max(0m, Position);
			if (volumeToSell > 0m)
			{
				SellMarket(volumeToSell);
				LogInfo($"Bearish slope detected. MACD(1)={macdPrev:F5}, MACD(2)={macdPrevPrev:F5}. Selling {volumeToSell}.");
			}
		}

		// Update stored values so the next candle compares the two previous MACD readings.
		_previousSlope = currentSlope;
		_prePreviousMacdValue = _previousMacdValue;
		_previousMacdValue = macdLine;
	}
}