MA Crossover ADX — это перенос советника MetaTrader MA_Crossover_ADX на платформу StockSharp. Алгоритм объединяет наклон экспоненциальной скользящей средней и фильтрацию по индикатору Average Directional Index (ADX), чтобы торговать только в подтверждённых трендах. Все вычисления выполняются по завершённым свечам выбранного таймфрейма, а обработка EMA и ADX синхронизируется внутри одной свечи, исключая ложные сигналы из‑за рассинхронизации данных. При открытии позиции стратегия автоматически выставляет защитные стоп‑лосс и тейк‑профит на основе заданных дистанций в пунктах.
Используемые индикаторы
EMA: основной трендовый фильтр. Хранятся три последних значения EMA, что позволяет вычислять текущий и предыдущий наклон — прямой аналог условий StateEMA(0) и StateEMA(1) из оригинального советника.
ADX: предоставляет основную линию силы тренда и направления DI+/DI-. Разница между DI+ и DI- соответствует условию StateADX(0), а минимальный уровень ADX гарантирует, что сделки заключаются только при достаточной силе тренда.
Ряд закрытий: предыдущее закрытие сравнивается с предыдушим значением EMA, что подтверждает отрыв цены от скользящей средней перед входом в позицию.
Все индикаторы работают на единой подписке свечей. Сигналы генерируются только тогда, когда и EMA, и ADX предоставили финальные значения для одной и той же свечи.
Правила торговли
Вход в длинную позицию
Текущий наклон EMA (EMA[0] - EMA[1]) положительный.
Предыдущий наклон EMA (EMA[1] - EMA[2]) также положительный, что подтверждает ускорение тренда.
Предыдущее закрытие выше значения EMA на предыдущей свече.
Основная линия ADX превышает заданный порог.
Значение DI+ больше DI-, т.е. доминирует восходящее направление.
При выполнении условий и отсутствии открытых позиций стратегия отправляет рыночную заявку на покупку с заданным объёмом. Если открыта короткая позиция, она закрывается при появлении длинного сигнала.
Вход в короткую позицию
Текущий наклон EMA отрицательный.
Предыдущий наклон EMA также отрицательный.
Предыдущее закрытие ниже значения EMA на предыдущей свече.
ADX превышает пороговое значение.
DI- больше DI+, что указывает на преобладание нисходящего движения.
При выполнении всех условий и отсутствии открытых позиций отправляется рыночная заявка на продажу. При наличии длинной позиции она закрывается.
Выход и риск-менеджмент
Закрытие лонга: при выполнении условий для короткой позиции.
Закрытие шорта: при выполнении условий для длинной позиции.
Защитные приказы: метод StartProtection рассчитывает уровни стоп‑лосса и тейк‑профита, умножая настроенные дистанции на PriceStep инструмента, и выставляет их автоматически, что соответствует параметрам StopLoss и TakeProfit исходного советника.
Параметры
Параметр
Значение по умолчанию
Описание
AdxPeriod
33
Количество баров в расчёте ADX.
AdxThreshold
22
Минимальное значение ADX для подтверждения тренда.
EmaPeriod
39
Период экспоненциальной скользящей средней.
StopLossPoints
400
Дистанция стоп‑лосса в пунктах (умножается на PriceStep).
TakeProfitPoints
900
Дистанция тейк‑профита в пунктах.
TradeVolume
0.1
Объём заявки при открытии новой позиции.
CandleType
Свечи 1 часа
Таймфрейм, по которому рассчитываются индикаторы.
Рекомендации по использованию
Убедитесь, что у инструмента задан PriceStep. Если шаг цены неизвестен, стратегия использует значение 1, чтобы защитные приказы всё равно могли быть рассчитаны.
Все параметры отмечены как доступные для оптимизации (SetCanOptimize(true)), что упрощает подбор настроек при тестировании.
Комментарии в исходном коде написаны на английском языке согласно требованиям репозитория.
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;
public class MaCrossoverAdxStrategy : 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;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public MaCrossoverAdxStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
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;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_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 ma_crossover_adx_strategy(Strategy):
def __init__(self):
super(ma_crossover_adx_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit 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(ma_crossover_adx_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(ma_crossover_adx_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
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 = 100
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 = 100
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 = 100
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 = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
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 = 100
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 = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return ma_crossover_adx_strategy()