Открыть на GitHub

Simple MA ADX EA

Стратегия, которая сочетает EMA и индекс направленного движения (ADX) для подтверждения тренда.

Покупка осуществляется при росте EMA, закрытии предыдущей свечи выше EMA, значении ADX выше порога и +DI больше -DI. Продажа выполняется при обратных условиях. Управление риском реализовано через стоп-лосс и тейк-профит.

Детали

  • Условия входа: направление EMA, цена относительно EMA, ADX, +DI/-DI.
  • Длинные/короткие: в обе стороны.
  • Условия выхода: противоположный сигнал или защитные ордера.
  • Стопы: да.
  • Значения по умолчанию:
    • AdxPeriod = 8
    • MaPeriod = 8
    • AdxThreshold = 22m
    • StopLoss = 30m
    • TakeProfit = 100m
    • Volume = 0.1m
    • CandleType = TimeSpan.FromMinutes(1)
  • Фильтры:
    • Категория: Тренд
    • Направление: Оба
    • Индикаторы: EMA, ADX
    • Стопы: Да
    • Сложность: Базовая
    • Таймфрейм: Внутридневной (1м)
    • Сезонность: Нет
    • Нейросети: Нет
    • Дивергенция: Нет
    • Уровень риска: Средний
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;
	}
}