English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Рецепты MQL5 - обработка пользовательских событий графика

Рецепты MQL5 - обработка пользовательских событий графика

MetaTrader 5Примеры | 10 октября 2014, 15:52
3 135 3
Denis Kirichenko
Denis Kirichenko

Введение

Данная статья является логическим продолжением статьи Рецепты MQL5 - обработка типичных событий графика. В текущем материале предлагаю читателю рассмотреть методику работы с пользовательскими событиями графика. Будут представлены примеры создания и обработки пользовательских событий. При этом использоваться будет объектно-ориентированный инструментарий.

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


1. Пользовательское событие графика

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

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

Разработчик предлагает для обработки всех событий графика 1 перечисление - ENUM_CHART_EVENT.

Согласно Документации, существует 65535 идентификаторов пользовательских событий. Первый и последний идентификаторы пользовательских событий задаются явными значениями CHARTEVENT_CUSTOM и CHARTEVENT_CUSTOM_LAST, что в численном выражении равно 1000 и 66534 соответственно (рис.1).

Рис.1 Первый и последний идентификаторы пользовательских событий

Рис.1 Первый и последний идентификаторы пользовательских событий

Если автор дружит с арифметикой, то с учетом первого и последнего идентификаторов всего получим: 66534-1000+1=65535.

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

Предлагаю рассмотреть такой критерий пользовательского события, как источник. К примеру, разработчик sergeev выдвинул идею прототипа торгового робота. Он разделяет все события на 3 группы (рис.2).

Рис.2 Группы источников пользовательских событий

Рис.2 Группы источников пользовательских событий

Тогда, согласно этой общей идее, нужно разрабатывать пользовательские события, исходя из групповой принадлежности.

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

Возьмем 2 события: приостановление и возобновление торговли вручную.

Рис.3 Источники пользовательских событий

Рис.3 Источники пользовательских событий

Дедуктивный метод (от общего к частному) позволит детализировать первичную схему (рис.3). Именно по этой схеме позже создадим типы событий в соответствующем классе (табл.1).

Табл.1 Пользовательские события

Табл.1 Пользовательские события

Может, на событийную концепцию данная Таблица и не "тянет", но начало положено. Представлю еще один подход. Общеизвестно, что модель абстрактной торговой системы состоит из трех подсистем — базовых модулей (рис.4).

Рис.4 Модель абстрактной торговой системы

Рис.4 Модель абстрактной торговой системы

Тогда пользовательские события на основании критерия "источник" можно классифицировать как события, возникающие в:

  1. сигнальной подсистеме;
  2. подсистеме сопровождения позиций;
  3. подсистеме управления капиталом.

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


2. Обработчик и генератор ChartEvent

Уделю несколько строк обработчику и генератору события графика. Что касается обработки пользовательского события графика, то принцип аналогичен обработке типичного события графика.

Обработчик — функция OnChartEvent(), принимает в качестве параметров четыре константы. Вероятно, что с помощью такого механизма разработчик реализовывал идею идентификации события и получения дополнительной информации о нем. На мой взгляд, очень компактный и удобный программный механизм.

Генерирует пользовательское событие графика функция EventChartCustom(). Причем можно создать событие как для "своего" графика, так и для "чужого". Пожалуй, самой интересной статьей о смысле своих и чужих графиков является Реализация мультивалютного режима в MetaTrader 5.

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


3. Класс пользовательского события

Итак, как я уже отмечал, автор советника должен сам позаботиться о событийной концепции. Попробуем поработать с событиями, представленными в Таблице 1. Сначала разберемся с базовым классом пользовательского события CEventBase и его потомками (рис.5).

Рис.5 Иерархия событийных классов

Рис.5 Иерархия событийных классов

Сам базовый класс выглядит так:

//+------------------------------------------------------------------+
//| Class CEventBase.                                                |
//| Purpose: base class for a custom event                           |
//| Derives from class CObject.                                      |
//+------------------------------------------------------------------+
class CEventBase : public CObject
  {
protected:
   ENUM_EVENT_TYPE   m_type;
   ushort            m_id;
   SEventData        m_data;

public:
   void              CEventBase(void)
     {
      this.m_id=0;
      this.m_type=EVENT_TYPE_NULL;
     };
   void             ~CEventBase(void){};
   //--
   bool              Generate(const ushort _event_id,const SEventData &_data,
                              const bool _is_custom=true);
   ushort            GetId(void) {return this.m_id;};

private:
   virtual bool      Validate(void) {return true;};
  };

Тип события задается перечислением ENUM_EVENT_TYPE:

//+------------------------------------------------------------------+
//| A custom event type enumeration                                  |
//+------------------------------------------------------------------+
enum ENUM_EVENT_TYPE
  {
   EVENT_TYPE_NULL=0,      // no event
   //---
   EVENT_TYPE_INDICATOR=1, // indicator event
   EVENT_TYPE_ORDER=2,     // order event
   EVENT_TYPE_EXTERNAL=3,  // external event
  };

Еще члены-данные включают идентификатор события и структуру данных.

Метод Generate() базового класса CEventBase занимается генерированием события. Метод GetId() возвращает id события, а виртуальный метод Validate() будет проверять значение идентификатора события. Сначала я включил в состав класса метод обработки события. Но потом понял, что каждое событие уникально, и абстрактным методом тут не обойдешься. В общем, переложил эту задачу на плечи класса-обработчика пользовательских событий CEventProcessor.


4. Класс обработчика пользовательских событий

Предполагается, что класс CEventProcessor будет генерировать и обрабатывать 8 представленных событий. Члены-данные класса выглядят так:

//+------------------------------------------------------------------+
//| Class CEventProcessor.                                           |
//| Purpose: base class for an event processor EA                    |
//+------------------------------------------------------------------+
class CEventProcessor
  {
//+----------------------------Data members--------------------------+
protected:
   ulong             m_magic;
   //--- flags
   bool              m_is_init;
   bool              m_is_trade;
   //---
   CEventBase       *m_ptr_event;
   //---
   CTrade            m_trade;
   //---
   CiMA              m_fast_ema;
   CiMA              m_slow_ema;
   //---
   CButton           m_button;
   bool              m_button_state;
//+------------------------------------------------------------------+
  };

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

Также есть указатель на объект типа CEventBase, который как раз и работает с событиями разных типов с помощью полиморфизма. Объект торгового класса CTrade предоставляет доступ к торговым операциям.

Пара объектов типа CiMA облегчают обработку данных от индикаторов. Для упрощения примера я взял 2 мувинга. Они будут ловить торговый сигнал. Еще есть представитель класса "Кнопка". Он будет обслуживать "ручное" включение/отключение советника.

Методы класса разбил по принципу "модули-процедуры-функции-макросы":

//+------------------------------------------------------------------+
//| Class CEventProcessor.                                           |
//| Purpose: base class for an event processor EA                    |
//+------------------------------------------------------------------+
class CEventProcessor
  {
//+-------------------------------Methods----------------------------+
public:
   //--- constructor/destructor
   void              CEventProcessor(const ulong _magic);
   void             ~CEventProcessor(void);

   //--- Modules
   //--- event generating
   bool              Start(void);
   void              Finish(void);
   void              Main(void);
   //--- event processing
   void              ProcessEvent(const ushort _event_id,const SEventData &_data);

private:
   //--- Procedures
   void              Close(void);
   void              Open(void);

   //--- Functions
   ENUM_ORDER_TYPE   CheckCloseSignal(const ENUM_ORDER_TYPE _close_sig);
   ENUM_ORDER_TYPE   CheckOpenSignal(const ENUM_ORDER_TYPE _open_sig);
   bool              GetIndicatorData(double &_fast_vals[],double &_slow_vals[]);

   //--- Macros
   void              ResetEvent(void);
   bool              ButtonStop(void);
   bool              ButtonResume(void);
  };

Среди модулей есть три, которые только генерируют события: стартовый — Start(), финишный — Finish(), главный — Main(). И четвертый модуль ProcessEvent() является как обработчиком событий, так и генератором.


4.1 Стартовый модуль

Предполагается, что модуль будет вызываться в обработчике OnInit().

//+------------------------------------------------------------------+
//| Start module                                                     |
//+------------------------------------------------------------------+
bool CEventProcessor::Start(void)
  {
//--- create an indicator event object
   this.m_ptr_event=new CIndicatorEvent();
   if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
     {
      SEventData data;
      data.lparam=(long)this.m_magic;
      //--- generate CHARTEVENT_CUSTOM+1 event
      if(this.m_ptr_event.Generate(1,data))
         //--- create a button
         if(this.m_button.Create(0,"Start_stop_btn",0,25,25,150,50))
            if(this.ButtonStop())
              {
               this.m_button_state=false;
               return true;
              }
     }

//---
   return false;
  }

В модуле создается указатель на объект индикаторного события. Затем генерируется событие "Создание индикатора". И в завершение создается кнопка. Ее состояние переводится в режим "Stop". Это означает, что последующее нажатие на кнопку прервет работу советника.

В определении метода еще задействована структура SEventData. Это простой контейнер для параметров, передаваемых генератору пользовательского события. Здесь будет заполнено только одно поле структуры — типа long. Сохраним в него магик советника.


4.2 Финишный модуль

Предполагается, что модуль будет вызываться в обработчике OnDeinit().

//+------------------------------------------------------------------+
//| Finish  module                                                   |
//+------------------------------------------------------------------+
void CEventProcessor::Finish(void)
  {
//--- reset the event object
   this.ResetEvent();
//--- create an indicator event object
   this.m_ptr_event=new CIndicatorEvent();
   if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
     {
      SEventData data;
      data.lparam=(long)this.m_magic;
      //--- generate CHARTEVENT_CUSTOM+2 event
      bool is_generated=this.m_ptr_event.Generate(2,data,false);
      //--- process CHARTEVENT_CUSTOM+2 event
      if(is_generated)
         this.ProcessEvent(CHARTEVENT_CUSTOM+2,data);
     }
  }

В нем сбрасывается предыдущий указатель на событие, и генерируется событие "Удаление индикатора". Отмечу такой нюанс: если в обработчике OnDeinit() сгенерировать пользовательское событие, то получим ошибку времени выполнения 4001 (неожиданная внутренняя ошибка). Поэтому для этого метода генерация и обработка события осуществляются в пределах метода без вызова OnChartEvent().

Посредством структуры SEventData также сохраним только магик советника.


4.3 Главный модуль

Предполагается, что модуль будет вызываться в обработчике OnTick().

//+------------------------------------------------------------------+
//| Main  module                                                     |
//+------------------------------------------------------------------+
void CEventProcessor::Main(void)
  {
//--- a new bar object
   static CisNewBar newBar;

//--- if initialized     
   if(this.m_is_init)
      //--- if not paused   
      if(this.m_is_trade)
         //--- if a new bar
         if(newBar.isNewBar())
           {
            //--- close module
            this.Close();
            //--- open module
            this.Open();
           }
  }

В этом модуле вызываются процедуры Open() и Close(). Первая может сгенерировать событие "Получение сигнала на открытие", а вторая — "Получение сигнала на закрытие". Текущая версия модуля полноценно работает при появлении нового бара. Класс для поиска нового бара определен Константином Груздевым.


4.4 Модуль обработки событий

Предполагается, что модуль будет вызываться в обработчике OnChartEvent(). По своему объему кода, да и по функциональной нагрузке этот модуль является самым большим.

//+------------------------------------------------------------------+
//| Process event module                                             |
//+------------------------------------------------------------------+
void CEventProcessor::ProcessEvent(const ushort _event_id,const SEventData &_data)
  {
//--- check event id
   if(_event_id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- button click
      if(StringCompare(_data.sparam,this.m_button.Name())==0)
        {
         //--- button state
         bool button_curr_state=this.m_button.Pressed();
         //--- to stop
         if(button_curr_state && !this.m_button_state)
           {
            if(this.ButtonResume())
              {
               this.m_button_state=true;
               //--- reset the event object
               this.ResetEvent();
               //--- create an external event object
               this.m_ptr_event=new CExternalEvent();
               //---
               if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
                 {
                  SEventData data;
                  data.lparam=(long)this.m_magic;
                  data.dparam=(double)TimeCurrent();
                  //--- generate CHARTEVENT_CUSTOM+7 event
                  ushort curr_id=7;
                  if(!this.m_ptr_event.Generate(curr_id,data))
                     PrintFormat("Failed to generate an event: %d",curr_id);
                 }
              }
           }
         //--- to resume
         else if(!button_curr_state && this.m_button_state)
           {
            if(this.ButtonStop())
              {
               this.m_button_state=false;
               //--- reset the event object
               this.ResetEvent();
               //--- create an external event object
               this.m_ptr_event=new CExternalEvent();
               //---
               if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
                 {
                  SEventData data;
                  data.lparam=(long)this.m_magic;
                  data.dparam=(double)TimeCurrent();
                  //--- generate CHARTEVENT_CUSTOM+8 event
                  ushort curr_id=8;
                  if(!this.m_ptr_event.Generate(curr_id,data))
                     PrintFormat("Failed to generate an event: %d",curr_id);
                 }
              }
           }
        }
     }
//--- user event 
   else if(_event_id>CHARTEVENT_CUSTOM)
     {
      long magic=_data.lparam;
      ushort curr_event_id=this.m_ptr_event.GetId();
      //--- check magic
      if(magic==this.m_magic)
         //--- check id
         if(curr_event_id==_event_id)
           {
            //--- process the definite user event 
            switch(_event_id)
              {
               //--- 1) indicator creation
               case CHARTEVENT_CUSTOM+1:
                 {
                  //--- create a fast ema
                  if(this.m_fast_ema.Create(_Symbol,_Period,21,0,MODE_EMA,PRICE_CLOSE))
                     if(this.m_slow_ema.Create(_Symbol,_Period,55,0,MODE_EMA,PRICE_CLOSE))
                        if(this.m_fast_ema.Handle()!=INVALID_HANDLE)
                           if(this.m_slow_ema.Handle()!=INVALID_HANDLE)
                             {
                              this.m_trade.SetExpertMagicNumber(this.m_magic);
                              this.m_trade.SetDeviationInPoints(InpSlippage);
                              //---
                              this.m_is_init=true;
                             }
                  //---
                  break;
                 }
               //--- 2) indicator deletion
               case CHARTEVENT_CUSTOM+2:
                 {
                  //---release indicators
                  bool is_slow_released=IndicatorRelease(this.m_fast_ema.Handle());
                  bool is_fast_released=IndicatorRelease(this.m_slow_ema.Handle());
                  if(!(is_slow_released && is_fast_released))
                    {
                     //--- to log?
                     if(InpIsLogging)
                        Print("Failed to release the indicators!");
                    }
                  //--- reset the event object
                  this.ResetEvent();
                  //---
                  break;
                 }
               //--- 3) check open signal
               case CHARTEVENT_CUSTOM+3:
                 {
                  MqlTick last_tick;
                  if(SymbolInfoTick(_Symbol,last_tick))
                    {
                     //--- signal type
                     ENUM_ORDER_TYPE open_ord_type=(ENUM_ORDER_TYPE)_data.dparam;
                     //---
                     double open_pr,sl_pr,tp_pr,coeff;
                     open_pr=sl_pr=tp_pr=coeff=0.;
                     //---
                     if(open_ord_type==ORDER_TYPE_BUY)
                       {
                        open_pr=last_tick.ask;
                        coeff=1.;
                       }
                     else if(open_ord_type==ORDER_TYPE_SELL)
                       {
                        open_pr=last_tick.bid;
                        coeff=-1.;
                       }
                     sl_pr=open_pr-coeff*InpStopLoss*_Point;
                     tp_pr=open_pr+coeff*InpStopLoss*_Point;

                     //--- to normalize prices
                     open_pr=NormalizeDouble(open_pr,_Digits);
                     sl_pr=NormalizeDouble(sl_pr,_Digits);
                     tp_pr=NormalizeDouble(tp_pr,_Digits);
                     //--- open the position
                     if(!this.m_trade.PositionOpen(_Symbol,open_ord_type,InpTradeLot,open_pr,
                        sl_pr,tp_pr))
                       {
                        //--- to log?
                        if(InpIsLogging)
                           Print("Failed to open the position: "+_Symbol);
                       }
                     else
                       {
                        //--- pause
                        Sleep(InpTradePause);
                        //--- reset the event object
                        this.ResetEvent();
                        //--- create an order event object
                        this.m_ptr_event=new COrderEvent();
                        if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
                          {
                           SEventData data;
                           data.lparam=(long)this.m_magic;
                           data.dparam=(double)this.m_trade.ResultDeal();
                           //--- generate CHARTEVENT_CUSTOM+5 event
                           ushort curr_id=5;
                           if(!this.m_ptr_event.Generate(curr_id,data))
                              PrintFormat("Failed to generate an event: %d",curr_id);
                          }
                       }
                    }
                  //---
                  break;
                 }
               //--- 4) check close signal
               case CHARTEVENT_CUSTOM+4:
                 {
                  if(!this.m_trade.PositionClose(_Symbol))
                    {
                     //--- to log?
                     if(InpIsLogging)
                        Print("Failed to close the position: "+_Symbol);
                    }
                  else
                    {
                     //--- pause
                     Sleep(InpTradePause);
                     //--- reset the event object
                     this.ResetEvent();
                     //--- create an order event object
                     this.m_ptr_event=new COrderEvent();
                     if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
                       {
                        SEventData data;
                        data.lparam=(long)this.m_magic;
                        data.dparam=(double)this.m_trade.ResultDeal();
                        //--- generate CHARTEVENT_CUSTOM+6 event
                        ushort curr_id=6;
                        if(!this.m_ptr_event.Generate(curr_id,data))
                           PrintFormat("Failed to generate an event: %d",curr_id);
                       }
                    }
                  //---
                  break;
                 }
               //--- 5) position opening
               case CHARTEVENT_CUSTOM+5:
                 {
                  ulong ticket=(ulong)_data.dparam;
                  ulong deal=(ulong)_data.dparam;
                  //---
                  datetime now=TimeCurrent();
                  //--- check the deals & orders history
                  if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now))
                     if(HistoryDealSelect(deal))
                       {
                        double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME);
                        ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY);
                        //---
                        if(deal_entry==DEAL_ENTRY_IN)
                          {
                           //--- to log?
                           if(InpIsLogging)
                             {
                              Print("\nNew position for: "+_Symbol);
                              PrintFormat("Volume: %0.2f",deal_vol);
                             }
                          }
                       }
                  //---
                  break;
                 }
               //--- 6) position closing
               case CHARTEVENT_CUSTOM+6:
                 {
                  ulong ticket=(ulong)_data.dparam;
                  ulong deal=(ulong)_data.dparam;
                  //---
                  datetime now=TimeCurrent();
                  //--- check the deals & orders history
                  if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now))
                     if(HistoryDealSelect(deal))
                       {
                        double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME);
                        ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY);
                        //---
                        if(deal_entry==DEAL_ENTRY_OUT)
                          {
                           //--- to log?
                           if(InpIsLogging)
                             {
                              Print("\nClosed position for: "+_Symbol);
                              PrintFormat("Volume: %0.2f",deal_vol);
                             }
                          }
                       }
                  //---
                  break;
                 }
               //--- 7) stop trading
               case CHARTEVENT_CUSTOM+7:
                 {
                  datetime stop_time=(datetime)_data.dparam;
                  //---
                  this.m_is_trade=false;                  
                  //--- to log?                  
                  if(InpIsLogging)
                     PrintFormat("Expert trading is stopped at: %s",
                                 TimeToString(stop_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS));
                  //---
                  break;
                 }
               //--- 8) resume trading 
               case CHARTEVENT_CUSTOM+8:
                 {
                  datetime resume_time=(datetime)_data.dparam;
                  this.m_is_trade=true;                  
                  //--- to log?                  
                  if(InpIsLogging)                     
                     PrintFormat("Expert trading is resumed at: %s",
                                 TimeToString(resume_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS));
                  //---
                  break;
                 }
              }
           }
     }
  }

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

Вторая часть призвана реагировать на сгенерированные пользовательские события. В ней есть 2 блока, где после обработки соответствующего события происходит создание нового. Первый блок — это обслуживание события "Получение сигнала на открытие". Его успешная обработка порождает новое ордерное событие "Открытие позиции". Второй блок — это обслуживание события "Получение сигнала на закрытие". Если сигнал обработан, то появляется событие "Закрытие позиции".

В качестве примера использования класса CEventProcessor приведу советник CustomEventProcessor.mq5. В нем все сделано так, чтобы создавать события и реагировать на события. Применение ООП позволило конечный файл исходного кода написать достаточно компактно. Код советника представлен в архиве.

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


Заключение

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

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

Исходные файлы из архива удобно расположить в папке проектов. В моем случае это папка MQL5\Projects\ChartUserEvent.

Прикрепленные файлы |
ChartUserEvent.zip (8.62 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Anatoli Kazharski
Anatoli Kazharski | 10 окт. 2014 в 16:56
Английский язык конечно знать хорошо и полезно. Но, если статья на русском, то и комментарии должны быть тоже на русском. ))
Alexey Volchanskiy
Alexey Volchanskiy | 10 февр. 2015 в 00:10
А я всегда пишу на инглише)) Просто потому, что куча клиентов из разных стран и английский худо-бедно знают все. Ну, а кто не знает, лишний стимул изучить основы, сейчас это легко и бесплатно, был бы интернет)) Так что все мяу, комменты на инглише.
juriy5555
juriy5555 | 20 апр. 2016 в 15:42
Неужели нет встроенной поддержки событийной модели . Да уж.   ИМХО, Это не   события(event) как в C# , скорее это сообщения (message).  Разница большая.  
Делегатов нет в языке, да и вообще много чего нет.  Даже обработчика ошибок.  А ведь чужой класс - это черный ящик, смешно читать в данном контексте комменты разработчиков языка, что надо все ошибки "ловить" при написании.  Разочаровался в языке =(
Работа с СУБД MySQL из MQL5 (MQL4) Работа с СУБД MySQL из MQL5 (MQL4)
Статья посвящена разработке интерфейса между MQL и СУБД MySQL. В ней рассматриваются существующие на данный момент практические решения и предлагается более удобный вариант исполнения библиотеки для работы с СУБД. В статье дано подробное описание функций, структуры интерфейса, приведены примеры и описаны некоторые тонкости при работе с MySQL. В плане программного решения, к статье прикреплены архивы с динамическими библиотеками, документацией и примерами-скриптами для языков MQL4 и MQL5.
Подготовка торгового счета к миграции на виртуальный хостинг Подготовка торгового счета к миграции на виртуальный хостинг
Клиентский терминал MetaTrader идеально подходит для автоматизации торговых стратегий. Для разработчиков торговых роботов в нем есть всё ‒ мощный язык программирования MQL4/MQL5 на основе C++, удобная среда разработки MetaEditor, многопоточный тестер стратегий с поддержкой распределенных вычислений в MQL5 Cloud Network. В этой статье вы узнаете, как перенести свой клиентский терминал со всеми разработками в виртуальную среду.
Рецепты MQL5 - обработка  события BookEvent Рецепты MQL5 - обработка события BookEvent
В статье рассматриваются событие стакана BookEvent и принцип его обработки. В качестве примера создается MQL5-программа, обрабатывающая состояния стакана. Используется объектно-ориентированный подход. Результаты обработки выводятся на экран в виде панели и уровней стакана.
Случайные леса предсказывают тренды Случайные леса предсказывают тренды
В статье описано использование пакета Rattle для автоматического поиска паттернов, способных предсказывать "лонги" и "шорты" для валютных пар рынка Форекс. Статья будет полезна как новичкам, так и опытным трейдерам.