English Deutsch 日本語
preview
Наблюдатель Connexus (Часть 8): Добавление Request Observer (Наблюдатель запросов)

Наблюдатель Connexus (Часть 8): Добавление Request Observer (Наблюдатель запросов)

MetaTrader 5Примеры | 19 июня 2025, 07:32
153 0
joaopedrodev
joaopedrodev

Введение

Настоящая статья является продолжением серии статей, в которых мы будем создавать библиотеку под названием Connexus. В первой статье мы разобрались с основами работы функции WebRequest, разобрались с каждым из ее параметров, а также создали пример кода, который демонстрирует использование данной функции и связанные с этим трудности. В прошлой статье мы создали клиентский уровень, простой и интуитивно понятный класс, отвечающий за отправку запросов, получение объекта запроса (CHttpRequest) и возврат ответа (CHttpResponse), который содержит информацию о запросе, такую как код состояния, продолжительность, тело и заголовок ответа. Мы также создали разделение класса с помощью функции WebRequest, что сделало библиотеку более гибкой, создав новый уровень под названием CHttpTransport.

В этой восьмой статье серии мы рассмотрим и реализуем Observer в библиотеке, чтобы облегчить управление многочисленными запросами клиента. Поехали!

Просто чтобы напомнить вам о текущем состоянии библиотеки, вот текущая схема:

Схема1


Что такое Наблюдатель?

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

Идея родилась, чтобы решить одну из тех досадных проблем, которые возникают в очень жестких системах, где один объект зависит от другого и "прилипает" к нему, не давая возможности дышать. Решение? Отделить, сделать легче. Еще до того, как Наблюдатель получил имя и фамилию, программисты уже стремились сделать системы легче. Это было еще в 1994 году в работе авторов **Эриха Гаммы (Erich Gamma), Ричарда Хелма (Richard Helm), Ральфа Джонсона (Ralph Johnson) и Джона Влиссидеса (John Vlissides)** - «Приёмы объектно-ориентированного проектирования. Па́ттерны проектирования» (Design Patterns: Elements of Reusable Object-Oriented Software) (1994), где они предложили Наблюдатель как идеальный способ держать несколько объектов в курсе изменений, вносимых в один объект, без цепочек строгой привязки.


Зачем использовать паттерн Наблюдатель?

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


Компоненты паттерна Наблюдатель

  1. Субъект (Subject): Это "владелец части", состояние которого меняется и который должен уведомлять наблюдателей об этих изменениях. Он ведет список наблюдателей и имеет методы для добавления или удаления кого-либо из этого списка.
  2. Наблюдатель (Observer): Каждый Наблюдатель подобен "слушателю", всегда готовому отреагировать на изменения в Субъекте. Он реализует метод обновления, который Субъект вызывает каждый раз при возникновении изменения.

Ниже я добавлю схему, которая показывает, как работает шаблон наблюдателя:

Схема 2

  1. Основные классы
    • Субъект (Subject): Этот класс поддерживает коллекцию наблюдателей ( observerCollection ) и предоставляет методы для управления этими наблюдателями. Его функция заключается в уведомлении наблюдателей всякий раз при изменении состояния.
      • Методы:
        • registerObserver(observer) : Добавляет наблюдателя в коллекцию.
        • unregisterObserver(observer) : Удаляет наблюдателя из коллекции.
        • notifyObservers() : Уведомляет всех наблюдателей, вызывая метод update() для каждого наблюдателя в observerCollection
    • Наблюдатель (Observer): Это интерфейс или абстрактный класс, определяющий метод update(). Все конкретные классы наблюдателей (конкреции Наблюдателя) должны реализовывать этот метод, который вызывается, когда Субъект уведомляет об изменениях.
  2. Конкретные классы
    • ConcreteObserverA и ConcreteObserverB: Это конкретные реализации интерфейса Наблюдатель. В каждом из них реализован метод update(), который определяет конкретный ответ на изменение в Субъекте.
  3. Отношения между Субъектом и Наблюдателем
    • Субъект ведет список Наблюдателей и уведомляет их, вызывая observer.update() для каждого наблюдателя в коллекции.
    • Конкретные Наблюдатели реагируют на изменения, происходящие в Субъекте, в соответствии с их конкретной реализацией в методе update().

Как это будет полезно в библиотеке Connexus? Мы будем использовать этот паттерн для информирования клиентского кода о том, когда отправлен запрос, когда получен ответ или даже когда была сгенерирована непредвиденная ошибка. Используя этот паттерн, клиент будет проинформирован о том, что это произошло. Это упрощает использование библиотеки, поскольку позволяет избежать создания в коде таких условий, как, например, “если сгенерирована ошибка, сделайте это”, “если сделан запрос, то сделайте это”, “если получен ответ, то сделайте это”.


Рука на коде

Сначала я продемонстрирую схему, которая показывает, как мы собираемся добавить этот паттерн в контекст библиотеки:

Схема 3

Лучше разберемся, как будет проходить реализация

  1. Обратите внимание, что схема имеет ту же структуру, что и референсная схема
  2. Я добавил два метода, к которым будут иметь доступ наблюдатели:
  3. OnSend() → Когда отправлен запрос.
  4. OnRecv() → Когда получен ответ.
  5. IHttpObserver будет не абстрактным классом, а скорее интерфейсом.


Создание интерфейса IHttpClient

Сначала создадим интерфейс IHttpClient и его путь будет <Connexus/Interface/IHttpClient.mqh> . И мы определяем две функции уведомления

//+------------------------------------------------------------------+
//|                                                IHttpObserver.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#include "../Core/HttpRequest.mqh"
#include "../Core/HttpResponse.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
interface IHttpObserver
  {
   void OnSend(CHttpRequest *request);
   void OnRecv(CHttpResponse *response);
  };
//+------------------------------------------------------------------+


Создание списка наблюдателей в CHttpClient

Добавим импорт интерфейса.

#include "../Interface/IHttpObserver.mqh"
Теперь создадим массив наблюдателей в частном поле класса. Помните, что в этом массиве должны храниться указатели, поэтому нам нужно добавить "*" перед именем переменной. Также создадим публичные методы для добавления, удаления и уведомления всех наблюдателей, которые хранятся в массиве.
//+------------------------------------------------------------------+
//|                                                   HttpClient.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "HttpRequest.mqh"
#include "HttpResponse.mqh"
#include "../Constants/HttpMethod.mqh"
#include "../Interface/IHttpTransport.mqh"
#include "../Interface/IHttpObserver.mqh"
#include "HttpTransport.mqh"
//+------------------------------------------------------------------+
//| class : CHttpClient                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CHttpClient                                        |
//| Heritage    : No heritage                                        |
//| Description : Class responsible for linking the request and      |
//|               response object with the transport layer.          |
//|                                                                  |
//+------------------------------------------------------------------+
class CHttpClient
  {
private:
   IHttpObserver     *m_observers[];                     // Array of observers
   
public:
   //--- Observers
   void              RegisterObserver(IHttpObserver *observer);
   void              UnregisterObserver(IHttpObserver *observer);
   void              OnSendNotifyObservers(CHttpRequest *request);
   void              OnRecvNotifyObservers(CHttpResponse *response);
  };
//+------------------------------------------------------------------+
//| Add observer pointer to observer list                            |
//+------------------------------------------------------------------+
void CHttpClient::RegisterObserver(IHttpObserver *observer)
  {
   int size = ArraySize(m_observers);
   ArrayResize(m_observers,size+1);
   m_observers[size] = observer;
  }
//+------------------------------------------------------------------+
//| Remove observer pointer to observer list                         |
//+------------------------------------------------------------------+
void CHttpClient::UnregisterObserver(IHttpObserver *observer)
  {
   int size = ArraySize(m_observers);
   for(int i=0;i<size;i++)
     {
      if(GetPointer(m_observers[i]) == GetPointer(observer))
        {
         ArrayRemove(m_observers,i,1);
         break;
        }
     }
  }
//+------------------------------------------------------------------+
//| Notifies observers that a request has been made                  |
//+------------------------------------------------------------------+
void CHttpClient::OnSendNotifyObservers(CHttpRequest *request)
  {
   int size = ArraySize(m_observers);
   for(int i=0;i<size;i++)
     {
      m_observers[i].OnSend(request);
     }
  }
//+------------------------------------------------------------------+
//| Notifies observers that a response has been received             |
//+------------------------------------------------------------------+
void CHttpClient::OnRecvNotifyObservers(CHttpResponse *response)
  {
   int size = ArraySize(m_observers);
   for(int i=0;i<size;i++)
     {
      m_observers[i].OnRecv(response);
     }
  }
//+------------------------------------------------------------------+
Наконец, вызываем функцию уведомления внутри функции, которая отправляет запрос, чтобы наблюдатели действительно были проинформированы:
//+------------------------------------------------------------------+
//| class : CHttpClient                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CHttpClient                                        |
//| Heritage    : No heritage                                        |
//| Description : Class responsible for linking the request and      |
//|               response object with the transport layer.          |
//|                                                                  |
//+------------------------------------------------------------------+
class CHttpClient
  {
private:
   
   IHttpObserver     *m_observers[];                     // Array of observers
   
public:
   //--- Basis function
   bool              Send(CHttpRequest &request, CHttpResponse &response);
  };
//+------------------------------------------------------------------+
//| Basis function                                                   |
//+------------------------------------------------------------------+
bool CHttpClient::Send(CHttpRequest &request, CHttpResponse &response)
  {
   //--- Request
   uchar body_request[];
   request.Body().GetAsBinary(body_request);
   
   //--- Response
   uchar body_response[];
   string headers_response;
   
   //--- Notify observer of request
   this.OnSendNotifyObservers(GetPointer(request));
   
   //--- Send
   ulong start = GetMicrosecondCount();
   int status_code = m_transport.Request(request.Method().GetMethodDescription(),request.Url().FullUrl(),request.Header().Serialize(),request.Timeout(),body_request,body_response,headers_response);
   ulong end = GetMicrosecondCount();
   
   //--- Notify observer of response
   this.OnRecvNotifyObservers(GetPointer(response));
   
   //--- Add data in Response
   response.Clear();
   response.Duration((end-start)/1000);
   response.StatusCode() = status_code;
   response.Body().AddBinary(body_response);
   response.Header().Parse(headers_response);
   
   //--- Return is success
   return(response.StatusCode().IsSuccess());
  }
//+------------------------------------------------------------------+

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


Тесты

Всё, что нам нужно сделать, - это использовать библиотеку. Для этого я создам новый тестовый файл под названием TestObserver.mq5, его путь: <Experts/Connexus/Tests/TestObserver.mq5>. Мы импортируем библиотеку и оставим только событие OnInit().

//+------------------------------------------------------------------+
//|                                                 TestObserver.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#include <Connexus/Core/HttpClient.mqh>
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Сразу после импорта я создам конкретный класс, который реализует интерфейс IHttpClient, он просто выведет на консоли терминала данные, которые были отправлены и получены с использованием библиотеки:

//+------------------------------------------------------------------+
//|                                                 TestObserver.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#include <Connexus/Core/HttpClient.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CMyObserver : public IHttpObserver
  {
public:
                     CMyObserver(void);
                    ~CMyObserver(void);
   
   void              OnSend(CHttpRequest *request);
   void              OnRecv(CHttpResponse *response);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMyObserver::CMyObserver(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMyObserver::~CMyObserver(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMyObserver::OnSend(CHttpRequest *request)
  {
   Print("-----------------------------------------------");
   Print("Order sent notification received in CMyObserver");
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CMyObserver::OnRecv(CHttpResponse *response)
  {
   Print("-----------------------------------------------");
   Print("Response notification received in CMyObserver");
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Create objects
   CHttpRequest request;
   CHttpResponse response;
   CHttpClient client;
   CMyObserver *my_observer = new CMyObserver();
   
   //--- Configure request
   request.Method() = HTTP_METHOD_GET;
   request.Url().Parse("https://httpbin.org/get");
   
   //--- Adding observer
   client.RegisterObserver(my_observer);
   
   //--- Send
   client.Send(request,response);
   
   //--- Delete pointer
   delete my_observer;
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

При отображении этого на графике мы получаем следующий результат:

Это показывает, что функции класса CMyObserver были вызваны внутри библиотеки, это все меняет, мы дополняем библиотеку, достигнув основной цели - сделать ее гибкой.

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

Теперь, с учетом этих включений наблюдателя, это текущая схема библиотеки:

Схема 4.



Рефакторинг папок

В настоящее время каталог всех библиотечных файлов выглядит следующим образом:

|--- Connexus
|--- |--- Constants
|--- |--- |--- HttpMethod.mqh
|--- |--- |--- HttpStatusCode.mqh
|--- |--- Core
|--- |--- |--- HttpClient.mqh
|--- |--- |--- HttpRequest.mqh
|--- |--- |--- HttpResponse.mqh
|--- |--- |--- HttpTransport.mqh
|--- |--- Data
|--- |--- |--- Json.mqh
|--- |--- Header
|--- |--- |--- HttpBody.mqh
|--- |--- |--- HttpHeader.mqh
|--- |--- Interface
|--- |--- |--- IHttpObserver.mqh
|--- |--- |--- IHttpTransport.mqh
|--- |--- URL
|--- |--- |--- QueryParam.mqh
|--- |--- |--- URL.mqh

Внесем две корректировки: добавим файлы из папки URL в папку Data и переименуем их в Utils, таким образом упростив обе папки, содержащие файлы аналогичного назначения. Мы также добавим папку interfaces в папку Core, поскольку интерфейсы являются частью ядра библиотеки. В итоге структура папок библиотеки выглядит следующим образом:

|--- Connexus
|--- |--- Constants
|--- |--- |--- HttpMethod.mqh
|--- |--- |--- HttpStatusCode.mqh
|--- |--- Core
|--- |--- |--- Interface
|--- |--- |--- |--- IHttpObserver.mqh
|--- |--- |--- |--- IHttpTransport.mqh
|--- |--- |--- HttpClient.mqh
|--- |--- |--- HttpRequest.mqh
|--- |--- |--- HttpResponse.mqh
|--- |--- |--- HttpTransport.mqh
|--- |--- Utils
|--- |--- |--- Json.mqh
|--- |--- |--- QueryParam.mqh
|--- |--- |--- URL.mqh
|--- |--- Header
|--- |--- |--- HttpBody.mqh
|--- |--- |--- HttpHeader.mqh


Переименование некоторых методов

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


Почему так важно придерживаться стандартного стиля?

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

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

  • Все классы используют префикс “ C ” перед названием
  • Все интерфейсы используют префикс “ I ” перед названием
  • Частные переменные используют префикс “ m_ ”
  • Методы всегда должны начинаться с заглавных букв
  • Значения ENUM должны быть записаны в верхнем регистре

Все эти стандарты уже были применены в библиотеке во время разработки, но мы добавим другие, такие как:

  • Методы для установки/получения атрибутов класса должны использовать префикс Get или Set
  • Методы, возвращающие размер массива, должны иметь имя “Size”
  • Методы, которые сбрасывают атрибуты класса, должны иметь имя “Clear”.
  • Методы для преобразования в строку должны иметь имя “ ToString ”
  • Избегайте избыточности имен в контекстных классах. Например, класс CQueryParam имеет метод AddParam(), который не имеет смысла. Идеальным вариантом было бы просто Add(), поскольку мы уже находимся в контексте параметров.

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

Схема 5


Заключение

Этой последней статьей мы завершаем серию статей о создании библиотеки Connexus, предназначенной для упрощения HTTP-взаимодействия. Это было непростое путешествие: мы изучили основы, углубились в более продвинутые методы проектирования и доработки кода, а также изучили паттерн Наблюдатель (Observer), чтобы придать Connexus реактивность, которая необходима в динамичных и гибких системах. Мы реализовали этот паттерн на практике, чтобы различные части приложения могли автоматически реагировать на изменения, создавая надежную и адаптируемую структуру.

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

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

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

Стратегия орла — Eagle Strategy (ES) Стратегия орла — Eagle Strategy (ES)
Eagle Strategy — алгоритм, имитирующий двухфазную охотничью стратегию орла: глобальный поиск через полеты Леви методом Мантенья, чередуется с интенсивной локальной эксплуатацией светлячкового алгоритма, математически обоснованный подход к балансу между исследованием и эксплуатацией, а также биоинспирированная концепция, объединяющая два природных феномена в единый вычислительный метод.
Нейросети в трейдинге: Интеллектуальный конвейер прогнозов (Окончание) Нейросети в трейдинге: Интеллектуальный конвейер прогнозов (Окончание)
Эта статья увлекательно покажет, как SwiGLU‑эмбеддинг раскрывает скрытые паттерны рынка, а разреженная смесь экспертов внутри Decoder‑Only Transformer делает прогнозы точнее при разумных вычислительных затратах. Мы подробно разбираем интеграцию Time‑MoE в MQL5 и OpenCL, шаг за шагом описываем настройку и обучение модели.
Инженерия признаков с Python и MQL5 (Часть II): Угол наклона цены Инженерия признаков с Python и MQL5 (Часть II): Угол наклона цены
На форуме MQL5 есть множество сообщений с просьбами помочь рассчитать угол наклона изменения цены. В этой статье мы рассмотрим один из способов расчета наклона изменения цены. Этот способ применим на любом рынке. Кроме того, мы определим, стоит ли разработка этой новой функции дополнительных усилий и времени. Выясним, может ли угол наклона цены улучшить точность нашей AI-модели при прогнозировании пары USDZAR на минутном таймфрейме.
Возможности Мастера MQL5, которые вам нужно знать (Часть 46): Ишимоку Возможности Мастера MQL5, которые вам нужно знать (Часть 46): Ишимоку
Ichimuko Kinko Hyo — известный японский индикатор, представляющий собой систему определения тренда. Как и в предыдущих статьях, мы рассмотрим этот индикатор с использованием паттернов и поделимся стратегиями и отчетами о тестировании, применив классы библиотеки Мастера MQL5.