English Deutsch 日本語
preview
Автоматизация торговых стратегий в MQL5 (Часть 25): Советник для торговли по линиям тренда с аппроксимацией методом наименьших квадратов и динамической генерацией сигналов

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

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

Введение

В своей предыдущей статье (Часть 24) мы разработали систему торговли на пробое лондонской сессии на MetaQuotes Language 5 (MQL5), которая использовала диапазоны до открытия лондонской сессии, для размещения отложенных ордеров с управлением рисками и трейлинг-стопами, что позволяло эффективно торговать внутри сессии. В части 25 мы создадим торговую программу на основе линий тренда, которая использует аппроксимацию методом наименьших квадратов (least squares fit) для определения линий тренда поддержки и сопротивления, формируя автоматические сигналы на покупку и продажу, когда цены касаются этих линий, дополненные визуальными индикаторами, такими как стрелки, а также настраиваемыми торговыми параметрами. В статье рассмотрим следующие темы:

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

В итоге у вас будет мощная стратегия в MQL5 для торговли на основе трендов, готовая к настройке. Перейдём к реализации!


Разработка торговой модели на основе линий тренда

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

DOWNWARD TRENDLINE

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

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

LEAST SQUARES OF FIT METHOD

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

TRENDLINE FRAMEWORK


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

Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку «Индикаторы» (Indicators), перейдите на вкладку "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования мы начнем с объявления некоторых входных параметров и структур, что сделает программу более динамичной.
//+------------------------------------------------------------------+
//|                                       a. Trendline Trader EA.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 description "Trendline Trader using mean Least Squares Fit"
#property version     "1.00"
#property strict

#include <Trade\Trade.mqh>                         //--- Include Trade library for trading operations
CTrade obj_Trade;                                  //--- Instantiate trade object

//+------------------------------------------------------------------+
//| Swing point structure                                            |
//+------------------------------------------------------------------+
struct Swing {                                     //--- Define swing point structure
   datetime time;                                  //--- Store swing time
   double   price;                                 //--- Store swing price
};

//+------------------------------------------------------------------+
//| Starting point structure                                         |
//+------------------------------------------------------------------+
struct StartingPoint {                             //--- Define starting point structure
   datetime time;                                  //--- Store starting point time
   double   price;                                 //--- Store starting point price
   bool     is_support;                            //--- Indicate support/resistance flag
};

//+------------------------------------------------------------------+
//| Trendline storage structure                                      |
//+------------------------------------------------------------------+
struct TrendlineInfo {                             //--- Define trendline info structure
   string   name;                                  //--- Store trendline name
   datetime start_time;                            //--- Store start time
   datetime end_time;                              //--- Store end time
   double   start_price;                           //--- Store start price
   double   end_price;                             //--- Store end price
   double   slope;                                 //--- Store slope
   bool     is_support;                            //--- Indicate support/resistance flag
   int      touch_count;                           //--- Store number of touches
   datetime creation_time;                         //--- Store creation time
   int      touch_indices[];                       //--- Store touch indices array
   bool     is_signaled;                           //--- Indicate signal flag
};

//+------------------------------------------------------------------+
//| Forward declarations                                             |
//+------------------------------------------------------------------+
void DetectSwings();                               //--- Declare swing detection function
void SortSwings(Swing &swings[], int count);       //--- Declare swing sorting function
double CalculateAngle(datetime time1, double price1, datetime time2, double price2); //--- Declare angle calculation function
bool ValidateTrendline(bool isSupport, datetime start_time, datetime ref_time, double ref_price, double slope, double tolerance_pen); //--- Declare trendline validation function
void FindAndDrawTrendlines(bool isSupport);        //--- Declare trendline finding/drawing function
void UpdateTrendlines();                           //--- Declare trendline update function
void RemoveTrendlineFromStorage(int index);        //--- Declare trendline removal function
bool IsStartingPointUsed(datetime time, double price, bool is_support); //--- Declare starting point usage check function
void LeastSquaresFit(const datetime &times[], const double &prices[], int n, double &slope, double &intercept); //--- Declare least squares fit function

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input int    LookbackBars = 200;                   // Set bars for swing detection lookback
input double TouchTolerance = 10.0;                // Set tolerance for touch points (points)
input int    MinTouches = 3;                       // Set minimum touch points for valid trendline
input double PenetrationTolerance = 5.0;           // Set allowance for bar penetration (points)
input int    ExtensionBars = 100;                  // Set bars to extend trendline right
input int    MinBarSpacing = 10;                   // Set minimum bar spacing between touches
input double inpLot = 0.01;                        // Set lot size
input double inpSLPoints = 100.0;                  // Set stop loss (points)
input double inpRRRatio = 1.1;                     // Set risk:reward ratio
input double MinAngle = 1.0;                       // Set minimum inclination angle (degrees)
input double MaxAngle = 89.0;                      // Set maximum inclination angle (degrees)
input bool   DeleteExpiredObjects = false;         // Enable deletion of expired/broken objects
input bool   EnableTradingSignals = true;          // Enable buy/sell signals and trades
input bool   DrawTouchArrows = true;               // Enable drawing arrows at touch points
input bool   DrawLabels = true;                    // Enable drawing trendline/point labels
input color  SupportLineColor = clrGreen;          // Set color for support trendlines
input color  ResistanceLineColor = clrRed;         // Set color for resistance trendlines

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
Swing swingLows[];                                 //--- Store swing lows
int numLows = 0;                                   //--- Track number of swing lows
Swing swingHighs[];                                //--- Store swing highs
int numHighs = 0;                                  //--- Track number of swing highs
TrendlineInfo trendlines[];                        //--- Store trendlines
int numTrendlines = 0;                             //--- Track number of trendlines
StartingPoint startingPoints[];                    //--- Store used starting points
int numStartingPoints = 0;                         //--- Track number of starting points

Начнём с настройки основных компонентов программы для автоматизации торговли на основе касаний линий тренда. Сначала подключим библиотеку "<Trade\Trade.mqh>" и создадим экземпляр объекта "CTrade" под названием "obj_Trade" для управления торговыми операциями, такими как исполнение ордеров на покупку и продажу. Затем перейдём к определению трех структур: "Swing" с параметрами "time" (datetime) и "price" (double) для фиксации точек разворота; "StartingPoint" с параметрами "time" (datetime), "price" (double) и "is_support" (bool) для отслеживания используемых начальных точек поддержки или сопротивления; и "TrendlineInfo" с параметрами "name" (string), "start_time" и "end_time" (datetimes), "start_price" и "end_price" (doubles), "slope" (double), "is_support" (bool), "touch_count" (int), "creation_time" (datetime), "touch_indices" (int array) и "is_signaled" (bool) для хранения сведений о линии тренда.

Далее предварительно объявляем функции для выполнения ключевых задач: "DetectSwings" для определения точек разворота, "SortSwings" для упорядочивания свингов, "CalculateAngle" для вычисления наклона линии тренда, "ValidateTrendline" для обеспечения критерия валидности линии тренда, "FindAndDrawTrendlines" для создания и построения линий тренда, "UpdateTrendlines" для их обслуживания, "RemoveTrendlineFromStorage" для очистки, "IsStartingPointUsed" для проверки использования точек и "LeastSquaresFit" для вычисления наклона и точки пересечения с помощью аппроксимации методом наименьших квадратов.

Наконец, настроим входные параметры и глобальные переменные: входные параметры, такие как "LookbackBars" (200) для диапазона обнаружения свингов, "TouchTolerance" (10,0 точек) для точности касания, "MinTouches" (3) для валидности, и остальные, которые понятны сами по себе; и глобальные переменные, такие как массивы "swingLows" и "swingHighs" с "numLows" и "numHighs" (0) для точек разворота, а также массивы "trendlines" и "startingPoints" с "numTrendlines" и "numStartingPoints" (0) для хранения линий тренда и точек. Такая структурированная настройка создает основу советника для эффективного определения линий тренда и торговли на них. Поскольку у нас все готово, мы можем инициализировать массивы хранения в процессе инициализации.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   ArrayResize(trendlines, 0);                     //--- Resize trendlines array
   numTrendlines = 0;                              //--- Reset trendlines count
   ArrayResize(startingPoints, 0);                 //--- Resize starting points array
   numStartingPoints = 0;                          //--- Reset starting points count
   return(INIT_SUCCEEDED);                         //--- Return success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ArrayResize(trendlines, 0);                     //--- Resize trendlines array
   numTrendlines = 0;                              //--- Reset trendlines count
   ArrayResize(startingPoints, 0);                 //--- Resize starting points array
   numStartingPoints = 0;                          //--- Reset starting points count
}

Чтобы обеспечить надлежащую настройку и очистку ресурсов, в обработчике OnInit подготовим советник, изменяя размер массива "trendlines" на 0 с помощью параметра ArrayResize и устанавливая значение "numTrendlines" на 0, чтобы очистить все существующие данные о линиях тренда. Затем уменьшаем массив "startingPoints" до нулевого размера и устанавливаем "numStartingPoints" в 0, чтобы сбросить записи о начальных точках и, наконец, возвращаем "INIT_SUCCEEDED" для подтверждения успешной инициализации.

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

//+------------------------------------------------------------------+
//| Check for new bar                                                |
//+------------------------------------------------------------------+
bool IsNewBar() {
   static datetime lastTime = 0;                      //--- Store last bar time
   datetime currentTime = iTime(_Symbol, _Period, 0); //--- Get current bar time
   if (lastTime != currentTime) {                     //--- Check for new bar
      lastTime = currentTime;                         //--- Update last time
      return true;                                    //--- Indicate new bar
   }
   return false;                                      //--- Indicate no new bar
}

//+------------------------------------------------------------------+
//| Sort swings by time (ascending, oldest first)                    |
//+------------------------------------------------------------------+
void SortSwings(Swing &swings[], int count) {
   for (int i = 0; i < count - 1; i++) {               //--- Iterate through swings
      for (int j = 0; j < count - i - 1; j++) {        //--- Compare adjacent swings
         if (swings[j].time > swings[j + 1].time) {    //--- Check time order
            Swing temp = swings[j];                    //--- Store temporary swing
            swings[j] = swings[j + 1];                 //--- Swap swings
            swings[j + 1] = temp;                      //--- Complete swap
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Detect swing highs and lows                                      |
//+------------------------------------------------------------------+
void DetectSwings() {
   numLows = 0;                                         //--- Reset lows count
   ArrayResize(swingLows, 0);                           //--- Resize lows array
   numHighs = 0;                                        //--- Reset highs count
   ArrayResize(swingHighs, 0);                          //--- Resize highs array
   int totalBars = iBars(_Symbol, _Period);             //--- Get total bars
   int effectiveLookback = MathMin(LookbackBars, totalBars); //--- Calculate effective lookback
   if (effectiveLookback < 5) {                         //--- Check sufficient bars
      Print("Not enough bars for swing detection.");    //--- Log insufficient bars
      return;                                           //--- Exit function
   }
   for (int i = 2; i < effectiveLookback - 2; i++) {    //--- Iterate through bars
      double low_i = iLow(_Symbol, _Period, i);         //--- Get current low
      double low_im1 = iLow(_Symbol, _Period, i - 1);   //--- Get previous low
      double low_im2 = iLow(_Symbol, _Period, i - 2);   //--- Get two bars prior low
      double low_ip1 = iLow(_Symbol, _Period, i + 1);   //--- Get next low
      double low_ip2 = iLow(_Symbol, _Period, i + 2);   //--- Get two bars next low
      if (low_i < low_im1 && low_i < low_im2 && low_i < low_ip1 && low_i < low_ip2) { //--- Check for swing low
         Swing s;                                       //--- Create swing struct
         s.time = iTime(_Symbol, _Period, i);           //--- Set swing time
         s.price = low_i;                               //--- Set swing price
         ArrayResize(swingLows, numLows + 1);           //--- Resize lows array
         swingLows[numLows] = s;                        //--- Add swing low
         numLows++;                                     //--- Increment lows count
      }
      double high_i = iHigh(_Symbol, _Period, i);       //--- Get current high
      double high_im1 = iHigh(_Symbol, _Period, i - 1); //--- Get previous high
      double high_im2 = iHigh(_Symbol, _Period, i - 2); //--- Get two bars prior high
      double high_ip1 = iHigh(_Symbol, _Period, i + 1); //--- Get next high
      double high_ip2 = iHigh(_Symbol, _Period, i + 2); //--- Get two bars next high
      if (high_i > high_im1 && high_i > high_im2 && high_i > high_ip1 && high_i > high_ip2) { //--- Check for swing high
         Swing s;                                       //--- Create swing struct
         s.time = iTime(_Symbol, _Period, i);           //--- Set swing time
         s.price = high_i;                              //--- Set swing price
         ArrayResize(swingHighs, numHighs + 1);         //--- Resize highs array
         swingHighs[numHighs] = s;                      //--- Add swing high
         numHighs++;                                    //--- Increment highs count
      }
   }
   if (numLows > 0) SortSwings(swingLows, numLows);     //--- Sort swing lows
   if (numHighs > 0) SortSwings(swingHighs, numHighs);  //--- Sort swing highs
}

Здесь мы реализуем ключевые функции для управления обнаружением баров и определения точек разворота, закладывая основу для анализа линий тренда. Сначала создадим функцию "IsNewBar", которая проверяет наличие нового бара, статически сохраняя "lastTime" как 0, сравнивая его с "currentTime" из iTime для текущего символа и периода при нулевом сдвиге для текущего бара, обновляя "lastTime", если он отличается, и возвращая значение true для нового бара или false в противном случае. Затем перейдем к реализации функции "SortSwings", которая сортирует массив "swings" с помощью "time" в порядке возрастания (от самых старых к самым новым) с использованием алгоритма пузырьковой сортировки, перебирая элементы со значением "count - 1" и меняя местами соседние структуры "Swing" с временной структурой "temp", если их значения времени не совпадают.

Наконец, мы реализуем функцию "DetectSwings", обнуляя "numLows" и "numHighs" и изменяя размер массивов "swingLows" и "swingHighs" до 0, вычисляя "effectiveLookback" как минимум из "LookbackBars" и общего количества баров из iBars, завершая работу с выводом в лог, если доступно менее 5 баров, и перебирая бары от 2 до "effectiveLookback - 2", чтобы определить минимумы и максимумы свингов, сравнивая значения "iLow" и "iHigh" с двумя предыдущими и последующими барами, создавая структуры "Swing" с "time" из "iTime" и "price" из "iLow" или iHigh, добавляя их к "swingLows" или "swingHighs" с помощью ArrayResize, увеличивая счетчики и сортируя массивы с помощью "SortSwings", если они не пусты. Это обеспечит своевременное обнаружение свингов для точного построения линий тренда. Теперь определим функции для расчета наклона линии тренда для ограничения на основе наклона и ее проверки.

//+------------------------------------------------------------------+
//| Calculate visual inclination angle                               |
//+------------------------------------------------------------------+
double CalculateAngle(datetime time1, double price1, datetime time2, double price2) {
   int x1, y1, x2, y2;                                               //--- Declare coordinate variables
   if (!ChartTimePriceToXY(0, 0, time1, price1, x1, y1)) return 0.0; //--- Convert time1/price1 to XY
   if (!ChartTimePriceToXY(0, 0, time2, price2, x2, y2)) return 0.0; //--- Convert time2/price2 to XY
   double dx = (double)(x2 - x1);                                    //--- Calculate x difference
   double dy = (double)(y2 - y1);                                    //--- Calculate y difference
   if (dx == 0.0) return (dy > 0.0 ? -90.0 : 90.0);                  //--- Handle vertical line case
   double angle = MathArctan(-dy / dx) * 180.0 / M_PI;               //--- Calculate angle in degrees
   return angle;                                                     //--- Return angle
}

//+------------------------------------------------------------------+
//| Validate trendline                                               |
//+------------------------------------------------------------------+
bool ValidateTrendline(bool isSupport, datetime start_time, datetime ref_time, double ref_price, double slope, double tolerance_pen) {
   int bar_start = iBarShift(_Symbol, _Period, start_time);          //--- Get start bar index
   if (bar_start < 0) return false;                                  //--- Check invalid bar index
   for (int bar = bar_start; bar >= 0; bar--) {                      //--- Iterate through bars
      datetime bar_time = iTime(_Symbol, _Period, bar);              //--- Get bar time
      double dk = (double)(bar_time - ref_time);                     //--- Calculate time difference
      double line_price = ref_price + slope * dk;                    //--- Calculate line price
      if (isSupport) {                                               //--- Check support case
         double low = iLow(_Symbol, _Period, bar);                   //--- Get bar low
         if (low < line_price - tolerance_pen) return false;         //--- Check if broken
      } else {                                                       //--- Handle resistance case
         double high = iHigh(_Symbol, _Period, bar);                 //--- Get bar high
         if (high > line_price + tolerance_pen) return false;        //--- Check if broken
      }
   }
   return true;                                                      //--- Return valid
}

Приступим к реализации критически важных функций для расчета углов линий тренда и проверки их целостности, обеспечивая надежное обнаружение линий тренда. Сначала создадим функцию "CalculateAngle", которая преобразует две точки ("time1", "price1" и "time2", "price2") в координаты графика с помощью функции ChartTimePriceToXY в значения "x1", "y1", "x2", "y2", возвращая 0.0 в случае неудачи преобразования. Затем вычисляет разность по оси x "dx" и разность по оси y "dy", обрабатывая вертикальные линии, возвращая -90.0 или 90.0, если "dx" равно нулю, и вычисляет угол в градусах, используя функцию "MathArctan(-dy / dx) * 180.0 / M_PI" для визуального наклона.

Затем перейдём к реализации функции "ValidateTrendline", которая проверяет линию тренда, получая индекс начального бара с помощью iBarShift для "start_time", возвращая значение false в случае недействительности и выполняя итерации от "bar_start" до 0, вычисляя цену линии тренда в каждой точке "bar_time" с использованием формулы "ref_price + slope * dk", где "dk" — разница во времени относительно эталонного времени. Для линий поддержки ("isSupport" true) мы проверим, опускается ли iLow бара ниже "line_price - tolerance_pen", возвращая значение false в случае пробития; для сопротивления мы проверим, превышает ли iHigh параметр "line_price + tolerance_pen", возвращая значение false в случае пробития и true, если линия тренда остается устойчивой. Теперь мы можем сосредоточиться на функции, отвечающей за логику расчета аппроксимации методом наименьших квадратов. Постараемся изложить все максимально просто.

//+------------------------------------------------------------------+
//| Perform least-squares fit for slope and intercept                |
//+------------------------------------------------------------------+
void LeastSquaresFit(const datetime &times[], const double &prices[], int n, double &slope, double &intercept) {
   double sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0; //--- Initialize sums
   for (int k = 0; k < n; k++) {                        //--- Iterate through points
      double x = (double)times[k];                      //--- Convert time to x
      double y = prices[k];                             //--- Set price as y
      sum_x += x;                                       //--- Accumulate x
      sum_y += y;                                       //--- Accumulate y
      sum_xy += x * y;                                  //--- Accumulate x*y
      sum_x2 += x * x;                                  //--- Accumulate x^2
   }
   slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x); //--- Calculate slope
   intercept = (sum_y - slope * sum_x) / n;             //--- Calculate intercept
}

Мы используем функцию "LeastSquaresFit" для вычисления оптимального наклона и точки пересечения для линий тренда, что позволяет точно аппроксимировать линии тренда. Сначала мы инициализируем переменные "sum_x", "sum_y", "sum_xy" и "sum_x2" нулями, чтобы накапливать значения для вычисления методом наименьших квадратов. Затем переберем "n" точек в массивах "times" и "prices", преобразуя каждый "times[k]" в число типа double как "x" и устанавливая "prices[k]" в качестве "y", добавляя "x" к "sum_x", "y" к "sum_y", "x * y" к "sum_xy" и "x * x" к "sum_x2". Наконец, вычислим параметр "slope" по формуле "(n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x)", а параметр "intercept" как "(sum_y - slope * sum_x) / n", получая линию наилучшего соответствия для линии тренда на основе точек входа. Если вам интересна формула, ознакомьтесь с ней ниже.

LEAST SQUARES FIT METHOD

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

//+------------------------------------------------------------------+
//| Check if starting point is already used                          |
//+------------------------------------------------------------------+
bool IsStartingPointUsed(datetime time, double price, bool is_support) {
   for (int i = 0; i < numStartingPoints; i++) {  //--- Iterate through starting points
      if (startingPoints[i].time == time && MathAbs(startingPoints[i].price - price) < TouchTolerance * _Point && startingPoints[i].is_support == is_support) { //--- Check match
         return true;                             //--- Return used
      }
   }
   return false;                                   //--- Return not used
}

//+------------------------------------------------------------------+
//| Remove trendline from storage and optionally chart objects       |
//+------------------------------------------------------------------+
void RemoveTrendlineFromStorage(int index) {
   if (index < 0 || index >= numTrendlines) return;                    //--- Check valid index
   Print("Removing trendline from storage: ", trendlines[index].name); //--- Log removal
   if (DeleteExpiredObjects) {                                         //--- Check deletion flag
      ObjectDelete(0, trendlines[index].name);                         //--- Delete trendline object
      for (int m = 0; m < trendlines[index].touch_count; m++) {        //--- Iterate touches
         string arrow_name = trendlines[index].name + "_touch" + IntegerToString(m); //--- Generate arrow name
         ObjectDelete(0, arrow_name);                                  //--- Delete touch arrow
         string text_name = trendlines[index].name + "_point_label" + IntegerToString(m); //--- Generate text name
         ObjectDelete(0, text_name);                                   //--- Delete point label
      }
      string label_name = trendlines[index].name + "_label";           //--- Generate label name
      ObjectDelete(0, label_name);                                     //--- Delete trendline label
      string signal_arrow = trendlines[index].name + "_signal_arrow";  //--- Generate signal arrow name
      ObjectDelete(0, signal_arrow);                                   //--- Delete signal arrow
      string signal_text = trendlines[index].name + "_signal_text";    //--- Generate signal text name
      ObjectDelete(0, signal_text);                                    //--- Delete signal text
   }
   for (int i = index; i < numTrendlines - 1; i++) {                   //--- Shift array
      trendlines[i] = trendlines[i + 1];                               //--- Copy next trendline
   }
   ArrayResize(trendlines, numTrendlines - 1);                         //--- Resize trendlines array
   numTrendlines--;                                                    //--- Decrement trendlines count
}

Приступим к внедрению служебных функций для управления начальными точками линий тренда и их очистки, обеспечивая эффективное отслеживание линий тренда и управление графиками. Сначала создадим функцию "IsStartingPointUsed", которая перебирает "numStartingPoints" в массиве "startingPoints", проверяя, соответствуют ли заданные значения "time", "price" и "is_support" какой-либо существующей начальной точке, сравнивая "time" точно, "price" в массиве "TouchTolerance * _Point" с помощью MathAbs и "is_support", возвращая значение true, если найдено, или false, если нет. Затем переходим к реализации функции "RemoveTrendlineFromStorage", которая проверяет входной параметр "index" на соответствие "numTrendlines", завершает работу, если он недействителен, и регистрирует удаление.

Если "DeleteExpiredObjects" равно true, мы удаляем объект линии тренда с помощью ObjectDelete для "trendlines[index].name", затем проходим циклом по "touch_count", чтобы удалить стрелки касания и метки с именами типа "trendlines[index].name + '_touch' + IntegerToString(m)" и "trendlines[index].name + '_point_label' + IntegerToString(m)", и удаляем метку линии тренда, стрелку сигнала и текст сигнала, используя "label_name", "signal_arrow" и "signal_text". Наконец, переместим массив "trendlines" из "index" в "numTrendlines - 1", чтобы удалить запись, изменяем размер "trendlines" с помощью ArrayResize и уменьшаем их количество, что помогает предотвратить дублирование линий тренда и эффективно очистить пробитые или утратившие актуальность линии тренда. Давайте теперь определим функцию для поиска и построения линий тренда с помощью определенных нами вспомогательных функций.

//+------------------------------------------------------------------+
//| Find and draw trendlines if no active one exists                 |
//+------------------------------------------------------------------+
void FindAndDrawTrendlines(bool isSupport) {
   bool has_active = false;                       //--- Initialize active flag
   for (int i = 0; i < numTrendlines; i++) {      //--- Iterate through trendlines
      if (trendlines[i].is_support == isSupport) { //--- Check type match
         has_active = true;                       //--- Set active flag
         break;                                   //--- Exit loop
      }
   }
   if (has_active) return;                        //--- Exit if active trendline exists
   Swing swings[];                                //--- Initialize swings array
   int numSwings;                                 //--- Initialize swings count
   color lineColor;                               //--- Initialize line color
   string prefix;                                 //--- Initialize prefix
   if (isSupport) {                               //--- Handle support case
      numSwings = numLows;                        //--- Set number of lows
      ArrayResize(swings, numSwings);             //--- Resize swings array
      for (int i = 0; i < numSwings; i++) {       //--- Iterate through lows
         swings[i].time = swingLows[i].time;      //--- Copy low time
         swings[i].price = swingLows[i].price;    //--- Copy low price
      }
      lineColor = SupportLineColor;               //--- Set support line color
      prefix = "Trendline_Support_";              //--- Set support prefix
   } else {                                       //--- Handle resistance case
      numSwings = numHighs;                       //--- Set number of highs
      ArrayResize(swings, numSwings);             //--- Resize swings array
      for (int i = 0; i < numSwings; i++) {       //--- Iterate through highs
         swings[i].time = swingHighs[i].time;     //--- Copy high time
         swings[i].price = swingHighs[i].price;   //--- Copy high price
      }
      lineColor = ResistanceLineColor;            //--- Set resistance line color
      prefix = "Trendline_Resistance_";           //--- Set resistance prefix
   }
   if (numSwings < 2) return;                     //--- Exit if insufficient swings
   double pointValue = _Point;                    //--- Get point value
   double touch_tolerance = TouchTolerance * pointValue; //--- Calculate touch tolerance
   double pen_tolerance = PenetrationTolerance * pointValue; //--- Calculate penetration tolerance
   int best_j = -1;                               //--- Initialize best j index
   int max_touches = 0;                           //--- Initialize max touches
   int best_touch_indices[];                      //--- Initialize best touch indices
   double best_slope = 0.0;                       //--- Initialize best slope
   double best_intercept = 0.0;                   //--- Initialize best intercept
   datetime best_min_time = 0;                    //--- Initialize best min time
   for (int i = 0; i < numSwings - 1; i++) {      //--- Iterate through first points
      for (int j = i + 1; j < numSwings; j++) {   //--- Iterate through second points
         datetime time1 = swings[i].time;         //--- Get first time
         double price1 = swings[i].price;         //--- Get first price
         datetime time2 = swings[j].time;         //--- Get second time
         double price2 = swings[j].price;         //--- Get second price
         double dt = (double)(time2 - time1);     //--- Calculate time difference
         if (dt <= 0) continue;                   //--- Skip invalid time difference
         double initial_slope = (price2 - price1) / dt; //--- Calculate initial slope
         int touch_indices[];                     //--- Initialize touch indices
         ArrayResize(touch_indices, 0);           //--- Resize touch indices
         int touches = 0;                         //--- Initialize touches count
         ArrayResize(touch_indices, touches + 1); //--- Add first index
         touch_indices[touches] = i;              //--- Set first index
         touches++;                               //--- Increment touches
         ArrayResize(touch_indices, touches + 1); //--- Add second index
         touch_indices[touches] = j;              //--- Set second index
         touches++;                               //--- Increment touches
         for (int k = 0; k < numSwings; k++) {    //--- Iterate through swings
            if (k == i || k == j) continue;       //--- Skip used indices
            datetime tk = swings[k].time;         //--- Get swing time
            double dk = (double)(tk - time1);     //--- Calculate time difference
            double expected = price1 + initial_slope * dk; //--- Calculate expected price
            double actual = swings[k].price;      //--- Get actual price
            if (MathAbs(expected - actual) <= touch_tolerance) { //--- Check touch within tolerance
               ArrayResize(touch_indices, touches + 1); //--- Add index
               touch_indices[touches] = k;        //--- Set index
               touches++;                         //--- Increment touches
            }
         }
         if (touches >= MinTouches) {             //--- Check minimum touches
            ArraySort(touch_indices);             //--- Sort touch indices
            bool valid_spacing = true;            //--- Initialize spacing flag
            for (int m = 0; m < touches - 1; m++) { //--- Iterate through touches
               int idx1 = touch_indices[m];       //--- Get first index
               int idx2 = touch_indices[m + 1];   //--- Get second index
               int bar1 = iBarShift(_Symbol, _Period, swings[idx1].time); //--- Get first bar
               int bar2 = iBarShift(_Symbol, _Period, swings[idx2].time); //--- Get second bar
               int diff = MathAbs(bar1 - bar2);   //--- Calculate bar difference
               if (diff < MinBarSpacing) {        //--- Check minimum spacing
                  valid_spacing = false;          //--- Mark invalid spacing
                  break;                          //--- Exit loop
               }
            }
            if (valid_spacing) {                  //--- Check valid spacing
               datetime touch_times[];            //--- Initialize touch times
               double touch_prices[];             //--- Initialize touch prices
               ArrayResize(touch_times, touches); //--- Resize times array
               ArrayResize(touch_prices, touches); //--- Resize prices array
               for (int m = 0; m < touches; m++) { //--- Iterate through touches
                  int idx = touch_indices[m];      //--- Get index
                  touch_times[m] = swings[idx].time;   //--- Set time
                  touch_prices[m] = swings[idx].price; //--- Set price
               }
               double slope, intercept;                //--- Declare slope and intercept
               LeastSquaresFit(touch_times, touch_prices, touches, slope, intercept); //--- Perform least squares fit
               int adjusted_touch_indices[];           //--- Initialize adjusted indices
               ArrayResize(adjusted_touch_indices, 0); //--- Resize adjusted indices
               int adjusted_touches = 0;               //--- Initialize adjusted touches count
               for (int k = 0; k < numSwings; k++) {   //--- Iterate through swings
                  double expected = intercept + slope * (double)swings[k].time; //--- Calculate expected price
                  double actual = swings[k].price;     //--- Get actual price
                  if (MathAbs(expected - actual) <= touch_tolerance) { //--- Check touch
                     ArrayResize(adjusted_touch_indices, adjusted_touches + 1); //--- Add index
                     adjusted_touch_indices[adjusted_touches] = k; //--- Set index
                     adjusted_touches++;               //--- Increment adjusted touches
                  }
               }
               if (adjusted_touches >= MinTouches) { //--- Check minimum adjusted touches
                  datetime temp_min_time = swings[adjusted_touch_indices[0]].time; //--- Get min time
                  double temp_ref_price = intercept + slope * (double)temp_min_time; //--- Calculate ref price
                  if (ValidateTrendline(isSupport, temp_min_time, temp_min_time, temp_ref_price, slope, pen_tolerance)) { //--- Validate trendline
                     datetime temp_max_time = swings[adjusted_touch_indices[adjusted_touches - 1]].time; //--- Get max time
                     double temp_max_price = intercept + slope * (double)temp_max_time; //--- Calculate max price
                     double angle = CalculateAngle(temp_min_time, temp_ref_price, temp_max_time, temp_max_price); //--- Calculate angle
                     double abs_angle = MathAbs(angle); //--- Get absolute angle
                     if (abs_angle >= MinAngle && abs_angle <= MaxAngle) { //--- Check angle range
                        if (adjusted_touches > max_touches || (adjusted_touches == max_touches && j > best_j)) { //--- Check better trendline
                           max_touches = adjusted_touches; //--- Update max touches
                           best_j = j;                     //--- Update best j
                           best_slope = slope;             //--- Update best slope
                           best_intercept = intercept;     //--- Update best intercept
                           best_min_time = temp_min_time;  //--- Update best min time
                           ArrayResize(best_touch_indices, adjusted_touches); //--- Resize best indices
                           ArrayCopy(best_touch_indices, adjusted_touch_indices); //--- Copy indices
                        }
                     }
                  }
               }
            }
         }
      }
   }
   if (max_touches < MinTouches) {                //--- Check insufficient touches
      string type = isSupport ? "Support" : "Resistance"; //--- Set type string
      return;                                     //--- Exit function
   }
   int touch_indices[];                           //--- Initialize touch indices
   ArrayResize(touch_indices, max_touches);       //--- Resize touch indices
   ArrayCopy(touch_indices, best_touch_indices);  //--- Copy best indices
   int touches = max_touches;                     //--- Set touches count
   datetime min_time = best_min_time;             //--- Set min time
   double price_min = best_intercept + best_slope * (double)min_time; //--- Calculate min price
   datetime max_time = swings[touch_indices[touches - 1]].time; //--- Set max time
   double price_max = best_intercept + best_slope * (double)max_time; //--- Calculate max price
   datetime start_time_check = min_time;          //--- Set start time check
   double start_price_check = swings[touch_indices[0]].price; //--- Set start price check
   if (IsStartingPointUsed(start_time_check, start_price_check, isSupport)) { //--- Check used starting point
      return;                                     //--- Skip if used
   }
   datetime time_end = iTime(_Symbol, _Period, 0) + PeriodSeconds(_Period) * ExtensionBars; //--- Calculate end time
   double dk_end = (double)(time_end - min_time);      //--- Calculate end time difference
   double price_end = price_min + best_slope * dk_end; //--- Calculate end price
   string unique_name = prefix + TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS); //--- Generate unique name
   if (ObjectFind(0, unique_name) < 0) {               //--- Check if trendline exists
      ObjectCreate(0, unique_name, OBJ_TREND, 0, min_time, price_min, time_end, price_end); //--- Create trendline
      ObjectSetInteger(0, unique_name, OBJPROP_COLOR, lineColor);   //--- Set color
      ObjectSetInteger(0, unique_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
      ObjectSetInteger(0, unique_name, OBJPROP_WIDTH, 1);           //--- Set width
      ObjectSetInteger(0, unique_name, OBJPROP_RAY_RIGHT, false);   //--- Disable right ray
      ObjectSetInteger(0, unique_name, OBJPROP_RAY_LEFT, false);    //--- Disable left ray
      ObjectSetInteger(0, unique_name, OBJPROP_BACK, false);        //--- Set to foreground
   }
   ArrayResize(trendlines, numTrendlines + 1);                      //--- Resize trendlines array
   trendlines[numTrendlines].name = unique_name;                    //--- Set trendline name
   trendlines[numTrendlines].start_time = min_time;                 //--- Set start time
   trendlines[numTrendlines].end_time = time_end;                   //--- Set end time
   trendlines[numTrendlines].start_price = price_min;               //--- Set start price
   trendlines[numTrendlines].end_price = price_end;                 //--- Set end price
   trendlines[numTrendlines].slope = best_slope;                    //--- Set slope
   trendlines[numTrendlines].is_support = isSupport;                //--- Set type
   trendlines[numTrendlines].touch_count = touches;                 //--- Set touch count
   trendlines[numTrendlines].creation_time = TimeCurrent();         //--- Set creation time
   trendlines[numTrendlines].is_signaled = false;                   //--- Set signaled flag
   ArrayResize(trendlines[numTrendlines].touch_indices, touches);   //--- Resize touch indices
   ArrayCopy(trendlines[numTrendlines].touch_indices, touch_indices); //--- Copy touch indices
   numTrendlines++;                                                 //--- Increment trendlines count
   ArrayResize(startingPoints, numStartingPoints + 1);              //--- Resize starting points array
   startingPoints[numStartingPoints].time = start_time_check; //--- Set starting point time
   startingPoints[numStartingPoints].price = start_price_check; //--- Set starting point price
   startingPoints[numStartingPoints].is_support = isSupport; //--- Set starting point type
   numStartingPoints++;                           //--- Increment starting points count
   if (DrawTouchArrows) {                         //--- Check draw arrows
      for (int m = 0; m < touches; m++) {         //--- Iterate through touches
         int idx = touch_indices[m];              //--- Get touch index
         datetime tk_time = swings[idx].time;     //--- Get touch time
         double tk_price = swings[idx].price;     //--- Get touch price
         string arrow_name = unique_name + "_touch" + IntegerToString(m); //--- Generate arrow name
         if (ObjectFind(0, arrow_name) < 0) {                             //--- Check if arrow exists
            ObjectCreate(0, arrow_name, OBJ_ARROW, 0, tk_time, tk_price); //--- Create touch arrow
            ObjectSetInteger(0, arrow_name, OBJPROP_ARROWCODE, 159);      //--- Set arrow code
            ObjectSetInteger(0, arrow_name, OBJPROP_ANCHOR, isSupport ? ANCHOR_TOP : ANCHOR_BOTTOM); //--- Set anchor
            ObjectSetInteger(0, arrow_name, OBJPROP_COLOR, lineColor);    //--- Set color
            ObjectSetInteger(0, arrow_name, OBJPROP_WIDTH, 1);            //--- Set width
            ObjectSetInteger(0, arrow_name, OBJPROP_BACK, false);         //--- Set to foreground
         }
      }
   }
   double angle = CalculateAngle(min_time, price_min, max_time, price_max); //--- Calculate angle
   string type = isSupport ? "Support" : "Resistance"; //--- Set type string
   Print(type + " Trendline " + unique_name + " drawn with " + IntegerToString(touches) + " touches. Inclination angle: " + DoubleToString(angle, 2) + " degrees."); //--- Log trendline
   if (DrawLabels) {                              //--- Check draw labels
      datetime mid_time = min_time + (max_time - min_time) / 2; //--- Calculate mid time
      double dk_mid = (double)(mid_time - min_time); //--- Calculate mid time difference
      double mid_price = price_min + best_slope * dk_mid; //--- Calculate mid price
      double label_offset = 20 * _Point * (isSupport ? -1 : 1); //--- Calculate label offset
      double label_price = mid_price + label_offset; //--- Calculate label price
      int label_anchor = isSupport ? ANCHOR_TOP : ANCHOR_BOTTOM; //--- Set label anchor
      string label_text = type + " Trendline";    //--- Set label text
      string label_name = unique_name + "_label"; //--- Generate label name
      if (ObjectFind(0, label_name) < 0) {        //--- Check if label exists
         ObjectCreate(0, label_name, OBJ_TEXT, 0, mid_time, label_price); //--- Create label
         ObjectSetString(0, label_name, OBJPROP_TEXT, label_text); //--- Set text
         ObjectSetInteger(0, label_name, OBJPROP_COLOR, clrBlack); //--- Set color
         ObjectSetInteger(0, label_name, OBJPROP_FONTSIZE, 8); //--- Set font size
         ObjectSetInteger(0, label_name, OBJPROP_ANCHOR, label_anchor); //--- Set anchor
         ObjectSetDouble(0, label_name, OBJPROP_ANGLE, angle); //--- Set angle
         ObjectSetInteger(0, label_name, OBJPROP_BACK, false); //--- Set to foreground
      }
      color point_label_color = isSupport ? clrSaddleBrown : clrDarkGoldenrod; //--- Set point label color
      double point_text_offset = 20.0 * _Point;   //--- Set point text offset
      for (int m = 0; m < touches; m++) {         //--- Iterate through touches
         int idx = touch_indices[m];              //--- Get touch index
         datetime tk_time = swings[idx].time;     //--- Get touch time
         double tk_price = swings[idx].price;     //--- Get touch price
         double text_price;                       //--- Initialize text price
         int point_text_anchor;                   //--- Initialize text anchor
         if (isSupport) {                         //--- Handle support
            text_price = tk_price - point_text_offset; //--- Set text price below
            point_text_anchor = ANCHOR_LEFT;      //--- Set left anchor
         } else {                                 //--- Handle resistance
            text_price = tk_price + point_text_offset; //--- Set text price above
            point_text_anchor = ANCHOR_BOTTOM;    //--- Set bottom anchor
         }
         string text_name = unique_name + "_point_label" + IntegerToString(m); //--- Generate text name
         string point_text = "Pt " + IntegerToString(m + 1); //--- Set point text
         if (ObjectFind(0, text_name) < 0) {     //--- Check if text exists
            ObjectCreate(0, text_name, OBJ_TEXT, 0, tk_time, text_price); //--- Create text
            ObjectSetString(0, text_name, OBJPROP_TEXT, point_text); //--- Set text
            ObjectSetInteger(0, text_name, OBJPROP_COLOR, point_label_color); //--- Set color
            ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 8); //--- Set font size
            ObjectSetInteger(0, text_name, OBJPROP_ANCHOR, point_text_anchor); //--- Set anchor
            ObjectSetDouble(0, text_name, OBJPROP_ANGLE, 0); //--- Set angle
            ObjectSetInteger(0, text_name, OBJPROP_BACK, false); //--- Set to foreground
         }
      }
   }
}

Здесь мы реализуем функцию "FindAndDrawTrendlines" для определения и построения линий тренда, обеспечивая только одну активную линию тренда для каждого типа с оптимальными точками касания. Сначала проверим наличие существующей линии тренда, перебирая "numTrendlines" в "trendlines", устанавливая для "has_active" значение true, если "is_support" соответствует входным данным, и выходим, если найдено. Затем переходим к настройке уровней поддержки или сопротивления на основе параметра "isSupport": для уровня поддержки мы копируем "numLows" в "numSwings", заполняем "swings" данными из "swingLows", устанавливаем "lineColor" равным "SupportLineColor", а "prefix" равным "Trendline_Support_"; для уровня сопротивления мы используем "numHighs", "swingHighs", "ResistanceLineColor" и "Trendline_Resistance_", завершая процесс, если "numSwings" меньше 2. Далее вычислим "touch_tolerance" и "pen_tolerance", используя "TouchTolerance" и "PenetrationTolerance" с помощью _Point, и перебираем пары "numSwings", чтобы вычислить начальный "initial_slope", собирая "touch_indices" для точек, находящихся в пределах допустимого уровня касания.

Если количество касаний соответствует значению "MinTouches" и проходит проверку "MinBarSpacing" через iBarShift, мы используем метод "LeastSquaresFit" для получения значений "slope" и "intercept", повторно проверяем касания и выполняем валидацию с помощью методов "ValidateTrendline" и "CalculateAngle" по значениям "MinAngle" и "MaxAngle", обновляя значения "best_j", "max_touches", "best_slope", "best_intercept", "best_min_time" и "best_touch_indices" для построения наилучшей линии тренда. Наконец, если "max_touches" не меньше "MinTouches", а начальная точка не используется с помощью параметра "IsStartingPointUsed", мы создаем линию тренда с помощью функции ObjectCreate как OBJ_TREND, используя "unique_name", рисуем стрелки касания и метки, если "DrawTouchArrows" и "DrawLabels" имеют значение true, сохраняем подробности в "trendlines", добавляем начальную точку к начальным точкам и регистрируем результат в логе, обеспечивая точное создание линии тренда. Теперь остается лишь управлять существующими линиями тренда посредством постоянного обновления и проверки на наличие пересечений для получения сигналов. Для простоты объединим всю логику в одну функцию.

//+------------------------------------------------------------------+
//| Update trendlines and check for signals                          |
//+------------------------------------------------------------------+
void UpdateTrendlines() {
   datetime current_time = iTime(_Symbol, _Period, 0);       //--- Get current time
   double pointValue = _Point;                               //--- Get point value
   double pen_tolerance = PenetrationTolerance * pointValue; //--- Calculate penetration tolerance
   double touch_tolerance = TouchTolerance * pointValue;     //--- Calculate touch tolerance
   for (int i = numTrendlines - 1; i >= 0; i--) {            //--- Iterate trendlines backward
      string type = trendlines[i].is_support ? "Support" : "Resistance"; //--- Determine trendline type
      string name = trendlines[i].name;                      //--- Get trendline name
      if (current_time > trendlines[i].end_time) {           //--- Check if expired
         PrintFormat("%s trendline %s is no longer valid (expired). End time: %s, Current time: %s.", type, name, TimeToString(trendlines[i].end_time), TimeToString(current_time)); //--- Log expiration
         RemoveTrendlineFromStorage(i);                      //--- Remove trendline
         continue;                                           //--- Skip to next
      }
      datetime prev_bar_time = iTime(_Symbol, _Period, 1);   //--- Get previous bar time
      double dk = (double)(prev_bar_time - trendlines[i].start_time); //--- Calculate time difference
      double line_price = trendlines[i].start_price + trendlines[i].slope * dk; //--- Calculate line price
      double prev_low = iLow(_Symbol, _Period, 1);           //--- Get previous bar low
      double prev_high = iHigh(_Symbol, _Period, 1);         //--- Get previous bar high
      bool broken = false;                                   //--- Initialize broken flag
      if (trendlines[i].is_support && prev_low < line_price - pen_tolerance) { //--- Check support break
         PrintFormat("%s trendline %s is no longer valid (broken by price). Line price: %.5f, Prev low: %.5f, Penetration: %.5f points.", type, name, line_price, prev_low, PenetrationTolerance); //--- Log break
         RemoveTrendlineFromStorage(i);           //--- Remove trendline
         broken = true;                           //--- Set broken flag
      } else if (!trendlines[i].is_support && prev_high > line_price + pen_tolerance) { //--- Check resistance break
         PrintFormat("%s trendline %s is no longer valid (broken by price). Line price: %.5f, Prev high: %.5f, Penetration: %.5f points.", type, name, line_price, prev_high, PenetrationTolerance); //--- Log break
         RemoveTrendlineFromStorage(i);           //--- Remove trendline
         broken = true;                           //--- Set broken flag
      }
      if (!broken && !trendlines[i].is_signaled && EnableTradingSignals) { //--- Check for trading signal
         bool touched = false;                    //--- Initialize touched flag
         string signal_type = "";                 //--- Initialize signal type
         color signal_color = clrNONE;            //--- Initialize signal color
         int arrow_code = 0;                      //--- Initialize arrow code
         int anchor = 0;                          //--- Initialize anchor
         double text_angle = 0.0;                 //--- Initialize text angle
         double text_offset = 0.0;                //--- Initialize text offset
         double text_price = 0.0;                 //--- Initialize text price
         int text_anchor = 0;                     //--- Initialize text anchor
         if (trendlines[i].is_support && MathAbs(prev_low - line_price) <= touch_tolerance) { //--- Check support touch
            touched = true;                       //--- Set touched flag
            signal_type = "BUY";                  //--- Set buy signal
            signal_color = clrBlue;               //--- Set blue color
            arrow_code = 217;                     //--- Set up arrow for support (BUY)
            anchor = ANCHOR_TOP;                  //--- Set top anchor
            text_angle = -90.0;                   //--- Set vertical upward for BUY
            text_offset = -20 * pointValue;        //--- Set text offset
            text_price = line_price + text_offset; //--- Calculate text price
            text_anchor = ANCHOR_LEFT;            //--- Set left anchor
            double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Get ask price
            double SL = NormalizeDouble(Ask - inpSLPoints * _Point, _Digits); //--- Calculate stop loss
            double TP = NormalizeDouble(Ask + (inpSLPoints * inpRRRatio) * _Point, _Digits); //--- Calculate take profit
            obj_Trade.Buy(inpLot, _Symbol, Ask, SL, TP); //--- Execute buy trade
         } else if (!trendlines[i].is_support && MathAbs(prev_high - line_price) <= touch_tolerance) { //--- Check resistance touch
            touched = true;                       //--- Set touched flag
            signal_type = "SELL";                 //--- Set sell signal
            signal_color = clrRed;                //--- Set red color
            arrow_code = 218;                     //--- Set down arrow for resistance (SELL)
            anchor = ANCHOR_BOTTOM;               //--- Set bottom anchor
            text_angle = 90.0;                    //--- Set vertical downward for SELL
            text_offset = 20 * pointValue;       //--- Set text offset
            text_price = line_price + text_offset; //--- Calculate text price
            text_anchor = ANCHOR_BOTTOM;          //--- Set bottom anchor
            double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Get bid price
            double SL = NormalizeDouble(Bid + inpSLPoints * _Point, _Digits); //--- Calculate stop loss
            double TP = NormalizeDouble(Bid - (inpSLPoints * inpRRRatio) * _Point, _Digits); //--- Calculate take profit
            obj_Trade.Sell(inpLot, _Symbol, Bid, SL, TP); //--- Execute sell trade
         }
         if (touched) {                           //--- Check if touched
            PrintFormat("Signal generated for %s trendline %s: %s at price %.5f, time %s.", type, name, signal_type, line_price, TimeToString(current_time)); //--- Log signal
            string arrow_name = name + "_signal_arrow"; //--- Generate signal arrow name
            if (ObjectFind(0, arrow_name) < 0) {  //--- Check if arrow exists
               ObjectCreate(0, arrow_name, OBJ_ARROW, 0, prev_bar_time, line_price); //--- Create signal arrow
               ObjectSetInteger(0, arrow_name, OBJPROP_ARROWCODE, arrow_code); //--- Set arrow code
               ObjectSetInteger(0, arrow_name, OBJPROP_ANCHOR, anchor); //--- Set anchor
               ObjectSetInteger(0, arrow_name, OBJPROP_COLOR, signal_color); //--- Set color
               ObjectSetInteger(0, arrow_name, OBJPROP_WIDTH, 1); //--- Set width
               ObjectSetInteger(0, arrow_name, OBJPROP_BACK, false); //--- Set to foreground
            }
            string text_name = name + "_signal_text"; //--- Generate signal text name
            if (ObjectFind(0, text_name) < 0) {   //--- Check if text exists
               ObjectCreate(0, text_name, OBJ_TEXT, 0, prev_bar_time, text_price); //--- Create signal text
               ObjectSetString(0, text_name, OBJPROP_TEXT, " " + signal_type); //--- Set text content
               ObjectSetInteger(0, text_name, OBJPROP_COLOR, signal_color); //--- Set color
               ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 10); //--- Set font size
               ObjectSetInteger(0, text_name, OBJPROP_ANCHOR, text_anchor); //--- Set anchor
               ObjectSetDouble(0, text_name, OBJPROP_ANGLE, text_angle); //--- Set angle
               ObjectSetInteger(0, text_name, OBJPROP_BACK, false); //--- Set to foreground
            }
            trendlines[i].is_signaled = true;     //--- Set signaled flag
         }
      }
   }
}

Чтобы обеспечить мониторинг активных линий тренда и принятие соответствующих мер, мы создаем функцию "UpdateTrendlines", которая является функцией типа void, поскольку нам не нужно ничего возвращать. Сначала мы получаем "current_time" с помощью iTime для текущего бара и вычисляем "pointValue" как _Point, "pen_tolerance" как "PenetrationTolerance * pointValue" и "touch_tolerance" как "TouchTolerance * pointValue". Затем мы перебираем в обратном порядке "numTrendlines" в массиве "trendlines", определяя "type" линии тренда как "Support" или "Resistance" на основе значения "is_support", и проверяем, превышает ли "current_time" значение "end_time", регистрируя истечение срока действия с помощью PrintFormat и удаляя линию тренда с помощью "RemoveTrendlineFromStorage", если срок действия истек.

Далее, для неистекших линий тренда мы вычисляем цену линии тренда в момент времени "prev_bar_time" (начиная с iTime при сдвиге 1) с помощью "start_price + slope * dk", проверяем, пробита ли линия тренда, сравнивая "prev_low" или "prev_high" с "line_price" с помощью функции "pen_tolerance", регистрируем пробития с помощью "PrintFormat" и удаляем их с помощью "RemoveTrendlineFromStorage", если они пробиты.

Наконец, если они не пробиты и "is_signaled" имеет значение false, а "EnableTradingSignals" — true, мы проверяем наличие касаний: для поддержки, если "prev_low" находится в пределах "touch_tolerance" от "line_price", мы устанавливаем сигнал "BUY", выполняем сделку на покупку с помощью "obj_Trade.Buy", используя "inpLot", "Ask", "SL" и "TP", рассчитанные с помощью "inpSLPoints" и "inpRRRatio", и рисуем синюю стрелку вверх (код 217) и текст; для сопротивления, если "prev_high" находится в пределах допустимого значения, мы устанавливаем сигнал "SELL", выполняем сделку на продажу с помощью "obj_Trade.Sell", и рисуем красную стрелку вниз (код 218) и текст, регистрируя данные с помощью "PrintFormat", создавая объекты с помощью ObjectCreate, устанавливая свойства с помощью ObjectSetInteger и ObjectSetString и помечая "is_signaled" как true, обеспечивая обновление линий тренда и генерацию точных торговых сигналов. Выбор используемых кодов стрелок зависит от вас. Вот список кодов, которые можно использовать из кодов Wingdings, определенных в MQL5.

MQL5 WINGDINGS

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if (!IsNewBar()) return;                        //--- Exit if not new bar
   DetectSwings();                                 //--- Detect swings
   UpdateTrendlines();                             //--- Update trendlines
   FindAndDrawTrendlines(true);                    //--- Find/draw support trendlines
}

В обработчике OnTick организуем обнаружение линии тренда и торговую логику на каждом новом баре. Сначала проверим, сформировался ли новый бар, вызывая "IsNewBar", и немедленно завершаем работу в случае значения false, чтобы избежать избыточной обработки. Затем переходим к вызову функции "DetectSwings", чтобы идентифицировать и обновить свинг-хай и свинг-лоу, хранящиеся в полях "swingHighs" и "swingLows". Далее вызовем функцию "UpdateTrendlines", чтобы проверить существующие линии тренда, удалить пробитые или утратившие актуальность и сформировать торговые сигналы, если касания цены обнаруживаются в пределах заданного допуска касания. Наконец, вызовем функцию "FindAndDrawTrendlines" с параметром true для создания линий тренда поддержки, гарантируя, что новые линии тренда будут нарисованы только в том случае, если активной линии тренда типа поддержки не существует. После компиляции получаем следующий результат.

CONFIRMED SUPPORT TRENDLINE

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

//--- other ontick functions

FindAndDrawTrendlines(false);                   //--- Find/draw resistance trendlines

//---

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

RESISTANCE TRENDLINE

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

COMBINED OUTCOME

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


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

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

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

График

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

REPORT


Заключение

В заключение отметим, что мы разработали программу стратегии торговли по линиям тренда на MQL5, использующую аппроксимацию методом наименьших квадратов для определения надежных линий тренда поддержки и сопротивления, формирования автоматических сигналов на покупку и продажу с помощью визуальных средств, таких как стрелки и метки. Благодаря использованию модульных компонентов, таких как структура "TrendlineInfo", и функций, таких как "FindAndDrawTrendlines", обеспечивается дисциплинированный подход к торговле на основе тренда, который можно усовершенствовать, скорректировав его параметры.

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

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

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

Прикрепленные файлы |
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Тестер стратегий для Python и MetaTrader 5 (Часть 1): Торговый симулятор Тестер стратегий для Python и MetaTrader 5 (Часть 1): Торговый симулятор
Модуль MetaTrader 5 для Python, предоставляет удобный способ открывать сделки в приложении MetaTrader 5 с помощью Python, но у него есть серьезная проблема: в нем нет возможностей тестера стратегий, присутствующих в приложении MetaTrader 5. В этой серии статей мы создадим фреймворк для бэктестинга ваших торговых стратегий в средах Python.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Разработка инструментария для анализа Price Action (Часть 40): ДНК-профиль рынка Разработка инструментария для анализа Price Action (Часть 40): ДНК-профиль рынка
В этой статье рассматривается уникальный профиль каждой валютной пары через призму исторической динамики ее цены. Вдохновляясь концепцией генетической ДНК, которая задает уникальный генетический код каждого живого существа, мы применяем аналогичный подход к рынкам, рассматривая динамику цены как "ДНК" каждой валютной пары. Анализируя такие структурные характеристики, как волатильность, свинги, откаты, всплески и особенности сессий, инструмент выявляет базовый профиль, который отличает одну пару от другой. Этот подход дает более глубокое понимание поведения рынка и помогает трейдерам системно соотносить стратегии с естественными склонностями каждого инструмента.