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

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

MetaTrader 5Торговые системы |
106 0
Clemence Benjamin
Clemence Benjamin

Содержание



Введение

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

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

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

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

Точно так же, как маркеры сессий определяют ритм рынка, маркеры экономического календаря определяют зависящие от событий зоны волатильности. Например, алгоритм может автоматически приостановить торговлю за 30 минут до важного события и возобновить ее только после стабилизации волатильности. MQL5 предоставляет прямой доступ к таким данным с помощью функций MqlalendarValue() и MqlalendarValue(), позволяя экспертам определять предстоящие релизы и соответствующим образом корректировать свою работу.

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

TFT

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

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

Используя фреймворк MQL5, мы можем создавать системы, которые:

  • Торгуют только в течение выбранных сессий (например, Европейская торговая сессия или Американская торговая сессия).
  • Приостанавливают торговлю до выхода важных новостей или в часы низкой ликвидности.
  • Доставляют оповещения, сигналы или записи только тогда, когда время и условия совпадают.

Концепция, лежащая в основе временной фильтрации

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

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

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

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

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

  1. Создадим модульный слой TimeFilter, который работает совместно с SessionVisualizer для предоставления временных окон на основе сессий, часов и (необязательно) событий.
  2. Создадим образец советника, совершающего сделки только тогда, когда активные временные фильтры позволяют торговать.
  3. Создадим образец индикатора, отображающего или запускающего сигналы только в пределах разрешенных временных интервалов.



Реализация

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

TimeFilter

1. Заголовок модуля и защита подключения Include Guard

Начинаем с того, что рассматриваем этот файл как библиотеку многократного использования, а не как одноразовый фрагмент кода. Использование #property strict обеспечивает соблюдение более безопасных правил компиляции, в то время как функция include guard предотвращает дублирование определений, когда файл используется в нескольких проектах. Мы также включаем SessionVisualizer.mqh, поскольку наш временной фильтр разработан для взаимодействия с существующей логикой визуализации сессии. Это своего рода структурная дисциплина, которая делает код масштабируемым.

// ============================================================================
// TimeFilters.mqh — modular helpers for time-filtered trading
// Author—Clemence Benjamin
// ============================================================================

#property strict
#ifndef __TIMEFILTERS_MQH__
#define __TIMEFILTERS_MQH__

#include "SessionVisualizer.mqh"

2. Входные данные и управляемые пользователем настройки

Далее мы открываем небольшую панель управления с помощью входных параметров. Вместо жесткого кодирования поведения мы оставляем трейдеру (или тестировщику) делать выбор, следует ли выполнять фильтрацию по сессиям, фиксированным часам или событиям. Начало / окончание часов, заполнение до / после сессии и (будущие) фильтры новостей можно настроить, не затрагивая источник. В названиях последовательно используется префикс Inp, и каждый ввод документируется — именно так вы делаете свои модули удобочитаемыми на вкладке MetaTrader 5 “Inputs” (“Входные данные”) и понятными внутри кода.

// --------------------------- Inputs / Settings ------------------------------
input bool   InpUseSessionFilter   = true;      // gate by session times
input bool   InpUseClockFilter     = true;      // gate by fixed clock window
input bool   InpUseEventFilter     = false;     // gate by economic calendar (stubbed safe)

input int    InpClockStartHour     = 10;        // e.g., 10:00
input int    InpClockEndHour       = 12;        // e.g., 12:00
input int    InpPreSessionMins     = 0;         // allow N minutes before session start
input int    InpPostSessionMins    = 0;         // allow N minutes after session end

// Event filter parameters (used when you enable InpUseEventFilter)
input int    InpCalLookAheadMin    = 60;
input int    InpCalBlockBeforeMin  = 30;
input int    InpCalBlockAfterMin   = 30;
input int    InpCalMinImportance   = 2;         // 0=low,1=medium,2=high

3. Контекстная оболочка временного фильтра

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

// --------------------------- Context wrapper --------------------------------
class CTimeFilterContext
{
public:
   CSessionVisualizer *viz;   // optional visualizer pointer
   int                 gmt_offset;

   CTimeFilterContext() : viz(NULL), gmt_offset(0) {}

   void AttachVisualizer(CSessionVisualizer &ref) { viz = &ref; }

   void SetGMTOffset(int off)
   {
      gmt_offset = off;
      if(viz != NULL)
      {
         // safest explicit dereference form
         (*viz).SetGMTOffset(off);
      }
   }
};

4. Основной фильтр часового окна

Часовое окно - это наш первый и самый простой временной барьер: “работайте только в эти часы”. Здесь мы преобразуем серверное время в структурированную форму и сверяем его с заданным пользователем диапазоном. Стоит отметить два варианта поведения: если startHour == endHour, интерпретируем это как “без ограничений”, а если startHour > endHour, рассматриваем окно как переходящее за полночь (например, 22:00-02:00). Многие новички забывают об универсальности; использование его здесь означает, что остальная часть вашей логики может уверенно полагаться на эту утилиту.

// --------------------------- Utility: Clock Window --------------------------
bool InClockWindow(const int startHour, const int endHour)
{
   MqlDateTime t;
   TimeToStruct(TimeCurrent(), t);

   if(startHour == endHour)
      return true; // treat as "always on"

   if(startHour < endHour)
      return (t.hour >= startHour && t.hour < endHour);

   // wrap across midnight, e.g., 22..02
   return (t.hour >= startHour || t.hour < endHour);
}

5. Фильтр окон на основе сессий

Данная функция связывает наш механизм временного фильтра с логикой сессии. Она ожидает CTimeFilterContext (для настройки) и SESSION_TYPE (Тихоокеанская, Азиатская, Европейская, Американская сессии). В зависимости от вашей реализации, GetTodaySessionWindow (в вашем SessionVisualizer) возвращает начало и конец сессии текущего дня. Кроме того, добавляем пред- и послесессионное заполнение в минутах, где трейдеры обычно прописывают такие правила, как “начинайте поиск за 15 минут до Европейской сессии” или “избегайте последние 30 минут Американской сессии”. Обратите внимание, почему у нас не получается открытие: если мы не можем открыть окно сессии, мы возвращаем значение true, чтобы отсутствующий визуализатор или конфигурация не остановили всю торговлю.

// --------------------------- Utility: Session Window ------------------------
bool InSessionWindow(CTimeFilterContext &ctx, const SESSION_TYPE sType,
                     const int preMins = 0, const int postMins = 0)
{
   if(ctx.viz == NULL)
      return true; // no visualizer attached -> don't block

   datetime ss, se;

   if(!(*ctx.viz).GetTodaySessionWindow(sType, ss, se))
      return true; // session not available -> don't block

   // Apply pre/post-minute padding
   ss -= (preMins * 60);
   se += (postMins * 60);

   datetime now = TimeCurrent();
   return (now >= ss && now <= se);
}

6. Заглушка фильтра событий (расширяемый перехват)

Здесь мы определяем EventWindowAllowed как безопасную заглушку: на данный момент она всегда возвращает значение true. Дизайнерское решение является преднамеренным. Многие читатели используют брокеров или тестировщиков без поддержки экономического календаря; жесткая зависимость нарушила бы мобильность. Снабдив этот перехват правильной сигнатурой, мы упрощаем последующее подключение реальной, основанной на календаре логики (блокировка в отношении NFP, FOMC, CPI и т.д.), без изменения кода советника, который уже использует IsTradingAllowed.

// --------------------------- Utility: Economic Calendar ---------------------
bool EventWindowAllowed(const int /*lookAheadMin*/,
                        const int /*blockBeforeMin*/,
                        const int /*blockAfterMin*/,
                        const int /*minImportance*/)
{
   // Stubbed: always allow for safe compilation
   return true;
}

7. Комплексное решение: Разрешена ли торговля?

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

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

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

// --------------------------- Composite Gate --------------------------------
bool IsTradingAllowed(CTimeFilterContext &ctx)
{
   bool ok = true;

   if(InpUseClockFilter)
      ok = ok && InClockWindow(InpClockStartHour, InpClockEndHour);

   if(InpUseSessionFilter)
   {
      bool any = false;
      any = any || InSessionWindow(ctx, SESSION_LONDON,  InpPreSessionMins, InpPostSessionMins);
      any = any || InSessionWindow(ctx, SESSION_NEWYORK, InpPreSessionMins, InpPostSessionMins);
      any = any || InSessionWindow(ctx, SESSION_TOKYO,   InpPreSessionMins, InpPostSessionMins);
      any = any || InSessionWindow(ctx, SESSION_SYDNEY,  InpPreSessionMins, InpPostSessionMins);
      ok = ok && any;
   }

   if(InpUseEventFilter)
      ok = ok && EventWindowAllowed(InpCalLookAheadMin,
                                    InpCalBlockBeforeMin,
                                    InpCalBlockAfterMin,
                                    InpCalMinImportance);

   return ok;
}

#endif // __TIMEFILTERS_MQH__

Образец советника, использующего временные фильтры

1. Заголовок советника и высокоуровневые намерения

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

// TimeFilteredEA.mq5 -                                             
#property copyright  "Clemence Benjamin"
#property description "Professional Time-Filtered EA with streamlined CTrade usage."
#property version     "1.0"

2. Стратегические включенные файлы: Торговля, Информация о символах, Сессии, Временные фильтры

Далее выборочно включаем только те компоновочные блоки, от которых действительно зависит этот советник. Trade.mqh предоставляет нам высокоуровневую обёртку CTrade; SymbolInfo.mqh помогает разобраться со свойствами символов, характерными для конкретного брокера; SessionVisualizer.mqh управляет нашим визуальным контекстом; TimeFilters.mqh обеспечивает центральные ворота IsTradingAllowed. Мы создаем поведение на основе четко определенных модулей, а не изобретаем все заново внутри одного раздутого файла.

#include <Trade/Trade.mqh>
#include <Trade/SymbolInfo.mqh>
#include <SessionVisualizer.mqh>
#include <TimeFilters.mqh>

3. Открытые входные данные: Настраиваемые

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

input int    InpGMTOffsetHours = 0;     // GMT offset for session alignment
input bool   InpDrawSessions    = true; // Enable session visualization
input int    InpLookbackDays    = 5;    // Days to draw sessions
input double InpLotSize         = 0.01; // Fixed lot size
input int    InpStopLossPips    = 50;   // SL in pips (0 = none; TP auto-set to 2x if >0)
input int    InpTakeProfitPips  = 0;    // TP in pips (0 = auto 2x SL if SL>0)
input int    InpMagicNumber     = 12345; // Magic number for trades
input int    InpDeviationPips   = 10;   // Max slippage in pips

4. Основные объекты и состояние

Теперь объявляем ключевые объекты, используемые советником: CTrade для исполнения ордеров, CSymbolInfo для получения данных о спреде/тиковом/объемном движении, CSessionVisualizer чтобы нарисовать сессионные блоки, а также CTimeFilterContext для координации связанных со временем решений. Мы также готовим хэндлы скользящей средней и gLastSignalBar, чтобы избежать двойного срабатывания сигналов на одной и той же свече. Этот паттерн прост: инструменты на уровне среды являются глобальными, в то время как торговая логика и фильтры остаются модульными.

CTrade              trade;
CSymbolInfo         gSymbolInfo;
CSessionVisualizer  gSV("TF_SESS_");
CTimeFilterContext  gCTX;

// MA handles
int       gFastMAHandle   = INVALID_HANDLE;
int       gSlowMAHandle   = INVALID_HANDLE;
datetime  gLastSignalBar  = 0;

5. Профессиональный торговый менеджер: Инкапсуляция логики исполнения

Вместо того, чтобы запускать необработанные вызовы trade.Buy() по всему советнику, мы внедрили CTradeManager. Этот класс централизует нормализацию лота, проскальзывание, назначение магического числа, расчет SL/TP и проверку счета. Именно так вы поддерживаете чистоту кода своей стратегии — логика вашего сигнала запрашивает покупку, а менеджер обрабатывает все необходимые детали в одном месте, включая надежную регистрацию причин сбоя.

//+------------------------------------------------------------------+
//| Professional Trade Manager Wrapper                               |
//+------------------------------------------------------------------+
class CTradeManager
{
private:
   CTrade* m_trade;
   int     m_magic;
   int     m_deviation;
   double  m_minVolume;
   double  m_maxVolume;
   double  m_volumeStep;
   int     m_digits;
   double  m_point;

public:
   CTradeManager() : m_trade(NULL), m_magic(0), m_deviation(0) {}
   ~CTradeManager() {}

   bool Init(int magic, int deviation)
   {
      m_trade = new CTrade();
      if(m_trade == NULL) return false;
      
      m_magic = magic;
      m_deviation = deviation;
      
      m_trade.SetExpertMagicNumber(m_magic);
      m_trade.SetDeviationInPoints(m_deviation);
      m_trade.LogLevel(LOG_LEVEL_ERRORS);  // Match reference: log errors only
      
      // Cache symbol props (no filling set - default to broker/symbol)
      m_minVolume  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
      m_maxVolume  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
      m_volumeStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
      m_digits     = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
      m_point      = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
      
      Print("TradeManager initialized: Magic=", m_magic,
            " Deviation=", m_deviation, "pts (default filling)");
      return true;
   }

   void Deinit()
   {
      if(m_trade != NULL)
      {
         delete m_trade;
         m_trade = NULL;
      }
   }

6. Безопасность объёма и проверка счета

Прежде чем открывать какую-либо сделку, надежный советник должен ответить на два вопроса: Действителен ли этот объем для данного символа? И находится ли этот счет в исправном состоянии? IsValidVolume и NormalizeVolume приводят запрашиваемый объем в соответствие с ограничениями брокера SYMBOL_VOLUME_STEP. ValidateAccount подтверждает, что баланс, собственные средства и свободная маржа не являются бессмысленными. 

   bool IsValidVolume(double volume)
   {
      if(volume < m_minVolume || volume > m_maxVolume) return false;
      double normalized = NormalizeDouble(volume / m_volumeStep, 0) * m_volumeStep;
      return (MathAbs(volume - normalized) < m_point);
   }

   double NormalizeVolume(double volume)
   {
      return NormalizeDouble(
               MathMax(m_minVolume,
                       MathMin(m_maxVolume,
                               NormalizeDouble(volume / m_volumeStep, 0) * m_volumeStep)),
               2);
   }

   bool ValidateAccount()
   {
      double balance    = AccountInfoDouble(ACCOUNT_BALANCE);
      double equity     = AccountInfoDouble(ACCOUNT_EQUITY);
      double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
      
      if(balance <= 0 || equity <= 0 || freeMargin < 0)
      {
         Print("TradeManager: Invalid account state - Balance=", balance,
               " Equity=", equity, " FreeMargin=", freeMargin);
         return false;
      }
      
      Print("TradeManager: Account validated - Balance=", balance,
            " FreeMargin=", freeMargin);
      return true;
   }

7. ExecuteBuy: Логика SL/TP, осведомленность о спреде и сообщения об ошибках

ExecuteBuy - вот где все части сходятся воедино. Мы проверяем учетную запись, нормализуем и верифицируем объем, получаем новые цены по инструментам и рассчитываем стоп-лосс и тейк-профит таким образом, чтобы учитывать спред, размер пипсов и минимальные уровни стопа. Если задан только SL, а TP равен нулю, советник автоматически рассчитывает соотношение риска и прибыли в соотношении 1:2 - что внедряет хорошую практику непосредственно во фреймворк. А если что-то пойдет не так, функция выведет как ResultRetcodeDescription(), так и GetLastError().

   bool ExecuteBuy(double volume, double sl = 0, double tp = 0, string comment = "")
   {
      if(!ValidateAccount()) return false;
      
      volume = NormalizeVolume(volume);
      if(!IsValidVolume(volume))
      {
         Print("TradeManager: Invalid volume ", volume);
         return false;
      }
      
      gSymbolInfo.Name(_Symbol);
      gSymbolInfo.RefreshRates();
      double ask    = gSymbolInfo.Ask();
      double bid    = gSymbolInfo.Bid();
      double spread = ask - bid;
      
      double price      = NormalizeDouble(ask, m_digits);
      double stoploss   = 0.0;
      double takeprofit = 0.0;
      
      double pipValue = (m_digits == 3 || m_digits == 5) ? m_point * 10 : m_point;

      // --- SL/TP logic (spread-aware, 1:2 RR auto if desired) ---
      if(InpStopLossPips > 0)
      {
         if(spread >= InpStopLossPips * m_point)
         {
            PrintFormat("StopLoss (%d points) <= current spread = %.0f points. Spread value will be used",
                        InpStopLossPips, spread / m_point);
            stoploss = NormalizeDouble(price - spread, m_digits);
         }
         else
         {
            stoploss = NormalizeDouble(price - InpStopLossPips * pipValue, m_digits);
         }

         if(InpTakeProfitPips == 0)
         {
            takeprofit = NormalizeDouble(price + (InpStopLossPips * 2 * pipValue), m_digits);
            Print("TradeManager: Auto-set TP for 1:2 RR: ", takeprofit);
         }
         else
         {
            if(spread >= InpTakeProfitPips * m_point)
            {
               PrintFormat("TakeProfit (%d points) < current spread = %.0f points. Spread value will be used",
                           InpTakeProfitPips, spread / m_point);
               takeprofit = NormalizeDouble(price + spread, m_digits);
            }
            else
            {
               takeprofit = NormalizeDouble(price + InpTakeProfitPips * pipValue, m_digits);
            }
         }
      }
      else if(InpTakeProfitPips > 0)
      {
         if(spread >= InpTakeProfitPips * m_point)
         {
            PrintFormat("TakeProfit (%d points) < current spread = %.0f points. Spread value will be used",
                        InpTakeProfitPips, spread / m_point);
            takeprofit = NormalizeDouble(price + spread, m_digits);
         }
         else
         {
            takeprofit = NormalizeDouble(price + InpTakeProfitPips * pipValue, m_digits);
         }
      }

      // Directional sanity checks
      if(stoploss > 0 && stoploss >= price)
      {
         Print("TradeManager: Invalid SL for BUY - resetting to 0");
         stoploss = 0;
      }
      if(takeprofit > 0 && takeprofit <= price)
      {
         Print("TradeManager: Invalid TP for BUY - resetting to 0");
         takeprofit = 0;
      }

      // Respect broker minimum stop levels
      long stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
      if(stopsLevel > 0)
      {
         double minDist = stopsLevel * m_point;
         if(stoploss > 0 && (price - stoploss) < minDist)
         {
            stoploss = NormalizeDouble(price - minDist, m_digits);
            Print("TradeManager: SL adjusted to min dist: ", stoploss);
         }
         if(takeprofit > 0 && (takeprofit - price) < minDist)
         {
            takeprofit = NormalizeDouble(price + minDist, m_digits);
            Print("TradeManager: TP adjusted to min dist: ", takeprofit);
         }
      }

      Print("TradeManager: Executing BUY - Entry=", price,
            " Vol=", volume, " SL=", stoploss, " TP=", takeprofit);

      ResetLastError();
      bool result = m_trade.Buy(volume, _Symbol, price, stoploss, takeprofit, comment);
      
      if(!result)
      {
         uint   retcode = m_trade.ResultRetcode();
         string ret_desc = m_trade.ResultRetcodeDescription();
         PrintFormat("Failed %s buy %G at %G (sl=%G tp=%G) Retcode=%u (%s) MQL Error=%d",
                     _Symbol, volume, price, stoploss, takeprofit,
                     retcode, ret_desc, GetLastError());
         m_trade.PrintResult();
         Print("   ");
      }
      else
      {
         Print("TradeManager: BUY success - Deal=", m_trade.ResultDeal(),
               " Price=", m_trade.ResultPrice());
      }
      
      return result;
   }
};

8. Инициализация: Подключение временных фильтров, визуализатора, торгового менеджера и EMA

OnInit - это когда советник становится интерактивным. Подключаем CSessionVisualizer к gCTX, устанавливаем смещение по Гринвичу, чтобы визуальные эффекты и логика совпадали по времени, и инициализируем CTradeManager нашим магическим числом и отклонением. Также проверяем учетную запись (что особенно полезно в тестере стратегий) и создаем хэндлы скользящих средних, управляющие логикой наших сигналов. Напечатанные сообщения являются заранее продуманными — они сообщают пользователю не только о том, что советник “готов”, но и о том, какую модель риска и конфигурацию он использует.

// Global Trade Manager
CTradeManager gTradeMgr;

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("=== PIONEER EA INITIALIZATION (v2.11) ===");
   
   // Time filter setup
   gCTX.AttachVisualizer(gSV);
   gCTX.SetGMTOffset(InpGMTOffsetHours);
   if(InpDrawSessions)
      gSV.RefreshSessions(InpLookbackDays);
   
   // Initialize Trade Manager
   if(!gTradeMgr.Init(InpMagicNumber, InpDeviationPips))
   {
      Print("FAIL: TradeManager initialization failed");
      return INIT_FAILED;
   }
   
   // Validate account early
   if(!gTradeMgr.ValidateAccount())
   {
      Print("FAIL: Account validation failed - Check deposit in tester");
      return INIT_FAILED;
   }
   
   // MA indicators
   gFastMAHandle = iMA(_Symbol, _Period, 9, 0, MODE_EMA, PRICE_CLOSE);
   gSlowMAHandle = iMA(_Symbol, _Period, 21, 0, MODE_EMA, PRICE_CLOSE);
   
   if(gFastMAHandle == INVALID_HANDLE || gSlowMAHandle == INVALID_HANDLE)
   {
      Print("FAIL: MA handles creation failed");
      return INIT_FAILED;
   }
   
   Print("SUCCESS: Pioneer EA ready - Clean CTrade integration active (default filling)");
   Print("RR Logic: SL=", InpStopLossPips, " pips; TP=",
         (InpStopLossPips > 0 && InpTakeProfitPips == 0
            ? InpStopLossPips * 2
            : InpTakeProfitPips),
         " pips (1:2 auto if TP=0)");
   return INIT_SUCCEEDED;
}

9. Очистка: Управление ресурсами

В OnDeinit мы освобождаем хэндлы индикаторов, деинициализируем торговый менеджер и очищаем объекты сессии. Этот советник убирает за собой. 

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(gFastMAHandle != INVALID_HANDLE) IndicatorRelease(gFastMAHandle);
   if(gSlowMAHandle != INVALID_HANDLE) IndicatorRelease(gSlowMAHandle);
   gTradeMgr.Deinit();
   gSV.ClearAll();
}

10. Логика входа: Пересечение EMA с одним сигналом на бар

Логика сигнала намеренно проста: пересечение 9-й ЕМА выше 21-й ЕМА. Важно то, как это реализовано. Мы извлекаем по два значения из каждого буфера скользящих средних, определяем пересечение посредством предыдущих и текущих значений, а затем защищаемся с помощью gLastSignalBar, чтобы не спамить несколько записей на одной свече. 

//+------------------------------------------------------------------+
//| Signal Detection: EMA Crossover                                  |
//+------------------------------------------------------------------+
bool EntrySignalDetected()
{
   double fast[2], slow[2];
   if(CopyBuffer(gFastMAHandle, 0, 0, 2, fast) != 2 ||
      CopyBuffer(gSlowMAHandle, 0, 0, 2, slow) != 2)
      return false;
   
   bool crossover = (fast[1] <= slow[1] && fast[0] > slow[0]);
   if(!crossover) return false;
   
   datetime barTime = iTime(_Symbol, _Period, 0);
   if(barTime == gLastSignalBar) return false;
   gLastSignalBar = barTime;
   return true;
}

11. Фильтрация позиций с помощью магии и символа

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

//+------------------------------------------------------------------+
//| Position Check                                                   |
//+------------------------------------------------------------------+
bool HasOpenPosition()
{
   int total = PositionsTotal();
   for(int i = 0; i < total; i++)
   {
      if(PositionGetSymbol(i) == _Symbol &&
         PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
         return true;
   }
   return false;
}

12. OnTick: Сначала временной фильтр, затем логика

Тело OnTick намеренно короткое и удобочитаемое — в этом преимущество всех абстракций. Сначала обновляем визуальные элементы сессии (если они включены). Затем проходим через временной фильтр: если IsTradingAllowed(gCTX) возвращает значение false, советник просто помечает “Trading OFF” (“Торговля закрыта”) и завершает работу. Только при соблюдении временных условий проверяем отсутствие открытой позиции и наличие сигнала EMA. Если оба условия верны, передаем выполнение gTradeMgr.ExecuteBuy

//+------------------------------------------------------------------+
//| OnTick                                                           |
//+------------------------------------------------------------------+
void OnTick()
{
   if(InpDrawSessions)
      gSV.RefreshSessions(InpLookbackDays);
   
   if(!IsTradingAllowed(gCTX))
   {
      Comment("Pioneer EA: Trading OFF");
      return;
   }
   
   Comment("Pioneer EA: Trading ON | Positions: ", PositionsTotal());
   
   if(!HasOpenPosition() && EntrySignalDetected())
   {
      Print("=== SIGNAL: EMA Crossover Detected ===");
      gTradeMgr.ExecuteBuy(InpLotSize, 0, 0, "Pioneer Buy");
   }
}

Образец индикатора, использующего TimeFilters

1. Обзор индикатора – визуальное дополнение к советнику с временной фильтрацией

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

// ============================================================================
// Author: Clemence Benjamin
// File: TimeFilteredSignal.mq5
// Purpose: Time-filtered RSI signal + alerts indicator
// Notes : Uses large Wingdings arrows for visual clarity
//         Signals only appear when IsTradingAllowed(iCTX) == true
// ============================================================================

#property strict
#property indicator_chart_window

// We use two plots: one for bullish arrows, one for bearish arrows
#property indicator_plots   2
#property indicator_buffers 2

// --- Plot 1: Bullish RSI signal (up arrow) ---
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrLime
#property indicator_width1  2
#property indicator_label1  "TF_RSI_Bull"

// --- Plot 2: Bearish RSI signal (down arrow) ---
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrRed
#property indicator_width2  2
#property indicator_label2  "TF_RSI_Bear"

2. Повторное использование временной инфраструктуры – Ссессии и фильтры, не изобретенные заново

Здесь мы используем те же компоновочные блоки, что и в советнике: SessionVisualizer.mqh и TimeFilters.mqh. Это ключевой паттерн: единая централизованная временная политика, множество потребителей (советники, индикаторы, информационные панели). Также объявляем входные данные для построения сессии и настройки RSI, а также поведение оповещений. Мы можем включать / выключать сессии, настраивать уровни RSI и выбирать, насколько шумными или тихими должны быть уведомления.

#include <SessionVisualizer.mqh>
#include <TimeFilters.mqh>

// --------------------------- Inputs -----------------------------------------
// Visual session context (indicator-side)
input bool   InpDrawSessions_i    = true;
input int    InpGMTOffsetHours_i  = 0;
input int    InpLookbackDays_i    = 5;

// RSI configuration
input int    InpRSIPeriod         = 14;
input int    InpRSIOverbought     = 70;
input int    InpRSIOversold       = 30;

// Alert behavior
input bool   InpAlertOnCross      = true;      // enable popup alert on RSI cross
input bool   InpSendPush          = false;     // send mobile push
input bool   InpSendEmail         = false;     // send email
input string InpAlertPrefix       = "TF-RSI";  // prefix tag in messages

3. Общий контекст и буферы – Один временной мозг, две стрелки

Индикатор использует тот же самый CTimeFilterContext, разработанный нами ранее, указывая на его собственный экземпляр CSessionVisualizer. Благодаря этому реализация согласуется с советником: оба они опираются на одну и ту же идею “разрешенного времени”. Выделяем два буфера: BuffUp для бычьих стрелок и BuffDn для медвежьих стрелок. Такое разделение упрощает интерпретацию на графике — зеленая стрелка вверх: отфильтрованное бычье событие; красная стрелка вниз: отфильтрованное медвежье событие.

// --------------------------- Globals ----------------------------------------
CSessionVisualizer  iSV("TFI_SESS_");
CTimeFilterContext  iCTX;

// Two buffers: one for bullish arrows, one for bearish arrows
double BuffUp[];
double BuffDn[];

int      rsiHandle            = INVALID_HANDLE;
datetime gLastRSIAlertBarTime = 0;   // avoid duplicate alerts per bar

4. Помощник по оповещению – Одно сообщение, много каналов

Вместо того, чтобы разбрасывать вызовы Alert() внутри нашего цикла, централизуем логику уведомлений в FireRSIAlert. Это сохраняет код сигнала чистым и упрощает внесение улучшений (изменение префикса, форматирование, дополнительные данные). Этот паттерн масштабируется — сегодня это сообщения RSI; завтра это может быть “Обнаружен кластер потока ордеров” или “Подтвержден сдвиг пользовательского режима” без рефакторинга всего индикатора.

// --------------------------- Alert helper -----------------------------------
void FireRSIAlert(const string direction, const double rsiValue, const datetime barTime)
{
   string timeStr = TimeToString(barTime, TIME_DATE|TIME_SECONDS);

   string msg = StringFormat("%s | %s | %s | RSI=%.2f | Time=%s (inside allowed window)",
                             InpAlertPrefix,
                             _Symbol,
                             direction,
                             rsiValue,
                             timeStr);

   Alert(msg);

   if(InpSendPush)
      SendNotification(msg);

   if(InpSendEmail)
      SendMail(InpAlertPrefix + " " + _Symbol, msg);

   Print("TimeFilteredSignal: ", msg);
}

5. OnInit– Привязка стрелок к буферам, а времени - к сессиям

В OnInit мы выполняем структурную сборку:

  • Назначаем буферы для двух графиков.
  • Выбираем коды стрелок Wingdings (233 вверх, 234 вниз).
  • Прикрепляем визуализатор к контексту и применяем смещение по Гринвичу.
  • Дополнительно отрисовываем сессии.
  • Создаем хэндл RSI и инициализируем буферы.

 Индикатор должен быстро выйти из строя, если не удается создать основной ресурс (RSI, информация о символе).”

// --------------------------- OnInit -----------------------------------------
int OnInit()
{
   // Bind buffers
   SetIndexBuffer(0, BuffUp, INDICATOR_DATA);
   SetIndexBuffer(1, BuffDn, INDICATOR_DATA);

   // Use large Wingdings arrows:
   // 233 = up arrow, 234 = down arrow
   PlotIndexSetInteger(0, PLOT_ARROW, 233); // Bullish arrow up
   PlotIndexSetInteger(1, PLOT_ARROW, 234); // Bearish arrow down

   // Time filter context
   iCTX.AttachVisualizer(iSV);
   iCTX.SetGMTOffset(InpGMTOffsetHours_i);

   if(InpDrawSessions_i)
      iSV.RefreshSessions(InpLookbackDays_i);

   // Create RSI handle
   rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
   {
      Print("TimeFilteredSignal: Failed to create RSI handle. Error = ", GetLastError());
      return(INIT_FAILED);
   }

   // Initialize buffers as empty
   ArrayInitialize(BuffUp, EMPTY_VALUE);
   ArrayInitialize(BuffDn, EMPTY_VALUE);

   return(INIT_SUCCEEDED);
}

6. OnCalculate — Упорядоченный по времени RSI пересекается стрелками

Именно здесь индикатор получил свое название. Мы перебираем столбцы, но логика намеренно проста и многослойна:

  1. По умолчанию убираем стрелки (значение EMPTY_VALUE скрывает их).
  2. При необходимости обновляем визуальные эффекты сессии при появлении новых баров.
  3. Извлекаем значения RSI через CopyBuffer.
Для каждого бара:

  1. Проверяем IsTradingAllowed(iCTX) — если временные фильтры отклоняют запрос, пропускаем его.
  2. Обнаружение пересечений RSI:
    • Бычий: уход от перепроданности вверх → рисуем зеленую стрелку вверх под баром.
    • Медвежий: уход от перекупленности вниз → рисуем красную стрелку вниз над баром.

Только для последнего бара запускаем оповещения один раз, используя gLastRSIAlertBarTime.

Сигналы - это не просто “пересечения RSI”; это “пересечения RSI, которые соответствуют нашим профессиональным торговым часам, сессиям и (будущим) блокам событий".

// --------------------------- OnCalculate ------------------------------------
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 <= 2 || rsiHandle == INVALID_HANDLE)
      return(rates_total);

   // Refresh session drawings when new bars appear
   if(InpDrawSessions_i && rates_total != prev_calculated)
      iSV.RefreshSessions(InpLookbackDays_i);

   // Prepare RSI buffer
   static double rsiBuffer[];
   ArrayResize(rsiBuffer, rates_total);

   int copied = CopyBuffer(rsiHandle, 0, 0, rates_total, rsiBuffer);
   if(copied <= 0)
      return(prev_calculated > 0 ? prev_calculated : rates_total);

   // Recalc from prev-1 so last bar updates smoothly
   int start = (prev_calculated > 1 ? prev_calculated - 1 : 1);

   for(int i = start; i < rates_total; ++i)
   {
      // Default: hide both arrows on this bar
      BuffUp[i] = EMPTY_VALUE;
      BuffDn[i] = EMPTY_VALUE;

      // Respect global time filters (same logic as EA)
      if(!IsTradingAllowed(iCTX))
         continue;

      double rsi_prev = rsiBuffer[i - 1];
      double rsi_curr = rsiBuffer[i];

      bool bullCross =
         (rsi_prev < InpRSIOversold && rsi_curr >= InpRSIOversold);

      bool bearCross =
         (rsi_prev > InpRSIOverbought && rsi_curr <= InpRSIOverbought);

      // Bullish RSI recovery: big green up arrow below bar
      if(bullCross)
      {
         BuffUp[i] = low[i] - (_Point * 5);

         if(InpAlertOnCross && i == rates_total - 1 && time[i] != gLastRSIAlertBarTime)
         {
            FireRSIAlert("RSI cross UP from oversold", rsi_curr, time[i]);
            gLastRSIAlertBarTime = time[i];
         }
      }

      // Bearish RSI rejection: big red down arrow above bar
      if(bearCross)
      {
         BuffDn[i] = high[i] + (_Point * 5);

         if(InpAlertOnCross && i == rates_total - 1 && time[i] != gLastRSIAlertBarTime)
         {
            FireRSIAlert("RSI cross DOWN from overbought", rsi_curr, time[i]);
            gLastRSIAlertBarTime = time[i];
         }
      }
   }

   return(rates_total);
}

Масштабируемость: Вне RSI

RSI - это просто заполнитель. Созданная нами структура поддерживает любой сигнальный движок практически без изменений:

Заменим блок RSI на:

  • Пересекающиеся кластеры скользящих средних.
  • Условия пробоя (максимум/минимум сессии, отклонения VWAP).
  • Свечные паттерны (паттерны "пин бар", поглощение) только во время Европейской сессии.
  • Режимы волатильности (фильтры ATR).
  • Метрики книги ордеров/ прокси-сервера, результаты машинного обучения - как хотите.

Сохраняйте что угодно:

  • IsTradingAllowed(iCTX) - это универсальные временные ворота.
  • Стрелки (или маркеры) на графике.
  • Дополнительные оповещения через FireRSIAlert (переименуем в обобщенный FireSignalAlert).

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



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

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

Тестер стратегий

Рис.1. Тестирование индикатора TimeFilteredEA в тестере стратегий


TimeFilteredSignal

Рис.2. Тестирование индикатора TimeFilteredSignal




Заключение

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

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


Основные выводы:

Основные выводыОписание
1. Централизация логики временной фильтрацииРеализация специального уровня TimeFilter (вместо разрозненных if-условий) упрощает повторное использование одних и тех же правил сессий, часов и событий для нескольких советников и индикаторов, обеспечивая согласованное поведение и более простое обслуживание.
2. Использование контекстных объектов вместо глобального хаосаПаттерн CTimeFilterContext показывает, как объединить конфигурацию (смещение по Гринвичу, ссылку на визуализатор) в единый объект, уменьшая глобальные зависимости и делая ваш код более модульным, тестируемым и масштабируемым.
3. Отделение логики сигнала от логики исполненияРазмещая исполнение сделок внутри CTradeManager, а обнаружение сигналов - в специальных функциях/индикаторах, мы четко отделяем “что срабатывает” от “как отправляются ордера”, повышая ясность, отлаживаемость и возможность повторного использования.
4. Дизайн для визуальной обратной связи и диагностикиСигналы в виде стрелок SessionVisualizer и Wingdings обеспечивают немедленное визуальное подтверждение того, что временные фильтры и сигналы работают должным образом, что важно для отладки сложной логики и укрепления доверия трейдера к системе.
5. Создание расширяемых сигнальных фреймворковИспользование RSI в качестве подключаемого модуля внутри фреймворка с временной фильтрацией демонстрирует масштабируемость проекта: любой будущий метод (движение цены, пересечение скользящих средних, фильтры волатильности, пользовательские модели) может быть интегрирован с помощью тех же ворот IsTradingAllowed() с минимальными изменениями кода.

Содержимое вложения:

Исходный файлВерсияОписание
SessionVisualizer.mqh1.02Модульная библиотека отображения и отрисовки сессий, которая отображает ключевые торговые сессии Форекс на графике и предоставляет многоразовые ссылки на временные периоды для инструментов более высокого уровня.
TimeFilters.mqh1.00Базовая платформа временной фильтрации, обеспечивающая проверку по часам, сессиям и (расширяемым) событиям с помощью унифицированного интерфейса IsTradingAllowed() и общего контекстного объекта.
TimeFilteredEA.mq51.0Пример советника, использующего TimeFilters и SessionVisualizer для совершения сделок на основе EMA только в разрешенных временных периодах, со структурированным управлением сделками.
TimeFilteredSignal.mq51.0Графический индикатор, отображающий крупные стрелки Wingdings и генерирующий оповещения на основе RSI строго в пределах заданных временных периодов, служащий визуальным и расширяемым сигнальным уровнем.

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

Прикрепленные файлы |
TimeFilters.mqh (12.16 KB)
TimeFilteredEA.mq5 (12.07 KB)
Нейросети в трейдинге: Пространственно-управляемая агрегация рыночных событий (Основные модули) Нейросети в трейдинге: Пространственно-управляемая агрегация рыночных событий (Основные модули)
В этой статье продолжается практическая реализация фреймворка STFlow. Показано, как идеи пространственно-временной агрегации и кросс-модальной обработки превращаются в рабочие спайковые модули для анализа рынка.
Знакомство с языком MQL5 (Часть 24): Создание советника для торговли по графическим объектам Знакомство с языком MQL5 (Часть 24): Создание советника для торговли по графическим объектам
В этой статье вы научитесь созданию советника, который обнаруживает зоны поддержки и сопротивления, нарисованные на графике, и автоматически исполняет сделки на их основе.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Функции Уолша в современном трейдинге Функции Уолша в современном трейдинге
Эта статья рассматривает применение функций Уолша в трейдинге. Мы познакомимся с основными принципами использования этих функций для анализа финансовых рынков, прогнозирования цен и принятия торговых решений. Также мы обсудим преимущества и недостатки этих функций, и перспективы их применения в трейдинге и техническом анализе.