Открыть на GitHub

3103 — ADX EA (C#)

Обзор

Оригинальный советник MetaTrader «ADX EA» сочетает пробои по индикатору ADX, пересечения линий +DI/−DI, подтверждение по моментуму старшего таймфрейма и месячный фильтр MACD. Порт на C# воспроизводит эту многоступенчатую схему на высокоуровневом API StockSharp. Стратегия подписывается на три потока свечей:

  1. Основной таймфрейм (по умолчанию 5 минут) — рассчитывает ADX, линейно-взвешенные средние, проверяет структуру свечей и фильтр объёмов.
  2. Таймфрейм моментума (по умолчанию 15 минут) — даёт отклонения вокруг базы 100, которые пропускают или блокируют вход.
  3. Таймфрейм MACD (по умолчанию 30 дней) — имитирует месячный MACD, управляющий выходами.

Логика торговли

  • Пробойный модуль. При включении длинные сделки требуют:

    • ADX или +DI выше EntryLevel и разницу между +DI и −DI больше MinDirectionalDifference.
    • Быстрая LWMA выше медленной, бычья структура свечей (Low[2] < High[1]) и растущий моментум (Momentum[1] > Momentum[2]).
    • Хотя бы одно из трёх последних значений моментума на старшем таймфрейме должно отклоняться от 100 больше, чем на MomentumBuyThreshold.
    • Рост объёма на основном таймфрейме (Volume[1] > Volume[2] или Volume[1] > Volume[3]).
    • Месячный MACD в бычьем состоянии (MacdMain[1] > MacdSignal[1]).
    • ADX выше ExitLevel, что подтверждает силу тренда.

    Короткие пробои используют зеркальную логику: доминирование −DI, условие Low[1] < High[2], моментум ниже 100 на MomentumSellThreshold и медвежий MACD.

  • Модуль пересечений. При активации отслеживает пересечение +DI над −DI (для покупок) или −DI над +DI (для продаж). Дополнительные фильтры совпадают с МТ4-версией:

    • RequireAdxSlope требует, чтобы ADX рос относительно предыдущего значения.
    • ConfirmCrossOnBreakout добавляет требования пробоя при пересечении.
    • MinAdxMainLine задаёт минимальное значение ADX на момент пересечения.
    • Совпадение LWMA, наклон моментума, рост объёма и полярность MACD остаются обязательными.
  • Пирамидинг. Каждый новый ордер увеличивает объём согласно LotExponent. TradeVolume выступает базовым лотом, а добавочные ступени умножаются на LotExponent^n, где n — число уже открытых ступеней. MaxTrades ограничивает суммарный нетто-объём.

Управление рисками

  • Защитные заявки. TakeProfitSteps и StopLossSteps передаются в StartProtection и задаются в шагах цены инструмента.
  • Трейлинг. TrailingStopSteps поддерживает ручной трейлинг за локальным экстремумом цены закрытия.
  • Безубыток. При UseBreakEven = true стоп переносится после прохода BreakEvenTrigger шагов и может быть смещён на BreakEvenOffset шагов выше точки входа.
  • Выход по MACD. При EnableMacdExit = true месячный MACD закрывает длинные позиции, когда главная линия опускается ниже сигнальной (и наоборот для коротких), что повторяет процедуры Close_BUY/Close_SELL.
  • Equity-стоп. UseEquityStop отслеживает плавающую доходность и закрывает позиции при просадке, равной TotalEquityRisk процентам.

Функции, завязанные на денежные цели счёта («Take Profit in Money», «Trailing Profit in Money» и т.д.), не переносились, поскольку в StockSharp принято управлять риском через ценовые дистанции и сервис защитных заявок. Все остальные решения советника реализованы через соответствующие индикаторы.

Параметры

Параметр Значение по умолчанию Описание
TradeVolume 0.01 Базовый лот первой сделки.
CandleType 5 минут Основной поток свечей для расчёта ADX и LWMA.
MomentumCandleType 15 минут Старший таймфрейм для фильтра моментума.
MacdCandleType 30 дней Таймфрейм MACD для выходов.
FastMaPeriod 6 Период быстрой LWMA.
SlowMaPeriod 85 Период медленной LWMA.
AdxPeriod 14 Период индикатора ADX.
MomentumPeriod 14 Период моментума на старшем таймфрейме.
MacdFastPeriod 12 Быстрый EMA в MACD-фильтре выхода.
MacdSlowPeriod 26 Медленный EMA в MACD-фильтре выхода.
MacdSignalPeriod 9 Сигнальная SMA MACD.
EnableBreakoutStrategy true Включает пробойный блок.
EnableCrossStrategy true Включает блок пересечений DI.
UseTrendFilter true Требует доминирования +DI для лонгов и −DI для шортов в пробойном блоке.
RequireAdxSlope true Требует роста ADX при оценке пересечений.
ConfirmCrossOnBreakout true Добавляет условия пробоя к модулю пересечений.
EnableMacdExit true Включает выходы по MACD.
EntryLevel 10 Минимальное значение ADX/+DI/−DI для пробоя.
ExitLevel 10 Минимальное значение ADX, допускающее новые входы.
MinDirectionalDifference 10 Минимальный разрыв между +DI и −DI.
MinAdxMainLine 10 Минимальное значение ADX при пересечении DI.
MomentumBuyThreshold 0.3 Отклонение моментума от 100 для подтверждения лонга.
MomentumSellThreshold 0.3 Отклонение моментума от 100 для подтверждения шорта.
MaxTrades 10 Максимальное число ступеней пирамидинга.
LotExponent 1.44 Множитель объёма на каждую дополнительную ступень.
TakeProfitSteps 50 Дистанция тейк-профита в шагах цены.
StopLossSteps 20 Дистанция стоп-лосса в шагах цены.
TrailingStopSteps 40 Шаги ручного трейлинг-стопа.
UseBreakEven true Активирует перенос стопа в безубыток.
BreakEvenTrigger 30 Шаги в прибыль, необходимые для переноса стопа.
BreakEvenOffset 30 Дополнительное смещение стопа относительно цены входа.
UseEquityStop true Включает эквити-стоп.
TotalEquityRisk 1 Допустимая просадка счёта в процентах.

Рекомендации

  • Подбирайте MomentumCandleType и MacdCandleType в соответствии с основным таймфреймом, чтобы воспроизвести оригинальную привязку (например, 5 минут → 15 минут → месяц).
  • Совместно настройте EntryLevel, MinDirectionalDifference и MinAdxMainLine: понижение всех трёх значительно ослабляет фильтры.
  • Значение LotExponent > 1.0 повторяет мартингейл-подход исходника; поставьте 1.0 для постоянного объёма.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// ADX EA strategy using EMA crossover with ADX trend strength filter.
/// Buys when fast EMA crosses above slow EMA with strong trend.
/// Sells on reverse crossover.
/// </summary>
public class AdxEaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public AdxEaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

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

		if (!_fast.IsFormed || !_slow.IsFormed)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// EMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 60;
		}
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 60;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}