English 中文 Español Deutsch 日本語
preview
Автоматизация торговых стратегий на MQL5 (Часть 5): Разработка стратегии Adaptive Crossover RSI Trading Suite

Автоматизация торговых стратегий на MQL5 (Часть 5): Разработка стратегии Adaptive Crossover RSI Trading Suite

MetaTrader 5Трейдинг |
312 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В предыдущей статье (часть 4 серии) мы представили систему многоуровневого зонального восстановления (Multi-Level Zone Recovery), продемонстрировав, как расширить принципы Zone Recovery для одновременного управления несколькими независимыми торговыми настройками на языке MetaQuotes Language 5 (MQL5). В этой статье (часть 5) мы выбираем новое направление с помощью стратегии Adaptive Crossover RSI Trading Suite, комплексной системы, предназначенной для выявления и использования торговых возможностей с высокой вероятностью. Эта стратегия сочетает в себе два важных инструмента технического анализа: пересечения адаптивных скользящих средних с периодами 14 и 50 в качестве основного генератора сигналов и индикатор относительной силы (RSI) с периодом 14 в качестве фильтра для подтверждения.

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

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

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


План стратегии

Стратегия Adaptive Crossover RSI Trading Suite построена на основе пересечения скользящих средних и подтверждения сигналов индикатором импульса, создавая сбалансированный подход к торговле. Основные сигналы будут получены из взаимодействия между быстрой скользящей средней (14) и медленной скользящей средней (50). Сигнал на покупку появляется, когда быстрая скользящая средняя пересекает медленную скользящую среднюю сверху, что указывает на бычий тренд, а сигнал на продажу появляется, когда быстрая скользящая средняя пересекает медленную скользящую среднюю снизу, что указывает на медвежий тренд.

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

План подтверждения сделки на продажу:

SELL TRADE BLUEPRINT

План подтверждения сделки на покупку:

BUY TRADE BLUEPRINT

Кроме того, после подтверждения сделки система пометит график стрелками сигналов и пояснениями, четко обозначив точки входа. Дашборд будет обновляться в режиме реального времени, отображая текущую активность сигналов, ключевые показатели и общий статус системы. Этот структурированный и адаптивный подход обеспечит надежность и удобство использования стратегии. Окончательный вид будет соответствовать изображенному ниже.

FINAL BLUEPRINT


Реализация в MQL5

Изучив всю теоретическую часть стратегии Adaptive Crossover RSI Trading Suite, приступим к автоматизации теории и создадим советника на языке MetaQuotes Language 5 (MQL5) для MetaTrader 5.

Чтобы создать советника, в терминале MetaTrader 5 перейдите на вкладку "Сервис" и выберите пункт "Редактор MetaQuotes Language" или просто нажмите F4 на клавиатуре. Также можно нажать на иконку IDE (интегрированная среда разработки) на панели инструментов. Откроется среда редактора MetaQuotes Language, которая позволяет писать торговых роботов, технические индикаторы, скрипты и библиотеки функций. После открытия MetaEditor в строке меню перейдите на вкладку "Файл" и выберите пункт "Новый файл" или просто нажмите CTRL + N, чтобы создать новый документ. Также можно нажать на иконку "Создать" на панели инструментов. Откроется всплывающее окно Мастера MQL.

В появившемся мастере выберите "Советник (шаблон)" и нажмите "Далее". В общих свойствах советника в разделе "Имя" укажите имя файла вашего советника. Обратите внимание, что для указания или создания папки, если она не существует, перед именем советника необходимо использовать обратный слеш. Например, здесь по умолчанию задано "Experts\". Это означает, что советник будет создан в папке Experts, и мы сможем найти его там. Остальные разделы довольно просты, но вы можете перейти по ссылке внизу мастера, чтобы узнать, как точно выполнить этот процесс.

NEW EA NAME

После указания имени файла нажмите "Далее", затем "Далее" и "Готово". После этого мы готовы к написанию кода и программированию нашей стратегии.

Сначала мы начинаем с определения некоторых метаданных о советнике. Они включают название советника, информацию об авторских правах и ссылку на сайт MetaQuotes. Мы также указываем версию советника – "1.00".

//+------------------------------------------------------------------+
//|                         Adaptive Crossover RSI Trading Suite.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "EA that trades based on MA Crossover, RSI + Day Filter"
#property strict

Это отобразит метаданные системы при загрузке программы. Затем мы можем перейти к добавлению некоторых глобальных переменных, которые будем использовать в программе. Сперва мы включаем экземпляр сделки через директиву #include в начале исходного кода. Это дает доступ к классу CTrade, который мы будем использовать для создания объекта сделки. Это очень важно, поскольку нам это необходимо для открытия сделок.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

Препроцессор заменит строку #include <Trade/Trade.mqh> содержимым файла Trade.mqh. Угловые скобки указывают, что файл Trade.mqh будет взят из стандартного каталога (обычно это terminal_installation_directory\MQL5\Include). Текущий каталог не включается в поиск. Строка может быть размещена в любом месте программы, но обычно все включения размещаются в начале исходного кода для лучшей структуры кода и удобства ссылки. Объявление объекта obj_Trade класса CTrade даст легкий доступ к методам, содержащимся в этом классе, благодаря разработчикам MQL5.

CTRADE CLASS

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

sinput group "GENERAL SETTINGS"
sinput double inpLots = 0.01; // LotSize
input int inpSLPts = 300; // Stoploss Points
input double inpR2R = 1.0; // Risk to Reward Ratio
sinput ulong inpMagicNo = 1234567; // Magic Number
input bool inpisAllowTrailingStop = true; // Apply Trailing Stop?
input int inpTrailPts = 50; // Trailing Stop Points
input int inpMinTrailPts = 50; // Minimum Trailing Stop Points

sinput group "INDICATOR SETTINGS"
input int inpMA_Fast_Period = 14; // Fast MA Period
input ENUM_MA_METHOD inpMA_Fast_Method = MODE_EMA; // Fast MA Method
input int inpMA_Slow_Period = 50; // Slow MA Period
input ENUM_MA_METHOD inpMA_Slow_Method = MODE_EMA; // Slow MA Method

sinput group "FILTER SETTINGS"
input ENUM_TIMEFRAMES inpRSI_Tf = PERIOD_CURRENT; // RSI Timeframe
input int inpRSI_Period = 14; // RSI Period
input ENUM_APPLIED_PRICE inpRSI_Applied_Price = PRICE_CLOSE; // RSI Application Price
input double inpRsiBUYThreshold = 50; // BUY Signal Threshold
input double inpRsiSELLThreshold = 50; // SELL Signal Threshold

input bool Sunday = false; // Trade on Sunday?
input bool Monday = false; // Trade on Monday?
input bool Tuesday = true; // Trade on Tuesday?
input bool Wednesday = true; // Trade on Wednesday?
input bool Thursday = true; // Trade on Thursday?
input bool Friday = false; // Trade on Friday?
input bool Saturday = false; // Trade on Saturday?

Здесь мы определяем основные параметры и настройки программы Adaptive Crossover RSI Trading Suite, что позволяет точно контролировать ее поведение. Мы разделяем эти настройки на три основные группы: "GENERAL SETTINGS" (общие настройки), "INDICATOR SETTINGS" (настройки индикаторов) и "FILTER SETTINGS" (настройки фильтров), а также специальные настройки для торговых дней. Использование типов переменных и перечислений повышает гибкость и ясность в дизайне системы.

В группе "GENERAL SETTINGS" мы определяем параметры управления торговлей. Мы используем ключевое слово input для оптимизируемых параметров и sinput для строковых или неоптимизируемых параметров. Переменная inpLots определяет размер лота, а inpSLPts устанавливает уровень стоп-лосса в пунктах, обеспечивая контроль риска для каждой сделки. Переменная inpR2R устанавливает желаемое соотношение риска и прибыли, поддерживая благоприятный баланс между риском и потенциальной прибылью. Уникальный идентификатор сделки назначается с помощью переменной inpMagicNo, которую программа использует для различения своих ордеров. Функция трейлинг-стопа управляется с помощью inpisAllowTrailingStop, позволяя пользователям активировать или деактивировать ее. Переменные inpTrailPts и inpMinTrailPts определяют расстояние трейлинг-стопа и минимальный порог активации соответственно, обеспечивая соответствие трейлинг-стопов рыночным условиям.

В группе "INDICATOR SETTINGS" мы настраиваем параметры скользящих средних, которые составляют основу генерации сигналов. Период быстрой скользящей средней определяется inpMA_Fast_Period, а метод его расчета выбирается с помощью перечисления ENUM_MA_METHOD с переменной inpMA_Fast_Method, которая поддерживает такие опции, как MODE_SMA, MODE_EMA, MODE_SMMA и MODE_LWMA. Аналогичным образом, медленная скользящая средняя устанавливается с помощью inpMA_Slow_Period, а ее метод определяется с помощью inpMA_Slow_Method. Эти перечисления позволяют пользователям настраивать стратегию с использованием предпочтительных типов скользящих средних для различных рыночных условий.

Группа FILTER SETTINGS (настройки фильтров) сосредоточена на индикаторе RSI, который служит в качестве фильтра импульса. Переменная inpRSI_Tf, определённая с помощью перечисления ENUM_TIMEFRAMES, позволяет пользователям выбирать временной интервал RSI, например, PERIOD_M1, PERIOD_H1 или PERIOD_D1. Период RSI указывается с помощью inpRSI_Period, а inpRSI_Applied_Price, перечисление ENUM_APPLIED_PRICE, определяет ценовые данные (например, PRICE_CLOSE, PRICE_OPEN или PRICE_MEDIAN), используемые для расчетов. Пороги для проверки сигналов на покупку и продажу устанавливаются с помощью inpRsiBUYThreshold и inpRsiSELLThreshold, что обеспечивает согласованность RSI с рыночным импульсом перед выполнением сделок.

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

int handleMAFast = INVALID_HANDLE;
int handleMASlow = INVALID_HANDLE;
int handleRSIFilter = INVALID_HANDLE;

Мы инициализируем три ключевые переменные — handleMAFast, handleMASlow и handleRSIFilter — и устанавливаем для них значение INVALID_HANDLE. Таким образом, мы гарантируем, что наш советник запускается в чистом и контролируемом состоянии, избегая потенциальных проблем, связанных с неинициализированными или недействительными дескрипторами индикаторов. Мы используем handleMAFast для управления индикатором быстрой скользящей средней, который мы настраиваем для фиксации краткосрочных ценовых трендов на основе определенных нами параметров.

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

double bufferMAFast[];
double bufferMASlow[];
double bufferRSIFilter[];

Здесь мы объявляем три динамических массива: bufferMAFast[], bufferMASlow[] и bufferRSIFilter[]. Эти массивы будут служить контейнерами хранения, в которых мы будем собирать и управлять рассчитанными значениями индикаторов, используемых в нашей стратегии. Организуя данные таким образом, мы обеспечиваем нашему советнику прямой и эффективный доступ к результатам индикаторов во время его работы. Теперь нам нужно перейти к функции инициализации и создать дескрипторы индикаторов.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   handleMAFast = iMA(_Symbol,_Period,inpMA_Fast_Period,0,inpMA_Fast_Method,PRICE_CLOSE);
   handleMASlow = iMA(_Symbol,_Period,inpMA_Slow_Period,0,inpMA_Slow_Method,PRICE_CLOSE);
   
   handleRSIFilter = iRSI(_Symbol,inpRSI_Tf,inpRSI_Period,inpRSI_Applied_Price);

//----

}

Здесь мы инициализируем дескрипторы для индикаторов, которые будем использовать в стратегии: быстрая скользящая средняя, медленная скользящая средняя и фильтр RSI. Мы начинаем с инициализации быстрой скользящей средней с помощью функции iMA. Эта функция требует нескольких параметров. Первый, _Symbol, указывает функции рассчитать скользящую среднюю для текущего торгового инструмента. Второй, _Period, указывает таймфрейм графика (например, 1 минута, 1 час).

Мы также передаем период быстрой скользящей средней (inpMA_Fast_Period), который определяет, сколько баров используется для расчета скользящей средней. Параметр "0" предназначен для сдвига скользящей средней, при этом "0" означает отсутствие сдвига. Метод расчета скользящей средней (inpMA_Fast_Method) указывает, является ли она экспоненциальной или простой скользящей средней, а PRICE_CLOSE указывает, что мы используем цены закрытия каждого бара для расчета среднего.

Результат этой функции присваивается handleMAFast, что дает возможность получить доступ к быстрому среднему значению для будущих вычислений.

Далее мы инициализируем медленную скользящую среднюю таким же образом, вызывая функцию iMA. Здесь мы используем те же _Symbol, _Period и период медленной скользящей средней (inpMA_Slow_Period). Опять же, мы указываем метод и цену (PRICE_CLOSE), используемые для расчета этой скользящей средней. Это значение сохраняется в handleMASlow для дальнейшего использования. Наконец, мы инициализируем фильтр RSI с помощью функции iRSI. Мы указываем _Symbol для определения инструмента, временной рамки RSI (inpRSI_Tf), периода RSI (inpRSI_Period) и применяемой цены (inpRSI_Applied_Price). Результат функции сохраняется в handleRSIFilter, что позволит нам использовать значение RSI для подтверждения торговых сигналов в стратегии.

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

if (handleMAFast == INVALID_HANDLE || handleMASlow == INVALID_HANDLE || handleRSIFilter == INVALID_HANDLE){
   Print("ERROR! Unable to create the indicator handles. Reveting Now!");
   return (INIT_FAILED);
}

Здесь мы проверяем, удалось ли инициализировать дескрипторы индикаторов. Мы проверяем, равен ли какой-либо из дескрипторов (handleMAFast, handleMASlow или handleRSIFilter) INVALID_HANDLE, что указывало бы на сбой при создании соответствующих индикаторов. Если какой-либо из дескрипторов не работает, функция Print выводит сообщение об ошибке в терминале, предупреждающего нас о проблеме. Наконец, мы возвращаем INIT_FAILED, что останавливает выполнение советника, если какой-либо из дескрипторов индикаторов недействителен, гарантируя, что советник не будет продолжать работу в неисправном состоянии.

Еще одна ошибка может возникнуть, если пользователь укажет период меньше или равный нулю. Таким образом, нам необходимо проверить заданные пользователем входные значения для периодов быстрой скользящей средней, медленной скользящей средней и RSI, чтобы убедиться, что периоды (inpMA_Fast_Period, inpMA_Slow_Period, inpRSI_Period) больше нуля.

if (inpMA_Fast_Period <= 0 || inpMA_Slow_Period <= 0 || inpRSI_Period <= 0){
   Print("ERROR! Periods cannot be <= 0. Reverting Now!");
   return (INIT_PARAMETERS_INCORRECT);
}

Здесь, если введенные пользователем значения не больше нуля, мы завершаем программу, возвращая INIT_PARAMETERS_INCORRECT. Если мы пропускаем этот этап, то у нас есть готовые дескрипторы индикаторов, и мы можем задать массивы хранения в качестве временных рядов.

ArraySetAsSeries(bufferMAFast,true);
ArraySetAsSeries(bufferMASlow,true);
ArraySetAsSeries(bufferRSIFilter,true);

obj_Trade.SetExpertMagicNumber(inpMagicNo);

Print("SUCCESS INITIALIZATION. ACCOUNT TYPE = ",trading_Account_Mode());

Наконец, выполним несколько ключевых действий в завершение инициализации. Функция ArraySetAsSeries задает массивы (bufferMAFast, bufferMASlow и bufferRSIFilter) в качестве временных рядов. Это важно, потому что обеспечивает хранение данных в этих массивах в формате, совместимом с обработкой временных рядов в MetaTrader, где данные самого последнего бара хранятся под индексом 0. Установив каждый из этих массивов в качестве ряда, мы обеспечиваем доступ к индикаторам в правильном порядке во время торговли.

Далее мы вызываем метод SetExpertMagicNumber на объекте obj_Trade, передавая значение inpMagicNo в качестве магического числа. Магическое число — это уникальный идентификатор ордеров советника, который позволяет отличать их от других сделок, размещенных вручную или другими советниками. Наконец, мы используем функцию Print для вывода сообщения об успешном завершении процесса инициализации в терминале. Сообщение включает тип счета, который извлекается с помощью функции trading_Account_Mode — указывающей, является ли счет демо-счетом или реальным счетом. Функция, отвечающая за это, выглядит следующим образом.

string trading_Account_Mode(){
   string account_mode;
   switch ((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)){
      case ACCOUNT_TRADE_MODE_DEMO:
         account_mode = "DEMO";
         break;
      case ACCOUNT_TRADE_MODE_CONTEST:
         account_mode = "COMPETITION";
         break;
      case ACCOUNT_TRADE_MODE_REAL:
         account_mode = "REAL";
         break;
   }
   return account_mode;
}

Здесь мы определяем строковую функцию trading_Account_Mode для определения типа торгового счета (демо-счет, конкурсный счет или реальный счет) на основе значения параметра ACCOUNT_TRADE_MODE. Мы начинаем с объявления переменной account_mode для хранения типа счета в виде строки. Затем мы используем оператор switch для оценки режима торговли счета, который получается путем вызова функции AccountInfoInteger с параметром ACCOUNT_TRADE_MODE. Эта функция возвращает режим торговли счета в виде целого числа. Оператор switch проверяет значение этого целого числа и сравнивает его с возможными режимами счета:

  1. Если режим счета ACCOUNT_TRADE_MODE_DEMO, мы устанавливаем "account_mode" в "DEMO".
  2. Если режим счета ACCOUNT_TRADE_MODE_CONTEST, то "account_mode" устанавливаем равным "COMPETITION".
  3. Если режим счета ACCOUNT_TRADE_MODE_REAL, то устанавливаем "account_mode" равным "REAL".

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   handleMAFast = iMA(_Symbol,_Period,inpMA_Fast_Period,0,inpMA_Fast_Method,PRICE_CLOSE);
   handleMASlow = iMA(_Symbol,_Period,inpMA_Slow_Period,0,inpMA_Slow_Method,PRICE_CLOSE);
   
   handleRSIFilter = iRSI(_Symbol,inpRSI_Tf,inpRSI_Period,inpRSI_Applied_Price);
   
   if (handleMAFast == INVALID_HANDLE || handleMASlow == INVALID_HANDLE || handleRSIFilter == INVALID_HANDLE){
      Print("ERROR! Unable to create the indicator handles. Reveting Now!");
      return (INIT_FAILED);
   }
   
   if (inpMA_Fast_Period <= 0 || inpMA_Slow_Period <= 0 || inpRSI_Period <= 0){
      Print("ERROR! Periods cannot be <= 0. Reverting Now!");
      return (INIT_PARAMETERS_INCORRECT);
   }
   
   ArraySetAsSeries(bufferMAFast,true);
   ArraySetAsSeries(bufferMASlow,true);
   ArraySetAsSeries(bufferRSIFilter,true);
   
   obj_Trade.SetExpertMagicNumber(inpMagicNo);
   
   Print("SUCCESS INITIALIZATION. ACCOUNT TYPE = ",trading_Account_Mode());
   
//---
   return(INIT_SUCCEEDED);
}

Теперь мы переходим к обработчику события OnDeinit, где нам нужно будет освободить дескрипторы индикаторов, так как они нам больше не понадобятся.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   IndicatorRelease(handleMAFast);
   IndicatorRelease(handleMASlow);
   IndicatorRelease(handleRSIFilter);
}

Чтобы освободить ресурсы, выделенные для дескрипторов индикаторов, мы начинаем с вызова функции IndicatorRelease для каждого из дескрипторов индикаторов: handleMAFast, handleMASlow и handleRSIFilter. Цель этой функции — освободить память и ресурсы, связанные с дескрипторами индикаторов, которые были инициализированы во время выполнения советника. Это предотвращает излишнее потребление ресурсов платформы индикаторами, которые больше не используются. Далее мы переходим к обработчику событий OnTick, где будет обрабатываться большая часть нашей торговой логики. Сначала нам нужно получить данные индикаторов из дескрипторов.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//--- Check if data can be retrieved for the fast moving average (MA)
   if (CopyBuffer(handleMAFast,0,0,3,bufferMAFast) < 3){
   //--- Print error message for fast MA data retrieval failure
      Print("ERROR! Failed to retrieve the requested FAST MA data. Reverting.");
   //--- Exit the function if data retrieval fails
      return;
   }
//--- Check if data can be retrieved for the slow moving average (MA)
   if (CopyBuffer(handleMASlow,0,0,3,bufferMASlow) < 3){
   //--- Print error message for slow MA data retrieval failure
      Print("ERROR! Failed to retrieve the requested SLOW MA data. Reverting.");
   //--- Exit the function if data retrieval fails
      return;
   }
//--- Check if data can be retrieved for the RSI filter
   if (CopyBuffer(handleRSIFilter,0,0,3,bufferRSIFilter) < 3){
   //--- Print error message for RSI data retrieval failure
      Print("ERROR! Failed to retrieve the requested RSI data. Reverting.");
   //--- Exit the function if data retrieval fails
      return;
   }

   //---   

}

Здесь мы сосредоточимся на извлечении последних данных индикаторов для быстрой скользящей средней, медленной скользящей средней и фильтра RSI, чтобы обеспечить советник необходимой информацией для принятия торговых решений. Сначала мы используем функцию CopyBuffer для дескриптора быстрой скользящей средней (handleMAFast). Функция извлекает значения индикаторов в соответствующий буфер (bufferMAFast) для обработки. В частности, мы запрашиваем 3 точки данных, начиная с индекса 0, который представляет самые последние данные на графике. Если количество извлеченных значений меньше 3, это указывает на сбой доступа к необходимым данным. В этом случае мы выводим сообщение об ошибке с помощью функции Print и досрочно завершаем работу функции с помощью оператора return.

Далее мы повторяем аналогичный процесс для дескриптора медленно движущегося среднего (handleMASlow) и его буфера (bufferMASlow). Опять же, если функция CopyBuffer не может извлечь как минимум 3 точки данных, мы выводим сообщение об ошибке и выходим из функции, чтобы предотвратить дальнейшее выполнение. Наконец, мы используем ту же функцию для дескриптора фильтра RSI (handleRSIFilter) и его буфера (bufferRSIFilter). Как и ранее, мы убеждаемся, что запрошенные точки данных успешно извлечены; в противном случае выводится сообщение об ошибке и функция завершается. Если мы не возвращаемся к этой точке, у нас есть необходимые данные, и мы можем продолжить генерировать сигналы. Однако мы хотим генерировать сигналы на каждом баре, а не на каждом тике. Таким образом, нам понадобится функция для обнаружения генерации новых баров.

//+------------------------------------------------------------------+
//|     Function to detect if a new bar is formed                    |
//+------------------------------------------------------------------+
bool isNewBar(){
//--- Static variable to store the last bar count
   static int lastBarCount = 0;
//--- Get the current bar count
   int currentBarCount = iBars(_Symbol,_Period);
//--- Check if the bar count has increased
   if (currentBarCount > lastBarCount){
   //--- Update the last bar count
      lastBarCount = currentBarCount;
   //--- Return true if a new bar is detected
      return true;
   }
//--- Return false if no new bar is detected
   return false;
}

Здесь мы определяем функцию isNewBar, которая предназначена для обнаружения появления нового бара на графике. Эта функция будет иметь решающее значение для обеспечения того, чтобы наши операции выполнялись только один раз за бар, а не повторно при каждом тике. Мы начинаем с объявления статической переменной lastBarCount и инициализации ее значения 0. Статическая переменная сохраняет свое значение между вызовами функций, что дает возможность сравнивать текущее состояние с предыдущим. Затем мы получаем общее количество баров на графике с помощью функции iBars, передавая в нее _Symbol (текущий торговый инструмент) и _Period (текущий таймфрейм). Результат сохраняется в currentBarCount.

Далее мы сравниваем currentBarCount с lastBarCount. Если currentBarCount больше, это означает, что на графике сформировался новый бар. В этом случае мы обновляем lastBarCount в соответствии с currentBarCount и возвращаем true, сигнализируя о наличии нового бара. Если новый бар не обнаружен, функция возвращает false. Эту функцию можно использовать в обработчике событий тика.

//--- Check if a new bar has formed
if (isNewBar()){
//--- Print debug message for a new tick
   //Print("THIS IS A NEW TICK");
   
//--- Identify if a buy crossover has occurred
   bool isMACrossOverBuy = bufferMAFast[1] > bufferMASlow[1] && bufferMAFast[2] <= bufferMASlow[2];
//--- Identify if a sell crossover has occurred
   bool isMACrossOverSell = bufferMAFast[1] < bufferMASlow[1] && bufferMAFast[2] >= bufferMASlow[2];
   
//--- Check if the RSI confirms a buy signal
   bool isRSIConfirmBuy = bufferRSIFilter[1] >= inpRsiBUYThreshold;
//--- Check if the RSI confirms a sell signal
   bool isRSIConfirmSell = bufferRSIFilter[1] <= inpRsiSELLThreshold;

   //---
}

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

Если обнаружен новый бар, мы сначала готовимся к определению пересечения на покупку, оценивая взаимосвязь между быстрыми и медленными скользящими средними. В частности, мы проверяем, больше ли значение быстрой скользящей средней для предыдущего бара (bufferMAFast[1]) значения медленной скользящей средней для того же бара (bufferMASlow[1]), и в то же время значение быстрой скользящей средней за два бара назад (bufferMAFast[2]) было меньше или равно значению медленной скользящей средней для этого бара (bufferMASlow[2]). Если оба условия выполняются, мы устанавливаем булеву переменную isMACrossOverBuy в значение true, указывая на пересечение в сторону покупки.

Аналогичным образом мы определяем пересечение для продажи, проверяя, меньше ли значение быстрой скользящей средней за предыдущий бар (bufferMAFast[1]) значения медленной скользящей средней для того же бара (bufferMASlow[1]), в то время как значение быстрой скользящей средней за два бара назад (bufferMAFast[2]) было больше или равно значению медленной скользящей средней для этого бара (bufferMASlow[2]). Если эти условия выполняются, мы присваиваем булевой переменной isMACrossOverSell значение true, указывая на пересечение в сторону продажи.

Далее мы используем RSI в качестве фильтра подтверждения для обнаруженных пересечений. Для подтверждения покупки мы проверяем, что значение RSI для предыдущего бара (bufferRSIFilter[1]) больше или равно порогу покупки (inpRsiBUYThreshold). Если это так, мы присваиваем булевой переменной isRSIConfirmBuy значение true. Аналогично, для подтверждения продажи мы проверяем, что значение RSI для предыдущего бара (bufferRSIFilter[1]) меньше или равно порогу продажи (inpRsiSELLThreshold). Если это так, мы присваиваем булевой переменной isRSIConfirmSell значение true. Эти переменные можно использовать для принятия торговых решений.

//--- Handle buy signal conditions
if (isMACrossOverBuy){
   if (isRSIConfirmBuy){
   //--- Print buy signal message
      Print("BUY SIGNAL");

   //---
}

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

//+------------------------------------------------------------------+
//|     Function to check trading days filter                        |
//+------------------------------------------------------------------+
bool isCheckTradingDaysFilter(){
//--- Structure to store the current date and time
   MqlDateTime dateTIME;
//--- Convert the current time into structured format
   TimeToStruct(TimeCurrent(),dateTIME);
//--- Variable to store the day of the week
   string today = "DAY OF WEEK";
   
//--- Assign the day of the week based on the numeric value
   if (dateTIME.day_of_week == 0){today = "SUNDAY";}
   if (dateTIME.day_of_week == 1){today = "MONDAY";}
   if (dateTIME.day_of_week == 2){today = "TUESDAY";}
   if (dateTIME.day_of_week == 3){today = "WEDNESDAY";}
   if (dateTIME.day_of_week == 4){today = "THURSDAY";}
   if (dateTIME.day_of_week == 5){today = "FRIDAY";}
   if (dateTIME.day_of_week == 6){today = "SATURDAY";}
   
//--- Check if trading is allowed based on the input parameters
   if (
      (dateTIME.day_of_week == 0 && Sunday == true) ||
      (dateTIME.day_of_week == 1 && Monday == true) ||
      (dateTIME.day_of_week == 2 && Tuesday == true) ||
      (dateTIME.day_of_week == 3 && Wednesday == true) ||
      (dateTIME.day_of_week == 4 && Thursday == true) ||
      (dateTIME.day_of_week == 5 && Friday == true) ||
      (dateTIME.day_of_week == 6 && Saturday == true)
   ){
   //--- Print acceptance message for trading
      Print("Today is on ",today,". Trade ACCEPTED.");
   //--- Return true if trading is allowed
      return true;
   }
   else {
   //--- Print rejection message for trading
      Print("Today is on ",today,". Trade REJECTED.");
   //--- Return false if trading is not allowed
      return false;
   }
}

Здесь мы создаем функцию isCheckTradingDaysFilter для определения, разрешена ли торговля в текущий день на основе настроек, введенных пользователем. Это гарантирует, что сделки будут выполняться только в разрешенные торговые дни, что повышает точность и позволяет избежать непреднамеренных операций в ограниченные дни. Сначала мы определяем объект-структуру dateTIME класса MqlDateTime для хранения текущей даты и времени. Используя функцию TimeToStruct, мы преобразуем текущее время сервера (TimeCurrent) в структуру dateTIME, что дает возможность легко получить доступ к таким компонентам, как день недели.

Затем мы определяем переменную today и присваиваем ей строку-заполнитель "DAY OF WEEK". Позже она будет хранить название текущего дня в удобочитаемом формате. Используя серию условий if, мы сопоставляем числовое значение day_of_week (от 0 для воскресенья до 6 для субботы) с соответствующим названием дня, обновляя переменную today правильным днем.

После этого мы проверяем, разрешена ли торговля в текущий день (фильтр торговых дней), сравнивая dateTIME.day_of_week с соответствующими булевыми входными переменными (Sunday, Monday и другие). Если текущий день совпадает с одним из разрешенных дней торговли, с помощью Print выводится сообщение о том, что торговля разрешена, включая название дня, и функция возвращает значение true. И наоборот, если торговля не разрешена, выводится сообщение о том, что торговля отклонена, и функция возвращает значение false. Технически эта функция служит в качестве контролера, обеспечивая соответствие торговых операций предпочтениям пользователя в отношении конкретного дня. Мы можем использовать ее для создания фильтра торговых дней.

//--- Verify trading days filter before placing a trade
if (isCheckTradingDaysFilter()){
//--- Retrieve the current ask price
   double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
//--- Retrieve the current bid price
   double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   
//--- Set the open price to the ask price
   double openPrice = Ask;
//--- Calculate the stop-loss price
   double stoploss = Bid - inpSLPts*_Point;
//--- Calculate the take-profit price
   double takeprofit = Bid + (inpSLPts*inpR2R)*_Point;
//--- Define the trade comment
   string comment = "BUY TRADE";
   
//--- Execute a buy trade
   obj_Trade.Buy(inpLots,_Symbol,openPrice,stoploss,takeprofit,comment);
//--- Initialize the ticket variable
   ulong ticket = 0;
//--- Retrieve the order result ticket
   ticket = obj_Trade.ResultOrder();
//--- Print success message if the trade is opened
   if (ticket > 0){
      Print("SUCCESS. Opened the BUY position with ticket # ",ticket);
   }
//--- Print error message if the trade fails to open
   else {Print("ERROR! Failed to open the BUY position.");}
}

Здесь мы проверяем, разрешена ли торговля в текущий день, вызывая функцию isCheckTradingDaysFilter. Если функция возвращает значение true, мы приступаем к сбору рыночных данных и размещению сделки, убедившись, что торговля соответствует заданным пользователем дневным фильтрам. Сначала мы извлекаем текущие рыночные цены с помощью функции SymbolInfoDouble. Параметры SYMBOL_ASK и SYMBOL_BID используются для получения текущих цен спроса и предложения для активного торгового символа (_Symbol). Эти значения хранятся в переменных Ask и Bid соответственно и служат основой для дальнейших вычислений.

Далее мы рассчитываем уровни цен, необходимые для сделки. Цена Ask устанавливается в качестве openPrice, представляя цену входа для позиции на покупку. Мы рассчитываем цену стоп-лосса, вычитая inpSLPts (входные точки стоп-лосса), умноженные на _Point, из цены Bid. Аналогичным образом цена тейк-профита определяется путем сложения произведения inpSLPts, соотношения риска к прибыли (inpR2R) и _Point к цене Bid. Эти вычисления определяют границы риска и прибыли для сделки.

Затем мы определяем комментарий к сделке ("BUY TRADE"), чтобы пометить сделку для будущего использования. После этого мы выполняем сделку на покупку с помощью метода obj_Trade.Buy, передавая в качестве параметров размер лота (inpLots), торговый символ, цену входа, цену стоп-лосса, цену тейк-профита и комментарий. Эта функция отправляет торговый ордер на рынок. После исполнения сделки мы инициализируем переменную ticket значением 0 и присваиваем ей тикет ордера, возвращенный методом obj_Trade.ResultOrder. Если тикет больше 0, это означает, что сделка была успешно открыта, и выводится сообщение об успешном исполнении с номером тикета. Если тикет остается равным 0, это означает неудачу сделки, и выводится сообщение об ошибке. Для позиции на продажу мы следуем той же процедуре, но с обратными условиями. Фрагмент кода выглядит следующим образом:

//--- Handle sell signal conditions
else if (isMACrossOverSell){
   if (isRSIConfirmSell){
   //--- Print sell signal message
      Print("SELL SIGNAL");
   //--- Verify trading days filter before placing a trade
      if (isCheckTradingDaysFilter()){
      //--- Retrieve the current ask price
         double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      //--- Retrieve the current bid price
         double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         
      //--- Set the open price to the bid price
         double openPrice = Bid;
      //--- Calculate the stop-loss price
         double stoploss = Ask + inpSLPts*_Point;
      //--- Calculate the take-profit price
         double takeprofit = Ask - (inpSLPts*inpR2R)*_Point;
      //--- Define the trade comment
         string comment = "SELL TRADE";
         
      //--- Execute a sell trade
         obj_Trade.Sell(inpLots,_Symbol,openPrice,stoploss,takeprofit,comment);
      //--- Initialize the ticket variable
         ulong ticket = 0;
      //--- Retrieve the order result ticket
         ticket = obj_Trade.ResultOrder();
      //--- Print success message if the trade is opened
         if (ticket > 0){
            Print("SUCCESS. Opened the SELL position with ticket # ",ticket);
         }
      //--- Print error message if the trade fails to open
         else {Print("ERROR! Failed to open the SELL position.");}
      }
   }
}

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

SIGNAL AND TRADE CONFIRMATIONS

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

//+------------------------------------------------------------------+
//|    Create signal text function                                   |
//+------------------------------------------------------------------+
void createSignalText(datetime time,double price,int arrowcode,
            int direction,color clr,double angle,string txt
){
//--- Generate a unique name for the signal object
   string objName = " ";
   StringConcatenate(objName, "Signal @ ",time," at Price ",DoubleToString(price,_Digits));
//--- Create the arrow object at the specified time and price
   if (ObjectCreate(0,objName,OBJ_ARROW,0,time,price)){
   //--- Set arrow properties
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrowcode);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
   }
   
//--- Generate a unique name for the description text object
   string objNameDesc = objName+txt;
//--- Create the text object at the specified time and price
   if (ObjectCreate(0,objNameDesc,OBJ_TEXT,0,time,price)){
   //--- Set text properties
      ObjectSetInteger(0,objNameDesc,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDesc,OBJPROP_ANGLE,angle);
      if (direction > 0){
         ObjectSetInteger(0,objNameDesc,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDesc,OBJPROP_TEXT,"    "+txt);
      }
      if (direction < 0){
         ObjectSetInteger(0,objNameDesc,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDesc,OBJPROP_TEXT,"    "+txt);
      }
      
   }
   
}

Здесь мы определяем функцию createSignalText для визуального представления торговых сигналов на графике с помощью стрелок и описательного текста. Эта функция повышает наглядность графика, отмечая значимые события, такие как сигналы на покупку или продажу. Сначала мы генерируем уникальное имя для объекта стрелки с помощью функции StringConcatenate. Имя включает в себя слово "Signal", указанное время и цену сигнала. Такое уникальное именование гарантирует, что оно не будет совпадать с другими объектами на графике.

Затем мы создаем объект стрелки на графике в указанное время и по указанной цене с помощью функции ObjectCreate. Если создание прошло успешно, мы приступаем к настройке его свойств. Параметр arrowcode определяет тип отображаемой стрелки, а параметр clr указывает цвет стрелки. В зависимости от направления сигнала, точка привязки стрелки устанавливается в верхней части (ANCHOR_TOP) для восходящих сигналов или в нижней части (ANCHOR_BOTTOM) для нисходящих сигналов. Это обеспечивает правильное выравнивание положения стрелки с контекстом сигнала.

Затем мы создаем текстовый объект с описанием, который будет сопровождать стрелку. Уникальное имя для текстового объекта генерируется путем добавления описания "txt" к имени стрелки. Текстовый объект размещается в тех же координатах времени и цены, что и стрелка. Свойства текстового объекта настраиваются для улучшения его внешнего вида и выравнивания. Параметр clr определяет цвет текста, а параметр angle — его поворот. Для восходящих сигналов анкор выравнивается по левому краю (ANCHOR_LEFT), а перед txt добавляются пробелы для регулировки интервала. Аналогично, для нисходящих сигналов анкор выравнивается по нижнему краю (ANCHOR_BOTTOM) с той же регулировкой интервала.

Эту функцию можно использовать для создания стрелок с соответствующими пояснениями.

//--- FOR A BUY SIGNAL
//--- Retrieve the time of the signal
datetime textTime = iTime(_Symbol,_Period,1);
//--- Retrieve the price of the signal
double textPrice = iLow(_Symbol,_Period,1);
//--- Create a visual signal on the chart for a buy
createSignalText(textTime,textPrice,221,1,clrBlue,-90,"Buy Signal");

//...

//--- FOR A SELL SIGNAL
//--- Retrieve the time of the signal
datetime textTime = iTime(_Symbol,_Period,1);
//--- Retrieve the price of the signal
double textPrice = iHigh(_Symbol,_Period,1);
//--- Create a visual signal on the chart for a sell
createSignalText(textTime,textPrice,222,-1,clrRed,90,"Sell Signal");

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

Для сигнала на покупку:

  • Получение времени сигнала:

С помощью функции iTime мы получаем дату и время предпоследнего завершенного бара (индекс 1) на графике для текущего символа и таймфрейма (_Symbol и _Period). Это гарантирует, что сигнал соответствует подтвержденному бару.

  • Получение цены сигнала:

Мы используем функцию iLow для получения минимальной цены того же бара (1). Это служит позицией, где мы хотим разместить маркер.

  • Создание визуального сигнала:

Функция createSignalText вызывается с полученными значениями textTime и textPrice, а также дополнительными параметрами:

  1. "221" – код стрелки для определенного типа стрелки, представляющий сигнал на покупку.
  2. "1" – направление сигнала, указывающее на движение вверх.
  3. "clrBlue" – цвет стрелки и текста, обозначающий положительный сигнал.
  4. "-90" – угол наклона текста для правильного выравнивания.
  5. "Buy Signal" – описательный текст отображается рядом со стрелкой. Это визуально отмечает сигнал на покупку на графике.

Для сигнала на продажу:

  • Получение времени сигнала:

Как и в случае с сигналом на покупку, мы используем iTime для получения даты и времени бара с индексом 1.

  • Получение цены сигнала:

Функция iHigh используется для получения максимальной цены того же бара. Это представляет собой положение размещения маркера сигнала на продажу.

  • Создание визуального сигнала:

Функция createSignalText вызывается с помощью:

  1. "222" – код стрелки, обозначающий сигнал на продажу.
  2. "-1" – направление сигнала, указывающее на движение вниз.
  3. "clrRed" – цвет стрелки и текста, обозначающий отрицательный сигнал.
  4. "90" – угол наклона текста для выравнивания.
  5. "Sell Signal" – описательный текст, отображаемый рядом со стрелкой. Это добавляет четкий маркер сигнала на продажу на графике.

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

ARROW WITH ANNOTATION

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

//+------------------------------------------------------------------+
//|         Trailing stop function                                   |
//+------------------------------------------------------------------+
void applyTrailingStop(int slpoints, CTrade &trade_object,ulong magicno=0,int minProfitPts=0){
//--- Calculate the stop loss price for buy positions
   double buySl = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID) - slpoints*_Point,_Digits);
//--- Calculate the stop loss price for sell positions
   double sellSl = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK) + slpoints*_Point,_Digits);
   
//--- Loop through all positions in the account
   for (int i=PositionsTotal()-1; i>=0; i--){
   //--- Get the ticket of the position
      ulong ticket = PositionGetTicket(i);
   //--- Ensure the ticket is valid
      if (ticket > 0){
      //--- Select the position by ticket
         if (PositionSelectByTicket(ticket)){
         //--- Check if the position matches the symbol and magic number (if provided)
            if (PositionGetSymbol(POSITION_SYMBOL) == _Symbol &&
               (magicno == 0 || PositionGetInteger(POSITION_MAGIC) == magicno)
            ){
            //--- Retrieve the open price and current stop loss of the position
               double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
               double positionSl = PositionGetDouble(POSITION_SL);
               
            //--- Handle trailing stop for buy positions
               if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
               //--- Calculate the minimum profit price for the trailing stop
                  double minProfitPrice = NormalizeDouble((positionOpenPrice+minProfitPts*_Point),_Digits);
               //--- Apply trailing stop only if conditions are met
                  if (buySl > minProfitPrice &&
                     buySl > positionOpenPrice &&
                     (positionSl == 0 || buySl > positionSl)
                  ){
                  //--- Modify the position's stop loss
                     trade_object.PositionModify(ticket,buySl,PositionGetDouble(POSITION_TP));
                  }
               }
               //--- Handle trailing stop for sell positions
               else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
               //--- Calculate the minimum profit price for the trailing stop
                  double minProfitPrice = NormalizeDouble((positionOpenPrice-minProfitPts*_Point),_Digits);
               //--- Apply trailing stop only if conditions are met
                  if (sellSl < minProfitPrice &&
                     sellSl < positionOpenPrice &&
                     (positionSl == 0 || sellSl < positionSl)
                  ){
                  //--- Modify the position's stop loss
                     trade_object.PositionModify(ticket,sellSl,PositionGetDouble(POSITION_TP));
                  }
               }
               
            }
         }
      }
   }
   
}

Здесь мы реализуем механизм трейлинг-стопа в функции applyTrailingStop, которая динамически корректирует уровни стоп-лоссов для активных торговых позиций. Благодаря этому при благоприятном движении рынка прибыль фиксируется, что минимизирует риски. Функция работает по следующей логике. Сначала мы рассчитываем уровни стоп-лоссов для позиций на покупку и продажу. Используя функцию SymbolInfoDouble, мы извлекаем цену SYMBOL_BID, чтобы определить уровень buySl, вычитая указанные slpoints (расстояние стоп-лосса в пунктах) и нормализуя его до правильного количества десятичных знаков с помощью функции NormalizeDouble. Аналогичным образом мы рассчитываем уровень sellSl, добавляя slpoints к цене SYMBOL_ASK и нормализуя его.

Далее мы просматриваем все активные позиции на торговом счете, используя обратный цикл for ("for (inti=PositionsTotal()-1; i>=0; i--)"). Для каждой позиции мы извлекаем ее "тикет" с помощью функции PositionGetTicket. Если тикет действителен, мы выбираем соответствующую позицию с помощью функции PositionSelectByTicket. Внутри цикла мы проверяем, соответствует ли позиция текущему символу (symbol) и предоставленному магическому числу (magicno). Если magicno равно 0, мы включаем все позиции, независимо от их магического числа. Для подходящих позиций мы извлекаем их POSITION_PRICE_OPEN (цена открытия) и POSITION_SL (текущий уровень стоп-лосса).

Для позиций на покупку мы рассчитываем minProfitPrice, добавляя minProfitPts (минимальная прибыль в пунктах) к цене открытия и нормализуя ее. Мы применяем трейлинг-стоп только в том случае, если уровень buySl соответствует всем условиям:

  • buySl превышает minProfitPrice.
  • buySl выше цены открытия.
  • buySl больше текущего стоп-лосса или стоп-лосс не установлен (positionSl == 0).

Если эти условия выполняются, мы изменяем стоп-лосс позиции с помощью метода PositionModify из объекта CTrade. Для позиций на продажу мы рассчитываем minProfitPrice, вычитая minProfitPts из цены открытия и нормализуя его. Аналогично, мы применяем трейлинг-стоп, если уровень sellSl соответствует следующим условиям:

  • sellSl ниже minProfitPrice.
  • sellSl ниже цены открытия.
  • sellSl ниже текущего стоп-лосса или стоп-лосс не установлен.

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

//--- Apply trailing stop if allowed in the input parameters
}

Здесь мы реализуем механизм трейлинг-стопа в функции applyTrailingStop, которая динамически корректирует уровни стоп-лоссов для активных торговых позиций. Благодаря этому при благоприятном движении рынка прибыль фиксируется, что минимизирует риски. Функция работает по следующей логике. Сначала мы рассчитываем уровни стоп-лоссов для позиций на покупку и продажу. Используя функцию SymbolInfoDouble, мы извлекаем цену SYMBOL_BID, чтобы определить уровень buySl, вычитая указанные slpoints (расстояние стоп-лосса в пунктах) и нормализуя его до правильного количества десятичных знаков с помощью функции NormalizeDouble. Аналогичным образом мы рассчитываем уровень sellSl, добавляя slpoints к цене SYMBOL_ASK и нормализуя его.

Далее мы просматриваем все активные позиции на торговом счете, используя обратный цикл for ("for (inti=PositionsTotal()-1; i>=0; i--)"). Для каждой позиции мы извлекаем ее тикет с помощью функции PositionGetTicket. Если тикет действителен, мы выбираем соответствующую позицию с помощью функции PositionSelectByTicket. Внутри цикла мы проверяем, соответствует ли позиция текущему символу (symbol) и предоставленному магическому числу (magicno). Если magicno равно 0, мы включаем все позиции, независимо от их магического числа. Для подходящих позиций мы извлекаем их POSITION_PRICE_OPEN (цена открытия) и POSITION_SL (текущий уровень стоп-лосса).

Для позиций на покупку мы рассчитываем minProfitPrice, добавляя minProfitPts (минимальная прибыль в пунктах) к цене открытия и нормализуя ее. Мы применяем трейлинг-стоп только в том случае, если уровень buySl соответствует всем условиям:

  • buySl превышает minProfitPrice.
  • buySl выше цены открытия.
  • buySl больше текущего стоп-лосса или стоп-лосс не установлен (positionSl == 0).

Если эти условия выполняются, мы изменяем стоп-лосс позиции с помощью метода PositionModify из объекта CTrade. Для позиций на продажу мы рассчитываем minProfitPrice, вычитая minProfitPts из цены открытия и нормализуя его. Аналогично, мы применяем трейлинг-стоп, если уровень sellSl соответствует следующим условиям:

  • sellSl ниже minProfitPrice.
  • sellSl ниже цены открытия.
  • sellSl ниже текущего стоп-лосса или стоп-лосс не установлен.

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

//--- Apply trailing stop if allowed in the input parameters
if (inpisAllowTrailingStop){
   applyTrailingStop(inpTrailPts,obj_Trade,inpMagicNo,inpMinTrailPts);
}

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

TRAILING STOP

На изображении видно, что цель трейлинг-стопа достигнута успешно. Теперь нам нужно визуализировать данные на графике. Для этого нам понадобится дашборд с основанием и метками. Для основания нам понадобится прямоугольная метка. Вот реализация функции.

//+------------------------------------------------------------------+
//|     Create Rectangle label function                              |
//+------------------------------------------------------------------+
bool createRecLabel(string objNAME,int xD,int yD,int xS,int yS,
                  color clrBg,int widthBorder,color clrBorder = clrNONE,
                  ENUM_BORDER_TYPE borderType = BORDER_FLAT,ENUM_LINE_STYLE borderStyle = STYLE_SOLID
){
//--- Reset the last error code
   ResetLastError();
//--- Attempt to create the rectangle label object
   if (!ObjectCreate(0,objNAME,OBJ_RECTANGLE_LABEL,0,0,0)){
   //--- Log the error if creation fails
      Print(__FUNCTION__,": Failed to create the REC LABEL. Error Code = ",_LastError);
      return (false);
   }
   
//--- Set rectangle label properties
   ObjectSetInteger(0, objNAME,OBJPROP_XDISTANCE, xD);
   ObjectSetInteger(0, objNAME,OBJPROP_YDISTANCE, yD);
   ObjectSetInteger(0, objNAME,OBJPROP_XSIZE, xS);
   ObjectSetInteger(0, objNAME,OBJPROP_YSIZE, yS);
   ObjectSetInteger(0, objNAME,OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, objNAME,OBJPROP_BGCOLOR, clrBg);
   ObjectSetInteger(0, objNAME,OBJPROP_BORDER_TYPE, borderType);
   ObjectSetInteger(0, objNAME,OBJPROP_STYLE, borderStyle);
   ObjectSetInteger(0, objNAME,OBJPROP_WIDTH, widthBorder);
   ObjectSetInteger(0, objNAME,OBJPROP_COLOR, clrBorder);
   ObjectSetInteger(0, objNAME,OBJPROP_BACK, false);
   ObjectSetInteger(0, objNAME,OBJPROP_STATE, false);
   ObjectSetInteger(0, objNAME,OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objNAME,OBJPROP_SELECTED, false);
   
//--- Redraw the chart to reflect changes
   ChartRedraw(0);
   
   return (true);
}

В определённой нами булевой функции createRecLabel мы создаём настраиваемую прямоугольную метку на диаграмме, выполняя ряд шагов. Сначала мы сбрасываем все предыдущие коды ошибок с помощью функции ResetLastError. Затем мы пытаемся создать объект прямоугольной метки с помощью функции ObjectCreate. Если создание не удается, мы выводим сообщение об ошибке с причиной сбоя и возвращаем значение false. Если создание прошло успешно, мы приступаем к настройке различных свойств прямоугольной метки с помощью функции ObjectSetInteger.

Эти свойства позволяют нам определить положение, размер, цвет фона, стиль границы и другие визуальные аспекты прямоугольника. Мы назначаем параметры xD, yD, xS и yS для определения положения и размера прямоугольной метки, используя OBJPROP_XDISTANCE, OBJPROP_YDISTANCE, OBJPROP_XSIZE и OBJPROP_YSIZE. Кроме того, мы устанавливаем цвет фона, тип границы и стиль границы с помощью OBJPROP_BGCOLOR, OBJPROP_BORDER_TYPE и OBJPROP_STYLE соответственно.

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

//+------------------------------------------------------------------+
//|    Create label function                                         |
//+------------------------------------------------------------------+
bool createLabel(string objNAME,int xD,int yD,string txt,
                  color clrTxt = clrBlack,int fontSize = 12,
                  string font = "Arial Rounded MT Bold"
){
//--- Reset the last error code
   ResetLastError();
//--- Attempt to create the label object
   if (!ObjectCreate(0,objNAME,OBJ_LABEL,0,0,0)){
   //--- Log the error if creation fails
      Print(__FUNCTION__,": Failed to create the LABEL. Error Code = ",_LastError);
      return (false);
   }
   
//--- Set label properties
   ObjectSetInteger(0, objNAME,OBJPROP_XDISTANCE, xD);
   ObjectSetInteger(0, objNAME,OBJPROP_YDISTANCE, yD);
   ObjectSetInteger(0, objNAME,OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(0, objNAME,OBJPROP_TEXT, txt);
   ObjectSetInteger(0, objNAME,OBJPROP_COLOR, clrTxt);
   ObjectSetString(0, objNAME,OBJPROP_FONT, font);
   ObjectSetInteger(0, objNAME,OBJPROP_FONTSIZE, fontSize);
   ObjectSetInteger(0, objNAME,OBJPROP_BACK, false);
   ObjectSetInteger(0, objNAME,OBJPROP_STATE, false);
   ObjectSetInteger(0, objNAME,OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objNAME,OBJPROP_SELECTED, false);
   
//--- Redraw the chart to reflect changes
   ChartRedraw(0);
   
   return (true);
}

Вооружившись этими функциями, мы теперь можем создать функцию для обработки создания дашборда при необходимости.

//+------------------------------------------------------------------+
//|    Create dashboard function                                     |
//+------------------------------------------------------------------+
void createDashboard(){

   //---
}

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

//---

createDashboard();

//---

После вызова функции мы можем определить ее тело. Первым делом мы определяем тело дашборда, и нам нужно определить его имя как глобальную константу.

//+------------------------------------------------------------------+
//|    Global constants for dashboard object names                   |
//+------------------------------------------------------------------+
const string DASH_MAIN = "MAIN";

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

//+------------------------------------------------------------------+
//|    Create dashboard function                                     |
//+------------------------------------------------------------------+
void createDashboard(){
//--- Create the main dashboard rectangle
   createRecLabel(DASH_MAIN,10,50+30,200,120,clrBlack,2,clrBlue,BORDER_FLAT,STYLE_SOLID);
   
   //---

}

В функции createDashboard мы инициируем процесс создания визуального дашборда на графике. Для этого мы вызываем функцию createRecLabel, которая отвечает за рисование прямоугольника на графике, который будет служить основанием для дашборда. Функция получает определенные параметры для определения внешнего вида и положения этого прямоугольника. Сначала мы указываем имя прямоугольника как DASH_MAIN, что позволит нам позже идентифицировать этот объект. Затем мы определяем положение прямоугольника, устанавливая его верхний левый угол по координатам (10, 50+30) на графике с помощью параметров xD и yD. Ширина и высота прямоугольника устанавливаются на 200 и 120 пикселей соответственно с помощью параметров xS и yS, но впоследствии их можно изменить.

Далее мы определяем внешний вид прямоугольника. Цвет фона прямоугольника устанавливается на clrBlack, а для границы выбирается синий цвет (clrBlue). Ширина границы составляет 2 пикселя, стиль линии — сплошная (STYLE_SOLID), а тип границы — плоский (BORDER_FLAT). Эти настройки обеспечивают четкий и отчетливый вид прямоугольника. Этот прямоугольник служит основанием дашборда, и на последующих этапах к нему можно добавлять дополнительные элементы, такие как текст или интерактивные компоненты. Тем не менее, запустим текущий этап и посмотрим на результат.

DASHBOARD BASE

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

//+------------------------------------------------------------------+
//|    Global constants for dashboard object names                   |
//+------------------------------------------------------------------+
const string DASH_MAIN = "MAIN";
const string DASH_HEAD = "HEAD";
const string DASH_ICON1 = "ICON 1";
const string DASH_ICON2 = "ICON 2";
const string DASH_NAME = "NAME";
const string DASH_OS = "OS";
const string DASH_COMPANY = "COMPANY";
const string DASH_PERIOD = "PERIOD";
const string DASH_POSITIONS = "POSITIONS";
const string DASH_PROFIT = "PROFIT";

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

//+------------------------------------------------------------------+
//|    Create dashboard function                                     |
//+------------------------------------------------------------------+
void createDashboard(){
//--- Create the main dashboard rectangle
   createRecLabel(DASH_MAIN,10,50+30,200,120+30,clrBlack,2,clrBlue,BORDER_FLAT,STYLE_SOLID);
   
//--- Add icons and text labels to the dashboard
   createLabel(DASH_ICON1,13,53+30,CharToString(40),clrRed,17,"Wingdings");
   createLabel(DASH_ICON2,180,53+30,"@",clrWhite,17,"Webdings");
   createLabel(DASH_HEAD,65,53+30,"Dashboard",clrWhite,14,"Impact");
}

Здесь мы улучшаем дашборд, добавляя значки и заголовок с помощью функции createLabel. Эта функция вызывается несколько раз, чтобы разместить текстовые элементы в определенных позициях на диаграмме, что дает возможность создать визуально привлекательный и информативный интерфейс. Сначала мы создаем значок с меткой DASH_ICON1, который располагается по координатам (13, 53+30) относительно диаграммы. Значок представлен символьным кодом 40, преобразованным в строку с помощью функции CharToString(40). Этот значок отображается красным цветом (clrRed) с размером шрифта 17, а стиль шрифта установлен на Wingdings, чтобы отобразить символ в виде графического значка.

Далее мы добавляем еще один значок с меткой DASH_ICON2, расположенный по координатам (180, 53+30). Этот значок использует символ "@", отображаемый белым цветом (clrWhite) с размером шрифта 17. Стиль шрифта — Webdings, что обеспечивает декоративное и стилизованное отображение символа "@". Вот как это выглядит.

WEBDINGS FONT

Наконец, добавляется текстовый заголовок с меткой DASH_HEAD в позиции (65, 53+30). Заголовок отображает текст "Dashboard" белым цветом (clrWhite) с размером шрифта 14. Стиль шрифта установлен на Impact, что придает заголовку жирный и выразительный вид. Затем мы можем определить остальные метки.

createLabel(DASH_NAME,20,90+30,"EA Name: Crossover RSI Suite",clrWhite,10,"Calibri");
createLabel(DASH_COMPANY,20,90+30+15,"LTD: "+AccountInfoString(ACCOUNT_COMPANY),clrWhite,10,"Calibri");
createLabel(DASH_OS,20,90+30+15+15,"OS: "+TerminalInfoString(TERMINAL_OS_VERSION),clrWhite,10,"Calibri");
createLabel(DASH_PERIOD,20,90+30+15+15+15,"Period: "+EnumToString(Period()),clrWhite,10,"Calibri");

createLabel(DASH_POSITIONS,20,90+30+15+15+15+30,"Positions: "+IntegerToString(PositionsTotal()),clrWhite,10,"Calibri");
createLabel(DASH_PROFIT,20,90+30+15+15+15+30+15,"Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY),clrWhite,10,"Calibri");

Здесь мы заполняем дашборд важными информационными метками с помощью функции createLabel. Сначала мы создаем метку DASH_NAME, расположенную в точке (20, 90+30). Эта метка отображает текст "EA Name: Crossover RSI Suite" белым цветом (clrWhite) с размером шрифта 10 и стилем шрифта Calibri. Эта метка служит названием советника, давая пользователю четкую идентификацию.

Далее мы добавляем метку DASH_COMPANY в точке (20, 90+30+15). Она отображает текст "LTD: ", за которым следует информация о компании, владеющей счетом, которая извлекается с помощью функции AccountInfoString с параметром ACCOUNT_COMPANY. Метка оформлена белым цветом с размером шрифта 10 и использует шрифт Calibri. Затем метка DASH_OS размещается в точке (20, 90+30+15+15). Она показывает версию операционной системы с текстом "OS: , в сочетании с результатом функции TerminalInfoString с параметром TERMINAL_OS_VERSION. Эта метка помогает пользователю узнать операционную систему терминала, также оформлена в белом цвете с размером шрифта 10 и шрифтом Calibri.

Затем мы включаем метку DASH_PERIOD в точке (20, 90+30+15+15+15). Эта метка отображает текущий временной интервал графика с текстом "Period: ", к которому добавляется результат функции EnumToString с периодом. Белый текст, маленький размер шрифта и шрифт Calibri поддерживают согласованность с общим дизайном дашборда. Кроме того, мы добавляем метку DASH_POSITIONS в точке (20, 90+30+15+15+15+30). Эта метка отображает общее количество позиций, открытых в данный момент на счете, с текстом "Positions: ", за которым следует общее количество позиций. Эта информация имеет решающее значение для отслеживания активных сделок.

Наконец, метка DASH_PROFIT размещается в точке (20, 90+30+15+15+15+30+15). Она отображает текущую прибыль счета с текстом "Profit: ", за которым следует результат функции прибыли счета, представляющий прибыль с точностью до двух знаков после запятой, а также валюту счета, полученную с помощью функции AccountInfoString.

Наконец, нам нужно удалить дашборд после удаления программы. Таким образом, нам нужна функция для удаления дашборда.

//+------------------------------------------------------------------+
//|     Delete dashboard function                                    |
//+------------------------------------------------------------------+
void deleteDashboard(){
//--- Delete all objects related to the dashboard
   ObjectDelete(0,DASH_MAIN);
   ObjectDelete(0,DASH_ICON1);
   ObjectDelete(0,DASH_ICON2);
   ObjectDelete(0,DASH_HEAD);
   ObjectDelete(0,DASH_NAME);
   ObjectDelete(0,DASH_COMPANY);
   ObjectDelete(0,DASH_OS);
   ObjectDelete(0,DASH_PERIOD);
   ObjectDelete(0,DASH_POSITIONS);
   ObjectDelete(0,DASH_PROFIT);
   
//--- Redraw the chart to reflect changes
   ChartRedraw();
} 

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

if (PositionsTotal() > 0){
   ObjectSetString(0,DASH_POSITIONS,OBJPROP_TEXT,"Positions: "+IntegerToString(PositionsTotal()));
   ObjectSetString(0,DASH_PROFIT,OBJPROP_TEXT,"Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
}

Здесь мы проверяем, что если позиции выше 0, у нас есть позиция, и мы можем обновить ее свойства. Вот результат.

PROFITS NOT DEFAULTING

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

//+------------------------------------------------------------------+
//|    OnTradeTransaction function                                   |
//+------------------------------------------------------------------+
void  OnTradeTransaction(
   const MqlTradeTransaction&    trans,     // trade transaction structure 
   const MqlTradeRequest&        request,   // request structure 
   const MqlTradeResult&         result     // response structure 
){
   if (trans.type == TRADE_TRANSACTION_DEAL_ADD){
      Print("A deal was added. Make updates.");
      if (PositionsTotal() <= 0){
         ObjectSetString(0,DASH_POSITIONS,OBJPROP_TEXT,"Positions: "+IntegerToString(PositionsTotal()));
         ObjectSetString(0,DASH_PROFIT,OBJPROP_TEXT,"Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
      }
   }
}

Здесь мы настраиваем функцию OnTradeTransaction, которая запускается каждый раз, когда происходит сделка, связанная с торговлей. Эта функция обрабатывает торговые события и обновляет соответствующую информацию на дашборде в ответ на определенные действия. Мы начинаем с проверки типа торговой транзакции, предоставляемого параметром trans типа MqlTradeTransaction: не равен ли он TRADE_TRANSACTION_DEAL_ADD. Это условие определяет, была ли добавлена новая сделка на счет. При обнаружении такой транзакции мы выводим сообщение "A deal was added. Make updates." в журнал для отладки или в информационных целях.

Далее мы проверяем, меньше или равно ли общее количество открытых позиций, полученное с помощью функции PositionsTotal, а именно 0. Это гарантирует, что обновления дашборда выполняются только в том случае, если на счете не осталось активных позиций. Если условие выполняется, мы используем функцию ObjectSetString для обновления двух меток на дашборде. Вот результат.

PROFITS DEFAULTING

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


Тестирование на исторических данных

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

График тестирования на исторических данных:

GRAPH

Отчет о тестировании на исторических данных:

REPORT

Здесь также представлено видео, демонстрирующее тестирование всей стратегии на исторических данных за годовой период, а точнее за 2024 год.


Заключение

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

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

Используя описанные приемы, вы сможете создавать более адаптивные торговые системы и совершенствовать свои алгоритмические торговые стратегии. Удачного программирования и успешной торговли!

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
1149190
1149190 | 17 февр. 2025 в 21:33
Я не могу воспроизвести результаты обратного тестирования на AUDUSD на периоде 2024. Результаты, которые я получаю, намного хуже. Я проверил, и мои входные параметры, похоже, идентичны тем, что использовались в обучающем видео. Есть идеи, почему мои результаты не совпадают с теми, что вы приводите в статье?
Allan Munene Mutiiria
Allan Munene Mutiiria | 18 февр. 2025 в 13:01
1149190 результаты обратного тестирования на AUDUSD на периоде 2024. Результаты, которые я получаю, намного хуже. Я проверил, и мои входные параметры, похоже, идентичны тем, что использовались в обучающем видео. Есть идеи, почему мои результаты не совпадают с теми, что вы привели в статье?

Здравствуйте. В видео показано все, начиная от компиляции, периода тестирования и заканчивая используемыми входными параметрами.

Выборочные методы MCMC — Алгоритм Метрополиса-Гастингса Выборочные методы MCMC — Алгоритм Метрополиса-Гастингса
Алгоритм Метрополиса-Гастингса — фундаментальный метод Монте-Карло по схеме марковских цепей (MCMC), широко применяемый для аппроксимации апостериорных распределений в байесовском выводе. Статья описывает теоретические основы алгоритма, реализацию класса MHSampler на MQL5 и примеры применения с анализом полученных выборок.
Анализ влияния солнечных и лунных циклов на цены валют Анализ влияния солнечных и лунных циклов на цены валют
Что если лунные циклы и сезонные паттерны влияют на валютные рынки? Эта статья показывает, как перевести астрологические концепции на язык математики и машинного обучения. Я создал Python-систему с 88 признаками на основе астрономических циклов, обучил CatBoost на 15 годах данных EUR/USD и получил интригующие результаты. Код открыт, методы проверяемы, выводы неожиданны — древняя мудрость встречается с градиентным бустингом.
Введение в MQL5 (Часть 11): Руководство для начинающих по работе со встроенными индикаторами в MQL5 (II) Введение в MQL5 (Часть 11): Руководство для начинающих по работе со встроенными индикаторами в MQL5 (II)
В этой статье мы узнаем, как написать на MQL5 советника с использованием нескольких индикаторов, таких как RSI, MA и Stochastic Oscillator. Индикаторы будут искать скрытые бычьи и медвежьи расхождения. В статье представлены примеры и исходный код с подробными комментариями — изучайте их, чтобы узнать, как эффективно управлять рисками и автоматизировать торговлю.
Автоматизация торговых стратегий на MQL5 (Часть 16): Пробой полуночного диапазона посредством ценового действия Прорыв структуры (BoS) Автоматизация торговых стратегий на MQL5 (Часть 16): Пробой полуночного диапазона посредством ценового действия Прорыв структуры (BoS)
В настоящей статье мы автоматизируем пробой полуночного диапазона с помощью стратегии прорыва структуры на MQL5, подробно описывая код для обнаружения пробоя и исполнения сделок. Определяем точные параметры риска для входа, стоп-ордеров и прибыли. Тестирование на истории и оптимизация включены для практической торговли.