English
preview
От новичка до эксперта: Торговля с использованием уровней Фибоначчи после публикации NFP

От новичка до эксперта: Торговля с использованием уровней Фибоначчи после публикации NFP

MetaTrader 5Примеры |
448 0
Clemence Benjamin
Clemence Benjamin

Разделы


Введение

Пропущенный момент важных экономических новостей, таких как NFP (отчет о занятости в несельскохозяйственном секторе США), часто вызывает FOMO (страх упустить что-то важное). Обычно это происходит потому, что трейдеры спешат уловить движение, даже когда оно уже закончилось. В большинстве случаев первоначальный всплеск длится менее минуты, сдвигая рынок на много пипсов в благоприятном направлении. Поздний вход в таких ситуациях крайне рискован, так как всплеск уже завершен, и цена может быстро развернуться, что приведет к значительным потерям.

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

В настоящем обсуждении мы рассмотрим проблемы торговли после вызванных новостями скачков, применяя принципы Фибоначчи алгоритмическим способом. Чтобы заложить основу, прежде чем приступить к разработке и реализации стратегии, в следующем разделе мы кратко представим концепцию Фибоначчи для тех, кто не знаком с ней.

Краткий обзор коррекции Фибоначчи

Впервые разработанная итальянским математиком Леонардо Боначчи (широко известным как Фибоначчи) много веков назад, последовательность Фибоначчи стала одним из самых влиятельных инструментов в современном техническом анализе. В математике последовательность проста: каждое число является суммой двух предыдущих чисел (1, 1, 2, 3, 5, 8, 13, ...). Тем не менее, её проявление в природе, архитектуре и даже поведении человека восхищало ученых на протяжении многих поколений.

На финансовых рынках последовательность Фибоначчи применяется с помощью уровней коррекции Фибоначчи (Fibonacci retracement) — метода, который трейдеры используют для определения потенциальных зон разворота или продолжения во время коррекции рынка. Наиболее часто используемыми коэффициентами коррекции являются 23.6%, 38.2%, 50%, 61.8%, и 78,6%, каждый из которых основан на соотношениях в последовательности Фибоначчи. Эти уровни действуют как психологические маркеры, по которым трейдеры предвидят реакцию цены: она либо приостанавливается, либо подпрыгивает, либо разворачивается вспять.

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

  • коррекция на 38,2% часто указывает на небольшой откат при сильном тренде.
  • коррекция на 50% (хотя и не является истинным числом Фибоначчи) широко используется в качестве коррекции по методу средней точки.
  • коррекция на 61,8%, известная как “золотое сечение”, считается наиболее критичным уровнем, на котором часто происходят серьезные развороты.

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

В этой стратегии мы концентрируемся на торговле после выхода NFP, где Fibonacci retracement помогает отфильтровать шум от первоначального всплеска и направляет трейдеров к структурированным точкам входа с более высокой вероятностью. Вместо того чтобы поддаваться FOMO, трейдер учится “ждать отката” и входить на более устойчивых уровнях. На платформе MQL5 Community имеется обширная информация о Фибоначчи, доступная для дальнейшего ознакомления.

Доступность в терминале MetaTrader 5

В интерфейсе MetaTrader 5 по умолчанию панель инструментов для построения графиков настроена по приоритетам и доступна сразу — наиболее часто используемые аналитические объекты предварительно загружены и могут быть перенесены непосредственно на график. Инструменты рисования (такие как объект Fibonacci retracement) имеют перетаскиваемые вершины, которые можно разместить на точках колебания для визуального отображения ценовых уровней. Трейдеры используют эти объекты как для анализа того, как цена реагировала на уровни в прошлом, так и для прогнозирования вероятных зон отката при будущих коррекциях.

Практические советы:

  • Перетащите инструмент на четкий максимум колебаний и минимум колебаний (или наоборот), чтобы привязать коррекцию к значимым опорным точкам.
  • Используйте хэндлы вершин объекта для точной настройки расположения, чтобы уровни совпадали с закрытиями или тенями свечей, в зависимости от вашего предпочтительного правила.
  • Сохраняйте часто используемые шаблоны или профили, чтобы ваши предпочтительные настройки Фибоначчи и компоновка графиков были мгновенно доступны на всех графиках.

Смотрите рисунок 1 ниже, на котором показана нанесенная на график коррекция Фибоначчи.

Drawing the Fibonacci Retracement tool on MetaTrader 5 Terminal

Рисунок 1. Рисование инструмента Fibonacci Retracement в терминале MetaTrader 5

Концептуальное исследование с использованием объявления данных о занятости в несельскохозяйственном секторе от 5 сентября 2025 года:

Ручной анализ ценового движения после объявления NFP 5 сентября 2025 года подтверждает идею о том, что уровни коррекции Фибоначчи могут существенно влиять на входы после события. На прилагаемых иллюстрациях используется инструмент Fibonacci Retracement, чтобы показать, как цена тестировала и соблюдала определенные уровни после первоначального всплеска, что помогает отличить зоны повторного входа с высокой вероятностью от шума.

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

Наше исследование основано на самых последних публикациях отчетов NFP, доступных на момент написания статьи. Для примера мы проанализировали три пары, ориентированные на доллар - GBP/USD, USD/JPY и EUR/USD, — прежде чем воплотить идею в алгоритм. Ниже приведены скриншоты диаграмм, которые иллюстрируют, как работает этот метод в каждом примере.

EURUSD post-NFP 05/09/25

Рисунок 2. EURUSD, M5, ценовое действие после объявления отчета NFP

На рисунке 2 (EUR/USD, M5) цена явно подчиняется законам коррекции. Маркеры A и B обозначают базовую линию до всплеска и экстремум всплеска, созданный объявлением NFP. Коррекция Фибоначчи проведена от A (базовой линии) до B (максимального пика) для первой свечи M5 после публикации. Впоследствии цена откатилась назад, чтобы протестировать инструмент Fibonacci bands (уровни Фибоначчи), и уровни коррекции явно соблюдаются — в этом примере был достигнут уровень в 50%, который послужил значимой точкой реакции. Цена вернулась назад, чтобы протестировать 61,8%—ную коррекцию Фибоначчи, и даже немного преодолела этот уровень, хотя и не вернулась полностью к базовой линии (100%). После этого небольшого скачка импульс восстановился, и рынок достиг нового максимума, который превысил первоначальный пиковый экстремум, подтверждая продолжение бычьего тренда.

Рисунок 2: GBPUSD, M5, ценовое действие после объявления отчета NFP

Рисунок 3. GBPUSD, M5, ценовое действие после объявления отчета NFP.

Пара EUR/USD сильно коррелирует с парой GBP/USD, а пара GBP/USD продемонстрировала схожую — хотя и не идентичную — бычью реакцию на это объявление. На рисунке 3 (выше) показана пара GBP/USD с тем же наложением линий Фибоначчи, что и на рисунке 2, иллюстрирующим реакцию цены на новости по этой паре и подтверждающим согласованность поведения коррекции между парами.

USDJPY, M5

Рисунок 4. USDJPY, M5, ценовое действие после объявления отчета NFP.

На рисунке 4 (пара USD/JPY) показана явная медвежья реакция на публикацию отчета NFP. После первоначального скачка вниз (точки A→B) цена откатилась примерно к 50%-ному уровню коррекции Фибоначчи, прежде чем возобновить свое снижение и достичь новых минимумов. Взятые вместе, три графика (EUR/USD, GBP/USD и USD/JPY) демонстрируют постоянную вероятность того, что цена вернется в зоны Фибоначчи после новостного всплеска — даже если направление движения у разных пар разное.

Импликация и высокоуровневый подход (перед кодированием)

Имея в виду три примера( EUR/USD, GBP/USD и USD/JPY),м ы можем разработать компактный план высокого уровня для воплощения идеи в алгоритм:

Определения
  • P_base — базовая линия до резкого скачка (цена, с которой началось быстрое движение).
  • P_spike — экстремум всплеска (самая высокая цена для бычьего всплеска, самая низкая для медвежьего всплеска).
  • Направление всплеска - бычье, если P_spike > P_base (всплеск, направленный вверх); медвежий, если p_spike < P_base (всплеск, направленный вниз).
Логика входа (в обоих направлениях)
1. Нарисуйте уровни коррекции Фибоначчи между P_base и P_spike после первой завершенной контрольной свечи, следующей за событием. 2. Дождитесь структурированного отката в целевую зону (например, 38,2–61,8%) с выбранным вами подтверждением (закрытие свечи, отклонение фитиля, нормализация тикового объема и т.д.). 3. Для бычьего всплеска (цена резко выросла, а затем откатилась назад):
  • Вход: открывайте длинную позицию, когда цена возвращается в выбранную зону Фибоначчи и наблюдается подтверждение.
  • Тейк-профит (TP): первичный тейк-профит на уровне P_spike (пиковый экстремум). Вторичные/расширенные TP могут использовать расширения Fib или кратные ATR значения.
  • Стоп-лосс (SL): установите SL на уровне P_base или чуть ниже него (основание всплеска / начало быстрого движения). Добавьте небольшой буфер (например, спред + X пипсов), чтобы избежать остановки из-за микрошумов.
4. Для медвежьего всплеска (цена резко упала, а затем откатилась назад):
  • Вход: открывайте короткую позицию, когда цена возвращается в выбранную зону Фибоначчи и наблюдается подтверждение.
  • Тейк-профит (TP): первичный тейк-профит на уровне P_spike (нижний экстремум нисходящего всплеска).
  • SL: Установите SL на уровне P_base или чуть выше него (возникновение быстрого нисходящего движения), с малым буфером.
К чему это сопоставление?
  • Использование P_spike в качестве TP приводит торговую цель в соответствие с самым последним очевидным экстремумом — мы пытаемся зафиксировать повторное тестирование экстремума скочка (“возвращение к скачку”).
  • Использование P_base в качестве SL использует точку начала быстрого движения в качестве логического доказательства недействительности: если цена возвращается к исходной точке, тезис о возврате после резкого скачка нарушается.

Практические корректировки и контроль рисков

Мы компенсируем SL на небольшую фиксированную величину (например, на несколько пипсов или спред × 1,5) и рассчитаем размер лота исходя из % риска счета × расстояния (SL—вход), чтобы сохранить абсолютный риск на низком уровне (целевой показатель 0,5%). Если значение P_base слишком велико, мы предложим альтернативный более жесткий SL (следующий Fib или ATR(5 м) × коэффициент) в качестве выбираемого параметра. Советник будет поддерживать частичные выходы на P_spike, закрытие оставшейся позиции, автоматическую отмену устаревших лимитных ордеров через M минут и дополнительный переключатель подтверждения коррелированных пар.

Strategizing with Fibonacci

Рисунок5. Визуальный обзор стратегии повторного входа по Фибоначчи после публикации NFP

Рисунок 5 (выше) представляет собой концептуальную визуализацию стратегии. Для бычьего тренда установите стоп-лосс чуть ниже точки A (основание скачка), а тейк-профит - выше точки B (экстремум скачка); для медвежьего тренда  установите стоп-лосс выше A, а тейк-профит - ниже B (типичный пример, рисунок 4 по паре USDJPY). Добавьте небольшой буфер к стоп-лоссу (например, несколько пипсов или спред × 1,5), чтобы уменьшить шумовые стоп-ауты.

Пограничные случаи

Если цена полностью восстановится до 100% (т.е. вернется к P_base), а затем продолжит движение дальше, подумайте об отмене первоначального предположения — восстановление завершено, и движение может быть обратным.

Если направление скачка неясно или размах колебания < минимального количества баров/пипсов, прервите сделку (слишком шумно).


Стратегия реализации

Чтобы упростить процесс разработки, я реализовал специальный заголовок класса в MetaEditor 5, ответственный за управление временными метками объявлений о занятости в несельскохозяйственном секторе (NFP) и корректное преобразование их между восточным временем (ET), UTC и серверным временем брокера. Этот заголовок централизует обработку перехода на летнее время, расчет смещения по восточному времени и утилиты преобразования, поэтому другие модули не зависят от часовых поясов.

Используя этот заголовок, я разработал советник, который:

  • Обнаруживает первую свечу M5, которая закрывается после публикации отчёта NFP,
  • Рисует коррекцию Фибоначчи, привязанную к этой свече,
  • Выставляет отложенные ордера на уровнях 38,2%, 50,0% и 61,8%.
  • Применяет надежные брокерские проверки (минимальные расстояния стоп-ордера, нормализация, истечение срока действия) и
  • Управляет жизненным циклом объектов графика и отложенных ордеров (очистка при заполнении или истечении срока действия).

На следующих двух этапах мы подготовим подробные, понятные разработчикам объяснения кода: сначала заголовок NFPTimeManager (дизайнерские решения, API, логика перехода на летнее время и часовых поясов), затем советник (поток данных, управление ордерами и проверки безопасности). Эти пояснения покажут, как система была спроектирована, реализована и протестирована на MQL5.

Шаг 1: Заголовок NFPTimeManager

1.1. Заголовок файла и метаданные

Этот файл начинается со стандартного блока заголовка и атрибутов #property на MQL5. В заголовке указаны авторство и управление версиями, чтобы другие разработчики (или вы в будущем) сразу поняли назначение и происхождение файла. Свойства позволяют системе сборки MQL5 отображать метаданные в MetaEditor 5.

//+------------------------------------------------------------------+ 
//|                                               NFPTimeManager.mqh  |
//|                        Copyright 2025, Clemence Benjamin  |
//|                                       https://www.mql5.com/ru/users/billionaire2024/seller|
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property version   "1.0"

1.2. Поля объявления класса и частного часового пояса

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

class CNFPTimeManager
{
private:
   int      m_traderTimeZone; // Trader time zone offset from UTC (hours) - optional
   int      m_serverTimeZone; // Server time zone offset from UTC (hours)

1.3. GetETOffset — вычисление смещения по восточному времени с помощью DST

NFP публикуется в фиксированное время по местному восточному времени (08:30 по восточному времени). GetETOffset вычисляет, используется ли для данной даты восточное стандартное время (UTC−5) или восточное летнее время (UTC−4). Она использует правила перехода на летнее время в США (со 2-го воскресенья марта по 1-е воскресенье ноября) и возвращает целочисленное смещение по UTC. Это позволяет поддерживать математическую корректность календаря в зависимости от времени года.

   // returns ET offset (-4 for EDT, -5 for EST) for a given datetime (trader time context)
   int GetETOffset(datetime time)
   {
      MqlDateTime dt;
      TimeToStruct(time, dt);
      int year = dt.year;

      // DST: 2nd Sunday of March to 1st Sunday of November (US rules)
      datetime dst_start = GetNthDayOfWeek(year, 3, 0, 2); // 2nd Sunday of March
      datetime dst_end   = GetNthDayOfWeek(year, 11, 0, 1); // 1st Sunday of November
      // Note: We interpret the DST boundaries as local US/Eastern dates at 02:00 local time,
      // but for our purpose using whole-day thresholds is acceptable (NFP at 8:30 ET).
      if(time >= dst_start && time < dst_end)
         return -4; // EDT
      return -5;    // EST
   }

1.4. GetNthDayOfWeek — надежная математика ежемесячного календаря

Календарная арифметика, подобная “второму воскресенью марта”, может быть на удивление подвержена ошибкам. GetNthDayOfWeek создает дату для 1-го числа месяца, находит день недели этого дня и вычисляет смещение к запрошенному N-му дню недели. Функция возвращает точную datetime для запрошенного события и повторно используется при вычислении временных меток летнего времени и NFP.

   // correctly compute the N-th day_of_week (0=Sunday..6=Saturday) in given month/year
   datetime GetNthDayOfWeek(int year, int month, int day_of_week, int n)
   {
      MqlDateTime dt;
      // build date for the 1st of month
      dt.year = year;
      dt.mon  = month;
      dt.day  = 1;
      dt.hour = 0;
      dt.min  = 0;
      dt.sec  = 0;
      datetime first = StructToTime(dt);
      MqlDateTime dt2;
      TimeToStruct(first, dt2);
      int dow_first = dt2.day_of_week; // 0..6
      int delta = (day_of_week - dow_first + 7) % 7;
      int days_to_nth = delta + (n - 1) * 7;
      return first + days_to_nth * 86400;
   }

1.5. Конструктор и методы-установщики часовых поясов

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

public:
   CNFPTimeManager()
   {
      m_traderTimeZone = 0;
      m_serverTimeZone = 0;
   }

   // set trader timezone if you need to convert between trader/local and server
   void SetTraderTimeZone(int timezone_hours)
   {
      m_traderTimeZone = timezone_hours;
   }

   void SetServerTimeZone(int timezone_hours)
   {
      m_serverTimeZone = timezone_hours;
   }

1.6. DetectServerTimeZone и GetServerTime — помощники по повышению осведомленности о сервере

DetectServerTimeZone это удобство, которое сравнивает TimeCurrent() (серверное время) с TimeLocal() (локальное время компьютера) и сохраняет целочисленное часовое смещение. GetServerTime просто оборачивает TimeCurrent() так, что другие модули вызывают единый источник для текущей временной метки сервера.

   // attempt to auto-detect server timezone (difference between server and local)
   int DetectServerTimeZone()
   {
      datetime local = TimeLocal();
      datetime server = TimeCurrent();
      int offset_seconds = (int)(server - local);
      int offset_hours = offset_seconds / 3600;
      m_serverTimeZone = offset_hours;
      return offset_hours;
   }

   datetime GetServerTime()
   {
      return TimeCurrent();
   }

1.7. GetNFPTimestampTrader — время на настенных часах NFP по восточному времени (ET)

Этот метод возвращает значение datetime, представляющее событие NFP в 08:30 по восточному времени в первую пятницу данного месяца/года. Функция работает в режиме “время трейдера” (настенные часы ET) и будет преобразована в серверное время отдельно. Такое разделение задач делает функцию простой и предсказуемой.

   // NFP candidate time in trader (ET) zone: first Friday of month at 08:30 ET
   datetime GetNFPTimestampTrader(int year, int month)
   {
      // first Friday (5) of the month
      datetime first_friday = GetNthDayOfWeek(year, month, 5, 1);
      MqlDateTime dt;
      TimeToStruct(first_friday, dt);
      dt.hour = 8; dt.min = 30; dt.sec = 0;
      return StructToTime(dt); // this is in server timescale semantics but represents the ET timestamp
   }

1.8. TraderETtoServer — преобразует временную метку ET в серверное время

Это важнейшая функция преобразования: она берет временную метку настенных часов ET (например, 2025-09-05, 08:30 по восточному времени), вычисляет, соответствует ли время ET переходу на летнее время, преобразует ее во время в формате UTC, а затем переключается на настроенный часовой пояс сервера. Результатом является абсолютный момент серверного времени, к которому советник должен привязать планирование и индексацию баров.

   // Convert a trader ET timestamp to server absolute time using ET offset and server tz
   datetime TraderETtoServer(datetime nfp_trader_time)
   {
      int et_offset = GetETOffset(nfp_trader_time); // UTC offset for ET at that date (-4/-5)
      // nfp_trader_time is interpreted in ET (i.e., wall-clock ET). To get UTC we add -ET offset:
      // UTC = ET - ET_offset_hours
      // Server local = UTC + server_tz
      int server_tz = m_serverTimeZone;
      datetime nfp_server_time = nfp_trader_time - et_offset * 3600 + server_tz * 3600;
      return nfp_server_time;
   }

1.9. GetNextNFPServerTime и GetLastNFPServerTime — поиск в направлении вперёд/назад

Эти помощники выполняют поиск по временной метке сервера NFP вперед (следующая) или назад (самая последняя), сканируя данные до 13 месяцев. Они преобразуют время ET-кандидата в серверное время и сравнивают с TimeCurrent(). Эти функции безопасны для крайних случаев, связанных с границами года, поскольку они корректно увеличивают или уменьшают месяц/год.

   // Return the next NFP server datetime strictly greater than now (search up to 13 months)
   datetime GetNextNFPServerTime()
   {
      datetime server_time = GetServerTime();
      MqlDateTime sd; TimeToStruct(server_time, sd);
      int year = sd.year, month = sd.mon;
      for(int i=0;i<13;i++)
      {
         datetime nfp_trader = GetNFPTimestampTrader(year, month);
         datetime nfp_server = TraderETtoServer(nfp_trader);
         if(nfp_server > server_time) return(nfp_server);
         month++; if(month>12){ month=1; year++; }
      }
      return(0);
   }

   // Return most recent NFP server datetime <= now (search backwards)
   datetime GetLastNFPServerTime()
   {
      datetime server_time = GetServerTime();
      MqlDateTime sd; TimeToStruct(server_time, sd);
      int year = sd.year, month = sd.mon;
      for(int i=0;i<13;i++)
      {
         datetime nfp_trader = GetNFPTimestampTrader(year, month);
         datetime nfp_server = TraderETtoServer(nfp_trader);
         if(nfp_server <= server_time) return(nfp_server);
         month--; if(month<1){ month=12; year--; }
      }
      return(0);
   }

1.10. IsNFPEventActive — быстрая проверка окна активности

Простой логический помощник проверяет, находится ли самый последний NFP в пределах настраиваемого временного интервала (по умолчанию ± один час). Это полезно, когда вы хотите, чтобы код вел себя по-другому во время NFP-окна (например, приостанавливал торговлю или регистрировал события).

   // Detect if an NFP event is currently within ±window_seconds of server time (default ±3600s)
   bool IsNFPEventActive(int window_seconds = 3600)
   {
      datetime server_time = GetServerTime();
      datetime last_nfp = GetLastNFPServerTime();
      if(last_nfp == 0) return(false);
      if(MathAbs((long)(server_time - last_nfp)) <= window_seconds) return(true);
      return(false);
   }

1.11. FirstM5OpenAfterNFP — сопоставляет время события с открытием M5

Торговая логика реагирует на закрытие первой свечи M5 после публикации NFP. Эта утилита возвращает время открытия M5, которое содержит событие (его закрытие является первым закрытием после временной метки). Мы используем простую поэтажную арифметику (временная метка / 300 * 300), поэтому она быстрая и однозначная.

   // Utility: compute the M5 open time for the bar that contains the NFP or the first bar after it.
   // Returns the M5 open time for the bar whose close is the first > nfp_server_time.
   datetime FirstM5OpenAfterNFP(datetime nfp_server_time)
   {
      if(nfp_server_time <= 0) return(0);
      // PeriodSeconds for M5 = 300
      int period = PERIOD_M5;
      int psec = 60 * 5;
      // floor open time:
      long open_floor = (long)(nfp_server_time / psec) * psec;
      // if the event falls exactly at an open boundary, then the bar with open=open_floor is the one that includes event
      // its close = open_floor + psec -> that is the first close after event
      return (datetime)open_floor;
   }

1.12. GetM5BarIndexByOpenTime — преобразование времени открытия в индекс бара

Наконец, это удобство позволяет использовать iBarShift для возврата индекса баров серии M5 (на основе текущего бара, равного 0) для заданного времени открытия M5. Оно возвращает значение -1, если точное соответствие не найдено. Эта функция удобна, когда советнику нужно вызвать CopyRates или проиндексировать исторические бары.

   // Convenience: compute the M5 bar index (iBarShift) for an M5 open time (exact match)
   int GetM5BarIndexByOpenTime(string symbol, datetime m5_open_time)
   {
      if(m5_open_time <= 0) return(-1);
      // prefer exact match - returns index where rates[].time == m5_open_time
      int idx = iBarShift(symbol, PERIOD_M5, m5_open_time, true);
      return idx; // -1 if not found
   }
};

Шаг 2:  Советник трейдера по Фибоначчи после публикации NFP

2.1. Заголовок, includes и исходные данные 

В этом верхнем блоке объявляются метаданные советника и импортируется торговый помощник и заголовок NFPTimeManager. Все входные данные - это кнопки, которые вы предоставляете трейдеру: цвета, названия, автоматически ли советник выставляет ордера, размер лота, буферы для SL/TP, строгость и продолжительность действия fib. Сохраняйте эти значения простыми при тестировании (демо) и настраивайте для каждого инструмента позже.

//+------------------------------------------------------------------+
//|             Post-NFP Fibonacci Trader EA                        |
//|  Post-NFP fib + pending orders with robust SL/TP buffer & object |
//|  lifecycle: destroy object when any order fills OR after expiry  |
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property version   "1.0"
#property strict

#include <Trade\Trade.mqh>
#include <NFPTimeManager.mqh>

// --- visual / fib inputs
input color  InpFiboColor = clrDodgerBlue;
input string InpPrefix = "FIBO_NFP_";
input bool   InvertFiboOrientation = true;
input bool   InpRayRight = true;

// Trading inputs
input double InpLots = 0.01;
input int    InpSlippage = 10;
input ulong  InpMagic = 123456;
input int    InpOrderExpirationMinutes = 0;
input bool   InpAutoPlaceOrders = true;
input bool   InpRequireStrictSLAt100 = false; // strict mode
input double InpSLBufferPoints = 20.0; // SL moved away from 100% by this many points
input double InpTPBufferPoints = 20.0; // TP moved away from 0% by this many points
input int    InpFibExpiryHours = 3;    // how many hours before fib auto-deletes

2.2. Глобальное состояние — в котором хранятся данные времени выполнения

Эти глобальные параметры содержат текущее состояние, необходимое советнику между вызовами: привязки fib, был ли исходный бар бычьим, отслеживаемые отложенные тикеты, текущее название объекта fib и время его создания (для истечения срока действия), фиксированные уровни fib и бухгалтерский учет M5. Они объявляются разработчиком и обновляются во время выполнения.

// --- internal
CTrade trade;
CNFPTimeManager nfpManager;

double g_price0 = 0.0;
double g_price100 = 0.0;
bool   g_fibBullish = false;

ulong g_pendingTickets[];                  // tracked pending tickets
string g_currentFibName = "";              // current fib object name
datetime g_fibCreateTime = 0;              // server time when fib created

static const double S_levels[] = {38.2, 50.0, 61.8};

datetime lastM5Open = 0;
datetime lastProcessedNFP = 0;

2.3. OnInit() — первоначальная настройка и привязка к заголовку NFP

Когда советник подключается, OnInit определяет часовой пояс сервера с помощью заголовка (чтобы убедиться в правильности преобразований на сервере ET), настраивает значения CTrade по умолчанию (магическое число и проскальзывание), очищает память любых предыдущих массивов тикетов и записывает текущее время открытия M5. Это подготавливает советника к обнаружению следующего закрытия M5 после NFP.

int OnInit()
{
   // initialize NFP manager tz
   int detected = nfpManager.DetectServerTimeZone();
   PrintFormat("EA: Detected server timezone UTC%+d", detected);

   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippage);

   ArrayFree(g_pendingTickets);

   // initialize last M5 open time
   MqlRates r[];
   if(CopyRates(_Symbol, PERIOD_M5, 0, 1, r) == 1) lastM5Open = r[0].time;
   else lastM5Open = 0;

   Print("EA: Initialized v1.50 — monitoring NFP, will create fib on first M5 close after NFP.");

   return(INIT_SUCCEEDED);
}

2.4. OnDeinit() — завершение работы и очистка

OnDeinit следит за тем, чтобы при удалении советника не оставалось потерянных отложенных ордеров или объектов графика. Она отменяет все отслеживаемые отложенные ордера, удаляет текущую fib (при наличии) и освобождает массив тикетов. Это важный этап технического обслуживания для безопасного тестирования и использования в реальных условиях.

void OnDeinit(const int reason)
{
   // cleanup: cancel tracked pending orders and delete fib
   CancelExistingPendingOrders();
   DeleteCurrentFibIfExists();
   ArrayFree(g_pendingTickets);
}

2.5. OnTradeTransaction() — обнаруживает заливки и немедленно реагирует

Этот обратный вызов прослушивает события торговой системы. Когда добавляется сделка (заливка), советник проверяет, соответствует ли заполненный ордер одному из отслеживаемых отложенных тикетов. Если да, то он удаляет fib и отменяет оставшиеся отложенные ордера (и очищает список тикетов). Он также удаляет тикеты из отслеживания, если они были удалены извне. Это гарантирует, что визуальная fib и отслеживаемые отложенные ордера будут синхронизированы с реальными сделками.

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
   // We care about deals added (fills) and order deletions possibly made externally
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
   {
      ulong orderTicket = trans.order;
      if(orderTicket == 0) return;

      for(int i = ArraySize(g_pendingTickets)-1; i >= 0; i--)
      {
         if(g_pendingTickets[i] == orderTicket)
         {
            PrintFormat("EA: Detected fill for tracked order %I64u -> destroying fib and cancelling remaining pending orders.", orderTicket);
            ArrayRemove(g_pendingTickets, i);
            CancelExistingPendingOrders();
            DeleteCurrentFibIfExists();
            ArrayFree(g_pendingTickets);
            return;
         }
      }
   }
   else if(trans.type == TRADE_TRANSACTION_ORDER_DELETE)
   {
      ulong orderTicket = trans.order;
      if(orderTicket == 0) return;
      for(int i = ArraySize(g_pendingTickets)-1; i>=0; i--)
      {
         if(g_pendingTickets[i] == orderTicket)
         {
            ArrayRemove(g_pendingTickets, i);
            PrintFormat("EA: Tracked order %I64u was deleted externally — removed from internal list.", orderTicket);
            break;
         }
      }
   }
}

2.6. OnTick() — логика сердцебиения: проверка истечения срока действия и триггер закрытия M5

OnTick отслеживает каждый тик. Выполняет две основные проверки: (А) если текущий индикатор fib существует и срок его действия (например, 3 часа) истек — удаляет его и отменяет отложенные ордера; (B) определяет, когда откроется новый бар M5 (предыдущий бар M5 закрылся). В этом событии M5-close она запрашивает у заголовка время последнего сервера NFP, проверяет, является ли закрытый M5 первым M5 после NFP, и, если это так, вызывает fib + процедуры размещения ордеров. Такой дизайн обеспечивает привязку fib к готовой свече M5 после публикации NFP.

void OnTick()
{
   // 1) Check for fib expiry by time
   if(StringLen(g_currentFibName) > 0 && g_fibCreateTime > 0)
   {
      datetime now = TimeCurrent();
      if(now >= (datetime)(g_fibCreateTime + InpFibExpiryHours * 3600))
      {
         PrintFormat("EA: Fib '%s' expired after %d hours -> deleting object and cancelling pending orders.",
                     g_currentFibName, InpFibExpiryHours);
         CancelExistingPendingOrders();
         DeleteCurrentFibIfExists();
      }
   }

   // 2) Detect M5 new bar open (previous M5 bar closed)
   MqlRates m5rt[];
   if(CopyRates(_Symbol, PERIOD_M5, 0, 1, m5rt) != 1) return;
   datetime currentM5Open = m5rt[0].time;
   if(currentM5Open == lastM5Open) return;

   // previous M5 open is the bar that just closed
   datetime prevOpen = lastM5Open;
   lastM5Open = currentM5Open;

   // find last NFP server time (the release to react to)
   datetime lastNFP = nfpManager.GetLastNFPServerTime();
   if(lastNFP == 0) return;

   // if already processed this NFP skip
   if(lastProcessedNFP == lastNFP) return;

   // compute bar open floor (M5) that contains the event
   int psec = 60*5;
   long floorOpen = (long)(lastNFP / psec) * psec;
   datetime firstM5OpenAfterNFP = (datetime)floorOpen;

   // we want prevOpen == firstM5OpenAfterNFP (i.e. the bar that closed is that target)
   if(prevOpen != firstM5OpenAfterNFP) return;

   // find M5 index of that open time
   int m5Index = iBarShift(_Symbol, PERIOD_M5, prevOpen, true);
   if(m5Index < 0)
   {
      PrintFormat("EA: Could not find M5 index for open time %s", TimeToString(prevOpen, TIME_DATE|TIME_MINUTES));
      return;
   }

   // update fib using that M5 bar and create pending orders
   if(!UpdateFibFromM5Bar(m5Index)) { Print("EA: UpdateFibFromM5Bar failed."); return; }
   if(InpAutoPlaceOrders) PlaceOrRefreshPendingOrders();

   // mark processed
   lastProcessedNFP = lastNFP;
   PrintFormat("EA: Processed NFP at %s (server time). Used M5 open %s as anchor.",
               TimeToString(lastNFP, TIME_DATE|TIME_MINUTES), TimeToString(prevOpen, TIME_DATE|TIME_MINUTES));
}

2.7. UpdateFibFromM5Bar() — считывает бар M5, вычисляет привязки, рисует fib

Эта функция считывает выбранный бар M5 с CopyRates, определяет, была ли эта свеча бычьей или медвежьей (закрытие или открытие), вычисляет привязки 0% и 100% в соответствии с вашим отображением, при необходимости инвертирует их для визуального предпочтения, сохраняет их для торговли, удаляет старые префиксные объекты и создает объект с уникальным именем OBJ_FIBO именно для этой свечи. Она записывает время создания логики истечения срока действия.

bool UpdateFibFromM5Bar(int m5Index)
{
   if(m5Index < 0) return(false);

   MqlRates r[];
   int copied = CopyRates(_Symbol, PERIOD_M5, m5Index, 1, r);
   if(copied != 1)
   {
      PrintFormat("EA: CopyRates for M5 index %d failed (copied=%d).", m5Index, copied);
      return(false);
   }

   double bar_high = r[0].high;
   double bar_low  = r[0].low;
   double bar_open = r[0].open;
   double bar_close= r[0].close;
   datetime bar_time = r[0].time;

   bool bullish = (bar_close > bar_open);
   g_fibBullish = bullish;

   // user's mapping:
   double price0 = bullish ? bar_high : bar_low;   // 0% anchor
   double price100= bullish ? bar_low  : bar_high; // 100% anchor

   if(InvertFiboOrientation) { double t = price0; price0 = price100; price100 = t; }

   // store anchors for trading
   g_price0 = price0;
   g_price100 = price100;

   // delete any existing prefix objects to avoid clutter
   int tot = ObjectsTotal(0);
   for(int i = tot-1; i >= 0; i--)
   {
      string nm = ObjectName(0, i);
      if(StringFind(nm, InpPrefix) == 0) ObjectDelete(0, nm);
   }

   // create a unique name for this fib
   g_currentFibName = InpPrefix + _Symbol + "_" + IntegerToString((int)bar_time);
   bool created = ObjectCreate(0, g_currentFibName, OBJ_FIBO, 0, bar_time, price0, (datetime)(bar_time + PeriodSeconds(PERIOD_M5)), price100);
   if(!created)
   {
      PrintFormat("EA: Failed to create Fibo '%s' (err=%d)", g_currentFibName, GetLastError());
      g_currentFibName = "";
      return(false);
   }

   ObjectSetInteger(0, g_currentFibName, OBJPROP_COLOR, InpFiboColor);
   ObjectSetInteger(0, g_currentFibName, OBJPROP_RAY_RIGHT, InpRayRight ? 1 : 0);
   ObjectSetInteger(0, g_currentFibName, OBJPROP_SELECTABLE, true);
   ObjectSetInteger(0, g_currentFibName, OBJPROP_HIDDEN, false);

   string desc = bullish ? "Fibo anchored to bullish M5 bar" : "Fibo anchored to bearish M5 bar";
   if(InvertFiboOrientation) desc += " (inverted)";
   if(InpRayRight) desc += " (ray-right)";
   ObjectSetString(0, g_currentFibName, OBJPROP_TEXT, desc);

   g_fibCreateTime = TimeCurrent();

   PrintFormat("EA: Drew Fibo '%s' on M5 index %d (open=%s) high=%.5f low=%.5f",
               g_currentFibName, m5Index, TimeToString(bar_time, TIME_DATE|TIME_MINUTES), bar_high, bar_low);

   return(true);
}

2.8. DeleteCurrentFibIfExists() — единое место для удаления fib

Крошечный помощник, который удаляет текущий объект fib, если тот существует, и очищает состояние советника относительно него. Централизованное удаление уменьшает дублирование и позволяет избежать незначительных ошибок, при которых имя не очищается.

void DeleteCurrentFibIfExists()
{
   if(StringLen(g_currentFibName) == 0) return;
   if(ObjectFind(0, g_currentFibName) >= 0)
   {
      ObjectDelete(0, g_currentFibName);
      PrintFormat("EA: Deleted fib object '%s'.", g_currentFibName);
   }
   g_currentFibName = "";
   g_fibCreateTime = 0;
}

2.9. CancelExistingPendingOrders() — стратегия отслеживания и очистки

Советник отслеживает тикеты, которые создает в g_pendingTickets. Эта процедура выполняет перебор этого списка и вызывает trade.OrderDelete для каждого тикета. Использование отслеживаемых тикетов безопаснее, чем удаление по символу или комментарию, поскольку оно нацелено только на ордера, созданные при текущем запуске советника (если вы позже не добавите персистентность).

void CancelExistingPendingOrders()
{
   if(ArraySize(g_pendingTickets) == 0) return;

   for(int i = ArraySize(g_pendingTickets)-1; i >= 0; i--)
   {
      ulong ticket = g_pendingTickets[i];
      if(ticket == 0) { ArrayRemove(g_pendingTickets, i); continue; }
      bool del = trade.OrderDelete(ticket);
      if(!del) PrintFormat("EA: Failed to delete pending ticket %I64u (err=%d)", ticket, GetLastError());
      else PrintFormat("EA: Deleted pending ticket %I64u", ticket);
      ArrayRemove(g_pendingTickets, i);
   }
}

2.10. PlaceOrRefreshPendingOrders() — расчет входов, SL/TP и размещение ордеров (ядро торговли)

Это сердце трейдинга. В отношении каждого уровня fib (38.2, 50, 61.8) она:

  • Вычисляет цену входа путем линейной интерполяции между g_price0 и g_price100.
  • Вычисляет желаемые значения SL и TP, привязанные к 100% и 0% соответственно, затем перемещает их от входа с помощью InpSLBufferPoints / InpTPBufferPoints в зависимости от того, был ли исходный бар M5 бычьим или медвежьим. (Это гарантирует, что SL будет ниже 100%, а TP — выше 0% в случае бычьего тренда и зеркально отразится на медвежьем тренде.)
  • Нормализует цены до цифр инструмента.
  • Запрашивает лимиты брокера (SYMBOL_TRADE_STOPS_LEVEL, SYMBOL_POINT) и устанавливает минимальные расстояния между остановками и ожиданиями. При необходимости корректирует SL/TP (или пропускает уровень, когда включен строгий режим).
  • Для каждого уровня определяет, подходит ли BUY_LIMIT (вход ниже рыночного значения) или SELL_LIMIT (вход выше рыночного значения), размещает отложенный ордер, используя CTrade с типом expiration/time, если он настроен, регистрирует в логе успех/неудачу и сохраняет тикет для очистки.

Такой подход сводит к минимуму количество отказов брокеров и поддерживает логически корректными ценовые соотношения.

void PlaceOrRefreshPendingOrders()
{
   // clear previously tracked tickets first
   CancelExistingPendingOrders();

   if(g_price0 == 0.0 || g_price100 == 0.0)
   {
      Print("EA: anchors not set - skipping placement");
      return;
   }

   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippage);

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

   long stops_level = 0;
   if(!SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL, stops_level)) stops_level = 0;
   double min_stop_distance = (double)stops_level * point;
   if(min_stop_distance < point) min_stop_distance = point;
   double min_pending_distance = min_stop_distance;

   // compute buffer values in price units (points * point)
   double sl_buffer_price = InpSLBufferPoints * point;
   double tp_buffer_price = InpTPBufferPoints * point;

   ENUM_ORDER_TYPE_TIME place_time_type = (InpOrderExpirationMinutes > 0) ? ORDER_TIME_SPECIFIED : ORDER_TIME_GTC;
   datetime place_expiration = (InpOrderExpirationMinutes > 0) ? (TimeCurrent() + InpOrderExpirationMinutes * 60) : 0;

   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   int levelsCount = ArraySize(S_levels);

   for(int i=0;i<levelsCount;i++)
   {
      double pct = S_levels[i] / 100.0;
      double entry = g_price0 + pct * (g_price100 - g_price0);
      // base desired SL and TP anchored at 100% and 0%
      double sl_desired = g_price100;
      double tp_desired = g_price0;

      // Apply buffers depending on the original fib bullish/bearish orientation
      if(g_fibBullish)
      {
         sl_desired = (g_price100 - sl_buffer_price);
         tp_desired = (g_price0 + tp_buffer_price);
      }
      else
      {
         sl_desired = (g_price100 + sl_buffer_price);
         tp_desired = (g_price0 - tp_buffer_price);
      }

      // normalize values
      entry = NormalizeDouble(entry, digits);
      sl_desired = NormalizeDouble(sl_desired, digits);
      tp_desired = NormalizeDouble(tp_desired, digits);

      // decide pending type by comparing entry price with market and min_pending_distance
      bool wantBuy = false, wantSell = false;
      if(entry <= (ask - min_pending_distance)) wantBuy = true;
      else if(entry >= (bid + min_pending_distance)) wantSell = true;
      else
      {
         PrintFormat("EA: Skipping level %.2f: entry(%.5f) too close to market (Ask=%.5f Bid=%.5f) min_pending_dist=%.5f",
                     S_levels[i], entry, ask, bid, min_pending_distance);
         continue;
      }

      ENUM_ORDER_TYPE otype = (wantBuy ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_SELL_LIMIT);
      bool adjusted = false;

      // Validate/adjust stops unless strict mode requested
      if(otype == ORDER_TYPE_BUY_LIMIT)
      {
         // For BUY_LIMIT we need sl < entry < tp
         if(!(sl_desired < entry - min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because SL would be adjusted (desired SL %.5f not < entry-%.5f).",
                           S_levels[i], sl_desired, (entry - min_stop_distance));
               continue;
            }
            double old = sl_desired;
            sl_desired = NormalizeDouble(entry - min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted SL BUY level %.2f old=%.5f -> new=%.5f", S_levels[i], old, sl_desired);
         }
         if(!(tp_desired > entry + min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because TP would be adjusted (desired TP %.5f not > entry+%.5f).",
                           S_levels[i], tp_desired, (entry + min_stop_distance));
               continue;
            }
            double old = tp_desired;
            tp_desired = NormalizeDouble(entry + min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted TP BUY level %.2f old=%.5f -> new=%.5f", S_levels[i], old, tp_desired);
         }
      }
      else // SELL_LIMIT
      {
         // For SELL_LIMIT we need tp < entry < sl
         if(!(sl_desired > entry + min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because SL would be adjusted (desired SL %.5f not > entry+%.5f).",
                           S_levels[i], sl_desired, (entry + min_stop_distance));
               continue;
            }
            double old = sl_desired;
            sl_desired = NormalizeDouble(entry + min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted SL SELL level %.2f old=%.5f -> new=%.5f", S_levels[i], old, sl_desired);
         }
         if(!(tp_desired < entry - min_stop_distance))
         {
            if(InpRequireStrictSLAt100)
            {
               PrintFormat("EA: Strict mode: skipping level %.2f because TP would be adjusted (desired TP %.5f not < entry-%.5f).",
                           S_levels[i], tp_desired, (entry - min_stop_distance));
               continue;
            }
            double old = tp_desired;
            tp_desired = NormalizeDouble(entry - min_stop_distance, digits);
            adjusted = true;
            PrintFormat("EA: Adjusted TP SELL level %.2f old=%.5f -> new=%.5f", S_levels[i], old, tp_desired);
         }
      }

      // final relation sanity
      bool valid = true;
      if(otype == ORDER_TYPE_BUY_LIMIT) valid = (sl_desired < entry && tp_desired > entry);
      else valid = (sl_desired > entry && tp_desired < entry);
      if(!valid)
      {
         PrintFormat("EA: Skipping level %.2f due to invalid final SL/TP relation entry=%.5f SL=%.5f TP=%.5f",
                     S_levels[i], entry, sl_desired, tp_desired);
         continue;
      }

      // build order comment
      string comment = InpPrefix + DoubleToString(S_levels[i], 2);

      // Place pending with proper signature (includes time type and expiration)
      bool placed = false;
      if(otype == ORDER_TYPE_BUY_LIMIT)
         placed = trade.BuyLimit(InpLots, entry, _Symbol, sl_desired, tp_desired, place_time_type, place_expiration, comment);
      else
         placed = trade.SellLimit(InpLots, entry, _Symbol, sl_desired, tp_desired, place_time_type, place_expiration, comment);

      if(!placed)
      {
         int err = GetLastError();
         PrintFormat("EA: Failed to place %s at %.5f (lvl %.2f) err=%d ret=%d",
                     (otype==ORDER_TYPE_BUY_LIMIT ? "BUY_LIMIT":"SELL_LIMIT"),
                     entry, S_levels[i], err, (int)trade.ResultRetcode());
      }
      else
      {
         ulong ticket = trade.ResultOrder();
         PrintFormat("EA: Placed %s ticket=%I64u lvl=%.2f entry=%.5f SL=%.5f TP=%.5f adjusted=%s",
                     (otype==ORDER_TYPE_BUY_LIMIT ? "BUY_LIMIT":"SELL_LIMIT"),
                     ticket, S_levels[i], entry, sl_desired, tp_desired, (adjusted ? "yes":"no"));

         // track ticket for cleanup and fill detection
         ArrayResize(g_pendingTickets, ArraySize(g_pendingTickets) + 1);
         g_pendingTickets[ArraySize(g_pendingTickets)-1] = ticket;
      }
   } // levels loop
}

2.11. Как советник использует заголовок NFPTimeManager — простые точки вызова

Заголовок выполняет основную работу по дате и времени: советник вызывает функцию DetectServerTimeZone() в OnInit() для настройки преобразования и функцию GetLastNFPServerTime() при каждом закрытии M5, чтобы найти момент времени сервера для последнего NFP. Используя эту временную метку, советник вычисляет первое открытое значение M5 после публикации NFP и реагирует, когда это значение закрывается. Это обеспечивает чистоту основной логики советника и безопасность в зависимости от часового пояса.

// example header calls in OnInit/OnTick
int detected = nfpManager.DetectServerTimeZone();
datetime lastNFP = nfpManager.GetLastNFPServerTime();


Результаты

Благодаря классу NFPTimeManager советник надежно определяет публикацию отчета о занятости в несельскохозяйственном секторе (NFP) даже на основе исторических данных, что позволяет нам запускать и проверять стратегию в тестере стратегий после успешной компиляции. Ниже приведены анимированные изображения, иллюстрирующие процесс тестирования в тестере стратегий.

metatester64_nDEhinFAuB

Рисунок 6. 7 июня 2024 года, объявление NFP

На рисунке 6 показано, что во время объявления NFP от 7 июня 2024 года советник успешно определил день события и корректно согласовал время публикации NFP с местным серверным временем, что было подтверждено в тестере стратегий. Инструмент коррекции Фибоначчи графически построен и выполнен в соответствии с замыслом. Однако отложенные ордера не сработали, поскольку коррекция была слишком мелкой и не смогла достичь уровней входа.

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

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

 metatester64_lfPG2lDG5m

Рисунок 7. 1 ноября 2024 года, объявление NFP



Заключение

Инструмент коррекции Фибоначчи может быть алгоритмически применен для торговли на данных о занятости в несельскохозяйственном секторе (NFP) с помощью основанного на правилах структурированного подхода. Вместо того чтобы гнаться за первоначальным всплеском, этот метод позволяет управлять рынком через несколько минут после публикации — во время фазы естественной коррекции. Теоретически, от 30% до 60% от первоначального пятиминутного скачка цен можно получить в качестве прибыли, даже если трейдер пропустил первое движение. Это стало возможным благодаря использованию коэффициентов Фибоначчи, которые отображают полную величину скачка в 100% и обеспечивают точные уровни коррекции для размещения сделок.

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

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

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

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


Основные уроки

Основной урок Описание:
Разбивайте логику на модули Разделите функциональность на небольшие, сфокусированные блоки (классы или функции). Пример: специальная функция NFPTimeManager для логики time/DST обеспечивает возможность чтения и повторного использования ядра советника.
Эксплицитно обрабатывайте часовые пояса и летнее время С помощью детерминированных правил преобразуйте время события (ET) в серверное время. Ошибки здесь приведут к смещению привязок в тестах на истории и реальных запусках — так что централизуйте и протестируйте эту логику.
Выполняйте привязку к закрытым барам Всегда основывайте расчеты на завершенных свечах (например, на первой M5, которая закрылась после публикации NFP). Это позволяет избежать нестабильности пересчета внутри бара и делает результаты воспроизводимыми в тестере стратегий.
Соблюдайте лимиты брокера Запрашивайте SYMBOL_TRADE_STOPS_LEVEL, SYMBOL_POINT и SYMBOL_DIGITS во время выполнения и проверяйте расстояния SL/TP/pending, чтобы избежать отклоненных ордеров.
Нормализуйте цены и используйте корректную точность Всегда приводите значения entry, SL и TP в соответствие с цифрами инструмента. Небольшие ошибки округления легко приводят к отказу брокера.
Разработайте безопасные варианты действий в случае резервного варианта поведения Используйте строгий и адаптивный режимы: либо пропускайте уровни, требующие изменения стоп-сигналов (строгий), либо автоматически корректируйте стоп-сигналы в соответствии с правилами брокера (адаптивный). Зарегистрируйте в логе оба решения.
Используйте надежные API-интерфейсы для размещения ордеров Используйте вспомогательные методы CTrade с корректными сигнатурами (включая тип expiration/time), чтобы избежать последующих изменений и упростить обработку ошибок.
Отслеживайте собственные тикеты и управляйте ими Ведите внутренний список созданных тикетов, чтобы советник мог надежно отменять или отслеживать только те ордера, которые он создал (избегайте удаления не связанных пользовательских ордеров).
Жизненный цикл объекта и чистота пользовательского интерфейса Присваивайте объектам графика короткие уникальные имена (например, fib_SYMBOL_YYMMDDhhmm), сохраняйте подробную информацию в OBJPROP_TEXT и удаляйте объекты по мере заполнения ордеров или истечения срока их действия.
Для заполнения используйте OnTradeTransaction Обнаруживайте заполнения и внешние удаления с помощью событий торговых транзакций, чтобы немедленно реагировать (очистка, ведение лога) и избегать задержек, связанных с опросами.
Подробное ведение лога для удобства отслеживания Регистрируйте каждое ключевое действие (создание, настройка, пропуск, размещение, сбой, удаление). Четкие логи необходимы для отладки, тестирования на истории и для целей регулирования/аудита.
Сначала протестируйте в тестере стратегий и демо-версии Проверьте логику на исторических датах NFP в тестере стратегий (детерминированный), а затем запустите на демо-счете. Для воспроизводимых тестов используйте детерминированные якоря (закрытые бары).



Вложения

Название файла Версия Описание
NFPTimeManager.mqh
1.0 Класс утилит для определения времени с учетом перехода на летнее время, который централизует все расчеты временных меток в отношении отчета о занятости в несельскохозяйственном секторе (NFP) и преобразования между восточным временем (ET), UTC и временем брокера/сервера. Основные функции включают в себя определение часового пояса сервера, расчет системных часов NFP (первая пятница, 08:30 по восточному времени), преобразование данных сервера по восточному времени, поиск последней/следующей серверной временной метки NFP, а также вспомогательных функций для сопоставления моментов публикации NFP с временем открытия M5 и индексами баров. Разработанный для повторного использования и модульного тестирования, советник не зависит от часовых поясов.
Post-NFP Fibonacci Trader EA.mq5
1.0 Полноценный советник, использующий NFPTimeManager для определения закрытия первой свечи M5 после публикации NFP, рисует коррекцию Фибоначчи, привязанную к этой свече, и выставляет отложенные ордера на уровнях коррекции 38,2%, 50% и 61,8%. Функции включают в себя буферы SL/TP, строгую и адаптивную обработку стопов, проверку лимита брокера (SYMBOL_TRADE_STOPS_LEVEL, SYMBOL_POINT, digits), отслеживание тикетов, автоматическую очистку (при заполнении или после настраиваемого истечения срока действия), а также подробное ведение лога для отслеживания. Настраивается с помощью входных данных; тестируется в тестере стратегий и демонстрационной среде.

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

Прикрепленные файлы |
Нейросети в трейдинге: Модели многократного уточнения прогнозов (Основные компоненты) Нейросети в трейдинге: Модели многократного уточнения прогнозов (Основные компоненты)
В статье мы раскрываем внутреннюю механику фреймворка RAFT — одного из самых точных и элегантных подходов к анализу динамических процессов. Мы шаг за шагом адаптируем его идею итеративного уточнения под финансовые временные ряды, создавая прочный фундамент для будущей модели. Читателя ждёт живое погружение в архитектуру, где каждый компонент имеет свой смысл и функцию.
Алгоритм кристаллической структуры — Crystal Structure Algorithm (CryStAl) Алгоритм кристаллической структуры — Crystal Structure Algorithm (CryStAl)
В статье представлены две версии Алгоритма кристаллической структуры, оригинальная и модифицированная. Алгоритм Crystal Structure Algorithm (CryStAl), опубликованный в 2021 году и вдохновленный физикой кристаллических структур, позиционировался как parameter-free метаэвристика для глобальной оптимизации. Однако тестирование выявило критическую проблему алгоритма. Представлена также модифицированная версия CryStAlm, которая исправляет ключевые недостатки оригинала.
Моделирование рынка (Часть 05): Создание класса C_Orders (II) Моделирование рынка (Часть 05): Создание класса C_Orders (II)
В данной статье я расскажу, как Chart Trade вместе с советником будет обрабатывать запрос на закрытие всех открытых позиций пользователя. Звучит просто, но есть несколько осложняющих моментов, и нужно знать, как управлять ими.
Нейросети в трейдинге: Модели многократного уточнения прогнозов (RAFT) Нейросети в трейдинге: Модели многократного уточнения прогнозов (RAFT)
Фреймворк RAFT предлагает принципиально иной подход к прогнозированию динамики рынка — не как разовый снимок, а как итеративное уточнение состояния в реальном времени. Он одновременно учитывает локальные и глобальные изменения, сохраняя высокую точность даже при сложных ценовых структурах.