English
preview
Автоматизация классических рыночных методов в MQL5 (Часть 3): Стадийный анализ Стэна Вайнштейна

Автоматизация классических рыночных методов в MQL5 (Часть 3): Стадийный анализ Стэна Вайнштейна

MetaTrader 5Трейдинг |
63 0
Tola Moses Hector
Tola Moses Hector

Введение

В Части 1 и Части 2 этой серии мы автоматизировали метод Ричарда Вайкоффа: определение структур накопления и распределения, вход в сделку в оптимальный момент и расчёт целей на основе «причины», измеренной в пределах торгового диапазона. Обе статьи были посвящены конкретным структурным событиям — «Пружине» и «Признаку силы» — и точной последовательности, в которой они должны возникнуть, чтобы торговый сигнал считался действительным.

Стадийный анализ Стэна Вайнштейна рассматривает рынок шире. Вместо ожидания конкретного события внутри структуры он прежде всего отвечает на вопрос: в какой фазе жизненного цикла сейчас находится данный рынок? Вайнштейн выделил четыре стадии — Стадия 1 (Основание), Стадия 2 (Рост), Стадия 3 (Вершина) и Стадия 4 (Снижение) — и сформулировал ключевое наблюдение: почти вся прибыль в устойчивом тренде зарабатывается на Стадии 2, то есть в фазе роста. Покупать на любой другой стадии — значит, по его словам, играть против казино, то есть против системы.

Изначально метод был разработан для акций на недельных графиках. Изучив тысячи ценовых моделей, Вайнштейн пришёл к выводу, что 30-недельная простая скользящая средняя является наиболее надёжным отдельным индикатором стадии рынка. Если 30-недельная MA растёт, а цена находится выше неё, рынок находится в Стадии 2. Обратная ситуация — снижение 30-недельной MA при цене ниже неё — соответствует Стадии 4. Если после снижения MA выравнивается, а цена начинает двигаться в боковом диапазоне над ней, формируется Стадия 1. Если же MA выравнивается после роста, а цена начинает колебаться ниже неё, формируется Стадия 3.

Сигналом на вход служит пробой вверх в Стадию 2: цена закрывается выше уровня сопротивления Стадии 1 при объёме как минимум вдвое выше недавнего среднего. Сигналом на выход становится первый признак Стадии 3 — пробой ценой 30-недельной MA вниз на повышенном объёме либо начало выравнивания самой MA после длительного роста. Для коротких позиций логика зеркальна: вход — на пробое вниз поддержки Стадии 3, выход — при начале формирования Стадии 1.

В этой статье данный метод реализован в виде полноценного торгового советника на MQL5. Мы рассмотрим следующие темы:

  1. Понимание четырёх стадий
  2. Адаптация метода Вайнштейна для рынка Форекс
  3. Реализация в MQL5
  4. Тестирование
  5. Известные ограничения
  6. Заключение


Понимание четырёх стадий

Согласно подходу Вайнштейна, любой рынок проходит через цикл стадий. Продолжительность цикла не является строго регулярной: Стадия 2 может длиться месяцы или годы, а Стадия 4 — быть как краткой, так и продолжительной. Однако порядок стадий остаётся неизменным. Понимание признаков каждой стадии служит основой для программной реализации логики их определения.

objective

Рис. 1. График со Стадией 1, Стадией 2 (вход) и Стадией 3

Стадия 1 — Основание

Стадия 1 наступает после нисходящего тренда. До этого цена снижалась, 30-недельная MA имела отрицательный наклон, а инициатива была на стороне продавцов. В определённый момент продажи исчерпываются. Цена перестаёт обновлять минимумы и переходит к боковому движению. 30-недельная MA начинает выравниваться: она уже не снижается, но ещё не перешла к росту. Объём на Стадии 1 обычно уменьшается, поскольку ни покупатели, ни продавцы в этот момент не проявляют большого интереса. В терминологии Вайкоффа это фаза накопления, хотя сам Вайнштейн такое определение не использовал.

Главный признак, отличающий позднюю Стадию 1 от её середины, — рост объёма в дни повышения цены и его сокращение в дни снижения. Институциональные участники постепенно формируют позиции. 30-недельная MA ещё не развернулась вверх, но характер движения цены вокруг неё уже меняется.

Стадия 2 — Фаза роста

Стадия 2 начинается с прорыва. Цена закрывается выше уровня сопротивления, ограничивавшего Стадию 1, при объёме заметно выше среднего. 30-недельная MA разворачивается вверх. По Вайнштейну, это единственная стадия, на которой следует покупать.

Признаки Стадии 2 однозначны: цена находится выше растущей 30-недельной MA, объём увеличивается на восходящих движениях и сокращается на откатах, а максимумы и минимумы последовательно повышаются. Откаты к 30-недельной MA на Стадии 2 являются возможностями для покупки, а не предупреждениями. Тренд сохраняется, пока цена не закрывается заметно ниже 30-недельной MA на повышенном объёме.

Стадия 3 — Вершина

Стадия 3 формируется, когда восходящее движение исчерпывает потенциал. Цена перестаёт формировать существенные новые максимумы и начинает колебаться вокруг 30-недельной MA. Сама MA выравнивается и больше не растёт. Объём в дни повышения цены сокращается, а в дни снижения начинает увеличиваться. В терминологии Вайкоффа это фаза распределения.

Опасность Стадии 3 заключается в том, что она может выглядеть как модель продолжения. Цена всё ещё находится вблизи максимумов, а 30-недельная MA по-прежнему проходит в той же области графика. Вайнштейн предупреждал об этом прямо: на Стадии 3 соотношение риск/прибыль уже сместилось. Трейдер больше не следует за трендом, а фактически торгует против назревающего разворота.

Стадия 4 — Фаза снижения

Стадия 4 начинается, когда цена пробивает поддержку Стадии 3 вниз на повышенном объёме. 30-недельная MA разворачивается вниз. С этого момента ралли становятся возможностями для продаж, а не точками для покупок на пробой. Цена последовательно формирует понижающиеся максимумы и минимумы. Объём увеличивается на нисходящих движениях и сокращается во время подъёмов.

Стадия 4 является зеркальным отражением Стадии 2. Здесь шорт-трейдеры получают то же структурное преимущество, какое у лонг-трейдеров есть на Стадии 2.


Адаптация метода Вайнштейна для рынка Форекс

Вайнштейн разработал стадийный анализ для акций на недельных графиках. Чтобы корректно применять его к валютным парам в MetaTrader 5, необходимо внести три изменения.

Таймфрейм. 30-недельной MA на недельном графике соответствует 150-периодная MA на дневном графике или приблизительно 750-периодная MA на H4. Советник использует дневной таймфрейм и 150-периодную простую скользящую среднюю. Это позволяет получать ту же структурную информацию, что и 30-недельная MA Вайнштейна, сохраняя практичность тестирования в MetaTrader 5 на дневных барах.

Объём. Для акций доступен реальный биржевой объём. Для валютных пар используется тиковый объём — косвенный показатель активности, как было установлено в предыдущих частях серии. Здесь применяется тот же подход к относительному объёму: текущий объём сравнивается со средним объёмом за скользящее окно. Чтобы прорыв считался подтверждённым, объём должен заметно превышать это среднее.

Без ранжирования по относительной силе. Оригинальный метод Вайнштейна включает фильтр относительной силы: он покупает только акции, динамика которых превосходит общерыночный индекс. На рынке Форекс нет единого эталонного индекса, аналогичного S&P 500. Поэтому фильтр относительной силы заменён подтверждением импульса: при входе на прорыве в Стадию 2 значение 14-периодного RSI должно быть выше 50, подтверждая направление движения.


Реализация в MQL5

Советник разрабатывается поэтапно. Перед каждым фрагментом кода приводится пояснение.

Подключаемые файлы, перечисления и входные параметры

Для исполнения торговых операций потребуется торговая библиотека, перечисление четырёх стадий и входные параметры, управляющие периодом скользящей средней, порогом объёма, фильтром RSI и настройками риска.

Примечание: листинги кода и комментарии в них приведены в оригинале.

//+------------------------------------------------------------------+
//|                                           WeinsteinStageEA.mq5   |
//|                                Copyright 2026, Tola Moses Hector |
//|                                          https://t.me/tolahector |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Tola Moses Hector"
#property link      "https://t.me/tolahector"
#property version   "1.00"
#property description "Automating Classic Market Methods in MQL5 Part 3"
#property description "Stan Weinstein Stage Analysis EA"
#property description "Trades Stage 2 breakouts long and Stage 4 breakdowns short"
#property description "Daily timeframe recommended"

#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Market Stage Enumeration                                         |
//+------------------------------------------------------------------+
enum ENUM_MARKET_STAGE
  {
   STAGE_UNKNOWN   = 0, // Stage cannot be determined
   STAGE_1_BASE    = 1, // Base: MA flattening, price sideways above or at MA
   STAGE_2_ADVANCE = 2, // Advance: MA rising, price above MA, higher highs
   STAGE_3_TOP     = 3, // Top: MA flattening, price oscillating around MA
   STAGE_4_DECLINE = 4  // Decline: MA falling, price below MA, lower lows
  };

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "=== Stage Detection ==="
input int    InpMAPeriod         = 150;    // MA period (150 daily = 30 weekly)
input int    InpSlopeLookback    = 10;     // Bars to measure MA slope direction
input double InpSlopeThreshold   = 0.0001; // Minimum slope to classify as rising or falling
input int    InpHighLowLookback  = 20;     // Bars to check for higher highs and lower lows

input group "=== Breakout Conditions ==="
input double InpVolBreakoutMult  = 1.8;   // Volume multiplier required at breakout
input int    InpVolAvgPeriod     = 20;    // Bars for average volume calculation
input int    InpRSIPeriod        = 14;    // RSI period for momentum filter
input double InpRSIBullMin       = 50.0;  // Minimum RSI for Stage 2 entry
input double InpRSIBearMax       = 50.0;  // Maximum RSI for Stage 4 entry

input group "=== Entry and Risk ==="
input double InpRiskPercent      = 1.0;   // Risk per trade as percent of balance
input double InpRR               = 3.0;   // Risk-reward ratio
input int    InpATRPeriod        = 14;    // ATR period for stop placement
input double InpATRMult          = 2.0;   // ATR multiplier for stop distance

input group "=== Exit Conditions ==="
input int    InpExitMAPeriod     = 150;   // MA period for exit check
input double InpExitVolMult      = 1.5;   // Volume multiplier for MA break exit signal

input group "=== General ==="
input int    InpMagicNumber      = 999001; // Magic number
input int    InpSlippage         = 10;     // Slippage in points
input bool   InpShowLabels       = true;   // Draw stage labels on chart
input bool   InpTradeShorts      = true;   // Allow Stage 4 short trades

Перечисление "ENUM_MARKET_STAGE" присваивает каждой стадии понятное имя. Далее в коде для обозначения стадии используются не числовые значения, а значения перечисления. Благодаря этому логика остаётся читаемой: если текущая стадия равна "STAGE_2_ADVANCE," условие читается буквально.

Параметр порога наклона ("InpSlopeThreshold") требует пояснения. Наклон MA рассчитывается как изменение на один бар за период анализа, после чего нормализуется по текущей цене. Порог 0,0001 означает, что для классификации MA как растущей или снижающейся она должна изменяться не менее чем на 0,01% за бар в соответствующем направлении. Если изменение меньше порога, MA считается горизонтальной — именно это условие используется для определения Стадий 1 и 3.

Глобальные переменные и дескрипторы индикаторов

Для всей логики определения стадий достаточно трёх дескрипторов индикаторов: скользящей средней для классификации стадии, ATR для размещения стоп-лосса и RSI для подтверждения импульса при прорывах. Переменная "g_prev_stage" важна, поскольку необходимо знать не только текущую стадию, но и момент перехода между стадиями. Сделка на Стадии 2 открывается при переходе со Стадии 1 на Стадию 2, а не на каждом баре, относящемся к Стадии 2.

Вспомогательные функции

Функции "PipSize()" и "CalcLots()" — это те же независимые от торгового инструмента функции, которые были представлены в Частях 1 и 2. Здесь они не приводятся повторно — добавьте их в начало файла в точности в том виде, в каком они были даны в предыдущих статьях.

//+------------------------------------------------------------------+
//| Returns pip size for the current symbol                          |
//+------------------------------------------------------------------+
double PipSize()
  {
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); // Get symbol digits
   return (digits == 3 || digits == 5) ? _Point * 10.0 : _Point; // Return pip size
  }

//+------------------------------------------------------------------+
//| Calculates lot size from risk percent and stop distance in pips  |
//+------------------------------------------------------------------+
double CalcLots(double sl_pips)
  {
   double balance    = AccountInfoDouble(ACCOUNT_BALANCE);                  // Get balance
   double risk_money = balance * InpRiskPercent / 100.0;                    // Monetary risk
   double tick_val   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);  // Tick value
   double tick_size  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);   // Tick size
   double pip_size   = PipSize();                                           // Get pip size
   if(tick_size <= 0 || tick_val <= 0 || sl_pips <= 0)
      return 0;                                                             // Validate inputs
   double pip_value  = (pip_size / tick_size) * tick_val;                   // Pip monetary value
   double lots       = risk_money / (sl_pips * pip_value);                  // Raw lot size
   double step       = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);       // Volume step
   double min_lot    = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);        // Minimum lot
   double max_lot    = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);        // Maximum lot
   lots = MathFloor(lots / step) * step;                                    // Normalize to step
   return MathMax(min_lot, MathMin(max_lot, lots));                         // Clamp to limits
  }

Отображение на графике

//+------------------------------------------------------------------+
//| Places a text label at the specified bar and price               |
//+------------------------------------------------------------------+
void DrawLabel(string name, datetime time, double price, string text, color clr)
  {
   if(!InpShowLabels)
      return;                                                               // Check flag
   string obj = "WST_" + name;                                              // Build object name
   ObjectDelete(0, obj);                                                    // Remove existing
   ObjectCreate(0, obj, OBJ_TEXT, 0, time, price);                          // Create text object
   ObjectSetString(0, obj, OBJPROP_TEXT, text);                             // Set text content
   ObjectSetInteger(0, obj, OBJPROP_COLOR, clr);                            // Set color
   ObjectSetInteger(0, obj, OBJPROP_FONTSIZE, 9);                           // Set font size
   ChartRedraw(0);                                                          // Redraw chart
  }
//+------------------------------------------------------------------+
//| Removes all chart objects created by this EA                     |
//+------------------------------------------------------------------+
void ClearLabels()
  {
   int total = ObjectsTotal(0);                                             // Get object count
   for(int i = total - 1; i >= 0; i--)                                      // Iterate backwards
     {
      string name = ObjectName(0, i);                                       // Get object name
      if(StringFind(name, "WST_") == 0)
         ObjectDelete(0, name);                                             // Delete if EA's
     }
   ChartRedraw(0);                                                          // Redraw chart
  }
Классификация стадий

Это основа советника. Функция "ClassifyStage()" считывает значения скользящей средней, рассчитывает её наклон, проверяет положение цены относительно MA и определяет, формируются ли в недавнем ценовом движении повышающиеся максимумы или понижающиеся минимумы. Текущая стадия возвращается в виде значения "ENUM_MARKET_STAGE" .

Наклон рассчитывается следующим образом. Берётся значение MA на баре 1 и значение MA на баре "InpSlopeLookback + 1." Разность делится на длину периода анализа, что даёт наклон на один бар. Затем результат нормализуется по текущей цене, чтобы порог не зависел от торгового инструмента. Благодаря этому значение 0,0001 означает одно и то же как для EURUSD при цене 1,08, так и для золота при цене 2400.

//+------------------------------------------------------------------+
//| Classifies the current market stage from MA, price, and volume   |
//+------------------------------------------------------------------+
ENUM_MARKET_STAGE ClassifyStage()
  {
   double ma_buf[];                                                         // MA buffer
   ArraySetAsSeries(ma_buf, true);                                          // Set as series
   int bars_needed = InpSlopeLookback + InpHighLowLookback + 5;             // Bars required
   if(CopyBuffer(g_ma_handle, 0, 1, bars_needed, ma_buf) < bars_needed)
      return STAGE_UNKNOWN;                                                 // Insufficient data
   double close1    = iClose(_Symbol, PERIOD_CURRENT, 1);                   // Last close
   double ma_now    = ma_buf[0];                                            // Current MA
   double ma_past   = ma_buf[InpSlopeLookback];                             // MA at lookback
   double slope     = (ma_now - ma_past) / InpSlopeLookback / close1;       // Normalized slope
   bool   ma_rising = slope >  InpSlopeThreshold;                           // MA rising
   bool   ma_falling= slope < -InpSlopeThreshold;                           // MA falling
   bool   ma_flat   = !ma_rising && !ma_falling;                            // MA flat
   bool   price_above_ma = close1 > ma_now;                                 // Price above MA
   bool   price_below_ma = close1 < ma_now;                                 // Price below MA
//--- Check for higher highs (Stage 2 characteristic)
   double high_recent = 0, high_older = 0;                                  // High comparators
   int half = InpHighLowLookback / 2;                                       // Split midpoint
   for(int i = 1; i <= half; i++)
      high_recent = MathMax(high_recent, iHigh(_Symbol, PERIOD_CURRENT, i)); // Recent highs
   for(int i = half + 1; i <= InpHighLowLookback; i++)
      high_older = MathMax(high_older, iHigh(_Symbol, PERIOD_CURRENT, i));  // Older highs
   bool higher_highs = high_recent > high_older;                            // Higher highs present
//--- Check for lower lows (Stage 4 characteristic)
   double low_recent = DBL_MAX, low_older = DBL_MAX;                        // Low comparators
   for(int i = 1; i <= half; i++)
      low_recent = MathMin(low_recent, iLow(_Symbol, PERIOD_CURRENT, i));   // Recent lows
   for(int i = half + 1; i <= InpHighLowLookback; i++)
      low_older = MathMin(low_older, iLow(_Symbol, PERIOD_CURRENT, i));     // Older lows
   bool lower_lows = low_recent < low_older;                                // Lower lows present
//--- Stage classification logic
   if(ma_rising && price_above_ma && higher_highs)
      return STAGE_2_ADVANCE;                                               // Stage 2: advancing
   if(ma_falling && price_below_ma && lower_lows)
      return STAGE_4_DECLINE;                                               // Stage 4: declining
   if(ma_flat && price_above_ma)
      return STAGE_1_BASE;                                                  // Stage 1: basing
   if(ma_flat && price_below_ma)
      return STAGE_3_TOP;                                                   // Stage 3: topping
   if(ma_rising && price_below_ma)
      return STAGE_3_TOP;                                                   // Stage 3: early decline
   if(ma_falling && price_above_ma)
      return STAGE_1_BASE;                                                  // Stage 1: early base
   return STAGE_UNKNOWN;                                                    // Cannot classify
  }

Логика классификации выглядит так. Если MA растёт, цена находится выше неё, а недавние максимумы превышают более ранние, определяется Стадия 2. Если MA снижается, цена находится ниже неё, а недавние минимумы ниже более ранних, определяется Стадия 4. Горизонтальная MA при цене выше неё соответствует Стадии 1, а при цене ниже неё — Стадии 3. Два дополнительных случая — растущая MA при цене ниже неё и снижающаяся MA при цене выше неё — представляют переходные состояния, которые Вайнштейн отнёс бы соответственно к поздней Стадии 3 и ранней Стадии 1.

Проверка прорывов и пробоев

Одного определения перехода между стадиями недостаточно. По методу Вайнштейна прорыв должен подтверждаться объёмом. Функция "IsValidBreakout()" проверяет, соответствует ли объём последнего бара заданному порогу.

//+------------------------------------------------------------------+
//| Returns true if volume confirms a valid Stage 2 breakout         |
//+------------------------------------------------------------------+
bool IsValidBreakout()
  {
   long vol_buf[];                                                          // Volume buffer
   ArraySetAsSeries(vol_buf, true);                                         // Set as series
   if(CopyTickVolume(_Symbol, PERIOD_CURRENT, 1, InpVolAvgPeriod + 1, vol_buf) < InpVolAvgPeriod + 1)
      return false;                                                         // Insufficient data
   long  breakout_vol = vol_buf[0];                                         // Breakout bar volume
   double avg_vol     = 0;                                                  // Volume accumulator
   for(int i = 1; i <= InpVolAvgPeriod; i++)
      avg_vol += (double)vol_buf[i];                                        // Sum prior volumes
   avg_vol /= InpVolAvgPeriod;                                              // Compute average
   bool vol_ok = ((double)breakout_vol > avg_vol * InpVolBreakoutMult);     // Volume check
   Print(StringFormat("Weinstein: BreakoutVol:%I64d AvgVol:%.0f Ratio:%.2f Required:%.1fx Valid:%s",
                      breakout_vol, avg_vol, (double)breakout_vol / avg_vol,
                      InpVolBreakoutMult, vol_ok ? "YES" : "NO"));          // Log result
   return vol_ok;                                                           // Return result
  }

//+------------------------------------------------------------------+
//| Returns true if RSI confirms momentum at the breakout bar        |
//+------------------------------------------------------------------+
bool RSIConfirmsBreakout(bool is_long)
  {
   double rsi_buf[];                                                        // RSI buffer
   ArraySetAsSeries(rsi_buf, true);                                         // Set as series
   if(CopyBuffer(g_rsi_handle, 0, 1, 1, rsi_buf) < 1)
      return false;                                                         // Insufficient data
   double rsi = rsi_buf[0];                                                 // RSI value
   bool ok = is_long ? rsi >= InpRSIBullMin : rsi <= InpRSIBearMax;         // Check threshold
   Print(StringFormat("Weinstein: RSI:%.1f Required:%s%.1f Valid:%s",
                      rsi, is_long ? ">=" : "<=",
                      is_long ? InpRSIBullMin : InpRSIBearMax,
                      ok ? "YES" : "NO"));                                  // Log result
   return ok;                                                               // Return result
  }

"IsValidBreakout()" рассчитывает средний объём за предыдущие "InpVolAvgPeriod" баров и проверяет, превышает ли объём бара прорыва это среднее значение в 1,8 раза. Вайнштейн считал идеальным подтверждением объём, в два-три раза превышающий средний. Значение по умолчанию 1,8 немного ниже, поскольку тиковый объём валютных пар обычно менее выражен, чем биржевой объём акций. "RSIConfirmsBreakout()" добавляет фильтр импульса: для входа в длинную позицию RSI должен быть не ниже 50, подтверждая направление прорыва.

Исполнение сделок

Функция входа вызывается только при обнаружении перехода между стадиями. Она проверяет условия прорыва, рассчитывает объём позиции, размещает стоп-лосс ниже MA для длинных позиций и выше неё для коротких, а также устанавливает тейк-профит в соответствии с заданным соотношением риска и прибыли.

//+------------------------------------------------------------------+
//| Opens a Stage 2 long trade on confirmed breakout                 |
//+------------------------------------------------------------------+
void OpenLong()
  {
   if(!IsValidBreakout())
     {
      Print("Weinstein: Stage 2 breakout — volume insufficient. Skipping.");  // Log skip
      return;                                                                 // Volume not confirmed
     }
   if(!RSIConfirmsBreakout(true))
     {
      Print("Weinstein: Stage 2 breakout — RSI not confirming. Skipping.");   // Log skip
      return;                                                                 // Momentum not confirmed
     }
   double atr_buf[];                                                          // ATR buffer
   ArraySetAsSeries(atr_buf, true);                                           // Set as series
   if(CopyBuffer(g_atr_handle, 0, 1, 1, atr_buf) < 1)
      return;                                                                 // Copy ATR
   double atr    = atr_buf[0];                                                // ATR value
   double ask    = SymbolInfoDouble(_Symbol, SYMBOL_ASK);                     // Current ask
   double sl     = ask - atr * InpATRMult;                                    // Stop below recent ATR
   double sl_pip = (ask - sl) / PipSize();                                    // Stop in pips
   double tp     = ask + sl_pip * InpRR * PipSize();                          // Take profit at RR
   double lots   = CalcLots(sl_pip);                                          // Calculate lot size
   if(lots <= 0)
      return;                                                                 // Invalid lot size
   long   stop_lv  = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);    // Broker stop level
   double min_dist = stop_lv * _Point;                                        // Minimum distance
   if(ask - sl < min_dist)
      sl = ask - min_dist - _Point;                                           // Adjust SL if needed
   if(tp - ask < min_dist)
      tp = ask + min_dist + _Point;                                           // Adjust TP if needed
   CTrade trade;                                                              // Trade object
   trade.SetExpertMagicNumber(InpMagicNumber);                                // Set magic number
   trade.SetDeviationInPoints(InpSlippage);                                   // Set slippage
   if(trade.Buy(lots, _Symbol, ask, sl, tp, "Weinstein Stage 2 Long"))        // Open long
     {
      datetime t1 = iTime(_Symbol, PERIOD_CURRENT, 1);                       // Get bar time
      DrawLabel("S2ENTRY", t1, iLow(_Symbol, PERIOD_CURRENT, 1) - PipSize() * 5,
                "STAGE 2", clrDodgerBlue);                                   // Draw label
      Print(StringFormat("Weinstein: LONG opened | Lots:%.2f | Ask:%.5f | SL:%.5f | TP:%.5f",
                         lots, ask, sl, tp));                                // Log trade
      g_in_trade = true;                                                     // Mark as in trade
     }
  }

//+------------------------------------------------------------------+
//| Opens a Stage 4 short trade on confirmed breakdown               |
//+------------------------------------------------------------------+
void OpenShort()
  {
   if(!InpTradeShorts)
      return;                                                                 // Shorts disabled
   if(!IsValidBreakout())
     {
      Print("Weinstein: Stage 4 breakdown — volume insufficient. Skipping."); // Log skip
      return;                                                                 // Volume not confirmed
     }
   if(!RSIConfirmsBreakout(false))
     {
      Print("Weinstein: Stage 4 breakdown — RSI not confirming. Skipping.");  // Log skip
      return;                                                                 // Momentum not confirmed
     }
   double atr_buf[];                                                          // ATR buffer
   ArraySetAsSeries(atr_buf, true);                                           // Set as series
   if(CopyBuffer(g_atr_handle, 0, 1, 1, atr_buf) < 1)
      return;                                                                 // Copy ATR
   double atr    = atr_buf[0];                                                // ATR value
   double bid    = SymbolInfoDouble(_Symbol, SYMBOL_BID);                     // Current bid
   double sl     = bid + atr * InpATRMult;                                    // Stop above recent ATR
   double sl_pip = (sl - bid) / PipSize();                                    // Stop in pips
   double tp     = bid - sl_pip * InpRR * PipSize();                          // Take profit at RR
   double lots   = CalcLots(sl_pip);                                          // Calculate lot size
   if(lots <= 0)
      return;                                                                 // Invalid lot size
   long   stop_lv  = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);    // Broker stop level
   double min_dist = stop_lv * _Point;                                        // Minimum distance
   if(sl - bid < min_dist)
      sl = bid + min_dist + _Point;                                           // Adjust SL if needed
   if(bid - tp < min_dist)
      tp = bid - min_dist - _Point;                                           // Adjust TP if needed
   CTrade trade;                                                              // Trade object
   trade.SetExpertMagicNumber(InpMagicNumber);                                // Set magic number
   trade.SetDeviationInPoints(InpSlippage);                                   // Set slippage
   if(trade.Sell(lots, _Symbol, bid, sl, tp, "Weinstein Stage 4 Short"))      // Open short
     {
      datetime t1 = iTime(_Symbol, PERIOD_CURRENT, 1);                        // Get bar time
      DrawLabel("S4ENTRY", t1, iHigh(_Symbol, PERIOD_CURRENT, 1) + PipSize() * 5,
                "STAGE 4", clrCrimson);                                       // Draw label
      Print(StringFormat("Weinstein: SHORT opened | Lots:%.2f | Bid:%.5f | SL:%.5f | TP:%.5f",
                         lots, bid, sl, tp));                                 // Log trade
      g_in_trade = true;                                                      // Mark as in trade
     }
  }

Стоп-лосс размещается на расстоянии ATR × 2,0 от цены входа, а не непосредственно у MA. Скользящая средняя используется как сигнал выхода: если на Стадии 2 цена закрывается ниже MA на повышенном объёме, позиция проверяется на необходимость досрочного закрытия. Однако первоначальный стоп должен учитывать нормальную волатильность и не срабатывать при обычных откатах к MA.

Управление выходом

Критериями выхода из длинной позиции на Стадии 2 по Вайнштейну являются закрытие цены ниже растущей 30-недельной MA на повышенном объёме либо начало выравнивания самой MA, указывающее на формирование Стадии 3. Для открытых позиций эти условия проверяются на каждом новом баре.

//+------------------------------------------------------------------+
//| Checks exit conditions for open positions                        |
//+------------------------------------------------------------------+
void CheckExit()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)                           // Iterate positions
     {
      ulong ticket = PositionGetTicket(i);                                  // Get ticket
      if(!PositionSelectByTicket(ticket))
         continue;                                                          // Select position
      if(PositionGetString(POSITION_SYMBOL)  != _Symbol)
         continue;                                                          // Check symbol
      if(PositionGetInteger(POSITION_MAGIC)  != InpMagicNumber)
         continue;                                                          // Check magic
      long pos_type = PositionGetInteger(POSITION_TYPE);                    // Get position type
      double ma_buf[];                                                      // MA buffer
      ArraySetAsSeries(ma_buf, true);                                       // Set as series
      if(CopyBuffer(g_ma_handle, 0, 1, 1, ma_buf) < 1)
         continue;                                                          // Copy MA
      double ma_now  = ma_buf[0];                                           // Current MA
      double close1  = iClose(_Symbol, PERIOD_CURRENT, 1);                  // Last close
      long   vol_buf[];                                                     // Volume buffer
      ArraySetAsSeries(vol_buf, true);                                      // Set as series
      bool vol_ok = false;                                                  // Volume flag
      if(CopyTickVolume(_Symbol, PERIOD_CURRENT, 1, InpVolAvgPeriod + 1, vol_buf) >= InpVolAvgPeriod + 1)
        {
         double avg_vol = 0;                                                // Average accumulator
         for(int j = 1; j <= InpVolAvgPeriod; j++)
            avg_vol += (double)vol_buf[j];                                  // Sum volumes
         avg_vol /= InpVolAvgPeriod;                                        // Compute average
         vol_ok = ((double)vol_buf[0] > avg_vol * InpExitVolMult);          // Volume check
        }
      bool exit_long  = (pos_type == POSITION_TYPE_BUY  && close1 < ma_now && vol_ok); // Long exit signal
      bool exit_short = (pos_type == POSITION_TYPE_SELL && close1 > ma_now && vol_ok); // Short exit signal
      if(exit_long || exit_short)                                           // Exit condition met
        {
         CTrade trade;                                                      // Trade object
         trade.SetExpertMagicNumber(InpMagicNumber);                        // Set magic
         trade.SetDeviationInPoints(InpSlippage);                           // Set slippage
         if(trade.PositionClose(ticket))                                    // Close position
           {
            string dir = (pos_type == POSITION_TYPE_BUY) ? "LONG" : "SHORT"; // Direction label
            Print(StringFormat("Weinstein: %s closed — MA breach on volume. Ticket:%I64u",
                               dir, ticket));                               // Log exit
            g_in_trade = false;                                             // Update flag
           }
        }
     }
  }

Для выхода должны одновременно выполняться два условия: цена закрывается с противоположной стороны MA, а объём превышает порог выхода. Закрытие ниже MA на низком объёме во время Стадии 2 считается обычным откатом. Вайнштейн прямо предостерегал от выхода при каждом незначительном касании MA. Сигнал на закрытие возникает только тогда, когда такое движение сопровождается значимым объёмом.

Функции OnInit, OnDeinit и OnTick

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_ma_handle  = iMA(_Symbol, PERIOD_CURRENT, InpMAPeriod, 0, MODE_SMA, PRICE_CLOSE); // Create MA handle
   g_atr_handle = iATR(_Symbol, PERIOD_CURRENT, InpATRPeriod);             // Create ATR handle
   g_rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, InpRSIPeriod, PRICE_CLOSE); // Create RSI handle
   if(g_ma_handle  == INVALID_HANDLE ||                                    // Check MA handle
      g_atr_handle == INVALID_HANDLE ||                                    // Check ATR handle
      g_rsi_handle == INVALID_HANDLE)                                      // Check RSI handle
     {
      Print("WeinsteinStageEA: Indicator handle creation failed.");        // Log error
      return INIT_FAILED;                                                  // Return failure
     }
   g_trade.SetExpertMagicNumber(InpMagicNumber);                           // Set magic number
   g_trade.SetDeviationInPoints(InpSlippage);                              // Set slippage
   g_prev_stage = STAGE_UNKNOWN;                                           // Initialize prev stage
   g_in_trade   = false;                                                   // No initial position
   g_last_bar   = 0;                                                       // Initialize bar time
   Print("WeinsteinStageEA initialized | Symbol:", _Symbol,
         " | TF:", EnumToString(Period()),
         " | MA Period:", InpMAPeriod);                                    // Log initialization
   return INIT_SUCCEEDED;                                                  // Return success
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   IndicatorRelease(g_ma_handle);                                           // Release MA handle
   IndicatorRelease(g_atr_handle);                                          // Release ATR handle
   IndicatorRelease(g_rsi_handle);                                          // Release RSI handle
   ClearLabels();                                                           // Remove chart objects
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   datetime current_bar = iTime(_Symbol, PERIOD_CURRENT, 0);                // Current bar open time
   if(current_bar == g_last_bar)
      return;                                                               // Skip if same bar
   g_last_bar = current_bar;                                                // Update last bar time
//--- Check exit conditions for any open position
   CheckExit();                                                             // Check exit signals
//--- Classify current stage
   ENUM_MARKET_STAGE current_stage = ClassifyStage();                       // Get current stage
   if(current_stage == STAGE_UNKNOWN)                                       // Cannot classify
     {
      g_prev_stage = STAGE_UNKNOWN;                                         // Reset previous stage
      return;                                                               // Exit tick
     }
//--- Log stage if changed
   if(current_stage != g_prev_stage)                                        // Stage changed
     {
      string stage_names[] = {"Unknown", "Stage 1 Base", "Stage 2 Advance", "Stage 3 Top", "Stage 4 Decline"}; // Stage names
      Print(StringFormat("Weinstein: Stage transition: %s", stage_names[current_stage])); // Log transition
      datetime t1 = iTime(_Symbol, PERIOD_CURRENT, 1);                     // Get bar time
      color stage_colors[] = {clrGray, clrYellow, clrDodgerBlue, clrOrange, clrCrimson}; // Stage colors
      DrawLabel("STAGE" + IntegerToString((int)current_stage),
                t1, iHigh(_Symbol, PERIOD_CURRENT, 1) + PipSize() * 8,
                stage_names[current_stage], stage_colors[current_stage]);    // Draw label
     }
//--- Enter on stage transition only — not on every bar within a stage
   if(!g_in_trade)                                                           // Only if no open trade
     {
      if(current_stage == STAGE_2_ADVANCE && g_prev_stage == STAGE_1_BASE)   // Stage 1 -> 2 transition
         OpenLong();                                                         // Enter long
      else
         if(current_stage == STAGE_4_DECLINE && g_prev_stage == STAGE_3_TOP) // Stage 3 -> 4 transition
            OpenShort();                                                     // Enter short
     }
   g_prev_stage = current_stage;                                             // Update previous stage
  }
//+------------------------------------------------------------------+

Логика входа в "OnTick()" заслуживает особого внимания. Сделка открывается только при переходе между стадиями: со Стадии 1 на Стадию 2 для длинных позиций и со Стадии 3 на Стадию 4 для коротких. Если советник был запущен несколько баров назад, а рынок уже далеко продвинулся в Стадии 2, вход не выполняется: переход уже произошёл, и оптимальная цена упущена. Так обеспечивается дисциплина метода Вайнштейна — не следует догонять уже сформировавшийся тренд.


Тестирование

Для тестирования советника откройте Тестер стратегий MetaTrader 5 и задайте следующие параметры: символ — EURUSD; таймфрейм — дневной; моделирование — «Каждый тик на основе реальных тиков»; начальный депозит — 10 000 долларов; период тестирования — 01.01.2019–31.12.2024. Затем установите входные параметры: InpMAPeriod=150, InpSlopeLookback=10, InpSlopeThreshold=0.0001, InpHighLowLookback=20, InpVolBreakoutMult=1.8, InpVolAvgPeriod=20, InpRSIPeriod=14, InpRSIBullMin=50, InpRSIBearMax=50, InpRiskPercent=1.0, InpRR=3.0, InpATRPeriod=14 и InpATRMult=2.0.

Здесь используется более длительный период тестирования, чем в Частях 1 и 2, поскольку дневной таймфрейм генерирует меньше сигналов. Переходы между стадиями на дневных барах происходят реже, чем побаровые определения на H4. Пятилетний тест даёт статистически более значимую выборку.

Чего ожидать

Переходы между стадиями регистрируются в журнале с указанием названия стадии и времени. Подтверждения по объёму и RSI записываются при каждой проверке прорыва, поэтому можно точно определить, почему сигнал был принят или отклонён. Точки входа отмечаются на графике синим цветом для Стадии 2 и красным для Стадии 4. Метки стадий отображаются в точках перехода между стадиями.

Процент прибыльных сделок будет умеренным: Вайнштейн создавал систему, в которой прибыльные позиции удерживаются значительно дольше убыточных. Процент прибыльных сделок 35–45% при средней прибыли 3R является вполне хорошим результатом. Соотношение риска и прибыли 3,0 означает, что даже одна прибыльная сделка из трёх выводит систему в безубыток, а всё, что выше, формирует положительное математическое ожидание.

Демонстрация и результаты тестирования

Demonstration input parameters

Рис. 2. Входные параметры демонстрационного теста


Demonstration test

Рис. 3. Демонстрация


Graph

Рис. 4. График баланса и эквити


Test results

Рис. 5. Результаты тестирования


Test results by profit and months

Рис. 6. Результаты тестирования — точки входа


Известные ограничения

Для получения надёжных значений 150-периодной дневной MA требуется значительный объём исторических данных. В тестере убедитесь, что до даты начала теста доступно не менее 300 баров истории. Первые 150 баров необходимы непосредственно для расчёта MA.

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

Метод разрабатывался для акций с реальным биржевым объёмом. Для валютных пар используется тиковый объём. Порог подтверждения объёмом может потребовать настройки для конкретного брокера и символа. Перед выводами об эффективности проведите предварительные тесты и изучите сообщения об объёме в журнале.

Советник входит только при первом обнаруженном переходе между стадиями. После закрытия сделки повторный вход не выполняется, пока не произойдёт новый переход. Поэтому, если движение Стадии 2 кратковременно прерывается сигналом Стадии 3, а затем возобновляется, советник войдёт повторно только при формировании нового перехода со Стадии 1 на Стадию 2. Это намеренно консервативный подход.

Выход при пробое MA на повышенном объёме — лишь один из нескольких допустимых вариантов выхода по Вайнштейну. Он также отмечал, что во время сильной Стадии 2 закрывать позицию при обычном касании 30-недельной MA не следует: акцию необходимо удерживать во время нормальных откатов. Требование повышенного объёма при выходе призвано учитывать это различие, однако на дневных данных оно работает не идеально.


Заключение

Ключевая идея Стэна Вайнштейна обманчиво проста: большую часть времени рынки находятся на Стадии 1, Стадии 3 или в переходном состоянии. Только на Стадиях 2 и 4 цена устойчиво движется в одном направлении. Торговля на любой другой стадии означает идти против структуры рынка, а не работать в её направлении.

Советник из этой статьи механически обеспечивает соблюдение этой дисциплины. Он не открывает длинную позицию на Стадии 2, если прорыв не подтверждён объёмом и импульс не поддерживает направление движения. Кроме того, длинные позиции не открываются на Стадиях 1, 3 или 4. Выход выполняется при первых признаках ухудшения стадии — закрытии ниже MA на значимом объёме, — не дожидаясь полного формирования Стадии 3.

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

Весь код был скомпилирован и протестирован в MetaTrader 5. Полный исходный файл "WeinsteinStageEA.mq5" приложен к статье. Скопируйте его в папку "MQL5\Experts\" и скомпилируйте в MetaEditor — дополнительные зависимости не требуются. Рекомендуется использовать советник на EURUSD на дневном таймфрейме. Перед запуском на реальном счёте обязательно протестируйте советник на демо-счёте.

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

Прикрепленные файлы |
Возможности Мастера MQL5, которые вам нужно знать (Часть 81): Использование паттернов Ишимоку и ADX Уайлдера в обучении на основе β-VAE-инференса Возможности Мастера MQL5, которые вам нужно знать (Часть 81): Использование паттернов Ишимоку и ADX Уайлдера в обучении на основе β-VAE-инференса
Эта статья продолжает часть 80, в которой мы рассмотрели сочетание индикаторов Ишимоку и ADX в рамках обучения с подкреплением. Теперь перейдем к обучению на основе инференса. Как уже отмечалось, Ишимоку и ADX дополняют друг друга. Однако мы вновь обратимся к выводам предыдущей статьи об использовании пайплайнов. Для обучения на основе инференса применяем β-вариант вариационного автокодировщика (Variational Auto Encoder, VAE). Также используем пользовательский класс сигнала, предназначенный для интеграции с Мастером MQL5.
Автоматизация торговых стратегий на MQL5 (Часть 34): Система пробоя линии тренда с оценкой качества соответствия по коэффициенту R² Автоматизация торговых стратегий на MQL5 (Часть 34): Система пробоя линии тренда с оценкой качества соответствия по коэффициенту R²
В этой статье мы разработаем на MQL5 систему пробоя линии тренда, которая определяет линии поддержки и сопротивления по свинговым точкам (локальным экстремумам). Надежность линий будет оцениваться по коэффициенту детерминации R², а также по ограничениям на угол наклона. После проверки система сможет автоматически открывать сделки на пробое. Мы будем находить локальные максимумы и минимумы в пределах заданной глубины анализа, строить линии тренда с минимально допустимым количеством касаний и оценивать их надежность по коэффициенту детерминации R² и углу наклона.
От начального до среднего уровня: Очереди, списки и деревья (II) От начального до среднего уровня: Очереди, списки и деревья (II)
Это статья, которую вам, уважаемый читатель, следует внимательно изучить. Это связано с типом материала, который будет в нём изложен. Хотя мы постарались изложить материал максимально просто и познавательно, представленная здесь информация, несомненно, может показаться довольно сложной для тех, кто только начинает изучать программирование. Тем не менее, это не повод для уныния или игнорирования того, что здесь объясняется, так как эта статья установит связь между двумя совершенно разными, хотя и тесно связанными темами.
Возможности Мастера MQL5, которые вам нужно знать (Часть 95): Использование DSU и DBN в пользовательском классе сигнала Возможности Мастера MQL5, которые вам нужно знать (Часть 95): Использование DSU и DBN в пользовательском классе сигнала
В этой статье мы переключимся на пользовательский класс Мастер MQL5 (MQL5 Wizard), который анализирует сигналы входа. На этот раз наш пользовательский класс называется ‘CSignalDSUDBN’ и построен на сочетании алгоритма Disjoint Set Union (DSU) и сети Deep Belief Network (DBN). Как и в предыдущих статьях этой серии, нашу модель можно протестировать с использованием советников, собранных с помощью Мастера MQL5, которые можно настраивать с помощью различных трейлинг-стопов и классов управления капиталом.