Знакомство с языком MQL5 (Часть 24): Создание советника для торговли по графическим объектам
Введение
И снова приветствуем вас в Части 24 серии "Знакомство с языком MQL5"! В этой статье мы сделаем значительный шаг вперед, объединив ручной анализ графиков с автоматизированным исполнением торговых операций. А точнее, мы создадим советника, который будет совершать сделки непосредственно на основе графических объектов, таких как прямоугольники, трендовые линии и линии поддержки и сопротивления.
Этот метод заполняет пробел между автоматизированной и ручной торговлей. Рисуя элементы на вашем графике, вы теперь сможете визуально управлять вашим советником, а не только полагаться на заранее заданные технические параметры. После обнаружения этих объектов советник будет отслеживать изменения цены и автоматически инициировать сделки всякий раз, когда цена взаимодействует с ними.
Также в этой статье вы научитесь обнаруживать графические объекты программно и использовать их координаты для расчета точек входа и выхода при торговле. Кроме того, мы настроим советник так, чтобы он реагировал на изменения динамически, то есть он будет мгновенно адаптироваться к любым изменениям, которые вы вносите в элементы на вашем графике.
Как работает советник
Мы создадим советник, который распознает развороты тренда в стратегических зонах, используя графические объекты. Мы создадим советник поддержки и сопротивления, в котором пользователь вручную отмечает возможные места разворота на графике с помощью объекта "прямоугольник". Если вы разместите прямоугольник выше цены, обозначив зону сопротивления, советник будет следить за этой областью и ждать, когда цена достигнет ее.
После достижения ценой указанной зоны, советник, прежде чем совершать сделку, будет ждать медвежьей смены характера, чтобы подтвердить формирование потенциального разворота. Советник автоматически инициирует сделку на продажу, если цена совершает отскок внутри прямоугольника, не пробивая его верхнюю границу, и если выявлена медвежья смена характера.
Когда рынок начинает формировать более низкие максимумы и более низкие минимумы (медвежья структура) вместо более высоких максимумов и более высоких минимумов (бычья структура), говорят, что он претерпел медвежью смену характера. Чтобы определить ее, советник будет искать четыре значимых движения цены:
- минимум;
- за которым следует максимум;
- затем более высокий минимум;
- затем более высокий максимум;
- и, наконец, пробой ниже предшествующего более высокого минимума.
Бычья структура была нарушена, и произошла смена характера на медвежий, что подтверждается последним пробоем ниже более высокого минимума. Советник размещает ордер на продажу, когда такое изменение структуры происходит внутри прямоугольника сопротивления или вблизи него, что указывает на высокую вероятность разворота.

Зона поддержки следует той же логике. Чтобы обозначить поддержку, пользователь рисует прямоугольник ниже цены, обозначая потенциальное место для бычьего разворота. Советник, прежде чем совершать какие-либо сделки, терпеливо ждет бычьей смены характера, как только цена достигнет этой зоны. Для выявления бычьего изменения характера также можно использовать четыре свинга:
- максимум;
- за которым следует минимум;
- затем более низкий максимум;
- тогда более низкий минимум;
- и, наконец, пробой выше предшествующего более низкого максимума.
Структура рынка изменилась с медвежьей на бычью, что подтверждается последним пробоем выше более низкого максимума. Это указывает на вероятный бычий разворот, и советник автоматически исполнит покупку, если это произойдет внутри прямоугольника поддержки или вблизи него.

Выявление графических объектов
Выявление графических объектов является ключевым компонентом этого проекта поскольку определяет, как советник будет выбирать, какой из прямоугольников использовать в качестве поддержки или сопротивления. Советник может выявлять графические объекты, которые он разработал самостоятельно, но он не может выявлять объекты, которые пользователь нарисовал вручную. Советник должен уметь точно определять, какие из множества элементов (прямоугольников, трендовых линий, текстовых меток или форм), которые часто присутствуют на графике, ему использовать в своем анализе.
Мы используем функцию ObjectCreate() при написании кода для создания графических объектов. С помощью этой функции советник может рисовать различные объекты на графике. Но важно понимать, что вы можете получить доступ к свойствам объекта или изменить их в любой момент после его создания. Эти атрибуты могут включать цвет, стиль, видимость, координаты времени и цены, и так далее.
Для более подробного объяснения того, как работает функция ObjectCreate(), можете обратиться к Части 9 этой серии, где я описывал, как создавать графические объекты в вашем коде.
Наиболее важный аспект заключается в том, что в языке MQL5 имя объекта содержит всю информацию о нем. Вы можете получить или изменить информацию об объекте, просто узнав его имя, независимо от того, был ли он создан вручную пользователем или автоматически советником. Запросить верхние и нижние ценовые уровни или проверить временные границы объекта "прямоугольник", используемого в качестве сопротивления, достаточно просто, если вы знаете его имя.
По этой причине имя объекта играет роль своеобразной связи между логикой советника и графиком пользователя. Это прокладывает путь для точных автоматизированных решений, позволяя советнику считывать точные координаты нарисованной зоны и использовать эту информацию для определения момента, когда цена входит в область.
Проблема заключается в том, как включить имя объекта, которое мы еще не знаем, в наш код. Представьте: программа была скомпилирована и начала работать работает еще до того, как пользователь нарисовал объект на графике. Следовательно как советник может заранее знать, какое имя объекта использовать?
Имя объекта можно получить прямо из графика простым и эффективным способом, даже после отрисовки объекта. Эта техника позволяет советнику динамически обнаруживать создаваемые пользователем объекты и не требует предварительного жесткого кодирования имени. Давайте обсудим, как это работает.
Пример:input string h_line = ""; // Horizontal Line ulong chart_id = ChartID(); double line_price; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- line_price = ObjectGetDouble(chart_id,h_line,OBJPROP_PRICE,0); Comment(line_price); }
Пояснение:
Первым этапом в этом подходе является ожидание размещения пользователем горизонтальной линии на графике. В результате мы предусмотрели строковый входной параметр h_line. С помощью этого входного параметра пользователь сможет вручную ввести точное название горизонтальной линии, которую он создал на графике, либо скопировать его. Тогда советник будет знать, с каким конкретным объектом работать.
После этого мы вызываем функцию ChartID(), чтобы получить идентификатор графика. Когда в MetaTrader 5 открыто несколько графиков, это гарантирует, что советник взаимодействует с соответствующим графиком. Чтобы получить ценовой уровень объекта, имя которого было указано во входном параметре, мы воспользуемся функцией ObjectGetDouble() внутри функции OnTick(). Параметры, передаваемые в эту функцию, включают идентификатор графика, имя объекта, свойство, к которому мы хотим получить доступ (OBJPROP_PRICE), и значение индекса. Индекс для горизонтальной линии имеет значение 0.
Чтобы убедиться, что советник правильно нашел созданный вручную объект и извлек из него данные, функция Comment() в конце отображает значение ценового уровня объекта непосредственно на графике. Этот метод делает советник универсальным и простым в использовании, позволяя ему динамически работать с любым именем объекта, которое предоставляет пользователь.
Вывод:

Чтобы определить имя объекта, который вы нарисовали на графике, просто щелкните по нему правой кнопкой мыши и выберите "Свойства" в появившемся меню. В окне свойств появится поле "Имя". Имя необходимо скопировать в точности, как оно указано, и вставить во входные настройки советника. При желании вы можете переименовать его во что-то более запоминающееся, например, ResistanceZone или SupportArea.

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

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

В то время как вертикальная линия содержит только информацию о времени, так как она представляет собой определенную временную точку.

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

Получив эти координаты, советник сможет определить точное местоположение прямоугольника на графике. В результате он сможет определять временные рамки и ценовой диапазон нарисованной пользователем области поддержки или сопротивления, что имеет решающее значение для определения момента, когда рынок переходит в эту зону, а также того, развивается ли возможный торговый сетап.
Прямоугольник определяется двумя точками, представляющими собой противоположные углы, каждая из которых имеет координаты времени и цены. Эти точки могут менять роли в зависимости от того, как нарисован прямоугольник. Из-за этой гибкости советник не должен предполагать, что один якорь представляет верхнюю или нижнюю часть зоны при получении данных из прямоугольника. Чтобы правильно выявить область поддержки или сопротивления, необходимо проверить обе координаты и использовать более низкую цену в качестве нижней границы, а более высокую цену в качестве верхней границы.
Пример:
input string reistance = ""; // Resistance Object Name ulong chart_id = ChartID(); double res_anchor1_price; double res_anchor2_price; double res_max_price; double res_min_price; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- res_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,0),_Digits); res_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,1),_Digits); res_max_price = NormalizeDouble(MathMax(res_anchor1_price,res_anchor2_price),_Digits); res_min_price = NormalizeDouble(MathMin(res_anchor1_price,res_anchor2_price),_Digits); Comment("Anchor 1 Price: ",res_anchor1_price,"\nAnchor 2 Price: ",res_anchor2_price, "\n\nMax Resistance Price: ",res_max_price,"\nMin Resistance Price: ",res_min_price); }
Вывод:

Пояснение:
Имя объекта предоставляется как входной параметр. Это позволяет вам вручную скопировать и вставить имя объекта "прямоугольник" с графика в настройки ввода. Затем ценовые уровни двух якорей в прямоугольнике сохраняются в две переменные. Якоря 1 и 2 являются двумя углами каждого прямоугольника, и каждый угол имеет координаты как для времени, так и для цены. Советник получает цену обоих якорей, поэтому зона сопротивления может быть определена с использованием только ценовых значений.
Советник считывает ценовые значения двух якорей прямоугольника с помощью функции ObjectGetDouble. Точка 1 обозначается в этой функции индексным значением 0, а точка 2 – индексным значением 1. Оба якоря имеют свои собственные временные и ценовые координаты, и каждый якорь представляет собой угол прямоугольника. Советник получает только ценовые данные для каждого якоря, потому что для текущего проекта нас интересуют лишь ценовые уровни.
После получения этих значений, советник определяет, какое из них выше, а какое ниже. Верхняя и нижняя границы зоны сопротивления представлены ценами максимума и минимума соответственно.
Благодаря этой логике советник будет точно определять границы зоны независимо от того, рисует ли пользователь прямоугольник сверху вниз или снизу вверх. Эти две границы будут использоваться для отслеживания движения цены в рамках проекта. Перед тем как исполнить сделку на продажу, советник будет ждать медвежьего сдвига в поведении рынка по мере приближения к этой зоне сопротивления.
Таким образом, советник может определить, какой якорь представляет собой начало, а какой указывает на конец прямоугольника, сравнивая значения времени двух якорей. Этот шаг помогает советнику корректно интерпретировать горизонтальное положение прямоугольника на графике и гарантирует, что советник будет реагировать только тогда, когда текущее рыночное время находится внутри действительного диапазона нарисованной зоны.
Пример:input string reistance = ""; // Resistance Object Name ulong chart_id = ChartID(); double res_anchor1_price; double res_anchor2_price; double res_max_price; double res_min_price; long res_anchor1_time; long res_anchor2_time; datetime res_start_time; datetime res_end_time; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- res_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,0),_Digits); res_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,1),_Digits); res_max_price = NormalizeDouble(MathMax(res_anchor1_price,res_anchor2_price),_Digits); res_min_price = NormalizeDouble(MathMin(res_anchor1_price,res_anchor2_price),_Digits); res_anchor1_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,0); res_anchor2_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,1); res_start_time = (datetime)MathMin(res_anchor1_time,res_anchor2_time); res_end_time = (datetime)MathMax(res_anchor1_time,res_anchor2_time); Comment("RESISTANCE START TIME: ",res_start_time,"\nRESISTANCE END TIME: ",res_end_time); }
Вывод:

Эта часть кода получает временные координаты прямоугольника сопротивления, определив первую и вторую точки (0 и 1). После этого он сравнивает эти точки, определяя какая из них наступила раньше, и устанавливает более раннюю точку в качестве времени начала, а более позднюю – в качестве времени окончания. Это гарантирует, что советник будет точно определять временной диапазон зоны сопротивления, независимо от того как был нарисован прямоугольник.
Медвежья смена характера
Следующим шагом является нахождение медвежьей смены характера, которая указывает на потенциальный разворот от зоны сопротивления. Чтобы подтвердить это, советник будет отслеживать изменения цены, чтобы выявить, происходит ли заметная структурная корректировка. В частности, он проверит, что зона сопротивления, нарисованная пользователем, содержит наибольший максимум рынка. Это будет гарантией того, что цена действительно взаимодействовала с этой зоной, и что она действительна. Медвежья смена характера подтверждается, когда рынок устанавливает этот максимум в зоне сопротивления, а затем совершает пробой ниже предшествующего более высокого минимума, что указывает на потенциальный нисходящий разворот из этой зоны.
Пример:
input string reistance = ""; // Resistance Object Name input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; // TIME-FRAME ulong chart_id = ChartID(); double res_anchor1_price; double res_anchor2_price; double res_max_price; double res_min_price; long res_anchor1_time; long res_anchor2_time; datetime res_start_time; datetime res_end_time; int res_total_bars; double res_close[]; double res_open[]; double res_high[]; double res_low[]; datetime res_time[]; double high; datetime high_time; double low; datetime low_time; double higher_low; datetime higher_low_time; double higher_high; datetime higher_high_time; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(res_close,true); ArraySetAsSeries(res_open,true); ArraySetAsSeries(res_high,true); ArraySetAsSeries(res_low,true); ArraySetAsSeries(res_time,true); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- res_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,0),_Digits); res_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,reistance,OBJPROP_PRICE,1),_Digits); res_max_price = NormalizeDouble(MathMax(res_anchor1_price,res_anchor2_price),_Digits); res_min_price = NormalizeDouble(MathMin(res_anchor1_price,res_anchor2_price),_Digits); res_anchor1_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,0); res_anchor2_time = ObjectGetInteger(chart_id,reistance,OBJPROP_TIME,1); res_start_time = (datetime)MathMin(res_anchor1_time,res_anchor2_time); res_end_time = (datetime)MathMax(res_anchor1_time,res_anchor2_time); res_total_bars = Bars(_Symbol,timeframe,TimeCurrent(),res_start_time); CopyOpen(_Symbol, timeframe, TimeCurrent(), res_start_time, res_open); CopyClose(_Symbol, timeframe, TimeCurrent(), res_start_time, res_close); CopyLow(_Symbol, timeframe, TimeCurrent(), res_start_time, res_low); CopyHigh(_Symbol, timeframe, TimeCurrent(), res_start_time, res_high); CopyTime(_Symbol, timeframe, TimeCurrent(), res_start_time, res_time); for(int i = 4; i < res_total_bars-3; i++) { if(IsSwingHigh(res_high, i, 3)) { higher_high = res_high[i]; higher_high_time = res_time[i]; for(int j = i; j < res_total_bars-3; j++) { if(IsSwingLow(res_low,j,3)) { higher_low = res_low[j]; higher_low_time = res_time[j]; for(int k = j; k < res_total_bars-3; k++) { if(IsSwingHigh(res_high, k, 3)) { high = res_high[k]; high_time = res_time[k]; for(int l = k; l < res_total_bars-3; l++) { if(IsSwingLow(res_low,l,3)) { // ObjectCreate(chart_id,"kk",OBJ_VLINE,0,res_time[l],0); ObjectDelete(chart_id,"kk"); low = res_low[l]; low_time = res_time[l]; for(int m = i; m > 0; m--) { if(res_close[m] < higher_low && res_open[m] > higher_low) { if(higher_low < higher_high && high > higher_low && high < higher_high && low < high && low < higher_low) { ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low); ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high); ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high,low_time,low); ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"Cross Line",OBJ_TREND,0,higher_low_time,higher_low,res_time[m],higher_low); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_WIDTH,2); } break; } } break; } } break; } } break; } } break; } } } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING LOW | //+------------------------------------------------------------------+ bool IsSwingLow(const double &low_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i]) return false; } return true; } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING HIGH | //+------------------------------------------------------------------+ bool IsSwingHigh(const double &high_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i]) return false; // If the current high is not the highest, return false. } return true; }
Вывод:

Пояснение:
Пользователь может сначала выбрать таймфрейм графика, который будет анализировать советник, используя входной параметр timeframe. Вы можете изменить его, чтобы использовать любой поддерживаемый период, но по умолчанию используется текущее окно графика. Это важно, поскольку таймфрейм используется для всех последующих методов выявления структуры рынка, включая временные метки, обнаружение свингов и подсчет баров. Правильный выбор периода – это архитектурное решение, позволяющее найти баланс между уровнем рыночного шума и скоростью реакции алгоритма.
В процессе инициализации массивы, которые будут хранить исторические данные, настраиваются в качестве временных рядов. Когда они настроены в режиме временных рядов, индексация идет в обратном порядке: чем больше индекс, тем старше бар, при этом самый свежий бар хранится по нулевому индексу. При поиске недавних свингов благодаря этому порядку можно легко пройти по массиву от новейших свечей к старейшим. Кроме того, логика ваших циклов может исходить из того, что нулевой индекс – это последний завершенный бар, так как это соответствует стандартному представлению временных рядов в языке MQL5.
Затем программа определяет количество баров, которые отделяют начало прямоугольника от текущего времени. Это число баров определяет размер окна для копирования. После того, как советнику становится известно это число, он дублирует массивы со значениями цен открытия, максимума, минимума, закрытия и времени для каждого бара в этом диапазоне. Набор данных, который анализирует советник, состоит из этих дублированных массивов. Ограничивая данные временной продолжительностью прямоугольника, советник учитывает только ценовое действие, которое действительно взаимодействовало с зоной, которую нарисовал пользователь.
Для выявления реальных точек разворота советник использует две вспомогательные функции, выявляющие минимумы и максимумы свинга. Каждая из этих функций осуществляет сравнение некоторого количества свечей слева и справа от свечи-кандидата. С обеих сторон функция минимума свинга возвращает true только в том случае, когда свеча-кандидат установила минимум ниже указанных соседей. Функция максимума свинга возвращает true, если свеча-кандидат установила максимум выше ее соседей с обеих сторон. В дополнение к подтверждению того, что наблюдаемые свинги действительно являются локальными опорными точками, а не случайным шумом, использование окна проверки с обеих сторон помогает избежать ложных срабатываний, вызванных изолированными тиками.
Когда мы соединяем все это, процесс работает следующим образом. Чтобы выбрать свечу-кандидата на максимум свинга, советник сначала анализирует дублированные массивы цен. После выявления максимума свинга, он продолжает искать следующий за ним минимум свинга, который становится кандидатом на более высокий минимум. Затем он ищет следующий за ним минимум свинга, а затем – последующий максимум свинга. После того, как эти четыре точки свинга образуют ожидаемый паттерн, советник ищет явный пробой, который будет заключаться в закрытии свечи ниже ранее определенного более высокого минимума. Такое приближение рассматривается как подтверждение смены характера.
Советник затем подтверждает корреляции между свингами, например, что более высокий минимум действительно ниже более высокого максимума, и что последующий максимум и минимум расположены таким образом, который соответствует ожидаемой структуре.
Чтобы проверить точность медвежьей смены характера (ChoCH), необходимо выполнить две критические проверки. Следующий шаг – установить, что ни один бар не пробил максимальную цену сопротивления с момента начала прямоугольника. Если такой пробой состоялся, сопротивление больше не является действительным. Сначала мы должны пройтись по всем барам, собрать все максимумы от начала прямоугольника до текущего бара, а затем определить, какой максимум является наибольшим.
Пример:int max_high_index; double max_high;
if(higher_low < higher_high && high > higher_low && high < higher_high && low < high && low < higher_low) { ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low); ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high); ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high,low_time,low); ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"Cross Line",OBJ_TREND,0,higher_low_time,higher_low,res_time[m],higher_low); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_WIDTH,2); max_high_index = ArrayMaximum(res_high,0,res_total_bars); max_high = res_high[max_high_index]; Comment(max_high); }
Вывод:

Пояснение:
Максимальная цена в выбранном диапазоне и ее расположение внутри массива определяются двумя переменными. Реальная цена, соответствующая этому индексу, сохраняется в одной переменной, а во второй сохраняется индекс (позиция) наибольшего максимума. После нахождения индекса максимальной цены в массиве приложение извлекает соответствующее значение цены.
Теперь действительность зоны сопротивления можно проверить с помощью двух условий. Первое требование заключается в том, что ни одна свеча не пробила зону сопротивления (наибольший максимум меньше максимальной цены сопротивления). Второе требование заключается в том, что наибольший максимум в структуре смены характера должен быть выше минимальной цены сопротивления, но ниже максимальной цены сопротивления. Это указывает на действительную точку отскока, где может произойти потенциальная медвежья смена характера, поскольку рынок действительно достиг зоны сопротивления.
Пример:if(higher_low < higher_high && high > higher_low && high < higher_high && low < high && low < higher_low) { ObjectCreate(chart_id,"HHHL",OBJ_TREND,0,higher_high_time,higher_high,higher_low_time,higher_low); ObjectSetInteger(chart_id,"HHHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HHHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HLH",OBJ_TREND,0,higher_low_time,higher_low,high_time,high); ObjectSetInteger(chart_id,"HLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"HL",OBJ_TREND,0,high_time,high,low_time,low); ObjectSetInteger(chart_id,"HL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"HL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"Cross Line",OBJ_TREND,0,higher_low_time,higher_low,res_time[m],higher_low); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"Cross Line",OBJPROP_WIDTH,2); max_high_index = ArrayMaximum(res_high,0,res_total_bars); max_high = res_high[max_high_index]; if(max_high < res_max_price && higher_high > res_min_price && higher_high < res_max_price) { } }
Исполнение сделок
Исполнение сделок происходит после того, как мы убедимся, что более высокий максимум находится внутри зоны сопротивления, и что выполнены все предварительные условия. Это указывает на то, что действительно произошла медвежья смена характера, и рынок показал признаки отскока от области сопротивления. Теперь советник в соответствии с указаниями стратегии сможет инициировать продажу или выполнить любое другое заранее определенное торговое действие. Тейк-профит (TP) будет определен в соответствии с заданным пользователем соотношением риска к прибыли, а стоп-лосс (SL) будет установлен на максимуме смены характера вместо точки более высокого максимума.
Пример:
#include <Trade/Trade.mqh> CTrade trade; int MagicNumber = 533915; // Unique Number double lot_size = 0.2; // Lot Size
double ask_price; double take_profit; datetime lastTradeBarTime = 0;
if(max_high < res_max_price && higher_high > res_min_price && higher_high < res_max_price) { if(res_time[1] == res_time[m] && currentBarTime != lastTradeBarTime) { take_profit = MathAbs(ask_price - ((high - ask_price) * RRR)); trade.Sell(lot_size,_Symbol,ask_price,high,take_profit); lastTradeBarTime = currentBarTime; } }
Вывод:

Пояснение:
Эта часть программы отвечает за планирование и осуществление сделки, когда все торговые требования выполнены. Первым делом в программу включена торговая библиотека, которая предлагает ресурсы, необходимые для проведения торговых операций. Чтобы позволить советнику инициировать, изменять и завершать сделки, создается экземпляр торгового класса. Чтобы предотвратить вмешательство в сделки других советников или ручные позиции, каждая сделка, совершенная этим конкретным советником, обозначается специальным магическим числом. Объем обмена в рамках ордера определяется размером лота.
Чтобы предотвратить открытие нескольких сделок в пределах одной свечи, программа также задает переменные для цены Ask (которая является текущей ценой, по которой может быть исполнен ордер на продажу), уровня тейк-профита и переменной, которая хранит время предыдущей проведенной транзакции. После этого она записывает время текущего бара и получает текущую цену Ask для символа.
Советник проверяет два условия перед совершением сделки: во-первых, соответствует ли текущее время точной временной метке подтверждения зоны сопротивления; и во-вторых, отличается ли время текущего бара от времени бара предыдущей сделки (чтобы избежать повторных входов). Если эти требования выполнены, программа определяет уровень тейк-профита на основе заданного пользователем соотношения риска к прибыли, и инициирует сделку на продажу. Тейк-профит определяется с использованием вычисленного соотношения, а стоп-лосс устанавливается на пике смены характера.
Бычья смена характера в зоне поддержки
Поскольку большинство рассуждений, которые мы использовали для выявления медвежьей смены характера в зоне сопротивления, также применимы и для данной темы, мы коснемся ее лишь кратко. Чтобы избежать недоразумений или проблем, необходимо обсудить несколько ключевых деталей. Поэтому в этой части мы сразу перейдем к сути и сосредоточимся в основном на фундаментальных отличиях.
Пример:
input string support = ""; // Support Object Name
double sup_anchor1_price; double sup_anchor2_price; double sup_max_price; double sup_min_price; long sup_anchor1_time; long sup_anchor2_time; datetime sup_start_time; datetime sup_end_time; int sup_total_bars; double sup_close[]; double sup_open[]; double sup_high[]; double sup_low[]; datetime sup_time[]; double lower_high; datetime lower_high_time; double lower_low; datetime lower_low_time; int min_low_index; double min_low; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(res_close,true); ArraySetAsSeries(res_open,true); ArraySetAsSeries(res_high,true); ArraySetAsSeries(res_low,true); ArraySetAsSeries(res_time,true); ArraySetAsSeries(sup_close,true); ArraySetAsSeries(sup_open,true); ArraySetAsSeries(sup_high,true); ArraySetAsSeries(sup_low,true); ArraySetAsSeries(sup_time,true); //--- return(INIT_SUCCEEDED); }
sup_anchor1_price = NormalizeDouble(ObjectGetDouble(chart_id,support,OBJPROP_PRICE,0),_Digits); sup_anchor2_price = NormalizeDouble(ObjectGetDouble(chart_id,support,OBJPROP_PRICE,1),_Digits); sup_max_price = NormalizeDouble(MathMax(sup_anchor1_price,sup_anchor2_price),_Digits); sup_min_price = NormalizeDouble(MathMin(sup_anchor1_price,sup_anchor2_price),_Digits); sup_anchor1_time = ObjectGetInteger(chart_id,support,OBJPROP_TIME,0); sup_anchor2_time = ObjectGetInteger(chart_id,support,OBJPROP_TIME,1); sup_start_time = (datetime)MathMin(sup_anchor1_time,sup_anchor2_time); sup_end_time = (datetime)MathMax(sup_anchor1_time,sup_anchor2_time); sup_total_bars = Bars(_Symbol,timeframe,TimeCurrent(),sup_start_time); CopyOpen(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_open); CopyClose(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_close); CopyLow(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_low); CopyHigh(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_high); CopyTime(_Symbol, timeframe, TimeCurrent(), sup_start_time, sup_time); for(int i = 4; i < sup_total_bars-3; i++) { if(IsSwingLow(sup_low, i, 3)) { lower_low = sup_low[i]; lower_low_time = sup_time[i]; for(int j = i; j < sup_total_bars-3; j++) { if(IsSwingHigh(sup_high,j,3)) { lower_high = sup_high[j]; lower_high_time = sup_time[j]; for(int k = j; k < sup_total_bars-3; k++) { if(IsSwingLow(sup_low, k, 3)) { low = sup_low[k]; low_time = sup_time[k]; for(int l = k; l < sup_total_bars-3; l++) { if(IsSwingHigh(sup_high,l,3)) { high = sup_high[l]; high_time = sup_time[l]; for(int m = i; m > 0; m--) { if(sup_close[m] > lower_high && sup_open[m] < lower_high) { if(lower_high > lower_low && low < lower_high && low > lower_low && high > low && high > lower_high) { ObjectCreate(chart_id,"LLLH",OBJ_TREND,0,lower_low_time,lower_low,lower_high_time,lower_high); ObjectSetInteger(chart_id,"LLLH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LLLH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"LHL",OBJ_TREND,0,lower_high_time,lower_high,low_time,low); ObjectSetInteger(chart_id,"LHL",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LHL",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"LH",OBJ_TREND,0,low_time,low,high_time,high); ObjectSetInteger(chart_id,"LH",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"LH",OBJPROP_WIDTH,2); ObjectCreate(chart_id,"S Cross Line",OBJ_TREND,0,lower_high_time,lower_high,sup_time[m],lower_high); ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,"S Cross Line",OBJPROP_WIDTH,2); min_low_index = ArrayMinimum(sup_low,0,sup_total_bars); min_low = sup_low[min_low_index]; if(min_low > sup_min_price && lower_low < sup_max_price && lower_low > sup_min_price) { if(sup_time[1] == sup_time[m] && currentBarTime != lastTradeBarTime) { take_profit = MathAbs(ask_price + ((ask_price - low) * RRR)); trade.Buy(lot_size,_Symbol,ask_price,low,take_profit); lastTradeBarTime = currentBarTime; } } } break; } } break; } } break; } } break; } } break; } }
Пояснение:
Логика, используемая в этом разделе программы, почти идентична логике зоны сопротивления. Основное отличие заключается в направлении цены и структуре рынка. Мы рассчитываем на бычью смену характера в зоне поддержки, то есть на разворот рынка вверх с отскоком от ценовой области ниже.
Для хранения свечных данных, особенно для зоны поддержки, было объявлено несколько отдельных массивов, что является важным аспектом кода. Это дополнительные массивы свечных данных. Поскольку время начала прямоугольника поддержки зачастую отличается от времени начала прямоугольника сопротивления, мы используем отдельные массивы. Отдельные массивы гарантируют, что анализируемые данные будут точно соответствовать выбранной зоне поддержки на графике. В результате советник может анализировать каждый уровень независимо и избегать наложения данных или путаницы между двумя зонам.
Примечание
Стратегия в этой статье полностью проектно-ориентированная и предназначена для обучения читателей языку MQL5 через практическое применение в реальном мире. Этот метод не гарантирует получение прибыли в реальной торговле.
Заключение
В этой статье мы объединили автоматическое выполнение сделок с ручным анализом графиков, чтобы создать советник, который реагирует на графические объекты, такие как зоны поддержки и сопротивления. Чтобы преодолеть разрыв между автоматизированной и дискреционной торговлей, вы научились извлекать и интерпретировать данные графика, чтобы исполнять сделки автоматически.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19912
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От новичка до эксперта: Торговля с временной фильтрацией
Функции Уолша в современном трейдинге
Нейросети в трейдинге: Пространственно-управляемая агрегация рыночных событий (Основные модули)
Нейросети в трейдинге: Пространственно-управляемая агрегация рыночных событий (STFlow)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования