Открыть на GitHub

Простейшая трендовая стратегия на ADX

Общее описание

Простейшая трендовая стратегия на ADX — это перенос советника MetaTrader «ADX Simple». Алгоритм отслеживает направление тренда по показаниям индикатора Average Directional Index (ADX): сделки открываются только тогда, когда линия ADX растёт, а приоритет направления определяется сравнением положительного и отрицательного направленного движения (DI+ и DI-). Реализация на StockSharp сохраняет лаконичность оригинала и вписывается в высокоуровневый API платформы.

Используемые индикаторы

  • Average Directional Index (ADX) с настраиваемым периодом (по умолчанию 25).
    • Главная линия ADX подтверждает усиление тренда.
    • Встроенные серии DI+ и DI- показывают доминирование быков или медведей.
  • Тип свечей задаётся параметром CandleType (по умолчанию 15-минутные свечи).

Правила входа

Лонг

  1. Дождаться закрытия свечи и получения финального значения ADX.
  2. Убедиться, что DI+ выше DI- на той же свече.
  3. Проверить, что текущее значение ADX строго выше предыдущего (тренд усиливается).
  4. Если позиция отсутствует, выставить рыночную заявку на покупку объёмом Strategy.Volume.

Шорт

  1. Дождаться закрытой свечи и финального значения ADX.
  2. Убедиться, что DI- выше DI+.
  3. Проверить, что ADX выше своего предыдущего значения.
  4. При отсутствии открытых позиций выставить рыночную заявку на продажу объёмом Strategy.Volume.

Правила выхода

  • Закрытие лонга: DI- пересекает DI+ сверху (приоритет смещается в сторону медведей).
  • Закрытие шорта: DI+ пересекает DI- сверху (приоритет переходит к быкам).
  • Для выхода не требуется дополнительная проверка роста ADX, что соответствует оригинальному советнику.

Управление позицией

  • Стратегия находится либо во флэте, либо в одной рыночной позиции (лонг/шорт). Одновременных разнонаправленных позиций нет.
  • Размер заявок задаётся свойством Strategy.Volume (значение по умолчанию — 1). Настройте объём перед запуском стратегии в зависимости от торгуемого инструмента.
  • Автоматические стопы и тейк-профиты отсутствуют. Рекомендуется подключить внешний контроль риска или расширить стратегию собственными ограничениями.

Параметры

Параметр Тип Значение по умолчанию Описание
AdxPeriod int 25 Длина окна для расчёта ADX, DI+ и DI-.
CandleType DataType 15-минутные свечи Свечной поток, на котором производится расчёт сигналов.

Отличия от версии MetaTrader

  • Управление капиталом: оригинал пересчитывал лоты от баланса. В StockSharp используется Strategy.Volume, а риск-менеджмент оставлен на усмотрение пользователя.
  • Учёт сделок: вместо перебора активных ордеров в MetaTrader применяется свойство Position, отражающее текущий объём позиции.
  • Работа с данными: стратегия анализирует только закрытые свечи и финальные значения индикаторов.
  • Визуализация: доступны методы CreateChartArea, DrawCandles, DrawIndicator для отладки и контроля поведения.

Рекомендации по применению

  1. Выбирайте инструменты с выраженными трендовыми движениями (валютные пары, индексы, сырьё).
  2. Настройте CandleType и AdxPeriod перед запуском стратегии.
  3. При необходимости подключите портфельные ограничения по просадке и максимальному риску.
  4. Наблюдайте за пересечениями DI и динамикой ADX на графике для верификации сигналов.

Возможные расширения

  • Добавьте фильтры волатильности (ATR, стандартное отклонение), чтобы избегать периодов низкой активности рынка.
  • Реализуйте автоматические стоп-лоссы и тейк-профиты, используя StartProtection или дополнительные ордера в ProcessCandle.
  • Подключите свечи старших таймфреймов и используйте их как фильтр направления.

Данный документ содержит максимально подробную информацию о стратегии, что позволяет безопасно внедрять и модифицировать её в инфраструктуре StockSharp.

using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// ADX Simple: Trend following with EMA crossover and ATR momentum filter.
/// </summary>
public class AdxSimpleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public AdxSimpleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 30)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

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

	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	protected override void OnReseted()
	{
		base.OnReseted();

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
	}

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

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, atr, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (close <= _entryPrice - atrVal * 2m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (close >= _entryPrice + atrVal * 2m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (fastVal < slowVal && _prevFast >= _prevSlow)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}