Знакомство с языком MQL5 (Часть 15): Руководство для начинающих по созданию пользовательских индикаторов (IV)
Введение
И снова добро пожаловать в серию "Знакомство с языком MQL5"! Вы обнаружите, что эта статья построена на идеях и техниках, которые мы уже обсуждали в предыдущих статьях. Поскольку мы будем использовать многое из того, что узнали до сих пор, по сути эта часть будет скорее продолжением, чем новым началом. На данный момент у вас должно быть твердое понимание основ языка MQL5, и в этой статье мы сделаем шаг вперед, объединив все эти знания для разработки более интересного пользовательского индикатора.
Ваш уровень определяется проектами, которые вы разработали на языке MQL5, именно поэтому статьи данной серии всегда основываются на проектном подходе. Это самый полезный метод для обучения и самосовершенствования. В этой части серии мы создадим индикатор, который сможет распознавать тренды, использовать пробой структуры и генерировать сигналы на покупку и продажу. Точка входа, стоп-лосс и несколько уровней тейк-профита включены в эти сигналы, предоставляя вам комплексную стратегию, которую вы можете тестировать и развивать. В этой статье вы узнаете, как разрабатывать пользовательские индикаторы на языке MQL5, используя концепции ценового действия. Чтобы создать трендовую стратегию, вы научитесь распознавать важные рыночные структуры, такие как более высокие максимумы, более низкие минимумы, а также более низкие максимумы и более низкие минимумы.
В этой статье вы узнаете:
- Как создать индикатор ценового действия.
- Выявление важных точек, таких как минимум (L), максимум (H), более высокий минимум (HL), более высокий максимум (HH), более низкий минимум (LL) и более низкий максимум (LH), для понимания структуры бычьего и медвежьего тренда.
- Отрисовка зоны премии и дисконта на основе ключевых трендовых пунктов и отметка уровня коррекции 50%.
- Как применять соотношение риска и вознаграждения при расчете потенциальных целевых уровней прибыли в сетапе бычьего тренда.
- Расчет и отметка точки входа, стоп-лосса (SL) и нескольких уровней тейк-профита (TP) на основе структуры тренда.
1. Настройка проекта
1.1. Как работает индикатор
Индикатор будет выявлять минимум, максимум, более высокий минимум и более высокий максимум для указания на восходящий тренд для сигналов на покупку. Затем будет определен уровень коррекции 50% между более высоким минимумом и более высоким максимумом. Пробой структуры выше более высокого максимума инициирует вход, а уровень коррекции 50% будет служить уровнем стоп-лосса. Целью Take Profit 1 будет соотношение риска и вознаграждения 1:1, а целью Take Profit 2 будет соотношение 1:2.

Чтобы выявить нисходящий тренд для сигналов на продажу, индикатор сначала выявит максимум, минимум, более низкий максимум и более низкий минимум. Затем он вычислит коррекцию 50% между более низким максимумом и более низким минимумом. TP1 будет установлен при соотношении риска и вознаграждения 1:1, TP2 – при соотношении 1:2, уровень стоп-лосса будет выставлен на уровне 50%, а вход будет осуществляться при пробое более низкого минимума.

2. Построение индикатора ценового действия
Каждая торговая стратегия может быть преобразована в индикатор: если это не так, значит, она просто еще не была визуализирована. Все, что соответствует набору указаний, может быть описано кодом и отображено на графике, будь то спрос и предложение, ценовое действие или уровни поддержки и сопротивления. Именно здесь и приходит на помощь язык MQL5. Для алгоритмических трейдеров это один из величайших и самых простых языков программирования, позволяющий преобразовать любую торговую логику в полезный и визуально привлекательный инструмент. В этом разделе мы начнем разрабатывать индикатор, который анализирует движение цены, распознавая рыночную структуру, такую как максимумы, минимумы, более высокие максимумы и более низкие минимумы, а затем использует эти данные для создания информативных сигналов на покупку и продажу, которые включают уровни входа, стоп-лосса и тейк-профита.
В первой главе я изложил цель проекта и то, как индикатор будет выявлять тренды, обнаруживать пробои структуры и генерировать полные торговые сигналы, которые включают точку входа, стоп-лосс и тей-профит. Теперь, в этой главе, мы начнем применять все средства языка MQL5 на практике. Мы возьмем логику, которую обсуждали, и начнем реализовывать ее в коде шаг за шагом.
2.1. Выявление максимумов и минимумов
Нахождение максимумов свинга и минимумов свинга является первым этапом в создании нашего индикатора ценового действия. Эти значимые разворотные точки на рынке помогают выявлять структуру тренда. Сравнивая максимум или минимум текущей свечи с максимумом или минимумом предыдущих и последующих свечей, мы можем идентифицировать их на языке MQL5. На этом принципе будет основано выявление более высоких максимумов, более высоких минимумов, более низких максимумов и более низких минимумов: все это имеет решающее значение для выявления паттернов и пробоев структуры.
Примеры://+------------------------------------------------------------------+ //| FUNCTION FOR SWING LOWS | //+------------------------------------------------------------------+ bool IsSwingLow(const double &low[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(low[index] > low[index - i] || low[index] > low[index + i]) return false; } return true; } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING HIGHS | //+------------------------------------------------------------------+ bool IsSwingHigh(const double &high[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(high[index] < high[index - i] || high[index] < high[index + i]) return false; } return true; }
В предыдущей статье использовались две функции, которые позволяют выявлять максимумы и минимумы свинга.
2.2. Бычий тренд
Используя рыночную структуру, этот индикатор должен сначала подтвердить наличие восходящего тренда, прежде чем давать сигнал на покупку. Максимум, минимум, более высокий минимум и более высокий максимум – это основные ценовые точки, которые необходимо определить для этой цели. Этот паттерн обозначает бычий тренд, и это означает, что покупатели контролируют ситуацию, а рынок, вероятно, будет продолжать расти. После подтверждения этого паттерна индикатор будет готов сгенерировать соответствующий правилам сигнал на покупку.

Пример:
// CHART ID long chart_id = ChartID(); // Input parameters input int LookbackBars = 10; // Number of bars to look back/forward for swing points input int bars_check = 1000; // Number of bars to check for swing points input bool show_bullish = true; //Show Buy Signals // Variables for Bullish Market Structure double L; // Low: the starting low point in the up trend datetime L_time; // Time of the low string L_letter; // Label for the low point (e.g., "L") double H; // High: the first high after the low datetime H_time; // Time of the high string H_letter; // Label for the high point (e.g., "H") double HL; // Higher Low: the next low that is higher than the first low datetime HL_time; // Time of the higher low string HL_letter; // Label for the higher low point (e.g., "HL") double HH; // Higher High: the next high that is higher than the first high datetime HH_time; // Time of the higher high string HH_letter; // Label for the higher high point (e.g., "HH") //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(chart_id); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- if(show_bullish == true) // Check if the bullish trend is to be displayed { if(rates_total >= bars_check) // Ensure enough bars are available for analysis { // Loop through the price data starting from a certain point based on bars_check and LookbackBars for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { // Check if the current bar is a swing low if(IsSwingLow(low, i, LookbackBars)) { // Store the values for the swing low L = low[i]; L_time = time[i]; L_letter = StringFormat("Low%d", i); // Loop through further to find a swing high after the low for(int j = i; j < rates_total - LookbackBars; j++) { // Check if the current bar is a swing high and occurs after the identified swing low if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time) { // Store the values for the swing high H = high[j]; H_time = time[j]; H_letter = StringFormat("High%d", j); // Loop further to find a higher low after the swing high for(int k = j; k < rates_total - LookbackBars; k++) { // Check if the current bar is a swing low and occurs after the swing high if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time) { // Store the values for the higher low HL = low[k]; HL_time = time[k]; HL_letter = StringFormat("Higher Low%d", j); // Loop further to find a higher high after the higher low for(int l = j ; l < rates_total - LookbackBars; l++) { // Check if the current bar is a swing high and occurs after the higher low if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time) { // Store the values for the higher high HH = high[l]; HH_time = time[l]; HH_letter = StringFormat("Higher High%d", l); // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High if(L < H && HL < H && HL > L && HH > H) { // Create and display text objects for Low, High, Higher Low, and Higher High on the chart ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL); ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL"); ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH); ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH"); ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15); } break; // Exit the loop once the pattern is found } } break; // Exit the loop once the higher low is found } } break; // Exit the loop once the higher high is found } } } } } } //--- return value of prev_calculated for next call return(rates_total); }
Вывод:

Пояснение:
Входные параметры, которые задаются в начале кода, используются для анализа структуры рынка. Чтобы определить максимумы или минимумы свинга и гарантировать актуальность для текущего рынка, переменная LookbackBars указывает количество баров, которые оцениваются до и после текущего бара. Тем временем, bars_check контролирует такое общее количество баров ценовых данных, которое будет проверено, что позволяет скрипту сканировать до 1000 баров в поисках возможных бычьих паттернов.
Хотя это также потребует большей вычислительной мощности, большие значения для bars_check указывают на то, что алгоритм будет учитывать более широкий диапазон данных при поиске этих мест. Булева входная переменная show_bullish управляет тем, следует ли отображать сигналы на покупку (бычьи сигналы). Если show_bullish имеет значение true, приложение будет анализировать движение цены и определять бычью рыночную структуру на основе точек свинга. Даже если требования выполнены, скрипт не будет отображать или выделять какие-либо бычьи структуры, если данная переменная имеет значение false.
Проверка show_bullish == true является первым действием, которое происходит в рамках логики индикатора. Это гарантирует, что бычьи структуры будут выявляться, только когда вы хотите видеть сигналы на покупку. Затем, если критерии удовлетворены, программа проверяет, достаточно ли ценовых данных доступно для анализа. Для проверки этого используется условие if (rates_total >= bars_check). Чтобы предотвратить ошибки из-за недостаточности данных, анализ пропускается, если доступно меньше баров, чем необходимо. Затем скрипт проходит по ценовым данным, чтобы найти точки свинга, если данный критерий выполнен.
Внешний цикл начинается с i и ищет соответствующие правилам минимумы свинга, просматривая ценовые данные в обратном порядке от самой последней свечи. Функция IsSwingLow() определяет, является ли текущая свеча самой низкой в диапазоне, заданном LookbackBars, тем самым выявляя минимум свинга. Цена и время формирования минимума фиксируются в переменных L и L_time, как только минимум свинга будет выявлен. Это закладывает основу для последующей фазы процесса обнаружения бычьего паттерна. После определения минимума свинга программа ищет следующий за ним максимум свинга. С помощью функции IsSwingHigh() второй цикл с индексом j ищет максимум свинга в каждой следующей свече. Значения любого максимума свинга, который обнаруживается после минимума, записываются в H и H_time. Это создает начальный сегмент бычьей рыночной структуры, который состоит из минимума и максимума.
После максимума свинга третья цикл с индексом k ищет более высокий минимум. Скрипт снова задействует функцию IsSwingLow(), чтобы найти более высокий минимум, который определяется как минимум, превышающий начальный минимум L. Когда обнаруживается более высокий минимум, переменные HL и HL_time заполняются его значением цены и временем, соответственно. После определения этого более высокого минимума программа продолжает искать последующий более высокий максимум. Максимум свинга, который следует за более высоким минимумом, проверяется в четвертом цикле с индексом l. Если этот более высокий максимум будет обнаружен, его значения будут сохранены в HH и HH_time. Код определяет, соответствуют ли четыре критических точки – минимум, максимум, более высокий минимум и более высокий максимум – правилам законному бычьего паттерна после их нахождения. Первый минимум должен быть ниже первого максимума, более высокий минимум должен быть ниже первого максимума, более высокий минимум должен быть выше первого минимума, а более высокий максимум должен быть выше первого максимума. Эти критерии проверяются условием if (L < H && HL < H && HL > L && HH > H). Это подтверждает бычий тренд, обеспечивая, что паттерн соответствует ожидаемой последовательности более высоких максимумов и более высоких минимумов.
Затем, если все эти требования выполнены, программа создает и отображает текстовые объекты на графике, чтобы выделить выявленные точки. На графике точки будут показаны метками с соответствующим временем и ценой: минимум (L), максимум (H), более высокий минимум (HL) и более высокий максимум (HH). Функция ObjectCreate() используется для создания текстовых объектов, в то время как функции ObjectSetInteger() и ObjectSetString() используются для настройки их характеристик, включая размер шрифта и цвет. Благодаря явному указанию точек для удобства пользователь теперь сможет легко распознать бычью структуру на графике. Подводя итог, цель программы заключается в том, чтобы найти паттерн более высоких максимумов и более высоких минимумов в ценовых данных для оценки его на предмет соответствия бычьей рыночной структуре. Это достигается путем анализа точек свинга в заранее определенном диапазоне баров, записи соответствующей информации и определения того, демонстрирует ли структура соответствие признакам паттерна. Если паттерн будет подтвержден, пользователь сможет увидеть его на графике. Входные параметры, которые допускают модификацию в соответствии с предпочтениями пользователя, управляют всей процедурой.
2.2.1. Отрисовка уровней премии и дисконта от более выского минимума до более высокого максимума
Такое деление помогает определить термины "зона премии" и "зона дисконта". Зона дисконта ниже отметки 50% обозначает диапазон цен, которые считаются более выгодными или "более дешевыми" для возможных покупок. Зона премии, с другой стороны, находится выше отметки 50% и обозначает сравнительно "дорогие" курсы. Чтобы оптимизировать соотношение риска и вознаграждения, трейдеры во многих торговых стратегиях обычно выбирают покупку из зоны дисконта; однако для данного конкретного индикатора мы используем немного другую стратегию.

В данном случае покупка нас интересует только тогда, когда рынок торгуется выше зоны премии или пробивает структуру более высокого максимума. Этот паттерн указывает на то, что бычья структура все еще имеет место, и что цена, вероятно, будет продолжать расти. Чтобы двигаться с трендом и уменьшить вероятность покупки на откате или развороте, рекомендуется дождаться пробоя выше более высокого максимума или зоны премии.
Пример:
// CHART ID long chart_id = ChartID(); // Input parameters input int LookbackBars = 10; // Number of bars to look back/forward for swing points input int bars_check = 1000; // Number of bars to check for swing points input bool show_bullish = true; //Show Buy Signals // Variables for Bullish Market Structure double L; // Low: the starting low point in the up trend datetime L_time; // Time of the low string L_letter; // Label for the low point (e.g., "L") double H; // High: the first high after the low datetime H_time; // Time of the high string H_letter; // Label for the high point (e.g., "H") double HL; // Higher Low: the next low that is higher than the first low datetime HL_time; // Time of the higher low string HL_letter; // Label for the higher low point (e.g., "HL") double HH; // Higher High: the next high that is higher than the first high datetime HH_time; // Time of the higher high string HH_letter; // Label for the higher high point (e.g., "HH") // Variables for Premium and Discount string pre_dis_box; // Name/ID for the premium-discount zone box (rectangle object on chart) double lvl_50; // The price level representing the 50% retracement between Higher Low and Higher High string lvl_50_line; // Name/ID for the horizontal line marking the 50% level //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(chart_id); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- if(show_bullish == true) // Check if the bullish trend is to be displayed { if(rates_total >= bars_check) // Ensure enough bars are available for analysis { // Loop through the price data starting from a certain point based on bars_check and LookbackBars for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { // Check if the current bar is a swing low if(IsSwingLow(low, i, LookbackBars)) { // Store the values for the swing low L = low[i]; L_time = time[i]; L_letter = StringFormat("Low%d", i); // Loop through further to find a swing high after the low for(int j = i; j < rates_total - LookbackBars; j++) { // Check if the current bar is a swing high and occurs after the identified swing low if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time) { // Store the values for the swing high H = high[j]; H_time = time[j]; H_letter = StringFormat("High%d", j); // Loop further to find a higher low after the swing high for(int k = j; k < rates_total - LookbackBars; k++) { // Check if the current bar is a swing low and occurs after the swing high if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time) { // Store the values for the higher low HL = low[k]; HL_time = time[k]; HL_letter = StringFormat("Higher Low%d", j); // Loop further to find a higher high after the higher low for(int l = j ; l < rates_total - LookbackBars; l++) { // Check if the current bar is a swing high and occurs after the higher low if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time) { // Store the values for the higher high HH = high[l]; HH_time = time[l]; HH_letter = StringFormat("Higher High%d", l); // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High if(L < H && HL < H && HL > L && HH > H) { // Create and display text objects for Low, High, Higher Low, and Higher High on the chart ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL); ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL"); ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH); ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH"); ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15); // Calculate the 50% retracement level between the Higher Low and Higher High lvl_50 = HL + ((HH - HL)/2); // Generate unique names for the premium-discount box and the 50% level line using the current loop index pre_dis_box = StringFormat("Premium and Discount Box%d", i); lvl_50_line = StringFormat("Level 50 Line%d", i); // Create a rectangle object representing the premium-discount zone from the Higher Low to the Higher High ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH); // Create a trend line (horizontal line) marking the 50% retracement level ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50); // Set the color of the premium-discount box to dark green ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_COLOR, clrDarkGreen); // Set the color of the 50% level line to dark green ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_COLOR, clrDarkGreen); // Set the width of the premium-discount box for better visibility ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_WIDTH, 2); // Set the width of the 50% level line for better visibility ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_WIDTH, 2); } break; // Exit the loop once the pattern is found } } break; // Exit the loop once the higher low is found } } break; // Exit the loop once the higher high is found } } } } } } //--- return value of prev_calculated for next call return(rates_total); }
Вывод:

Пояснение:
Код определяет уровень коррекции 50%, который является серединой между более высоким минимумом (HL) и более высоким максимумом (HH). Этот уровень разделяет зоны премии (выше) и дисконта (ниже). Серединный уровень между HL и HH определяется по формуле lvl_50 = HL + ((HH - HL)/2);. Далее индекс цикла i включается в имена двух строковых переменных – pre_dis_box и lvl_50_line. Они служат в качестве отличительных идентификаторов для визуальных элементов, которые мы будем рисовать на графике. При включении индекса цикла каждый рисунок будет уникальным и не заменит ранее созданные.
Прямоугольник создается на графике с помощью строчки ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH);, которая графически отображает переход от более низкого минимума к более высокому максимуму. С помощью этого бокса трейдеры смогут быстро определить диапазон самого последнего бычьего свинга. Конец прямоугольника зафиксирован на будущем баре (l + LookbackBars) на уровне цены HH, в то время как его начало зафиксировано по времени и цене в точке HL. Это обеспечивает видимость прямоугольника и слегка растягивает его в будущее.
Затем на графике проводится горизонтальная линия на уровне 50% с помощью строчки ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50);. Этот порог важен, потому что, в соответствии с логикой этого индикатора, мы ищем возможные сигналы на покупку, только когда рынок торгуется выше зоны премии или выше уровня 50% от последнего свинга вверх. Прямоугольнику и линии задан цвет clrDarkGreen, а функция ObjectSetInteger используется для увеличения их толщины (или ширины) до 2, чтобы сделать их более визуально отчетливыми. Рынок должен полностью пробить уровень HH, чтобы сигнал на покупку считался действительным; другими словами, цена должна закрыться выше всей зоны премии. Иными словами, мы хотим открывать покупку только тогда, когда рыночная структура явно бычья и пробивает предыдущий максимум свинга (HH).
2.2.2. Указание точки входа, стоп-лосса и тейк-профита
Поиск возможных сигналов на покупку следует после того, как будут корректно отмечены зоны премии и дисконта между более высоким минимумом (HL) и более высоким максимумом (HH). Ожидание бычьего пробойного бара, который пробьет более высокий максимум (HH), указывая на то, что рынок все еще с силой движется вверх, является ключом к действительному сетапу на покупку.
Но самого по себе пересечения HH недостаточно. Бычий бар должен закрыться выше HH для подтверждения входа, поскольку нам нужно убедиться, что пробой настоящий. Тот факт, что цена закрылась выше максимума, указывает на наличие продолжающегося покупательского спроса, и, вероятно, она будет продолжать расти. После подтверждения пробоя на цене закрытия бычьей свечи, которая пробивает уровень HH, устанавливается точка входа. Здесь мы уверены, что рынок продемонстрировал достаточную силу на текущий момент для того, чтобы мы могли войти в сделку.
Чтобы защититься от возможного разворота, мы устанавливаем стоп-лосс (SL) на уровне 50% (lvl_50), который является серединной точкой между HL и HH. Мы устанавливаем SL именно здесь, чтобы избежать возможного попадания в снижение в зону дисконта (ниже 50% уровня), что может указывать на изменение настроения на рынке. Для установки уровней тейк-профита (TP) наш метод основан на соотношении риска и вознаграждения (R:R). Цель по прибыли для первого уровня тейк-профита (TP1) равен расстоянию риска от точки входа до SL, поскольку данный тейк-профит устанавливается при соотношении R:R, равном 1:1. Цель по прибыли для второго уровня тейк-профита (TP2) равняется удвоенному расстоянию от точки входа до SL (соотношение R:R, равное 1:2). Для трейдеров, которые предпочитают зафиксировать часть прибыли на уровне TP1, эти два уровня тейк-профита обеспечивают гибкость, позволяя им оставить часть сделки открытой для роста прибыли, если восходящий тренд на рынке продолжится.
Пример:
// Variables for Entry, Stop Loss, and Take Profit string entry_line; // Line object to represent the entry point on the chart string entry_txt; // Text object for displaying "BUY" at the entry point double lvl_SL; // Stop Loss level (set at the 50% retracement level) string lvl_sl_line; // Line object for representing the Stop Loss level string lvl_sl_txt; // Text object for labeling the Stop Loss level double TP1; // Take Profit 1 level (1:1 risk-reward ratio) double TP2; // Take Profit 2 level (1:2 risk-reward ratio) string lvl_tp_line; // Line object for representing the Take Profit 1 level string lvl_tp2_line; // Line object for representing the Take Profit 2 level string lvl_tp_txt; // Text object for labeling the Take Profit 1 level string lvl_tp2_txt; // Text object for labeling the Take Profit 2 level string buy_object; // Arrow object to indicate the Buy signal on the chart
if(show_bullish == true) // Check if the bullish trend is to be displayed { if(rates_total >= bars_check) // Ensure enough bars are available for analysis { // Loop through the price data starting from a certain point based on bars_check and LookbackBars for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { // Check if the current bar is a swing low if(IsSwingLow(low, i, LookbackBars)) { // Store the values for the swing low L = low[i]; L_time = time[i]; L_letter = StringFormat("Low%d", i); // Loop through further to find a swing high after the low for(int j = i; j < rates_total - LookbackBars; j++) { // Check if the current bar is a swing high and occurs after the identified swing low if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time) { // Store the values for the swing high H = high[j]; H_time = time[j]; H_letter = StringFormat("High%d", j); // Loop further to find a higher low after the swing high for(int k = j; k < rates_total - LookbackBars; k++) { // Check if the current bar is a swing low and occurs after the swing high if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time) { // Store the values for the higher low HL = low[k]; HL_time = time[k]; HL_letter = StringFormat("Higher Low%d", j); // Loop further to find a higher high after the higher low for(int l = j ; l < rates_total - LookbackBars; l++) { // Check if the current bar is a swing high and occurs after the higher low if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time) { // Store the values for the higher high HH = high[l]; HH_time = time[l]; HH_letter = StringFormat("Higher High%d", l); // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High if(L < H && HL < H && HL > L && HH > H) { // Create and display text objects for Low, High, Higher Low, and Higher High on the chart ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL); ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL"); ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH); ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH"); ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15); // Calculate the 50% retracement level between the Higher Low and Higher High lvl_50 = HL + ((HH - HL)/2); // Generate unique names for the premium-discount box and the 50% level line using the current loop index pre_dis_box = StringFormat("Premium and Discount Box%d", i); lvl_50_line = StringFormat("Level 50 Line%d", i); // Create a rectangle object representing the premium-discount zone from the Higher Low to the Higher High ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH); // Create a trend line (horizontal line) marking the 50% retracement level ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50); // Set the color of the premium-discount box to dark green ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_COLOR, clrDarkGreen); // Set the color of the 50% level line to dark green ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_COLOR, clrDarkGreen); // Set the width of the premium-discount box for better visibility ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_WIDTH, 2); // Set the width of the 50% level line for better visibility ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_WIDTH, 2); for(int m = l; m < rates_total-1; m++) { if(close[m] > open[m] && close[m] > HH && time[m] >= time[l+LookbackBars]) { TP1 = close[m] + (close[m] - lvl_50); TP2 = TP1 + (close[m] - lvl_50); entry_line = StringFormat("Entry%d", m); lvl_sl_line = StringFormat("SL%d", m); lvl_tp_line = StringFormat("TP%d", m); lvl_tp2_line = StringFormat("TP 2%d", m); ObjectCreate(chart_id,entry_line,OBJ_TREND,0,HL_time,close[m],time[m],close[m]); ObjectCreate(chart_id,lvl_sl_line,OBJ_TREND,0,HL_time,lvl_50,time[m],lvl_50); ObjectCreate(chart_id,lvl_tp_line,OBJ_TREND,0,HL_time,TP1,time[m],TP1); ObjectCreate(chart_id,lvl_tp2_line,OBJ_TREND,0,HL_time,TP2,time[m],TP2); ObjectSetInteger(chart_id,entry_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_sl_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_tp_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_tp2_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,entry_line,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_sl_line,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_tp_line,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_tp2_line,OBJPROP_COLOR,clrDarkGreen); entry_txt = StringFormat("Entry Text%d", m); lvl_sl_txt = StringFormat("SL Text%d", m); lvl_tp_txt = StringFormat("TP 1 Text%d", m); lvl_tp2_txt = StringFormat("TP 2 Text%d", m); ObjectCreate(chart_id, lvl_sl_txt, OBJ_TEXT, 0,time[m],lvl_50); ObjectSetString(chart_id, lvl_sl_txt, OBJPROP_TEXT, "SL"); ObjectSetInteger(chart_id,lvl_sl_txt,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_sl_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, entry_txt, OBJ_TEXT, 0,time[m],close[m]); ObjectSetString(chart_id, entry_txt, OBJPROP_TEXT, "BUY"); ObjectSetInteger(chart_id,entry_txt,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,entry_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_tp_txt, OBJ_TEXT, 0,time[m],TP1); ObjectSetString(chart_id, lvl_tp_txt, OBJPROP_TEXT, "TP1"); ObjectSetInteger(chart_id,lvl_tp_txt,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_tp_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_tp2_txt, OBJ_TEXT, 0,time[m],TP2); ObjectSetString(chart_id, lvl_tp2_txt, OBJPROP_TEXT, "TP2"); ObjectSetInteger(chart_id,lvl_tp2_txt,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_tp2_txt,OBJPROP_FONTSIZE,15); buy_object = StringFormat("Buy Object%d", m); ObjectCreate(chart_id,buy_object,OBJ_ARROW_BUY,0,time[m],close[m]); break; } } } break; // Exit the loop once the pattern is found } } break; // Exit the loop once the higher low is found } } break; // Exit the loop once the higher high is found } } } } } }Вывод:

Вы можете увидеть, что зоны премии и дисконта, а также маркеры сигналов на покупку на изображении выше нарисованы неточно. Это связано с тем, что некоторые условия не были учтены перед отрисовкой объектов на графике. Чтобы улучшить наш подход, мы должны добавить больше проверок, чтобы убедиться, что сигнал на покупку является действительным. Прежде чем рисовать какие-либо объекты на графике, мы сначала должны убедиться, что свеча действительно пробила более высокий максимум (HH). Пробой HH означает продолжение бычьего тренда, что необходимо для того, чтобы сигнал на покупку считался действительным, что делает это важнейшим критерием. Нам не следует начинать вычисление точки входа и уровней риска, пока это требование не будет выполнено.
Необходимо подсчитать количество баров от более высокого минимума (HL) до конца зоны премии и дисконта. Это гарантирует, что ценовое движение находится в приемлемом диапазоне и помогает нам понять, насколько далеко рынок продвинулся. После завершения этого подсчета нам нужно подтвердить, что цена закрытия бычьего бара, который пробил более высокий максимум (HH), находится близко к зоне премии и дисконта. Это гарантирует, что сигнал на покупку не будет слишком далеко от предполагаемой рыночной структуры и находится в пределах разумного ценового диапазона.
Пример:
// Declare variables to count bars int n_bars; // Number of bars from Higher Low to the end of the Premium/Discount box int n_bars_2; // Number of bars from the end of the Premium/Discount box to the bullish bar that broke HH
if(show_bullish == true) // Check if the bullish trend is to be displayed { if(rates_total >= bars_check) // Ensure enough bars are available for analysis { // Loop through the price data starting from a certain point based on bars_check and LookbackBars for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { // Check if the current bar is a swing low if(IsSwingLow(low, i, LookbackBars)) { // Store the values for the swing low L = low[i]; L_time = time[i]; L_letter = StringFormat("Low%d", i); // Loop through further to find a swing high after the low for(int j = i; j < rates_total - LookbackBars; j++) { // Check if the current bar is a swing high and occurs after the identified swing low if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time) { // Store the values for the swing high H = high[j]; H_time = time[j]; H_letter = StringFormat("High%d", j); // Loop further to find a higher low after the swing high for(int k = j; k < rates_total - LookbackBars; k++) { // Check if the current bar is a swing low and occurs after the swing high if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time) { // Store the values for the higher low HL = low[k]; HL_time = time[k]; HL_letter = StringFormat("Higher Low%d", j); // Loop further to find a higher high after the higher low for(int l = j ; l < rates_total - LookbackBars; l++) { // Check if the current bar is a swing high and occurs after the higher low if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time) { // Store the values for the higher high HH = high[l]; HH_time = time[l]; HH_letter = StringFormat("Higher High%d", l); // Loop through the bars to check for the conditions for entry for(int m = l; m < rates_total-1; m++) { // Check if the current bar is a bullish bar and if the price has broken the higher high (HH) if(close[m] > open[m] && close[m] > HH && time[m] >= time[l+LookbackBars]) { // Count the bars between HL_time and the end of the Premium/Discount box n_bars = Bars(_Symbol, PERIOD_CURRENT, HL_time, time[l + LookbackBars]); // Count the bars between the end of the Premium/Discount box and the candle that broke HH n_bars_2 = Bars(_Symbol, PERIOD_CURRENT, time[l + LookbackBars], time[m]); // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High if(L < H && HL < H && HL > L && HH > H && open[l+LookbackBars] <= HH && n_bars_2 < n_bars) { // Create and display text objects for Low, High, Higher Low, and Higher High on the chart ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL); ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL"); ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH); ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH"); ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15); // Calculate the 50% retracement level between the Higher Low and Higher High lvl_50 = HL + ((HH - HL)/2); // Generate unique names for the premium-discount box and the 50% level line using the current loop index pre_dis_box = StringFormat("Premium and Discount Box%d", i); lvl_50_line = StringFormat("Level 50 Line%d", i); // Create a rectangle object representing the premium-discount zone from the Higher Low to the Higher High ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH); // Create a trend line (horizontal line) marking the 50% retracement level ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50); // Set the color of the premium-discount box to dark green ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_COLOR, clrDarkGreen); // Set the color of the 50% level line to dark green ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_COLOR, clrDarkGreen); // Set the width of the premium-discount box for better visibility ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_WIDTH, 2); // Set the width of the 50% level line for better visibility ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_WIDTH, 2); // Calculate Take Profit levels based on the 50% retracement TP1 = close[m] + (close[m] - lvl_50); // TP1 at 1:1 risk-reward ratio TP2 = TP1 + (close[m] - lvl_50); // TP2 at 1:2 risk-reward ratio // Create unique object names for Entry, Stop Loss, and Take Profit lines and text entry_line = StringFormat("Entry%d", m); lvl_sl_line = StringFormat("SL%d", m); lvl_tp_line = StringFormat("TP%d", m); lvl_tp2_line = StringFormat("TP 2%d", m); // Create the lines on the chart for Entry, Stop Loss, and Take Profit levels ObjectCreate(chart_id, entry_line, OBJ_TREND, 0, HL_time, close[m], time[m], close[m]); ObjectCreate(chart_id, lvl_sl_line, OBJ_TREND, 0, HL_time, lvl_50, time[m], lvl_50); ObjectCreate(chart_id, lvl_tp_line, OBJ_TREND, 0, HL_time, TP1, time[m], TP1); ObjectCreate(chart_id, lvl_tp2_line, OBJ_TREND, 0, HL_time, TP2, time[m], TP2); // Set the properties for the lines (width, color, etc.) ObjectSetInteger(chart_id, entry_line, OBJPROP_WIDTH, 2); ObjectSetInteger(chart_id, lvl_sl_line, OBJPROP_WIDTH, 2); ObjectSetInteger(chart_id, lvl_tp_line, OBJPROP_WIDTH, 2); ObjectSetInteger(chart_id, lvl_tp2_line, OBJPROP_WIDTH, 2); ObjectSetInteger(chart_id, entry_line, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_sl_line, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_tp_line, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_tp2_line, OBJPROP_COLOR, clrDarkGreen); // Create the text labels for Entry, Stop Loss, and Take Profit levels entry_txt = StringFormat("Entry Text%d", m); lvl_sl_txt = StringFormat("SL Text%d", m); lvl_tp_txt = StringFormat("TP 1 Text%d", m); lvl_tp2_txt = StringFormat("TP 2 Text%d", m); // Create the text objects for the Entry, Stop Loss, and Take Profit labels ObjectCreate(chart_id, lvl_sl_txt, OBJ_TEXT, 0, time[m], lvl_50); ObjectSetString(chart_id, lvl_sl_txt, OBJPROP_TEXT, "SL"); ObjectSetInteger(chart_id, lvl_sl_txt, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_sl_txt, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, entry_txt, OBJ_TEXT, 0, time[m], close[m]); ObjectSetString(chart_id, entry_txt, OBJPROP_TEXT, "BUY"); ObjectSetInteger(chart_id, entry_txt, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, entry_txt, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, lvl_tp_txt, OBJ_TEXT, 0, time[m], TP1); ObjectSetString(chart_id, lvl_tp_txt, OBJPROP_TEXT, "TP1"); ObjectSetInteger(chart_id, lvl_tp_txt, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_tp_txt, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, lvl_tp2_txt, OBJ_TEXT, 0, time[m], TP2); ObjectSetString(chart_id, lvl_tp2_txt, OBJPROP_TEXT, "TP2"); ObjectSetInteger(chart_id, lvl_tp2_txt, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_tp2_txt, OBJPROP_FONTSIZE, 15); // Create a Buy arrow object to indicate the Buy signal on the chart buy_object = StringFormat("Buy Object%d", m); ObjectCreate(chart_id, buy_object, OBJ_ARROW_BUY, 0, time[m], close[m]); break; // Exit the loop once a Buy signal is found } } } break; // Exit the loop once the pattern is found } } break; // Exit the loop once the higher low is found } } break; // Exit the loop once the higher high is found } } } } } }
Вывод:

Пояснение:
В глобальной области видимости кода были объявлены две целочисленные переменные: n_bars и n_bars_2. Эти переменные определяют количество свеч (баров) между важными точками в паттерне бычьей рыночной структуры. В частности, n_bars – это количество баров, которые отделяют конец зоны премии/дисконта (time[l + LookbackBars]) от более высокого минимума (HL). При этом n_bars_2 отсчитывает количество свечей, которые отделяют бычью свечу, пробившую более высокий максимум (HH), от конца зоны премии/дисконта. Этот подсчет используется для оценки того, отклонилось ли ценовое действие слишком далеко от оптимальной зоны для торговли, и действителен ли по-прежнему сигнал на покупку.
Эти переменные используются позже в коде как часть дополнительного условия проверки, которое усиливает аргумент в пользу бычьей рыночной структуры. Дополнительное условие n_bars_2 < n_bars проверяется после определения минимума, максимума, более высокого минимума и более высокого максимума (убедившись, что они соответствуют структуре L < H, HL < H, HL > L и HH > H) и проверки, что цена открытия свечи, которая завершила зону премии/дисконта, находится не выше более высокого максимума. Это гарантирует, что бычья свеча пробоя (которая пробивает уровень HH) не появляется слишком далеко от формации, что может указывать на слабый или недействительный сетап, и что она появляется достаточно близко к паттерну.
Все процедуры с функциями ObjectCreate() и ObjectSet*(), которые ранее использовались для рисования минимума, максимума, более высокого минимума, более высокого максимума, зоны премии/дисконта, линии 50% и маркеров точки входа, SL и TP, были перенесены внутрь этого условия, чтобы обеспечить более строгую проверку. Это подразумевает, что эти визуальные компоненты будут созданы и показаны, только когда будут выполнены все требования к бычьей структуре и времени. Таким образом, график остается чистым и не будет перегружен ошибочными или преждевременными элементами из-за вводящих в заблуждение сигналов.
2.3. Медвежий тренд
Этот индикатор должен сначала использовать рыночную структуру для подтверждения снижения, прежде чем он сможет дать сигнал на продажу. Это достигается за счет нахождения последовательности значимых ценовых точек – максимума, минимума, более низкого максимума и более низкого минимума. Этот паттерн демонстрирует, что продавцы контролируют ситуацию, и что рынок, вероятно, будет продолжать двигаться вниз, что поддерживает медвежий моментум. Индикатор начнет искать действительные сигналы на продажу, как только эта структура будет подтверждена.

Пример:
// Variables for Bearish Market Structure double LH; // Lower High: the high formed after the initial low in a downtrend datetime LH_time; // Time of the Lower High string LH_letter; // Label used to display the Lower High on the chart (e.g., "LH") double LL; // Lower Low: the new low formed after the Lower High in a downtrend datetime LL_time; // Time of the Lower Low string LL_letter; // Label used to display the Lower Low on the chart (e.g., "LL") string sell_object; // Arrow object to indicate the Sell signal on the chart
// BEARISH TREND if(show_bearish == true) // Check if the user enabled the bearish trend display { if(rates_total >= bars_check) // Ensure enough candles are available for processing { // Loop through historical bars to find a swing high (potential start of bearish structure) for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { if(IsSwingHigh(high, i, LookbackBars)) // Detect first swing high { H = high[i]; H_time = time[i]; H_letter = StringFormat("High B%d", i); // Label for the high // From the swing high, look for the next swing low for(int j = i; j < rates_total - LookbackBars; j++) { if(IsSwingLow(low, j, LookbackBars) && time[j] > H_time) // Confirm next swing low { L = low[j]; L_time = time[j]; L_letter = StringFormat("Low B%d", j); // Label for the low // From the swing low, look for the Lower High for(int k = j; k < rates_total - LookbackBars; k++) { if(IsSwingHigh(high, k, LookbackBars) && time[k] > L_time) { LH = high[k]; LH_time = time[k]; LH_letter = StringFormat("Lower High%d", k); // Label for the Lower High // From the LH, find a Lower Low for(int l = j ; l < rates_total - LookbackBars; l++) { if(IsSwingLow(low, l, LookbackBars) && time[l] > LH_time) { LL = low[l]; LL_time = time[l]; LL_letter = StringFormat("Lower Low%d", l); // Label for Lower Low // Calculate 50% retracement level from LH to LL lvl_50 = LL + ((LH - LL)/2); // Prepare object names pre_dis_box = StringFormat("Gan Box B%d", i); lvl_50_line = StringFormat("Level 50 Line B%d", i); // Search for a bearish entry condition for(int m = l; m < rates_total-1; m++) { // Confirm bearish candle breaking below the LL if(close[m] < open[m] && close[m] < LL && time[m] >= time[l+LookbackBars]) { // Count bars for pattern distance validation n_bars = Bars(_Symbol,PERIOD_CURRENT,LH_time, time[l+LookbackBars]); // From LH to box end n_bars_2 = Bars(_Symbol,PERIOD_CURRENT,time[l+LookbackBars], time[m]); // From box end to break candle // Confirm valid bearish structure and proximity of break candle if(H > L && LH > L && LH < H && LL < L && open[l+LookbackBars] >= LL && n_bars_2 < n_bars) { // Draw the Premium/Discount box ObjectCreate(chart_id,pre_dis_box, OBJ_RECTANGLE,0,LH_time,LH, time[l+LookbackBars],LL); ObjectCreate(chart_id,lvl_50_line, OBJ_TREND,0,LH_time,lvl_50, time[l+LookbackBars],lvl_50); ObjectSetInteger(chart_id,pre_dis_box,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_50_line,OBJPROP_WIDTH,2); // Label the structure points ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id,H_letter,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id,L_letter,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, LH_letter, OBJ_TEXT, 0, LH_time, LH); ObjectSetString(chart_id, LH_letter, OBJPROP_TEXT, "LH"); ObjectSetInteger(chart_id,LH_letter,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, LL_letter, OBJ_TEXT, 0, LL_time, LL); ObjectSetString(chart_id, LL_letter, OBJPROP_TEXT, "LL"); ObjectSetInteger(chart_id,LL_letter,OBJPROP_FONTSIZE,15); ObjectSetInteger(chart_id,H_letter,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,L_letter,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,LL_letter,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,LH_letter,OBJPROP_WIDTH,2); // Calculate Take Profits based on 1:1 and 1:2 RR TP1 = close[m] - (lvl_50 - close[m]); TP2 = TP1 - (lvl_50 - close[m]); // Generate entry, SL and TP object names entry_line = StringFormat("Entry B%d", m); lvl_sl_line = StringFormat("SL B%d", m); lvl_tp_line = StringFormat("TP B%d", m); lvl_tp2_line = StringFormat("TP 2 B%d", m); // Draw entry, SL, TP1, TP2 levels ObjectCreate(chart_id,entry_line,OBJ_TREND,0,LH_time,close[m],time[m],close[m]); ObjectCreate(chart_id,lvl_sl_line, OBJ_TREND,0,LH_time,lvl_50, time[m],lvl_50); ObjectCreate(chart_id,lvl_tp_line, OBJ_TREND,0,LH_time,TP1, time[m],TP1); ObjectCreate(chart_id,lvl_tp2_line, OBJ_TREND,0,LH_time,TP2, time[m],TP2); ObjectSetInteger(chart_id,entry_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_sl_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_tp_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_tp2_line,OBJPROP_WIDTH,2); // Generate text labels entry_txt = StringFormat("Entry Text B%d", m); lvl_sl_txt = StringFormat("SL Text B%d", m); lvl_tp_txt = StringFormat("TP Text B%d", m); lvl_tp2_txt = StringFormat("TP 2 Text B%d", m); ObjectCreate(chart_id, entry_txt, OBJ_TEXT, 0,time[m],close[m]); ObjectSetString(chart_id, entry_txt, OBJPROP_TEXT, "SELL"); ObjectSetInteger(chart_id,entry_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_sl_txt, OBJ_TEXT, 0,time[m],lvl_50); ObjectSetString(chart_id, lvl_sl_txt, OBJPROP_TEXT, "SL"); ObjectSetInteger(chart_id,lvl_sl_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_tp_txt, OBJ_TEXT, 0,time[m],TP1); ObjectSetString(chart_id, lvl_tp_txt, OBJPROP_TEXT, "TP1"); ObjectSetInteger(chart_id,lvl_tp_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_tp2_txt, OBJ_TEXT, 0,time[m],TP2); ObjectSetString(chart_id, lvl_tp2_txt, OBJPROP_TEXT, "TP2"); ObjectSetInteger(chart_id,lvl_tp2_txt,OBJPROP_FONTSIZE,15); // Draw sell arrow sell_object = StringFormat("Sell Object%d", m); ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,0,time[m],close[m]); } break; // Exit loop after valid setup } } break; // Exit LL search } } break; // Exit LH search } } break; // Exit L search } } } } } }
Вывод:

Пояснение:
Код начинается с проверки if(show_bearish == true), чтобы увидеть, активировал ли пользователь логику обнаружения медвежего тренда. Чтобы найти действительную медвежью рыночную структуру, индикатор проходит по историческим барам, если эта логика включена, и доступно достаточно баров (rates_total >= bars_check). Первым шагом в данном процессе является определение максимума свинга (H). После выявления максимума свинга код ищет минимум свинга (L), который следует за максимумом. Если он будет обнаружен, код продолжает искать более низкий минимум (LL), который подтверждает медвежью структуру, за которым следует более низкий максимум (LH), который является максимумом свинга ниже первого максимума H. Метки, такие как "H", "L", "LH" и "LL", создаются на графике с использованием полученных значений и связанных с ними временных меток.
Затем рисуется зона премии/дисконта от LH до LL, и определяется уровень коррекции 50% между LH и LL (lvl_50 = LL + ((LH - LL)/2). Индикатор ищет медвежью свечу (close < open), которая закрывается ниже LL, прежде чем размещать какие-либо объекты, относящиеся к торговле (точка входа, SL, TP1, TP2). Используя две переменные, а именно n_bars, которая считает свечи от более низкого максимума (LH) до конца зоны премии/дисконта, и n_bars_2, которая считает свечи от конца зоны до медвежьей свечи, пробивающей более низкий минимум (LL), код также гарантирует, что пробой структуры происходит в на разумном расстоянии (в свечах). Код рисует линию входа на цене закрытия свечи, устанавливает уровень стоп-лосса (SL) на уровне 50% и размещает TP1 и TP2 на уровнях соотношения риска и прибыли 1:1 и 1:2, соответственно, только когда все требования выполнены, включая правильную структуру, действительный пробой и надлежащее расстояние.
Кроме того, к медвежьей свече добавляется стрелка продажи и слово "SELL". Для нисходящего тренда логика в целом такая же, как и для паттерна восходящего тренда, но инвертированная. Поскольку медвежий тренд является лишь противоположностью бычьего тренда, который уже был подробно описан, объяснение будет кратким. Структура и логика остаются такими же за тем исключением, что они перевернуты, чтобы отображать нисходящий тренд, а не восходящий.
Заключение
В этой статье мы создали пользовательский индикатор на языке MQL5, который выявляет рыночную структуру, обнаруживая такие ключевые точки, как минимум (L), более низкий минимум (LL), более высокий минимум (HL), максимум (H) и более высокий максимум (HH). Используя эти точки, индикатор определяет бычьи или медвежьи тренды и автоматически рисует точки входа, стоп-лосс на уровне 50% и уровни тейк-профита (TP1 и TP2) на основе структурного паттерна. Он также отмечает зоны премии и дисконта, чтобы визуально выделить, где цена, вероятно, будет реагировать. Все графические объекты рисуются только при выполнении определенных условий, что обеспечивает чистоту и надежность сигналов.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17689
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: Пространственно-временная модель состояния для анализа финансовых данных (STSSM-блок)
Алгоритм дифференциального поиска — Differential Search Algorithm (DSA)
Знакомство с языком MQL5 (Часть 16): Создание советников с использованием паттернов технического анализа
Знакомство с языком MQL5 (Часть 14): Руководство для начинающих по созданию пользовательских индикаторов (III)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования