English Deutsch 日本語
preview
Разработка инструментария для анализа Price Action (Часть 42): Интерактивное тестирование на графике с кнопочной логикой и статистическими уровнями

Разработка инструментария для анализа Price Action (Часть 42): Интерактивное тестирование на графике с кнопочной логикой и статистическими уровнями

MetaTrader 5Примеры |
229 0
Christian Benjamin
Christian Benjamin

Содержание



Введение

Добро пожаловать в следующую часть серии Разработка инструментария для анализа Price Action. Наша цель – автоматизировать анализ ценового движения (Price Action) и сделать его интуитивно понятным и доступным для трейдеров, которые опираются на структуру цены вместо непрозрачных индикаторов. В этой статье мы развиваем предыдущую работу, в которой были введены такие статистические метрики, как среднее, стандартное отклонение, медиана и другие уровни распределения, вычисляемые по типичным ценам свечей. Мы также показываем, как эти метрики естественным образом соотносятся с важными рыночными опорными уровнями – поддержкой, сопротивлением и пивот-уровнями.

Вместо жестко заданных параметров и ручной подстройки входных параметров мы переходим к более интерактивному подходу прямо на графике – статистической панели управления. Эта панель размещает кнопки управления и редактируемые поля прямо на графике, чтобы пользователь мог по запросу вычислять и визуализировать статистические уровни. Хотя многие функции из предыдущей реализации сохранены, этот вариант представляет собой более зрелый и ориентированный на пользователя подход к аналитике на графике.

В этой статье мы:

  • разберем логику и назначение статистической панели управления;
  • рассмотрим ключевые моменты реализации на MQL5;
  • покажем примеры результатов и сценарии использования;
  • подведем итоги и наметим следующие шаги.


Знакомство с концепцией

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

В основе этого инструмента – управление с помощью кнопок, которое упрощает анализ рынка. Панель превращает заданные разработчиком параметры в легкий интерфейс, встроенный в график, и позволяет трейдерам:

  • выбирать диапазон даты и времени (на графике или вручную);
  • вычислять статистику распределения по выбранному диапазону;
  • отображать линии для среднего, полос стандартного отклонения, процентилей, медианы и мод;
  • фиксировать снимки опорных уровней для мониторинга в реальном времени (обнаружение касаний, пробоев и разворотов);
  • экспортировать данные снимка в CSV;
  • быстро сбрасывать панель в чистое состояние.

Этот интерфейс на графике избавляет от догадок при выборе настроек и ускоряет рабочий процесс анализа. Это также делает инструмент более безопасным и удобным для торговли в реальном времени: вместо постоянного редактирования кода трейдер может за считанные секунды рассчитать показатели и оценить результат на графике.

Flowchart

Диаграмма показывает простую модель взаимодействия с панелью: один клик по кнопке запускает одно из трех понятных действий ("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(), настраивая ее внешний вид и метку для наглядности. Одновременно скрипт обновляет статистическую метку рядом с кнопкой, подставляя в нее новое значение среднего и обеспечивая мгновенную визуальную обратную связь. После этого процесс завершается, и система готова к следующему действию пользователя. Такой сценарий гарантирует, что каждое нажатие кнопки запускает последовательность выборки данных, анализа и визуального обновления, делая инструмент интерактивным и информативным.

Button Logic

Визуальные средства особенно важны для быстрой интерпретации; для этого мы реализуем такие функции, как 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" советник выбирает данные из заданного диапазона – либо по периоду окна проверки, либо по конкретным датам на графике, – и вычисляет такие показатели, как среднее, стандартное отклонение, процентили, медиана и моды. Затем он рисует соответствующие горизонтальные линии и метки, заполняет поля статистики, а если вы решите сделать снимок, начнет отслеживать эти уровни на предмет касаний, устойчивости и возможных сигналов, таких как пробои или развороты.

Outcome 1

Ниже я покажу, как задать анализируемый диапазон, указав дату начала и дату окончания.

Setting Time Range

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

Советник установлен на график с заданным диапазоном дат (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" сохраняет текущую конфигурацию для последующей работы.


Заключение

Сила этого инструмента заключается в логике, построенной вокруг кнопок, что делает тестирование и анализ на графике быстрыми и интерактивными. Одним кликом мыши статистические уровни, такие как среднее, стандартное отклонение, мода, медиана и процентили, мгновенно вычисляются и отображаются на графике в виде подписанных горизонтальных линий. Это избавляет от ручных расчетов и позволяет быстро сравнивать, строить и удалять уровни в процессе тестирования. Возможность задавать и сбрасывать диапазоны, сохранять снимки и управлять уровнями прямо через панель делает этот инструмент мощным помощником для анализа того, как цена реагирует на разные статистические зоны, упрощая и исследование, и анализ в реальном времени.

Support and Resistance

Дополнительная диаграмма усиливает этот вывод, показывая, как рассчитанные уровни соотносятся с реальным поведением рынка. Фиолетовая пунктирная линия (25-й перцентиль) и желтая сплошная линия (среднее плюс отклонение) показывают, что эти статистические уровни устойчиво выступают как реальные зоны поддержки и сопротивления. То, как цена реагирует вблизи этих уровней, наглядно показывает, что инструмент определяет ключевые зоны отскока и отката, а также выделяет надежные точки для прогнозирования пробоев и разворотов.

Этот инструмент предназначен для анализа ценовых уровней с помощью статистических расчетов. Он не совершает сделки за вас – решение по-прежнему остается за вами. Его можно рассматривать как вспомогательный советник для работы с уровнями, который лучше всего работает в связке с вашей собственной торговой стратегией. Его задача – прояснить, как цена взаимодействует со статистически рассчитанными уровнями, давая более ясный контекст для пробоев, разворотов и откатов. В дальнейшем я планирую разрабатывать и другие инструменты, которые позволят углубить анализ Price Action и принимать более обоснованные торговые решения.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19697

Прикрепленные файлы |
Разработка инструментария для анализа Price Action (Часть 43): Вероятностный анализ свечных паттернов и пробоев Разработка инструментария для анализа Price Action (Часть 43): Вероятностный анализ свечных паттернов и пробоев
Улучшите рыночный анализ с помощью советника Candlestick Probability на MQL5 – компактного инструмента, который преобразует исходные ценовые бары в вероятностную аналитику в реальном времени по конкретному инструменту. Он классифицирует пин-бары, паттерны поглощения и доджи на закрытии бара, использует фильтрацию с учетом волатильности по ATR и при необходимости подтверждение пробоя. Советник рассчитывает простые и взвешенные по объему проценты отработки, помогая понять, каков типичный исход каждого паттерна на конкретных символах и таймфреймах. Маркеры на графике, компактная информационная панель и интерактивные переключатели позволяют быстро проверять результаты и сосредоточиться на нужном. Экспортируйте подробные CSV-логи для последующего анализа вне терминала. Используйте советник, чтобы строить вероятностные профили, оптимизировать стратегии и превращать распознавание паттернов в измеримое преимущество.
От матриц к модели: Как запустить ML-пайплайн в MQL5 и довести его до ONNX От матриц к модели: Как запустить ML-пайплайн в MQL5 и довести его до ONNX
Показано, как организовать согласованный ML-конвейер в MetaTrader 5 с разделением ролей: Python обучает и экспортирует модель в ONNX, MQL5 воспроизводит нормализацию и PCA через matrix/vector и выполняет инференс. Такой подход делает входы модели стабильными и проверяемыми, а тестер стратегий MetaTrader 5 даёт метрики для анализа поведения системы.
Тестер стратегий для Python и MetaTrader 5 (Часть 02): Работа с барами, тиками и реализация встроенных функций в симуляторе Тестер стратегий для Python и MetaTrader 5 (Часть 02): Работа с барами, тиками и реализация встроенных функций в симуляторе
В этой статье мы представим функции, аналогичные тем, которые предоставляет модуль Python–MetaTrader 5, предоставляя симулятору привычный интерфейс и собственный механизм внутренней обработки баров и тиков.
Нейросети в трейдинге: Внимание, память и рыночные паттерны в GDformer Нейросети в трейдинге: Внимание, память и рыночные паттерны в GDformer
Статья разбирает архитектуру GDformer применительно к алгоритмическому трейдингу. Показано, как обучаемая память, Dictionary-based Cross-Attention и Similarity Branch помогают сопоставлять текущее состояние рынка с выученными режимами и оценивать степень надёжности интерпретации. Дана реализация прямого прохода механизма внимания в OpenCL с использование разреженных коэффициентов без повторного перенормирования, что повышает устойчивость модели и эффективность на длинных последовательностях.