Оригинальный советник MetaTrader «ADX EA» сочетает пробои по индикатору ADX, пересечения линий +DI/−DI, подтверждение по моментуму старшего таймфрейма и месячный фильтр MACD. Порт на C# воспроизводит эту многоступенчатую схему на высокоуровневом API StockSharp. Стратегия подписывается на три потока свечей:
Основной таймфрейм (по умолчанию 5 минут) — рассчитывает ADX, линейно-взвешенные средние, проверяет структуру свечей и фильтр объёмов.
Таймфрейм моментума (по умолчанию 15 минут) — даёт отклонения вокруг базы 100, которые пропускают или блокируют вход.
Пробойный модуль. При включении длинные сделки требуют:
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class adx_ea_strategy(Strategy):
def __init__(self):
super(adx_ea_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 50) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 200) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(adx_ea_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(adx_ea_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
# Check SL/TP
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# EMA crossover
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 60
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return adx_ea_strategy()