View on GitHub

Divergence + EMA + RSI Close Buy Only

Overview

This strategy ports the MetaTrader "Divergence + ema + rsi close buy only" expert advisor to StockSharp's high-level API. It acts on 5-minute candles while consulting hourly and daily data to confirm trend alignment and oversold conditions. Orders are long-only. Entries require a bullish MACD histogram divergence that is confirmed by an hourly stochastic crossover inside a tight oversold band and by a rising daily EMA structure. Exits rely on a fixed RSI overshoot combined with optional stop-loss and take-profit protection managed by the framework.

Trading Logic

  1. Higher-timeframe trend filter

    • Daily EMA(9) must be above EMA(20) to ensure a prevailing uptrend.
    • The latest 5-minute close has to remain below the daily EMA(9) so that long entries are attempted from pullbacks.
  2. Hourly stochastic confirmation

    • The most recent completed hourly stochastic %K value must lie between the StochasticLowerBound (default 0) and StochasticUpperBound (default 40).
    • %K must have crossed above %D on the last hourly bar (current %K > %D while the previous %K ≤ previous %D).
  3. MACD divergence trigger (5-minute)

    • The MACD histogram (MACD line minus signal line) must improve by at least MacdThreshold while the 5-minute close sets a lower low compared with the previous candle. This approximates the bullish divergence used by the original EA.
  4. Entry execution

    • When all filters align and no long position is open, the strategy sends a market buy. If an unexpected short position exists, the requested volume is increased to neutralize it before flipping long.
  5. Exit rules

    • A protective RSI exit closes the long when the 5-minute RSI crosses above RsiExitLevel (default 77).
    • StartProtection activates both stop-loss and take-profit levels converted from pips into price distances whenever the corresponding parameters are positive.
  6. Order management

    • All active orders are cancelled prior to sending a new market buy order to avoid duplicated fills.
    • Volume defaults to the TradeVolume parameter and can be adjusted for optimization.

Parameters

Name Description Default
CandleType Primary timeframe for MACD, RSI, and execution. 5-minute candles
HourTimeFrame Hourly timeframe used by the stochastic filter. 1 hour
DayTimeFrame Daily timeframe for EMA trend confirmation. 1 day
MacdFastPeriod / MacdSlowPeriod / MacdSignalPeriod MACD structure on the primary timeframe. 6 / 13 / 5
MacdThreshold Minimum MACD histogram increase to accept a divergence. 0.0003
DailyFastPeriod / DailySlowPeriod Daily EMA periods. 9 / 20
StochasticKPeriod / StochasticDPeriod / StochasticSlowing Hourly stochastic configuration. 30 / 5 / 9
StochasticUpperBound / StochasticLowerBound Accepted hourly %K range. 40 / 0
RsiPeriod RSI length on the primary timeframe. 7
RsiExitLevel RSI value that forces long exits. 77
TradeVolume Base order size for buys. 0.01
StopLossPips Stop-loss distance in pips (0 disables). 100
TakeProfitPips Take-profit distance in pips (0 disables). 200

Notes

  • The strategy subscribes to three data streams: the configured primary timeframe, an hourly series, and a daily series. Each stream drives its own indicator set via Bind/BindEx to keep the implementation concise and event-driven.
  • Indicator values are only processed on finished candles to mirror the original EA's shift parameters.
  • The MACD divergence detection uses the previous bar's close and histogram value as a simple yet robust approximation of the builder-generated logic from the source MQL file.
  • Stop-loss and take-profit are handled by StartProtection to remain synchronized with broker fills and support backtesting or live trading without manual order replication.
using System;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Divergence EMA RSI Close Buy Only" MetaTrader expert.
/// Long-only strategy: buys when price pulls back below fast EMA with RSI oversold,
/// exits when RSI reaches overbought level.
/// </summary>
public class DivergenceEmaRsiCloseBuyOnlyStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiEntry;
	private readonly StrategyParam<decimal> _rsiExit;

	private ExponentialMovingAverage _ema;
	private RelativeStrengthIndex _rsi;
	private decimal? _prevRsi;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public decimal RsiEntry
	{
		get => _rsiEntry.Value;
		set => _rsiEntry.Value = value;
	}

	public decimal RsiExit
	{
		get => _rsiExit.Value;
		set => _rsiExit.Value = value;
	}

	public DivergenceEmaRsiCloseBuyOnlyStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signals", "General");

		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period for trend filter", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 7)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period for entry/exit", "Indicators");

		_rsiEntry = Param(nameof(RsiEntry), 35m)
			.SetDisplay("RSI Entry", "RSI level to enter long", "Signals");

		_rsiExit = Param(nameof(RsiExit), 65m)
			.SetDisplay("RSI Exit", "RSI level to exit long", "Signals");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_ema = new ExponentialMovingAverage { Length = EmaPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_prevRsi = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_ema, _rsi, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _ema);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_ema.IsFormed || !_rsi.IsFormed)
		{
			_prevRsi = rsiValue;
			return;
		}

		if (_prevRsi is null)
		{
			_prevRsi = rsiValue;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;
		var close = candle.ClosePrice;

		// Exit: RSI crosses above exit level
		if (Position > 0 && _prevRsi.Value < RsiExit && rsiValue >= RsiExit)
		{
			SellMarket(Position);
		}

		// Entry: RSI crosses below entry level and price is near/below EMA (buy the dip)
		if (Position <= 0 && _prevRsi.Value > RsiEntry && rsiValue <= RsiEntry && close <= emaValue * 1.005m)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));

			BuyMarket(volume);
		}

		_prevRsi = rsiValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_ema = null;
		_rsi = null;
		_prevRsi = null;

		base.OnReseted();
	}
}