Разработка инструментария для анализа Price Action (Часть 42): Интерактивное тестирование на графике с кнопочной логикой и статистическими уровнями
Содержание
Введение
Добро пожаловать в следующую часть серии Разработка инструментария для анализа Price Action. Наша цель – автоматизировать анализ ценового движения (Price Action) и сделать его интуитивно понятным и доступным для трейдеров, которые опираются на структуру цены вместо непрозрачных индикаторов. В этой статье мы развиваем предыдущую работу, в которой были введены такие статистические метрики, как среднее, стандартное отклонение, медиана и другие уровни распределения, вычисляемые по типичным ценам свечей. Мы также показываем, как эти метрики естественным образом соотносятся с важными рыночными опорными уровнями – поддержкой, сопротивлением и пивот-уровнями.
Вместо жестко заданных параметров и ручной подстройки входных параметров мы переходим к более интерактивному подходу прямо на графике – статистической панели управления. Эта панель размещает кнопки управления и редактируемые поля прямо на графике, чтобы пользователь мог по запросу вычислять и визуализировать статистические уровни. Хотя многие функции из предыдущей реализации сохранены, этот вариант представляет собой более зрелый и ориентированный на пользователя подход к аналитике на графике.
В этой статье мы:
- разберем логику и назначение статистической панели управления;
- рассмотрим ключевые моменты реализации на MQL5;
- покажем примеры результатов и сценарии использования;
- подведем итоги и наметим следующие шаги.
Знакомство с концепцией
Статистические показатели, вычисляемые по ценовым данным, такие как среднее, медиана, процентили и моды на основе плотности, отражают центральную тенденцию и форму распределения недавней динамики цены. Эти показатели часто совпадают с ключевыми зонами реакции рынка: зонами, где концентрируется ликвидность, куда цена часто возвращается и откуда начинаются пробои и развороты. Использование этих статистических показателей как опорных уровней подходит как для дискреционного, так и для системного подхода. Они легко интерпретируются визуально и подходят для автоматического мониторинга. Подробнее о таких метриках, как среднее, стандартное отклонение и медиана, можно прочитать в предыдущей статье, где они разобраны подробно.
В основе этого инструмента – управление с помощью кнопок, которое упрощает анализ рынка. Панель превращает заданные разработчиком параметры в легкий интерфейс, встроенный в график, и позволяет трейдерам:- выбирать диапазон даты и времени (на графике или вручную);
- вычислять статистику распределения по выбранному диапазону;
- отображать линии для среднего, полос стандартного отклонения, процентилей, медианы и мод;
- фиксировать снимки опорных уровней для мониторинга в реальном времени (обнаружение касаний, пробоев и разворотов);
- экспортировать данные снимка в CSV;
- быстро сбрасывать панель в чистое состояние.
Этот интерфейс на графике избавляет от догадок при выборе настроек и ускоряет рабочий процесс анализа. Это также делает инструмент более безопасным и удобным для торговли в реальном времени: вместо постоянного редактирования кода трейдер может за считанные секунды рассчитать показатели и оценить результат на графике.

Диаграмма показывает простую модель взаимодействия с панелью: один клик по кнопке запускает одно из трех понятных действий ("Reset", "Calculate", "Toggle"). Каждое действие выполняет строго определенные операции (очистку данных, вычисление и отрисовку статистики или переключение видимости), сразу обновляет график, а затем возвращает советник в состояние ожидания. Такой сценарий работы на графике избавляет от необходимости редактировать код для рутинного анализа, ускоряет принятие решений и снижает риск того, что в терминале останутся устаревшие объекты или глобальные переменные.
Реализация
Сначала задаем базовые метаданные нашего индикатора. Мы добавляем комментарии в заголовке, где указываются имя файла, автор и сведения об авторских правах, что помогает идентифицировать скрипт и обозначить авторство. Далее переходим к директивам платформы и задаем такие свойства, как #property copyright, #property version и #property strict. Эти директивы обеспечивают корректное распознавание нашего скрипта в MetaTrader, включают строгие правила синтаксиса для более безопасной разработки и дают необходимую информацию о версии для сопровождения. Этот начальный этап критически важен, поскольку он обеспечивает корректную интеграцию кода с MetaTrader и соответствие лучшим практикам.
//+------------------------------------------------------------------+ //| Statistical Dashboard.mq5| //| Copyright 2025, Christian Benjamin.| //| https://www.mql5.com/ru/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "https://www.mql5.com/ru/users/lynnchris" #property version "1.36" #property strict
Далее мы подключаем внешние библиотеки, чтобы расширить возможности нашего скрипта. Мы подключаем файл ArrayObj.mqh, который предоставляет расширенные возможности для работы с массивами. Это позволяет эффективно работать с динамическими коллекциями объектов и данных – важное свойство при работе с несколькими уровнями, сигналами и элементами интерфейса. Такой модульный подход делает код чище и лучше масштабируемым, особенно по мере добавления графических объектов и глобальных переменных.
// Include array utility library #include <Arrays/ArrayObj.mqh>
Затем мы определяем входные параметры, которые позволяют трейдерам настраивать поведение индикатора. Эти входные переменные включают окно проверки (Lookback), пороги сигналов, визуальные настройки и управляющие переключатели. Например, переменная Lookback определяет, сколько баров анализируется одновременно, а ZScoreSignalEnter задает чувствительность к отклонениям рынка. Благодаря этим параметрам трейдер может адаптировать индикатор к разным рынкам, таймфреймам и собственным стратегиям, что делает реализацию гибкой и удобной в работе.
// === Inputs === input int Lookback = 1000; input bool ExcludeCurrent = true; input bool UseWeightedByVol = true; input int ModeBins = 30; input int KDEGridPoints = 100; input double KDEBandwidthFactor = 1.0; input int RefreshEveryXTicks = 1; input double ZScoreSignalEnter = 2.0; input double ZScoreSignalExit = 0.8; input bool AllowLongSignals = true; input bool AllowShortSignals = true; input bool SendAlertOnSignal = false; input bool PlaySoundOnSignal = false; input string SoundFileOnSignal = "alert.wav"; input bool SendPushOnSignal = false; input ENUM_TIMEFRAMES TF = PERIOD_CURRENT; input int TimerIntervalSeconds = 60; input int CleanupIntervalSeconds = 3600; input bool AutoSnapshotLevels = false; input datetime InputRefStart = 0; input datetime InputRefEnd = 0; input int MonitorBars = 20; input double TouchTolerancePips = 3.0; input double BreakoutPips = 5.0; input double ReversalPips = 5.0; input bool UseCloseForConfirm = true; input bool UseATRforThresholds = true; input double ATRMultiplier = 0.5; input int ATRperiod = 14; input bool ClearSnapshotOnStart = false; input int LabelOffset_Mean_Sec = 0; input int LabelOffset_Median_Sec = -60; input int LabelOffset_ModeB_Sec = -120; input int LabelOffset_ModeK_Sec = -180; input int LabelOffset_Pct_Sec = -240; input bool DebugMode = false; input string InputRefStartStr = ""; input string InputRefEndStr = "";
Далее мы настраиваем внутренние переменные состояния, чтобы отслеживать текущее состояние индикатора. Переменные вроде awaitingSetStart и refStartChart хранят информацию о действиях пользователя – например, устанавливает ли пользователь опорные точки, – а также сохраняют соответствующие временные метки. Переменные вроде currentSignal отслеживают, подает ли система сейчас сигнал на покупку, продажу или не подает никакого сигнала. Эти внутренние переменные важны, потому что позволяют скрипту сохранять контекст между тиками и командами пользователя, обеспечивая последовательное и отзывчивое поведение.
// Internal variables to track state bool awaitingSetStart = false; bool awaitingSetEnd = false; datetime refStartChart = 0; datetime refEndChart = 0; int currentSignal = 0; // 1 for long, -1 for short, 0 for neutral
Чтобы работать с несколькими опорными уровнями, мы определяем структурированный тип данных RefLevel. Эта структура содержит такие поля, как имя уровня, цена, факт касания, количество касаний, зафиксированные максимальная и минимальная цены, а также другие показатели. Затем мы создаем массив refLevels[] для хранения нескольких таких экземпляров, что позволяет одновременно отслеживать и анализировать несколько уровней. Такой структурированный подход обеспечивает ясность и масштабируемость: по мере добавления новых уровней код может обрабатывать их последовательно и эффективно.
struct RefLevel { string name; double price; bool touched; datetime touchTime; int touchCount; double highest; double lowest; double avgTouchVol; int recentTouches; double persistence; int result; // -1, 0, 1 datetime resolvedTime; }; RefLevel refLevels[]; // Array to hold multiple levels
В нашей реализации мы объявляем множество функций, чтобы разбить логику на модули. Например, здесь есть функции для создания кнопок, меток и графических объектов (CreateButton(), CreateHLine_Pro(), DrawArrowAt()), для экспорта данных (ExportSnapshotCSV()) и для статистических вычислений (ComputeLevelScore(), Median(), Variance()). Такой модульный подход принципиально важен, поскольку отделяет управление интерфейсом от аналитических процедур, делая код более понятным, удобным для отладки и расширения. Это также позволяет повторно использовать фрагменты кода в разных частях скрипта.
// Forward declarations void CreateToolbar(); void DeleteToolbar(); void CreateButton(string name,int corner,int xdist,int ydist,int xsize,int ysize,string text); void CreateButtonStatLabel(string labName,int corner,int xdist,int ydist,string text); void CreateEditField(string name,int corner,int xdist,int ydist,int xsize,int ysize,string text); void ExportSnapshotCSV(); void CreateHLine_Pro(string name,double price,double score,string friendlyLabel); double ComputeLevelScore(int touchCount,double avgTouchVolume,int recentTouches,double persistenceBars,datetime lastTouchTime); void DrawArrowAt(string name, datetime when, double price, bool isBuy); void CreatePanel(); void CreateOrUpdateLineText(string name, datetime t, double price, string text); void RemoveOldObjects(int ageSec); void ClearSnapshot(); void ClearSnapshotVisuals(); void SnapshotReferenceLevels(double mean_val,double p25,double p75,double median_val,double mode_b,double mode_k,double stddev); void MonitorReferenceLevels(const MqlRates &rates[], int copied); double pipToPointMultiplier(); void DeleteObjectIfExists(string name); void RemoveHistogramObjects(); void SetObjTimestamp(string name); datetime GetObjTimestamp(string name); void CleanupMetaForObject(string name); void CleanupAllMetaGlobals(); double Mean(const double &a[], int n); double WeightedMean(const double &a[], const double &w[], int n); double WeightedMeanFromRates(const MqlRates &rates[], int copied); double Variance(const double &a[], int n, bool sample); double Median(const double &a[], int n); double Percentile(const double &a[], int n, double q); double ModeBinned(const double &a[], int n, int bins); double ModeKDE(const double &a[], int n, int gridPts, double bwFactor); double ArrayMin(const double &a[], int n); double ArrayMax(const double &a[], int n); bool ComputeStatsFromGlobals(double &mean,double &stddev,double &median,double &modeb,double &modek,double &p25,double &p75,double &zscore);
Затем мы разрабатываем вспомогательные функции, которые упрощают выполнение типовых задач. Например, UpdateLabelText() динамически обновляет метки интерфейса, TrimString() очищает строки пользовательского ввода, а pipToPointMultiplier() переводит пипсы в используемые платформой пункты для точных вычислений. Эти вспомогательные функции повышают надежность, уменьшают избыточность кода и обеспечивают последовательную обработку данных – все это необходимо для индикатора профессионального уровня.
// Example: update label text void UpdateLabelText(string name, string text) { if(ObjectFind(0, name) >= 0) ObjectSetString(0, name, OBJPROP_TEXT, text); else CreateButtonStatLabel(name, 0, 0, 0, text); } // Example: trim strings string TrimString(string s) { // Implementation omitted for brevity }
Далее мы реализуем полную процедуру сброса под названием ResetAll(). Эта функция очищает снимки, удаляет все графические объекты и связанные с ними метаданные, сбрасывает внутренние переменные и заново строит интерфейс. Это важная функция, поскольку она позволяет заново начать анализ без перезапуска MetaTrader, особенно после изменения параметров или при возникновении неожиданных состояний. Это повышает удобство работы и поддерживает целостность анализа.
void ResetAll() { // Clear snapshots, delete objects, reset globals, rebuild UI ClearSnapshot(); RemoveExistingEAObjects(); CleanupAllMetaGlobals(); RemoveHistogramObjects(); DeleteToolbar(); // Reset internal variables currentSignal = 0; refStartChart = 0; refEndChart = 0; refSnapshotTaken = false; snapshotTakenTime = 0; // Recreate UI CreatePanel(); CreateToolbar(); }
В функции OnInit() мы инициализируем индикатор при его загрузке. Здесь генерируются уникальные идентификаторы на основе символа и таймфрейма, создаются визуальные панели и панели инструментов, настраиваются таймеры для периодической очистки и инициализируются опорные точки по пользовательским параметрам или пометкам на графике. Этот этап инициализации обеспечивает корректную настройку всех необходимых ресурсов – компонентов интерфейса, глобальных переменных и внутренних состояний – до начала обработки данных в реальном времени. Правильная инициализация принципиально важна для предотвращения ошибок и обеспечения бесперебойной работы.
int OnInit() { // Set base strings for global variables S_base = StringFormat("CSTATS_%s_%d", _Symbol, (int)TF); CreatePanel(); if(ClearSnapshotOnStart) ClearSnapshot(); EventSetTimer(TimerIntervalSeconds); CreateToolbar(); // Additional setup... return INIT_SUCCEEDED; }
Аналогичным образом, OnDeinit() выполняет очистку при удалении индикатора. Эта функция останавливает таймеры, удаляет графические объекты, очищает глобальные переменные и сбрасывает рабочее окружение. Этот шаг принципиально важен, поскольку предотвращает утечки ресурсов, не допускает захламления графика и гарантирует, что последующие индикаторы или скрипты будут работать без помех. Это поддерживает стабильность и производительность торговой среды в целом.
void OnDeinit(const int reason) { EventKillTimer(); // Delete graphical objects DeleteObjectIfExists(S_mean); DeleteObjectIfExists(S_panel); // Clear global variables CleanupAllMetaGlobals(); }
Затем мы реализуем обработчик таймера OnTimer(), который срабатывает через заданные интервалы. Он удаляет устаревшие графические объекты и просроченные снимки, не давая графику захламляться и сохраняя актуальность отображаемых данных. Эта периодическая очистка поддерживает наглядность и производительность, особенно во время длительных торговых сессий с непрерывным потоком данных.
void OnTimer() { // Periodic cleanup of old objects RemoveOldObjects(CleanupIntervalSeconds); // Clear expired snapshots if(refSnapshotTaken && snapshotTakenTime > 0 && (TimeCurrent() - snapshotTakenTime) >= CleanupIntervalSeconds) { ClearSnapshot(); } }
Основная логика анализа в реальном времени сосредоточена в OnTick(). При каждом новом тике эта функция проверяет, не поставлен ли индикатор на паузу и не нужно ли пропустить обработку в соответствии с заданной частотой обновления. Затем через GetRatesForSelection() собираются последние рыночные данные, включая исторические цены в пределах заданного пользователем диапазона или окна проверки. На основе этих данных вычисляются статистические показатели – среднее, медиана, мода и стандартное отклонение – с помощью таких функций, как ComputeStatsFromRates(). Эти метрики служат основой для выявления рыночных режимов, отклонений и потенциальных сигналов.
Затем вычисляется z-оценка, показывающая, насколько последняя цена отклоняется от среднего; далее она используется как триггер сигнала. На основе пороговых значений обновляется текущее состояние сигнала, а сами сигналы помечаются стрелками, чтобы трейдер сразу видел состояние рынка.
void OnTick() { if(GlobalVariableCheck(S_base + "_PAUSED") && GlobalVariableGet(S_base + "_PAUSED") == 1.0) return; // Paused // Throttle refresh rate tick_count++; if(tick_count < RefreshEveryXTicks) return; tick_count = 0; // Gather data MqlRates rates[]; int copied=0; if(!GetRatesForSelection(rates, copied)) return; // Compute stats double mean_val, stddev, median_val, mode_b, mode_k, p25, p75; if(!ComputeStatsFromRates(rates, copied, mean_val, stddev, median_val, mode_b, mode_k, p25, p75)) return; // Calculate z-score double latest = (rates[0].high + rates[0].low + rates[0].close) / 3.0; double zscore = (stddev > 0) ? (latest - mean_val) / stddev : 0; // Store global variables GlobalVariableSet(S_base + "_mean", mean_val); GlobalVariableSet(S_base + "_zscore", zscore); // Generate signals int newSignal = 0; if(zscore >= ZScoreSignalEnter && AllowLongSignals) newSignal = 1; else if(zscore <= -ZScoreSignalEnter && AllowShortSignals) newSignal = -1; // Update visual signals if(newSignal != currentSignal) { if(newSignal == 1) DrawArrowAt(S_arrow_long, iTime(_Symbol, TF, 0), latest, true); else if(newSignal == -1) DrawArrowAt(S_arrow_short, iTime(_Symbol, TF, 0), latest, false); currentSignal = newSignal; } }
Чтобы упростить анализ данных, мы создаем такие функции, как GetRatesForSelection() и ComputeStatsFromRates(). Первая получает релевантные рыночные данные с учетом заданных пользователем диапазонов дат или окна проверки, чтобы анализ был сосредоточен на интересующем трейдера участке рынка. Вторая выполняет статистические расчеты, включая вычисление среднего, медианы и мод, с использованием надежных алгоритмов, таких как сортировка массива и вычисление перцентилей. Эти функции составляют основу аналитических возможностей индикатора, преобразуя исходные рыночные данные в значимые ориентиры для принятия решений.
bool GetRatesForSelection(MqlRates &rates[], int &copied) { // Fetch data based on date range or lookback if(UseDateRangeOnChart && refStartChart > 0 && refEndChart > 0) { int shiftStart = iBarShift(_Symbol, TF, refEndChart, false); int shiftEnd = iBarShift(_Symbol, TF, refStartChart, false); int startShift = MathMin(shiftStart, shiftEnd); int endShift = MathMax(shiftStart, shiftEnd); int count = endShift - startShift + 1; ArrayResize(rates, count); copied = CopyRates(_Symbol, TF, startShift, count, rates); return copied > 0; } else { int startShift = ExcludeCurrent ? 1 : 0; int needed = Lookback; ArrayResize(rates, needed); copied = CopyRates(_Symbol, TF, startShift, needed, rates); return copied > 0; } }
Интерактивность здесь играет ключевую роль; мы создаем функции для обработки пользовательского ввода и взаимодействий. Например, CreateButton(), CreateEditField() и CreateToolbar() создают элементы управления на графике, позволяя трейдерам настраивать параметры и запускать нужные действия. Функция OnChartEvent() обрабатывает клики мыши, нажатия кнопок и изменения объектов, соответствующим образом обновляя внутренние переменные и опорные точки. Такой подход делает индикатор очень гибким, позволяя трейдерам на лету настраивать параметры анализа, что особенно важно в динамичной торговой среде.
bool ComputeStatsFromRates(const MqlRates &rates[], int copied, double &mean, double &stddev, double &median, double &modeb, double &modek, double &p25, double &p75) { // Extract data double vals[]; ArrayResize(vals, copied); for(int i=0; i<copied; i++) { double tp = (rates[i].high + rates[i].low + rates[i].close) / 3.0; vals[i] = tp; } // Compute measures mean = Mean(vals, copied); stddev = MathSqrt(Variance(vals, copied, true)); median = Median(vals, copied); p25 = Percentile(vals, copied, 0.25); p75 = Percentile(vals, copied, 0.75); modeb = ModeBinned(vals, copied, ModeBins); modek = ModeKDE(vals, copied, KDEGridPoints, KDEBandwidthFactor); return true; }
При клике по кнопке "Mean" функция OnChartEvent() фиксирует это действие по событию CHARTEVENT_OBJECT_CLICK. Затем код по имени объекта проверяет, что был нажат именно элемент "Mean". После этого советник вызывает GetRatesForSelection(), чтобы выбрать соответствующий диапазон рыночных данных. Если получить данные не удалось – то есть они отсутствуют или произошла ошибка, – скрипт обновляет соответствующую метку, сообщает пользователю, что данные не найдены, и завершает обработку.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id != CHARTEVENT_OBJECT_CLICK) return; string objName = sparam; if(objName == S_base + "_BTN_SHOWMEAN") { // Step 1: Sample data for the selected range MqlRates rates[]; int copied=0; if(!GetRatesForSelection(rates, copied) || copied <= 0) { // Failure: No data available UpdateLabelText(S_base + "_LBL_BTN_MEAN", "No data for selection"); return; } // Step 2: Compute statistics (mean, stddev, etc.) double mean_val, stddev, median_val, mode_b, mode_k, p25, p75; if(!ComputeStatsFromRates(rates, copied, mean_val, stddev, median_val, mode_b, mode_k, p25, p75)) { // Failure: Calculation failed UpdateLabelText(S_base + "_LBL_BTN_MEAN", "Compute failed"); return; } // Step 3: Draw horizontal line at mean CreateHLine_Pro(S_mean, mean_val, 0.85, "Mean"); // Step 4: Update label with computed mean string rangeTxt = ""; if(UseDateRangeOnChart && refStartChart > 0 && refEndChart > 0) rangeTxt = StringFormat("%s -> %s", TimeToString(refStartChart, TIME_DATE|TIME_MINUTES), TimeToString(refEndChart, TIME_DATE|TIME_MINUTES)); else rangeTxt = StringFormat("Lookback %d bars", copied); CreateOrUpdateLineText(S_mean + "_TXT", iTime(_Symbol, TF, 0), mean_val, StringFormat("Mean: %s | %s | N=%d", DoubleToString(mean_val, _Digits), rangeTxt, copied)); UpdateLabelText(S_base + "_LBL_BTN_MEAN", "Mean: " + DoubleToString(mean_val, _Digits)); // End of process for button press return; } // Similar structure applies for other buttons like Std, Mode, Draw Levels, etc. }
Если данные получены успешно, советник переходит к вычислению среднего значения, передавая выборку в ComputeStatsFromRates(). Если вычисление по какой-либо причине не удается, соответствующая метка обновляется, после чего процесс завершается. В противном случае, после успешного расчета, советник рисует горизонтальную линию на уровне вычисленного среднего с помощью CreateHLine_Pro(), настраивая ее внешний вид и метку для наглядности. Одновременно скрипт обновляет статистическую метку рядом с кнопкой, подставляя в нее новое значение среднего и обеспечивая мгновенную визуальную обратную связь. После этого процесс завершается, и система готова к следующему действию пользователя. Такой сценарий гарантирует, что каждое нажатие кнопки запускает последовательность выборки данных, анализа и визуального обновления, делая инструмент интерактивным и информативным.

Визуальные средства особенно важны для быстрой интерпретации; для этого мы реализуем такие функции, как CreateHLine_Pro(), DrawArrowAt() и CreateOrUpdateLineText(). Эти функции рисуют горизонтальные линии для статистических уровней, например среднего или медианы, строят стрелки для сигналов пробоя и разворота и выводят текстовые аннотации с подробной информацией.
void CreateHLine_Pro(string name, double price, double score, string friendlyLabel) { int width = 1 + (int)MathRound(score * 3.0); color col = clrDodgerBlue; // Determine color/style based on label if(ObjectFind(0, name) >= 0) { ObjectSetDouble(0, name, OBJPROP_PRICE, price); ObjectSetInteger(0, name, OBJPROP_COLOR, col); ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); } else { ObjectCreate(0, name, OBJ_HLINE, 0, 0, price); ObjectSetDouble(0, name, OBJPROP_PRICE, price); ObjectSetInteger(0, name, OBJPROP_COLOR, col); ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); } // Add label CreateOrUpdateLineText(name + "_TXT", iTime(_Symbol, TF, 0), price, friendlyLabel); }
Эти визуальные маркеры помогают трейдеру мгновенно распознавать критические уровни и сигналы без необходимости вчитываться в цифры, повышая скорость и точность принятия решений.
void DrawArrowAt(string name, datetime when, double price, bool isBuy) { if(ObjectFind(0, name) >= 0) ObjectDelete(0, name); color col = isBuy ? clrLime : clrMaroon; int arrowCode = isBuy ? 233 : 234; // Up or down arrow ObjectCreate(0, name, OBJ_ARROW, 0, when, price); ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode); ObjectSetInteger(0, name, OBJPROP_COLOR, col); ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); }
Мониторинг рыночных взаимодействий с опорными уровнями выполняется с помощью MonitorReferenceLevels(). Эта функция отслеживает, как цена взаимодействует с заранее заданными уровнями, выявляя касания, пробои и развороты. Она обновляет количество касаний, зафиксированные максимальные и минимальные цены, а также метрики устойчивости, чтобы оценивать значимость каждого уровня. Когда выполняются определенные условия – например, цена несколько раз касается уровня или выходит за заданные пороги, – функция определяет результат для уровня и запускает визуальные уведомления или оповещения, помогая трейдерам выявлять точки разворота рынка.
void MonitorReferenceLevels(const MqlRates &rates[], int copied) { // For each level, check touch, breakout, or reversal conditions for(int i=0; i<ArraySize(refLevels); i++) { RefLevel &L = refLevels[i]; // Touch detection if(!L.touched) { if(rates[0].high >= L.price - touchTol && rates[0].low <= L.price + touchTol) { L.touched = true; L.touchTime = rates[0].time; L.touchCount++; // Update visual CreateOrUpdateLineText(...); } } else { // Check for breakout or reversal if(rates[0].high >= L.price + breakoutThreshold) L.result = 1; // Breakout else if(rates[0].low <= L.price - reversalThreshold) L.result = -1; // Reversal // Draw outcome DrawOutcome(L, L.result == 1); } } }
Индикатор также поддерживает создание снимков текущего состояния рынка с помощью SnapshotReferenceLevels(). Эта функция сохраняет текущие значения уровней, вычисляет оценки на основе активности касаний и записывает результаты для дальнейшего использования.
void SnapshotReferenceLevels(double mean_val, double p25, double p75, double median_val, double mode_b, double mode_k, double stddev) { // Store snapshot data snapshot_mean = mean_val; snapshot_p25 = p25; snapshot_p75 = p75; snapshot_median = median_val; snapshot_modeb = mode_b; snapshot_modek = mode_k; // Visualize snapshot ClearSnapshotVisuals(); // Create lines CreateHLine_Pro(...); CreateOrUpdateLineText(...); refSnapshotTaken = true; snapshotTakenTime = TimeCurrent(); }
Затем данные снимка можно экспортировать в CSV-файлы через ExportSnapshotCSV(), что позволяет трейдерам анализировать исторические уровни, сравнивать разные рыночные режимы и делиться данными вне терминала. Эта возможность углубляет анализ рынка, позволяя проводить офлайн-разбор и стратегическое планирование.
void ExportSnapshotCSV() { // Save snapshot data to CSV file string filename = StringFormat("CSTATS_SNAPSHOT_%s_%d.csv", _Symbol, (int)TimeCurrent()); int handle = FileOpen(filename, FILE_WRITE | FILE_CSV); // Write headers and data FileWrite(handle, "symbol", _Symbol); // ... FileClose(handle); }
На протяжении всей реализации мы последовательно придерживаемся единого подхода к управлению графическими объектами и метаданными. Функции вроде DeleteObjectIfExists(), SetObjTimestamp() и CleanupMetaForObject() обеспечивают корректное создание, обновление и удаление объектов, предотвращая захламление графика и сохраняя целостность данных. Корректная работа с временными метками позволяет отслеживать, когда объекты были созданы или изменены в последний раз, что помогает удалять устаревшие визуальные элементы и поддерживать точность разметки на графике.
void CreatePanel() { if(ObjectFind(0, S_panel) >= 0) ObjectDelete(0, S_panel); if(!ObjectCreate(0, S_panel, OBJ_LABEL, 0, 0, 0)) { if(DebugMode) Print("CreatePanel: ObjectCreate failed: ", GetLastError()); return; } ObjectSetInteger(0, S_panel, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, S_panel, OBJPROP_XDISTANCE, 6); ObjectSetInteger(0, S_panel, OBJPROP_YDISTANCE, 24); ObjectSetString(0, S_panel, OBJPROP_TEXT, "Statistical — Dashboard"); ObjectSetInteger(0, S_panel, OBJPROP_FONTSIZE, 11); ObjectSetInteger(0, S_panel, OBJPROP_SELECTABLE, false); #ifdef __MQL5__ ObjectSetInteger(0, S_panel, OBJPROP_BACK, false); #endif SetObjTimestamp(S_panel); }
Результаты
В этом разделе показано, какие результаты советник дает на практике и как их интерпретировать. На диаграмме ниже показано состояние советника сразу после его подключения к графику. Слева видны статистическая панель и панель инструментов с кнопками "Mean", "Std", "Mode", "Draw Levels", "Snapshot", "Apply Dates" и "Reset All". Рядом с каждой кнопкой расположены метки-заглушки, которые после нажатия обновятся и покажут вычисленные статистические значения. В правом верхнем углу расположен элемент управления, в который можно ввести пользовательские диапазоны дат или опорные точки. Сейчас статистические уровни еще не рассчитаны, поэтому на графике видны только некоторые стандартные примитивы терминала и несколько оставшихся горизонтальных линий от предыдущих запусков.
При клике по кнопкам вроде "Mean" или "Draw Levels" советник выбирает данные из заданного диапазона – либо по периоду окна проверки, либо по конкретным датам на графике, – и вычисляет такие показатели, как среднее, стандартное отклонение, процентили, медиана и моды. Затем он рисует соответствующие горизонтальные линии и метки, заполняет поля статистики, а если вы решите сделать снимок, начнет отслеживать эти уровни на предмет касаний, устойчивости и возможных сигналов, таких как пробои или развороты.
Ниже я покажу, как задать анализируемый диапазон, указав дату начала и дату окончания.

Теперь давайте посмотрим на общую работу инструмента.

Советник установлен на график с заданным диапазоном дат (Start: 2025.08.14 11:00—End: 2025.09.23 04:00) и двумя временными маркерами на графике. Каждая кнопка на панели управления срабатывает сразу: при нажатии Mean, Std, Mode, Median, P75/P25 и т.д. соответствующая метрика вычисляется для выбранного интервала и отображается на графике в виде горизонтальной линии с текстовой меткой. Команда "Draw Levels" сразу строит выбранную группу уровней, "Remove Levels" удаляет их, а "Snapshot"/"Save Snapshot" сохраняет текущую конфигурацию для последующей работы.
Заключение
Сила этого инструмента заключается в логике, построенной вокруг кнопок, что делает тестирование и анализ на графике быстрыми и интерактивными. Одним кликом мыши статистические уровни, такие как среднее, стандартное отклонение, мода, медиана и процентили, мгновенно вычисляются и отображаются на графике в виде подписанных горизонтальных линий. Это избавляет от ручных расчетов и позволяет быстро сравнивать, строить и удалять уровни в процессе тестирования. Возможность задавать и сбрасывать диапазоны, сохранять снимки и управлять уровнями прямо через панель делает этот инструмент мощным помощником для анализа того, как цена реагирует на разные статистические зоны, упрощая и исследование, и анализ в реальном времени.
Дополнительная диаграмма усиливает этот вывод, показывая, как рассчитанные уровни соотносятся с реальным поведением рынка. Фиолетовая пунктирная линия (25-й перцентиль) и желтая сплошная линия (среднее плюс отклонение) показывают, что эти статистические уровни устойчиво выступают как реальные зоны поддержки и сопротивления. То, как цена реагирует вблизи этих уровней, наглядно показывает, что инструмент определяет ключевые зоны отскока и отката, а также выделяет надежные точки для прогнозирования пробоев и разворотов.
Этот инструмент предназначен для анализа ценовых уровней с помощью статистических расчетов. Он не совершает сделки за вас – решение по-прежнему остается за вами. Его можно рассматривать как вспомогательный советник для работы с уровнями, который лучше всего работает в связке с вашей собственной торговой стратегией. Его задача – прояснить, как цена взаимодействует со статистически рассчитанными уровнями, давая более ясный контекст для пробоев, разворотов и откатов. В дальнейшем я планирую разрабатывать и другие инструменты, которые позволят углубить анализ Price Action и принимать более обоснованные торговые решения.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19697
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Разработка инструментария для анализа Price Action (Часть 43): Вероятностный анализ свечных паттернов и пробоев
От матриц к модели: Как запустить ML-пайплайн в MQL5 и довести его до ONNX
Тестер стратегий для Python и MetaTrader 5 (Часть 02): Работа с барами, тиками и реализация встроенных функций в симуляторе
Нейросети в трейдинге: Внимание, память и рыночные паттерны в GDformer
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования