English 中文 Deutsch 日本語
preview
Автоматизация торговых стратегий на MQL5 (Часть 14): Стратегия каскадной торговли с MACD-RSI и статистическими методами

Автоматизация торговых стратегий на MQL5 (Часть 14): Стратегия каскадной торговли с MACD-RSI и статистическими методами

MetaTrader 5Трейдинг |
648 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В нашей предыдущей статье (Часть 13) мы реализовали торговый алгоритм «Голова-Плечи» на MetaQuotes Language 5 (MQL5) для автоматизации классического разворотного паттерна с целью определения разворотов рынка. Теперь, в Части 14, мы перейдем к разработке стратегии лейеринга с использованием Индикатора схождения-расхождения скользящих средних (MACD) и Индикатора относительной силы (RSI), дополненных статистическими методами, для динамичного масштабирования позиций на трендовых рынках. В статье рассмотрим следующие темы:

  1. Архитектура стратегии
  2. Реализация средствами MQL5
  3. Тестирование на истории
  4. Заключение

К концу настоящей статьи у вас будет надежный советник, предназначенный для точного распределения сделок — давайте начнем!


Архитектура стратегии

Стратегия лейеринга сделок или многослойной торговли, которую мы рассматриваем в настоящей статье, предназначена для извлечения выгоды из устойчивых рыночных тенденций путем постепенного добавления позиций по мере движения цены в благоприятном направлении. Данный метод часто называют каскадным. В отличие от традиционных стратегий однократного входа, которые нацелены на фиксированную цель, этот подход использует импульс путем осуществления лейеринга дополнительных сделок каждый раз при достижении порога прибыли, эффективно увеличивая потенциальную прибыль при сохранении контролируемого риска. По своей сути, стратегия сочетает в себе два широко известных технических индикатора — MACD и RSI — со статистическим наложением для обеспечения своевременности и надежности входов, что делает ее подходящей для рынков с четким направленным движением.

Мы будем использовать сильные стороны MACD и RSI, чтобы заложить прочную основу для торговых сигналов, установив четкие правила, когда начинать процесс лейеринга. Наш план предполагает использование MACD для подтверждения направления и силы тренда, гарантируя, что мы заключаем сделки только тогда, когда рынок демонстрирует устойчивую тенденцию, в то время как RSI будет определять оптимальные моменты входа, обнаруживая отклонения от экстремальных ценовых уровней. Интегрируя эти индикаторы, мы стремимся создать надежный триггерный механизм, запускающий начальную сделку, которая затем послужит отправной точкой для нашей каскадной последовательности, позволяя нам наращивать позиции по мере развития тренда. Вот визуализация такой стратегии.

STRATEGY BLUEPRINT

Далее мы усовершенствуем эту настройку, внедрив статистические методы для повышения точности ввода и управления процессом лейеринга. Мы рассмотрим, как применять статистические фильтры, такие как анализ исторического поведения RSI, для проверки сигналов, гарантируя, что сделки будут совершаться только при статистически значимых условиях. Затем план распространяется на определение правил лейеринга, где мы расскажем о том, как добавляется каждая новая сделка при достижении целевых показателей прибыли, а также о корректировке уровней риска для защиты прибыли, что приводит к созданию динамичной стратегии, которая адаптируется к динамике рынка, сохраняя при этом дисциплинированное исполнение. Итак, начнем.


Реализация средствами MQL5

Чтобы создать программу на MQL5, откройте  MetaEditor, перейдите в Навигатор, найдите папку «Индикаторы» (Indicators), перейдите на вкладку "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования нам нужно будет объявить некоторые  глобальные переменные, которые будем использовать во всей программе.

//+------------------------------------------------------------------+
//|                                  MACD-RSI LAYERING STRATEGY.mq5  |
//|      Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                           https://youtube.com/@ForexAlgo-Trader? |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://youtube.com/@ForexAlgo-Trader?"
#property description "MACD-RSI-based layering strategy with adjustable Risk:Reward and visual levels"
#property version   "1.0"

#include <Trade\Trade.mqh>//---- Includes the Trade.mqh library for trading operations
CTrade obj_Trade;//---- Declares a CTrade object for executing trade operations

int rsiHandle = INVALID_HANDLE;//---- Initializes RSI indicator handle as invalid
double rsiValues[];//---- Declares an array to store RSI values

int handleMACD = INVALID_HANDLE;//---- Initializes MACD indicator handle as invalid
double macdMAIN[];//---- Declares an array to store MACD main line values
double macdSIGNAL[];//---- Declares an array to store MACD signal line values

double takeProfitLevel = 0;//---- Initializes the take profit level variable
double stopLossLevel = 0;//---- Initializes the stop loss level variable
bool buySequenceActive = false;//---- Flag to track if a buy sequence is active
bool sellSequenceActive = false;//---- Flag to track if a sell sequence is active

// Inputs with clear names
input int stopLossPoints = 300;        // Initial Stop Loss (points)
input double tradeVolume = 0.01;       // Trade Volume (lots)
input int minStopLossPoints = 100;     // Minimum SL for cascading orders (points)
input int rsiLookbackPeriod = 14;      // RSI Lookback Period
input double rsiOverboughtLevel = 70.0; // RSI Overbought Threshold
input double rsiOversoldLevel = 30.0;   // RSI Oversold Threshold
input bool useStatisticalFilter = true; // Enable Statistical Filter
input int statAnalysisPeriod = 20;      // Statistical Analysis Period (bars)
input double statDeviationFactor = 1.0; // Statistical Deviation Factor
input double riskRewardRatio = 1.0;     // Risk:Reward Ratio

// Object names for visualization
string takeProfitLineName = "TakeProfitLine";//---- Name of the take profit line object for chart visualization
string takeProfitTextName = "TakeProfitText";//---- Name of the take profit text object for chart visualization


Начинаем с создания необходимой основы для нашей стратегии лейеринга сделок, начиная с включения библиотеки "Trade.mqh" и объявления объекта "CTrade" с именем "obj_Trade" для обработки всех торговых операций. Инициализируем ключевые хэндлы индикаторов — "rsiHandle" для индикатора RSI и "handleMACD" для индикатора MACD - оба изначально установлены на значение INVALID_HANDLE, наряду с такими массивами, как "rsiValues", "macdMAIN" и "macdSIGNAL" для хранения соответствующих данных. Для отслеживания состояния стратегии определяем такие переменные, как "takeProfitLevel" и "stopLossLevel" для уровней сделок, а также логические флаги "buySequenceActive" и "sellSequenceActive", чтобы отслеживать, выполняется ли последовательность лейеринга на покупку или продажу, гарантируя, что система знает, когда следует каскадировать сделки.

Затем устанавливаем настраиваемые пользователем параметры для адаптации стратегии, включая "stopLossPoints" для начального расстояния стоп-лосса, "tradeVolume" для размера лота и "minStopLossPoints" для более жестких стоп-лоссов в каскадных сделках. Для индикаторов мы установили "rsiLookbackPeriod", чтобы определить окно расчета RSI, "rsiOverboughtLevel" и "rsiOversoldLevel" в качестве пороговых значений для входа, и "riskRewardRatio" для контроля целевых показателей прибыли по отношению к риску. Чтобы внедрить статистические методы, вводим "useStatisticalFilter" в качестве переключателя в сочетании с "statAnalysisPeriod" и "statDeviationFactor", которые позволят нам уточнять сигналы на основе статистического поведения RSI, обеспечивая соответствие сделок значительным отклонениям рынка.

Наконец, мы готовимся к визуальной обратной связи, определяя "takeProfitLineName" и "takeProfitTextName" в качестве названий объектов для линий и меток тейк-профита на графике, что расширяет возможности трейдера отслеживать уровни в режиме реального времени. После компиляции программы увидим следующий результат.

USER INPUTS

Далее переходим к обработчику событий инициализации (OnInit), где обрабатываем свойства инициализации.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){//---- Expert advisor initialization function
   rsiHandle = iRSI(_Symbol, _Period, rsiLookbackPeriod, PRICE_CLOSE);//---- Creates RSI indicator handle with specified parameters
   handleMACD = iMACD(_Symbol,_Period,12,26,9,PRICE_CLOSE);//---- Creates MACD indicator handle with standard 12,26,9 settings
   
   if(rsiHandle == INVALID_HANDLE){//---- Checks if RSI handle creation failed
      Print("UNABLE TO LOAD RSI, REVERTING NOW");//---- Prints error message if RSI failed to load
      return(INIT_FAILED);//---- Returns initialization failure code
   }
   if(handleMACD == INVALID_HANDLE){//---- Checks if MACD handle creation failed
      Print("UNABLE TO LOAD MACD, REVERTING NOW");//---- Prints error message if MACD failed to load
      return(INIT_FAILED);//---- Returns initialization failure code
   }
   
   ArraySetAsSeries(rsiValues, true);//---- Sets RSI values array as a time series (latest data at index 0)
   
   ArraySetAsSeries(macdMAIN,true);//---- Sets MACD main line array as a time series
   ArraySetAsSeries(macdSIGNAL,true);//---- Sets MACD signal line array as a time series

   return(INIT_SUCCEEDED);//---- Returns successful initialization code
}

Здесь мы начинаем реализовывать нашу стратегию лейеринга сделок с помощью функции OnInit, которая является начальной точкой для инициализации ключевых компонентов перед тем, как советник начнет работать с рыночными данными. Настраиваем индикатор RSI с помощью функции iRSI, чтобы присвоить хэндл параметру "rsiHandle", передавая _Symbol в отношении текущего графика, _Period в отношении таймфрейма, "rsiLookbackPeriod" в отношении окна ретроспективного анализа и "PRICE_CLOSE" для использования цен закрытия. Далее следуют настройки индикатора MACD с функцией iMACD, в которой хранится его хэндл в "handleMACD" с использованием стандартных периодов 12, 26, 9 на "PRICE_CLOSE" для анализа тренда.

В целях обеспечения надежности проверяем равен ли "rsiHandle" параметру INVALID_HANDLE. Если это так, используем функцию Print для регистрации "UNABLE TO LOAD RSI, REVERTING NOW" и возвращаем INIT_FAILED, повторяя это действие для "handleMACD" с "UNABLE TO LOAD MACD, REVERTING NOW", чтобы остановить ошибку. После подтверждения настраиваем "rsiValues", "macdMAIN" и "macdSIGNAL" как массивы временных рядов, используя функцию ArraySetAsSeries со значением true, выравнивая последние данные по нулевому индексу, а затем возвращаем INIT_SUCCEEDED,  чтобы сигнализировать об успешной настройке и готовности к торговле. Далее мы можем перейти к обработчику событий OnTick и определить нашу реальную торговую логику.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){//---- Function called on each price tick
   double askPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);//---- Gets and normalizes current ask price
   double bidPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);//---- Gets and normalizes current bid price
   
   if(CopyBuffer(rsiHandle, 0, 1, 3, rsiValues) < 3){//---- Copies 3 RSI values into array, checks if successful
      Print("INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK");//---- Prints error if insufficient RSI data
      return;//---- Exits function if data copy fails
   }
   
   if (!CopyBuffer(handleMACD,MAIN_LINE,0,3,macdMAIN))return;//---- Copies 3 MACD main line values, exits if fails
   if (!CopyBuffer(handleMACD,SIGNAL_LINE,0,3,macdSIGNAL))return;//---- Copies 3 MACD signal line values, exits if fails
}

Здесь мы совершенствуем нашу стратегию лейеринга, реализуя функцию OnTick, которая активируется при каждом обновлении цены для принятия решений в советнике в режиме реального времени. Начинаем со сбора данных о текущих рыночных ценах с использованием функции NormalizeDouble, чтобы установить "askPrice" с помощью SymbolInfoDouble для _Symbol и "SYMBOL_ASK", скорректированную до "_Digits", а также "bidPrice" с помощью "SYMBOL_BID", обеспечивая точные данные о ценах для расчетов по сделкам. Этот шаг закладывает основу для мониторинга динамики цен, что приведет в действие нашу логику лейеринга, основанную на последних рыночных условиях.

Далее собираем данные индикатора для анализа сигналов, начиная с RSI, используя функцию CopyBuffer, чтобы загрузить три значения в "rsiValues" из "rsiHandle", начиная при индексе 1 и с буфером 0, и проверяем, скопировано ли менее 3 значений. Если это так, используем функцию "Print" для регистрации "INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK" (НЕДОСТАТОЧНО ДАННЫХ RSI ДЛЯ АНАЛИЗА, ПРОПУСК ТИКА) и возвращаемся для выхода из тика, предотвращая принятие решений с неполными данными. Затем применяем тот же подход в отношении MACD, используя "CopyBuffer" для заполнения "macdMAIN" тремя значениями основной линии из "handleMACD" в MAIN_LINE, а также "macdSIGNAL" со значениями сигнальной линии в SIGNAL_LINE, немедленно возвращаясь в случае ошибки, гарантируя продолжение работы только при полном наборе данных RSI и MACD. Однако нам нужно будет получить статистические данные и включить их и здесь. Итак, воспользуемся функциями.

//+------------------------------------------------------------------+
//| Calculate RSI Average                                            |
//+------------------------------------------------------------------+
double CalculateRSIAverage(int bars){//---- Function to calculate RSI average
   double sum = 0;//---- Initializes sum variable
   double buffer[];//---- Declares buffer array for RSI values
   ArraySetAsSeries(buffer, true);//---- Sets buffer as time series
   if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails
   
   for(int i = 0; i < bars; i++){//---- Loops through specified number of bars
      sum += buffer[i];//---- Adds each RSI value to sum
   }
   return sum / bars;//---- Returns average RSI value
}

//+------------------------------------------------------------------+
//| Calculate RSI STDDev                                             |
//+------------------------------------------------------------------+
double CalculateRSIStandardDeviation(int bars){//---- Function to calculate RSI standard deviation
   double average = CalculateRSIAverage(bars);//---- Calculates RSI average
   double sumSquaredDiff = 0;//---- Initializes sum of squared differences
   double buffer[];//---- Declares buffer array for RSI values
   ArraySetAsSeries(buffer, true);//---- Sets buffer as time series
   if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails
   
   for(int i = 0; i < bars; i++){//---- Loops through specified number of bars
      double diff = buffer[i] - average;//---- Calculates difference from average
      sumSquaredDiff += diff * diff;//---- Adds squared difference to sum
   }
   return MathSqrt(sumSquaredDiff / bars);//---- Returns standard deviation
}

Реализуем две ключевые функции — "CalculateRSIAverage" и "CalculateRSIStandardDeviation" — для проведения статистического анализа данных RSI, повышая точность сигнала. В функции "CalculateRSIAverage" определяем функцию, которая принимает "bars" в качестве входных данных, инициализируя "sum" со значения 0 и объявляя массив "buffer", который мы настраиваем как временной ряд, используя функцию ArraySetAsSeries со значением true, обеспечивая выравнивание последних значений RSI по нулевому индексу. Затем используем функцию CopyBuffer, чтобы загрузить количество "bars" значений RSI из "rsiHandle" в "buffer", возвращая 0, если скопировано меньше, чем "bars", и перебираем массив, чтобы добавить каждый "buffer[i]" в "sum", и, наконец, возвращаем значение "sum/bars" в качестве среднего значения RSI для использования при статистической фильтрации.

Далее, в "CalculateRSIStandardDeviation" мы развиваем это, вычисляя стандартное отклонение RSI за тот же период "bars", начиная с вызова "CalculateRSIAverage" для сохранения результата в "average" и устанавливая "sumSquaredDiff" равным 0 для квадратов разностей. Снова объявляем массив "buffer", задаем его как временной ряд с помощью ArraySetAsSeries и используем функцию "CopyBuffer" для извлечения значений RSI из "rsiHandle", возвращая 0 в случае сбоя копирования, обеспечивая целостность данных. Выполняем перебор по "buffer", вычисляя каждый "diff" как "buffer[i] - average", добавляя "diff * diff" к "sumSquaredDiff" и возвращаем стандартное отклонение, используя функцию MathSqrt для "sumSquaredDiff/bars", предоставляя статистическое измерение для уточнения наших решений по лейерингу сделок. Теперь мы можем использовать эти данные для статистического анализа, но нам нужно будет выполнять его один раз для каждого бара, чтобы избежать двусмысленности. Итак, давайте определим для этого функцию.

//+------------------------------------------------------------------+
//| Is New Bar                                                       |
//+------------------------------------------------------------------+
bool IsNewBar(){//---- Function to detect a new bar
   static int previousBarCount = 0;//---- Stores the previous bar count
   int currentBarCount = iBars(_Symbol, _Period);//---- Gets current number of bars
   if(previousBarCount == currentBarCount) return false;//---- Returns false if no new bar
   previousBarCount = currentBarCount;//---- Updates previous bar count
   return true;//---- Returns true if new bar detected
}

Здесь добавляем функцию "IsNewBar" к нашей стратегии, чтобы определить новые бары, используя значение статического "previousBarCount" равным 0, чтобы отслеживать счет последних баров и функцию iBars, чтобы получить "currentBarCount" для _Symbol и _Period, возвращая значение false при отсутствии изменений или значение true после обновления "previousBarCount", когда формируется новый бар. Вооружившись этой функцией, мы теперь можем определять правила торговли.

// Calculate statistical measures if enabled
double rsiAverage = useStatisticalFilter ? CalculateRSIAverage(statAnalysisPeriod) : 0;//---- Calculates RSI average if filter enabled
double rsiStdDeviation = useStatisticalFilter ? CalculateRSIStandardDeviation(statAnalysisPeriod) : 0;//---- Calculates RSI std dev if filter enabled

Мы реализуем статистические улучшения, вычисляя "rsiAverage" с помощью функции "CalculateRSIAverage", используя "statAnalysisPeriod", если значение "useStatisticalFilter" равно true, в противном случае устанавливая его равным 0, и аналогичным образом вычисляя "rsiStdDeviation" с помощью функции "CalculateRSIStandardDeviation" или устанавливая значение по умолчанию равным 0, что позволяет выполнять улучшенную фильтрацию сигналов при активации. Затем можем использовать результаты для определения торговых условий. Начнем с условий покупки.

if(PositionsTotal() == 0 && IsNewBar()){//---- Checks for no positions and new bar
   // Buy Signal
   bool buyCondition = rsiValues[1] <= rsiOversoldLevel && rsiValues[0] > rsiOversoldLevel;//---- Checks RSI crossing above oversold
   if(useStatisticalFilter){//---- Applies statistical filter if enabled
      buyCondition = buyCondition && (rsiValues[0] < (rsiAverage - statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition
   }
   
   buyCondition = macdMAIN[0] < 0 && macdSIGNAL[0] < 0;//---- Confirms MACD below zero for buy signal
}

Определяем логику сигнала на покупку, проверяя, равен ли PositionsTotal значению 0, и используя функцию "IsNewBar" для подтверждения нового бара, гарантируя, что сделки будут совершаться только при открытии бара без открытых позиций. Устанавливаем "buyCondition" в значение true, если "rsiValues[1]" находится ниже "rsiOversoldLevel", а "rsiValues[0]" поднимается выше него, и если включен "useStatisticalFilter", мы уточняем его далее, требуя, чтобы "rsiValues[0]" было меньше, чем "rsiAverage" минус "statDeviationFactor", умноженное на "rsiStdDeviation", добавляя статистическую точность. Наконец, подтверждаем сигнал значениями "macdMAIN[0]" и "macdSIGNAL[0]", которые оба находятся ниже нуля, выравнивая покупку по медвежьей зоне MACD для подтверждения тренда. Если условия выполнены, приступаем к открытию позиций, инициализируем переменные отслеживания и рисуем подтвержденные уровни на графике, в частности уровень тейк-профита. Для отрисовки уровней нам понадобится пользовательская функция.

//+------------------------------------------------------------------+
//| Draw TrendLine                                                   |
//+------------------------------------------------------------------+
void DrawTradeLevelLine(double price, bool isBuy){//---- Function to draw take profit line on chart
   // Delete existing objects first
   DeleteTradeLevelObjects();//---- Removes existing trade level objects
   
   // Create horizontal line
   ObjectCreate(0, takeProfitLineName, OBJ_HLINE, 0, 0, price);//---- Creates a horizontal line at specified price
   ObjectSetInteger(0, takeProfitLineName, OBJPROP_COLOR, clrBlue);//---- Sets line color to blue
   ObjectSetInteger(0, takeProfitLineName, OBJPROP_WIDTH, 2);//---- Sets line width to 2
   ObjectSetInteger(0, takeProfitLineName, OBJPROP_STYLE, STYLE_SOLID);//---- Sets line style to solid
   
   // Create text, above for buy, below for sell with increased spacing
   datetime currentTime = TimeCurrent();//---- Gets current time
   double textOffset = 30.0 * _Point;//---- Sets text offset distance from line
   double textPrice = isBuy ? price + textOffset : price - textOffset;//---- Calculates text position based on buy/sell
   
   ObjectCreate(0, takeProfitTextName, OBJ_TEXT, 0, currentTime + PeriodSeconds(_Period) * 5, textPrice);//---- Creates text object
   ObjectSetString(0, takeProfitTextName, OBJPROP_TEXT, DoubleToString(price, _Digits));//---- Sets text to price value
   ObjectSetInteger(0, takeProfitTextName, OBJPROP_COLOR, clrBlue);//---- Sets text color to blue
   ObjectSetInteger(0, takeProfitTextName, OBJPROP_FONTSIZE, 10);//---- Sets text font size to 10
   ObjectSetInteger(0, takeProfitTextName, OBJPROP_ANCHOR, isBuy ? ANCHOR_BOTTOM : ANCHOR_TOP);//---- Sets text anchor based on buy/sell
}

Здесь мы реализуем функцию "DrawTradeLevelLine" для визуализации уровней тейк-профита, начиная с вызова функции "DeleteTradeLevelObjects", чтобы очистить существующие объекты. Затем используем функцию ObjectCreate для рисования горизонтальной линии в "price" с помощью "takeProfitLineName" в качестве OBJ_HLINE, со стилем ObjectSetInteger для синего цвета "clrBlue", шириной 2 и параметром "STYLE_SOLID". Добавляем текстовую метку, выбирая "currentTime" с помощью "TimeCurrent", устанавливая для "textOffset" значение "30.0 * _Point" и вычисляя "textPrice" выше или ниже значения "price" на основе "isBuy", создавая его с помощью "ObjectCreate" в качестве "takeProfitTextName" в "OBJ_TEXT". Настраиваем текст, используя ObjectSetString, чтобы он отображал "price" посредством DoubleToString с помощью "_Digits" и "ObjectSetInteger" для синего цвета "clrBlue", размера 10 и "ANCHOR_BOTTOM" или "ANCHOR_TOP" в зависимости от "isBuy", что улучшает читаемость графика. Теперь мы можем использовать эту функцию для визуализации целевых уровней.

if(buyCondition){//---- Executes if buy conditions are met
   Print("BUY SIGNAL - RSI: ", rsiValues[0],//---- Prints buy signal details
         useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : "");
   stopLossLevel = askPrice - stopLossPoints * _Point;//---- Calculates stop loss level for buy
   takeProfitLevel = askPrice + (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for buy
   obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Signal Position");//---- Places buy order
   buySequenceActive = true;//---- Activates buy sequence flag
   DrawTradeLevelLine(takeProfitLevel, true);//---- Draws take profit line for buy
}

Здесь мы обрабатываем исполнение сделки на покупку, когда "buyCondition" имеет значение true, используя функцию Print для регистрации в логе "BUY SIGNAL - RSI: " с помощью "rsiValues[0]", добавляя "rsiAverage" и "rsiStdDeviation" с помощью функции DoubleToString, если включена "useStatisticalFilter" для получения подробной обратной связи. Рассчитываем "stopLossLevel" как "askPrice" минус "stopLossPoints * _Point" и "takeProfitLevel" как "askPrice" плюс "stopLossPoints * riskRewardRatio * _Point", затем используем метод "obj_Trade.Buy" - метод для размещения ордера на покупку с указанием "tradeVolume", _Symbol, "askPrice", "stopLossLevel" и "takeProfitLevel", с пометкой "Signal Position". Наконец, устанавливаем для параметра "buySequenceActive" значение true и вызываем "DrawTradeLevelLine" с параметром "takeProfitLevel" и значением true, чтобы визуализировать линию тейк-профита на покупку. После запуска программы получаем следующий результат.

CONFIRMED BUY POSITION

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

// Sell Signal
bool sellCondition = rsiValues[1] >= rsiOverboughtLevel && rsiValues[0] < rsiOverboughtLevel;//---- Checks RSI crossing below overbought
if(useStatisticalFilter){//---- Applies statistical filter if enabled
   sellCondition = sellCondition && (rsiValues[0] > (rsiAverage + statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition
}

sellCondition = macdMAIN[0] > 0 && macdSIGNAL[0] > 0;//---- Confirms MACD above zero for sell signal

if(sellCondition){//---- Executes if sell conditions are met
   Print("SELL SIGNAL - RSI: ", rsiValues[0],//---- Prints sell signal details
         useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : "");
   stopLossLevel = bidPrice + stopLossPoints * _Point;//---- Calculates stop loss level for sell
   takeProfitLevel = bidPrice - (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for sell
   obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Signal Position");//---- Places sell order
   sellSequenceActive = true;//---- Activates sell sequence flag
   DrawTradeLevelLine(takeProfitLevel, false);//---- Draws take profit line for sell
}

Здесь мы действуем по логике, противоположной логике сделки buy, устанавливая "sellCondition", если "rsiValues[1]" превышает "rsiOverboughtLevel", а "rsiValues[0]" опускается ниже, добавляя "useStatisticalFilter", чтобы проверить "rsiValues[0]" выше "rsiAverage + statDeviationFactor * rsiStdDeviation", и подтверждая с помощью "macdMAIN[0]" и "macdSIGNAL[0]" выше нуля. Если значение равно true, используем "Print" для "SELL SIGNAL - RSI: " с "rsiValues[0]" и статистикой посредством DoubleToString, устанавливаем "bidPrice + stopLossPoints * _Point", а "takeProfitLevel" как "bidPrice - (stopLossPoints * riskRewardRatio) * _Point", затем вызываем "obj_Trade.Sell" и "DrawTradeLevelLine" со значением false, активируя "sellSequenceActive". Теперь, когда позиции открыты, нам нужно распределить выигрышные позиции по каскаду, следуя тренду и изменяя позиции. Вот функция для изменения сделок.

//+------------------------------------------------------------------+
//| Modify Trades                                                    |
//+------------------------------------------------------------------+
void ModifyTrades(ENUM_POSITION_TYPE positionType, double newStopLoss){//---- Function to modify open trades
   for(int i = 0; i < PositionsTotal(); i++){//---- Loops through all open positions
      ulong ticket = PositionGetTicket(i);//---- Gets ticket number of position
      if(ticket > 0 && PositionSelectByTicket(ticket)){//---- Checks if ticket is valid and selectable
         ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);//---- Gets position type
         if(type == positionType){//---- Checks if position matches specified type
            obj_Trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP));//---- Modifies position with new stop loss
         }
      }
   }
}

Здесь мы реализуем функцию "ModifyTrades" для обновления открытых сделок, используя "positionType" и "newStopLoss" в качестве входных данных, а затем используем функцию PositionsTotal для перебора всех позиций с помощью цикла for от 0 до "i". Для каждой мы используем функцию PositionGetTicket, чтобы получить номер "ticket", проверить действителен ли он и можно ли его выбрать с помощью PositionSelectByTicket и используем "PositionGetInteger", чтобы получить "type" в виде ENUM_POSITION_TYPE, сравнивая его с "positionType". При совпадении используем "obj_Trade.PositionModify", чтобы скорректировать стоп-лосс сделки на "newStopLoss", сохраняя при этом тейк-профит от PositionGetDouble с помощью "POSITION_TP", обеспечивая точное управление сделками. Затем можем использовать эту функцию для каскадирования сделок.

else {//---- Handles cascading logic when positions exist
   // Cascading Buy Logic
   if(buySequenceActive && askPrice >= takeProfitLevel){//---- Checks if buy sequence active and price hit take profit
      double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level
      takeProfitLevel = previousTakeProfit + (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level
      stopLossLevel = askPrice - minStopLossPoints * _Point;//---- Sets new stop loss level
      obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Cascade Position");//---- Places new buy order
      ModifyTrades(POSITION_TYPE_BUY, stopLossLevel);//---- Modifies existing buy trades with new stop loss
      Print("CASCADING BUY - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading buy details
      DrawTradeLevelLine(takeProfitLevel, true);//---- Updates take profit line for buy
   }
   // Cascading Sell Logic
   else if(sellSequenceActive && bidPrice <= takeProfitLevel){//---- Checks if sell sequence active and price hit take profit
      double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level
      takeProfitLevel = previousTakeProfit - (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level
      stopLossLevel = bidPrice + minStopLossPoints * _Point;//---- Sets new stop loss level
      obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Cascade Position");//---- Places new sell order
      ModifyTrades(POSITION_TYPE_SELL, stopLossLevel);//---- Modifies existing sell trades with new stop loss
      Print("CASCADING SELL - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading sell details
      DrawTradeLevelLine(takeProfitLevel, false);//---- Updates take profit line for sell
   }
}

Здесь мы обрабатываем каскадную логику, когда позиции существуют, проверяя, соответствует ли значение "buySequenceActive" значению "true", а "askPrice" соответствует "takeProfitLevel", затем сохраняем "previousTakeProfit", устанавливаем новый "takeProfitLevel" с добавлением "stopLossPoints * riskRewardRatio * _Point" и "stopLossLevel" в виде "askPrice - minStopLossPoints * _Point", используя "obj_Trade.Buy" для нового ордера и "ModifyTrades" для обновления стоп-ордеров POSITION_TYPE_BUY, при этом "Print" регистрирует информацию "CASCADING BUY", а "DrawTradeLevelLine" обновляет строку покупки.

Для сделок на продажу, если значение "sellSequenceActive" равно true и "bidPrice" достигает "takeProfitLevel", мы отражаем это, вычитая из "previousTakeProfit" новый "takeProfitLevel", устанавливая "stopLossLevel" как "bidPrice + minStopLossPoints * _Point", вызывая "obj_Trade.Sell" и "ModifyTrades" для POSITION_TYPE_SELL, регистрируя с помощью "Print" и обновляя строку продажи с помощью "DrawTradeLevelLine". Запустив систему, получаем следующие результаты.

POSITIONS CASCADING

Смотря на изображение мы можем подтвердить каскад позиций и изменить стоп-лосс для всех позиций. Теперь все, что нам нужно сделать, это убедиться, что когда система нам не нужна, все добавленные объекты удалены. Этого можно добиться с помощью обработчика событий OnDeinit, но сначала нам понадобится функция очистки.

//+------------------------------------------------------------------+
//| Delete Level Objects                                             |
//+------------------------------------------------------------------+
void DeleteTradeLevelObjects(){//---- Function to delete trade level objects
   ObjectDelete(0, takeProfitLineName);//---- Deletes take profit line object
   ObjectDelete(0, takeProfitTextName);//---- Deletes take profit text object
}

Здесь мы реализуем функцию "DeleteTradeLevelObjects" для очистки визуальных элементов графика, используя функцию ObjectDelete для удаления линейного объекта "takeProfitLineName" и текстового объекта "takeProfitTextName", обеспечивая очистку старых уровней тейк-профита перед построением новых. Теперь вызываем эту функцию в обработчике событий OnDeinit

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){//---- Expert advisor deinitialization function
   DeleteTradeLevelObjects();//---- Removes trade level visualization objects from chart
}

Здесь мы просто вызываем функцию для удаления объектов с графика, гарантируя, что мы очистим график после удаления программы, и, следовательно, достигнем нашей цели. Осталось провести повторное тестирование программы, и это будет выполнено в следующем разделе.


Тестирование на истории

После тщательного тестирования на истории мы получили следующие результаты.

График тестирования на истории:

GRAPH

Отчет о тестировании на истории:

REPORT


Заключение

В заключение отметим, что нами успешно разработана стратегия лейеринга сделок на MQL5, сочетающая в себе индикаторы MACD и RSI со статистическими методами для автоматизации динамического масштабирования позиций на трендовых рынках. Программа обеспечивает надежное обнаружение сигналов, каскадную торговую логику и визуальные уровни тейк-профита, с высокой точностью адаптируясь к изменениям импульса. Вы можете использовать эту основу для дальнейшего улучшения с помощью таких настроек, как оптимизация "rsiLookbackPeriod" или корректируя "riskRewardRatio" для повышения эффективности.

Отказ от ответственности: Содержание настоящей статьи предназначено только для целей обучения. Торговля сопряжена со значительным финансовым риском, а поведение рынка может быть изменчивым. Перед началом использования в реальных условиях критически важным является тщательное тестирование на истории и управление рисками.

С помощью этого примера вы сможете отточить свои навыки автоматизации и усовершенствовать стратегию. Экспериментируйте и оптимизируйте. Удачной торговли!

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17741

Алгоритм искусственной коронарной циркуляции — Artificial Coronary Circulation System (ACCS) Алгоритм искусственной коронарной циркуляции — Artificial Coronary Circulation System (ACCS)
Метаэвристический алгоритм, имитирующий рост коронарных артерий в сердце человека для задач оптимизации. Использует принципы ангиогенеза (роста новых сосудов), бифуркации (разветвления) и обрезки слабых ветвей для поиска оптимальных решений в многомерном пространстве. Проверка его эффективности на широком спектре задач принесла неожиданные результаты.
Разрабатываем менеджер терминалов (Часть 2): Запуск нескольких экземпляров Разрабатываем менеджер терминалов (Часть 2): Запуск нескольких экземпляров
Переходим к использованию сразу нескольких экземпляров терминала на сервере, организовав простую панель управления запуском и остановкой. Теперь пришло время расширять функциональность и переходить к следующим этапам — реализации более сложных возможностей, таких как управление несколькими экземплярами, хранение состояния, интеграция с MetaTrader5 API и веб-интерфейс с полной информацией о терминалах.
От начального до среднего уровня: Struct (V) От начального до среднего уровня: Struct (V)
В данной статье мы рассмотрим, как перегрузить структурный код. Я знаю, что сначала это довольно сложно для понимания, особенно если увидеть это впервые. Очень важно, чтобы вы усвоили эти понятия и хорошо поняли их, прежде чем пытаться вникать в более сложные и проработанные вещи.
От начального до среднего уровня: Struct (IV) От начального до среднего уровня: Struct (IV)
В данной статье мы рассмотрим, как создавать так называемый структурный код, в котором весь контекст и способы манипулирования переменными и информацией помещаются в структуру, чтобы создать подходящий контекст для реализации любого кода. Итак, мы рассмотрим необходимость использования приватной (private) части кода, чтобы отделить то, что является общедоступным, от того, что не является таковым, соблюдая тем самым правило инкапсуляции и сохраняя контекст, для которого была создана структура данных.