Создание новостного торгового советника

laplacianlab | 16 сентября, 2013

Введение

Как указано в Инвестопедии, трейдер, торгующий на новостях, – это "трейдер или инвестор, принимающий торговые или инвестиционные решения на основе сводок новостей". Действительно, выход экономических отчетов, включая ВВП стран, индексы потребительского доверия, данные по безработице в том или ином государстве и многие другие, часто приводит к сильным движениям на валютном рынке. Доводилось ли вам следить за выходом отчетов по количеству рабочих мест вне аграрного сектора в США (Non-Farm Payrolls)? Если да, то вы знаете, что выход этих отчетов часто определяет последующее движение валют и может ускорить смену тренда.

Газеты

Рис. 1. Газеты. Изображение распространяется по лицензии Creative Commons

1. Приступаем к программированию нашего торгового советника


1.1. Торговая идея

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


1.2. Основа объектно-ориентированного робота

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

//+---------------------------------------------------------------------+
//|                                               ExpertNewsWatcher.mq5 |
//|                    Copyright © 2013, laplacianlab, Jordi Bassagañas | 
//+---------------------------------------------------------------------+

#property copyright     "Copyright © 2013, laplacianlab. Jordi Bassagañas"
#property link          "https://www.mql5.com/ru/articles"
#property version       "1.00"
#property tester_file   "news_watcher.csv"  

#include <..\Experts\NewsWatcher\CNewsWatcher.mqh>

input ENUM_TIMEFRAMES   Period=PERIOD_M1;
input int               StopLoss=400;
input int               TakeProfit=600;
input double            LotSize=0.01;
input string            CsvFile="news_watcher.csv";
 
MqlTick tick;
CNewsWatcher* NW = new CNewsWatcher(StopLoss,TakeProfit,LotSize,CsvFile);

int OnInit(void)
  {
   NW.Init();
   NW.GetTechIndicators().GetMomentum().SetHandler(Symbol(), Period, 13, PRICE_CLOSE);
   return(0);
  }

void OnDeinit(const int reason)
  {
   delete(NW);
  }

void OnTick()
  {
   SymbolInfoTick(_Symbol, tick);              
   NW.GetTechIndicators().GetMomentum().UpdateBuffer(2);
   NW.OnTick(tick.ask,tick.bid);
  }
//+------------------------------------------------------------------+

CNewsWatcher – основной класс советника. Давайте посмотрим на код.

//+---------------------------------------------------------------------+
//|                                                    CNewsWatcher.mqh |
//|                                  Copyright © 2013, Jordi Bassagañas |
//+---------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <Mine\Enums.mqh>
#include <..\Experts\NewsWatcher\CBrain.mqh>
#include <..\Experts\NewsWatcher\CEvolution.mqh>
#include <..\Experts\NewsWatcher\CTechIndicators.mqh>
//+---------------------------------------------------------------------+
//| Класс CNewsWatcher                                                  |
//+---------------------------------------------------------------------+
class CNewsWatcher
  {
protected:
   //--- Пользовательские типы
   CBrain               *m_brain;
   CEvolution           *m_evolution;
   CTechIndicators      *m_techIndicators; 
   //--- Типы MQL5
   CTrade               *m_trade;
   CPositionInfo        *m_positionInfo;
public:
   //--- Конструктор и деструктор
                        CNewsWatcher(int stop_loss,int take_profit,double lot_size,string csv_file);
                        ~CNewsWatcher(void);
   //--- Геттеры
   CBrain               *GetBrain(void);
   CEvolution           *GetEvolution(void);
   CTechIndicators      *GetTechIndicators(void);
   CTrade               *GetTrade(void);
   CPositionInfo        *GetPositionInfo(void);
   //--- Методы CNewsWatcher
   bool                 Init();
   void                 Deinit(void);
   void                 OnTick(double ask,double bid);
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CNewsWatcher::CNewsWatcher(int stop_loss,int take_profit,double lot_size, string csv_file)
  {
   m_brain=new CBrain(stop_loss,take_profit,lot_size,csv_file);
   m_evolution=new CEvolution(DO_NOTHING);
   m_techIndicators=new CTechIndicators;
   m_trade=new CTrade();
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CNewsWatcher::~CNewsWatcher(void)
  {
   Deinit();
  }
//+------------------------------------------------------------------+
//| GetBrain                                                         |
//+------------------------------------------------------------------+
CBrain *CNewsWatcher::GetBrain(void)
  {
   return m_brain;
  }
//+------------------------------------------------------------------+
//| GetEvolution                                                     |
//+------------------------------------------------------------------+
CEvolution *CNewsWatcher::GetEvolution(void)
  {
   return m_evolution;
  }
//+------------------------------------------------------------------+
//| GetTechIndicators                                                |
//+------------------------------------------------------------------+
CTechIndicators *CNewsWatcher::GetTechIndicators(void)
  {
   return m_techIndicators;
  }  
//+------------------------------------------------------------------+
//| GetTrade                                                         |
//+------------------------------------------------------------------+
CTrade *CNewsWatcher::GetTrade(void)
  {
   return m_trade;
  }
//+------------------------------------------------------------------+
//| GetPositionInfo                                                  |
//+------------------------------------------------------------------+
CPositionInfo *CNewsWatcher::GetPositionInfo(void)
  {
   return m_positionInfo;
  }
//+------------------------------------------------------------------------+
//| CNewsWatcher OnTick                                                    |
//| Проверяем характер ценового движения в момент выхода новости           |
//+------------------------------------------------------------------------+
void CNewsWatcher::OnTick(double ask,double bid)
  {
//--- имеются ли какие-либо новости для обработки?  
   if(GetBrain().GetNewsContainer().GetCurrentIndex() < GetBrain().GetNewsContainer().GetTotal())
   {     
      double momentumBuffer[];
      
      GetTechIndicators().GetMomentum().GetBuffer(momentumBuffer, 2);
      
      //--- Количество секунд перед выходом новостей. GMT +- timeWindow – фактическое время, с которого робот начинает 
      //--- следить за рынком. Например, если выход новостей ожидается в 13:00 по Гринвичу, вы можете установить TimeWindow 
      //--- на 900 секунд. Таким образом, советник начинает следить за рынком за пятнадцать минут до выхода новостей. 
      int timeWindow=600;
      
      CNew *currentNew = GetBrain().GetNewsContainer().GetCurrentNew();      
      int indexCurrentNew = GetBrain().GetNewsContainer().GetCurrentIndex();
            
      if(TimeGMT() >= currentNew.GetTimeRelease() + timeWindow)
      {
         GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+1);
         return;
      }
      
      //--- есть ли открытая позиция?
      if(!m_positionInfo.Select(_Symbol))
      {
         //--- если открытой позиции нет, открываем ее
         bool timeHasCome = TimeGMT() >= currentNew.GetTimeRelease() - timeWindow && TimeGMT() <= currentNew.GetTimeRelease() + timeWindow;
             
         if(timeHasCome && momentumBuffer[0] > 100.10)
         {
            GetEvolution().SetStatus(SELL);
            GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+1);
         }
         else if(timeHasCome && momentumBuffer[0] < 99.90)
         {
            GetEvolution().SetStatus(BUY);
            GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+1);
         }
      }
      //--- если есть открытая позиция, ждем отработки мат. ожидания
      else 
      {
         GetEvolution().SetStatus(DO_NOTHING);         
      }  
      
      double tp;
      double sl; 

      switch(GetEvolution().GetStatus())
      {      
         case BUY:
            tp = ask + m_brain.GetTakeProfit() * _Point;
            sl = bid - m_brain.GetStopLoss() * _Point;
            GetTrade().PositionOpen(_Symbol,ORDER_TYPE_BUY,m_brain.GetSize(),ask,sl,tp);
            break;

         case SELL:
            sl = ask + m_brain.GetStopLoss() * _Point;
            tp = bid - m_brain.GetTakeProfit() * _Point;
            GetTrade().PositionOpen(_Symbol,ORDER_TYPE_SELL,m_brain.GetSize(),bid,sl,tp);
            break;

         case DO_NOTHING:
            // Ничего не происходит...
            break;
      }
   }   
//--- выходим, как только все новости в контейнере обработаны
   else return;
  }
//+------------------------------------------------------------------+
//| Инициализация CNewsWatcher                                       |
//+------------------------------------------------------------------+
bool CNewsWatcher::Init(void)
  {
// Здесь располагается логика инициализации...
   return true;
  }
//+------------------------------------------------------------------+
//| Деинициализация CNewsWatcher                                     |
//+------------------------------------------------------------------+
void CNewsWatcher::Deinit(void)
  {
   delete(m_brain);
   delete(m_evolution);
   delete(m_techIndicators);
   delete(m_trade);
   Print("Деинициализация CNewsWatcher выполнена!");
   Print("Спасибо, что воспользовались данным советником.");
  }
//+------------------------------------------------------------------+

Не беспокойтесь, если вам что-то на данном этапе непонятно. Это нормально. Во-первых, вам необходимо изучить все элементы данного эксперта, чтобы понять, как все работает. Я рекомендую вам сначала прочитать эту статью поверхностно, а затем перечитать ее еще один-два раза, вникая в подробности. В любом случае, попытаюсь объяснить ключевые элементы CNewsWatcher.

Самая важная часть советника – это, конечно же, метод OnTick, в котором, как вы увидите, CNewsWatcher использует для работы объектно-ориентированный новостной контейнер. Данный элемент, который может рассматриваться как обычная газета, содержит новости, предназначенные для советника. 

Обратите внимание на то, как мы получаем новостной контейнер:

GetBrain().GetNewsContainer();

А также на то, как мы получаем последние новости для обработки:

CNew *currentNew = GetBrain().GetNewsContainer().GetCurrentNew();   

Это делается с помощью CBrain. Помните, что CBrain – важное центральное звено в нашем объектно-ориентированном подходе, содержащее элементы для нормальной работы советника. Другими словами, это что-то вроде постоянного запоминающего устройства (ПЗУ).

//+------------------------------------------------------------------+
//|                                                       CBrain.mqh |
//|                               Copyright © 2013, Jordi Bassagañas |
//+------------------------------------------------------------------+
#include <..\Experts\NewsWatcher\CNewsContainer.mqh>
//+------------------------------------------------------------------+
//| Класс CBrain                                                     |
//+------------------------------------------------------------------+
class CBrain
  {
protected:
   double               m_size;                 // Размер позиций
   int                  m_stopLoss;             // Stop loss
   int                  m_takeProfit;           // Take profit
   CNewsContainer       *m_news_container;      // Новостной контейнер

public:
   //--- Конструктор и деструктор
                        CBrain(int stopLoss,int takeProfit,double size,string csv);
                        ~CBrain(void);
   //--- Геттеры
   double               GetSize(void);
   int                  GetStopLoss(void);
   int                  GetTakeProfit(void);
   CNewsContainer       *GetNewsContainer(void);
   //--- Сеттеры
   void                 SetSize(double size);
   void                 SetStopLoss(int stopLoss);
   void                 SetTakeProfit(int takeProfit);
   //--- Особые методы CBrain
   bool                 Init();
   void                 Deinit(void);
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CBrain::CBrain(int stopLoss,int takeProfit,double size,string csv)
  {
   m_size=size;
   m_stopLoss=stopLoss;
   m_takeProfit=takeProfit;
   m_news_container=new CNewsContainer(csv);   
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CBrain::~CBrain(void)
  {
   Deinit();
  }
//+------------------------------------------------------------------+
//| GetSize                                                          |
//+------------------------------------------------------------------+
double CBrain::GetSize(void)
  {
   return m_size;
  }
//+------------------------------------------------------------------+
//| GetStopLoss                                                      |
//+------------------------------------------------------------------+
int CBrain::GetStopLoss(void)
  {
   return m_stopLoss;
  }
//+------------------------------------------------------------------+
//| GetTakeProfit                                                    |
//+------------------------------------------------------------------+
int CBrain::GetTakeProfit(void)
  {
   return m_takeProfit;
  }
//+------------------------------------------------------------------+
//| GetNewsContainer                                                 |
//+------------------------------------------------------------------+
CNewsContainer *CBrain::GetNewsContainer(void)
  {
   return m_news_container;
  }
//+------------------------------------------------------------------+
//| SetSize                                                          |
//+------------------------------------------------------------------+
void CBrain::SetSize(double size)
  {
   m_size=size;
  }
//+------------------------------------------------------------------+
//| SetStopLoss                                                      |
//+------------------------------------------------------------------+
void CBrain::SetStopLoss(int stopLoss)
  {
   m_stopLoss=stopLoss;
  }
//+------------------------------------------------------------------+
//| SetTakeProfit                                                    |
//+------------------------------------------------------------------+
void CBrain::SetTakeProfit(int takeProfit)
  {
   m_takeProfit=takeProfit;
  }
//+------------------------------------------------------------------+
//| Инициализация CBrain                                             |
//+------------------------------------------------------------------+
bool CBrain::Init(void)
  {
// Здесь располагается логика инициализации...
   return true;
  }
//+------------------------------------------------------------------+
//| Деинициализация CBrain                                           |
//+------------------------------------------------------------------+
void CBrain::Deinit(void)
  {
   delete(m_news_container);
   Print("Деинициализация CBrain выполнена!");
  }
//+------------------------------------------------------------------+

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

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

Робот на кормовом поручне

Рис. 2. Робот на кормовом поручне. Изображение распространяется по лицензии Creative Commons


1.3. Объектно-ориентированный контейнер для технических индикаторов


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

На этом этапе мы можем обратиться к Стандартной библиотеке MQL5. Эта библиотека упрощает разработку программ (индикаторов, скриптов, советников), предоставляя удобный доступ к большинству внутренних функций MQL5. В нашем сегодняшнем упражнении мы отчасти используем функционал Стандартной библиотеки. Как уже было сказано, она весьма удобна с точки зрения ООП. Яркий пример – новостной контейнер, о котором я еще скажу позже. Мы используем в нем MQL5-класс CArrayObj , чтобы хранить в оперативной памяти компьютера наши пользовательские объектно-ориентированные новости сложного типа.

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

1.3.1. CTechIndicators, контейнер для технических индикаторов
//+------------------------------------------------------------------+
//|                                              CTechIndicators.mqh |
//|                               Copyright © 2013, Jordi Bassagañas |
//+------------------------------------------------------------------+
#include <..\Experts\NewsWatcher\CMomentum.mqh>
//+------------------------------------------------------------------+
//| Класс CTechIndicators                                            |
//+------------------------------------------------------------------+
class CTechIndicators
  {
protected:
   CMomentum               *m_momentum;
                  
public:
   //--- Конструктор и деструктор
                           CTechIndicators(void);
                           ~CTechIndicators(void);
   //--- Геттеры
   CMomentum               *GetMomentum(void);
   //--- Особые методы CTechIndicators
   bool                 Init();
   void                 Deinit(void);
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+   
CTechIndicators::CTechIndicators(void)
  {
   m_momentum = new CMomentum; 
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+               
CTechIndicators::~CTechIndicators(void)
  {
   Deinit();
  }
//+------------------------------------------------------------------+
//| GetMomentum                                                      |
//+------------------------------------------------------------------+        
CMomentum* CTechIndicators::GetMomentum(void)
  {
   return m_momentum;
  }
//+------------------------------------------------------------------+
//| Инициализация CTechIndicators                                    |
//+------------------------------------------------------------------+
bool CTechIndicators::Init(void)
  {
// Здесь располагается логика инициализации...
   return true;
  }
//+------------------------------------------------------------------+
//| Деинициализация CTechIndicators                                  |
//+------------------------------------------------------------------+
void CTechIndicators::Deinit(void)
  {
   delete(m_momentum);
   Print("Деинициализация CTechIndicators выполнена!");
  }
//+------------------------------------------------------------------+
1.3.2. CMomentum, объектно-ориентированная основа для iMomentum
//+------------------------------------------------------------------+
//|                                                    CMomentum.mqh |
//|                               Copyright © 2013, Jordi Bassagañas |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Класс CMomentum                                                  |
//+------------------------------------------------------------------+

class CMomentum
  {
protected:   
   int m_handler;
   double m_buffer[];
               
public:
   //--- Конструктор и деструктор
                           CMomentum(void);
                           ~CMomentum(void);
   //--- Геттеры
   int                     GetHandler(void);
   void                    GetBuffer(double &buffer[], int ammount);
   //--- Сеттеры
   bool                    SetHandler(string symbol,ENUM_TIMEFRAMES period,int mom_period,ENUM_APPLIED_PRICE mom_applied_price);
   bool                    UpdateBuffer(int ammount);
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+   
CMomentum::CMomentum(void)
  {
   ArraySetAsSeries(m_buffer, true);   
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+               
CMomentum::~CMomentum(void)
  {
   IndicatorRelease(m_handler);
   ArrayFree(m_buffer);
  }
//+------------------------------------------------------------------+
//| GetHandler                                                       |
//+------------------------------------------------------------------+        
int CMomentum::GetHandler(void)
  {
   return m_handler;
  }
//+------------------------------------------------------------------+
//| GetBuffer                                                        |
//+------------------------------------------------------------------+
void CMomentum::GetBuffer(double &buffer[], int ammount)
  {
   ArrayCopy(buffer, m_buffer, 0, 0, ammount);
  }
//+------------------------------------------------------------------+
//| SetHandler                                                       |
//+------------------------------------------------------------------+      
bool CMomentum::SetHandler(string symbol,ENUM_TIMEFRAMES period,int mom_period,ENUM_APPLIED_PRICE mom_applied_price)
  {   
   if((m_handler=iMomentum(symbol,period,mom_period,mom_applied_price))==INVALID_HANDLE)
   {
      printf("Ошибка при создании индикатора Momentum");
      return false;
   }
   return true;
  }
//+------------------------------------------------------------------+
//| UpdateBuffer                                                     |
//+------------------------------------------------------------------+   
bool CMomentum::UpdateBuffer(int ammount)
  {   
   if(CopyBuffer(m_handler, 0, 0, ammount, m_buffer) < 0)
   { 
      Alert("Ошибка при копировании буферов Momentum, ошибка: " , GetLastError());
      return false;
   }
   return true;
  }
//+------------------------------------------------------------------+


1.4. Объектно-ориентированный новостной контейнер

Короткие новостные заметки – ключевой элемент, с которым нашему советнику придется иметь дело. При заключении новости в объектно-ориентированный контейнер можно использовать аналогию с газетой. Проще говоря, объектно-ориентированный контейнер CNewsContainer и есть газета. Сами новости при этом названы CNew. Таков наш пользовательский объектно-ориентированный тип для представления новостей.

1.4.1. Новостной контейнер CNewsContainer

//+------------------------------------------------------------------+
//|                                               CNewsContainer.mqh |
//|                               Copyright © 2013, Jordi Bassagañas |
//+------------------------------------------------------------------+
#include <Files\FileTxt.mqh>
#include <Arrays\ArrayObj.mqh>
#include <..\Experts\NewsWatcher\CNew.mqh>
//+------------------------------------------------------------------+
//| Класс CNewsContainer                                             |
//+------------------------------------------------------------------+
class CNewsContainer
  {
protected:
   string               m_csv;                  // Имя csv-файла
   CFileTxt             m_fileTxt;              // Функционал файла MQL5
   int                  m_currentIndex;         // Индекс следующих новостей, предназначенных для обработки в контейнере
   int                  m_total;                // Общее число новостей, предназначенных для обработки
   CArrayObj            *m_news;                // Список новостей в памяти компьютера, загруженный из csv-файла

public:
   //--- Конструктор и деструктор
                        CNewsContainer(string csv);
                        ~CNewsContainer(void);
   //--- Геттеры
   int                  GetCurrentIndex(void);
   int                  GetTotal(void);
   CNew                 *GetCurrentNew();
   CArrayObj            *GetNews(void);
   //--- Сеттеры
   void                 SetCurrentIndex(int index);
   void                 SetTotal(int total);
   void                 SetNews(void);
   //--- Методы CNewsContainer
   bool                 Init();
   void                 Deinit(void);
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CNewsContainer::CNewsContainer(string csv)
  {
   m_csv=csv;
   m_news=new CArrayObj;
   SetNews();
   }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CNewsContainer::~CNewsContainer(void)
  {
   Deinit();
  }
//+------------------------------------------------------------------+
//| GetCurrentIndex                                                  |
//+------------------------------------------------------------------+
int CNewsContainer::GetCurrentIndex(void)
  {   
   return m_currentIndex;
  }
//+------------------------------------------------------------------+
//| GetTotal                                                         |
//+------------------------------------------------------------------+
int CNewsContainer::GetTotal(void)
  {   
   return m_total;
  }
//+------------------------------------------------------------------+
//| GetNews                                                          |
//+------------------------------------------------------------------+
CArrayObj *CNewsContainer::GetNews(void)
  {   
   return m_news;
  }
//+------------------------------------------------------------------+
//| GetCurrentNew                                                    |
//+------------------------------------------------------------------+
CNew *CNewsContainer::GetCurrentNew(void)
  {   
   return m_news.At(m_currentIndex);
  }
//+------------------------------------------------------------------+
//| SetCurrentIndex                                                  |
//+------------------------------------------------------------------+
void CNewsContainer::SetCurrentIndex(int index)
  {
   m_currentIndex=index;
  }
//+------------------------------------------------------------------+
//| SetTotal                                                         |
//+------------------------------------------------------------------+
void CNewsContainer::SetTotal(int total)
  {
   m_total=total;
  }
//+------------------------------------------------------------------+
//| SetNews                                                          |
//+------------------------------------------------------------------+
void CNewsContainer::SetNews(void)
  {
   //--- прежде всего инициализируем некоторые переменные!
   SetCurrentIndex(0);
   string sep= ";";
   ushort u_sep;
   string substrings[];   
   u_sep=StringGetCharacter(sep,0);   
   //--- затем откроем и обработаем CSV-файл
   int file_handle=m_fileTxt.Open(m_csv, FILE_READ|FILE_CSV);
   if(file_handle!=INVALID_HANDLE)
   {
      while(!FileIsEnding(file_handle))
      {               
         string line = FileReadString(file_handle);         
         int k = StringSplit(line,u_sep,substrings);         
         CNew *current = new CNew(substrings[0],(datetime)substrings[1],substrings[2]);         
         m_news.Add(current);
      }
      FileClose(file_handle);
      //--- и наконец обработаем и подсчитаем новости
      m_news.Delete(0); // --- здесь мы удаляем заголовок CSV!
      SetTotal(m_news.Total());
   }
   else
   {
      Print("Не удалось открыть файл ",m_csv);
      Print("Код ошибки ",GetLastError());
   }   
  }
//+------------------------------------------------------------------+
//| Инициализация CNewsContainer                                     |
//+------------------------------------------------------------------+
bool CNewsContainer::Init(void)
  {
// Здесь располагается логика инициализации...
   return true;
  }
//+------------------------------------------------------------------+
//| Деинициализация CNewsContainer                                   |
//+------------------------------------------------------------------+
void CNewsContainer::Deinit(void)
  {
   m_news.DeleteRange(0, m_total-1);
   delete(m_news);
   Print("Деинициализация CNewsContainer выполнена!");
  }
//+------------------------------------------------------------------+

SetNews является наиболее важным методом CNewsContainer. Данный метод считывает CSV-файл и загружает его в оперативную память компьютера в виде объектов типа CNew. Кстати, я до сих пор не упомянул о том, что CSV-файлы должны храниться в data_folder\MQL5\FILES\. Пожалуйста, ознакомьтесь с файловыми операциями для более глубокого понимания функций, используемых в SetNews.

1.4.2. CNew, новости

//+------------------------------------------------------------------+
//|                                                         CNew.mqh |
//|                               Copyright © 2013, Jordi Bassagañas |
//+------------------------------------------------------------------+
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Класс CNew                                                       |
//+------------------------------------------------------------------+
class CNew : public CObject
  {
protected:
   string            m_country;           // Страна
   datetime          m_time_release;      // Дата и время выхода новости
   string            m_name;              // Название новости
   
public:
   //--- Конструктор и деструктор
                     CNew(string country,datetime time_release,string name);
                    ~CNew(void);
   //--- Геттеры
   string            GetCountry(void);
   datetime          GetTimeRelease(void);
   string            GetName(void);
   //--- Сеттеры
   void              SetCountry(string country);
   void              SetTimeRelease(datetime time_release);
   void              SetName(string name);
   //--- Особые методы CNew
   bool              Init();
   void              Deinit(void);
  };
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CNew::CNew(string country,datetime time_release,string name)
  {
   m_country=country;
   m_time_release=time_release;
   m_name=name;
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CNew::~CNew(void)
  {
   Deinit();
  }
//+------------------------------------------------------------------+
//| GetCountry                                                       |
//+------------------------------------------------------------------+
string CNew::GetCountry(void)
  {
   return m_country;
  }  
//+------------------------------------------------------------------+
//| GetTimeRelease                                                   |
//+------------------------------------------------------------------+
datetime CNew::GetTimeRelease(void)
  {
   return m_time_release;
  }
//+------------------------------------------------------------------+
//| GetName                                                          |
//+------------------------------------------------------------------+
string CNew::GetName(void)
  {
   return m_name;
  }
//+------------------------------------------------------------------+
//| SetCountry                                                       |
//+------------------------------------------------------------------+
void CNew::SetCountry(string country)
  {
   m_country=country;
  }
//+------------------------------------------------------------------+
//| SetTimeRelease                                                   |
//+------------------------------------------------------------------+
void CNew::SetTimeRelease(datetime timeRelease)
  {
   m_time_release=timeRelease;
  }
//+------------------------------------------------------------------+
//| SetName                                                          |
//+------------------------------------------------------------------+
void CNew::SetName(string name)
  {
   m_name=name;
  }
//+------------------------------------------------------------------+
//| Инициализация CNew                                               |
//+------------------------------------------------------------------+
bool CNew::Init(void)
  {
//--- Здесь располагается логика инициализации...
   return true;
  }
//+------------------------------------------------------------------+
//| Деинициализация CNew                                             |
//+------------------------------------------------------------------+
void CNew::Deinit(void)
  {
//--- Здесь располагается логика деинициализации...
   Print("Деинициализация CNew выполнена!");
  }
//+------------------------------------------------------------------+


2. Тестирование ExpertNewsWatcher.mq5 на истории


2.1. Приложения

ExpertNewsWatcher состоит из следующих файлов:

2.2. Установка

Прежде всего необходимо создать папку MQL5\Include\Mine для хранения пользовательских элементов, а затем скопировать туда файл Enums.mqh. Сразу после этого необходимо создать папку MQL5\Experts\NewsWatcher и скопировать туда файлы, указанные ниже:

Внимание! После перечисленных действий переименуйте файл news_watcher.txt, в news_watcher.csv и положите его в папку data_folder\MQL5\FILES\. На момент публикации присоединение csv-файлов в качестве приложений к статье невозможно, но можно прикладывать txt-файлы.

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


2.3. Результаты тестирования на истории

ExpertNewsWatcher запускался со следующими входными параметрами.

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

Примеры фиктивных данных для использования в news_watcher.csv:

Country;Time;Event
USD;2013.06.03 17:19:00;A. Momentum equals 100.47
USD;2013.06.13 17:09:00;B. Momentum equals 100.40
USD;2013.06.21 18:52:00;C. Momentum equals 100.19
USD;2013.07.01 17:32:00;D. Momentum equals 100.18 
USD;2013.07.08 15:17:00;E. Momentum equals 100.18
USD;2013.07.16 10:00:00;F. Momentum equals 99.81
USD;2013.07.24 09:30:00;G. Momentum equals 100.25


Результаты, полученные при использовании фиктивных данных

Рис. 3. Результаты, полученные при использовании фиктивных данных

Указанный график с фиктивными новостями дает представление о том, как данный робот может вести себя в реальных условиях. Поместите следующие реальные данные, взятые на DailyFX, в news_watcher.csv и снова запустите ExpertNewsWatcher.

Примеры реальных данных для использования в news_watcher.csv:

Country;Time;Event
USD;2013.07.15 12:00:00;USD Fed's Tarullo Speaks on Banking Regulation in Washington 
USD;2013.07.15 12:30:00;USD Advance Retail Sales (JUN) and others
USD;2013.07.15 14:00:00;USD USD Business Inventories (MAY)
USD;2013.07.15 21:00:00;USD EIA Gasoline and Diesel Fuel Update
USD;2013.07.16 12:30:00;USD Several Consumer Price Indexes 
USD;2013.07.16 13:00:00;USD USD Net Long-term TIC Flows (MAY) & USD Total Net TIC Flows (MAY)
USD;2013.07.16 13:15:00;USD Industrial Production (JUN) and others
USD;2013.07.16 14:00:00;USD NAHB Housing Market Index (JUL)
USD;2013.07.16 18:15:00;USD Fed's George Speaks on Economic Conditions and Agriculture
USD;2013.07.22 12:30:00;USD Chicago Fed Nat Activity Index (JUN)
USD;2013.07.22 14:00:00;USD Existing Home Sales (MoM) (JUN) & Existing Home Sales (JUN)
USD;2013.07.22 21:00:00;USD EIA Gasoline and Diesel Fuel Update
USD;2013.07.23 13:00:00;USD House Price Index (MoM) (MAY)
USD;2013.07.23 14:00:00;USD Richmond Fed Manufacturing Index (JUL)
USD;2013.07.24 11:00:00;USD MBA Mortgage Applications (JUL 19)
USD;2013.07.24 12:58:00;USD Markit US PMI Preliminary (JUL)
USD;2013.07.24 14:00:00;USD USD New Home Sales (MoM) (JUN) & USD New Home Sales (JUN)
USD;2013.07.24 14:30:00;USD USD DOE U.S. Crude Oil Inventories (JUL 19) and others



Результаты, полученные при использовании реальных данных

Рис. 4. Результаты, полученные при использовании реальных данных

Этот простой обработчик новостей может работать одновременно только с одной новостью, выходящей в определенное время. По этой причине конкретное время, например, 2013.07.15 12:30:00, может содержать несколько новостей. Если в конкретный момент времени выходят несколько важных новостей, сделайте одну запись в CSV-файле.

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

       double momentumBuffer[];
      
      GetTechIndicators().GetMomentum().GetBuffer(momentumBuffer, 2);
      
      //--- Количество секунд перед выходом новостей. GMT +- timeWindow – фактическое время, с которого робот начинает 
      //--- следить за рынком. Например, если выход новостей ожидается в 13:00 по Гринвичу, вы можете установить TimeWindow 
      //--- на 900 секунд. Таким образом, советник начинает следить за рынком за пятнадцать минут до выхода новостей. 
      int timeWindow=600;
      
      CNew *currentNew = GetBrain().GetNewsContainer().GetCurrentNew();      
      int indexCurrentNew = GetBrain().GetNewsContainer().GetCurrentIndex();
            
      if(TimeGMT() >= currentNew.GetTimeRelease() + timeWindow)
      {
         GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+1);
         return;
      }
      
      //--- имеется ли открытая позиция?
      if(!m_positionInfo.Select(_Symbol))
      {
         //--- если открытой позиции нет, открываем ее
         bool timeHasCome = TimeGMT() >= currentNew.GetTimeRelease() - timeWindow && TimeGMT() <= currentNew.GetTimeRelease() + timeWindow;
             
         if(timeHasCome && momentumBuffer[0] > 100.10)
         {
            GetEvolution().SetStatus(SELL);
            GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+1);
         }
         else if(timeHasCome && momentumBuffer[0] < 99.90)
         {
            GetEvolution().SetStatus(BUY);
            GetBrain().GetNewsContainer().SetCurrentIndex(indexCurrentNew+1);
         }
      }
      //--- если имеется открытая позиция, ждем отработки мат. ожидания
      else 
      {
         GetEvolution().SetStatus(DO_NOTHING);         
      }  


Заключение

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