Ver no GitHub

Simple MA ADX EA

Strategy combining an EMA with the Average Directional Index to confirm trend strength.

It buys when the EMA is rising, the previous close is above the EMA, ADX exceeds a threshold and +DI is greater than -DI. It sells when the opposite conditions appear. Stop-loss and take-profit levels manage risk.

Details

  • Entry Criteria: EMA direction, price vs EMA, ADX, +DI/-DI.
  • Long/Short: Both directions.
  • Exit Criteria: Opposite signal or protection orders.
  • Stops: Yes.
  • Default Values:
    • AdxPeriod = 8
    • MaPeriod = 8
    • AdxThreshold = 22m
    • StopLoss = 30m
    • TakeProfit = 100m
    • Volume = 0.1m
    • CandleType = TimeSpan.FromMinutes(1)
  • Filters:
    • Category: Trend
    • Direction: Both
    • Indicators: EMA, ADX
    • Stops: Yes
    • Complexity: Basic
    • Timeframe: Intraday (1m)
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: Medium
using System;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy combining EMA direction with a slower trend filter.
/// </summary>
public class SimpleMaAdxEaStrategy : Strategy
{
	private readonly StrategyParam<int> _adxPeriod;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<decimal> _adxThreshold;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _ema;
	private ExponentialMovingAverage _trendMa;

	private decimal _emaPrev1;
	private decimal _emaPrev2;
	private decimal _trendPrev;
	private decimal _prevClose;
	private bool _isInitialized;
	private int _barsSinceTrade;

	/// <summary>
	/// Trend filter period.
	/// </summary>
	public int AdxPeriod
	{
		get => _adxPeriod.Value;
		set => _adxPeriod.Value = value;
	}

	/// <summary>
	/// EMA period.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Minimum percentage distance between the fast and slow averages.
	/// </summary>
	public decimal AdxThreshold
	{
		get => _adxThreshold.Value;
		set => _adxThreshold.Value = value;
	}

	/// <summary>
	/// Stop loss in price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit in price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Bars to wait after a completed trade.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="SimpleMaAdxEaStrategy"/> class.
	/// </summary>
	public SimpleMaAdxEaStrategy()
	{
		_adxPeriod = Param(nameof(AdxPeriod), 21)
			.SetDisplay("Trend Period", "Period for trend confirmation", "Indicators");

		_maPeriod = Param(nameof(MaPeriod), 8)
			.SetDisplay("MA Period", "EMA calculation period", "Indicators");

		_adxThreshold = Param(nameof(AdxThreshold), 0.05m)
			.SetDisplay("Trend Threshold", "Minimum average distance in percent", "Indicators");

		_stopLoss = Param(nameof(StopLoss), 400m)
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk Management");

		_takeProfit = Param(nameof(TakeProfit), 1200m)
			.SetDisplay("Take Profit", "Take profit in price units", "Risk Management");

		_cooldownBars = Param(nameof(CooldownBars), 200)
			.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

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

		_ema = null;
		_trendMa = null;
		_emaPrev1 = 0m;
		_emaPrev2 = 0m;
		_trendPrev = 0m;
		_prevClose = 0m;
		_isInitialized = false;
		_barsSinceTrade = CooldownBars;
	}

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

		_ema = new ExponentialMovingAverage { Length = MaPeriod };
		_trendMa = new ExponentialMovingAverage { Length = AdxPeriod };

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

		StartProtection(
			stopLoss: new Unit(StopLoss, UnitTypes.Absolute),
			takeProfit: new Unit(TakeProfit, UnitTypes.Absolute),
			useMarketOrders: true);
	}

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

		var ema = _ema.Process(new DecimalIndicatorValue(_ema, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();
		var trendMa = _trendMa.Process(new DecimalIndicatorValue(_trendMa, candle.ClosePrice, candle.OpenTime) { IsFinal = true }).ToDecimal();

		if (!_ema.IsFormed || !_trendMa.IsFormed || trendMa == 0m)
			return;

		if (_barsSinceTrade < CooldownBars)
			_barsSinceTrade++;

		if (!_isInitialized)
		{
			_emaPrev2 = ema;
			_emaPrev1 = ema;
			_trendPrev = trendMa;
			_prevClose = candle.ClosePrice;
			_isInitialized = true;
			return;
		}

		var distancePercent = Math.Abs(ema - trendMa) / trendMa * 100m;
		var buyCond1 = ema > _emaPrev1 && _emaPrev1 >= _emaPrev2;
		var buyCond2 = _prevClose > _emaPrev1 && candle.ClosePrice > trendMa;
		var buyCond3 = trendMa >= _trendPrev && distancePercent >= AdxThreshold;
		var sellCond1 = ema < _emaPrev1 && _emaPrev1 <= _emaPrev2;
		var sellCond2 = _prevClose < _emaPrev1 && candle.ClosePrice < trendMa;
		var sellCond3 = trendMa <= _trendPrev && distancePercent >= AdxThreshold;

		if (_barsSinceTrade >= CooldownBars && Position == 0)
		{
			if (buyCond1 && buyCond2 && buyCond3)
			{
				BuyMarket();
				_barsSinceTrade = 0;
			}
			else if (sellCond1 && sellCond2 && sellCond3)
			{
				SellMarket();
				_barsSinceTrade = 0;
			}
		}

		_emaPrev2 = _emaPrev1;
		_emaPrev1 = ema;
		_trendPrev = trendMa;
		_prevClose = candle.ClosePrice;
	}
}