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

Далее мы усовершенствуем эту настройку, внедрив статистические методы для повышения точности ввода и управления процессом лейеринга. Мы рассмотрим, как применять статистические фильтры, такие как анализ исторического поведения 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" в качестве названий объектов для линий и меток тейк-профита на графике, что расширяет возможности трейдера отслеживать уровни в режиме реального времени. После компиляции программы увидим следующий результат.

Далее переходим к обработчику событий инициализации (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, чтобы визуализировать линию тейк-профита на покупку. После запуска программы получаем следующий результат.

На изображении видно, что все условия для подачи сигнала на покупку выполнены, и мы автоматически отмечаем следующий уровень на графике. Таким образом, мы можем продолжать делать то же самое для сигнала на продажу.
// 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". Запустив систему, получаем следующие результаты.

Смотря на изображение мы можем подтвердить каскад позиций и изменить стоп-лосс для всех позиций. Теперь все, что нам нужно сделать, это убедиться, что когда система нам не нужна, все добавленные объекты удалены. Этого можно добиться с помощью обработчика событий 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 }
Здесь мы просто вызываем функцию для удаления объектов с графика, гарантируя, что мы очистим график после удаления программы, и, следовательно, достигнем нашей цели. Осталось провести повторное тестирование программы, и это будет выполнено в следующем разделе.
Тестирование на истории
После тщательного тестирования на истории мы получили следующие результаты.
График тестирования на истории:

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

Заключение
В заключение отметим, что нами успешно разработана стратегия лейеринга сделок на MQL5, сочетающая в себе индикаторы MACD и RSI со статистическими методами для автоматизации динамического масштабирования позиций на трендовых рынках. Программа обеспечивает надежное обнаружение сигналов, каскадную торговую логику и визуальные уровни тейк-профита, с высокой точностью адаптируясь к изменениям импульса. Вы можете использовать эту основу для дальнейшего улучшения с помощью таких настроек, как оптимизация "rsiLookbackPeriod" или корректируя "riskRewardRatio" для повышения эффективности.
Отказ от ответственности: Содержание настоящей статьи предназначено только для целей обучения. Торговля сопряжена со значительным финансовым риском, а поведение рынка может быть изменчивым. Перед началом использования в реальных условиях критически важным является тщательное тестирование на истории и управление рисками.
С помощью этого примера вы сможете отточить свои навыки автоматизации и усовершенствовать стратегию. Экспериментируйте и оптимизируйте. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17741
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Алгоритм искусственной коронарной циркуляции — Artificial Coronary Circulation System (ACCS)
Разрабатываем менеджер терминалов (Часть 2): Запуск нескольких экземпляров
От начального до среднего уровня: Struct (V)
От начального до среднего уровня: Struct (IV)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования