English Deutsch 日本語
preview
Автоматизация торговых стратегий на MQL5 (Часть 40): Торговля по уровням коррекции Фибоначчи с пользовательскими уровнями

Автоматизация торговых стратегий на MQL5 (Часть 40): Торговля по уровням коррекции Фибоначчи с пользовательскими уровнями

MetaTrader 5Трейдинг |
68 4
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В предыдущей статье (часть 39) мы разработали на языке MetaQuotes Language 5 (MQL5) систему статистического возврата к среднему. Она анализировала ценовые данные и рассчитывала такие статистические характеристики, как среднее, дисперсия, асимметрия, эксцесс и статистика Харке-Бера; формировала сигналы возврата к среднему на основе доверительных интервалов, адаптивных порогов и подтверждения по старшему таймфрейму; управляла сделками с расчётом объёма позиции на основе капитала счёта (equity), трейлинг-стопом, частичным закрытием и выходом по времени, а также выводила на график информационную панель для мониторинга в реальном времени. В части 40 мы разработаем торговую систему по уровням коррекции Фибоначчи с пользовательскими уровнями.

Система рассчитывает уровни коррекции по диапазону дневной свечи либо по массиву баров за заданную глубину истории, определяет направление по тому, закрылась ли свеча выше или ниже цены открытия и открывает позиции, когда цена пересекает заданные уровни, например 50% или 61,8%, с ограничением количества сделок на каждом уровне. Также предусмотрены необязательное закрытие позиций при новом расчёте уровней Фибоначчи, трейлинг-стоп по пунктам после достижения минимальной прибыли и буферы SL/TP, задаваемые в процентах от диапазона. Рассмотрим следующие темы:

  1. Принцип работы стратегии коррекции Фибоначчи
  2. Реализация на MQL5
  3. Бэктестирование
  4. Заключение

К концу статьи у нас будет работоспособная стратегия MQL5 для торговли по уровням коррекции Фибоначчи, которую можно будет адаптировать под свои задачи. Приступим!


Принцип работы стратегии коррекции Фибоначчи

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

В бычьем сценарии после восходящего движения от минимума к максимуму уровни коррекции, например 50% или 61,8%, рассматриваются как потенциальные зоны покупки во время отката с расчётом на последующее возобновление роста. В медвежьем сценарии после нисходящего движения от максимума к минимуму эти уровни выступают зонами продажи во время восходящей коррекции с расчётом на продолжение нисходящего тренда.

Мы уточняем точки входа, подтверждая пересечение этих уровней, применяем к уровням входа буферы, рассчитанные по диапазону свинга, ограничиваем количество сделок на одном уровне, чтобы не перегружать рыночную экспозицию на одном уровне, и используем трейлинг-стоп для защиты прибыли при благоприятном движении цены. При необходимости позиции можно закрывать при новом расчёте уровней Фибоначчи, чтобы работать с актуальной конфигурацией. Сочетание этих элементов позволяет искать точки разворота внутри тренда. Ниже показан пример возможной медвежьей конфигурации коррекции.

МЕДВЕЖИЙ СЦЕНАРИЙ КОРРЕКЦИИ

Мы рассчитаем уровни Фибоначчи по дневным свечам или по массиву баров за заданную глубину истории. Между этими подходами можно переключаться или заменить их любым вариантом, предусмотренным вашей стратегией. Направление будет определяться тем, закрылась ли свеча выше или ниже цены открытия, а входы — по пересечению пользовательских коэффициентов, например 50% и 61,8%, либо любых других значений. Это условно выбранные уровни, которые авторы считают наиболее распространёнными и значимыми. Система ограничит количество сделок, рассчитает SL/TP с необязательными буферами от диапазона, включит трейлинг-стоп по пунктам после достижения порога прибыли, при необходимости закроет позиции при новом расчёте уровней и отобразит данные с помощью цветовых графических объектов и подписей. В результате получится гибкая система для торговли на коррекциях.


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

Чтобы создать этот советник на MQL5, откройте MetaEditor, перейдите в окно «Навигатор», найдите папку Experts, нажмите вкладку «New» и следуйте указаниям мастера создания файла. После создания файла в редакторе кода необходимо объявить входные параметры и глобальные переменные, которые будут использоваться во всей программе.

//+------------------------------------------------------------------+
//|                                 Fibonacci Retracement Ratios.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>                                        // Для выполнения торговых операций

//+------------------------------------------------------------------+
//| Перечисления |
//+------------------------------------------------------------------+
enum CloseOnNewEnum {                                             // Определяем перечисление для закрытия позиций при новом расчёте Фибоначчи
   CloseOnNew_No  = 0,                                            // Нет
   CloseOnNew_Yes = 1                                             // Да
};
enum TrailingTypeEnum {                                           // Определяем перечисление типов трейлинг-стопа
   Trailing_None   = 0,                                           // Нет
   Trailing_Points = 2                                            // По пунктам
};

//+------------------------------------------------------------------+
//| Входные параметры |
//+------------------------------------------------------------------+
input bool   UseDailyApproach     = true;                         // Использовать дневную свечу (true) или массив баров (false)
input string fibLevelsStr         = "50,61.8";                    // Уровни Фибоначчи для входа через запятую (например, 50,61.8)
input int    maxTradesPerLevel    = 1;                            // Максимум сделок на уровне за один период Фибоначчи (0 — без ограничений)
input CloseOnNewEnum CloseOnNewFib = CloseOnNew_No;               // Закрывать позиции при новом расчёте Фибоначчи
input TrailingTypeEnum TrailingType = Trailing_None;              // Тип трейлинг-стопа
input double Trailing_Stop_Pips   = 30.0;                         // Расстояние трейлинг-стопа в пунктах (режим Trailing_Points)
input double Min_Profit_To_Trail_Pips = 50.0;                     // Минимальная прибыль в пунктах для запуска трейлинга
input int    LookbackSize         = 100;                          // Количество свечей для расчёта по массиву баров
input double LotSize              = 0.1;                          // Торговый объём в лотах
input int    MagicNumber          = 12345;                        // Магический номер сделок
input bool   IncludeCurrentBar    = false;                        // Включать текущий бар в расчёты по массиву
input double SlBufferPercent      = 0.0;                          // Буфер SL в процентах от диапазона (0 — без буфера)
input double TpBufferPercent      = 0.0;                          // Буфер TP в процентах от диапазона (0 — без буфера)

Сначала подключаем библиотеку Trade с помощью директивы #include <Trade\Trade.mqh>, чтобы получить функции исполнения ордеров и управления позициями. Затем определяем два перечисления с пользовательскими вариантами. Перечисление CloseOnNewEnum содержит CloseOnNew_No, при котором позиции не закрываются при новом расчёте уровней Фибоначчи, и CloseOnNew_Yes, при котором они закрываются. Перечисление TrailingTypeEnum содержит Trailing_None для отключения трейлинга и Trailing_Points для сопровождения позиции по пунктам.

Далее задаём входные параметры. UseDailyApproach по умолчанию равен true и включает расчёт по диапазону дневной свечи; значение false включает режим работы по массиву баров за заданную глубину истории. Строка fibLevelsStr со значением «50,61.8» содержит разделённые запятыми уровни коррекции для входа. Параметр maxTradesPerLevel со значением 1 ограничивает число сделок на уровне в пределах одного периода Фибоначчи, а 0 снимает ограничение. CloseOnNewFib определяет, закрывать ли позиции при пересчёте уровней, а TrailingType выбирает режим трейлинг-стопа.

Параметр Trailing_Stop_Pips со значением 30,0 задаёт расстояние трейлинг-стопа, а Min_Profit_To_Trail_Pips со значением 50,0 — минимальную прибыль, после которой начинается сопровождение. Несмотря на суффикс Pips в названиях параметров, в статье этот режим описывается как трейлинг-стоп по пунктам. LookbackSize со значением 100 задаёт число свечей для режима работы по массиву баров, LotSize со значением 0,1 — размер позиции, MagicNumber со значением 12345 — идентификатор сделок, а IncludeCurrentBar со значением false определяет, включать ли формирующийся бар в массив баров. SlBufferPercent и TpBufferPercent по умолчанию равны 0,0 и задают буферы для Stop Loss и Take Profit в процентах от диапазона; положительные значения увеличивают соответствующий отступ. После компиляции получаем следующий набор входных параметров.

НАСТРОЙКА ВХОДНЫХ ПАРАМЕТРОВ

После объявления входных параметров создадим глобальные переменные, которые понадобятся во всей программе.

//+------------------------------------------------------------------+
//| Глобальные переменные |
//+------------------------------------------------------------------+
CTrade obj_Trade;                                                 //--- Торговый объект
int    barsTotal;                                                 //--- Для дневного подхода
#define FIB_OBJ "Fibonacci Retracement"                           //--- Задаём имя объекта Фибоначчи
// Постоянные переменные для обоих подходов
static double storedEntryLvls[];                                  //--- Массив уровней входа
static int    storedTradesCount[];                                //--- Счётчик сделок на каждом уровне
static double storedSl = 0.0;                                     //--- Сохранённый Stop Loss
static double storedTp = 0.0;                                     //--- Сохранённый Take Profit
static string storedInfo = "";                                    //--- Сохранённая информационная строка
static bool   storedIsBullish = false;                            //--- Сохранённый флаг бычьего сценария
static double fibLevels[];                                        //--- Разобранные уровни Фибоначчи в исходном порядке
static string lastShownInfo = "";                                 //--- Для отслеживания изменений и предотвращения лишних обновлений
// Для расчёта по массиву баров
static bool   fibCalculated = false;                              //--- Флаг расчёта уровней Фибоначчи
static double currentHigh = 0.0;                                  //--- Текущий максимум
static double currentLow = 0.0;                                   //--- Текущий минимум
static string fibName = "Fib_Array";                              //--- Имя объекта Фибоначчи для режима работы по массиву баров

Сначала объявляем obj_Trade — экземпляр CTrade для работы с ордерами, и barsTotal для учёта дневных баров при дневном подходе. Макрос FIB_OBJ со значением «Fibonacci Retracement» задаёт имя основного объекта Фибоначчи. Для сохранения состояния между тиками в обоих режимах используются статические массивы storedEntryLvls[] с рассчитанными ценами входа и storedTradesCount[] со счётчиками сделок на каждом уровне; переменные storedSl и storedTp, инициализированные значением 0,0, хранят Stop Loss и Take Profit; storedInfo содержит текст для отображения; storedIsBullish хранит направление сценария; fibLevels[] содержит разобранные коэффициенты в исходном порядке; lastShownInfo позволяет отслеживать изменения текста и избегать лишней перерисовки.

Для режима работы по массиву баров переменная fibCalculated изначально равна false и показывает, рассчитаны ли уровни. currentHigh и currentLow со значением 0,0 хранят текущие экстремумы для проверки выхода цены за границы диапазона, а fibName со значением «Fib_Array» используется как имя графического объекта. Теперь можно перейти к основной логике. Начнём с обработчика OnInit.

//+------------------------------------------------------------------+
//| Функция инициализации советника |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(MagicNumber);                   //--- Задаём магический номер торгового объекта
   // Принудительно запускаем первый расчёт для дневного подхода
   barsTotal = 0;                                                 //--- Обеспечиваем обновление на первом тике
   // Разбираем fibLevelsStr в массив fibLevels с помощью StringSplit
   string tempLevels[];                                           //--- Временный массив уровней
   ushort commaSep = StringGetCharacter(",", 0);                  //--- Получаем разделитель-запятую
   int numLevels = StringSplit(fibLevelsStr, commaSep, tempLevels); //--- Разделяем строку на уровни
   ArrayResize(fibLevels, numLevels);                             //--- Изменяем размер массива fibLevels
   for (int i = 0; i < numLevels; i++) {                          //--- Перебираем уровни
      fibLevels[i] = StringToDouble(tempLevels[i]);               //--- Преобразуем значение в double
   }
   ArrayResize(storedEntryLvls, numLevels);                       //--- Изменяем размер storedEntryLvls
   ArrayResize(storedTradesCount, numLevels);                     //--- Изменяем размер storedTradesCount
   // Удаляем старые метки
   ObjectsDeleteAll(0, "InfoLabel_", -1, OBJ_LABEL);              //--- Удаляем все текстовые подписи
   lastShownInfo = "";                                            //--- Сбрасываем последнюю отображённую информацию
   // Удаляем старый объект Фибоначчи режима работы по массиву баров
   ObjectDelete(0, fibName);                                      //--- Удаляем объект Фибоначчи
   fibCalculated = false;                                         //--- Сбрасываем флаг расчёта
   return(INIT_SUCCEEDED);                                        //--- Возвращаем код успешной инициализации
}

В обработчике OnInit настраиваем торговый объект, вызывая obj_Trade.SetExpertMagicNumber с параметром MagicNumber для идентификации ордеров. Для дневного режима устанавливаем barsTotal в 0, чтобы первый тик обязательно запустил обновление. Затем разбираем строку fibLevelsStr в массив fibLevels: получаем код запятой с помощью StringGetCharacter, разделяем строку функцией StringSplit, сохраняем части во временном массиве tempLevels, изменяем размер fibLevels и в цикле преобразуем каждое значение в тип double функцией StringToDouble. После этого изменяем размеры storedEntryLvls и storedTradesCount в соответствии с количеством уровней.

При очистке удаляем все текстовые подписи функцией ObjectsDeleteAll с префиксом «InfoLabel_» и типом OBJ_LABEL, сбрасываем lastShownInfo, удаляем старый объект Фибоначчи с именем fibName функцией ObjectDelete и устанавливаем fibCalculated в false. В конце возвращаем INIT_SUCCEEDED, подтверждая успешную инициализацию. Теперь перейдём к обработчику тиков и сначала реализуем дневной вариант формирования сигналов.

//+------------------------------------------------------------------+
//| Функция обработки тиков советника |
//+------------------------------------------------------------------+
void OnTick() {
   if (UseDailyApproach) {                                        //--- Проверяем, включён ли дневной подход
      // Логика дневного подхода
      int bars = iBars(_Symbol, PERIOD_D1);                       //--- Получаем количество дневных баров
      if (barsTotal != bars && TimeCurrent() > StringToTime("00:05")) { //--- Проверяем формирование нового бара
         barsTotal = bars;                                        //--- Обновляем общее количество баров
         ObjectDelete(0, FIB_OBJ);                                //--- Удаляем объект Фибоначчи
         double openPrice = iOpen(_Symbol, PERIOD_D1, 1);         //--- Получаем цену открытия
         double closePrice = iClose(_Symbol, PERIOD_D1, 1);       //--- Получаем цену закрытия
         double high = iHigh(_Symbol, PERIOD_D1, 1);              //--- Получаем максимум
         double low = iLow(_Symbol, PERIOD_D1, 1);                //--- Получаем минимум
         datetime startingTime = iTime(_Symbol, PERIOD_D1, 1);    //--- Получаем начальное время
         datetime endingTime = iTime(_Symbol, PERIOD_D1, 0) - 1;  //--- Получаем конечное время
         double range = high - low;                               //--- Рассчитываем диапазон
         storedIsBullish = (closePrice > openPrice);              //--- Устанавливаем флаг бычьего сценария
         string levelsList = "";                                  //--- Инициализируем список уровней
         for (int i = 0; i < ArraySize(fibLevels); i++) {         //--- Перебираем уровни
            storedTradesCount[i] = 0;                             //--- Сбрасываем счётчик
            if (storedIsBullish) {                                //--- Проверяем бычий сценарий
               storedEntryLvls[i] = NormalizeDouble(high - range * fibLevels[i] / 100, _Digits); //--- Рассчитываем уровень входа
            } else {                                              //--- Обрабатываем медвежий сценарий
               storedEntryLvls[i] = NormalizeDouble(low + range * fibLevels[i] / 100, _Digits); //--- Рассчитываем уровень входа
            }
            levelsList += DoubleToString(fibLevels[i], 1) + ": " + DoubleToString(storedEntryLvls[i], _Digits) + "\n"; //--- Добавляем значение в список
         }
         if (storedIsBullish) {                                   //--- Проверяем бычий сценарий
            // Бычий сценарий: строим Фибоначчи от минимума к максимуму; 0% у максимума, 100% у минимума, цвет зелёный
            ObjectCreate(0, FIB_OBJ, OBJ_FIBO, 0, startingTime, low, endingTime, high); //--- Создаём объект Фибоначчи
            ObjectSetInteger(0, FIB_OBJ, OBJPROP_COLOR, clrGreen); //--- Задаём цвет
            for (int i = 0; i < ObjectGetInteger(0, FIB_OBJ, OBJPROP_LEVELS); i++) { //--- Перебираем уровни
               ObjectSetInteger(0, FIB_OBJ, OBJPROP_LEVELCOLOR, i, clrGreen); //--- Задаём цвет уровня
            }
            storedSl = NormalizeDouble(low - range * (SlBufferPercent / 100), _Digits); //--- Рассчитываем SL
            storedTp = NormalizeDouble(high + range * (TpBufferPercent / 100), _Digits); //--- Рассчитываем TP
            storedInfo = "Daily Approach - Bullish\n" +           //--- Формируем информационную строку
                         "Open: " + DoubleToString(openPrice, _Digits) + "\n" +
                         "Close: " + DoubleToString(closePrice, _Digits) + "\n" +
                         "Buy Entries:\n" + levelsList +
                         "SL: " + DoubleToString(storedSl, _Digits) + "\n" +
                         "TP: " + DoubleToString(storedTp, _Digits);
            Print("New daily bar: Bullish Fibonacci levels calculated. Entries: ", levelsList); //--- Выводим сообщение в журнал
         } else {                                                 //--- Обрабатываем медвежий сценарий
            // Медвежий сценарий: строим Фибоначчи от максимума к минимуму; 0% у минимума, 100% у максимума, цвет красный
            ObjectCreate(0, FIB_OBJ, OBJ_FIBO, 0, startingTime, high, endingTime, low); //--- Создаём объект Фибоначчи
            ObjectSetInteger(0, FIB_OBJ, OBJPROP_COLOR, clrRed);  //--- Задаём цвет
            for (int i = 0; i < ObjectGetInteger(0, FIB_OBJ, OBJPROP_LEVELS); i++) { //--- Перебираем уровни
               ObjectSetInteger(0, FIB_OBJ, OBJPROP_LEVELCOLOR, i, clrRed); //--- Задаём цвет уровня
            }
            storedSl = NormalizeDouble(high + range * (SlBufferPercent / 100), _Digits); //--- Рассчитываем SL
            storedTp = NormalizeDouble(low - range * (TpBufferPercent / 100), _Digits); //--- Рассчитываем TP
            storedInfo = "Daily Approach - Bearish\n" +           //--- Формируем информационную строку
                         "Open: " + DoubleToString(openPrice, _Digits) + "\n" +
                         "Close: " + DoubleToString(closePrice, _Digits) + "\n" +
                         "Sell Entries:\n" + levelsList +
                         "SL: " + DoubleToString(storedSl, _Digits) + "\n" +
                         "TP: " + DoubleToString(storedTp, _Digits);
            Print("New daily bar: Bearish Fibonacci levels calculated. Entries: ", levelsList); //--- Выводим сообщение в журнал
         }
      }
   }
   // Перерисовываем объекты графика
   ChartRedraw();                                                 //--- Перерисовываем график
}

В обработчике OnTick при включённом UseDailyApproach получаем число дневных баров функцией iBars для текущего символа _Symbol и периода PERIOD_D1. Когда формируется новый дневной бар и текущее время, полученное через TimeCurrent, превышает 00:05, заданное через StringToTime, обновляем barsTotal, удаляем существующий объект FIB_OBJ и получаем данные предыдущего дневного бара: цену открытия через iOpen, цену закрытия через iClose, максимум через iHigh, минимум через iLow, а также начальное и конечное время. Из конечного времени вычитается одна секунда, чтобы объект отображался корректно. Затем рассчитываем диапазон как разность максимума и минимума и устанавливаем storedIsBullish в true, если цена закрытия выше цены открытия. В цикле по массиву fibLevels сбрасываем счётчики storedTradesCount[i], рассчитываем нормализованные цены входа функцией NormalizeDouble — для бычьего сценария вычитаем процент диапазона из максимума, для медвежьего прибавляем его к минимуму — и формируем строку levelsList для отображения.

В бычьем сценарии создаём объект FIB_OBJ типа OBJ_FIBO от минимума к максимуму, задаём зелёный цвет через ObjectSetInteger и OBJPROP_COLOR и окрашиваем каждый уровень в зелёный цвет с помощью OBJPROP_LEVELCOLOR. storedSl рассчитывается ниже минимума с учётом процентного буфера диапазона, а storedTp — выше максимума. В storedInfo записываем тип подхода, цены открытия и закрытия, список уровней входа, SL и TP, после чего выводим результат в журнал. Для медвежьего сценария выполняются зеркальные действия: объект строится от максимума к минимуму и окрашивается в красный цвет, SL располагается выше максимума, а TP — ниже минимума. В конце вызываем ChartRedraw для обновления графика. Хорошая практика — компилировать и проверять код после каждого этапа. После компиляции получаем следующий результат.

ПОДТВЕРЖДЁННЫЕ УРОВНИ КОРРЕКЦИИ ФИБОНАЧЧИ

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

//+------------------------------------------------------------------+
//| Вывод информации с помощью меток без мерцания |
//+------------------------------------------------------------------+
void ShowLabels(string info) {
   if (info == lastShownInfo) return;                             //--- Пропускаем обновление, если данные не изменились
   lastShownInfo = info;                                          //--- Обновляем последнюю отображённую информацию
   // Разделяем информацию на строки
   string lines[];                                                //--- Массив строк
   ushort nlSep = StringGetCharacter("\n", 0);                    //--- Получаем разделитель новой строки
   int numLines = StringSplit(info, nlSep, lines);                //--- Разделяем текст на строки
   int y = 10;                                                    //--- Начальная координата Y
   for (int i = 0; i < numLines; i++) {                           //--- Перебираем строки
      string name = "InfoLabel_" + IntegerToString(i);            //--- Имя метки
      if (ObjectFind(0, name) < 0) {                              //--- Проверяем существование метки
         ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);               //--- Создаём метку
         ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Задаём угол привязки
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10);        //--- Задаём отступ по X
         ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 8);          //--- Задаём размер шрифта
      }
      ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);            //--- Задаём отступ по Y
      ObjectSetString(0, name, OBJPROP_TEXT, lines[i]);           //--- Задаём текст
      y += 15;                                                    //--- Увеличиваем координату Y
   }
   // Удаляем лишние метки, если количество строк уменьшилось
   for (int i = numLines; ; i++) {                                //--- Перебираем лишние метки
      string name = "InfoLabel_" + IntegerToString(i);            //--- Имя метки
      if (ObjectFind(0, name) < 0) break;                         //--- Завершаем цикл, если меток больше нет
      ObjectDelete(0, name);                                      //--- Удаляем метку
   }
}

// Выводим информацию на каждом тике, обновляя метки только при изменении данных
ShowLabels(storedInfo);                                           //--- Отображаем метки
// Логика входа: проверяем сохранённые уровни на каждом тике при отсутствии открытой позиции
if (PositionsTotal() == 0) {                                      //--- Проверяем отсутствие открытых позиций
   double close1 = iClose(_Symbol, _Period, 1);                   //--- Получаем цену закрытия бара 1
   double close2 = iClose(_Symbol, _Period, 2);                   //--- Получаем цену закрытия бара 2
   for (int i = 0; i < ArraySize(storedEntryLvls); i++) {         //--- Перебираем уровни
      // Для входа используем только уровни 0 < fib <= 100; уровни 0, 100 и расширения пропускаем
      if (fibLevels[i] <= 0 || fibLevels[i] > 100.0) continue;    //--- Пропускаем неподходящий уровень
      if ((maxTradesPerLevel == 0 || storedTradesCount[i] < maxTradesPerLevel) && //--- Проверяем счётчик сделок
          ((storedIsBullish && close1 > storedEntryLvls[i] && close2 <= storedEntryLvls[i]) || //--- Проверяем пересечение для покупки
           (!storedIsBullish && close1 < storedEntryLvls[i] && close2 >= storedEntryLvls[i]))) { //--- Проверяем пересечение для продажи
         string levelStr = DoubleToString(fibLevels[i], 1);       //--- Формируем строку уровня
         ulong ticket = 0;                                        //--- Инициализируем тикет
         if (storedIsBullish) {                                   //--- Проверяем сигнал на покупку
            Print("Buy signal triggered at ", close1, " crossing level ", levelStr, " (", storedEntryLvls[i], ")"); //--- Выводим сообщение в журнал
            obj_Trade.Buy(LotSize, _Symbol, 0, storedSl, storedTp, "Fibo Buy at " + levelStr); //--- Открываем позицию на покупку
            ticket = obj_Trade.ResultDeal();                      //--- Получаем номер сделки
         } else {                                                 //--- Обрабатываем сигнал на продажу
            Print("Sell signal triggered at ", close1, " crossing level ", levelStr, " (", storedEntryLvls[i], ")"); //--- Выводим сообщение в журнал
            obj_Trade.Sell(LotSize, _Symbol, 0, storedSl, storedTp, "Fibo Sell at " + levelStr); //--- Открываем позицию на продажу
            ticket = obj_Trade.ResultDeal();                      //--- Получаем номер сделки
         }
         storedTradesCount[i]++;                                  //--- Увеличиваем счётчик сделок
         break;                                                   //--- Завершаем цикл
      }
   }
}

Сначала определяем функцию ShowLabels, которая выводит сведения о стратегии на график с помощью меток без лишней перерисовки. Она принимает строку info и сразу завершает работу, если строка совпадает с lastShownInfo, предотвращая мерцание; в противном случае обновляет lastShownInfo. Затем строка разбивается на массив lines по символу новой строки с помощью StringSplit и StringGetCharacter. В цикле создаются или обновляются метки с именами «InfoLabel_» и индексом. Наличие объекта проверяется функцией ObjectFind; новая метка создаётся функцией ObjectCreate как OBJ_LABEL, привязывается к левому верхнему углу, получает отступ X, размер шрифта и последовательно увеличиваемый отступ Y. Текст задаётся через ObjectSetString и OBJPROP_TEXT. Если число строк уменьшилось, лишние метки удаляются функцией ObjectDelete.

Затем в обработчике тиков сразу после ранее созданной логики вызываем ShowLabels со строкой storedInfo, поэтому отображение обновляется только при изменении данных. Для входа, если PositionsTotal равно нулю, получаем цены закрытия предыдущих баров функцией iClose со сдвигами 1 и 2 и перебираем массив storedEntryLvls. Уровни вне диапазона от 0 до 100 пропускаются, поскольку для входов используются только уровни коррекции. Если лимит сделок не задан либо счётчик меньше maxTradesPerLevel и произошло пересечение уровня — в бычьем сценарии close1 выше уровня, а close2 не выше него; в медвежьем close1 ниже уровня, а close2 не ниже него — форматируем уровень через DoubleToString, выводим сигнал в журнал и вызываем obj_Trade.Buy или obj_Trade.Sell с объёмом LotSize, текущим символом, рыночной ценой 0, storedSl, storedTp и комментарием с уровнем. После успешного входа увеличиваем storedTradesCount[i] и выходим из цикла, чтобы не открывать несколько позиций за один тик. После компиляции видим следующий результат.

ПОДТВЕРЖДЁННЫЙ ВХОД

Теперь, когда входы в дневном режиме подтверждены, перейдём ко второму подходу. В проекте специально реализованы два варианта, чтобы показать, как можно переключаться между ними или адаптировать их под собственную торговую логику. Расчёт по массиву является динамическим и формирует больше сигналов, поэтому анализ выполняется один раз, а пока цена остаётся внутри предыдущего диапазона, система ждёт. Новый расчёт запускается только после выхода цены за границы предыдущего сценария, обычно за пределы уровней от 0 до 100. Поскольку уровни могут продолжаться и за отметкой 100, понадобится функция, определяющая такой пробой.

//+------------------------------------------------------------------+
//| Проверка выхода цены за текущие экстремумы Фибоначчи |
//+------------------------------------------------------------------+
bool IsBreach() {
   if (!fibCalculated) return false;                              //--- Возвращаем false, если уровни ещё не рассчитаны
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);            //--- Получаем цену Bid
   if (storedIsBullish) {                                         //--- Проверяем бычий сценарий
      // В бычьем сценарии 0% находится у максимума, а 100% — у минимума
      return (bid > currentHigh || bid < currentLow);             //--- Проверяем выход за границы
   } else {                                                       //--- Обрабатываем медвежий сценарий
      // В медвежьем сценарии 0% находится у минимума, а 100% — у максимума
      return (bid > currentLow || bid < currentHigh);             //--- Проверяем выход за границы
   }
}

Функция IsBreach проверяет, вышла ли текущая цена Bid, полученная через SymbolInfoDouble и SYMBOL_BID, за сохранённые экстремумы Фибоначчи. Если fibCalculated не равна true, функция сразу возвращает false. В бычьем сценарии уровень 0% расположен у максимума, а 100% — у минимума, поэтому пробой фиксируется, если Bid выше currentHigh или ниже currentLow. В медвежьем сценарии код проверяет условие bid > currentLow || bid < currentHigh, что запускает новый расчёт в режиме работы по массиву баров. Теперь можно добавить сам расчёт по массиву баров, который во многом повторяет дневной подход.

else {                                                          //--- Подход на основе массива баров
   // В режиме работы по массиву баров рассчитываем уровни только при первом запуске или после выхода цены за границы
   if (!fibCalculated || IsBreach()) {                          //--- Проверяем необходимость пересчёта
      if (fibCalculated) {                                      //--- Проверяем, были ли уровни рассчитаны
         // Сбрасываем предыдущий расчёт
         ObjectDelete(0, fibName);                              //--- Удаляем объект Фибоначчи
         fibCalculated = false;                                 //--- Сбрасываем флаг
      }
      int startShift = IncludeCurrentBar ? 0 : 1;               //--- Задаём начальный сдвиг
      int copyCount = IncludeCurrentBar ? LookbackSize : LookbackSize; //--- Задаём количество копируемых баров
      double high[], low[];                                     //--- Массивы максимумов и минимумов
      ArraySetAsSeries(high, true);                             //--- Переводим массивы в режим временного ряда
      ArraySetAsSeries(low, true);                              //--- Переводим массивы в режим временного ряда
      if (CopyHigh(_Symbol, _Period, startShift, copyCount, high) <= 0) return; //--- Копируем максимумы
      if (CopyLow(_Symbol, _Period, startShift, copyCount, low) <= 0) return; //--- Копируем минимумы
      int highestCandle = ArrayMaximum(high, 0, copyCount);     //--- Получаем максимум
      int lowestCandle = ArrayMinimum(low, 0, copyCount);       //--- Получаем минимум
      MqlRates pArray[];                                        //--- Массив котировок
      ArraySetAsSeries(pArray, true);                           //--- Переводим массивы в режим временного ряда
      int pData = CopyRates(_Symbol, _Period, startShift, copyCount, pArray); //--- Копируем котировки
      if (pData <= 0) return;                                   //--- Проверяем достаточность данных
      double highVal = pArray[highestCandle].high;              //--- Получаем максимум
      double lowVal = pArray[lowestCandle].low;                 //--- Получаем минимум
      double range = highVal - lowVal;                          //--- Рассчитываем диапазон
      int oldestShift = IncludeCurrentBar ? (LookbackSize - 1) : LookbackSize; //--- Сдвиг самого старого бара
      double openCandle = iOpen(_Symbol, _Period, oldestShift); //--- Получаем цену открытия
      double closeCandle = iClose(_Symbol, _Period, IncludeCurrentBar ? 0 : 1); //--- Получаем цену закрытия
      storedIsBullish = (closeCandle > openCandle);             //--- Определяем бычий сценарий
      string levelsList = "";                                   //--- Инициализируем список
      for (int i = 0; i < ArraySize(fibLevels); i++) {          //--- Перебираем уровни
         storedTradesCount[i] = 0;                              //--- Сбрасываем счётчик
         if (storedIsBullish) {                                 //--- Проверяем бычий сценарий
            storedEntryLvls[i] = NormalizeDouble(highVal - range * fibLevels[i] / 100, _Digits); //--- Рассчитываем уровень входа
         } else {                                               //--- Обрабатываем медвежий сценарий
            storedEntryLvls[i] = NormalizeDouble(lowVal + range * fibLevels[i] / 100, _Digits); //--- Рассчитываем уровень входа
         }
         levelsList += DoubleToString(fibLevels[i], 1) + ": " + DoubleToString(storedEntryLvls[i], _Digits) + "\n"; //--- Добавляем значение в список
      }
      if (storedIsBullish) {                                    //--- Проверяем бычий сценарий
         // Бычий сценарий: строим от минимума к максимуму
         datetime time1 = pArray[lowestCandle].time;            //--- Первая временная координата
         double price1 = lowVal;                                //--- Первая ценовая координата
         datetime time2 = pArray[highestCandle].time;           //--- Вторая временная координата
         double price2 = highVal;                               //--- Вторая ценовая координата
         ObjectCreate(0, fibName, OBJ_FIBO, 0, time1, price1, time2, price2); //--- Создаём объект Фибоначчи
         ObjectSetInteger(0, fibName, OBJPROP_COLOR, clrGreen); //--- Задаём цвет
         for (int i = 0; i < ObjectGetInteger(0, fibName, OBJPROP_LEVELS); i++) { //--- Перебираем уровни
            ObjectSetInteger(0, fibName, OBJPROP_LEVELCOLOR, i, clrGreen); //--- Задаём цвет уровня
         }
         storedSl = NormalizeDouble(lowVal - range * (SlBufferPercent / 100), _Digits); //--- Рассчитываем SL
         storedTp = NormalizeDouble(highVal + range * (TpBufferPercent / 100), _Digits); //--- Рассчитываем TP
         storedInfo = "Array Approach - Bullish\n" +            //--- Формируем информационную строку
                      "Array Open: " + DoubleToString(openCandle, _Digits) + "\n" +
                      "Array Close: " + DoubleToString(closeCandle, _Digits) + "\n" +
                      "Buy Entries:\n" + levelsList +
                      "SL: " + DoubleToString(storedSl, _Digits) + "\n" +
                      "TP: " + DoubleToString(storedTp, _Digits);
      } else {                                                  //--- Обрабатываем медвежий сценарий
         // Медвежий сценарий: строим от максимума к минимуму
         datetime time1 = pArray[highestCandle].time;           //--- Первая временная координата
         double price1 = highVal;                               //--- Первая ценовая координата
         datetime time2 = pArray[lowestCandle].time;            //--- Вторая временная координата
         double price2 = lowVal;                                //--- Вторая ценовая координата
         ObjectCreate(0, fibName, OBJ_FIBO, 0, time1, price1, time2, price2); //--- Создаём объект Фибоначчи
         ObjectSetInteger(0, fibName, OBJPROP_COLOR, clrRed);   //--- Задаём цвет
         for (int i = 0; i < ObjectGetInteger(0, fibName, OBJPROP_LEVELS); i++) { //--- Перебираем уровни
            ObjectSetInteger(0, fibName, OBJPROP_LEVELCOLOR, i, clrRed); //--- Задаём цвет уровня
         }
         storedSl = NormalizeDouble(highVal + range * (SlBufferPercent / 100), _Digits); //--- Рассчитываем SL
         storedTp = NormalizeDouble(lowVal - range * (TpBufferPercent / 100), _Digits); //--- Рассчитываем TP
         storedInfo = "Array Approach - Bearish\n" +            //--- Формируем информационную строку
                      "Array Open: " + DoubleToString(openCandle, _Digits) + "\n" +
                      "Array Close: " + DoubleToString(closeCandle, _Digits) + "\n" +
                      "Sell Entries:\n" + levelsList +
                      "SL: " + DoubleToString(storedSl, _Digits) + "\n" +
                      "TP: " + DoubleToString(storedTp, _Digits);
      }
      currentHigh = storedIsBullish ? highVal : lowVal;         //--- Сохраняем текущий максимум
      currentLow = storedIsBullish ? lowVal : highVal;          //--- Сохраняем текущий минимум
      fibCalculated = true;                                     //--- Устанавливаем флаг завершённого расчёта
   }
   // Отображаем информацию метками и обновляем её только при изменении
   ShowLabels(storedInfo);                                        //--- Отображаем метки
   // Логика входа: проверяем сохранённые уровни на каждом тике при отсутствии открытой позиции
   if (PositionsTotal() == 0) {                                   //--- Проверяем отсутствие открытых позиций
      double close1 = iClose(_Symbol, _Period, 1);                //--- Получаем цену закрытия бара 1
      double close2 = iClose(_Symbol, _Period, 2);                //--- Получаем цену закрытия бара 2
      for (int i = 0; i < ArraySize(storedEntryLvls); i++) {      //--- Перебираем уровни
         if (fibLevels[i] <= 0 || fibLevels[i] > 100.0) continue; //--- Пропускаем неподходящий уровень
         if ((maxTradesPerLevel == 0 || storedTradesCount[i] < maxTradesPerLevel) && //--- Проверяем счётчик сделок
             ((storedIsBullish && close1 > storedEntryLvls[i] && close2 <= storedEntryLvls[i]) || //--- Проверяем пересечение для покупки
              (!storedIsBullish && close1 < storedEntryLvls[i] && close2 >= storedEntryLvls[i]))) { //--- Проверяем пересечение для продажи
            string levelStr = DoubleToString(fibLevels[i], 1);     //--- Формируем строку уровня
            ulong ticket = 0;                                      //--- Инициализируем тикет
            if (storedIsBullish) {                                 //--- Проверяем сигнал на покупку
               Print("Buy signal triggered (Array) at ", close1, " crossing level ", levelStr, " (", storedEntryLvls[i], ")"); //--- Выводим сообщение в журнал
               obj_Trade.Buy(LotSize, _Symbol, 0, storedSl, storedTp, "Fibo Buy Array at " + levelStr); //--- Открываем позицию на покупку
               ticket = obj_Trade.ResultDeal();                        //--- Получаем номер сделки
            } else {                                               //--- Обрабатываем сигнал на продажу
               Print("Sell signal triggered (Array) at ", close1, " crossing level ", levelStr, " (", storedEntryLvls[i], ")"); //--- Выводим сообщение в журнал
               obj_Trade.Sell(LotSize, _Symbol, 0, storedSl, storedTp, "Fibo Sell Array at " + levelStr); //--- Открываем позицию на продажу
               ticket = obj_Trade.ResultDeal();                        //--- Получаем номер сделки
            }
            storedTradesCount[i]++;                                //--- Увеличиваем счётчик сделок
            break;                                                 //--- Завершаем цикл
         }
      }
   }
}

При отключённом дневном режиме используется подход на основе массива баров. Уровни пересчитываются только в том случае, если они ещё не рассчитаны либо функция IsBreach обнаружила выход цены за границы. Если предыдущая разметка уровней уже существует, удаляем её функцией ObjectDelete по имени fibName и сбрасываем fibCalculated. Начальный сдвиг и количество копируемых баров зависят от IncludeCurrentBar: при true используется сдвиг 0 и полный период LookbackSize, при false — сдвиг 1 и только завершённые бары. Массивы максимумов и минимумов переводятся в режим временного ряда функцией ArraySetAsSeries и заполняются через CopyHigh и CopyLow. При ошибке копирования функция завершается. Затем функции ArrayMaximum и ArrayMinimum находят индексы свечей с наибольшим максимумом и наименьшим минимумом.

Для получения точных цен и времени копируем котировки в массив pArray функцией CopyRates и завершаем обработку при недостаточном объёме данных. Затем извлекаем highVal из максимума соответствующей свечи и lowVal из её минимума и рассчитываем диапазон. Направление определяется по цене открытия самого старого бара, полученной через iOpen, и цене закрытия последнего бара через iClose; storedIsBullish устанавливается, если закрытие выше открытия. Далее в цикле по fibLevels сбрасываются счётчики, рассчитываются нормализованные уровни входа — для бычьего сценария процент диапазона вычитается из highVal, для медвежьего прибавляется к lowVal — и формируется строка levelsList. Построение объектов и торговая логика выполняются аналогично дневному режиму. На этом этапе компиляция даёт такой результат.

ПОДТВЕРЖДЕНИЕ ЛОГИКИ РАСЧЁТА ПО МАССИВУ

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

//+------------------------------------------------------------------+
//| Закрытие всех позиций с заданным магическим номером и символом |
//+------------------------------------------------------------------+
void CloseAllPositions() {
   for (int i = PositionsTotal() - 1; i >= 0; i--) {              //--- Перебираем позиции в обратном порядке
      if (PositionGetTicket(i) > 0 && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol) { //--- Проверяем позицию
         obj_Trade.PositionClose(PositionGetTicket(i));                //--- Закрываем позицию
      }
   }
}

//+------------------------------------------------------------------+
//| Применение трейлинг-стопа в пунктах |
//+------------------------------------------------------------------+
void ApplyPointsTrailing() {
   double point = _Point;                                         //--- Получаем размер пункта
   for (int i = PositionsTotal() - 1; i >= 0; i--) {              //--- Перебираем позиции в обратном порядке
      if (PositionGetTicket(i) > 0) {                             //--- Проверяем корректность тикета
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Проверяем символ и магический номер
            double sl = PositionGetDouble(POSITION_SL);              //--- Получаем текущий SL
            double tp = PositionGetDouble(POSITION_TP);              //--- Получаем текущий TP
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Получаем цену открытия
            ulong ticket = PositionGetInteger(POSITION_TICKET);      //--- Получаем тикет позиции
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Проверяем сигнал на покупку
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Pips * point, _Digits); //--- Рассчитываем новый SL
               if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Pips * point) { //--- Проверяем условия изменения
                  obj_Trade.PositionModify(ticket, newSL, tp);           //--- Изменяем параметры позиции
               }
            } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Проверяем позицию на продажу
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Pips * point, _Digits); //--- Рассчитываем новый SL
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Pips * point) { //--- Проверяем условия изменения
                  obj_Trade.PositionModify(ticket, newSL, tp);           //--- Изменяем параметры позиции
               }
            }
         }
      }
   }
}

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

//+------------------------------------------------------------------+
//| Функция обработки тиков советника |
//+------------------------------------------------------------------+
void OnTick() {
   // Трейлинг-стоп по пунктам может выполняться на каждом тике
   if (TrailingType == Trailing_Points && PositionsTotal() > 0) { //--- Проверяем настройки трейлинга
      ApplyPointsTrailing();                                      //--- Применяем трейлинг-стоп
   }
   //--- Вызываем функцию в необходимом месте
         if (CloseOnNewFib == CloseOnNew_Yes) {                   //--- Проверяем настройку закрытия при новом расчёте
            CloseAllPositions();                                  //--- Закрываем позицию
         }
}

Вызываем функции управления в обработчике OnTick там, где это необходимо. После этого остаётся удалить созданные графические объекты при завершении работы советника.

//+------------------------------------------------------------------+
//| Функция деинициализации советника |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0, "InfoLabel_", -1, OBJ_LABEL);              //--- Удаляем все текстовые подписи
   ObjectDelete(0, FIB_OBJ);                                      //--- Удаляем дневной объект Фибоначчи
   ObjectDelete(0, fibName);                                      //--- Удаляем объект Фибоначчи режима работы по массиву баров
}

В функции OnDeinit удаляем все текстовые подписи функцией ObjectsDeleteAll с префиксом «InfoLabel_» и типом OBJ_LABEL во всех подокнах, затем удаляем дневной объект Фибоначчи FIB_OBJ и объект режима расчёта по массиву баров fibName функцией ObjectDelete. В итоге после компиляции получаем следующий результат.

РЕЗУЛЬТАТ КОМПИЛЯЦИИ

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


Бэктестирование

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

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

ГРАФИК

Отчёт бэктестирования:

ОТЧЁТ


Заключение

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

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

Эта стратегия коррекции Фибоначчи даёт основу для эффективной работы с откатами и готова к дальнейшей оптимизации под вашу торговую практику. Успешной торговли!

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Israr Hussain Shah
Israr Hussain Shah | 17 нояб. 2025 в 15:47
Вау, это просто супер! Я использую это в своей торговой стратегии — идеально подходит. Спасибо за это!
Allan Munene Mutiiria
Allan Munene Mutiiria | 18 нояб. 2025 в 06:04
Israr Hussain Shah торговой стратегии — идеально. Спасибо за это!
Спасибо за добрый отзыв. Не за что.
Lesley Malabi Barasa
Lesley Malabi Barasa | 19 нояб. 2025 в 07:12
Это просто потрясающе — применять то, чему я научился в университете, к своим торговым стратегиям. Может, мы также рассмотрим геометрическое броуновское движение и дифференциальное исчисление?
Allan Munene Mutiiria
Allan Munene Mutiiria | 19 нояб. 2025 в 07:29
Lesley Malabi Barasa торговых стратегиях. Может, мы могли бы провести занятие и по геометрическому броуновскому движению, а также по математическому анализу?
Спасибо за добрые отзывы. Конечно.
Моделирование рынка: Position View (XI) Моделирование рынка: Position View (XI)
В этой статье я покажу вам, уважаемый читатель, как можновыбирать объекты, которые мы создаём на графике модифицировать индикатор позиции, чтобы он мог выполнять гораздо больше функций, чем это было предусмотрено изначально. Мы рассмотрим, как реализовать возможность перемещения цен и создания ценовых линий непосредственно на графике. Многим это покажется сложным для решения. Однако вы увидите, что мы сделаем это с минимальными усилиями. Вам нужно всего лишь немного подумать.
Создание торговых систем с искусственным интеллектом на MQL5 (Часть 4): Преодоление ограничений многострочного ввода, сохранение чатов между сеансами и генерация торговых сигналов Создание торговых систем с искусственным интеллектом на MQL5 (Часть 4): Преодоление ограничений многострочного ввода, сохранение чатов между сеансами и генерация торговых сигналов
В этой статье мы улучшим программу с интеграцией ChatGPT на MQL5: устраним ограничения многострочного ввода благодаря улучшенной отрисовке текста, добавим боковую панель для навигации по истории сохраненных чатов с шифрованием AES256 и ZIP-сжатием, а также сформируем начальные торговые сигналы на основе данных графика.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Автоматизация торговых стратегий на MQL5 (Часть 39): Стратегия статистического возврата к среднему с доверительными интервалами и информационной панелью Автоматизация торговых стратегий на MQL5 (Часть 39): Стратегия статистического возврата к среднему с доверительными интервалами и информационной панелью
В этой статье мы разработаем советник на MQL5 для торговли по стратегии статистического возврата к среднему. Советник рассчитывает среднее, дисперсию, асимметрию, эксцесс и статистику Харке-Бера за заданный период, выявляет ненормальные распределения и формирует сигналы на покупку и продажу по доверительным интервалам с адаптивными порогами.