Автоматизация классических рыночных методов в MQL5 (Часть 3): Стадийный анализ Стэна Вайнштейна
Введение
В Части 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. Мы рассмотрим следующие темы:
- Понимание четырёх стадий
- Адаптация метода Вайнштейна для рынка Форекс
- Реализация в MQL5
- Тестирование
- Известные ограничения
- Заключение
Понимание четырёх стадий
Согласно подходу Вайнштейна, любой рынок проходит через цикл стадий. Продолжительность цикла не является строго регулярной: Стадия 2 может длиться месяцы или годы, а Стадия 4 — быть как краткой, так и продолжительной. Однако порядок стадий остаётся неизменным. Понимание признаков каждой стадии служит основой для программной реализации логики их определения.

Рис. 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 означает, что даже одна прибыльная сделка из трёх выводит систему в безубыток, а всё, что выше, формирует положительное математическое ожидание.
Демонстрация и результаты тестирования

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

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

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

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

Рис. 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Возможности Мастера MQL5, которые вам нужно знать (Часть 81): Использование паттернов Ишимоку и ADX Уайлдера в обучении на основе β-VAE-инференса
Автоматизация торговых стратегий на MQL5 (Часть 34): Система пробоя линии тренда с оценкой качества соответствия по коэффициенту R²
От начального до среднего уровня: Очереди, списки и деревья (II)
Возможности Мастера MQL5, которые вам нужно знать (Часть 95): Использование DSU и DBN в пользовательском классе сигнала
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования