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

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

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

Реализация средствами 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 ×[], 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 ×[], 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", получая линию наилучшего соответствия для линии тренда на основе точек входа. Если вам интересна формула, ознакомьтесь с ней ниже.

Это обеспечит математически точное расположение линий тренда для получения надежных торговых сигналов. Теперь определим функцию для управления линиями тренда.
//+------------------------------------------------------------------+ //| 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.

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

На изображении видно, что мы находим, анализируем, строим и совершаем сделки по линии тренда при её касании. Просроченные линии также успешно удаляются из массива хранения. Мы можем добиться того же самого и для линий тренда сопротивления, вызвав ту же функцию, что и для линий поддержки, но указав во входном параметре значение false.
//--- other ontick functions FindAndDrawTrendlines(false); //--- Find/draw resistance trendlines //---
После добавления этого вызова и компиляции получаем следующий результат.

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

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

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

Заключение
В заключение отметим, что мы разработали программу стратегии торговли по линиям тренда на MQL5, использующую аппроксимацию методом наименьших квадратов для определения надежных линий тренда поддержки и сопротивления, формирования автоматических сигналов на покупку и продажу с помощью визуальных средств, таких как стрелки и метки. Благодаря использованию модульных компонентов, таких как структура "TrendlineInfo", и функций, таких как "FindAndDrawTrendlines", обеспечивается дисциплинированный подход к торговле на основе тренда, который можно усовершенствовать, скорректировав его параметры.
Отказ от ответственности: Содержание настоящей статьи предназначено только для целей обучения. Торговля сопряжена со значительными финансовыми рисками, а волатильность рынка может привести к убыткам. Тщательное тестирование на истории и управление рисками имеют решающее значение перед внедрением этой программы в реальных условиях рынка.
Используя представленные концепции и методы реализации, можно адаптировать эту систему линий тренда к своему стилю торговли, улучшая свои алгоритмические стратегии. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19077
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Тестер стратегий для Python и MetaTrader 5 (Часть 1): Торговый симулятор
Разработка инструментария для анализа Price Action (Часть 40): ДНК-профиль рынка
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования