English
preview
Создание индикатора для моделирования будущих свечей после пересечения EMA в MQL5

Создание индикатора для моделирования будущих свечей после пересечения EMA в MQL5

MetaTrader 5 — Примеры |
38 2
Hlomohang John Borotho
Hlomohang John Borotho

Содержание

  1. Введение
  2. Обзор моделирования
  3. Начало работы
  4. Демонстрация моделирования
  5. Заключение


Введение

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

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


Обзор моделирования

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

Механизм моделирования фиксирует наклон EMA на баре пересечения и экстраполирует его вперед с коэффициентом затухания 0,91, поэтому каждый будущий бар смещается меньше, чем предыдущий.

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


Начало работы

//+------------------------------------------------------------------+
//|                                            ForwardSimEngine.mq5  |
//|                     Forward Simulation Engine  –  EMA Crossover  |
//|                   Visualized as Predicted Future Candles  (M15)  |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ru/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ru/users/johnhlomohang/"
#property version     "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2

#property indicator_label1  "Fast EMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

#property indicator_label2  "Slow EMA"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrOrangeRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

//+------------------------------------------------------------------+
//| INDICATOR BUFFERS                                                |
//+------------------------------------------------------------------+
double FastEMABuffer[];
double SlowEMABuffer[];

//+------------------------------------------------------------------+
//| INPUT PARAMETERS                                                 |
//+------------------------------------------------------------------+
input int    FastEMA_Period     = 9;            // Fast EMA period
input int    SlowEMA_Period     = 21;           // Slow EMA period
input int    FutureBars         = 10;           // Number of future candles to project
input double SpreadMultiplier   = 2.0;          // Wick size multiplier
input bool   AutoAnchor         = true;         // Auto-move anchor to latest cross bar
input string AnchorLineName     = "FSE_Anchor"; // Anchor vertical line name
input color  BullishColor       = clrDodgerBlue;    // Bullish body color
input color  BearishColor       = clrCrimson;       // Bearish body color
input color  WickColor          = clrDimGray;       // Wick color
input bool   ShowZoneLabel      = true;         // Show projection label
input bool   ShowSeparatorLine  = true;         // Show dashed separator at anchor
input int    InvalidationPips   = 10;           // Pip distance to trigger invalidation

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

Далее мы определяем набор входных параметров, которые управляют как логикой EMA, так и работой механизма моделирования будущих свечей. Мы задаем периоды быстрой и медленной EMA, а также количество будущих свечей для построения проекции. Кроме того, мы добавляем настройки влияния спреда, работы с точкой привязки и визуального оформления, включая цвета бычьих и медвежьих свечей, внешний вид теней, а также необязательные метки или разделительные линии. Наконец, мы добавляем настройку отмены сигнала, определяющую, когда построенный сценарий перестает считаться действительным. Это позволяет точнее управлять адаптацией моделирования к меняющимся рыночным условиям.

//+------------------------------------------------------------------+
//| STRUCTS                                                          |
//+------------------------------------------------------------------+
struct PredictedCandle
  {
   double            open;
   double            high;
   double            low;
   double            close;
   bool              bullish;
  };

//+------------------------------------------------------------------+
//| GLOBAL VARS                                                      |
//+------------------------------------------------------------------+
int      g_FastHandle    = INVALID_HANDLE;
int      g_SlowHandle    = INVALID_HANDLE;

int      g_LastSignal    = 0;     // +1 bull, -1 bear, 0 none
bool     g_SignalActive  = false;
double   g_SignalPrice   = 0.0;
datetime g_SignalBarTime = 0;
datetime g_DrawnAnchor   = 0;     // anchor time of the last successful draw

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Bind and configure the two visible EMA plot buffers
   SetIndexBuffer(0, FastEMABuffer, INDICATOR_DATA);
   SetIndexBuffer(1, SlowEMABuffer, INDICATOR_DATA);

   ArraySetAsSeries(FastEMABuffer, false);
   ArraySetAsSeries(SlowEMABuffer, false);

   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, FastEMA_Period);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, SlowEMA_Period);

//--- Internal calculation handles
   g_FastHandle = iMA(_Symbol, _Period, FastEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
   g_SlowHandle = iMA(_Symbol, _Period, SlowEMA_Period, 0, MODE_EMA, PRICE_CLOSE);

   if(g_FastHandle == INVALID_HANDLE || g_SlowHandle == INVALID_HANDLE)
     {
      Print("ForwardSimEngine [ERROR]: iMA handle creation failed. "
            "Symbol=", _Symbol, " TF=", EnumToString(_Period));
      return INIT_FAILED;
     }

   Print("ForwardSimEngine [INIT]: OK  FastEMA=", FastEMA_Period,
         "  SlowEMA=", SlowEMA_Period,
         "  FutureBars=", FutureBars,
         "  InvalidationPips=", InvalidationPips);

   EventSetTimer(3);
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
   CleanAllObjects();
   Print("ForwardSimEngine [DEINIT]: objects cleaned, reason=", reason);
  }

В этом фрагменте кода мы определяем структуру PredictedCandle. Она хранит значения OHLC и флаг бычьего направления для каждой моделируемой свечи. Это позволяет работать с будущими свечами как с целостными наборами данных, а не с разрозненными значениями. Наряду с этим мы объявляем несколько глобальных переменных для управления хэндлами EMA и отслеживания состояния системы. В них хранятся последний обнаруженный сигнал, признак его активности, цена и время сигнала, а также точка привязки для построения проекций. Такая организация данных гарантирует, что перед генерацией будущих свечей механизм моделирования всегда располагает сведениями о текущем состоянии системы.

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

//+-----------------------------------------------------------------------+
//| ON CALCULATE                                                          |
//+-----------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double   &open[],
                const double   &high[],
                const double   &low[],
                const double   &close[],
                const long     &tick_volume[],
                const long     &volume[],
                const int      &spread[])
  {
   if(rates_total < SlowEMA_Period + 5)
     {
      Print("ForwardSimEngine [WARN]: Not enough bars: ", rates_total);
      return 0;
     }

//--- How many bars to (re)calculate
   int limit = (prev_calculated > 1) ? rates_total - prev_calculated + 1
               : rates_total;
//--- Start index in the buffer (oldest bar that needs updating)
   int startBar = rates_total - limit;

//--- Local arrays for engine logic – we own these, safe to size freely
   double tmpFast[], tmpSlow[];
   ArraySetAsSeries(tmpFast, false);
   ArraySetAsSeries(tmpSlow, false);
   ArrayResize(tmpFast, rates_total);
   ArrayResize(tmpSlow, rates_total);

//--- Copy ALL bars into local arrays (needed by RunEngine for crossover)
   int copiedFast = CopyBuffer(g_FastHandle, 0, 0, rates_total, tmpFast);
   int copiedSlow = CopyBuffer(g_SlowHandle, 0, 0, rates_total, tmpSlow);

   if(copiedFast <= 0 || copiedSlow <= 0)
     {
      Print("ForwardSimEngine [WARN]: CopyBuffer returned <=0. fast=",
            copiedFast, " slow=", copiedSlow);
      return prev_calculated;
     }

//--- Write into plot buffers ONLY within the confirmed copied range
//--- and only within what MT5 has allocated (ArraySize guard).
   int bufSzFast = ArraySize(FastEMABuffer);
   int bufSzSlow = ArraySize(SlowEMABuffer);
   int safeFast  = MathMin(copiedFast, bufSzFast);
   int safeSlow  = MathMin(copiedSlow, bufSzSlow);

   for(int i = startBar; i < safeFast; i++)
      FastEMABuffer[i] = tmpFast[i];
   for(int i = startBar; i < safeSlow; i++)
      SlowEMABuffer[i] = tmpSlow[i];

//--- Run the simulation engine
   RunEngine(rates_total, time, close, tmpFast, tmpSlow);

   return rates_total;
  }

//+------------------------------------------------------------------+
//|  ON TIMER                                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- If user moved the anchor manually, force a redraw on next tick
   if(!AutoAnchor)
     {
      datetime curAnchor = GetAnchorTime();
      if(curAnchor != g_DrawnAnchor && curAnchor != 0)
        {
         g_DrawnAnchor = 0;
         ChartRedraw();
        }
     }
  }

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

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

//+------------------------------------------------------------------+
//| CORE ENGINE                                                      |
//| All arrays: index 0 = oldest,  rates_total-1 = live forming bar  |
//|            rates_total-2 = last fully closed bar  (bar index 1)  |
//|            rates_total-3 = bar before that         (bar index 2) |
//+------------------------------------------------------------------+
void RunEngine(const int      rates_total,
               const datetime &time[],
               const double   &close[],
               const double   &fastBuf[],
               const double   &slowBuf[])
  {
   int barLive    = rates_total - 1; // live/forming bar
   int barClosed  = rates_total - 2; // last fully closed bar
   int barPrev    = rates_total - 3; // bar before that

   if(barPrev < SlowEMA_Period)
      return;

//--- 1. Crossover detection
//--- We look at the two most recently CLOSED bars (barPrev and barClosed).
//--- Cross occurs when the fast EMA crossed over the slow EMA between them.
   double fCur  = fastBuf[barClosed];
   double fPrev = fastBuf[barPrev];
   double sCur  = slowBuf[barClosed];
   double sPrev = slowBuf[barPrev];

   int newSignal = 0;
   if(fPrev <= sPrev && fCur > sCur)
      newSignal =  1; // bullish
   if(fPrev >= sPrev && fCur < sCur)
      newSignal = -1; // bearish

//--- Heartbeat log (first 3 ticks + every new signal)
   static int s_ticks = 0;
   s_ticks++;
   if(s_ticks <= 3 || newSignal != 0)
     {
      Print("ForwardSimEngine [TICK #", s_ticks, "]",
            "  fCur=",   DoubleToString(fCur,  _Digits),
            "  sCur=",   DoubleToString(sCur,  _Digits),
            "  gap=",    DoubleToString(fCur - sCur, _Digits),
            "  newSig=", newSignal,
            "  active=", g_SignalActive,
            "  lastSig=",g_LastSignal);
     }

//--- 2. Latch new signal
   if(newSignal != 0)
     {
      g_LastSignal    = newSignal;
      g_SignalActive  = true;
      g_SignalPrice   = close[barClosed];
      g_SignalBarTime = time[barClosed];
      g_DrawnAnchor   = 0; // force full redraw

      Print("ForwardSimEngine [SIGNAL]: ",
            (newSignal == 1 ? ">>> BULLISH CROSS <<<" : ">>> BEARISH CROSS <<<"),
            "  bar=", TimeToString(g_SignalBarTime),
            "  px=",  DoubleToString(g_SignalPrice, _Digits));
     }

//--- 3. Invalidation check
   if(g_SignalActive)
     {
      double livePx = close[barLive];
      //--- 1 pip = 10 * _Point for a 5-digit broker (covers 3-digit too)
      double pipSize = _Point * 10.0;
      double thresh  = InvalidationPips * pipSize;

      bool inv = false;
      if(g_LastSignal ==  1 && livePx < g_SignalPrice - thresh)
         inv = true;
      if(g_LastSignal == -1 && livePx > g_SignalPrice + thresh)
         inv = true;

      if(inv)
        {
         Print("ForwardSimEngine [INVALIDATED]",
               "  signalPx=", DoubleToString(g_SignalPrice, _Digits),
               "  livePx=",   DoubleToString(livePx, _Digits),
               "  thresh=",   DoubleToString(thresh,  _Digits));

         g_SignalActive = false;
         g_LastSignal   = 0;
         g_DrawnAnchor  = 0;
         CleanAllObjects();
         DrawInvalidationLabel(g_SignalBarTime, g_SignalPrice);
         ChartRedraw();
         return;
        }
     }

//--- 4. Only draw/redraw when there is an active signal
   if(!g_SignalActive || g_LastSignal == 0)
      return;

   datetime anchorTime = (AutoAnchor) ? g_SignalBarTime : GetAnchorTime();
   if(anchorTime == 0)
      anchorTime = g_SignalBarTime;

//--- Skip if already drawn at this anchor
   if(anchorTime == g_DrawnAnchor)
      return;
   g_DrawnAnchor = anchorTime;

//--- 5. Build synthetic candles
   double emaSlope = fCur - fPrev;
   PredictedCandle pred[];
   ArrayResize(pred, FutureBars);
   GeneratePrediction(close[barClosed], emaSlope, pred);

//--- 6. Render
   CleanAllObjects();
   DrawAllCandles(anchorTime, pred);
   if(ShowSeparatorLine)
      DrawSeparator(anchorTime);
   if(ShowZoneLabel)
      DrawZoneLabel(anchorTime, close[barClosed]);
   UpdateAnchorLine(anchorTime);
   ChartRedraw();

   Print("ForwardSimEngine [DRAWN]",
         "  signal=",   g_LastSignal,
         "  anchor=",   TimeToString(anchorTime),
         "  bars=",     FutureBars,
         "  slope=",    DoubleToString(emaSlope, _Digits));
  }

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

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

//+------------------------------------------------------------------+
//|  PREDICTION ENGINE                                               |
//+------------------------------------------------------------------+
void GeneratePrediction(double startPrice, double emaSlope, PredictedCandle &out[])
  {
//--- Wick size in price: 1 pip × multiplier × 3
   double pipVal = _Point * 10.0;
   double wickSz = pipVal * SpreadMultiplier * 3.0;
   if(wickSz < _Point * 5)
      wickSz = _Point * 5;

//--- Step direction: preserve sign from signal, floor at 3 points minimum
   double direction = (g_LastSignal == 1) ? MathAbs(emaSlope) : -MathAbs(emaSlope);
   if(MathAbs(direction) < _Point * 3)
      direction = (g_LastSignal == 1 ? 1.0 : -1.0) * _Point * 3;

   double decayBase = 0.91; // each bar's move is 91% of the previous
   double prevClose = startPrice;

   MathSrand((int)(TimeLocal() % 32767));

   for(int i = 0; i < FutureBars; i++)
     {
      double decay = MathPow(decayBase, i);
      double step  = direction * decay;

      //--- ±15% noise relative to wickSz
      double noise = ((double)(MathRand() % 200) - 100.0) / 100.0 * wickSz * 0.15;
      step += noise;

      out[i].open  = prevClose;
      out[i].close = prevClose + step;
      out[i].bullish = (out[i].close >= out[i].open);

      double bodyTop = MathMax(out[i].open, out[i].close);
      double bodyBot = MathMin(out[i].open, out[i].close);

      //--- Wick: 30%–120% of wickSz
      double upWk = wickSz * (0.3 + (double)(MathRand() % 90) * 0.01);
      double dnWk = wickSz * (0.3 + (double)(MathRand() % 90) * 0.01);

      out[i].high = bodyTop + upWk;
      out[i].low  = bodyBot - dnWk;

      prevClose = out[i].close;
     }
  }

//+------------------------------------------------------------------+
//| DRAW ALL CANDLES – project one bar-width ahead of anchor         |
//+------------------------------------------------------------------+
void DrawAllCandles(datetime startTime, PredictedCandle &candles[])
  {
   int barSec = PeriodSeconds();
   int total  = ArraySize(candles);

   for(int i = 0; i < total; i++)
     {
      //--- +1 so first projected candle starts ONE bar after the anchor
      datetime t1 = startTime + (datetime)((i + 1) * barSec);
      DrawSingleCandle(i, t1, candles[i]);
     }
  }

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

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

//+------------------------------------------------------------------+
//|  DRAW ONE CANDLE                                                 |
//+------------------------------------------------------------------+
void DrawSingleCandle(int idx, datetime t1, const PredictedCandle &c)
  {
   int      barSec  = PeriodSeconds();
   string   pfx     = "FutureCandle_" + IntegerToString(idx);
   datetime t2      = t1 + (datetime)barSec;
   datetime tMid    = t1 + (datetime)(barSec / 2);

   color bodyCol = c.bullish ? BullishColor : BearishColor;

//--- Doji guard: ensure body has at least 1 point height
   double bOpen  = c.open;
   double bClose = c.close;
   if(MathAbs(bOpen - bClose) < _Point)
      bClose = bOpen + (c.bullish ? _Point : -_Point);

//--- Filled body
   string nm = pfx + "_body";
   ObjectDelete(0, nm);
   if(ObjectCreate(0, nm, OBJ_RECTANGLE, 0, t1, bOpen, t2, bClose))
     {
      ObjectSetInteger(0, nm, OBJPROP_COLOR,      bodyCol);
      ObjectSetInteger(0, nm, OBJPROP_FILL,       true);
      ObjectSetInteger(0, nm, OBJPROP_BACK,       false);
      ObjectSetInteger(0, nm, OBJPROP_WIDTH,      1);
      ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nm, OBJPROP_HIDDEN,     false);
     }

//--- Body outline (unfilled rectangle)
   nm = pfx + "_bord";
   ObjectDelete(0, nm);
   if(ObjectCreate(0, nm, OBJ_RECTANGLE, 0, t1, bOpen, t2, bClose))
     {
      ObjectSetInteger(0, nm, OBJPROP_COLOR,      bodyCol);
      ObjectSetInteger(0, nm, OBJPROP_FILL,       false);
      ObjectSetInteger(0, nm, OBJPROP_BACK,       false);
      ObjectSetInteger(0, nm, OBJPROP_WIDTH,      1);
      ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nm, OBJPROP_HIDDEN,     false);
     }

//--- Upper wick
   nm = pfx + "_wU";
   ObjectDelete(0, nm);
   double wTop = MathMax(bOpen, bClose);
   if(ObjectCreate(0, nm, OBJ_TREND, 0, tMid, wTop, tMid, c.high))
     {
      ObjectSetInteger(0, nm, OBJPROP_COLOR,      WickColor);
      ObjectSetInteger(0, nm, OBJPROP_WIDTH,      1);
      ObjectSetInteger(0, nm, OBJPROP_RAY_RIGHT,  false);
      ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nm, OBJPROP_HIDDEN,     false);
     }

//--- Lower wick
   nm = pfx + "_wD";
   ObjectDelete(0, nm);
   double wBot = MathMin(bOpen, bClose);
   if(ObjectCreate(0, nm, OBJ_TREND, 0, tMid, wBot, tMid, c.low))
     {
      ObjectSetInteger(0, nm, OBJPROP_COLOR,      WickColor);
      ObjectSetInteger(0, nm, OBJPROP_WIDTH,      1);
      ObjectSetInteger(0, nm, OBJPROP_RAY_RIGHT,  false);
      ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nm, OBJPROP_HIDDEN,     false);
     }
  }

//+------------------------------------------------------------------+
//| SEPARATOR                                                        |
//+------------------------------------------------------------------+
void DrawSeparator(datetime t)
  {
   string nm = "FSE_Sep";
   ObjectDelete(0, nm);
   if(ObjectCreate(0, nm, OBJ_VLINE, 0, t, 0))
     {
      ObjectSetInteger(0, nm, OBJPROP_COLOR,      clrSilver);
      ObjectSetInteger(0, nm, OBJPROP_STYLE,      STYLE_DASH);
      ObjectSetInteger(0, nm, OBJPROP_WIDTH,      1);
      ObjectSetInteger(0, nm, OBJPROP_BACK,       true);
      ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nm, OBJPROP_HIDDEN,     false);
     }
  }

//+------------------------------------------------------------------+
//| ZONE LABEL                                                       |
//+------------------------------------------------------------------+
void DrawZoneLabel(datetime t, double price)
  {
   string nm  = "FSE_Label";
   string txt = (g_LastSignal == 1) ? "[ BULLISH PROJECTION ]" : "[ BEARISH PROJECTION ]";
   color  col = (g_LastSignal == 1) ? BullishColor : BearishColor;

   ObjectDelete(0, nm);
   if(ObjectCreate(0, nm, OBJ_TEXT, 0, t + (datetime)PeriodSeconds(), price))
     {
      ObjectSetString(0,  nm, OBJPROP_TEXT,       txt);
      ObjectSetInteger(0, nm, OBJPROP_COLOR,      col);
      ObjectSetInteger(0, nm, OBJPROP_FONTSIZE,   10);
      ObjectSetString(0,  nm, OBJPROP_FONT,       "Courier New");
      ObjectSetInteger(0, nm, OBJPROP_ANCHOR,     ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nm, OBJPROP_HIDDEN,     false);
     }
  }

Этот фрагмент кода отвечает за точную и наглядную отрисовку каждой прогнозируемой свечи с помощью графических объектов. Для построения каждой свечи мы определяем ее временной интервал и его середину, затем задаем цвет тела в зависимости от того, является свеча бычьей или медвежьей. Небольшая дополнительная проверка обеспечивает видимость тела даже у свечей доджи, предотвращая проблемы с отрисовкой. Тело свечи отрисовывается с помощью двух прямоугольников: один с заливкой представляет основное тело, а другой, без заливки, создает четкий контур. Перед отрисовкой существующий объект с таким же именем удаляется, чтобы избежать дублирования и не загромождать график.

Затем мы завершаем построение свечи, отрисовывая верхнюю и нижнюю тени вертикальными трендовыми линиями в середине ее временного интервала. Эти тени проходят от тела до максимума и минимума свечи, придавая ей реалистичную структуру. Помимо отрисовки отдельных свечей, мы улучшаем визуализацию, добавляя разделительную линию в точке привязки, чтобы четко отделить реальную динамику цены от данных проекции. Рядом с зоной проекции размещается метка, указывающая, является ли сценарий бычьим или медвежьим; для единообразия используются соответствующие цвета.

//+------------------------------------------------------------------+
//|  INVALIDATION LABEL                                              |
//+------------------------------------------------------------------+
void DrawInvalidationLabel(datetime t, double price)
  {
   string nm = "FSE_Invalid";
   ObjectDelete(0, nm);
   if(ObjectCreate(0, nm, OBJ_TEXT, 0, t + (datetime)PeriodSeconds(), price))
     {
      ObjectSetString(0,  nm, OBJPROP_TEXT,       "[ INVALIDATED - AWAITING NEXT CROSS ]");
      ObjectSetInteger(0, nm, OBJPROP_COLOR,      clrOrange);
      ObjectSetInteger(0, nm, OBJPROP_FONTSIZE,   9);
      ObjectSetString(0,  nm, OBJPROP_FONT,       "Courier New");
      ObjectSetInteger(0, nm, OBJPROP_ANCHOR,     ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, nm, OBJPROP_HIDDEN,     false);
     }
  }

//+------------------------------------------------------------------+
//|  ANCHOR LINE HELPERS                                             |
//+------------------------------------------------------------------+
void PlaceAnchorLine(datetime t)
  {
   ObjectDelete(0, AnchorLineName);
   if(ObjectCreate(0, AnchorLineName, OBJ_VLINE, 0, t, 0))
     {
      ObjectSetInteger(0, AnchorLineName, OBJPROP_COLOR,      clrGold);
      ObjectSetInteger(0, AnchorLineName, OBJPROP_STYLE,      STYLE_DASHDOTDOT);
      ObjectSetInteger(0, AnchorLineName, OBJPROP_WIDTH,      2);
      ObjectSetInteger(0, AnchorLineName, OBJPROP_SELECTABLE, true);
      ObjectSetInteger(0, AnchorLineName, OBJPROP_HIDDEN,     false);
     }
  }

//+------------------------------------------------------------------+
//|  Update Anchor Line                                              |
//+------------------------------------------------------------------+
void UpdateAnchorLine(datetime t)
  {
   if(ObjectFind(0, AnchorLineName) < 0)
      PlaceAnchorLine(t);
   else
      ObjectSetInteger(0, AnchorLineName, OBJPROP_TIME, t);
  }

//+------------------------------------------------------------------+
//|  Get Anchor Time                                                 |
//+------------------------------------------------------------------+
datetime GetAnchorTime()
  {
   if(ObjectFind(0, AnchorLineName) >= 0)
      return (datetime)ObjectGetInteger(0, AnchorLineName, OBJPROP_TIME);
   return 0;
  }

//+------------------------------------------------------------------+
//|  CLEANUP                                                         |
//+------------------------------------------------------------------+
void CleanAllObjects()
  {
   ObjectsDeleteAll(0, "FutureCandle_");
   ObjectsDeleteAll(0, "FSE_");
  }
//+------------------------------------------------------------------+

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

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


Демонстрация моделирования

Моделирование бычьего сценария


Моделирование медвежьего сценария


Заключение

Мы реализовали практичный механизм моделирования будущих свечей, который выходит за рамки стандартного индикатора EMA и предоставляет структурированную систему визуализации сценариев. Система обнаруживает пересечения EMA на закрытых барах, фиксирует точку привязки, измеряет наклон EMA в момент сигнала и генерирует последовательность синтетических OHLC-свечей с использованием затухания направленного движения и контролируемого шума. Кроме того, система предусматривает отмену сигнала по порогу в пипсах, поддерживает автоматическую и ручную привязку и избегает ненужных перерисовок для повышения эффективности работы с графиком. Для завершения визуализации механизм моделирования отрисовывает тела и тени свечей проекции, разделительные линии и метки проекции, не загромождая график.

В результате у вас есть рабочая основа для MetaTrader 5: детектор пересечений, воспроизводимый генератор, формирующий заданное параметром FutureBars количество будущих свечей, правило отмены сигнала, механизм привязки и надежная последовательность отрисовки и очистки. Это создает прочную основу для расширений без переработки ядра обработки сигналов или визуализации — например, для замены простой модели на основе наклона и шума шагами с учетом волатильности, фильтрами сценариев на основе структуры или наложением на график оценок, рассчитанных моделями машинного обучения. Иными словами, в статье представлен воспроизводимый и расширяемый инструмент, который превращает сигналы EMA в понятные сценарии дальнейшего движения, а не просто в оповещения, оставляющие открытым вопрос о том, что будет дальше.

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Bidhaan Yuusuf
Bidhaan Yuusuf | 14 мая 2026 в 06:34
MetaQuotes:

Ознакомьтесь с новой статьёй: «Создание индикатора EMA Crossover Forward Simulation в MQL5».

Автор: Хломоханг Джон Борото

Здравствуйте, как можно скачать этот индикатор?
Hlomohang John Borotho
Hlomohang John Borotho | 14 мая 2026 в 10:37

Bidhaan Yuusuf #:
Hi how can dolowod this indicator 


Здесь:
RiskGate: Централизованное управление рисками для нескольких советников RiskGate: Централизованное управление рисками для нескольких советников
Во многих конфигурациях MetaTrader 5 на одном счете работают несколько советников, поэтому управление рисками оказывается разрозненным, а экспозиция по коррелирующим инструментам остается незамеченной. В статье представлен RiskGate — централизованный сервис, который оценивает торговые намерения советников на уровне всего счета: советники отправляют сигнал в формате JSON, а сервис возвращает поля approved, lot и reason. Вы увидите схему взаимодействия клиента и сервера, примеры правил (лимит дневного убытка, лимиты экспозиции и корреляции), архитектуру обработчика, проверенную модульными тестами, и пример советника. В результате обеспечивается согласованное управление рисками на уровне портфеля, а сами советники становятся проще.
Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования (вероятностное представление) Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования (вероятностное представление)
Рассматриваем реализацию вероятностных компонентов Habi на MQL5 и OpenCL: обработку рыночных наблюдений, преобразование параметров нормального распределения и пакетное семплирование латентных векторов. На примере CNeuronHabiObservationOCL, CNeuronHabiGaussianOCL и CNeuronHabiVAEOCL разбираем архитектуру, инициализацию, прямой и обратный проходы. Показываем согласование Prior и Posterior через условную KL-дивергенцию и накопление градиентов от нескольких выборок. Полученные компоненты создают основу для последующей генерации торговых действий.
Разработка классического индикатора «Крестики-нолики» на языке MQL5 Разработка классического индикатора «Крестики-нолики» на языке MQL5
В данной статье на языке MQL5 реализован пользовательский индикатор «Крестики-нолики» (Point and Figure, PF), который отображает движение цены в столбцы X/O, используя фиксированный размер клетки и логику разворота через три клетки. Мы определяем базовую цену, преобразуем цены в интервалы клеток, управляем трендами и разворотами, автоматически масштабируем окно индикатора и отображаем символы с помощью объектов, обеспечивая четкое, независимое от времени представление трендов, прорывов и уровней поддержки и сопротивления.
Разработка объектно-ориентированного сканера разрывов справедливой стоимости (FVG) на MQL5 Разработка объектно-ориентированного сканера разрывов справедливой стоимости (FVG) на MQL5
В статье показано, как создать объектно-ориентированный сканер разрывов справедливой стоимости (FVG) на языке MQL5 и отображать разрывы ликвидности непосредственно на графике MetaTrader 5. Рассматриваются формализация геометрии дисбаланса на основе трех свечей, синхронизация массивов OHLC с помощью CopyRates, управление объектами-прямоугольниками без утечек и отслеживание митигации (отработки зоны) в реальном времени. Также показано, как интегрировать этот класс в советник со строгой проверкой появления нового бара для стабильной и эффективной работы.