Разработка индикатора Volume Bubble на MQL5 с использованием стандартного отклонения
Введение
Всплеск объема может выглядеть впечатляюще, но без контекста это всего лишь более высокий бар. Трейдеры используют объем для оценки силы ценовых движений, однако стандартная гистограмма не показывает, является скачок действительно необычным или он просто часть нормального ритма рынка. Это затрудняет выявление значимых изменений в участии и еще больше затрудняет сравнение объемов с течением времени.
В данной статье мы решаем эту проблему, создавая индикатор Volume Bubble на языке MQL5. Вместо того чтобы рассматривать фактический («сырой») объем как простую последовательность баров, мы будем сравнивать его с недавним поведением рынка, используя окно ретроспективного анализа (lookback window), средний объем и стандартное отклонение. Затем мы нормализуем эти значения и отобразим их в виде пузырьков непосредственно на графике, где размер пузырька отражает интенсивность текущей активности относительно недавнего базового уровня. В результате получается более понятный и удобный способ быстрой оценки объема.
В этой статье вы узнаете, как:
- в MQL5 отличать тиковый объем от реального объема
- получить доступ к встроенным массивам объемов внутри индикаторов
- определить недавний рыночный контекст, используя окно ретроспективного анализа
- рассчитывать средний объем, дисперсию и стандартное отклонение
- нормализовать объем таким образом, чтобы он оставался сопоставимым при изменении условий
- преобразовать нормализованные значения в размеры пузырьков
- создавать графические объекты для визуализации пузырьков и управлять ими
- настраивать цвет, размер, положение и привязку объекта
- добавлять форматированные метки объема к большим пузырькам
- предотвратить дублирование, обновлять существующие объекты и удалять устаревшие элементы в процессе очистки
Обзор проекта и план его реализации
Прежде чем написать хотя бы одну строчку кода, важно представить всю картину того, что мы создаем, и как каждый элемент связан с последующим. В этой главе индикатор разбит на понятные этапы, так что к моменту его фактической реализации ничто не станет для нас неожиданностью.
Что мы будем создавать
Индикатор Volume Bubble — это индикатор, отображаемый непосредственно поверх свечного графика. Вместо того чтобы расположить гистограмму в отдельном окне, она размещает непосредственно над каждой свечой кружок, который можно назвать пузырьком. Размер пузырька показывает, насколько значителен объем данной свечи по сравнению с недавней активностью. Цвет показывает, увеличивается или уменьшается объем. А на самых экстремальных пиках появляется отформатированная метка, в компактном и понятном виде отображающая точное значение объема.
В итоге, индикатор будет вести себя на вашем графике следующим образом. На барах с небольшим объемом образуются пузырьки небольшого размера. На барах со слегка увеличенным объемом пузырьки получаются среднего размера. При очень большом объеме свечи образуются крупные пузырьки, которые будут сопровождаться текстовой меткой, отображающей фактический объем торговли. Зеленый пузырек означает, что объем торгов на этой свече выше, чем на предыдущей. Оранжевый цвет означает, что объем торгов ниже, чем у предыдущей свечи. Серый цвет используется для первой свечи в окне, поскольку ее не с чем сравнить.

Все, что касается этого индикатора, построено вокруг одного вопроса. Не является ли объем этой свечи необычным по сравнению с тем, что в недавнее время было нормой?
Чтобы объективно ответить на этот вопрос, воспользуемся стандартным отклонением, которое является статистической мерой, показывающей, насколько любое отдельное значение отклоняется от среднего значения. Полный расчет будет подробно рассмотрен в одной из последующих глав. Пока что достаточно знать, что результатом этого вычисления является одно число, которое мы назовем нормализованным объемом, и каждое визуальное решение, принимаемое индикатором, основывается на этом одном числе. Чем больше это число, тем больше пузырек.
План реализации
Индикатор разрабатывается в шесть четких этапов. Каждый уровень выполняет одну задачу и передает свой результат на следующий уровень.
Этап 1. Определение окна ретроспективного анализа
Индикатор не анализирует каждую свечу на графике. Он анализирует только последние N свечей, где N — это число баров, включаемых в анализ. Это окно (временной интервал) в N свечей определяет, что считается недавним и нормальным. Все, что находится за пределами этого окна, игнорируется. На этом этапе просто определяется, где это окно начинается на графике.
Этап 2. Расчет стандартного отклонения
После определения окна ретроспективного анализа перебираем все N свечей и выполняем три последовательных вычисления. Сначала идет среднее значение, то есть средний объем по этим N свечам. Далее следует дисперсия, которая показывает, насколько сильно числа разбросаны относительно среднего значения. Последним вычисляется стандартное отклонение, которое получается из дисперсии и служит нам базовым показателем типичного объема в этот период.
Этап 3. Нормализация объема каждой свечи
Теперь, после вычисления стандартного отклонения, мы можем оценить каждую свечу. Для получения нормализованного значения разделим исходный объем на стандартное отклонение. Это относительная мера активности, используемая в рамках данного индикатора; далее по тексту она интерпретируется как показатель силы объема относительно недавнего фона. Свеча считается относительно «тихой», если ее нормализованное значение меньше 1. Свеча считается чрезвычайно активной, если ее нормализованное значение равно 4 или более. Все последующее основывается на этой оценке.
Этап 4. Рисование пузырька
Для каждой свечи в окне мы рисуем кружок над ее наивысшей точкой на графике. Размер этого кружка определяется непосредственно нормализованным значением, рассчитанным на предыдущем этапе. Задаются пять уровней размера, каждый из которых соответствует диапазону нормализованных значений. Чем выше нормализованное значение, тем больше пузырек. Цвет пузырька определяется путем сравнения объема текущей свечи с объемом предыдущей свечи, что позволяет трейдеру мгновенно определить, ускоряется активность или замедляется.

Этап 5. Добавление метки объема для экстремальных всплесков объема
Когда нормализованное значение достигает четырех или поднимается выше этого значения, недостаточно только пузырька. Рядом со свечой располагается текстовая метка с указанием фактического значения объема. Для сохранения удобочитаемости графика большие числа автоматически сокращаются до компактного формата, поэтому, например, миллион отображается как 1M, а не как 1000000.

Этап 6. Управление жизненным циклом объекта
Большинство новичков игнорируют этот шаг, который поддерживает удобочитаемость графика и эффективность индикатора. Здесь необходимо позаботиться о трех вещах. Во-первых, необходимо избегать дублирования объектов: если пузырек уже существует, его следует обновлять, а не создавать заново; устаревшие объекты вне окна нужно удалять. Во-вторых, для свечей, которые выходят за левую границу окна, соответствующие пузырьки должны удаляться с графика, когда окно сдвигается вправо по мере появления новых баров и формируются новые свечи. В-третьих, все созданные индикатором объекты должны быть очищены, чтобы ничего не оставалось после его полного удаления с графика.
Понимание объема в MQL5
Уровень торговой активности на рынке за определенный период времени выражается объемом торгов. Он дает ответ на простой, но важный вопрос: какой уровень вовлеченности лежит в основе изменения цены? Высокий интерес и активная деятельность участников рынка подтверждаются большим объемом торгов. Низкий объем торгов может указывать на консолидацию, снижение интереса или слабый темп торгов. В результате объем торгов часто используется для выявления аномального поведения рынка, подтверждения тенденций и определения возможных разворотов.
MQL5 предоставляет два вида объема: тиковый объем и реальный объем, и понимание разницы между ними имеет важное значение.
Количество обновлений цены (тиков), происходящих в течение одного бара, измеряется тиковым объемом. При каждом изменении цены добавляется один тик. Это служит заменой рыночной активности, хотя и не отражает реальные объемы сделок. Тиковый объем становится основным методом оценки активности на децентрализованных рынках, таких как Форекс, где нет центральной биржи, регистрирующей общий объем торгов. Важно отметить, что тиковый объем часто имеет сильную корреляцию с фактическим объемом, что делает его полезной заменой для анализа. В свою очередь, реальный объем показывает, сколько контрактов или единиц актива было фактически проторговано за определенный период. Такой объем могут предложить только централизованные торговые площадки с зарегистрированными на бирже транзакциями. Поскольку реальный объем отражает фактически совершенные сделки, а не колебания цен, он предлагает более точный способ количественной оценки вовлеченности участников рынка.
Торгуемый актив определяет, какие категории объема будут доступны. Ввиду децентрализованного характера рынка и отсутствия центрального механизма отчетности, такие валютные пары, как EURUSD, обычно предоставляют только данные о тиковом объеме. Однако такие активы, как акции (например, акции Apple) и фьючерсы (например, фьючерсы на индекс S&P 500), обеспечивают реальный объем, поскольку они торгуются на централизованных биржах. Иногда, особенно у некоторых брокеров или при использовании связанных с биржевой торговлей инструментов, может быть доступен как тиковый объем, так и реальный объем торгов, что позволяет трейдерам выбирать для своих исследований оптимальный источник данных.
Крайне важно понимать эти различия, поскольку они влияют на то, как следует воспринимать объем. Поскольку тиковый объем является наиболее широко доступным во многих торговых средах и достаточен для создания надежного индикатора на основе объема, именно ему будет уделено в этой статье основное внимание.
Встроенный индикатор объема в MetaTrader 5
MetaTrader 5 предоставляет встроенный индикатор объема, позволяющий визуализировать торговую активность для каждого бара на текущем таймфрейме. Она отображается в виде гистограммы под основным графиком, где каждый столбик представляет собой зарегистрированный за этот период объем торгов. Это позволяет быстро оценить активность рынка, поэтому объем торгов является одним из первых инструментов, используемых трейдерами для понимания участия в деятельности рынка.

Индикатор включает в себя несколько настраиваемых параметров, которые управляют отображением объема торгов. Одна из ключевых настроек — Value Up («Увеличение значения») и Value Down («Уменьшение значения»), которые определяют цвета столбцов гистограммы. В отличие от цены, где бычья свеча (цена закрытия больше цены открытия) и медвежья свеча (цена закрытия меньше цены открытия) определяют направление, объем сравнивается относительно предыдущего бара.
- Value Up используется, когда объем текущего бара превышает объем предыдущего бара.
- Value Down используется, когда объем текущего бара ниже объема предыдущего бара.
Это означает, что цвет отражает изменения в активности, а не направление движения цены. Медвежья свеча может по-прежнему отображаться как Value Up, если ее объем выше, чем на предыдущем баре, что подчеркивает участие, а не направление.
Еще один важный параметр позволяет выбирать между тиковым объемом и реальным объемом. Тиковый объем подсчитывает обновления цен, тогда как реальный объем отражает фактическое торгуемое количество. Доступность этих параметров зависит от актива и брокера, а выбор неподдерживаемого типа приведет к тому, что окно индикатора будет пустым.
Хотя этот встроенный индикатор полезен, он все же отображает исходные значения объема. Это затрудняет быстрое определение того, является текущая активность относительно высокой или низкой по сравнению с недавним поведением рынка. Трейдерам приходится оценивать значимость визуально, без четкого ориентира. Именно это ограничение и обуславливает подход, представленный в данной статье, где мы преобразуем объем в нормализованную величину и представляем его с помощью пузырьков различного размера и цвета, что позволяет с первого взгляда более эффективно интерпретировать изменения активности.
Создание пузырьков объема
В этой главе мы шаг за шагом, но очень практичным способом, построим индикатор пузырьков объема (Volume Bubble). Во-первых, подумайте вот о чем: когда вы смотрите на общий объем торгов, можете ли вы с легкостью определить, является значение «максимумом» или «минимумом»? На самом деле, нет. Итак, первым делом мы создадим точку отсчета. Рассчитаем средний объем торгов (среднее значение объема) за последние несколько периодов, чтобы понять, как выглядит «норма». Затем мы пойдем дальше и измерим, насколько обычно объем отклоняется от этого среднего значения, используя стандартное отклонение. Это помогает нам ответить на более конкретный вопрос: является текущий объем нормальным или необычно высоким?
Как только мы это поймем, используем эту информацию для создания чего-то визуального. Возьмем текущий объем и сравним его со стандартным отклонением, чтобы получить нормализованное значение. Затем будем использовать это значение для регулирования размера пузырьков, так что более высокая активность будет создавать пузырьки большего размера.
Расчет среднего объема
Среднее значение, также известное как среднее арифметическое, представляет собой типичное значение набора данных за определенный период. Проще говоря, оно отвечает на вопрос: «Каков нормальный уровень объема, если мы посмотрим на последние бары?» Для его расчета суммируем объем фиксированного количества баров (наш период ретроспективного анализа) и делим на это число. Это дает нам единственное значение, представляющее собой центральную тенденцию изменения объема в пределах данного временного окна.
При таком сценарии среднее значение полезно, поскольку служит отправной точкой для сравнения. Сами по себе значения исходного объема мало что говорят, поскольку объем естественным образом колеблется от бара к бару. Вычисляя среднее значение, мы определяем, как выглядит «нормальный» объем торгов в текущих рыночных условиях. Это позволяет оценить, является объем текущего бара относительно высоким или низким, сравнивая его со средним показателем. Оценить значимость всплеска или спада объема без среднего значения было бы сложно, но его наличие дает нам ориентир, повышающий согласованность и достоверность анализа объема.
Пример:
#property indicator_chart_window #property indicator_plots 0 //--- Look-back window: number of bars used to compute mean and stddev of volume input int N = 30; // Look Back Bars //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { //--- //--- Need at least N bars to calculate meaningful statistics if(rates_total < N) return 0; //--- return value of prev_calculated for next call return(rates_total); }
То есть:
Этот код, использующий графические объекты вместо традиционных построений или буферов, определяет пользовательский индикатор, который работает непосредственно на основном графике MetaTrader 5. Он позволяет пользователю сбалансировать скорость отклика и плавность работы, вводя определяемый пользователем период ретроспективного анализа N, который определяет, сколько баров используется для вычислений. Каждый раз при получении новых рыночных данных активируется основная вычислительная функция. Она анализирует рыночные условия, обрабатывая массивы данных, включая цену, объем и спред. Функция первоначально оценивает, хватает ли доступных баров за период ретроспективного анализа, чтобы предотвратить ошибки, вызванные недостатком данных. Если условие выполнено, процесс продолжается, возвращая общее количество обработанных баров для обеспечения эффективных обновлений.
Теперь, когда структура индикатора определена и мы убедились, что для наших расчетов имеется достаточно доступных данных, можем перейти к вычислению среднего значения.
Пример:
double sum; double mean; int window_start;
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { //--- //--- Need at least N bars to calculate meaningful statistics if(rates_total < N) return 0; //--- The window covers the most recent N bars window_start = rates_total - N; //--- Compute the mean tick volume over the window sum = 0.0; for(int i = window_start; i < rates_total; i++) sum += (double)tick_volume[i]; mean = sum / N; //--- return value of prev_calculated for next call return(rates_total); }
То есть:
Вычисляем средний объем торгов по выбранному количеству последних баров, чтобы установить базовый уровень типичной рыночной активности. Первым шагом является инициализация переменной с именем sum значением 0.0. Эта переменная суммирует общий объем по мере прохождения каждого бара в указанном диапазоне.
Затем используем цикл для итерации каждого бара в нашем окне ретроспективного анализа, начиная с window_start и заканчивая rates_total. Самые последние N баров, которые нас интересуют, определяются этими двумя переменными. Используем tick_volume[i] для получения тикового объема текущего бара для каждой итерации цикла. Тиковый объем специально преобразуется в тип double перед добавлением к общему значению, поскольку он хранится как целочисленный тип. Это гарантирует точность вычислений, особенно при последующем делении общей суммы для определения среднего значения. Объем каждого бара добавляется к сумме по мере выполнения цикла, постепенно увеличивая общий объем всех N баров. Когда цикл завершается, сумма становится равной общему объему всего окна.
Наконец, делим всю эту сумму на N, что обозначает количество баров, использованных в вычислениях, чтобы получить среднее значение. На этом этапе мы получаем единственное значение, представляющее средний объем за выбранный период, путем равномерного распределения общего объема по количеству баров. Это среднее значение критически важно, поскольку создает ориентир, позволяющий оценить, является текущий объем обычным или существенно отличается от предшествующих рыночных тенденций.
Расчет дисперсии и стандартного отклонения
После вычисления среднего значения следующим шагом является понимание того, как объем ведет себя в окрестностях этого среднего значения. Именно здесь в игру вступают дисперсия и стандартное отклонение. Хотя среднее значение показывает, как выглядит «нормальный» объем, оно не указывает, насколько объем обычно изменяется от одного бара к другому. Дисперсия решает эту проблему, измеряя, насколько каждое значение объема отклоняется от среднего значения. Это достигается путем вычисления разницы между каждым значением и средним значением, возведения полученной разницы в квадрат, а затем усреднения этих квадратов разностей. В результате получается показатель того, насколько разбросаны данные об объеме торгов.

Стандартное отклонение, которое представляет собой всего лишь квадратный корень из дисперсии, напрямую коррелирует с дисперсией. Этот шаг упрощает считывание значения, возвращая его к той же единице измерения, что и в исходных данных. Проще говоря, стандартное отклонение дает более полезное и значимое объяснение разброса данных, чем дисперсия в виде квадратных членов.
Это звено имеет решающее значение в отношении данного индикатора. Стандартное отклонение показывает, насколько сильно объем обычно изменяется относительно базового уровня, который обеспечивает среднее значение. Это позволяет нам определить, имеет ли место значительное превышение текущего объема по сравнению с типичным или средним значением. Без дисперсии и стандартного отклонения мы не смогли бы определить значимость отклонения от среднего значения. Именно это нам и необходимо для создания индикатора Volume Bubble. При помощи стандартного отклонения мы можем нормализовать значения объема и определить, когда активность является аномально высокой. Впоследствии размер пузырьков будет зависеть от этих изменений: более крупные пузырьки будут свидетельствовать о более высокой активности на рынке, а пузырьки меньшего размера — о более спокойном периоде.
Пример:double variance; double diff; double stddev;
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { //--- Need at least N bars to calculate meaningful statistics if(rates_total < N) return 0; //--- The window covers the most recent N bars window_start = rates_total - N; //--- Compute the mean tick volume over the window sum = 0.0; for(int i = window_start; i < rates_total; i++) sum += (double)tick_volume[i]; mean = sum / N; //--- Compute variance then standard deviation variance = 0.0; for(int i = window_start; i < rates_total; i++) { diff = (double)tick_volume[i] - mean; variance += diff * diff; } variance /= N; stddev = MathSqrt(variance); //--- Guard: if all volumes are identical stddev is zero; nothing useful to show if(stddev == 0.0) return rates_total; //--- return value of prev_calculated for next call return(rates_total); }
То есть:
В первой строке переменная дисперсии инициализируется значением 0. Это крайне важно, поскольку перед добавлением чего-либо необходима отправная точка, потому что дисперсия вычисляется как накопление значений. На данном этапе переменная служит лишь контейнером для хранения суммы всех квадратов разностей, вычисленных в ходе цикла. Затем цикл запускается и проходит по каждому бару в указанном окне. Код получает доступ к объему текущего бара и сравнивает его с ранее определенным средним значением для каждой итерации. На этом этапе измеряется расстояние между каждым отдельным измерением объема и средним объемом торгов. Для того чтобы учесть все значения за период ретроспективного анализа, каждый бар добавляет одну точку данных к общему расчету.
Цикл вычисляет и сохраняет во временной переменной разницу между текущим объемом торгов и средним значением. Эта разница показывает, как объем данного конкретного бара отклоняется от характерной закономерности. Небольшое отклонение указывает на то, что объем почти нормальный, тогда как большее отклонение свидетельствует о необычайной активности по сравнению с недавней историей. Код вычисляет разницу, возводит ее в квадрат, а затем добавляет результат к текущей сумме, которая хранится в переменной дисперсии. Независимо от того, находится отклонение выше или ниже среднего значения, возведение в квадрат гарантирует, что каждое отклонение вносит положительный вклад. Кроме того, более серьезным отклонениям придается больший вес, что полезно при поиске существенных изменений в рыночной активности. По мере выполнения цикла все эти квадратичные отклонения суммируются в одно общее значение. Это число делится на количество баров в окне после завершения цикла. На этом этапе суммарное значение преобразуется в среднее измерение отклонения значений от среднего.
После этого дисперсия начинает отображать общее распределение значений объема за выбранный период времени. Затем стандартное отклонение вычисляется путем извлечения квадратного корня из дисперсии. Это делается потому, что для выражения дисперсии используются квадраты единиц, которые не сразу интуитивно понятны. Стандартное отклонение упрощает понимание результата и его использование для дополнительных вычислений, таких как нормализация, поскольку возвращает его к тому же масштабу, что и исходные данные об объеме.
Наконец, если стандартное отклонение равно нулю, код обеспечивает верификацию. Это состояние возникает, когда нет никаких изменений и все значения объема в окне одинаковы. Использование стандартного отклонения в последующей операции деления в данной ситуации приведет к ошибке. Для предотвращения деления на ноль и сохранения стабильности проверка гарантирует безопасное завершение программой текущего вычислительного цикла.
Нормализация объема и сопоставление размеров пузырьков
Поскольку сами по себе исходные значения объема не всегда полезны при сравнении в различных рыночных условиях, нормализация объема является важнейшим шагом. В зависимости от актива, временного периода и общей рыночной активности объем торгов может резко меняться. Например, значение объема, которое считается максимальным в один период, может быть совершенно типичным в другой. Мы преобразуем исходные числовые значения в относительную шкалу, которая показывает значимость текущего объема по отношению к недавнему поведению, нормализуя объем с использованием стандартного отклонения. Это позволяет нам в рамках единого фреймворка определить, является текущая активность аномально низкой, средней или высокой.
Создание единого метода сравнения значений объема в пределах одного и того же окна ретроспективного анализа является еще одним важным преимуществом нормализации. Нормализованное значение выражает объем с точки зрения отклонения от среднего значения, а не в виде неустойчивых исходных данных. Это облегчает применение логических пороговых значений для классификации силы объема торгов. Гарантируя, что индикатор динамически реагирует на меняющиеся рыночные условия, а не зависит от заранее определенных предположений, нормализация в данной ситуации служит связующим звеном между статистическим анализом и визуальной интерпретацией.
Сопоставление нормализованного объема с размером пузырьков превращает числовые данные в визуальное представление, которое с первого же взгляда легко интерпретировать. Идея заключается в том, чтобы присваивать пузырьки большего размера более высоким значениям нормализованного объема, а пузырьки меньшего размера — более низким. Такое визуальное масштабирование помогает трейдерам быстро выявлять периоды повышенной активности без необходимости анализа точных цифр. Размер каждого пузырька соответствует уровню интенсивности объема, что позволяет графику интуитивно передавать информацию.
Пример:
long v; double n_vol; int bubble_size;
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { //--- //--- Need at least N bars to calculate meaningful statistics if(rates_total < N) return 0; //--- The window covers the most recent N bars window_start = rates_total - N; //--- Compute the mean tick volume over the window sum = 0.0; for(int i = window_start; i < rates_total; i++) sum += (double)tick_volume[i]; mean = sum / N; //--- Compute variance then standard deviation variance = 0.0; for(int i = window_start; i < rates_total; i++) { diff = (double)tick_volume[i] - mean; variance += diff * diff; } variance /= N; stddev = MathSqrt(variance); //--- Guard: if all volumes are identical stddev is zero; nothing useful to show if(stddev == 0.0) return rates_total; //Draw or update a bubble (and optional label) for every bar in the window for(int i = window_start; i < rates_total; i++) { v = tick_volume[i]; n_vol = (double)v / stddev; // normalised volume: how many stddevs above the mean //--- Map normalised volume to a bubble font size if(n_vol < 1) bubble_size = 24; else if(n_vol < 2) bubble_size = 32; else if(n_vol < 3) bubble_size = 40; else if(n_vol < 4) bubble_size = 48; else { bubble_size = 56; } } //--- return value of prev_calculated for next call return(rates_total); }
То есть:
Задавая каждому бару размер пузырька, основанный на его нормализованном объеме, этот фрагмент алгоритма преобразует вычисленные данные в визуальное представление. Для сравнения с недавней активностью он циклически проходит по выбранному окну ретроспективного анализа, получает тиковый объем для каждого бара и нормализует его, деля на стандартное отклонение. Затем с помощью условных проверок полученное значение классифицируется по диапазонам, каждый из которых представляет собой отдельную степень рыночной активности. Более крупные пузырьки указывают на постепенное увеличение объема по сравнению с обычным, тогда как более мелкие пузырьки свидетельствуют о низкой активности. Самые крупные пузырьки выделяют исключительные ситуации. В целом, этот метод преобразует исходные данные об объеме торгов в четкую визуальную шкалу, которая позволяет с легкостью увидеть различные уровни участия в деятельности рынка прямо на графике.
Отображение пузырьков объема на графике
Следующий шаг — отобразить эти пузырьки непосредственно на графике после вычисления размера каждого пузырька с использованием нормализованного объема. Для визуального отображения динамики объема торгов мы используем графические объекты, поскольку этот индикатор не использует обычные построения или буферы. Каждый пузырек отображается в виде текстового объекта, размещенного в определенном месте на графике, обычно около максимума каждой свечи, чтобы его всегда было хорошо видно.
Для каждого бара в выбранном окне создается отдельное имя для объекта-пузырька. Это гарантирует, что каждый пузырек связан с определенной свечой и может быть изменен или удален без влияния на другие пузырьки. Этот код определяет, существует ли уже объект с таким же именем, прежде чем создавать новый. Если объект с таким именем уже существует, он удаляется во избежание дублирования. Этот шаг необходим для обеспечения четкости и точности представления информации при получении новых данных.
Пример:
string name; color bubble_color;
//+------------------------------------------------------------------+ //| stable bubble object name based on candle timestamp | //+------------------------------------------------------------------+ string BubbleName(datetime t) { return StringFormat("Bubble%d", (int)t); }
//Draw or update a bubble (and optional label) for every bar in the window for(int i = window_start; i < rates_total; i++) { v = tick_volume[i]; n_vol = (double)v / stddev; // normalised volume: how many stddevs above the mean //--- Map normalised volume to a bubble font size if(n_vol < 1) bubble_size = 24; else if(n_vol < 2) bubble_size = 32; else if(n_vol < 3) bubble_size = 40; else if(n_vol < 4) bubble_size = 48; else { bubble_size = 56; } //--- Determine bubble colour: green if volume rose, orange if fell, gray for first bar if(i > window_start) bubble_color = (tick_volume[i] > tick_volume[i - 1]) ? clrLime : clrOrange; else bubble_color = clrGray; //--- Draw the bubble dot — create only if missing, otherwise just update properties name = BubbleName(time[i]); if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_TEXT, 0, time[i], high[i]); ObjectSetString(0, name, OBJPROP_TEXT, "●"); ObjectSetInteger(0, name, OBJPROP_COLOR, bubble_color); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, bubble_size); ObjectSetInteger(0, name, OBJPROP_BACK, true); ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectMove(0, name, 0, time[i], high[i]); }
Выводимая информация:

То есть:
Чтобы предотвратить конфликты и гарантировать точное отслеживание, в этом разделе описывается, как формируется, поддерживается и отображается каждый пузырек на графике с использованием различных названий, привязанных ко времени. Вместо того чтобы удалять и создавать объекты заново при каждом обновлении, код сначала проверяет, существует ли уже объект с таким именем. Он создает новый объект только в том случае, если объект с тем же именем не найден; в противном случае он просто обновляет свойства существующего объекта. Затем пузырек располагается над соответствующей свечой и оформляется в виде кружка. Цвет выбирается путем сравнения текущего и предыдущего объемов. Зеленый цвет указывает на рост активности, оранжевый — на снижение активности, а серый цвет присваивается первому бару в окне, когда отсутствует предыдущий бар для сравнения. В то время как другие свойства гарантируют правильное выравнивание и видимость пузырька, его размер представляет собой нормализованный объем.
Предыдущие пузырьки, выходящие за пределы окна ретроспективного анализа, не удаляются мгновенно, что может привести к загромождению графика. Для решения этой проблемы внедрена процедура очистки, использующая ту же логику именования для выявления и удаления устаревших элементов, что гарантирует точность, аккуратность и сосредоточенность на актуальных данных.
Пример:
string bubble_old_name; int old_index;
//--- Remove objects that have scrolled out of the look-back window static int last_window_start = -1; if(window_start != last_window_start) { last_window_start = window_start; old_index = window_start - 1; if(old_index >= 0) { bubble_old_name = BubbleName(time[old_index]); if(ObjectFind(0, bubble_old_name) >= 0) { ObjectDelete(0, bubble_old_name); } } }
То есть:
В этом блоке кода осуществляется очистка устаревших объектов-пузырьков, которые больше не находятся внутри активного окна ретроспективного анализа. Для поддержания точности и аккуратности графика необходимо удалять соответствующие пузырьки по мере добавления новых баров и смещения старых баров за пределы диапазона, указанного числом N. Цикл начинается с prev_calculated - N и продолжается до window_start. Этот диапазон представляет собой бары, которые ранее были обработаны, но теперь вышли за пределы текущего окна. Иными словами, это «просроченные» бары, пузырьки которых больше не должны отображаться.
Внутри цикла предусмотрена проверка безопасности, чтобы убедиться, что индекс не опустится ниже нуля. Это крайне важно, поскольку значение prev_calculated может быть очень малым на начальных этапах работы индикатора, и вычитание N может дать отрицательную отправную точку. Данное условие пропускает такие недопустимые индексы, чтобы предотвратить ошибки во время выполнения. Для каждого допустимого бара в этом диапазоне код генерирует имя объекта-пузырька, используя ту же функцию именования, основанную на временной метке свечи. Это обеспечивает согласованность, то есть программа может правильно идентифицировать именно тот объект, который был ранее создан для этого бара.
Далее алгоритм определяет, существует ли на графике объект с заданным именем. В этом случае элемент будет удален. На этом этапе гарантируется удаление устаревших пузырьков, которые больше не находятся в пределах окна расчетов. Благодаря этому каждое обновление обеспечивает четкость, эффективность и согласованность визуализации с указанным периодом ретроспективного анализа, создавая и корректируя пузырьки для недавних данных и удаляя те, которые больше не нужны.
Добавление меток об объеме к большим пузырькам
Для дальнейшего повышения читаемости индикатора можно отображать фактические значения объема на пузырьках, представляющих высокую активность. Поскольку большие пузырьки уже указывают на значительный объем, добавление текстовой метки помогает предоставить точную информацию, не перегружая график. Обычно после определения категории с наибольшим размером пузырька рядом с ней добавляется текстовая метка. Для облегчения чтения числа, обозначающие объемы, можно сократить, используя обозначения типа «К» для тысяч или «М» для миллионов. Благодаря такому сочетанию размера и числовых обозначений на метках пользователи могут быстро определить местоположение и интенсивность сильной активности.
Пример:
string tex_name; string old_name; //+------------------------------------------------------------------+ //| Returns a stable label object name based on candle timestamp | //+------------------------------------------------------------------+ string MakeName(datetime t) { return StringFormat("VolLabel_%d", (int)t); } //+------------------------------------------------------------------+ //| Formats a volume number into a compact readable string | //| e.g. 1500 -> "1K", 3000000 -> "3M", 2000000000 -> "2B" | //+------------------------------------------------------------------+ string FormatVolume(long vol) { int digits = StringLen(IntegerToString((int)vol)); if(digits <= 3) return IntegerToString((int)vol); // hundreds: 123 else if(digits <= 6) return IntegerToString((int)(vol / 1000)) + "K"; // thousands: 45K else if(digits <= 9) return IntegerToString((int)(vol / 1000000)) + "M"; // millions: 12M else return IntegerToString((int)(vol / 1000000000)) + "B"; // billions: 3B }
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, "VolLabel_"); ObjectsDeleteAll(0, "Bubble"); ChartRedraw(0); }
//--- Draw or update a bubble (and optional label) for every bar in the window bool need_redraw = false; for(int i = window_start; i < rates_total; i++) { v = tick_volume[i]; n_vol = (double)v / stddev; // normalised volume: how many stddevs above the mean //--- Map normalised volume to a bubble font size if(n_vol < 1) bubble_size = 24; else if(n_vol < 2) bubble_size = 32; else if(n_vol < 3) bubble_size = 40; else if(n_vol < 4) bubble_size = 48; else { bubble_size = 56; //--- Exceptional volume (>= 4 stddevs): also show a formatted volume label tex_name = MakeName(time[i]); //--- Create the label only if it does not already exist; otherwise just update it if(ObjectFind(0, tex_name) < 0) { ObjectCreate(0, tex_name, OBJ_TEXT, 0, time[i], high[i]); need_redraw = true; } ObjectSetString(0, tex_name, OBJPROP_TEXT, FormatVolume(v)); ObjectSetInteger(0, tex_name, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetInteger(0, tex_name, OBJPROP_COLOR, clrDodgerBlue); ObjectSetInteger(0, tex_name, OBJPROP_FONTSIZE, 10); ObjectSetString(0, tex_name, OBJPROP_FONT, "Arial Bold"); ObjectSetInteger(0, tex_name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, tex_name, OBJPROP_HIDDEN, true); if(ObjectMove(0, tex_name, 0, time[i], high[i])) need_redraw = true; } //--- Determine bubble colour: green if volume rose, orange if fell, gray for first bar if(i > window_start) bubble_color = (tick_volume[i] > tick_volume[i - 1]) ? clrLime : clrOrange; else bubble_color = clrGray; //--- Draw the bubble dot — create only if missing, otherwise just update properties name = BubbleName(time[i]); if(ObjectFind(0, name) < 0) { ObjectCreate(0, name, OBJ_TEXT, 0, time[i], high[i]); need_redraw = true; } ObjectSetString(0, name, OBJPROP_TEXT, "●"); ObjectSetInteger(0, name, OBJPROP_COLOR, bubble_color); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, bubble_size); ObjectSetInteger(0, name, OBJPROP_BACK, true); ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); if(ObjectMove(0, name, 0, time[i], high[i])) need_redraw = true; } //--- Remove objects that have scrolled out of the look-back window static int last_window_start = -1; if(window_start != last_window_start) { last_window_start = window_start; old_index = window_start - 1; if(old_index >= 0) { bubble_old_name = BubbleName(time[old_index]); if(ObjectFind(0, bubble_old_name) >= 0) { ObjectDelete(0, bubble_old_name); need_redraw = true; } old_name = MakeName(time[old_index]); if(ObjectFind(0, old_name) >= 0) { ObjectDelete(0, old_name); need_redraw = true; } } } //--- Redraw ONLY when needed if(need_redraw) ChartRedraw(0);
Выводимая информация:

То есть:
Уникальные имена каждой текстовой метки, которая будет отображать значение объема на графике, хранятся в переменной tex_name. Как и для пузырьков, для эффективного управления каждой меткой необходимо присвоить ей уникальный идентификатор. Это достигается путем определения функции, которая использует метку времени свечи для получения стабильного наименования. Каждая метка уникальным образом связывается с конкретным баром путем преобразования времени в строку и добавления префикса. Это позволяет избежать конфликтов имен при создании множества объектов и гарантирует, что каждая метка точно соответствует свече, которую она представляет.
Исходные значения объема необходимо преобразовать в более понятный формат с помощью другой вспомогательной функции. Она сводит большие числа к таким формам, как тысячи, миллионы или миллиарды, вместо того, чтобы показывать их целиком. Благодаря этому повышается наглядность графиков. Такое значение, как 1 200 000, можно представить как 1M без потери смысла, поскольку функция масштабирует целое число в зависимости от оценки его размера.
В разделе, где задается наибольший размер пузырька, код добавляет дополнительную характеристику, отображая значение объема в виде текста. Это происходит только в категории с наибольшим объемом торгов, что помогает избежать перегруженности графика. Во-первых, метка времени используется для создания уникального названия метки пузырька. Затем код проверяет, существует ли уже метка с идентичным именем. Если такая метка существует, свойства имеющегося объекта обновляются напрямую, а не удаляются и создаются заново. Если ничего не найдено, в месте расположения свечи создается новый текстовый объект. Для обеспечения читаемости метки и ее визуальной согласованности с остальной частью показаний индикатора добавляются дополнительные характеристики, такие как выравнивание, цвет, размер шрифта и стиль шрифта.
При появлении новых баров необходимо удалить пузырьки и метки со старых баров, поскольку они уже прошла окно ретроспективного анализа. Цикл очистки позаботится об этом. Используя описанную логику именования, код создает имена для соответствующих друг другу пузырька и метки, проверяет их наличие на графике и при необходимости удаляет их. Это позволяет сохранять чистоту и концентрацию графика на последних данных, гарантируя, что видны только элементы, находящиеся в активном окне.
Для повышения скорости работы добавлен флаг с именем need_redraw, регулирующий время обновления графика. Этот флаг устанавливается в значение true только при создании, обновлении, перемещении или удалении графического объекта, в отличие от случаев, когда требуется перерисовка на каждом тике. График перерисовывается только после вычисления, если этот флаг поднят. Этот метод гарантирует обновление графика только в том случае, когда происходят действительно заметные изменения, сводит к минимуму ненужные процессы перерисовки и повышает эффективность.
Наконец, при удалении индикатора с графика функция деинициализации гарантирует удаление всех объектов. Вместо удаления каждого элемента по отдельности, программа удаляет все объекты, имеющие те же префиксы, что и используемые для меток и пузырьков. Это гарантирует, что график будет пустым, когда индикатор перестанет использоваться. Затем результаты очистки незамедлительно отражаются в новом изображении графика. Этот шаг крайне важен для поддержания порядка в рабочем пространстве и гарантированного удаления всех артефактов индикатора.
Заключение
Мы разработали полный рабочий процесс для преобразования исходных данных об объеме в осмысленное и адаптивное визуальное представление с использованием индикатора Volume Bubble в MQL5. В этой статье вы узнали:
- О роли объема в MQL5 и различии между тиковым объемом и реальным объемом, в том числе, когда каждый тип доступен в зависимости от рынка или актива.
- О доступе к данным об объеме через встроенные массивы и их интеграции в логику индикатора для дальнейших вычислений и визуализации.
- Об установлении базового уровня путем вычисления среднего значения в пределах заданного окна ретроспективного анализа, позволяющего понять, что представляет собой нормальная рыночная активность.
- Об измерении отклонения от базового уровня с использованием дисперсии и стандартного отклонения, чтобы оценить разброс изменений объема во времени.
- О нормализации значений объема с использованием стандартного отклонения, благодаря которой можно сохранять осмысленность сравнений при изменении рыночных условий.
- О преобразовании нормализованных значений в размеры пузырьков, позволяющем наглядно и интуитивно отображать на графике различные уровни активности.
- Об использовании графических объектов в MQL5 для отображения пузырьков с возможностью управления их положением, размером, цветом и общим внешним видом.
- О повышении наглядности путем добавления форматированных меток объема к наиболее значимым пузырькам, что облегчает интерпретацию фактических значений объема.
- О поддержании чистоты и эффективности графика путем надлежащего обновления существующих объектов, удаления устаревших пузырьков и меток, а также проведения полной очистки во время деинициализации.
При сочетании этих компонентов объем рассматривается скорее как контекстный индикатор участия в деятельности рынка, а не просто как числовые данные. По сравнению со стандартной гистограммой MetaTrader 5, этот метод проще для чтения, эффективнее выделяет значимую активность и предоставляет прочную основу для развития более сложных методов анализа на основе объемов, сигналов и автоматизированных стратегий.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21871
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От начального до среднего уровня: Перегрузка операторов (V)
Разработка инструментария для анализа Price Action (Часть 72): Создание индикатора заполнения гэпов на MQL5
Нейросети в трейдинге: Адаптация прогноза при смене рыночного режима (OpenCL примитивы)
Возможности Мастера MQL5, которые вам нужно знать (Часть 98): Использование сигма-точечного фильтра Калмана и капсульной сети в пользовательском классе сигналов
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования