Разработка инструментария для анализа Price Action (Часть 50): Создание модуля согласования сигналов RVGI, CCI и SMA на MQL5
Содержание
Введение
Определение момента, когда рынок входит в состояние перекупленности или перепроданности, остается одной из самых сложных задач в торговле. Даже опытным трейдерам часто трудно точно определить момент, когда сильный тренд достигает пика и разворот уже близок. Можно часами анализировать графики и ждать продолжения текущего движения, а потом слишком поздно понять, что импульс уже сменился. Для многих, особенно для тех, кто еще осваивает торговлю по Price Action, эта неопределенность оборачивается путаницей и эмоциональными решениями, подрывающими дисциплину исполнения сделок. За годы работы с графиками я наблюдал это снова и снова. Дело не в нехватке навыков. Рынки просто не движутся в идеальном равновесии. Они порождают ложные сигналы, резкие всплески и обманчивые откаты, способные заманить в ловушку даже самого дисциплинированного трейдера. Эта реальность и вдохновила серию "Разработка инструментария для анализа Price Action". Проект призван упростить интерпретацию рынка, снизить эмоциональные помехи и сделать анализ графиков более упорядоченным.
С самого начала цель была четкой и практичной: разложить поведение цены на измеримые компоненты, которые можно тестировать, автоматизировать и последовательно применять. Каждая задача – выявление настоящих разворотов, подтверждение пробоев, фильтрация слабого импульса – становится возможностью сформулировать системные правила. Эти правила должны воспроизводиться в коде и оставаться устойчивыми в реальной торговле. С теми же трудностями сталкивается и разработчик системы, и именно этот общий опыт способствует ее постоянному совершенствованию. В нынешних условиях автоматизация дает явное преимущество. MQL5 позволяет превращать внимательные наблюдения в детерминированную логику. Хэндлы индикаторов, чтение данных из буферов и проверки закрытых баров работают без усталости. Хорошо спроектированный советник выполняет объективный анализ и поддерживает дисциплину, позволяя трейдеру сосредоточиться на исполнении сделок и управлении рисками.
В этой части я представляю аналитический инструмент, призванный внести ясность в условиях неопределенного рынка. Стратегия включает три компонента: RVGI, CCI(14) и SMA(30), при этом у каждого компонента – своя роль. RVGI дает сглаженный сигнал импульса. CCI выявляет зоны перекупленности и перепроданности. SMA задает структурный контекст и трендовое смещение. Когда эти элементы согласуются, система формирует единый сигнал, который подтверждает направление движения, указывает на возможные развороты и выявляет ранний потенциал пробоя.

Далее в статье рассматриваются логика принятия решений и реализация на MQL5. Здесь будет показано четкое соответствие между концепцией и кодом, включая хэндлы индикаторов, логику сигналов, визуализацию и жизненный цикл советника – в частности OnInit и OnTick. Цель – не очередной индикатор. Наша цель – создать структурированный механизм принятия решений, который грамотно читает цену, снижает шум и повышает уверенность в торговле. Этот инструментарий превращает многолетние наблюдения в адаптивную систему, призванную упростить и улучшить анализ Price Action в реальных торговых условиях.
Логика системы и ее архитектура
Каждый аналитический инструмент начинается с четкой цели. Для этой системы основная цель – выявлять моменты истощения и перехода в рамках преобладающего тренда: те критические точки, где импульс начинает ослабевать и формируется потенциальный разворот. Для этого нужна система, способная улавливать тонкие сдвиги рыночной энергии, направления и тайминга. Для этого стратегия объединяет три разных, но взаимосвязанных компонента: распознавание импульса (RVGI), оценку состояния рынка (CCI) и фильтрацию тренда (SMA). В совокупности эти элементы образуют синхронизированный механизм, который считывает и интерпретирует ритм рынка, давая целостное представление об энергии, чрезмерном отклонении и направленном смещении.
1. Распознавание импульса – роль RVGI
Индикатор Relative Vigor Index (RVGI) измеряет устойчивую силу, стоящую за движением цены, сравнивая положение цены закрытия в диапазоне бара и затем сглаживая эту зависимость во времени. В устойчивом восходящем тренде цены закрытия тяготеют к максимумам, и RVGI растет; в нисходящем тренде они тяготеют к минимумам, и RVGI снижается. В этой стратегии RVGI служит первым уровнем подтверждения: именно он показывает, поддерживает ли энергия рынка возможный разворот. Четкое пересечение основной линии RVGI с сигнальной линией указывает на смену направления импульса, и для снижения внутрибарного шума мы используем только пересечения по закрытым барам. Дивергенция – например, снижение RVGI на фоне обновления ценой максимума – предупреждает о том, что текущее движение теряет силу, и подготавливает сценарий к подтверждению со стороны CCI и SMA. При таком использовании RVGI не дает сигнал на вход сам по себе; он лишь подтверждает, что импульс меняется, благодаря чему любой разворот, на который указывает CCI и который рассматривается в контексте SMA, получает большую убедительность.
2. Оценка состояния рынка – функция CCI
CCI с периодом 14 показывает, насколько далеко цена отклонилась от своего статистического среднего. Для этого он сравнивает типичную цену с ее скользящей средней с поправкой на среднее отклонение. На практике CCI показывает, когда цена чрезмерно отклоняется от своего обычного диапазона, и потому хорошо подходит для выявления состояний перекупленности и перепроданности. На экстремальных значениях CCI работает как предупреждающий сигнал. Показания выше +100 обычно указывают на перекупленность, а ниже -100 – на перепроданность. Сами по себе такие экстремальные значения не гарантируют немедленного разворота, но они отмечают зоны, где разворот становится более вероятным при смене направления импульса.
В вопросах тайминга и подтверждения CCI лучше всего работает как дополнительная проверка, а не как самостоятельный сигнал. Он помогает ответить на простой вопрос: зашел ли рынок достаточно далеко, чтобы ожидать встречное движение? В сочетании с таким индикатором импульса, как RVGI, CCI добавляет важный контекст. Положительный CCI вблизи +100 при затухании импульса RVGI повышает вероятность формирования вершины. Глубоко отрицательный CCI при ослаблении нисходящего импульса указывает на предпосылки для отскока. На практике система использует CCI как фильтр истощения. Для подтвержденного бычьего разворота CCI должен достичь уровня -100 или уйти ниже него, а затем пересечь -100 снизу вверх. Для подтвержденного медвежьего разворота CCI должен достичь уровня +100 или уйти выше него, а затем опуститься обратно ниже +100. Эта проверка "возврата от экстремума" предотвращает входы, пока цена все еще остается в экстремальной зоне, и повышает планку качества сигнала.
В нашей стратегии CCI никогда не используется отдельно. Он должен согласовываться с импульсом RVGI и контекстом, заданным SMA. Вместе они образуют трехслойное подтверждение: SMA определяет структурное смещение, RVGI подтверждает сдвиг импульса, а CCI подтверждает, что цена достигла экстремальной зоны и начинает выходить из нее. Такое двойное подтверждение – разворот импульса плюс возврат CCI из экстремальной зоны – заметно снижает количество ложных сигналов и повышает надежность каждого отмеченного на графике разворота.
bool CheckReversalSignal(int shift, bool &isBuy) { // defensive if(iBars(_Symbol,_Period) <= shift + InpCCI_Period + InpRVI_Smooth + 4) return(false); // CCI read double cciNow = cci_at(shift, InpCCI_Period); double cciPrev = cci_at(shift + 1, InpCCI_Period); // RVI read double rviMainNow = rvi_main_at(shift); double rviMainPrev = rvi_main_at(shift + 1); double rviSigNow = rvi_signal_at(shift); double rviSigPrev = rvi_signal_at(shift + 1); // SMA & price double smaVal = /* read or compute SMA */; double price = EA_Close(shift); bool priceAboveSMA = (price > smaVal); bool priceBelowSMA = (price < smaVal); // flags bool cciBuy = (cciPrev <= CCI_LOWER) && (cciNow > CCI_LOWER); bool cciSell = (cciPrev >= CCI_UPPER) && (cciNow < CCI_UPPER); bool rviUp = (rviMainPrev <= rviSigPrev) && (rviMainNow > rviSigNow); bool rviDown = (rviMainPrev >= rviSigPrev) && (rviMainNow < rviSigNow); // final combination (mean-reversion style) if(priceBelowSMA && cciBuy && rviUp) { isBuy = true; return(true); } if(priceAboveSMA && cciSell && rviDown){ isBuy = false; return(true); } return(false); }
3. Фильтрация тренда – роль SMA(30)
Простая скользящая средняя с периодом 30 служит структурной основой этой аналитической системы. Она сглаживает рыночный шум и выявляет базовое направленное движение цены. В более широком масштабе SMA выступает как линия динамического равновесия: когда цена устойчиво держится выше нее, рынок считается бычьим, а устойчивое удержание ниже нее отражает медвежью среду. Это делает SMA простой, но мощной точкой отсчета для оценки трендового контекста.
Помимо определения тренда, SMA(30) играет ключевую роль в тайминге и подтверждении сигнала. В тренде она выделяет потенциальные зоны истощения, когда цена слишком далеко уходит от своего среднего значения, показывая, что импульс может быть чрезмерно растянут. Когда цена возвращается к SMA, это часто указывает на начало коррекции или возможного разворота. Эти естественные колебания вокруг SMA образуют структуру, на основе которой RVGI и CCI могут выполнять более глубокий анализ.
С точки зрения стратегии SMA позволяет разделить поведение рынка на два рабочих состояния – импульсное расширение и возврат к среднему. В фазах расширения, когда цена уверенно следует за SMA и движется с импульсом, мы избегаем сделок против тренда и ждем подтверждения истощения. В фазах возврата к среднему, когда цена слишком далеко отклоняется выше или ниже SMA и импульс начинает ослабевать, мы готовимся к сигналам разворота. В нашей текущей конфигурации SMA(30) определяет эти ключевые границы. Когда цена находится выше SMA, система отслеживает признаки возможного медвежьего разворота; он подтверждается, когда CCI достигает зоны выше +100, а затем возвращается ниже +100, а RVGI формирует нисходящее пересечение. И наоборот, когда цена находится ниже SMA, мы ожидаем бычий разворот; он подтверждается, когда CCI опускается до -100 или ниже, а RVGI формирует восходящее пересечение.
Такое согласование формирует трехслойную модель подтверждения:
- SMA(30) определяет структурный контекст и направленное смещение цены.
- CCI(14) выявляет зоны чрезмерного отклонения цены.
- RVGI подтверждает смену импульса через пересечения.
Вместе эти компоненты гарантируют, что каждый сигнал будет формироваться сбалансированным сочетанием структуры, импульса и истощения. SMA не просто выступает как линия скользящей средней – она становится осью, вокруг которой строится вся система, помогая синхронизировать момент входа, направление и уверенность сигнала. Объединяя ее с RVGI и CCI, мы превращаем обычно статичный фильтр тренда в динамичную систему принятия решений, которая адаптируется к ритму рынка и повышает точность выявления разворотов.
Формирование единого сигнала
Настоящая сила этой системы заключается в синхронизации трех компонентов. Сигнал считается действительным только тогда, когда все три уровня – импульс (RVGI), состояние рынка (CCI) и тренд (SMA) – согласованы между собой.
- Бычий сценарий: формируется, когда RVGI дает восходящее пересечение, CCI выходит из зоны перепроданности, а цена находится ниже SMA(30).

- Медвежий сценарий: формируется, когда RVGI дает нисходящее пересечение, CCI выходит из зоны перекупленности, а цена находится выше SMA(30).

Эта логика тройного подтверждения отсеивает большинство ложных сигналов и концентрируется только на точках, где действительно сходятся рыночные факторы. Такой подход делает ставку на качество, а не на количество, ориентируясь на стабильные сделки с высокой вероятностью, а не на частые ненадежные входы. Этот структурированный подход дает трейдерам дисциплинированный способ точно интерпретировать рыночную динамику, снижая эмоциональные реакции и поддерживая системное принятие решений.
Реализация на MQL5
Настройка базовой структуры
Сначала закладываем основу нашего советника. В начале кода мы добавляем блок заголовочных комментариев, где указываем авторские права, номер версии и ссылки на связанные ресурсы. Это помогает поддерживать код в порядке и на профессиональном уровне. Затем мы используем директивы #property, такие как #property copyright, #property link и #property version, чтобы задать метаданные, по которым MetaTrader корректно идентифицирует наш скрипт. Такая структура обеспечивает хорошую документированность кода и облегчает его дальнейшую поддержку, особенно когда мы делимся им с другими или возвращаемся к нему впоследствии.
//+------------------------------------------------------------------+ //| RVGI_CCI_SMA_Panel_EA.mq5 | //| Copyright 2025, Christian Benjamin. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00"
Определение входных параметров
Далее мы добавляем входные параметры, чтобы сделать систему гибкой и адаптируемой. Эти параметры важны, потому что позволяют настраивать систему без переписывания кода. Например, мы задаем InpSMA_Period, чтобы указать, по скольким барам будет рассчитываться Simple Moving Average, и это влияет на определение тренда. Аналогичным образом InpCCI_Period задает окно проверки для индикатора Commodity Channel Index, который выявляет состояния перекупленности и перепроданности. InpRVI_Smooth определяет, по скольким барам усредняется RVGI, сглаживая рыночный шум и точнее отражая импульс.
Мы также включаем параметры ATR: InpATR_Period задает, сколько баров учитывается при расчете ATR, а InpATR_Multiplier масштабирует ATR при установке уровней стоп-лосса и тейк-профита. Кроме того, переключатели вроде InpUseATRStop позволяют выбирать, будут ли стопы рассчитываться по ATR или по экстремумам свинга. Эти параметры позволяют управлять чувствительностью системы и риском, чтобы оптимизировать ее под разные инструменты и таймфреймы.
// User inputs input int InpSMA_Period = 30; // SMA period input int InpCCI_Period = 14; // CCI period input int InpRVI_Smooth = 4; // RVI smoothing input int InpATR_Period = 14; // ATR period input double InpATR_Multiplier = 1.5; // ATR multiplier for stops input bool InpUseATRStop = true; // Use ATR for stops input int InpSL_SwingBars = 10; // Swing lookback input double InpTarget1_ATR = 1.0; // Target 1 ATR multiple input double InpTarget2_ATR = 2.0; // Target 2 ATR multiple input int InpSignalLookback = 1; // Bars to look back input int InpCheckIntervalMs = 500; // Check interval in ms
Инициализация хэндлов индикаторов и переменных
После задания входных параметров мы объявляем внутренние переменные для хранения хэндлов индикаторов: hSMA для скользящей средней и hATR для оценки волатильности. Мы инициализируем их значением INVALID_HANDLE, чтобы показать, что они еще не созданы. В функции OnInit() мы создаем хэндлы индикаторов с помощью таких функций, как iMA() для SMA и iATR() для ATR.
// Declare handles int hSMA = INVALID_HANDLE; int hATR = INVALID_HANDLE; // Inside OnInit() hSMA = iMA(_Symbol, _Period, InpSMA_Period, 0, MODE_SMA, PRICE_CLOSE); hATR = iATR(_Symbol, _Period, InpATR_Period);
Такая схема позволяет эффективно обращаться к буферам индикаторов на каждом тике, не пересчитывая индикаторы вручную каждый раз, что повышает производительность. Мы также инициализируем переменные lastSignalledShift (для отслеживания последнего бара, на котором был сформирован сигнал), lastCheckMs (для управления частотой оценки рыночных условий), lastSignalText и lastSignalTime. Эти переменные помогают предотвращать дублирование сигналов и управлять частотой проверок, сохраняя отзывчивость системы и не перегружая процессор.
// Internal variables int lastSignalledShift = -1; uint lastCheckMs = 0; string lastSignalText = "none"; datetime lastSignalTime = 0;
Создание вспомогательных функций для читаемости
Чтобы код оставался чистым, удобным в сопровождении и пригодным для повторного использования, мы создаем вспомогательные функции. Например, make_object_name() создает уникальные имена для объектов графика, объединяя префикс с временной меткой, что важно при создании нескольких графических объектов, чтобы они не перезаписывали друг друга. Аналогичным образом, format_time_dt() преобразует значения datetime в удобочитаемые строки, делая метки и комментарии понятнее. Мы также создаем функции-обертки, такие как EA_Close(), EA_Open(), EA_High(), EA_Low() и EA_Time(), которые внутри вызывают стандартные iClose(), iOpen() и т.д., но с упрощенными параметрами. Эти обертки делают доступ к ценовым данным проще и единообразнее во всем коде, уменьшая количество ошибок и улучшая читаемость.
// Generate unique object names string make_object_name(const string prefix, datetime t) { return prefix + IntegerToString((int)t); } // Format time for display string format_time_dt(const datetime t) { return(TimeToString(t, TIME_DATE | TIME_SECONDS)); } // Wrappers for data retrieval double EA_Close(int shift) { return(iClose(_Symbol, _Period, shift)); } double EA_Open(int shift) { return(iOpen(_Symbol, _Period, shift)); } double EA_High(int shift) { return(iHigh(_Symbol, _Period, shift)); } double EA_Low(int shift) { return(iLow(_Symbol, _Period, shift)); } datetime EA_Time(int shift) { return(iTime(_Symbol, _Period, shift)); }
Расчет индикатора RVGI
Теперь перейдем к реализации основного индикатора импульса – RVGI. Начнем с rvi_raw_at_shift(), которая вычисляет взвешенную сумму цен закрытия и открытия за четыре бара. Эта функция делает акцент на недавней рыночной активности, придавая больший вес средним барам, что помогает уловить текущий импульс. Для нормализации исходного значения функция делит его на диапазон от максимума до минимума для каждого бара, благодаря чему индикатор не зависит от масштаба. Важно, что мы добавляем проверку на деление на ноль: если знаменатель слишком мал, функция возвращает ноль, что поддерживает стабильность.
// Raw RVGI calculation double rvi_raw_at_shift(int shift) { if(iBars(_Symbol, _Period) <= shift + 3) return 0.0; double num0 = EA_Close(shift) - EA_Open(shift); double num1 = EA_Close(shift + 1) - EA_Open(shift + 1); double num2 = EA_Close(shift + 2) - EA_Open(shift + 2); double num3 = EA_Close(shift + 3) - EA_Open(shift + 3); double num = num0 + 2.0 * num1 + 2.0 * num2 + num3; double den0 = EA_High(shift) - EA_Low(shift); double den1 = EA_High(shift + 1) - EA_Low(shift + 1); double den2 = EA_High(shift + 2) - EA_Low(shift + 2); double den3 = EA_High(shift + 3) - EA_Low(shift + 3); double den = den0 + 2.0 * den1 + 2.0 * den2 + den3; if(MathAbs(den) < DBL_EPSILON) return 0.0; return num / den; }
Далее мы создаем rvi_main_at(), которая сглаживает исходный RVGI по указанному числу баров (часто по четырем), чтобы уменьшить рыночный шум и получить более надежную линию импульса.
// Smoothed RVGI (main line) double rvi_main_at(int shift) { int n = MathMax(1, InpRVI_Smooth); double sum = 0.0; for(int i=0; i<n; i++) { sum += rvi_raw_at_shift(shift + i); } return sum / n; }
Это делается путем усреднения исходных значений в пределах окна сглаживания. После этого функция rvi_signal_at() идет дальше и повторно сглаживает основную линию RVGI, формируя сигнальную линию, которая необходима для выявления пересечений, указывающих на изменение рыночного импульса. Вместе эти функции дают устойчивый и чувствительный индикатор импульса, который помогает заранее выявлять потенциальные развороты.
// Signal line (smoothed RVGI) double rvi_signal_at(int shift) { int n = MathMax(1, InpRVI_Smooth); double sum = 0.0; for(int i=0; i<n; i++) { sum += rvi_main_at(shift + i); } return sum / n; }
Реализация расчета CCI
Затем мы реализуем функцию cci_at() для ручного расчета Commodity Channel Index. Сначала вычисляется типичная цена для каждого бара в окне проверки: (максимум + минимум + закрытие) / 3. Затем мы суммируем эти типичные цены за период, чтобы получить среднее значение, и вычисляем среднее отклонение – насколько каждая типичная цена отклоняется от этого среднего. Отклонение текущей типичной цены от среднего, масштабированное нужным образом, дает значение CCI. Такое ручное вычисление обеспечивает точность и согласованность индикатора, а также позволяет легко настраивать параметры. CCI помогает выявлять ситуации, когда рынок чрезмерно растянут в ту или иную сторону, что критично для выбора момента входа и выхода.
double cci_at(int shift, int period) { int totalBars = iBars(_Symbol, _Period); if(totalBars <= shift + period - 1) return 0.0; // Not enough bars double sumTP = 0.0; for(int k=0; k<period; k++) { int idx = shift + k; double tp = (EA_High(idx) + EA_Low(idx) + EA_Close(idx)) / 3.0; sumTP += tp; } double smaTP = sumTP / period; double meanDev = 0.0; for(int k=0; k<period; k++) { int idx = shift + k; double tp = (EA_High(idx) + EA_Low(idx) + EA_Close(idx)) / 3.0; meanDev += MathAbs(tp - smaTP); } meanDev /= period; if(meanDev < DBL_EPSILON) return 0.0; double tp_current = (EA_High(shift) + EA_Low(shift) + EA_Close(shift)) / 3.0; double cci = (tp_current - smaTP) / (0.015 * meanDev); return cci; }
Доступ к данным ATR
Далее мы реализуем функцию get_atr_current(), которая получает текущее значение ATR с помощью хэндла индикатора hATR. Мы используем CopyBuffer(), чтобы получить последнее значение ATR; если хэндл недействителен или данные недоступны, функция возвращает ноль. Это значение важно, потому что позволяет задавать адаптивные уровни стоп-лосса и тейк-профита, отражающие текущую рыночную волатильность. Использование ATR позволяет избежать слишком близких или слишком далеких уровней, сохраняя хороший баланс риск/прибыль.
double get_atr_current()
{
if(hATR == INVALID_HANDLE)
return 0.0;
double tmp[];
if(CopyBuffer(hATR, 0, 0, 1, tmp) <= 0)
return 0.0;
return tmp[0];
}Построение торговых уровней
Когда все данные индикаторов готовы, мы определяем структуру Levels для хранения цены входа, стоп-лосса и двух целей тейк-профита, а также дополнительной информации, такой как направление сделки и временная метка. Функция build_levels() рассчитывает эти уровни динамически. Сначала функция задает вход по текущей цене закрытия. Если включены стопы на основе ATR, уровни стоп-лосса и тейк-профита рассчитываются умножением ATR на заданные пользователем коэффициенты. Если ATR недоступен или отключен, функция ищет минимумы и максимумы свинга в окне проверки, чтобы задать уровни стопа, добавляя буферы для надежности. Такой подход позволяет уровням адаптироваться к текущей рыночной среде, сохраняя баланс между риском и потенциальной прибылью.
struct Levels { double entry; double stop; double tp1; double tp2; datetime time; bool isBuy; }; Levels build_levels(bool isBuy, int shift) { Levels L; L.entry = EA_Close(shift); L.time = EA_Time(shift); L.isBuy = isBuy; double atr = get_atr_current(); if(atr <= 0.0) atr = SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 10; if(InpUseATRStop) { L.stop = isBuy ? L.entry - atr * InpATR_Multiplier : L.entry + atr * InpATR_Multiplier; } else { // Swing low/high search double extremePrice = isBuy ? EA_Low(shift) : EA_High(shift); for(int i=1; i<=InpSL_SwingBars; i++) { double v = isBuy ? EA_Low(shift + i) : EA_High(shift + i); if(v == WRONG_VALUE) continue; if(isBuy && v < extremePrice) extremePrice = v; if(!isBuy && v > extremePrice) extremePrice = v; } // Add buffer double buffer = SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 5; L.stop = isBuy ? extremePrice - buffer : extremePrice + buffer; } // Targets L.tp1 = isBuy ? L.entry + atr * InpTarget1_ATR : L.entry - atr * InpTarget1_ATR; L.tp2 = isBuy ? L.entry + atr * InpTarget2_ATR : L.entry - atr * InpTarget2_ATR; return L; }
Визуализация сигналов и уровней
Чтобы наглядно показывать сигналы, мы реализуем функцию draw_signal_objects(). Эта функция создает графические элементы на графике: стрелки, отмечающие точку входа, горизонтальные линии для уровней стоп-лосса и тейк-профита, а также метки с точными ценами. Каждый объект получает уникальное имя за счет объединения префикса с временной меткой, что предотвращает конфликты при появлении нескольких сигналов. Мы настраиваем цвета, толщину линий и форму стрелок для наглядности. После создания этих объектов мы вызываем ChartRedraw(), чтобы сразу обновить график. Такая визуализация позволяет с одного взгляда проверить сигналы и уровни, упрощая ручное управление.
void draw_signal_objects(const Levels &L) { string baseName = make_object_name("RVGI_SIG_", L.time); // Draw arrow string arrowName = baseName + (L.isBuy ? "_BUY_ARR" : "_SELL_ARR"); if(ObjectFind(0, arrowName) >= 0) ObjectDelete(0, arrowName); ObjectCreate(0, arrowName, OBJ_ARROW, 0, L.time, L.entry); ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, L.isBuy ? 233 : 234); ObjectSetInteger(0, arrowName, OBJPROP_COLOR, L.isBuy ? InpArrowUpColor : InpArrowDownColor); ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, arrowName, OBJPROP_BACK, true); // Stop line string slName = baseName + "_SL"; if(ObjectFind(0, slName) >= 0) ObjectDelete(0, slName); ObjectCreate(0, slName, OBJ_HLINE, 0, 0, L.stop); ObjectSetDouble(0, slName, OBJPROP_PRICE, L.stop); ObjectSetInteger(0, slName, OBJPROP_COLOR, InpSL_Color); ObjectSetString(0, slName, OBJPROP_TEXT, "SL: " + DoubleToString(L.stop, _Digits)); // TP1 line string tp1Name = baseName + "_TP1"; if(ObjectFind(0, tp1Name) >= 0) ObjectDelete(0, tp1Name); ObjectCreate(0, tp1Name, OBJ_HLINE, 0, 0, L.tp1); ObjectSetDouble(0, tp1Name, OBJPROP_PRICE, L.tp1); ObjectSetInteger(0, tp1Name, OBJPROP_COLOR, InpTP_Color); ObjectSetString(0, tp1Name, OBJPROP_TEXT, "TP1: " + DoubleToString(L.tp1, _Digits)); // TP2 line string tp2Name = baseName + "_TP2"; if(ObjectFind(0, tp2Name) >= 0) ObjectDelete(0, tp2Name); ObjectCreate(0, tp2Name, OBJ_HLINE, 0, 0, L.tp2); ObjectSetDouble(0, tp2Name, OBJPROP_PRICE, L.tp2); ObjectSetInteger(0, tp2Name, OBJPROP_COLOR, InpTP_Color); ObjectSetString(0, tp2Name, OBJPROP_TEXT, "TP2: " + DoubleToString(L.tp2, _Digits)); ChartRedraw(); }
Создание панели данных в реальном времени
Мы также добавляем функцию draw_panel(), чтобы выводить краткий обзор прямо на графике. Она получает текущие значения индикаторов – таких как SMA, RVGI, CCI и ATR, – либо из буферов, либо путем ручного расчета, если хэндлы индикаторов недействительны. Мы форматируем эти данные в ясную, читаемую строку и выводим ее с помощью Comment(). Эта панель сразу показывает рыночные условия и состояние системы, помогая принимать обоснованные решения и следить за текущей эффективностью без переключения между экранами.
void draw_panel() { string txt = "RVGI + CCI + SMA Panel\n"; // SMA double smaValue = 0; if(hSMA != INVALID_HANDLE) { double tmp[]; if(CopyBuffer(hSMA, 0, 0, 1, tmp) > 0) smaValue = tmp[0]; else smaValue = 0; // fallback } else { // manual calculation fallback double sum=0; int cnt=0; for(int i=0; i<InpSMA_Period; i++) { if(iBars(_Symbol, _Period) <= i) break; sum += EA_Close(i); cnt++; } if(cnt>0) smaValue = sum / cnt; } txt += "SMA(" + IntegerToString(InpSMA_Period) + "): " + DoubleToString(smaValue, _Digits) + "\n"; // RVGI & CCI double rviM = rvi_main_at(1); double rviS = rvi_signal_at(1); double cciV = cci_at(1, InpCCI_Period); txt += "RVI: " + DoubleToString(rviM,5) + " sig: " + DoubleToString(rviS,5) + "\n"; txt += "CCI: " + DoubleToString(cciV,2) + "\n"; // ATR double atr = get_atr_current(); txt += "ATR: " + DoubleToString(atr, _Digits) + "\n"; // Last signal info txt += "Last signal: " + lastSignalText; if(lastSignalTime != 0) txt += " " + TimeToString(lastSignalTime, TIME_DATE | TIME_SECONDS); Comment(txt); }
Оценка рыночных условий и генерация сигналов
Основной механизм принятия решений реализован в функции check_for_signals(). Мы реализуем ограничение частоты с помощью GetTickCount(), чтобы избежать чрезмерной нагрузки на процессор и контролировать, как часто выполняется оценка. Мы определяем значение shift на основе параметра окна проверки и проверяем, что для анализа загружено достаточно баров. Затем мы получаем данные индикаторов из буферов или пересчитываем их, если хэндлы недействительны, чтобы расчеты всегда оставались актуальными.
void check_for_signals() { uint now = (uint)GetTickCount(); if(lastCheckMs != 0 && (now - lastCheckMs) < (uint)InpCheckIntervalMs) return; // Throttle lastCheckMs = now; int shift = InpSignalLookback; // Verify enough bars loaded if(iBars(_Symbol, _Period) <= shift + 6) return; // Retrieve SMA double smaVal = 0; if(hSMA != INVALID_HANDLE) { double tmp[]; if(CopyBuffer(hSMA, 0, shift, 1, tmp) > 0) smaVal = tmp[0]; } else { // fallback manual SMA double sum=0; int cnt=0; for(int i=shift; i<shift + InpSMA_Period; i++) { if(iBars(_Symbol, _Period) <= i) break; sum += EA_Close(i); cnt++; } if(cnt > 0) smaVal = sum / cnt; else return; // Not enough data } // Get indicator values double rviMainNow = rvi_main_at(shift); double rviMainPrev = rvi_main_at(shift + 1); double rviSigNow = rvi_signal_at(shift); double rviSigPrev = rvi_signal_at(shift + 1); double cciNow = cci_at(shift, InpCCI_Period); double cciPrev = cci_at(shift + 1, InpCCI_Period); double price = EA_Close(shift); // Trend and momentum analysis bool priceAboveSMA = (price > smaVal); bool priceBelowSMA = (price < smaVal); bool rviCrossUp = (rviMainPrev <= rviSigPrev) && (rviMainNow > rviSigNow); bool rviCrossDown = (rviMainPrev >= rviSigPrev) && (rviMainNow < rviSigNow); bool cciOverbought = (cciNow >= 100); bool cciOversold = (cciNow <= -100); // Generate signals based on conditions if(priceAboveSMA && cciOversold && rviCrossUp) { // Buy signal if(shift != lastSignalledShift) { Levels L = build_levels(true, shift); draw_signal_objects(L); lastSignalledShift = shift; lastSignalText = "BUY @ " + DoubleToString(price, _Digits); lastSignalTime = EA_Time(shift); // Send alerts if enabled... } } else if(priceBelowSMA && cciOverbought && rviCrossDown) { // Sell signal if(shift != lastSignalledShift) { Levels L = build_levels(false, shift); draw_signal_objects(L); lastSignalledShift = shift; lastSignalText = "SELL @ " + DoubleToString(price, _Digits); lastSignalTime = EA_Time(shift); // Send alerts if enabled... } } // Update panel info draw_panel(); }
Далее мы анализируем сигналы индикаторов: проверяем, пересек ли RVGI свою сигнальную линию вверх или вниз – это указывает на изменение импульса, – и оцениваем, находится ли CCI в зонах перекупленности или перепроданности. Мы также сравниваем текущую цену с SMA, чтобы определить тренд. Когда все эти условия совпадают – например, RVGI пересекает сигнальную линию вверх, CCI находится в зоне перепроданности, а цена ниже SMA, – формируется сигнал на покупку. Обратные условия формируют сигнал на продажу. Мы исключаем дублирование сигналов, отслеживая последнее значение shift, по которому уже был сформирован сигнал. Когда новый сигнал подтверждается, мы рассчитываем торговые уровни, отображаем их на графике и обновляем внутренние переменные. Мы также отправляем алерты или уведомления, если они включены, чтобы не пропустить сигнал.
Управление жизненным циклом советника и очистка ресурсов
Наконец, в OnInit() создаются хэндлы индикаторов для SMA и ATR, выполняется проверка ошибок и инициализация переменных. Мы также выводим информационную панель для быстрого обновления статуса.
// In OnInit() int OnInit() { hSMA = iMA(_Symbol, _Period, InpSMA_Period, 0, MODE_SMA, PRICE_CLOSE); hATR = iATR(_Symbol, _Period, InpATR_Period); // Initialize variables lastSignalledShift = -1; lastCheckMs = 0; lastSignalText = "none"; lastSignalTime = 0; draw_panel(); Print("EA initialized"); return(INIT_SUCCEEDED); }
В OnDeinit() мы освобождаем хэндлы индикаторов с помощью IndicatorRelease(), чтобы корректно освободить ресурсы. В OnTick() просто вызывается check_for_signals(), чтобы система работала непрерывно. Такая структура обеспечивает эффективность, отзывчивость и корректную управляемость советника на протяжении всей его работы.
// In OnDeinit() void OnDeinit(const int reason) { if(hSMA != INVALID_HANDLE) IndicatorRelease(hSMA); if(hATR != INVALID_HANDLE) IndicatorRelease(hATR); } // In OnTick() void OnTick() { check_for_signals(); }
Тестирование и результаты
После внедрения системы мы провели тщательное тестирование, чтобы оценить ее эффективность в реальных рыночных условиях. Результаты ясно показывают, что интегрированные индикаторы и уровни формируют надежные сигналы входа, которые визуально подтверждаются на графике, обеспечивая прозрачность и простоту проверки.
Визуальное подтверждение и точность сигналов
Первым наблюдаемым результатом стала точная визуальная индикация сигналов на графике. Система генерировала стрелки покупки и продажи именно в те моменты, когда условия RVGI, CCI и SMA совпадали в соответствии с нашей логикой. Например, зеленая стрелка вверх появлялась, когда RVGI пересекал сигнальную линию вверх, CCI находился в зоне перепроданности, а цена находилась ниже SMA, что указывало на сильный сигнал на покупку. Эти сигналы сопровождались четко обозначенными уровнями входа, стоп-лосса и тейк-профита, обеспечивая мгновенное визуальное подтверждение анализа системы.
Сигнал на покупку: GBPUSD M1

Сигнал на продажу: Volatility 75 (1s) Index M1

Проверка данных индикаторов
Вторым ключевым результатом стала согласованность показаний индикаторов в момент появления сигналов. Подробные данные показали, что RVGI двигался вверх, подтверждая усиление импульса. CCI находился в зоне перепроданности (ниже -100), что указывало на чрезмерную растянутость рынка вниз и готовность к развороту. Одновременно цена находилась ниже SMA, что соответствовало бычьему сценарию. Это многоуровневое подтверждение повышало уверенность в том, что сигналы основаны на надежной технической логике, и снижало количество ложных срабатываний.

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

Общая эффективность
- Сочетание сигналов индикаторов (RVGI, CCI, SMA) обеспечивало стабильные и надежные точки входа, подтвержденные визуально.
- Уровни стопов и целей были заданы удачно, что позволяло оптимально управлять соотношением риск/прибыль.
- Визуальные элементы и данные системы позволяли легко выполнять ручную проверку и корректировку, обеспечивая прозрачность.
Исходя из этих результатов, мы рекомендуем дополнительно настроить параметры (такие как периоды индикаторов, множители ATR и условия сигналов) так, чтобы оптимизировать работу системы для конкретных рынков или таймфреймов. Непрерывное тестирование и проверка помогут повысить точность, сократить количество ложных сигналов и улучшить общую прибыльность.
Заключение
Эта система вносит ясность в одну из самых сложных проблем трейдинга: надежное выявление разворотов. Требуя трех независимых подтверждений – тренда, импульса и истощения, – модуль выделяет моменты, когда структура цены, направленный импульс и рыночное истощение сходятся. В результате сигналов становится меньше, но каждый из них имеет четкое техническое обоснование и наглядное подтверждение на графике. Рассматривайте советник как инструмент анализа, а не как готовое решение, которое можно просто подключить и сразу использовать. Используйте его для визуализации сетапов, записи сигналов и системной проверки идей. Проводите воспроизводимое тестирование на исторических данных с логикой по закрытому бару, фиксируйте в логе каждый сигнал в привязке к базовой структуре и анализируйте вневыборочную эффективность перед изменением параметров. Такая дисциплина предотвращает переоптимизацию и сохраняет достоверность ваших результатов.
Управляйте риском осознанно. Используйте предлагаемые советником стоп-лоссы – на основе минимумов и максимумов свинга или рассчитанные по ATR – как базовые правила. Определяйте размер позиции так, чтобы защищать капитал, и отдавайте предпочтение частичным выходам или трейлинг-стопам, соответствующим вашему торговому горизонту и допустимому риску. Если в дальнейшем вы создадите модуль исполнения, сохраните исходные правила сигналов и разработайте логику ордеров, которая опирается на те же подтверждения и ограничения по риску.
Наконец, воспринимайте этот инструмент как связующее звено между наблюдением и действием. Он переводит поведение цены в повторяемые правила, снижает эмоциональный шум и дает четкую основу для дисциплинированного принятия решений. Используйте его, чтобы отточить понимание рынка, зафиксировать то, что работает, и превратить систему из исследовательского инструмента в надежную часть вашего торгового процесса.
Обратите внимание! Файл с полным исходным кодом приложен в конце этой статьи. Нажмите на файл, чтобы скачать его, затем откройте и скомпилируйте его в MetaEditor. Тщательно протестируйте его и настройте параметры под свои предпочтения и торговые условия.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20262
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Разработка динамического мультивалютного советника (Часть 5): Скальпинг и свинг-трейдинг
Фундаментальные предобученные модели в трейдинге: прогнозирование временных рядов с TimesFM 2.5 от Google в MetaTrader 5
Рекуррентный количественный анализ (RQA) в MQL5: Разработка полноценной библиотеки для анализа
Нейросети в трейдинге: Принятие торговых решений с учётом неопределённости (Оценка неопределённости)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования