
Введение в MQL5 (Часть 10): Руководство по работе со встроенными индикаторами в MQL5 для начинающих
Введение
Представляю вашему вниманию очередную статью из нашей серии, посвященной MQL5. Рад видеть вас в этой десятой части, где мы разберем еще один важный аспект алгоритмической торговли — работу со встроенными индикаторами. Как и в предыдущих статьях, здесь содержится полезный и практичный материал: мы продолжаем следовать проектному подходу, таким образом вы сразу сможете применять знания на практике в собственных торговых стратегиях.
В этой статье мы разработаем советник на основе индекса относительной силы (Relative Strength Index, RSI). Это один из самых популярных технических индикаторов, используемых трейдерами. Мы разработаем инструмент, который будет отслеживать рыночные условия и автоматически совершать сделки на основе показателей RSI. При этом обсуждаемые здесь идеи применимы практически ко всем встроенным индикаторам, так как принцип их работы схож. Поскольку серия рассчитана прежде всего на начинающих, моя цель — максимально упростить объяснения и код. Я знаю, насколько важно для новичка понимать каждую деталь: зачем пишется конкретная строка кода, что именно выполняет тот или иной блок, и каким образом все части соединяются в рабочую систему.
В профессиональной MQL5-разработке ценится лаконичный и оптимизированный код. Но такой стиль нередко делает его сложным для понимания, особенно тем, кто только начинает. Поэтому в этой серии я намеренно использую более подробный и методичный подход, чтобы вам было комфортно разбираться в процессе.
В этой статье вы узнаете:
- Как использовать встроенные индикаторы.
- Что такое хэндлы индикаторов и как с ними работать.
- Как получать доступ к буферам индикаторов и извлекать из них рассчитанные значения.
- Как получить данные RSI и соответствующие значения свечей с графика.
- Как находить экстремумы RSI и использовать их для реализации концепции "снятия ликвидности".
- Пошаговое создание советника на основе RSI и данных свечей.
- Как создавать объекты для разметки ключевых максимумов и минимумов RSI прямо на графике для наглядного анализа.
- Как задать процент риска на сделку, учитывая разные размеры свечей при работе со встроенными индикаторами.
В результате вы получите полное понимание того, как интегрировать встроенные индикаторы в торговые стратегии с акцентом на управление рисками, адаптацию стратегии и практические приемы при создании советников на базе RSI.
1. Понимание встроенных индикаторов в MQL5
1.1. Что такое встроенные индикаторы?
Встроенные индикаторы в MetaTrader 5 — это готовые инструменты для анализа рынка. Они позволяют быстро оценить текущую ситуацию: настроение рынка, силу движения и динамику цен. Например, полосы Боллинджера (Bollinger Bands) показывают степень волатильности, скользящая средняя помогает выявить тренд, а RSI сигнализирует о перекупленности или перепроданности. Эти инструменты значительно упрощают торговлю и экономят время.
1.2. Хэндлы индикаторов
В MQL5 хэндл handle индикатора — это уникальный идентификатор, который присваивается индикатору при его создании или инициализации. По сути, это "ссылка" на индикатор, через которую программа может обращаться к его данным. Когда вы добавляете индикатор на график, необходимо указать его параметры: период, тип цены и другие настройки, определяющие поведение индикатора.
В коде хэндл выполняет аналогичную функцию: он дает программе "понимание", с каким именно индикатором идет работа, и позволяет управлять его параметрами. Иными словами, хэндл связывает настройки индикатора с вашей программой, чтобы вы могли использовать его в торговой стратегии.
Когда вы создаете хэндл с помощью функций вроде iRSI или iBands, программа "привязывается" к конкретному индикатору, что позволяет получать его данные и работать с ними. Без хэндла программа не сможет отличить один индикатор от другого и не получит доступ к рассчитанным значениям его буферов. Например, если нужно задать параметры RSI прямо в коде, вы используете функцию iRSI, где указываете период, тип цены и сдвиг. Эта функция создаст хэндл, через который вы сможете работать с данным индикатором.
Синтаксис:
iRSI(symbol, period, rsi_period, applied_price);
Пояснение:
- symbol — параметр определяет символ (валютная пара, акция и пр.), для которого рассчитывается индикатор.
- period — период для расчета RSI. Он определяет, насколько далеко в прошлом RSI будет учитывать точки данных.
- rsi_period — количество периодов, используемых для расчета RSI. RSI обычно рассчитывается на 14 периодах, но это значение можно настроить в соответствии с вашей стратегией.
- applied_price — тип цены, используемой для расчета RSI. RSI можно считать на разных значениях цены: цена закрытия, цена открытия, максимум/минимум.
int rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE);
Пояснение:
int rsi_handle:
- Объявляет целочисленную переменную rsi_handle для хранения хэндла индикатора RSI (уникальный идентификатор индикатора).
iRSI(...):
- Функция, используемая для расчета RSI для заданного символа, таймфрейма и настроек.
_Symbol:
- Означает текущий торговый символ графика. Автоматически использует текущий символ, с которым вы работаете.
PERIOD_CURRENT:
- Относится к таймфрейму графика (например, часовой, дневной). Current означает текущий таймфрейм графика, на котором запущен индикатор.
14:
- Период RSI определяет количество баров/свечей, которые будут использоваться в расчете (обычно 14 периодов).
PRICE_CLOSE:
- Указывает на расчет RSI по ценам закрытия баров.
Так вы задаете параметры индикатора прямо в коде — точно так же, как если бы устанавливали его на график в MetaTrader 5 вручную. Используя функции вроде iRSI, вы определяете символ, таймфрейм, период и тип цены — аналогично настройкам в терминале. Благодаря этому ваш код получает доступ к данным индикатора и может работать с ними, а торговая стратегия будет работать по этим заданным параметрам.
Эти же принципы применимы и к другим встроенным индикаторам в MetaTrader 5. Для каждого индикатора используется своя функция с уникальным набором параметров. Вот несколько часто используемых функций:
- iBands — для индикатора Bollinger Bands, позволяет устанавливать символ, таймфрейм, период, отклонение и цену расчета.
- iMA — для скользящего среднего, здесь задаются символ, таймфрейм, период, сдвиг, метод усреднения и тип цены.
- iMACD — для индикатора MACD, определяет символ, таймфрейм, быструю и медленную EMA, период сигнальной линии и тип цены.
- iADX — для индикатора Average Directional Index, указывает символ, таймфрейм и период.
В MQL5 доступно множество других функций для работы со встроенными индикаторами, что дает трейдерам широкий арсенал инструментов технического анализа. Освоив принцип работы хотя бы с одним из них, вы легко сможете применить те же подходы и к остальным. Найти индикаторы, которые лучше всего подходят под ваши задачи, поможет документация по MQL5.
1.2. Индикаторные буферы
После того как индикатор определен с помощью хэндла, следующим шагом является извлечение данных. Это выполняется через буферы индикаторов — специальные массивы, где хранятся рассчитанные значения индикатора для каждой точки на графике. Количество буферов зависит от типа индикатора и того, какие данные он генерирует:
Скользящая средняя (MA)
Этот индикатор имеет всего 1 буфер, где сохраняются рассчитанные значения скользящей средней для каждой свечи.
Индекс относительной силы (Relative Strength Index, RSI).
У RSI тоже 1 буфер — для хранения значений RSI
Индикатор Bollinger Bands
Использует 3 буфера данных.
- Средняя полоса (индекс 0) является основной линией тренда.
- Верхняя полоса (индекс 1) представляет собой потенциальный уровень перекупленности.
- Нижняя полоса (индекс 2) — потенциальный уровень перепроданности.
Все эти буферы можно получить программно с помощью функции CopyBuffer().
Функция CopyBuffer() в MQL5 используется для копирования данных из буфера индикатора в массив, чтобы затем анализировать их или использовать для принятия торговых решений. После того как вы создали хэндл индикатора (например, через iRSI или iBands), вы используете CopyBuffer(), чтобы получить рассчитанные значения.
Синтаксис:int CopyBuffer(indicator_handle, buffer_number, start_position, count, buffer);
Параметры:
- indicator_handle — уникальный идентификатор (хэндл) индикатора, созданный ранее (например, с помощью iRSI или iBands).
- buffer_number — индекс буфера, из которого мы получаем данные. Например, для Bollinger Bands: 0 — средняя линия, 1 — верхняя полоса, 2 — нижняя полоса. Для RSI или скользящей средней — только 0, поскольку у них один буфер.
- start_pos — начальная позиция на графике, где 0 означает самую последнюю свечу.
- count — количество значений, которые нужно извлечь.
- buffer[] — массив, куда сохраняются скопированные данные.
int band_handle; // Bollinger Bands handle double upper_band[], mid_band[], lower_band[]; // Buffers for the bands void OnStart() { // Create the Bollinger Bands indicator band_handle = iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE); // Ensure arrays are series for correct indexing ArraySetAsSeries(upper_band, true); ArraySetAsSeries(mid_band, true); ArraySetAsSeries(lower_band, true); // Copy data from buffers (Index 0 = Middle, 1 = Upper, 2 = Lower) CopyBuffer(band_handle, 0, 0, 10, mid_band); // Middle band data CopyBuffer(band_handle, 1, 0, 10, upper_band); // Upper band data CopyBuffer(band_handle, 2, 0, 10, lower_band); // Lower band data // Print the most recent values Print("Middle Band: ", mid_band[0]); Print("Upper Band: ", upper_band[0]); Print("Lower Band: ", lower_band[0]); }
Пояснение:
В этом примере показано, как использовать функцию CopyBuffer() для получения данных индикатора Bollinger Bands. Сначала при помощи функции iBands создается хэндл индикатора с заданными параметрами: символ (_Symbol), таймфрейм (PERIOD_CURRENT), период (20), сдвиг (0), отклонение (2.0) и цена расчета (PRICE_CLOSE). Этот хэндл, сохраненный в переменной band_handle, выступает в роли моста между нашим приложением и индикатором Bollinger Bands, обеспечивая доступ к рассчитанным значениям. Затем объявляются три массива: upper_band, mid_band и lower_band, в которых будут храниться данные верхней, средней и нижней полосы. Если для этих массивов вызвать функцию ArraySetAsSeries(), то их индексация будет организована как у ценового ряда: с нуля (0) начинается последняя свеча. Это удобно, потому что самое актуальное значение всегда находится в array[0].
Каждый из трех буферов индикатора Bollinger Bands — 0 для средней линии, 1 для верхней полосы и 2 для нижней — имеет свой вызов функции CopyBuffer(). При этом в каждый массив загружаются последние 11 значений. В завершение программа выводит в лог с помощью Print() последние значения средней, верхней и нижней полос. Связь между ценой и линиями Bollinger Bands можно использовать для выявления возможных пробоев или разворотов тренда, а также для других аналитических задач в рамках торговой системы.
2. Разработка советника на основе RSI
2.1. Как работает советник:
Советник анализирует уровни перекупленности и перепроданности как ключевые сигналы возможного разворота.
2.1.1. Логика открытия сделки на покупку (Buy)
- Проверяем, находится ли значение RSI ниже уровня 30.
- Определяем, сформировал ли RSI локальный минимум.
- Находим соответствующий минимум свечи на графике цен.
- Ждем , когда цена пробьет этот минимум (снятие ликвидности).
- Когда после пробоя минимумов первая бычья свеча закроется выше пробитого уровня, советник открывает сделку Buy, ожидая роста цены.
2.1.2. Логика открытия сделки на продажу (Sell)
- Проверяем, находится ли значение RSI выше уровня 70.
- Определяем, сформировал ли RSI локальный максимум.
- Находим соответствующий максимум свечи на графике цен.
- Дождемся, когда цена пробьет этот максимум, сняв ликвидность.
- После пробоя, когда первая медвежья свеча закроется ниже пробитого уровня, советник открывает сделку Sell, ожидая снижения цены.
2.2. Подключение торговой библиотеки
Первый шаг при создании советника, который открывает, закрывает или модифицирует позиции — это подключение торговой библиотеки. Эта библиотека предоставляет набор основных функций для программного открытия и управления сделками.
Пример:#include <Trade/Trade.mqh> // Include the trade library for trading functions // Create an instance of the CTrade class for trading operations CTrade trade; //magic number input int MagicNumber = 1111; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Set the magic number for the EA's trades trade.SetExpertMagicNumber(MagicNumber); // Return initialization success return(INIT_SUCCEEDED); }
Пояснение:
Подключаем торговую библиотеку
- Чтобы использовать класс CTrade, необходимо импортировать торговую библиотеку с помощью директивы #include<Trade/Trade.mqh>. Класс CTrade упрощает работу с торговыми операциями, предоставляя функции для управления тейк-профитом и стоп-лоссом, открытия рыночных и отложенных ордеров, а также выполнения других действий, связанных с торговлей.
Создание экземпляра класса CTrade
- CTrade trade; — эта строка создает экземпляр класса CTrade, который будет использоваться на протяжении всей работы советника для открытия и управления сделками.
- Переменная MagicNumber объявляется как входная с помощью ключевого слова Input. Это позволяет пользователю советника задать уникальный идентификатор для сделок, которые открывает EA.
Почему это важно?
- Магический номер позволяет отличать сделки, открытые советником, от других сделок, включая ручные.
- С его помощью советник может управлять только своими позициями, не вмешиваясь в другие.
- Изменяя магик, пользователь может запускать советник на разных инструментах или на одном инструменте с разными таймфреймами и условиями рынка.
int OnInit() { // Set the magic number for the EA's trades trade.SetExpertMagicNumber(MagicNumber); // Return initialization success return(INIT_SUCCEEDED); }
Функция инициализации:
Функция OnInit() выполняется при запуске советника. С помощью метода SetExpertMagicNumber() указанное значение Magic Number присваивается советнику, чтобы все сделки, открытые этим экземпляром, имели этот идентификатор. Этот подход обеспечивает гибкость: пользователь может задавать разные значения Magic Number для разных сценариев торговли, что удобно при управлении несколькими экземплярами советника на разных инструментах или стратегиях.
2.3. Получение значений RSI и данных по свечам
Для корректной работы советника крайне важно получать точные рыночные данные. Логика данного советника строится на анализе свечных моделей и индикатора RSI, поэтому критично получать эти значения надежно. Данные позволяют советнику определить, соответствуют ли экстремумы RSI конкретным свечам, что важно для точного определения точек входа и выхода. Комбинируя значения RSI с информацией по свечам, советник оценивает рыночные условия комплексно, выявляя перекупленность и перепроданность в контексте движения цены. Такой подход повышает точность и логичность принимаемых торговых решений.
2.3.1. Получение значений RSI
Как уже обсуждалось, получение значений RSI — ключевой этап для анализа рыночной ситуации. Процесс состоит из двух частей: настройка параметров RSI с помощью хэндла, созданного функцией iRSI, и извлечение фактических значений RSI с использованием функции CopyBuffer().
Пример:
#include <Trade/Trade.mqh> CTrade trade; // Magic number input int MagicNumber = 1111; //RSI handle int rsi_handle; double rsi_buffer[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Set the magic number (for trading, not relevant to RSI retrieval here) trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Display the most recent RSI value for verification Comment("rsi_buffer[0]: ", rsi_buffer[0]); }
Пояснение:
Организация буфера RSI
- В функции OnInit() используется ArraySetAsSeries(), чтобы индексация в массиве rsi_buffer была как у таймсерии. Это значит, что самое последнее значение RSI будет находиться в индексе 0, что упрощает доступ к актуальным данным.
Создание хэндла RSI
- В функции OnTick() вызывается функция iRSI, которая инициализирует хэндл RSI (rsi_handle). Эта функция задает свойства индикатора: символ, таймфрейм, период и цена расчета.
Параметры:
- _Symbol — торговый инструмент, для которого рассчитывается RSI.
- PERIOD_CURRENT — текущий таймфрейм графика.
- 14 — период RSI (часто используемое значение по умолчанию).
- PRICE_CLOSE — RSI вычисляется по ценам закрытия свечей.
Копирование значений RSI
- Функция CopyBuffer() извлекает значения RSI и заполняет массив rsi_buffer. Копирование начинается с последнего значения (смещение 1). За один раз извлекается до 100 значений.
Параметры:
- 0 — индекс буфера RSI (для основной линии),
- 1 — начальная позиция (второе по свежести значение), поскольку последнее значение RSI ещё формируется и может быть нестабильным.
- 100 — количество значений, которые нужно получить.
Так советник всегда будет работать с актуальными данными RSI, что критически важно для анализа перекупленности и перепроданности, а также для принятия торговых решений.
Отображение значения RSI
- Для проверки корректности извлечения данных самое последнее значение RSI (rsi_buffer[0]) можно вывести на график с помощью функции Comment().
- Это позволяет убедиться, что советник имеет доступ к актуальной информации для анализа рыночных условий и принятия решений.
Результат:
2.3.2. Получение данных по свечам
Для анализа ценовой активности и сопоставления ее с показателями RSI необходимо получать данные по свечам. Советник получает ключевую информацию о свечах: открытие (Open), закрытие (Close), максимум (High), минимум (Low), временные метки (Timestamp), соответствующие этим значениям. Эти данные необходимы для оценки рыночных условий и принятия торговых решений.
Пример:
#include <Trade/Trade.mqh> CTrade trade; // Magic number input int MagicNumber = 1111; int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); // Display the most recent candlestick data for verification Comment("open[0]: ", open[0], "\nclose[0]: ", close[0], "\nhigh[0]: ", high[0], "\nlow[0]: ", low[0], "\ntime[0]: ", time[0]); }
Пояснение:
Организация массивов свечей
- В функции OnInit() используется ArraySetAsSeries(), чтобы установить индексацию массивов open, close, high, low и time как у таймсерии.
- При этом самые свежие данные будут находиться в индексе 0, что упрощает доступ к последним сформированным свечам и их анализ.
Копирование данных по свечам
- В функции OnTick() используются функции CopyOpen, CopyClose, CopyHigh, CopyLow и CopyTime для получения соответствующих данных по свечам.
Параметры:
- _Symbol — текущий торговый инструмент,
- PERIOD_CURRENT — таймфрейм графика (например, M1, H1),
- 1 — копирование начинается с предпоследней свечи, так как последняя (индекс 0) еще не до конца сформирована,
- 100 — извлекается до 100 свечей для анализа.
Каждая функция заполняет свой массив:
- open[] — цены открытия свечей,
- close[] — цены закрытия,
- high[] — значения цен максимума,
- low[] — значения цен минимума,
- time[] — временные метки открытия свечей для синхронизации с ценовым движением.
Отображение данных по свечам
Для проверки корректности получения данных можно вывести информацию о последней завершенной свече (индекс 0) на график с помощью функции Comment():
- open[0] — цена открытия,
- close[0] — цена закрытия,
- high[0] — максимум свечи,
- low[0] — минимум свечи,
- time[0] — время открытия свечи.
2.3.3. Определение максимумов и минимумов RSI
Советник должен распознавать минимумы RSI, когда индикатор опускается ниже уровня перепроданности (30), и максимумы RSI, когда индикатор поднимается выше уровня перекупленности (70). Чтобы выявить потенциальные зоны интереса, эти точки RSI связываются с конкретными свечами на графике. Этот этап является фундаментом работы советника, так как именно эти метки используются при построении логики снятия ликвидности.
Максимумы и минимумы RSI определяются довольно просто:
Минимум RSI:
Максимум RSI:
Пример:
// Magic number input int MagicNumber = 1111; // RSI handle and buffer int rsi_handle; double rsi_buffer[]; // Candlestick data arrays double open[]; double close[]; double high[]; double low[]; datetime time[]; // Variables to store high and low levels double max_high = 0; // Maximum high for the candlesticks datetime min_time1 = 0; // Time of the maximum high candlestick double min_low = 0; // Minimum low for the candlesticks datetime min_time2 = 0; // Time of the minimum low candlestick // Variables to store RSI highs and lows datetime time_low = 0; // Time of the RSI low datetime times_high = 0; // Time of the RSI high //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number for the EA trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data (open, close, high, low, time) CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); // Loop to find the maximum high from a bullish candlestick pattern for(int i = 0; i < 12; i++) { // Check for a bullish pattern: current close < open and previous close > open if(close[i] < open[i] && close[i+1] > open[i+1]) { // Calculate the maximum high between the two candlesticks max_high = MathMax(high[i], high[i+1]); // Record the time of the corresponding candlestick min_time1 = MathMin(time[i], time[i+1]); break; } } // Loop to find the minimum low from a bearish candlestick pattern for(int i = 0; i < 12; i++) { // Check for a bearish pattern: current close > open and previous close < open if(close[i] > open[i] && close[i+1] < open[i+1]) { // Calculate the minimum low between the two candlesticks min_low = MathMin(low[i], low[i+1]); // Record the time of the corresponding candlestick min_time2 = MathMin(time[i], time[i+1]); break; } } // Loop to find the RSI low point for(int i = 0; i < 12; i++) { // Check if the RSI is oversold and forms a low point if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { // Record the time of the RSI low time_low = time[i+1]; break; } } // Loop to find the RSI high point for(int i = 0; i < 12; i++) { // Check if the RSI is overbought and forms a high point if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { // Record the time of the RSI high times_high = time[i+1]; break; } } }
Пояснение:
Минимумы RSI (условия перепроданности)
- Цикл проходит по массиву RSI и ищет точки, где: значение RSI опускается ниже 30 (rsi_buffer[i+1] < 30), а затем начинает расти (rsi_buffer[i] > rsi_buffer[i+1]).
- Эти условия указывают на то, что RSI достиг минимума и начинает разворот.
- Временная метка соответствующей свечи сохраняется в массиве time_low.
Максимумы RSI (условия перекупленности)
- Цикл ищет точки, где значение RSI поднимается выше 70 (rsi_buffer[i+1] > 70), а затем начинает падать (rsi_buffer[i] < rsi_buffer[i+1]).
- Эти условия показывают, что RSI достиг максимума и начинает разворот.
- Временная метка соответствующей свечи сохраняется в массиве times_high.
2.3.4. Разметка минимумов и максимумов на графике
2.3.4.1. Разметка минимума на графике
После распознания минимума RSI программа анализирует данные свечей, чтобы определить точный минимальный уровень на графике. Поскольку для формирования минимума RSI требуется две последовательные точки, советник анализирует минимумы двух соответствующих свечей для вычисления точного уровня.
Это важный уровень, поскольку он задает область, в которой советник будет ожидать потенциальное снятие ликвидности.
Логика:
- RSI формирует минимум, когда:
- rsi_buffer[i+1] < 30 — RSI опускается ниже уровня перепроданности,
- rsi_buffer[i] > rsi_buffer[i+1] — RSI начинает расти после достижения минимума.
- Как только минимум RSI подтвержден, программа определяет минимальное значение с помощью массива low[] двух соответствующих свечей.
- Это уровень минимального минимума, где советник ожидает потенциальный разворот или снятие ликвидности.
- Итак, программа извлекает минимальные значения двух соответствующих свечей из массива минимумов.
- Используя функцию MathMin(), определяется наименьшее значение между этими двумя минимумами, которое становится уровнем для мониторинга снятия ликвидности.
- Этот минимальный уровень служит точкой, где мы и ожидаем потенциальный разворот или снятие ликвидности — ключевой фактор для принятия торговых решений.
// Loop to find RSI and candlestick lows for(int i = 0; i < 12; i++) { // Check if the RSI is oversold and forms a low point if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { // Record the time of the RSI low time_low = time[i+1]; // Find the minimum low from the two corresponding candlesticks min_low = (double)MathMin(low[i], low[i+1]); // Break the loop once the low is found break; } }
2.3.4.2. Разметка максимума на графике
После распознания максимума RSI программа анализирует данные свечей, чтобы определить точный максимальный уровень на графике. Поскольку для формирования максимума RSI требуется две последовательные точки, советник использует максимумы двух соответствующих свечей для вычисления точного уровня.
Этот максимальный уровень служит ориентиром для зоны, в которой советник будет ожидать потенциальное снятие ликвидности. Логика здесь зеркальна той, что используется для минимумов.
Логика:
- RSI формирует максимум, когда:
- rsi_buffer[i+1] > 70 — RSI поднимается выше уровня перекупленности,
- rsi_buffer[i] < rsi_buffer[i+1] — RSI начинает падать после достижения максимума.
- Как только максимум RSI подтвержден, программа определяет максимальный максимум с помощью массива high[] двух соответствующих свечей.
- Этот максимальный максимум задает область, где мы будем ожидать возможное снятие ликвидности.
- После идентификации максимума RSI программа получает значения максимума двух свечей из массива high[].
- С помощью функции MathMax() определяется наибольшее значение между ними, которое и становится уровнем для мониторинга снятия ликвидности.
// Loop to find RSI and candlestick highs for(int i = 0; i < 12; i++) { // Check if the RSI is overbought and forms a high point if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { // Record the time of the RSI high times_high = time[i+1]; // Find the maximum high from the two corresponding candlesticks max_high = (double)MathMax(high[i], high[i+1]); // Break the loop once the high is found break; } }
2.3.5. Контроль RSI максимумов и минимумов с задержкой в 12 свечей
При работе с RSI может возникнуть ситуация, когда в зоне перекупленности или перепроданности формируется несколько максимумов или минимумов подряд. Это приведет к тому, что на графике будут отмечены разные уровни, что внесет шум. Чтобы исключить частое обновление уровней, используется правило 12 свечей: после фиксации максимума или минимума RSI обновление уровня не происходит, пока не закроется как минимум 12 новых свечей.
Пример:
// Magic number input int MagicNumber = 1111; int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; double max_high = 0; datetime min_time1 = 0; double min_low = 0; datetime min_time2 = 0; datetime time_low = 0; datetime times_high = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); static double max_high_static = max_high; static datetime min_time1_static = min_time1; static double min_low_static = min_low; static datetime min_time2_static = min_time2; int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] < open[i] && close[i+1] > open[i+1]) { max_high = (double)MathMax(high[i],high[i+1]); min_time1 = (datetime)MathMin(time[i],time[i+1]); break; } } int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] > open[i] && close[i+1] < open[i+1]) { min_low = (double)MathMin(low[i],low[i+1]); min_time2 = (datetime)MathMin(time[i],time[i+1]); break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { time_low = time[i+1]; break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { times_high = time[i+1]; break; } } if((total_bar_high == 0 || total_bar_high > 12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > 13) { max_high_static = 0; min_time1_static = 0; } if((total_bar_low == 0 || total_bar_low > 12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > 13) { min_low_static = 0; min_time2_static = 0; } }
Пояснение:
Определение максимума/минимума RSI и свечей
- Для определения появления максимума или минимума используется анализ активности индикатора относительно уровней перекупленности или перепроданности.
- Если это так, программа определяет самый высокий максимум или самый низкий минимум среди связанных свечей.
- С помощью функции Bars() программа вычисляет, сколько свечей сформировалось с момента последнего максимума или минимума.
- Максимальные и минимальные значения обновляются только по истечении не менее 12 свечей, что позволяет избежать частых изменений.
- Если прошло более 13 свечей, а условие перекупленности/перепроданности больше не выполняется, сохраненные уровни обнуляются.
- Это гарантирует, что советник будет работать только с актуальными уровнями и не будет учитывать устаревшие сигналы.
Чтобы повысить наглядность, советник будет строить на графике линии, обозначающие найденные максимумы и минимумы RSI. Это не только позволяет советнику использовать эти объекты для программного определения максимумов и минимумов, но и дает трейдерам возможность вручную отслеживать критические уровни.
#include <Trade/Trade.mqh> CTrade trade; // Magic number input int MagicNumber = 1111; int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; double max_high = 0; datetime min_time1 = 0; double min_low = 0; datetime min_time2 = 0; datetime time_low = 0; datetime times_high = 0; string high_obj_name = "High_Line"; string low_obj_name = "Low_Line"; long chart_id; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); static double max_high_static = max_high; static datetime min_time1_static = min_time1; static double min_low_static = min_low; static datetime min_time2_static = min_time2; //CHART ID chart_id = ChartID(); int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] < open[i] && close[i+1] > open[i+1]) { max_high = (double)MathMax(high[i],high[i+1]); min_time1 = (datetime)MathMin(time[i],time[i+1]); break; } } int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] > open[i] && close[i+1] < open[i+1]) { min_low = (double)MathMin(low[i],low[i+1]); min_time2 = (datetime)MathMin(time[i],time[i+1]); break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { time_low = time[i+1]; break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { times_high = time[i+1]; break; } } if((total_bar_high == 0 || total_bar_high > 12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > 13) { max_high_static = 0; min_time1_static = 0; } if((total_bar_low == 0 || total_bar_low > 12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > 13) { min_low_static = 0; min_time2_static = 0; } ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,0,min_time1_static,max_high_static,TimeCurrent(),max_high_static); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,3); ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,0,min_time2_static,min_low_static,TimeCurrent(),min_low_static); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,3); }
Пояснение:
Код создает на графике две линии тренда с помощью функции ObjectCreate(): high_obj_name, которая размечает максимум, и low_obj_name, которая строит минимум. Эти линии тянутся до настоящего времени (TimeCurrent()) и выводятся из вычисленных максимальных и минимальных уровней цен (max_high_static и min_low_static) для соответствующих моментов времени (min_time1_static и min_time2_static). Это позволяет трейдерам визуально отслеживать максимумы и минимумы на графике.
С помощью функции ObjectSetInteger() можно настроить внешний вид этих линий. Линия для high отображается зеленым цветом, а линия для low (low_obj_name) — красным. Толщина обеих линий устанавливается в 3, чтобы уровни были легко читаемыми. Таким образом, трейдер может визуально контролировать критические зоны, а советник использовать их для программной логики входа.
2.3.7. Условия входа в сделку (Liquidity Sweeps)
Торговые условия должны точно соответствовать определенным рыночным сценариям, чтобы затем исполнять заложенную торговую логику. Давайте рассмотрим логику совершения сделок на покупку и продажу на этих уровнях.
// Magic number input int MagicNumber = 1111; int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; double max_high = 0; datetime min_time1 = 0; double min_low = 0; datetime min_time2 = 0; datetime time_low = 0; datetime times_high = 0; string high_obj_name = "High_Line"; string low_obj_name = "Low_Line"; long chart_id; double take_profit; double ask_price = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); static double max_high_static = max_high; static datetime min_time1_static = min_time1; static double min_low_static = min_low; static datetime min_time2_static = min_time2; //GETTING TOTAL POSITIONS int totalPositions = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { totalPositions++; } } } ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(totalPositions < 1) { if(((low[0] < min_low_static && close[0] > min_low_static && close[0] > open[0]) || (low[1] < min_low_static && close[0] > min_low_static && close[0] > open[0]) || (low[2] < min_low_static && close[0] > min_low_static && close[0] > open[0] && close[1] < open[1]))) { take_profit = (close[0] - low[0]) * 3 + close[0]; trade.Buy(0.5,_Symbol,ask_price, low[0], take_profit); } else if(((high[0] > max_high_static && close[0] < max_high_static && close[0] < open[0]) || (high[1] > max_high_static && close[0] < max_high_static && close[0] < open[0]) || (high[2] > max_high_static && close[0] < max_high_static && close[0] < open[0] && close[1] > open[1]))) { take_profit = MathAbs((high[0] - close[0]) * 3 - close[0]); // Adjusted take-profit calculation trade.Sell(0.5,_Symbol,ask_price, high[0], take_profit); } } //CHART ID chart_id = ChartID(); int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] < open[i] && close[i+1] > open[i+1]) { max_high = (double)MathMax(high[i],high[i+1]); min_time1 = (datetime)MathMin(time[i],time[i+1]); break; } } int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] > open[i] && close[i+1] < open[i+1]) { min_low = (double)MathMin(low[i],low[i+1]); min_time2 = (datetime)MathMin(time[i],time[i+1]); break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { time_low = time[i+1]; break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { times_high = time[i+1]; break; } } if((total_bar_high == 0 || total_bar_high > 12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > 13) { max_high_static = 0; min_time1_static = 0; } if((total_bar_low == 0 || total_bar_low > 12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > 13) { min_low_static = 0; min_time2_static = 0; } ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,0,min_time1_static,max_high_static,TimeCurrent(),max_high_static); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,3); ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,0,min_time2_static,min_low_static,TimeCurrent(),min_low_static); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,3); }
Пояснение:
Предотвращение множественных позиций с помощью totalPositions
Мы используем переменную totalPositions, чтобы убедиться, что в любой момент времени у советника не больше одной открытой позиции. Этот механизм проверяет все открытые позиции, магическое число (идентификатор сделок советника) и символ. Если активных сделок по текущему символу и магику нет (totalPositions < 1), советник может рассматривать новый вход.
Это исключает перекрытие позиций, упрощает управление стратегией.
Условия для открытия сделки на покупку (Low Liquidity Sweep)
Сигнал возникает при снятии ликвидности ниже ключевого минимума (min_low_static), когда формируется бычье подтверждение.
Условия
Пробой минимума:
- Хотя бы одна из последних трех свечей (low[0], low[1], low[2]) пробила уровень min_low_static.
Бычья реакция:
- Текущая цена закрытия (close[0]) вернулась выше уровня min_low_static — признак бычьего настроения после разворота.
- Кроме того, Свеча должны быть бычьей (close[0] > open[0]) и показывать восходящий импульс.
- Если две свечи до текущей (close[1] < open[1]) были медвежьими, добавляется компонент разворота как подтверждение.
Исполнение сделки
При выполнении условий устанавливается ордер на покупку:- Take Profit — тройной диапазон от закрытия до минимума (tp = (close[0] - low[0]) * 3 + close[0]).
- Stop Loss — минимум свечи (low[0]).
Условия для открытия сделки на продажу (High Liquidity Sweep)
Логика продажи зеркально отражает логику покупки, но фокусируется на определении ликвидности на ранее установленном максимуме (max_high_static) с медвежьим подтверждением.
Условия
Пробой выше максимума:
- Любая из трех последних свечей (high[0], high[1] или high[2]) должна превышать найденный максимальный максимум, что указывает на подъем ликвидности выше этого ключевого уровня.
Медвежий разворот:
- Текущая цена закрытия (close[0]) должна опуститься ниже максимального максимума (max_high_static), что указывает на неспособность удержать прорыв.
- Кроме того, свеча должна быть медвежьей (close[0] < open[0]), что указывает на нисходящий импульс.
- Если две свечи до текущей (close[1] > open[1]) были бычьими, это указывает на потенциальный разворот.
Исполнение сделки
При выполнении нужных условий размещается ордер на продажу:
- Take Profit — в размере тройного диапазон от максимума до закрытия (take_profit = MathAbs((high[0] - close[0]) * 3 - close[0])).
- Stop Loss — на максимуме свечи (high[0]).
Выводы
Чтобы добиться точности принятия торговых решений и ограничить риски, анализируем общее количество позиций totalPositions и условия для торговли:
- Сделки на покупку совершаются после падения ниже ключевого минимума с бычьим восстановлением.
- Сделки на продажу совершаются после подъема выше ключевого максимума с медвежьим разворотом.
Также одним из ограничений этого метода является то, что стоп-лосс устанавливается динамически на основе минимума [0] или максимума [0] свечи. Это означает, что риск на сделку варьируется в зависимости от размера свечи, что приводит к неравномерному распределению риска. Для решения этой проблемы стратегия должна позволять указывать фиксированный размер риска на сделку в процентах от баланса счета (например, 2%). Это позволяет контролировать риск через расчет размера позиции на основе расстояния между ценой входа и стоп-лоссом в соответствии с указанным процентом риска.
2.3.8. Управление рисками и модификация безубытка
Одним из недостатков динамических стопов является то, что риск на сделку напрямую зависит от размера свечи. При этом размеры свечей могут существенно различаться, соответственно и риск на свечу будет разный. Чтобы это исправить, можно указывать фиксированный риск в процентах от депозита. Кроме того, можно корректировать уровни стопа, чтобы точно зафиксировать определенную прибыль, если цена вдруг развернется. Это такая страховка прибыли за счет переноса стопа в безубыток.
Пример:
//+------------------------------------------------------------------+ //| MQL5INDICATORS_PROJECT4.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "ForexYMN" #property link "crownsoyin@gmail.com" #property version "1.00" #include <Trade/Trade.mqh> CTrade trade; // Magic number input int MagicNumber = 1111; input double account_balance = 1000; // Account Balance input double percentage_risk = 2.0; // How many percent of the account do you want to risk per trade? input bool allow_modify = false; // Do you allow break even modifications? input int rrr = 3; // Choose Risk Reward Ratio int rsi_handle; double rsi_buffer[]; double open[]; double close[]; double high[]; double low[]; datetime time[]; double max_high = 0; datetime min_time1 = 0; double min_low = 0; datetime min_time2 = 0; datetime time_low = 0; datetime times_high = 0; string high_obj_name = "High_Line"; string low_obj_name = "Low_Line"; long chart_id; double take_profit; double ask_price = 0; double lot_size; double risk_Amount; double points_risk; // Risk modification double positionProfit = 0; double positionopen = 0; double positionTP = 0; double positionSL = 0; double modifyLevel = 0.0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); // Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); // Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); // Set the magic number trade.SetExpertMagicNumber(MagicNumber); return (INIT_SUCCEEDED); // Indicate successful initialization } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int currBars = iBars(_Symbol,_Period); static int prevBars = currBars; if(prevBars == currBars) return; prevBars = currBars; // Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, 0, 1, 100, rsi_buffer); // Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, 1, 100, open); CopyClose(_Symbol, PERIOD_CURRENT, 1, 100, close); CopyHigh(_Symbol, PERIOD_CURRENT, 1, 100, high); CopyLow(_Symbol, PERIOD_CURRENT, 1, 100, low); CopyTime(_Symbol, PERIOD_CURRENT, 1, 100, time); static double max_high_static = max_high; static datetime min_time1_static = min_time1; static double min_low_static = min_low; static datetime min_time2_static = min_time2; //GETTING TOTAL POSITIONS int totalPositions = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { totalPositions++; } } } ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(totalPositions < 1) { if(((low[0] < min_low_static && close[0] > min_low_static && close[0] > open[0]) || (low[1] < min_low_static && close[0] > min_low_static && close[0] > open[0]) || (low[2] < min_low_static && close[0] > min_low_static && close[0] > open[0] && close[1] < open[1]))) { take_profit = (close[0] - low[0]) * rrr + close[0]; points_risk = close[0] - low[0]; double riskAmount = account_balance * (percentage_risk / 100.0); double minus = NormalizeDouble(close[0] - low[0],5); lot_size = CalculateLotSize(_Symbol, riskAmount, minus); trade.Buy(lot_size,_Symbol,ask_price, low[0], take_profit); } else if(((high[0] > max_high_static && close[0] < max_high_static && close[0] < open[0]) || (high[1] > max_high_static && close[0] < max_high_static && close[0] < open[0]) || (high[2] > max_high_static && close[0] < max_high_static && close[0] < open[0] && close[1] > open[1]))) { take_profit = MathAbs((high[0] - close[0]) * rrr - close[0]); // Adjusted take-profit calculation points_risk = MathAbs(high[0] - close[0]); double riskAmount = account_balance * (percentage_risk / 100.0); double minus = NormalizeDouble(high[0] - close[0],5); lot_size = CalculateLotSize(_Symbol, riskAmount, minus); trade.Sell(lot_size,_Symbol,ask_price, high[0], take_profit); } } //CHART ID chart_id = ChartID(); int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] < open[i] && close[i+1] > open[i+1]) { max_high = (double)MathMax(high[i],high[i+1]); min_time1 = (datetime)MathMin(time[i],time[i+1]); break; } } int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(int i = 0; i < 12; i++) { if(close[i] > open[i] && close[i+1] < open[i+1]) { min_low = (double)MathMin(low[i],low[i+1]); min_time2 = (datetime)MathMin(time[i],time[i+1]); break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] < 30 && rsi_buffer[i] > rsi_buffer[i+1]) { time_low = time[i+1]; break; } } for(int i = 0; i < 12; i++) { if(rsi_buffer[i+1] > 70 && rsi_buffer[i] < rsi_buffer[i+1]) { times_high = time[i+1]; break; } } if((total_bar_high == 0 || total_bar_high > 12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > 13) { max_high_static = 0; min_time1_static = 0; } if((total_bar_low == 0 || total_bar_low > 12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > 13) { min_low_static = 0; min_time2_static = 0; } ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,0,min_time1_static,max_high_static,TimeCurrent(),max_high_static); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen); ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,3); ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,0,min_time2_static,min_low_static,TimeCurrent(),min_low_static); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed); ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,3); if(allow_modify) { for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { positionopen = PositionGetDouble(POSITION_PRICE_OPEN); positionTP = PositionGetDouble(POSITION_TP); positionSL = PositionGetDouble(POSITION_SL); positionProfit = PositionGetDouble(POSITION_PROFIT); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionSL - positionopen) - positionopen,4)); if(ask_price <= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionopen - positionSL) + positionopen,4)); if(ask_price >= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } } } } } //+------------------------------------------------------------------+ //| Function to calculate the lot size based on risk amount and stop loss //+------------------------------------------------------------------+ double CalculateLotSize(string symbol, double riskAmount, double stopLossPips) { // Get symbol information double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); // Calculate pip value per lot double pipValuePerLot = tickValue / point; // Calculate the stop loss value in currency double stopLossValue = stopLossPips * pipValuePerLot; // Calculate the lot size double lotSize = riskAmount / stopLossValue; // Round the lot size to the nearest acceptable lot step double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); lotSize = MathFloor(lotSize / lotStep) * lotStep; // Ensure the lot size is within the allowed range double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(lotSize < minLot) lotSize = minLot; if(lotSize > maxLot) lotSize = maxLot; return lotSize; }
Пояснение:
input double account_balance = 1000; // Account Balance input double percentage_risk = 2.0; // How many percent of the account do you want to risk per trade? input bool allow_modify = false; // Do you allow break even modifications? input int rrr = 3; // Choose Risk Reward Ratio
Это ключевые параметры управления рисками в торговой стратегии. account_balance — задает фиксированный баланс счета, который используется для расчета суммы риска на сделку, обеспечивая стабильность. percentage_risk — определяет процент баланса счета, которым трейдер готов рисковать в каждой сделке. Помогает контролировать экспозицию и поддерживать стабильный уровень риска. rrr (Risk-Reward Ratio) — задает желаемое соотношение риска к прибыли, чтобы стратегия стремилась к большей прибыли по сравнению с убытками. allow_modify — управляет тем, следует ли переводить стоп-лосс в безубыток, если сделка движется в прибыльную сторону. Если параметр включен, мы фиксируем прибыль и уменьшает риск в положительных сделках. Совместно эти параметры создают дисциплинированный подход к торговле, обеспечивая постоянный риск на сделку и защиту прибыли.
take_profit = (close[0] - low[0]) * rrr + close[0]; points_risk = close[0] - low[0]; double riskAmount = account_balance * (percentage_risk / 100.0); double minus = NormalizeDouble(close[0] - low[0],5); lot_size = CalculateLotSize(_Symbol, riskAmount, minus); trade.Buy(lot_size,_Symbol,ask_price, low[0], take_profit);
- take_profit = (close[0] - low[0]) * rrr + close[0]; рассчитывает уровень тейк-профита, умножая расстояние от текущего закрытия свечи до её минимума на коэффициент риск-прибыль (rrr), и прибавляет его к цене закрытия.
- points_risk = close[0] - low[0]; определяет расстояние от цены входа (close) до стоп-лосса (low).
- riskAmount = account_balance * (percentage_risk / 100.0); вычисляет сумму риска в денежном выражении, основываясь на балансе счета и заданном проценте риска.
- lot_size = CalculateLotSize(_Symbol, riskAmount, minus); рассчитывает размер лота в зависимости от величины риска и расстояния до стоп-лосса, чтобы сделка соответствовала заданным параметрам управления риском.
Для сделок на продажу:
- take_profit = MathAbs((high[0] - close[0]) * rrr - close[0]); рассчитывает тейк-профит, используя расстояние от закрытия свечи до её максимума, умноженное на RRR.
- points_risk = MathAbs(high[0] - close[0]); определяет величину риска от точки входа до стоп-лосса (максимум свечи).
- Остальная логика (расчёт суммы риска и размера лота) полностью аналогична сделкам на покупку, что обеспечивает единое управление рисками.
if(allow_modify) { for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { positionopen = PositionGetDouble(POSITION_PRICE_OPEN); positionTP = PositionGetDouble(POSITION_TP); positionSL = PositionGetDouble(POSITION_SL); positionProfit = PositionGetDouble(POSITION_PROFIT); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionSL - positionopen) - positionopen,4)); if(ask_price <= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id)) { modifyLevel = MathAbs(NormalizeDouble((positionopen - positionSL) + positionopen,4)); if(ask_price >= modifyLevel) { trade.PositionModify(ticket, positionopen, positionTP); } } } } }
Код также реализует логику изменения активных позиций, добавляя защиту через перевод в безубыток. Вот что делает этот код:
Проверка разрешения на модификацию (allow_modify):
- Блок if(allow_modify) гарантирует, что модификация выполняется только если пользователь ее включил.
- Цикл for проходит по всем открытым сделкам (PositionsTotal()). Для каждой позиции выбирается её тикет (PositionGetTicket(i)) и загружается с помощью PositionSelectByTicket(ticket). Получение параметров позиции Для каждой выбранной позиции программа получает: positionopen — цену открытия. positionTP — уровень тейк-профита. positionSL — уровень стоп-лосса. positionProfit — текущую прибыль. Модификация Sell-позиций Проверяется, что позиция является POSITION_TYPE_SELL. Вычисляется modifyLevel как абсолютное расстояние между ценой открытия и стоп-лоссом. Если текущая цена Ask прошла это расстояние, стоп-лосс переносится в точку открытия (break-even), тейк-профит не изменяется:
Выбор позиции:
- Получаем тикет каждой позиции (PositionGetTicket(i)) и используем его для выбора позиции (PositionSelectByTicket(ticket)).
Получение деталей позиции:
Для каждой выбранной позиции получаем следующие данные:
- positionopen — цена, по которой была открыта позиция.
- positionTP — уровень тейк-профита позиции.
- positionSL — уровень стоп-лосса позиции.
- positionProfit — текущая прибыль по позиции.
Модификация позиции Sell:
- Код проверяет, является ли позиция позицией на продажу (POSITION_TYPE_SELL).
- Затем код вычисляет уровень modifyLevel как абсолютную разницу между стоп-лоссом (positionSL) и ценой открытия (positionopen), а потом проверяет, достигла ли текущая цена предложения (ask_price) этого уровня или прошла его.
- Если условие выполняется, стоп-лосс модифицируется до цены открытия (уровня безубытка), при этом тейк-профит не меняется (trade.PositionModify(ticket, positionopen, positionTP)).
- Аналогично для позиции на покупку (POSITION_TYPE_BUY) - вычисляется уровень modifyLevel как абсолютная разница между ценой открытия и стоп-лоссом, а также проверяется, прошла ли цена этот уровень.
- Если условие выполняется, стоп-лосс модифицируется до цены открытия (уровня безубытка), тейк-профит не меняется.
Заключение
В этой статье мы рассмотрели использование встроенных индикаторов в MQL5 на практическом примере. Мы разработали советник для автоматической торговли на примере RSI — советник с входами и выходами по сигналам перекупленности и перепроданности. Мы подчеркнули важные моменты в этом процессе, такие как использование соотношения риска и прибыли (RRR) для сопоставления целевых показателей прибыли с рассчитанными рисками. Также рассмотрели использование постоянного % риска для каждой сделки. Чтобы сделать стратегию более надежной и успешной, мы также добавили модификацию уровня безубыточности, чтобы фиксировать прибыль внутри позиции. Это позволяет советнику адаптироваться к изменениям на рынке.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16514
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Здравствуйте, Олуватосин,
Я принял к сведению ваш запрос.