Разработка инструментария для анализа Price Action (Часть 43): Вероятностный анализ свечных паттернов и пробоев
Содержание
Введение
В этой статье мы проводим вероятностный анализ трех распространенных типов свечных паттернов – доджи, поглощения и пин-бара, – чтобы оценить, как они влияют на ценовую динамику различных валютных пар и иных инструментов. Эти свечи часто сигнализируют либо о продолжении тренда, либо о его развороте. Например, в нисходящем тренде бычий пин-бар или бычье поглощение могут указывать на возможный разворот вверх; и наоборот, в восходящем тренде медвежий пин-бар или медвежье поглощение могут предвещать разворот вниз. Доджи часто указывает на неопределенность: внутри сильного тренда такая свеча может означать краткую паузу с последующим продолжением, а на ключевом уровне поддержки или сопротивления либо после длительного движения – возможный разворот. Свеча доджи, сформировавшаяся в боковом рынке или без учета объемов, часто не несет направленного сигнала.
Распространенная проблема трейдеров – преждевременный вход: увидев бычий пин-бар, они сразу покупают, а затем получают убыточную позицию, если рынок не разворачивается. Разные инструменты и таймфреймы по-разному реагируют на один и тот же паттерн; у некоторых пар после определенных паттернов вероятность продолжения выше. Этот советник работает как инструмент вероятностного анализа: он просматривает исторические бары и оценивает, как часто каждый паттерн приводит к продолжению, развороту или успешному пробою на конкретной паре и таймфрейме. Такой эмпирический подход помогает трейдерам, работающим с Price Action, понять, какие паттерны надежнее работают как сигналы разворота или продолжения на тех рынках, где они торгуют.
Если внимательно посмотреть на схему выше, паттерн A – это бычье поглощение, похожее на сигнал на покупку; после его формирования рынок идет вверх, что подтверждает успешность сигнала. В точке B мы видим доджи, за которым следует медвежий пин-бар; затем рынок движется в пользу продавцов, что подтверждает медвежий сигнал. То же относится и к C: это медвежье поглощение дало сигнал на продажу и привело к ожидаемому снижению.
В точке D на уровне поддержки сформировалась свеча доджи, что указывает на возможный бычий разворот; следующий за ней пин-бар нужно интерпретировать по длине хвоста и расположению, поскольку сам по себе цвет свечи может вводить в заблуждение. Наконец, в точке E показано медвежье поглощение, которое не сработало: цена пошла вверх, а не вниз, что показывает вероятностную природу этих паттернов и возможность ложных сигналов. Такой подход на основе вероятностей позволяет количественно оценить, как часто каждая формация приводит к развороту, продолжению или не срабатывает на заданной паре и таймфрейме.
Обзор стратегии
Здесь важно понять идею, лежащую в основе данного инструмента. Сначала я объясню концепцию и покажу блок-схему, чтобы было легче проследить логику, а затем перейду к деталям реализации в соответствующем разделе.
Сначала инструмент собирает последние ценовые бары для выбранного символа и таймфрейма, которые нужно проанализировать. То есть загружаются цена открытия, цена максимума, цена минимума и цена закрытия каждой свечи вместе с торговой активностью на этом баре. Он также подготавливает отображение на графике и сбрасывает все счетчики, чтобы каждый запуск начинался с чистого состояния. Цель в том, чтобы использовать чистую недавнюю историю, чтобы проценты отражали именно ту рыночную среду, в которой вы собираетесь торговать.
Далее инструмент последовательно анализирует исторические бары и проверяет, соответствует ли каждый бар одному из трех отслеживаемых свечных паттернов: пин-бар, поглощение или доджи. Проще говоря, у пин-бара маленькое тело и длинная тень; поглощение – это ситуация, когда тело одной свечи полностью перекрывает тело предыдущей; а у доджи тело очень маленькое. Когда инструмент находит совпадение, он фиксирует этот момент как обнаружение паттерна.
Для каждого появления паттерна инструмент сохраняет несколько параметров: сам факт появления, время его появления и торговую активность на этом баре ("объем"). Затем для этого случая проверяются три исхода: двинулся ли сразу следующий закрытый бар в направлении, заданном паттерном; двигалась ли цена в эту сторону в какой-либо момент в пределах следующих N баров; и пробила ли цена максимум или минимум паттерна на заданное число пунктов с последующим подтверждением следующим баром. Результат каждой проверки фиксируется как положительный или отрицательный, а объемы для этих конкретных случаев также сохраняются.

В самом простом варианте эти подсчеты переводятся в легко читаемые проценты. Формулы таковы:
- Процент успеха на следующем баре = (Количество успешных случаев на следующем баре ÷ Общее количество случаев) × 100
- Процент успеха в пределах N баров = (Количество успешных случаев в пределах N баров ÷ Общее количество случаев) × 100
- Процент успешных пробоев = (Количество подтвержденных пробоев ÷ Общее число пробоев) × 100
- Процент успеха на следующем баре (взвешенный по объему) = (Объем успешных паттернов на следующем баре ÷ Общий объем всех паттернов) × 100
- Процент успеха в пределах N баров (взвешенный по объему) = (Объем успешных паттернов в пределах N баров ÷ Общий объем всех паттернов) × 100
- Процент успешных пробоев (взвешенный по объему) = (Объем подтвержденных пробоев ÷ Общий объем пробоев) × 100
Пример: Если у 50 пин-баров суммарный объем составил 10 000, а у 15 успешных – 3 000, то 3 000 ÷ 10 000 = 0,30 → 0,30 × 100 = 30% успеха на следующем баре (взвешенного по объему).
Наконец, инструмент показывает показатели по каждому паттерну на информационной панели и при необходимости записывает их в CSV-файл. Для каждого паттерна вы обычно увидите общее число обнаружений, процент успеха на следующем баре, процент успеха в пределах N баров, количество пробоев и процент успешных пробоев, а также взвешенные по объему версии этих метрик, если они включены. Если паттерн ни разу не встречался, инструмент показывает 0% или "N/A" для процентов, которые нельзя вычислить. Практический совет прост: смотрите на обе метрики. Простые проценты показывают, как часто происходило то или иное событие, а взвешенные по объему – какой объем торгов стоял за этими исходами.
Реализация
Советник Candlestick Probability EA – компактный, но функционально насыщенный инструмент для выявления ключевых свечных паттернов (пин-бар, поглощение, доджи), количественной оценки их исторического поведения и вывода результатов прямо на график и в файл. Советник создан для практических исследований и быстрой визуальной проверки: он помогает трейдерам и разработчикам систем превращать распознавание паттернов в измеримую и повторяемую статистику, которую можно использовать при разработке стратегии или принятии решений при ручной торговле.
Ниже представлена структурированная реализация на MQL5, охватывающая архитектуру системы, входные параметры, статистическую модель, визуализацию и пользовательский интерфейс.
Конфигурация и входные параметры
В верхней части файла расположен компактный и хорошо документированный набор входных параметров, который позволяет настраивать советник без редактирования кода. В числе этих параметров – окно проверки (LookbackBars), окно просмотра вперед (LookAheadBars), пороги пробоя (BreakoutPips, BreakoutConfirmBars), таймфрейм (TF), флаги визуализации и интерфейса, такие как ShowPatternMarkers и ShowControlButtons, а также параметры экспорта и взвешивания, такие как ExportCSV и UseVolumeWeighting. Вынос этих настроек во входные параметры упрощает быстрые эксперименты и показывает, какие именно параметры важны при настройке и сравнении результатов.
//--- Inputs, user-configurable parameters input int LookbackBars = 2000; // how many bars to scan input int LookAheadBars = 5; // how many bars forward to look for follow-up input int BreakoutPips = 5; // breakout threshold in pips input int BreakoutConfirmBars = 1; // bars after breakout used for confirmation input ENUM_TIMEFRAMES TF = PERIOD_CURRENT; input bool ExportCSV = false; input string CSVFileName = "PatternStats.csv"; input bool ShowPatternMarkers = true; input int MaxMarkers = 500; input int MarkerFontSize = 12; input int MarkerOffsetPips = 3; input int MarkerStackWindow = 2; input bool UseVolumeWeighting = true; input bool ShowControlButtons = true;
Модель данных для статистики паттернов
Метрики для каждого паттерна инкапсулированы в единой структуре PatternStats , которая хранит число появлений, совпадения на следующем баре и в пределах окна просмотра, пробои и подтвержденные успешные пробои, суммарный объем и взвешенные по объему аналоги. Такая организация метрик сохраняет модульность агрегации и упрощает добавление новой статистики или паттернов без изменения основного цикла анализа.
struct PatternStats { string name; int total; int nextSameDir; int withinLookAheadSame; int breakouts; int breakoutSuccess; double volTotal; double weightedNextSameDir; double weightedWithin; double weightedBreakouts; double weightedBreakoutSuccess; }; // Example declarations PatternStats pinbar, engulfing, dojiStats;
Инициализация (OnInit)
Задачи инициализации последовательно выполняются в OnInit(); код обнуляет всю статистику через ResetStats(), присваивает читаемые имена каждому экземпляру PatternStats, подготавливает счетчики маркеров для каждого бара и при необходимости создает кнопки управления, если включена интерактивность. С помощью EventSetTimer() запускается короткий таймер событий, а затем сразу однократно вызывается Analyze(), чтобы результаты появились на графике сразу после запуска советника - это обеспечивает повторяемое поведение при перезапусках и безопасную работу во время активной торговли.
int OnInit() { // reset stats and name patterns ResetStats(); pinbar.name = "Pinbar"; engulfing.name = "Engulfing"; dojiStats.name = "Doji"; // prepare marker counts buffer ArrayResize(markerCounts, 64); for(int i=0;i<ArraySize(markerCounts);i++) markerCounts[i]=0; // create UI if requested if(ShowControlButtons) CreateControlButtons(); // run periodically to detect new bars EventSetTimer(5); // initial analysis Analyze(); return(INIT_SUCCEEDED); }
Очистка перед выходом (OnDeinit)
В OnDeinit() реализовано корректное завершение: код останавливает таймер, удаляет все созданные советником графические объекты, включая маркеры, кнопки и элементы информационной панели, и принудительно перерисовывает график, чтобы убрать артефакты. Такая тщательная очистка не оставляет на графике лишних объектов и гарантирует, что при удалении советника другие инструменты и шаблоны не будут затронуты.
void OnDeinit(const int reason) { EventKillTimer(); DeleteAllOurObjects(); // removes only objects with EA prefixes ChartRedraw(); }
Реактивные обновления (OnTimer и OnChartEvent)
Отзывчивость графика обеспечивается легким таймером и обработкой событий графика. OnTimer() отслеживает появление новых баров и запускает Analyze() только при необходимости, чтобы избежать лишних вычислений, а OnChartEvent() обрабатывает нажатия на кнопки и переключает отдельные режимы распознавания. Клик по элементу управления обновляет флаг включения, обновляет метку кнопки текущими метриками и повторно запускает анализ, чтобы действия пользователя сразу отражались в экранной статистике.
void OnTimer() { static datetime lastTime = 0; datetime t = iTime(_Symbol, TF, 1); // previous completed bar if(t == lastTime) return; lastTime = t; Analyze(); } void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK && StringFind(sparam, "Btn_") == 0) { if(StringCompare(sparam, "Btn_Pinbar") == 0) { enabledPinbar = !enabledPinbar; UpdateButtonLabel("Btn_Pinbar", pinbar, enabledPinbar); Analyze(); } if(StringCompare(sparam, "Btn_Engulfing") == 0) { enabledEngulfing = !enabledEngulfing; UpdateButtonLabel("Btn_Engulfing", engulfing, enabledEngulfing); Analyze(); } if(StringCompare(sparam, "Btn_Doji") == 0) { enabledDoji = !enabledDoji; UpdateButtonLabel("Btn_Doji", dojiStats, enabledDoji); Analyze(); } } }
Функции распознавания паттернов
Каждое правило паттерна реализовано как краткая параметризованная вспомогательная функция, например IsPinbar(shift), IsEngulfing(shift) и IsDoji(shift). Эти функции читают только нужные поля бара и применяют настраиваемые пороги, такие как PinbarBodyPct, PinbarTailPct и DojiBodyPct, возвращая направленные значения (+1, -1, 0), что упрощает дальнейшую агрегацию и позволяет реализовать приоритетную или комбинированную логику паттернов.
int IsPinbar(int shift) { if(IsDoji(shift)) return 0; double o = iOpen(_Symbol, TF, shift), c = iClose(_Symbol, TF, shift); double h = iHigh(_Symbol, TF, shift), l = iLow(_Symbol, TF, shift); double range = h - l; if(range <= 0) return 0; double body = MathAbs(c - o); if(body > PinbarBodyPct * range) return 0; double upperTail = h - MathMax(o, c); double lowerTail = MathMin(o, c) - l; if(lowerTail >= PinbarTailPct * range && upperTail <= (1.0 - PinbarTailPct) * range) return +1; if(upperTail >= PinbarTailPct * range && lowerTail <= (1.0 - PinbarTailPct) * range) return -1; return 0; } int IsEngulfing(int shift) { int totalBars = iBars(_Symbol, TF); if(totalBars <= shift + 1) return 0; double o1 = iOpen(_Symbol, TF, shift), c1 = iClose(_Symbol, TF, shift); double o2 = iOpen(_Symbol, TF, shift + 1), c2 = iClose(_Symbol, TF, shift + 1); double body1 = c1 - o1, body2 = c2 - o2; if(body1 > 0 && body2 < 0 && o1 <= c2 && c1 >= o2) return +1; if(body1 < 0 && body2 > 0 && o1 >= c2 && c1 <= o2) return -1; return 0; } bool IsDoji(int shift) { double o = iOpen(_Symbol, TF, shift), c = iClose(_Symbol, TF, shift); double h = iHigh(_Symbol, TF, shift), l = iLow(_Symbol, TF, shift); double range = h - l; if(range <= 0) return false; return (MathAbs(c - o) <= DojiBodyPct * range); }
Основной цикл анализа (Analyze)
Основу советника составляет один линейный проход по заданному окну анализа; масштабирование пунктов и пипсов для конкретного символа рассчитывается один раз заранее, для каждого сдвига вызываются функции распознавания паттернов, обнаруженные паттерны обрабатываются через UpdateStatsOnPattern(), а при необходимости рисуются маркеры с вертикальным смещением. Однопроходный цикл обеспечивает предсказуемые результаты и предсказуемую нагрузку на процессор.
void Analyze() { ResetStats(); int totalBars = iBars(_Symbol, TF); if(totalBars < 3) return; int maxIndex = MathMin(totalBars - 1, LookbackBars); if(maxIndex < 2) return; // pip scaling double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); double factor = (digits == 5 || digits == 3) ? 10.0 : 1.0; double breakoutPoints = BreakoutPips * point * factor; ArrayResize(markerCounts, MathMax(3, maxIndex + 3)); for(int i=0;i<ArraySize(markerCounts);i++) markerCounts[i]=0; if(ShowPatternMarkers) DeletePatternObjects(); int drawn=0; for(int shift=2; shift<=maxIndex; shift++) { int eng = IsEngulfing(shift); int pin = IsPinbar(shift); bool doj = IsDoji(shift); bool matched = false; if(eng != 0 && enabledEngulfing) { UpdateStatsOnPattern(engulfing, shift, eng, LookAheadBars, breakoutPoints); if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Engulfing", eng, MarkerFontSize, MarkerOffsetPips, factor); drawn++; } matched = true; } if(!matched && pin != 0 && enabledPinbar) { UpdateStatsOnPattern(pinbar, shift, pin, LookAheadBars, breakoutPoints); if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Pinbar", pin, MarkerFontSize, MarkerOffsetPips, factor); drawn++; } matched = true; } if(!matched && doj && enabledDoji) { UpdateStatsOnPattern(dojiStats, shift, 0, LookAheadBars, breakoutPoints); if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Doji", 0, MarkerFontSize, MarkerOffsetPips, factor); drawn++; } } } if(ExportCSV) SaveToCSV(); DrawDashboard(); DrawSummaryHeaderAndLabel(); if(ShowControlButtons) { UpdateButtonLabel("Btn_Pinbar", pinbar, enabledPinbar); UpdateButtonLabel("Btn_Engulfing", engulfing, enabledEngulfing); UpdateButtonLabel("Btn_Doji", dojiStats, enabledDoji); } ChartRedraw(); }
Обновление статистики (UpdateStatsOnPattern)
Распознавание паттерна запускает комплексную процедуру оценки, которая увеличивает счетчики появлений, накапливает объем, проверяет сразу следующий бар и заданное окно просмотра вперед на движение в том же направлении, обнаруживает пробои экстремумов паттерна с использованием BreakoutPips и оценивает подтверждение через BreakoutConfirmBars. Поддерживаются как простые подсчеты, так и счетчики с учетом объема, чтобы результаты можно было интерпретировать либо по частоте, либо по участию в рынке.
void UpdateStatsOnPattern(PatternStats &s, int shift, int direction, int lookAhead, double breakoutPoints) { s.total++; double vol = (double)iVolume(_Symbol, TF, shift); if(vol <= 0) vol = 1.0; s.volTotal += vol; // next-bar direction if(shift - 1 >= 0) { double nO = iOpen(_Symbol, TF, shift - 1), nC = iClose(_Symbol, TF, shift - 1); int nDir = (nC > nO) ? +1 : (nC < nO) ? -1 : 0; if(direction == 0) { if(nDir != 0) { s.nextSameDir++; s.weightedNextSameDir += vol; } } else { if(nDir == direction) { s.nextSameDir++; s.weightedNextSameDir += vol; } } } // within lookahead bool anySame = false; double weightedAny = 0.0; int totalBars = iBars(_Symbol, TF); for(int k=1; k<=lookAhead; k++) { int idx = shift - k; if(idx < 1 || idx > totalBars - 1) break; double o = iOpen(_Symbol, TF, idx), c = iClose(_Symbol, TF, idx); double v = (double)iVolume(_Symbol, TF, idx); if(v <= 0) v = 1.0; int d = (c > o) ? +1 : (c < o) ? -1 : 0; if(direction == 0) { if(d != 0) { anySame = true; weightedAny += v; break; } } else { if(d == direction) { anySame = true; weightedAny += v; break; } } } if(anySame) { s.withinLookAheadSame++; s.weightedWithin += (weightedAny > 0.0 ? weightedAny : vol); } // breakout detection and confirmation double highP = iHigh(_Symbol, TF, shift), lowP = iLow(_Symbol, TF, shift); bool breakout = false; if(shift - 1 >= 0) { double nextHigh = iHigh(_Symbol, TF, shift - 1), nextLow = iLow(_Symbol, TF, shift - 1); if(direction == +1 && nextHigh >= highP + breakoutPoints) breakout = true; else if(direction == -1 && nextLow <= lowP - breakoutPoints) breakout = true; else if(direction == 0 && (nextHigh >= highP + breakoutPoints || nextLow <= lowP - breakoutPoints)) breakout = true; } if(breakout) { s.breakouts++; s.weightedBreakouts += vol; int confirmShift = shift - 1 - BreakoutConfirmBars; if(confirmShift >= 0 && confirmShift <= totalBars - 1) { double cO = iOpen(_Symbol, TF, confirmShift), cC = iClose(_Symbol, TF, confirmShift); double cVol = (double)iVolume(_Symbol, TF, confirmShift); if(cVol <= 0) cVol = 1.0; int cDir = (cC > cO) ? +1 : (cC < cO) ? -1 : 0; bool confirmed = false; if(direction == +1 && cC >= highP + breakoutPoints) confirmed = true; else if(direction == -1 && cC <= lowP - breakoutPoints) confirmed = true; else if(direction == 0 && (cC >= highP + breakoutPoints || cC <= lowP - breakoutPoints)) confirmed = true; if(confirmed && (direction == 0 ? cDir != 0 : cDir == direction)) { s.breakoutSuccess++; s.weightedBreakoutSuccess += cVol; } } } }
Вычисление процентов и экспорт в CSV
Преобразование простых подсчетов или взвешенных сумм в наглядные проценты централизовано в функции ComputePct(); если включена опция UseVolumeWeighting, возвращаются проценты, взвешенные по объему; в противном случае используются простые проценты по частоте. Если опция ExportCSV включена, функция SaveToCSV() записывает файл с разделителем табуляции, заголовками и одной строкой на каждый паттерн, чтобы последующий анализ в Python, R или табличных редакторах был простым и воспроизводимым.
double ComputePct(double weightedValue, PatternStats &s, int simpleCount, int total) { if(UseVolumeWeighting) { if(s.volTotal <= 0.0) return 0.0; return 100.0 * weightedValue / s.volTotal; } else { if(total <= 0) return 0.0; return 100.0 * simpleCount / total; } } void SaveToCSV() { int handle = FileOpen(CSVFileName, FILE_WRITE|FILE_CSV|FILE_ANSI, '\t'); if(handle == INVALID_HANDLE) { Print("Failed to open CSV: ", CSVFileName); return; } FileWrite(handle, "Pattern","Total","Next%","Within%","Breakouts","Success%","VolTotal","W_Next%","W_Within%","W_Breakouts%","W_Success%"); WriteStatsLine(handle, pinbar); WriteStatsLine(handle, engulfing); WriteStatsLine(handle, dojiStats); FileClose(handle); } void WriteStatsLine(int handle, PatternStats &s) { double nextPct = ComputePct(s.weightedNextSameDir, s, s.nextSameDir, s.total); double withinPct = ComputePct(s.weightedWithin, s, s.withinLookAheadSame, s.total); double successPct = s.breakouts > 0 ? 100.0 * s.breakoutSuccess / s.breakouts : 0.0; double wBreakoutsPct = s.volTotal > 0.0 ? 100.0 * s.weightedBreakouts / s.volTotal : 0.0; double wSuccessPct = s.weightedBreakouts > 0.0 ? 100.0 * s.weightedBreakoutSuccess / s.weightedBreakouts : 0.0; FileWrite(handle, s.name, s.total, DoubleToString(nextPct,1), DoubleToString(withinPct,1), s.breakouts, DoubleToString(successPct,1), DoubleToString(s.volTotal,0), DoubleToString(nextPct,1), DoubleToString(withinPct,1), DoubleToString(wBreakoutsPct,1), DoubleToString(wSuccessPct,1)); }
Визуализация на графике, маркеры и наложение
Визуальные маркеры рисуются функцией DrawPatternOnChartWithStacking(), которая размещает компактные текстовые стрелки или символы на баре паттерна и располагает их по вертикали, чтобы избежать наложения. За размещение маркеров по уровням отвечают массив markerCounts[] для каждого бара и параметр MarkerStackWindow. Маркеры имеют цветовую кодировку и намеренно сделаны ненавязчивыми, чтобы сразу давать контекст и не заслонять движение цены.
void DrawPatternOnChartWithStacking(int shift, string patternName, int direction, int fontSize, int offsetPips, double factor) { string name = "Pattern_" + patternName + "_" + IntegerToString(shift); if(ObjectFind(0, name) != -1) ObjectDelete(0, name); datetime t = iTime(_Symbol, TF, shift); double highP = iHigh(_Symbol, TF, shift), lowP = iLow(_Symbol, TF, shift); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double offset = offsetPips * point * factor; double priceBase = (highP + lowP) / 2.0; if(direction > 0) priceBase = lowP - offset; else if(direction < 0) priceBase = highP + offset; int idx = shift; if(idx >= ArraySize(markerCounts)) ArrayResize(markerCounts, idx + 5); int stack = markerCounts[idx]++; double stackOffset = (stack / MarkerStackWindow) * offset * 1.2; if(direction > 0) priceBase -= stackOffset; else priceBase += stackOffset; string arrow = (direction>0 ? "▲" : direction<0 ? "▼" : "◆"); string text = arrow + " " + StringSubstr(patternName, 0, 3); ObjectCreate(0, name, OBJ_TEXT, 0, t, priceBase); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, 0); ObjectSetInteger(0, name, OBJPROP_BACK, 1); ObjectSetInteger(0, name, OBJPROP_COLOR, (direction>0 ? clrLime : direction<0 ? clrRed : clrLightBlue)); }
Информационная панель и метки
Компактная информационная панель на графике, создаваемая функцией DrawDashboard(), выводит стандартные столбцы Pattern, Total, Next%, Within%, Breakouts и Success%, а функция DrawSummaryHeaderAndLabel() создает краткую сводку в левом верхнем углу для быстрой проверки. Использование единых префиксов в именах объектов позволяет советнику надежно удалять эти элементы интерфейса, когда они больше не нужны.
void DrawDashboard() { string rectName = "Dash_Background"; if(ObjectFind(0, rectName) != -1) ObjectDelete(0, rectName); ObjectCreate(0, rectName, OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, rectName, OBJPROP_CORNER, 0); ObjectSetInteger(0, rectName, OBJPROP_XDISTANCE, 8); ObjectSetInteger(0, rectName, OBJPROP_YDISTANCE, 70); ObjectSetInteger(0, rectName, OBJPROP_XSIZE, 660); ObjectSetInteger(0, rectName, OBJPROP_YSIZE, 80); ObjectSetInteger(0, rectName, OBJPROP_BACK, 1); ObjectSetInteger(0, rectName, OBJPROP_COLOR, clrDarkSlateGray); // header and rows omitted for brevity, see DrawDashboard in main code for full cell logic } void DrawSummaryHeaderAndLabel() { string header = "SummaryHeader"; if(ObjectFind(0, header) != -1) ObjectDelete(0, header); ObjectCreate(0, header, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, header, OBJPROP_CORNER, 0); ObjectSetInteger(0, header, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, header, OBJPROP_YDISTANCE, 12); ObjectSetInteger(0, header, OBJPROP_FONTSIZE, 11); ObjectSetString(0, header, OBJPROP_TEXT, "Candlestick Pattern Stats Summary"); ObjectSetInteger(0, header, OBJPROP_COLOR, clrBlue); string sum = "SummaryLabel"; if(ObjectFind(0, sum) != -1) ObjectDelete(0, sum); ObjectCreate(0, sum, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, sum, OBJPROP_CORNER, 0); ObjectSetInteger(0, sum, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, sum, OBJPROP_YDISTANCE, 30); ObjectSetInteger(0, sum, OBJPROP_FONTSIZE, 9); ObjectSetInteger(0, sum, OBJPROP_BACK, 1); ObjectSetString(0, sum, OBJPROP_TEXT, FormatStatsForLabel(pinbar) + "\n" + FormatStatsForLabel(engulfing) + "\n" + FormatStatsForLabel(dojiStats)); }
Кнопки управления и динамические метки
Кнопки с автоматическим подбором ширины создаются функцией CreateAutoSizedButton() и обновляются в реальном времени через UpdateButtonLabel(). Каждая кнопка показывает короткий идентификатор, состояние ON/OFF и текущие метрики Next% и Succ%. Функция EstimateButtonWidth() предотвращает обрезку текста, а цвет кнопок меняется в зависимости от состояния, чтобы пользователь мог интерактивно настраивать, какие паттерны участвуют в статистике.
void CreateAutoSizedButton(string name, int x, int y, string text) { if(ObjectFind(0, name) != -1) ObjectDelete(0, name); ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_CORNER, 1); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetString(0, name, OBJPROP_FONT, ButtonFont); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, ButtonFontSize); ObjectSetString(0, name, OBJPROP_TEXT, text); int w = EstimateButtonWidth(text, ButtonFontSize); int h = ButtonFontSize + ButtonPaddingY * 2; ObjectSetInteger(0, name, OBJPROP_XSIZE, w); ObjectSetInteger(0, name, OBJPROP_YSIZE, h); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrWhite); } void UpdateButtonLabel(string name, PatternStats &s, bool enabled) { if(ObjectFind(0, name) == -1) return; string onoff = enabled ? "ON" : "OFF"; double nextPct = ComputePct(s.weightedNextSameDir, s, s.nextSameDir, s.total); double successPct = s.breakouts > 0 ? 100.0 * s.breakoutSuccess / s.breakouts : 0.0; string shortName = (StringCompare(s.name, "Pinbar") == 0) ? "Pin" : (StringCompare(s.name,"Engulfing")==0) ? "Eng" : "Doj"; string txt = shortName + ": " + onoff + " Next% " + DoubleToString(nextPct,1) + " Succ% " + DoubleToString(successPct,1); ObjectSetString(0, name, OBJPROP_TEXT, txt); ObjectSetInteger(0, name, OBJPROP_XSIZE, EstimateButtonWidth(txt, ButtonFontSize)); if(enabled) { ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrWhite); ObjectSetInteger(0, name, OBJPROP_COLOR, clrBlack); } else { ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrLightGray); ObjectSetInteger(0, name, OBJPROP_COLOR, clrDimGray); } } int EstimateButtonWidth(const string txt, const int fontSize) { int len = StringLen(txt); double charW = fontSize * 0.75; int base = (int)MathCeil(len * charW) + ButtonPaddingX * 2 + 10; if(base < 100) base = 100; return base; }
Служебные операции и управление объектами
Каждый объект графика, созданный советником, получает фиксированный префикс, например OBJ_PREFIX_PATTERN, OBJ_PREFIX_DASH или OBJ_PREFIX_BTN. Специальные функции удаления удаляют только объекты с этими префиксами, что снижает риск случайного удаления пользовательских объектов и делает поведение советника предсказуемым и ненавязчивым.
void DeleteAllOurObjects() { int total = ObjectsTotal(0); for(int i = total - 1; i >= 0; i--) { string nm = ObjectName(0, i); if(StringFind(nm, "Pattern_") == 0 || StringFind(nm, "Dash_") == 0 || StringFind(nm, "Btn_") == 0 || StringCompare(nm, "SummaryLabel") == 0 || StringCompare(nm, "SummaryHeader") == 0) ObjectDelete(0, nm); } }
Тестирование
В этом разделе мы оцениваем советник на Step Index и Boom 300 Index, чтобы проверить устойчивость инструмента на синтетических инструментах с разными профилями волатильности.
- Step Index

Схема выше показывает результаты работы инструмента на Step Index. После запуска советника на графике создается компактная информационная панель в левом верхнем углу со сводкой по статистике паттернов, а в левом нижнем углу – набор кнопок-переключателей для управления визуализацией. Нажатие на кнопку отключает или включает соответствующий режим распознавания паттерна, убирая или возвращая подписи свечей, чтобы можно было пошагово просматривать график и визуально проверять распознавание паттернов. Такие переключатели полезны, когда нужно сосредоточиться на паттернах одного типа.
В приведенном примере информационная панель отображает показатели успешности: 40,2% для пин-бара и 31,7% для поглощения. Для доджи показатель составляет 65,1%; однако в этом анализе доджи рассматривается как нейтральный сигнал, поскольку высокий показатель в основном обусловлен фильтрацией пробоев, а не направленным смещением. Поэтому результат для доджи лучше трактовать как указание на склонность инструмента к продолжению движения или реализации пробоя, а не как простой бычий или медвежий сигнал.
Результаты также экспортируются в CSV для независимой проверки. Чтобы создать файлы, откройте входные параметры советника и установите ExportCSV = true; имя сводного файла можно изменить через CSVFileName. Советник записывает сводный CSV-файл в папку Files терминала для последующего автономного анализа.
input bool ExportCSV = true; input string CSVFileName = "PatternStats.csv";
- Boom 300 Index

На Boom 300 Index измеренные показатели успешности составляют 40,0% для поглощения, 42,1% для пин-бара и 78,6% для доджи. Эти значения показывают, что на этом инструменте после появления доджи по правилам советника гораздо чаще реализуются сценарии продолжения движения или пробоя, тогда как поглощение и пин-бар демонстрируют более скромные направленные результаты.
Если сравнить Boom 300 со Step Index, то эффективность пин-бара на обоих инструментах практически одинакова, что говорит об определенной устойчивости этого паттерна. При этом поглощение на Boom 300 работает заметно лучше, чем на Step Index; это означает, что в рамках текущих параметров советника и рассматриваемой выборки сигналы поглощения чаще дают положительную отработку на Boom 300.
Заключение
В этой статье мы рассмотрели построение советника на MQL5, который измеряет вероятности свечных паттернов на нескольких инструментах; показали, как обнаруживать пин-бар, поглощение и доджи, собирать статистику по каждому паттерну и выводить показатели успешности, чтобы оценивать, какие паттерны дают положительную отработку на каждой паре. Я рассматриваю этот советник как экспериментальный аналитический инструмент для визуализации паттернов Price Action и их эмпирических вероятностей. Он задуман как инструмент исследования и проверки, а не как готовый торговый робот; пользователю нужно самостоятельно подбирать входные параметры и процедуры валидации под свои таймфреймы и инструменты.
Когда надежность этого подхода подтверждается на достаточно больших выборках, он помогает формировать реалистичные ожидания относительно того, как тот или иной паттерн обычно ведет себя в реальной торговле; кроме того, он поддерживает воспроизводимое тестирование через CSV-файлы аудита. В дальнейшем я планирую усовершенствовать данный инструмент в последующих статьях цикла. Буду рад вашим предложениям по улучшению и запросам на добавление новых аналитических функций.
Читайте другие мои статьи здесь.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19738
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Тестер стратегий для Python и MetaTrader 5 (Часть 02): Работа с барами, тиками и реализация встроенных функций в симуляторе
Разработка инструментария для анализа Price Action (Часть 42): Интерактивное тестирование на графике с кнопочной логикой и статистическими уровнями
Построение моделей волатильности в MQL5 (Часть I): Первичная реализация
От матриц к модели: Как запустить ML-пайплайн в MQL5 и довести его до ONNX
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования