English 中文 Español Deutsch 日本語 Português
Работа с ценами в библиотеке DoEasy (Часть 62): Реалтайм-обновление тиковых серий, подготовка к работе со стаканом цен

Работа с ценами в библиотеке DoEasy (Часть 62): Реалтайм-обновление тиковых серий, подготовка к работе со стаканом цен

MetaTrader 5Примеры | 29 января 2021, 08:02
1 685 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

При этом, каждый новый тик будет увеличивать количество хранимых объектов в коллекции. Чтобы ограничить их количество, а соответственно — и количество используемой памяти, мы введём константу, в которой можно задать максимально возможное количество тиков, хранимое в базе библиотеки по одному инструменту. Это обезопасит нас от нехватки памяти. Если в программе используется много инструментов и если тиков в базе накопилось уже достаточное количество, то библиотека автоматически удалит нужное количество самых старых тиков. Таким образом, мы всегда будем иметь заданное количество тиков по инструменту. По умолчанию — 200 000. Такого количества должно хватить для проведения статистических исследований примерно за двое последних суток. В любом случае, максимальный размер количества хранимых в коллекции тиков по одному инструменту всегда можно изменить под свои нужды.

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


Доработка классов библиотеки

Как уже стало принятым, начнём с добавления новых текстовых сообщений библиотеки.
В файле \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений:

   MSG_SYM_EVENT_SYMBOL_ADD,                          // В окно "Обзор рынка" добавлен символ
   MSG_SYM_EVENT_SYMBOL_DEL,                          // Из окна "Обзор рынка" удалён символ
   MSG_SYM_EVENT_SYMBOL_SORT,                         // Изменено расположение символов в окне "Обзор рынка"
   MSG_SYM_SYMBOLS_MODE_CURRENT,                      // Работа только с текущим символом
   MSG_SYM_SYMBOLS_MODE_DEFINES,                      // Работа с предопределённым списком символов
   MSG_SYM_SYMBOLS_MODE_MARKET_WATCH,                 // Работа с символами из окна "Обзор рынка"
   MSG_SYM_SYMBOLS_MODE_ALL,                          // Работа с полным списком всех доступных символов
   MSG_SYM_SYMBOLS_BOOK_ADD,                          // Осуществлена подписка на стакан цен 
   MSG_SYM_SYMBOLS_BOOK_DEL,                          // Осуществлена отписка от стакан цен 
   MSG_SYM_SYMBOLS_MODE_BOOK,                         // Подписка на стакан цен
   
//--- CAccount

и текстовые сообщения, соответствующие вновь добавленным индексам:

   {"В окно \"Обзор рынка\" добавлен символ","Added a symbol to the \"Market Watch\" window"},
   {"Из окна \"Обзор рынка\" удалён символ","From the \"Market Watch\" window was removed"},
   {"Изменено расположение символов в окне \"Обзор рынка\"","Changed the arrangement of symbols in the \"Market Watch\" window"},
   {"Работа только с текущим символом","Work only with the current symbol"},
   {"Работа с предопределённым списком символов","Work with a predefined list of symbols"},
   {"Работа с символами из окна \"Обзор рынка\"","Working with symbols from the \"Market Watch\" window"},
   {"Работа с полным списком всех доступных символов","Work with the full list of all available symbols"},
   {"Осуществлена подписка на стакан цен ","Subscribed to Depth of Market"},
   {"Осуществлена отписка от стакан цен ","Unsubscribed from Depth of Market"},
   {"Подписка на стакан цен","Subscription to Depth of Market"},
   
//--- CAccount


Так как мы сегодня будем делать реалтайм-обновление коллекции тиковых данных, то уточним как это будет происходить: при поступлении нового тика на текущем символе, нам нужно получить этот тик в структуру MqlTick, на её основании создать новый объект-тик и добавить его в список тиковой серии, хранящийся в коллекции наряду со списками других символов. Но тики по другим символам мы не можем получить в обработчике OnTick() программы — этот обработчик срабатывает при поступлении очередного тика на текущем символе. Поэтому для получения новых тиков по другим используемым символам, нам их необходимо контролировать в таймере библиотеки при помощи ранее созданного объекта класса "Новый тик". Для этого нам нужен ещё один таймер в библиотеке, в котором будут отслеживаться тики по всем инструментам кроме текущего для обновления списков тиковых данных этих инструментов.

В файле \MQL5\Include\DoEasy\Defines.mqh добавим параметры таймера коллекции тиковых данных и константу для указания максимально возможного количества объектов-тиков по одному символу:

//--- Параметры таймера коллекции таймсерий индикаторных данных
#define COLLECTION_IND_TS_PAUSE        (64)                       // Пауза таймера коллекции таймсерий индикаторных данных в миллисекундах
#define COLLECTION_IND_TS_COUNTER_STEP (16)                       // Шаг приращения счётчика таймера таймсерий индикаторных данных
#define COLLECTION_IND_TS_COUNTER_ID   (7)                        // Идентификатор счётчика таймера таймсерий индикаторных данных
//--- Параметры таймера коллекции тиковых серий
#define COLLECTION_TICKS_PAUSE         (64)                       // Пауза таймера коллекции тиковых серий в миллисекундах
#define COLLECTION_TICKS_COUNTER_STEP  (16)                       // Шаг приращения счётчика таймера тиковых серий
#define COLLECTION_TICKS_COUNTER_ID    (8)                        // Идентификатор счётчика таймера тиковых серий
//--- Идентификаторы списков коллекций
#define COLLECTION_HISTORY_ID          (0x777A)                   // Идентификатор списка исторической коллекции
#define COLLECTION_MARKET_ID           (0x777B)                   // Идентификатор списка рыночной коллекции
#define COLLECTION_EVENTS_ID           (0x777C)                   // Идентификатор списка коллекции событий
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Идентификатор списка коллекции аккаунтов
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Идентификатор списка коллекции символов
#define COLLECTION_SERIES_ID           (0x777F)                   // Идентификатор списка коллекции таймсерий
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Идентификатор списка коллекции индикаторных буферов
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Идентификатор списка коллекции индикаторов
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Идентификатор списка коллекции индикаторных данных
#define COLLECTION_TICKSERIES_ID       (0x7783)                   // Идентификатор списка коллекции тиковых серий
//--- Параметры данных для файловых операций
#define DIRECTORY                      ("DoEasy\\")               // Каталог библиотеки для расположения папок объектов классов
#define RESOURCE_DIR                   ("DoEasy\\Resource\\")     // Каталог библиотеки для расположения папок ресурсов
//--- Параметры символов
#define CLR_DEFAULT                    (0xFF000000)               // Цвет фона символа в навигаторе по умолчанию
#ifdef __MQL5__
   #define SYMBOLS_COMMON_TOTAL        (TerminalInfoInteger(TERMINAL_BUILD)<2430 ? 1000 : 5000)   // Общее количество рабочих символов MQL5
#else 
   #define SYMBOLS_COMMON_TOTAL        (1000)                     // Общее количество рабочих символов MQL4
#endif 
//--- Идентификаторы типов отложенных запросов
#define PENDING_REQUEST_ID_TYPE_ERR    (1)                        // Тип отложенного запроса, созданного по коду возврата сервера
#define PENDING_REQUEST_ID_TYPE_REQ    (2)                        // Тип отложенного запроса, созданного по запросу
//--- Параметры таймсерий
#define SERIES_DEFAULT_BARS_COUNT      (1000)                     // Требуемое количество данных таймсерий по умолчанию
#define PAUSE_FOR_SYNC_ATTEMPTS        (16)                       // Количество миллисекунд паузы между попытками синхронизации
#define ATTEMPTS_FOR_SYNC              (5)                        // Количество попыток получения факта синхронизации с сервером
//--- Параметры тиковых серий
#define TICKSERIES_DEFAULT_DAYS_COUNT  (1)                        // Требуемое количество дней для тиковых данных в сериях по умолчанию
#define TICKSERIES_MAX_DATA_TOTAL      (200000)                   // Максимальное количество хранимых тиковых данных одного символа
//+------------------------------------------------------------------+

Чтобы иметь возможность понимать, подписаны ли мы на трансляцию стакана цен по символу, нам нужно добавить к свойствам символа параметр, указывающий на состояние подписки. Для этого добавим к целочисленным свойствам символа ещё один параметр и увеличим количество целочисленных свойств с 36 до 37:

//+------------------------------------------------------------------+
//| Целочисленные свойства символа                                   |
//+------------------------------------------------------------------+
enum ENUM_SYMBOL_PROP_INTEGER
  {
   SYMBOL_PROP_STATUS = 0,                                  // Статус символа
   SYMBOL_PROP_INDEX_MW,                                    // Индекс символа в окне "Обзор рынка"
   SYMBOL_PROP_CUSTOM,                                      // Признак того, что символ является пользовательским
   SYMBOL_PROP_CHART_MODE,                                  // Тип цены для построения баров – Bid или Last (из перечисления ENUM_SYMBOL_CHART_MODE)
   SYMBOL_PROP_EXIST,                                       // Признак того, что символ с таким именем существует
   SYMBOL_PROP_SELECT,                                      // Признак того, что символ выбран в Market Watch
   SYMBOL_PROP_VISIBLE,                                     // Признак того, что выбранный символ отображается в Market Watch
   SYMBOL_PROP_SESSION_DEALS,                               // Количество сделок в текущей сессии 
   SYMBOL_PROP_SESSION_BUY_ORDERS,                          // Общее число ордеров на покупку в текущий момент
   SYMBOL_PROP_SESSION_SELL_ORDERS,                         // Общее число ордеров на продажу в текущий момент
   SYMBOL_PROP_VOLUME,                                      // Volume - объем в последней сделке
   SYMBOL_PROP_VOLUMEHIGH,                                  // Максимальный Volume за день
   SYMBOL_PROP_VOLUMELOW,                                   // Минимальный Volume за день
   SYMBOL_PROP_TIME,                                        // Время последней котировки
   SYMBOL_PROP_DIGITS,                                      // Количество знаков после запятой
   SYMBOL_PROP_DIGITS_LOTS,                                 // Количество знаков после запятой для лота
   SYMBOL_PROP_SPREAD,                                      // Размер спреда в пунктах
   SYMBOL_PROP_SPREAD_FLOAT,                                // Признак плавающего спреда
   SYMBOL_PROP_TICKS_BOOKDEPTH,                             // Максимальное количество показываемых заявок в стакане
   SYMBOL_PROP_BOOKDEPTH_STATE,                             // Признак подписки на стакан
   SYMBOL_PROP_TRADE_CALC_MODE,                             // Способ вычисления стоимости контракта (из перечисления ENUM_SYMBOL_CALC_MODE)
   SYMBOL_PROP_TRADE_MODE,                                  // Тип исполнения ордеров (из перечисления ENUM_SYMBOL_TRADE_MODE)
   SYMBOL_PROP_START_TIME,                                  // Дата начала торгов по инструменту (обычно используется для фьючерсов)
   SYMBOL_PROP_EXPIRATION_TIME,                             // Дата окончания торгов по инструменту (обычно используется для фьючерсов)
   SYMBOL_PROP_TRADE_STOPS_LEVEL,                           // Минимальный отступ в пунктах от текущей цены закрытия для установки Stop ордеров
   SYMBOL_PROP_TRADE_FREEZE_LEVEL,                          // Дистанция заморозки торговых операций (в пунктах)
   SYMBOL_PROP_TRADE_EXEMODE,                               // Режим заключения сделок (из перечисления ENUM_SYMBOL_TRADE_EXECUTION)
   SYMBOL_PROP_SWAP_MODE,                                   // Модель расчета свопа (из перечисления ENUM_SYMBOL_SWAP_MODE)
   SYMBOL_PROP_SWAP_ROLLOVER3DAYS,                          // День недели для начисления тройного свопа (из перечисления ENUM_DAY_OF_WEEK)
   SYMBOL_PROP_MARGIN_HEDGED_USE_LEG,                       // Режим расчета хеджированной маржи по наибольшей стороне (Buy или Sell)
   SYMBOL_PROP_EXPIRATION_MODE,                             // Флаги разрешенных режимов истечения ордера
   SYMBOL_PROP_FILLING_MODE,                                // Флаги разрешенных режимов заливки ордера
   SYMBOL_PROP_ORDER_MODE,                                  // Флаги разрешенных типов ордера
   SYMBOL_PROP_ORDER_GTC_MODE,                              // Срок действия StopLoss и TakeProfit ордеров, если SYMBOL_EXPIRATION_MODE=SYMBOL_EXPIRATION_GTC (из перечисления ENUM_SYMBOL_ORDER_GTC_MODE)
   SYMBOL_PROP_OPTION_MODE,                                 // Тип опциона (из перечисления ENUM_SYMBOL_OPTION_MODE)
   SYMBOL_PROP_OPTION_RIGHT,                                // Право опциона (Call/Put) (из перечисления ENUM_SYMBOL_OPTION_RIGHT)
   //--- пропускаемое свойство
   SYMBOL_PROP_BACKGROUND_COLOR                             // Цвет фона, которым подсвечивается символ в Market Watch
  }; 
#define SYMBOL_PROP_INTEGER_TOTAL    (37)                   // Общее количество целочисленных свойств
#define SYMBOL_PROP_INTEGER_SKIP     (1)                    // Количество неиспользуемых в сортировке целочисленных свойств символа
//+------------------------------------------------------------------+

В перечислениях возможных критериев сортировки объектов-символов, добавим сортировку по новому целочисленному свойству:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки символов                           |
//+------------------------------------------------------------------+
#define FIRST_SYM_DBL_PROP          (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP)
#define FIRST_SYM_STR_PROP          (SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_INTEGER_SKIP+SYMBOL_PROP_DOUBLE_TOTAL-SYMBOL_PROP_DOUBLE_SKIP)
enum ENUM_SORT_SYMBOLS_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_SYMBOL_STATUS = 0,                               // Сортировать по статусу символа
   SORT_BY_SYMBOL_INDEX_MW,                                 // Сортировать по индексу в окне "Обзор рынка"
   SORT_BY_SYMBOL_CUSTOM,                                   // Сортировать по признаку пользовательского символа
   SORT_BY_SYMBOL_CHART_MODE,                               // Сортировать по типу цены для построения баров – Bid или Last (из перечисления ENUM_SYMBOL_CHART_MODE)
   SORT_BY_SYMBOL_EXIST,                                    // Сортировать по признаку того, что символ с таким именем существует
   SORT_BY_SYMBOL_SELECT,                                   // Сортировать по признаку того, что символ выбран в Market Watch
   SORT_BY_SYMBOL_VISIBLE,                                  // Сортировать по признаку того, что выбранный символ отображается в Market Watch
   SORT_BY_SYMBOL_SESSION_DEALS,                            // Сортировать по количеству сделок в текущей сессии 
   SORT_BY_SYMBOL_SESSION_BUY_ORDERS,                       // Сортировать по общему числу ордеров на покупку в текущий момент
   SORT_BY_SYMBOL_SESSION_SELL_ORDERS,                      // Сортировать по общему числу ордеров на продажу в текущий момент
   SORT_BY_SYMBOL_VOLUME,                                   // Сортировать по Volume - объему в последней сделке
   SORT_BY_SYMBOL_VOLUMEHIGH,                               // Сортировать по максимальному Volume за день
   SORT_BY_SYMBOL_VOLUMELOW,                                // Сортировать по минимальному Volume за день
   SORT_BY_SYMBOL_TIME,                                     // Сортировать по времени последней котировки
   SORT_BY_SYMBOL_DIGITS,                                   // Сортировать по количеству знаков после запятой
   SORT_BY_SYMBOL_DIGITS_LOT,                               // Сортировать по количеству знаков после запятой в лоте
   SORT_BY_SYMBOL_SPREAD,                                   // Сортировать по размеру спреда в пунктах
   SORT_BY_SYMBOL_SPREAD_FLOAT,                             // Сортировать по признаку плавающего спреда
   SORT_BY_SYMBOL_TICKS_BOOKDEPTH,                          // Сортировать по максимальному количеству показываемых заявок в стакане
   SORT_BY_SYMBOL_BOOKDEPTH_STATE,                          // Сортировать по признаку подписки на стакан цен
   SORT_BY_SYMBOL_TRADE_CALC_MODE,                          // Сортировать по способу вычисления стоимости контракта (из перечисления ENUM_SYMBOL_CALC_MODE)
   SORT_BY_SYMBOL_TRADE_MODE,                               // Сортировать по типу исполнения ордеров (из перечисления ENUM_SYMBOL_TRADE_MODE)
   SORT_BY_SYMBOL_START_TIME,                               // Сортировать по дате начала торгов по инструменту (обычно используется для фьючерсов)
   SORT_BY_SYMBOL_EXPIRATION_TIME,                          // Сортировать по дате окончания торгов по инструменту (обычно используется для фьючерсов)
   SORT_BY_SYMBOL_TRADE_STOPS_LEVEL,                        // Сортировать по минимальному отступу в пунктах от текущей цены закрытия для установки Stop ордеров
   SORT_BY_SYMBOL_TRADE_FREEZE_LEVEL,                       // Сортировать по дистанции заморозки торговых операций (в пунктах)
   SORT_BY_SYMBOL_TRADE_EXEMODE,                            // Сортировать по режиму заключения сделок (из перечисления ENUM_SYMBOL_TRADE_EXECUTION)
   SORT_BY_SYMBOL_SWAP_MODE,                                // Сортировать по модели расчета свопа (из перечисления ENUM_SYMBOL_SWAP_MODE)
   SORT_BY_SYMBOL_SWAP_ROLLOVER3DAYS,                       // Сортировать по дню недели для начисления тройного свопа (из перечисления ENUM_DAY_OF_WEEK)
   SORT_BY_SYMBOL_MARGIN_HEDGED_USE_LEG,                    // Сортировать по режиму расчета хеджированной маржи по наибольшей стороне (Buy или Sell)
   SORT_BY_SYMBOL_EXPIRATION_MODE,                          // Сортировать по флагам разрешенных режимов истечения ордера
   SORT_BY_SYMBOL_FILLING_MODE,                             // Сортировать по флагам разрешенных режимов заливки ордера
   SORT_BY_SYMBOL_ORDER_MODE,                               // Сортировать по флагам разрешенных типов ордера
   SORT_BY_SYMBOL_ORDER_GTC_MODE,                           // Сортировать по сроку действия StopLoss и TakeProfit ордеров
   SORT_BY_SYMBOL_OPTION_MODE,                              // Сортировать по типу опциона (из перечисления ENUM_SYMBOL_OPTION_MODE)
   SORT_BY_SYMBOL_OPTION_RIGHT,                             // Сортировать по праву опциона (Call/Put) (из перечисления ENUM_SYMBOL_OPTION_RIGHT)
//--- Сортировка по вещественным свойствам


Обновление тиковых серий

Так как тики могут приходить одновременно сразу пачкой, то мы не сможем их поочерёдно, тик за тиком, добавлять в список тиковой серии. Нам, чтобы сохранять все пришедшие одной пачкой тики, нужно контролировать миллисекундное время последнего полученного тика и копировать тики, начиная от этого времени до самого конца исторических данных. После копирования всех вновь пришедших тиков (а это может быть как один тик, так и сразу несколько в одной пачке), нам нужно сохранить время последнего тика. Чтобы на следующем срабатывании OnTick() начать копировать тики от этого времени + 1 миллисекунда (чтобы прошлый последний тик повторно не копировать) до самого конца исторических данных — до текущего времени. Таким образом, мы всегда сможем на каждом новом срабатывании OnTick() получать все необходимые данные, которые появились с приходом нового тика, а по завершении копирования запоминать новое время последнего тика для последующего копирования.

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

В файле \MQL5\Include\DoEasy\Objects\Ticks\TickSeries.mqh в приватной секции класса объявим переменную-член класса для хранения миллисекундного времени последнего тика и метод для создания нового объекта-тика и добавления его в список-тиковую серию:

//+------------------------------------------------------------------+
//| Класс "Серия тиковых данных"                                     |
//+------------------------------------------------------------------+
class CTickSeries : public CBaseObj
  {
private:
   string            m_symbol;                                          // Символ
   ulong             m_last_time;                                       // Время последнего тика
   uint              m_amount;                                          // Количество используемых данных тиковой серии
   uint              m_required;                                        // Требуемое количество дней для данных тиковой серии
   CArrayObj         m_list_ticks;                                      // Список тиковых данных
   CNewTickObj       m_new_tick_obj;                                    // Объект "Новый тик"
//--- Создаёт новый объект тиковых данных
   CDataTick        *CreateNewTickObj(const MqlTick &tick);

public:

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

//--- Возвращает объект тиковых данных по (1) индексу в списке, (2) по времени,
//--- (3) по времени в миллисекундах, (4) последний в списке, (4) размер списка
   CDataTick        *GetTickByListIndex(const uint index);
   CDataTick        *GetTick(const datetime time); 
   CDataTick        *GetTick(const ulong time_msc); 
   CDataTick        *GetLastTick(void); 
   int               DataTotal(void)                              const { return this.m_list_ticks.Total();       }

//--- Метод сравнения для поиска по символу одинаковых объектов тиковых серий

Так как нам потребуется использовать объект "Новый тик", то для правильной его работы нам необходимо указать, с каким символом ему работать.
Сделаем это в конструкторе класса:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CTickSeries::CTickSeries(const string symbol,const uint required=0) : m_symbol(symbol),m_last_time(0)
  {
   this.m_list_ticks.Clear();
   this.m_list_ticks.Sort(SORT_BY_TICK_TIME_MSC);
   this.SetRequiredUsedDays(required);
   this.m_new_tick_obj.SetSymbol(this.m_symbol);
   this.m_new_tick_obj.Refresh();
  }
//+------------------------------------------------------------------+

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

Метод, создающий новый объект тиковых данных и помещающий его в список:

//+------------------------------------------------------------------+
//| Создаёт новый объект тиковых данных                              |
//+------------------------------------------------------------------+
CDataTick *CTickSeries::CreateNewTickObj(const MqlTick &tick)
  {
   //--- создаём новый объект тиковых данных из данных структуры MqlTick, переданной в метод
   int err=ERR_SUCCESS;
   ::ResetLastError();
   //--- Если объект создать не удалось - сообщаем об этом и возвращаем NULL
   CDataTick* tick_obj=new CDataTick(this.m_symbol,tick);
   if(tick_obj==NULL)
     {
      ::Print
        (
         DFUN,CMessage::Text(MSG_TICKSERIES_FAILED_CREATE_TICK_DATA_OBJ)," ",this.Header()," ",::TimeMSCtoString(tick.time_msc),". ",
         CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError())
        );
      return NULL;
     }
   //--- Если не удалось добавить новый объект тиковых данных в список
   //--- выводим об этом сообщение в журнал с описанием ошибки,
   //--- удаляем вновь созданный объект и возвращаем NULL
   this.m_list_ticks.Sort();
   if(!this.m_list_ticks.InsertSort(tick_obj))
     {
      err=::GetLastError();
      ::Print(DFUN,CMessage::Text(MSG_TICKSERIES_FAILED_ADD_TO_LIST)," ",tick_obj.Header()," ",
                   CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
      delete tick_obj;
      return NULL;
     }
   //--- Возвращаем указатель на созданный и добавленный в список объект тиковых данных
   return tick_obj;
  }
//+------------------------------------------------------------------+

В методе вся его логика расписана в комментариях. Именно этот блок кода и был перенесён в этот новый метод из метода создания списка тиковой серии, сделанный нами в прошлой статье. Сейчас в этот метод передаётся структура тика, по данным которой необходимо создать новый объект тиковых данных. А по завершении его создания и добавления его в список, возвращается указатель на этот объект, либо NULL — если объект не удалось создать или добавить в список.

Метод, создающий список-серию тиковых данных:

//+------------------------------------------------------------------+
//| Создаёт список-серию тиковых данных                              |
//+------------------------------------------------------------------+
int CTickSeries::Create(const uint required=0)
  {
//--- Если тиковая серия не используется - сообщаем об этом и выходим
   if(!this.m_available)
     {
      ::Print(DFUN,this.m_symbol,": ",CMessage::Text(MSG_TICKSERIES_TEXT_IS_NOT_USE));
      return false;
     }
//--- Объявим массив ticks[], в который будем получать исторические тиковые данные,
//--- очистим список объектов тиковых данных и установим ему флаг сортировки по времени в миллисекундах
   MqlTick ticks_array[];
   this.m_list_ticks.Clear();
   this.m_list_ticks.Sort(SORT_BY_TICK_TIME_MSC);
   this.m_last_time=0;
   ::ResetLastError();
   int err=ERR_SUCCESS;
//--- Рассчитаем время начала дня в миллисекундах, от которого необходимо скопировать тики
   MqlDateTime date_str={0};
   datetime date=::iTime(m_symbol,PERIOD_D1,this.m_required);
   ::TimeToStruct(date,date_str);
   date_str.hour=date_str.min=date_str.sec=0;
   date=::StructToTime(date_str);
   long date_from=(long)date*1000;
   if(date_from<1) date_from=1;
//--- Получим в массив tick[] исторические данные структуры MqlTick,
//--- от рассчитанной даты до текущего времени и сохраним полученное количество в m_amount.
//--- Если получить данные не удалось - выводим об этом сообщение и возвращаем ноль
   this.m_amount=::CopyTicksRange(m_symbol,ticks_array,COPY_TICKS_ALL,date_from);
   if(this.m_amount<1)
     {
      err=::GetLastError();
      ::Print(DFUN,CMessage::Text(MSG_TICKSERIES_ERR_GET_TICK_DATA),": ",CMessage::Text(err),CMessage::Retcode(err));
      return 0;
     }
//--- Исторические данные получены в массив ticks[]
//--- В цикле по массиву ticks[]
   for(int i=0; i<(int)this.m_amount; i++)
     {
      //--- Создаём объект-тик и добавляем его в список
      CDataTick *tick_obj=this.CreateNewTickObj(ticks_array[i]);
      if(tick_obj==NULL)
         continue;
      //--- Если время тика больше прошлого, записываем в m_last_time новое время тика как стартовое
      //--- для копирования вновь поступивих тиков в методе обновления тиковой серии
      if(this.m_last_time<(ulong)tick_obj.TimeMSC())
         this.m_last_time=tick_obj.TimeMSC();
     }
//--- Возвращаем размер созданного списка объектов-тиков
   return this.m_list_ticks.Total();
  }
//+------------------------------------------------------------------+

Метод мы писали в статье, посвящённой созданию тиковых серий, а здесь его видоизменили так, что теперь создание и добавление нового объекта тиковых данных мы осуществляем при помощи нового метода, рассмотренного нами выше. А после успешного добавления объекта в список сохраняем время последнего тика для последующего его использования в методе обновления тиковой серии.

Метод для обновления тиковой серии:

//+------------------------------------------------------------------+
//| Обновляет список-тиковую серию                                   |
//+------------------------------------------------------------------+
void CTickSeries::Refresh(void)
  {
   MqlTick ticks_array[];
   if(IsNewTick())
     {
      //--- Копируем тики от времени m_last_time+1 мсек до конца истории
      int err=ERR_SUCCESS;
      int total=::CopyTicksRange(this.Symbol(),ticks_array,COPY_TICKS_ALL,this.m_last_time+1,0);
      //--- Если тики скопированы, в цикле по их количеству создаём новые объекты тиковых данных и добавляем их в список
      if(total>0)
        {
         for(int i=0;i<total;i++)
           {
            //--- Создаём объект-тик и добавляем его в список
            CDataTick *tick_obj=this.CreateNewTickObj(ticks_array[i]);
            if(tick_obj==NULL)
               break;
            //--- Записываем время последнего тика для последующего копирования вновь поступивших тиков
            long end_time=ticks_array[::ArraySize(ticks_array)-1].time_msc;
            if(this.Symbol()=="AUDUSD")
               Comment(DFUN,this.Symbol(),", copied=",total,", m_last_time=",TimeMSCtoString(m_last_time),", end_time=",TimeMSCtoString(end_time),", total=",DataTotal());
            this.m_last_time=end_time;
           }
         //--- Если количество тиков в списке превысило заданное по умолчанию максимальное их количество -
         //--- удаляем рассчитанное количество объектов-тиков с конца списка
         if(this.DataTotal()>TICKSERIES_MAX_DATA_TOTAL)
           {
            int total_del=m_list_ticks.Total()-TICKSERIES_MAX_DATA_TOTAL;
            for(int j=0;j<total_del;j++)
               this.m_list_ticks.Delete(j);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Здесь всё просто — у нас есть время последнего тика, зафиксированное на прошлом срабатывании OnTick(), хранящееся в переменной m_last_time. Теперь, чтобы начать копировать новые тики, нам нужно к этому времени прибавить одну миллисекунду, так как функция CopyTicksRange() копирует от указанного времени, включая это время. Именно для того, чтобы избавиться от включения в список-серию уже скопированного прошлого объекта-тика в копируемые тики на новом срабатывании OnTick(), мы и начинаем копировать не от зафиксированного на прошлом тике времени, а от времени с разницей в одну миллисекунду. Если после копирования новых тиков и добавления их в список их общее количество превысит установленную для них максимальную величину, то рассчитываем количество лишних объектов в списке и удаляем их из списка — это самые старые объекты-тики в списке.

В метод были нарочно добавлены строки кода, выводящие данные по количеству скопированных тиков, прошлому и текущему времени и общему количеству тиковых данных в списке тиковой серии — для проверки верности их копирования, и только для символа AUDUSD.
В следующих статьях эти тестовые строки уберём. Сейчас же эти строки смогут нам показать, что тики на "неродном" символе копируются в таймере библиотеки и объекты тиковых данных заносятся в список-тиковую серию.

Метод, возвращающий самый последний объект тиковых данных из списка:

//+------------------------------------------------------------------+
//| Возвращает самый последний объект тиковых данных из списка       |
//+------------------------------------------------------------------+
CDataTick *CTickSeries::GetLastTick(void)
  {
   return this.m_list_ticks.At(this.m_list_ticks.Total()-1);
  }
//+------------------------------------------------------------------+

Метод просто возвращает указатель на объект, хранящийся в списке самым последним.

Теперь доработаем немного класс-коллекцию тиковых серий в файле \MQL5\Include\DoEasy\Collections\TickSeriesCollection.mqh.

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

//--- Обновляет тиковую серию (1) указанного символа, (2) всех символов, (3) всех символов кроме текущего
   void                    Refresh(const string symbol);
   void                    Refresh(void);
   void                    RefreshExpectCurrent(void);

//--- Выводит в журнал (1) полное, (2) краткое описание коллекции

а за пределами тела класса напишем его реализацию:

//+------------------------------------------------------------------+
//| Обновляет тиковые серии всех символов кроме текущего             |
//+------------------------------------------------------------------+
void CTickSeriesCollection::RefreshExpectCurrent(void)
  {
   for(int i=0;i<this.m_list.Total();i++)
     {
      CTickSeries *tickseries=this.m_list.At(i);
      if(tickseries==NULL || tickseries.Symbol()==::Symbol())
         continue;
      tickseries.Refresh();
     }
  }
//+------------------------------------------------------------------+

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

В главный объект библиотеки CEngine в файле \MQL5\Include\DoEasy\Engine.mqh добавим три метода — для обновления тиковой серии указанного символа , для обновления всех тиковых серий, и для обновления всех тиковых серий коллекции кроме серии текущего символа:

//--- Возвращает (1) коллекцию тиковых серий, (2) список тиковых серий из коллекции тиковых серий
   CTickSeriesCollection *GetTickSeriesCollection(void)                                { return &this.m_tick_series;                         }
   CArrayObj           *GetListTickSeries(void)                                        { return this.m_tick_series.GetList();                }

//--- Обновляет тиковую серию (1) указанного символа, (2) всех символов коллекции, (3) всех символов кроме текущего
   void                 TickSeriesRefresh(const string symbol)                         { this.m_tick_series.Refresh(symbol);                 }
   void                 TickSeriesRefreshAll(void)                                     { this.m_tick_series.Refresh();                       }
   void                 TickSeriesRefreshAllExceptCurrent(void)                        { this.m_tick_series.RefreshExpectCurrent();          }

//--- Возвращает (1) коллекцию буферов, (2) список буферов из коллекции 

Методы просто вызывают одноимённые методы класса-коллекции тиковых серий.

В конструкторе класса добавим создание таймера коллекции тиковых данных:

//+------------------------------------------------------------------+
//| CEngine конструктор                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name=::MQLInfoString(MQL_PROGRAM_NAME);
   
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
   this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE);
   this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE);
   this.CreateCounter(COLLECTION_TICKS_COUNTER_ID,COLLECTION_TICKS_COUNTER_STEP,COLLECTION_TICKS_PAUSE);
   
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   #endif 
   //---
  }
//+------------------------------------------------------------------+

Теперь таймер коллекции тиковых серий создан, и нужно прописать в таймере класса блок работы с этим таймером:

//+------------------------------------------------------------------+
//| CEngine таймер                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(SDataCalculate &data_calculate)
  {
//--- Если это не тестер - работаем с событиями коллекций по таймеру
   if(!this.IsTester())
     {
   //--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций
      int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
      CTimerCounter* cnt1=this.m_list_counters.At(index);
      if(cnt1!=NULL)
        {
         //--- Если пауза завершилась - работаем с событиями коллекций ордеров, сделок и позиций
         if(cnt1.IsTimeDone())
            this.TradeEventsControl();
        }
   //--- Таймер коллекции аккаунтов
      index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
      CTimerCounter* cnt2=this.m_list_counters.At(index);
      if(cnt2!=NULL)
        {
         //--- Если пауза завершилась - работаем с событиями коллекции аккаунтов
         if(cnt2.IsTimeDone())
            this.AccountEventsControl();
        }
   //--- Таймер1 коллекции символов (обновление котировочных данных символов в коллекции)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
      CTimerCounter* cnt3=this.m_list_counters.At(index);
      if(cnt3!=NULL)
        {
         //--- Если пауза завершилась - обновляем котировочные данные всех символов в коллекции
         if(cnt3.IsTimeDone())
            this.m_symbols.RefreshRates();
        }
   //--- Таймер2 коллекции символов (обновление всех данных всех символов в коллекции и отслеживание событий символов и списка символов в окне обзора рынка)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
      CTimerCounter* cnt4=this.m_list_counters.At(index);
      if(cnt4!=NULL)
        {
         //--- Если пауза завершилась
         if(cnt4.IsTimeDone())
           {
            //--- обновляем данные и работаем с событиями всех символов в коллекции
            this.SymbolEventsControl();
            //--- Если работаем со списком из обзора рынка - проверяем события окна обзора рынка
            if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
               this.MarketWatchEventsControl();
           }
        }
   //--- Таймер торгового класса
      index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
      CTimerCounter* cnt5=this.m_list_counters.At(index);
      if(cnt5!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком отложенных запросов
         if(cnt5.IsTimeDone())
            this.m_trading.OnTimer();
        }
   //--- Таймер коллекции таймсерий
      index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
      CTimerCounter* cnt6=this.m_list_counters.At(index);
      if(cnt6!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком таймсерий (обновляем все кроме текущей)
         if(cnt6.IsTimeDone())
            this.SeriesRefreshAllExceptCurrent(data_calculate);
        }
        
   //--- Таймер коллекции таймсерий данных индикаторных буферов
      index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID);
      CTimerCounter* cnt7=this.m_list_counters.At(index);
      if(cnt7!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком таймсерий индикаторных данных (обновляем все кроме текущей)
         if(cnt7.IsTimeDone()) 
            this.IndicatorSeriesRefreshAll();
        }
   //--- Таймер коллекции тиковых серий
      index=this.CounterIndex(COLLECTION_TICKS_COUNTER_ID);
      CTimerCounter* cnt8=this.m_list_counters.At(index);
      if(cnt8!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком тиковых серий (обновляем все кроме текущей)
         if(cnt8.IsTimeDone())
            this.TickSeriesRefreshAllExceptCurrent();
        }
     }
//--- Если тестер - работаем с событиями коллекций по тику
   else
     {
      //--- работаем с событиями коллекций ордеров, сделок и позиций по тику
      this.TradeEventsControl();
      //--- работаем с событиями коллекции аккаунтов по тику
      this.AccountEventsControl();
      //--- обновляем котировочные данные всех символов в коллекции по тику
      this.m_symbols.RefreshRates();
      //--- работаем с событиями всех символов в коллекции по тику
      this.SymbolEventsControl();
      //--- работаем со списком отложенных запросов по тику
      this.m_trading.OnTimer();
      //--- работаем со списком таймсерий по тику
      this.SeriesRefresh(data_calculate);
      //--- работаем со списком таймсерий индикаторных буферов по тику
      this.IndicatorSeriesRefreshAll();
      //--- работаем со списком тиковых серий по тику
      this.TickSeriesRefreshAll();
     }
  }
//+------------------------------------------------------------------+

Теперь все тиковые серии всех "неродных" символов будут обновляться в таймере библиотеки.

А для обновления тиковой серии текущего символа, в обработчик OnTick() класса впишем вызов метода обновления тиковой серии указанного символа:

//+------------------------------------------------------------------+
//| Обработчик события NewTick                                       |
//+------------------------------------------------------------------+
void CEngine::OnTick(SDataCalculate &data_calculate,const uint required=0)
  {
//--- Если это не эксперт - уходим
   if(this.m_program!=PROGRAM_EXPERT)
      return;
//--- Пересоздание пустых таймсерий и обновление таймсерий текущего символа
   this.SeriesSync(data_calculate,required);
   this.SeriesRefresh(NULL,data_calculate);
   this.TickSeriesRefresh(NULL);
//--- end
  }
//+------------------------------------------------------------------+

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


Доработка класса символа для работы со стаканом цен

Со следующей статьи мы начнём создавать функционал библиотеки для работы со стаканом цен.

Чтобы получать события BookEvent по любому символу, достаточно предварительно подписаться на получение этих событий для этого символа с помощью функции MarketBookAdd(). Для того чтобы отписаться от получения события BookEvent по конкретному символу, необходимо вызывать функцию MarketBookRelease().

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

Откроем класс объекта-символа в файле \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh и впишем необходимые изменения.
В приватной секции класса объявим переменную для хранения флага подписки на стакан цен, а в публичной секции объявим деструктор класса:

//+------------------------------------------------------------------+
//| Класс абстрактного символа                                       |
//+------------------------------------------------------------------+
class CSymbol : public CBaseObjExt
  {
private:
   struct MqlMarginRate
     {
      double         Initial;                                  // коэффициент взимания начальной маржи
      double         Maintenance;                              // коэффициент взимания поддерживающей маржи
     };
   struct MqlMarginRateMode
     {
      MqlMarginRate  Long;                                     // MarginRate длинных позиций
      MqlMarginRate  Short;                                    // MarginRate коротких позиций
      MqlMarginRate  BuyStop;                                  // MarginRate BuyStop-ордеров
      MqlMarginRate  BuyLimit;                                 // MarginRate BuyLimit-ордеров
      MqlMarginRate  BuyStopLimit;                             // MarginRate BuyStopLimit-ордеров
      MqlMarginRate  SellStop;                                 // MarginRate SellStop-ордеров
      MqlMarginRate  SellLimit;                                // MarginRate SellLimit-ордеров
      MqlMarginRate  SellStopLimit;                            // MarginRate SellStopLimit-ордеров
     };
   MqlMarginRateMode m_margin_rate;                            // Структура коэффициентов взимания маржи
   MqlBookInfo       m_book_info_array[];                      // Массив структур данных стакана
   
   long              m_long_prop[SYMBOL_PROP_INTEGER_TOTAL];   // Целочисленные свойства
   double            m_double_prop[SYMBOL_PROP_DOUBLE_TOTAL];  // Вещественные свойства
   string            m_string_prop[SYMBOL_PROP_STRING_TOTAL];  // Строковые свойства
   bool              m_is_change_trade_mode;                   // Флаг изменения режима торговли для символа
   bool              m_book_subscribed;                        // Флаг активности подписки на стакан цен по символу
   CTradeObj         m_trade;                                  // Объект торгового класса
//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство символа
   int               IndexProp(ENUM_SYMBOL_PROP_DOUBLE property)  const { return(int)property-SYMBOL_PROP_INTEGER_TOTAL;                                    }
   int               IndexProp(ENUM_SYMBOL_PROP_STRING property)  const { return(int)property-SYMBOL_PROP_INTEGER_TOTAL-SYMBOL_PROP_DOUBLE_TOTAL;           }
//--- (1) Заполняет все свойства символа "коэффициент взимания маржи", (2) инициализирует коэффициенты
-+/   bool              MarginRates(void);
   void              InitMarginRates(void);
//--- Обнуляет все данные объекта-символа
   void              Reset(void);
//--- Возвращает текущий день недели
   ENUM_DAY_OF_WEEK  CurrentDayOfWeek(void)              const;

public:
//--- Конструктор по умолчанию
                     CSymbol(void){;}
//--- Деструктор
                    ~CSymbol(void);
protected:
//--- Защищённый параметрический конструктор
                     CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index);

В публичной секции класса в методах упрощённого доступа к свойствам символа впишем новый метод, возвращающий статус подписки на стакан цен:

public:
//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-символа           |
//+------------------------------------------------------------------+
//--- Целочисленные свойства
   long              Status(void)                                 const { return this.GetProperty(SYMBOL_PROP_STATUS);                                      }
   int               IndexInMarketWatch(void)                     const { return (int)this.GetProperty(SYMBOL_PROP_INDEX_MW);                               }
   bool              IsCustom(void)                               const { return (bool)this.GetProperty(SYMBOL_PROP_CUSTOM);                                }
   color             ColorBackground(void)                        const { return (color)this.GetProperty(SYMBOL_PROP_BACKGROUND_COLOR);                     }
   ENUM_SYMBOL_CHART_MODE ChartMode(void)                         const { return (ENUM_SYMBOL_CHART_MODE)this.GetProperty(SYMBOL_PROP_CHART_MODE);          }
   bool              IsExist(void)                                const { return (bool)this.GetProperty(SYMBOL_PROP_EXIST);                                 }
   bool              IsExist(const string name)                   const { return this.SymbolExists(name);                                                   }
   bool              IsSelect(void)                               const { return (bool)this.GetProperty(SYMBOL_PROP_SELECT);                                }
   bool              IsVisible(void)                              const { return (bool)this.GetProperty(SYMBOL_PROP_VISIBLE);                               }
   long              SessionDeals(void)                           const { return this.GetProperty(SYMBOL_PROP_SESSION_DEALS);                               }
   long              SessionBuyOrders(void)                       const { return this.GetProperty(SYMBOL_PROP_SESSION_BUY_ORDERS);                          }
   long              SessionSellOrders(void)                      const { return this.GetProperty(SYMBOL_PROP_SESSION_SELL_ORDERS);                         }
   long              Volume(void)                                 const { return this.GetProperty(SYMBOL_PROP_VOLUME);                                      }
   long              VolumeHigh(void)                             const { return this.GetProperty(SYMBOL_PROP_VOLUMEHIGH);                                  }
   long              VolumeLow(void)                              const { return this.GetProperty(SYMBOL_PROP_VOLUMELOW);                                   }
   long              Time(void)                                   const { return (datetime)this.GetProperty(SYMBOL_PROP_TIME);                              }
   int               Digits(void)                                 const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS);                                 }
   int               DigitsLot(void)                              const { return (int)this.GetProperty(SYMBOL_PROP_DIGITS_LOTS);                            }
   int               Spread(void)                                 const { return (int)this.GetProperty(SYMBOL_PROP_SPREAD);                                 }
   bool              IsSpreadFloat(void)                          const { return (bool)this.GetProperty(SYMBOL_PROP_SPREAD_FLOAT);                          }
   int               TicksBookdepth(void)                         const { return (int)this.GetProperty(SYMBOL_PROP_TICKS_BOOKDEPTH);                        }
   bool              BookdepthSubscription(void)                  const { return (bool)this.GetProperty(SYMBOL_PROP_BOOKDEPTH_STATE);                       }
   ENUM_SYMBOL_CALC_MODE TradeCalcMode(void)                      const { return (ENUM_SYMBOL_CALC_MODE)this.GetProperty(SYMBOL_PROP_TRADE_CALC_MODE);      }
   ENUM_SYMBOL_TRADE_MODE TradeMode(void)                         const { return (ENUM_SYMBOL_TRADE_MODE)this.GetProperty(SYMBOL_PROP_TRADE_MODE);          }
   datetime          StartTime(void)                              const { return (datetime)this.GetProperty(SYMBOL_PROP_START_TIME);                        }
   datetime          ExpirationTime(void)                         const { return (datetime)this.GetProperty(SYMBOL_PROP_EXPIRATION_TIME);                   }
   int               TradeStopLevel(void)                         const { return (int)this.GetProperty(SYMBOL_PROP_TRADE_STOPS_LEVEL);                      }
   int               TradeFreezeLevel(void)                       const { return (int)this.GetProperty(SYMBOL_PROP_TRADE_FREEZE_LEVEL);                     }
   ENUM_SYMBOL_TRADE_EXECUTION TradeExecutionMode(void)           const { return (ENUM_SYMBOL_TRADE_EXECUTION)this.GetProperty(SYMBOL_PROP_TRADE_EXEMODE);  }
   ENUM_SYMBOL_SWAP_MODE SwapMode(void)                           const { return (ENUM_SYMBOL_SWAP_MODE)this.GetProperty(SYMBOL_PROP_SWAP_MODE);            }
   ENUM_DAY_OF_WEEK  SwapRollover3Days(void)                      const { return (ENUM_DAY_OF_WEEK)this.GetProperty(SYMBOL_PROP_SWAP_ROLLOVER3DAYS);        }
   bool              IsMarginHedgedUseLeg(void)                   const { return (bool)this.GetProperty(SYMBOL_PROP_MARGIN_HEDGED_USE_LEG);                 }
   int               ExpirationModeFlags(void)                    const { return (int)this.GetProperty(SYMBOL_PROP_EXPIRATION_MODE);                        }
   int               FillingModeFlags(void)                       const { return (int)this.GetProperty(SYMBOL_PROP_FILLING_MODE);                           }
   int               OrderModeFlags(void)                         const { return (int)this.GetProperty(SYMBOL_PROP_ORDER_MODE);                             }
   ENUM_SYMBOL_ORDER_GTC_MODE OrderModeGTC(void)                  const { return (ENUM_SYMBOL_ORDER_GTC_MODE)this.GetProperty(SYMBOL_PROP_ORDER_GTC_MODE);  }
   ENUM_SYMBOL_OPTION_MODE OptionMode(void)                       const { return (ENUM_SYMBOL_OPTION_MODE)this.GetProperty(SYMBOL_PROP_OPTION_MODE);        }
   ENUM_SYMBOL_OPTION_RIGHT OptionRight(void)                     const { return (ENUM_SYMBOL_OPTION_RIGHT)this.GetProperty(SYMBOL_PROP_OPTION_RIGHT);      }
//--- Вещественные свойства

В конструкторе класса инициализируем флаг подписки в состояние false и впишем значение этого флага в свойство символа:

//+------------------------------------------------------------------+
//| Закрытый параметрический конструктор                             |
//+------------------------------------------------------------------+
CSymbol::CSymbol(ENUM_SYMBOL_STATUS symbol_status,const string name,const int index)
  {
   this.m_name=name;
   this.m_book_subscribed=false;
   this.m_type=COLLECTION_SYMBOLS_ID;
   if(!this.Exist())
     {
      ::Print(DFUN_ERR_LINE,"\"",this.m_name,"\"",": ",CMessage::Text(MSG_LIB_SYS_NOT_SYMBOL_ON_SERVER));
      this.m_global_error=ERR_MARKET_UNKNOWN_SYMBOL;
     }
   bool select=::SymbolInfoInteger(this.m_name,SYMBOL_SELECT);
   ::ResetLastError();
   if(!select)
     {
      if(!this.SetToMarketWatch())
        {
         this.m_global_error=::GetLastError();
         ::Print(DFUN_ERR_LINE,"\"",this.m_name,"\": ",CMessage::Text(MSG_LIB_SYS_FAILED_PUT_SYMBOL),this.m_global_error);
        }
     }
   ::ResetLastError();
   if(!::SymbolInfoTick(this.m_name,this.m_tick))
     {
      this.m_global_error=::GetLastError();
      ::Print(DFUN_ERR_LINE,"\"",this.m_name,"\": ",CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),this.m_global_error);
     }
//--- Инициализация массивов данных базового объекта
   this.SetControlDataArraySizeLong(SYMBOL_PROP_INTEGER_TOTAL);
   this.SetControlDataArraySizeDouble(SYMBOL_PROP_DOUBLE_TOTAL);
   this.ResetChangesParams();
   this.ResetControlsParams();
   
//--- Инициализация данных символа
   this.Reset();
   this.InitMarginRates();
#ifdef __MQL5__
   ::ResetLastError();
   if(!this.MarginRates())
     {
      this.m_global_error=::GetLastError();
      ::Print(DFUN_ERR_LINE,this.Name(),": ",CMessage::Text(MSG_LIB_SYS_NOT_GET_MARGIN_RATES),this.m_global_error);
      return;
     }
#endif 
   
//--- Сохранение целочисленных свойств
   this.m_long_prop[SYMBOL_PROP_STATUS]                                             = symbol_status;
   this.m_long_prop[SYMBOL_PROP_INDEX_MW]                                           = index;
   this.m_long_prop[SYMBOL_PROP_VOLUME]                                             = (long)this.m_tick.volume;
   this.m_long_prop[SYMBOL_PROP_SELECT]                                             = ::SymbolInfoInteger(this.m_name,SYMBOL_SELECT);
   this.m_long_prop[SYMBOL_PROP_VISIBLE]                                            = ::SymbolInfoInteger(this.m_name,SYMBOL_VISIBLE);
   this.m_long_prop[SYMBOL_PROP_SESSION_DEALS]                                      = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_DEALS);
   this.m_long_prop[SYMBOL_PROP_SESSION_BUY_ORDERS]                                 = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_BUY_ORDERS);
   this.m_long_prop[SYMBOL_PROP_SESSION_SELL_ORDERS]                                = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_SELL_ORDERS);
   this.m_long_prop[SYMBOL_PROP_VOLUMEHIGH]                                         = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMEHIGH);
   this.m_long_prop[SYMBOL_PROP_VOLUMELOW]                                          = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMELOW);
   this.m_long_prop[SYMBOL_PROP_DIGITS]                                             = ::SymbolInfoInteger(this.m_name,SYMBOL_DIGITS);
   this.m_long_prop[SYMBOL_PROP_SPREAD]                                             = ::SymbolInfoInteger(this.m_name,SYMBOL_SPREAD);
   this.m_long_prop[SYMBOL_PROP_SPREAD_FLOAT]                                       = ::SymbolInfoInteger(this.m_name,SYMBOL_SPREAD_FLOAT);
   this.m_long_prop[SYMBOL_PROP_TICKS_BOOKDEPTH]                                    = ::SymbolInfoInteger(this.m_name,SYMBOL_TICKS_BOOKDEPTH);
   this.m_long_prop[SYMBOL_PROP_TRADE_MODE]                                         = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_MODE);
   this.m_long_prop[SYMBOL_PROP_START_TIME]                                         = ::SymbolInfoInteger(this.m_name,SYMBOL_START_TIME);
   this.m_long_prop[SYMBOL_PROP_EXPIRATION_TIME]                                    = ::SymbolInfoInteger(this.m_name,SYMBOL_EXPIRATION_TIME);
   this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL]                                  = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_STOPS_LEVEL);
   this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL]                                 = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_FREEZE_LEVEL);
   this.m_long_prop[SYMBOL_PROP_TRADE_EXEMODE]                                      = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_EXEMODE);
   this.m_long_prop[SYMBOL_PROP_SWAP_ROLLOVER3DAYS]                                 = ::SymbolInfoInteger(this.m_name,SYMBOL_SWAP_ROLLOVER3DAYS);
   this.m_long_prop[SYMBOL_PROP_TIME]                                               = this.TickTime();
   this.m_long_prop[SYMBOL_PROP_EXIST]                                              = this.SymbolExists();
   this.m_long_prop[SYMBOL_PROP_CUSTOM]                                             = this.SymbolCustom();
   this.m_long_prop[SYMBOL_PROP_MARGIN_HEDGED_USE_LEG]                              = this.SymbolMarginHedgedUseLEG();
   this.m_long_prop[SYMBOL_PROP_ORDER_MODE]                                         = this.SymbolOrderMode();
   this.m_long_prop[SYMBOL_PROP_FILLING_MODE]                                       = this.SymbolOrderFillingMode();
   this.m_long_prop[SYMBOL_PROP_EXPIRATION_MODE]                                    = this.SymbolExpirationMode();
   this.m_long_prop[SYMBOL_PROP_ORDER_GTC_MODE]                                     = this.SymbolOrderGTCMode();
   this.m_long_prop[SYMBOL_PROP_OPTION_MODE]                                        = this.SymbolOptionMode();
   this.m_long_prop[SYMBOL_PROP_OPTION_RIGHT]                                       = this.SymbolOptionRight();
   this.m_long_prop[SYMBOL_PROP_BACKGROUND_COLOR]                                   = this.SymbolBackgroundColor();
   this.m_long_prop[SYMBOL_PROP_CHART_MODE]                                         = this.SymbolChartMode();
   this.m_long_prop[SYMBOL_PROP_TRADE_CALC_MODE]                                    = this.SymbolCalcMode();
   this.m_long_prop[SYMBOL_PROP_SWAP_MODE]                                          = this.SymbolSwapMode();
   this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]                                    = this.m_book_subscribed;
//--- Сохранение вещественных свойств
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKHIGH)]                          = ::SymbolInfoDouble(this.m_name,SYMBOL_ASKHIGH);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASKLOW)]                           = ::SymbolInfoDouble(this.m_name,SYMBOL_ASKLOW);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTHIGH)]                         = ::SymbolInfoDouble(this.m_name,SYMBOL_LASTHIGH);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_LASTLOW)]                          = ::SymbolInfoDouble(this.m_name,SYMBOL_LASTLOW);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_POINT)]                            = ::SymbolInfoDouble(this.m_name,SYMBOL_POINT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)]                 = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)]          = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)]            = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_LOSS);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_SIZE)]                  = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_SIZE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_CONTRACT_SIZE)]              = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_CONTRACT_SIZE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MIN)]                       = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MIN);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MAX)]                       = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MAX);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_STEP)]                      = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_STEP);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_LIMIT)]                     = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_LIMIT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_LONG)]                        = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_LONG);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SHORT)]                       = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SHORT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_INITIAL)]                   = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_INITIAL);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_MAINTENANCE)]               = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_MAINTENANCE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_VOLUME)]                   = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_VOLUME);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_TURNOVER)]                 = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_TURNOVER);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_INTEREST)]                 = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_INTEREST);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME)]        = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_BUY_ORDERS_VOLUME);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME)]       = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_SELL_ORDERS_VOLUME);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_OPEN)]                     = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_OPEN);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_CLOSE)]                    = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_CLOSE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_AW)]                       = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_AW);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT)]         = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_SETTLEMENT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN)]          = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MIN);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX)]          = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MAX);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_BID)]                              = this.m_tick.bid;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_ASK)]                              = this.m_tick.ask;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_LAST)]                             = this.m_tick.last;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDHIGH)]                          = this.SymbolBidHigh();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_BIDLOW)]                           = this.SymbolBidLow();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_REAL)]                      = this.SymbolVolumeReal();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMEHIGH_REAL)]                  = this.SymbolVolumeHighReal();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMELOW_REAL)]                   = this.SymbolVolumeLowReal();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_OPTION_STRIKE)]                    = this.SymbolOptionStrike();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_ACCRUED_INTEREST)]           = this.SymbolTradeAccruedInterest();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_FACE_VALUE)]                 = this.SymbolTradeFaceValue();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_LIQUIDITY_RATE)]             = this.SymbolTradeLiquidityRate();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_HEDGED)]                    = this.SymbolMarginHedged();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_INITIAL)]              = this.m_margin_rate.Long.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL)]          = this.m_margin_rate.BuyStop.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL)]         = this.m_margin_rate.BuyLimit.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL)]     = this.m_margin_rate.BuyStopLimit.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE)]          = this.m_margin_rate.Long.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE)]      = this.m_margin_rate.BuyStop.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE)]     = this.m_margin_rate.BuyLimit.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE)] = this.m_margin_rate.BuyStopLimit.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_INITIAL)]             = this.m_margin_rate.Short.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL)]         = this.m_margin_rate.SellStop.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL)]        = this.m_margin_rate.SellLimit.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL)]    = this.m_margin_rate.SellStopLimit.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE)]         = this.m_margin_rate.Short.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE)]     = this.m_margin_rate.SellStop.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE)]    = this.m_margin_rate.SellLimit.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE)]= this.m_margin_rate.SellStopLimit.Maintenance;
//--- Сохранение строковых свойств
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_NAME)]                             = this.m_name;
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_BASE)]                    = ::SymbolInfoString(this.m_name,SYMBOL_CURRENCY_BASE);
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_PROFIT)]                  = ::SymbolInfoString(this.m_name,SYMBOL_CURRENCY_PROFIT);
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_CURRENCY_MARGIN)]                  = ::SymbolInfoString(this.m_name,SYMBOL_CURRENCY_MARGIN);
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_DESCRIPTION)]                      = ::SymbolInfoString(this.m_name,SYMBOL_DESCRIPTION);
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_PATH)]                             = ::SymbolInfoString(this.m_name,SYMBOL_PATH);
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_BASIS)]                            = this.SymbolBasis();
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_BANK)]                             = this.SymbolBank();
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_ISIN)]                             = this.SymbolISIN();
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_FORMULA)]                          = this.SymbolFormula();
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_PAGE)]                             = this.SymbolPage();
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_CATEGORY)]                         = this.SymbolCategory();
   this.m_string_prop[this.IndexProp(SYMBOL_PROP_EXCHANGE)]                         = this.SymbolExchange();
//--- Сохранение дополнительных целочисленных свойств
   this.m_long_prop[SYMBOL_PROP_DIGITS_LOTS]                                        = this.SymbolDigitsLot();
   
//--- Заполнение текущих данных символа
   for(int i=0;i<SYMBOL_PROP_INTEGER_TOTAL;i++)
      this.m_long_prop_event[i][3]=this.m_long_prop[i];
   for(int i=0;i<SYMBOL_PROP_DOUBLE_TOTAL;i++)
      this.m_double_prop_event[i][3]=this.m_double_prop[i];
   
//--- Обновление данных в базовом объекте и поиск изменений
   CBaseObjExt::Refresh();
//---
   if(!select)
      this.RemoveFromMarketWatch();

//--- Инициализация умолчательных значений торгового объекта
   this.m_trade.Init(this.Name(),0,this.LotsMin(),5,0,0,false,this.GetCorrectTypeFilling(),this.GetCorrectTypeExpiration(),LOG_LEVEL_ERROR_MSG);
  }
//+------------------------------------------------------------------+

Реализация деструктора класса:

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CSymbol::~CSymbol(void)
  {
   if(this.m_book_subscribed)
      this.BookClose();
  }
//+------------------------------------------------------------------+

Если флаг подписки на стакан цен установленотписываемся от трансляции стакана цен.

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

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

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства символа              |
//+------------------------------------------------------------------+
string CSymbol::GetPropertyDescription(ENUM_SYMBOL_PROP_INTEGER property)
  {
   return
     (
      property==SYMBOL_PROP_STATUS              ?  CMessage::Text(MSG_ORD_STATUS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetStatusDescription()
         )  :
      property==SYMBOL_PROP_INDEX_MW            ?  CMessage::Text(MSG_SYM_PROP_INDEX)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==SYMBOL_PROP_CUSTOM              ?  CMessage::Text(MSG_SYM_PROP_CUSTOM)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==SYMBOL_PROP_CHART_MODE          ?  CMessage::Text(MSG_SYM_PROP_CHART_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetChartModeDescription()
         )  :
      property==SYMBOL_PROP_EXIST               ?  CMessage::Text(MSG_SYM_PROP_EXIST)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==SYMBOL_PROP_SELECT  ?  CMessage::Text(MSG_SYM_PROP_SELECT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==SYMBOL_PROP_VISIBLE ?  CMessage::Text(MSG_SYM_PROP_VISIBLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==SYMBOL_PROP_SESSION_DEALS       ?  CMessage::Text(MSG_SYM_PROP_SESSION_DEALS)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ #ifdef __MQL5__(string)this.GetProperty(property) #else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SESSION_BUY_ORDERS  ?  CMessage::Text(MSG_SYM_PROP_SESSION_BUY_ORDERS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ #ifdef __MQL5__(string)this.GetProperty(property) #else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_SESSION_SELL_ORDERS ?  CMessage::Text(MSG_SYM_PROP_SESSION_SELL_ORDERS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ #ifdef __MQL5__(string)this.GetProperty(property) #else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_VOLUME              ?  CMessage::Text(MSG_SYM_PROP_VOLUME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ #ifdef __MQL5__(string)this.GetProperty(property) #else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_VOLUMEHIGH          ?  CMessage::Text(MSG_SYM_PROP_VOLUMEHIGH)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ #ifdef __MQL5__(string)this.GetProperty(property) #else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_VOLUMELOW           ?  CMessage::Text(MSG_SYM_PROP_VOLUMELOW)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ #ifdef __MQL5__(string)this.GetProperty(property) #else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_TIME                ?  CMessage::Text(MSG_SYM_PROP_TIME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)==0 ? "("+CMessage::Text(MSG_LIB_SYS_NO_TICKS_YET)+")" : TimeMSCtoString(this.GetProperty(property)))
         )  :
      property==SYMBOL_PROP_DIGITS              ?  CMessage::Text(MSG_SYM_PROP_DIGITS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==SYMBOL_PROP_DIGITS_LOTS         ?  CMessage::Text(MSG_SYM_PROP_DIGITS_LOTS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==SYMBOL_PROP_SPREAD              ?  CMessage::Text(MSG_SYM_PROP_SPREAD)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==SYMBOL_PROP_SPREAD_FLOAT        ?  CMessage::Text(MSG_SYM_PROP_SPREAD_FLOAT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_YES))
         )  :
      property==SYMBOL_PROP_TICKS_BOOKDEPTH     ?  CMessage::Text(MSG_SYM_PROP_TICKS_BOOKDEPTH)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ #ifdef __MQL5__(string)this.GetProperty(property) #else CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      property==SYMBOL_PROP_BOOKDEPTH_STATE     ?  CMessage::Text(MSG_SYM_SYMBOLS_MODE_BOOK)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+ #ifdef __MQL5__
                  (this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) 
                #else 
                  CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) 
                #endif 
         )  :
      property==SYMBOL_PROP_TRADE_CALC_MODE     ?  CMessage::Text(MSG_SYM_PROP_TRADE_CALC_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetCalcModeDescription()
         )  :
      property==SYMBOL_PROP_TRADE_MODE ?  CMessage::Text(MSG_SYM_PROP_TRADE_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTradeModeDescription()
         )  :
      property==SYMBOL_PROP_START_TIME          ?  CMessage::Text(MSG_SYM_PROP_START_TIME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         (this.GetProperty(property)==0  ?  ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+TimeMSCtoString(this.GetProperty(property)*1000))
         )  :
      property==SYMBOL_PROP_EXPIRATION_TIME     ?  CMessage::Text(MSG_SYM_PROP_EXPIRATION_TIME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         (this.GetProperty(property)==0  ?  ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+TimeMSCtoString(this.GetProperty(property)*1000))
         )  :
      property==SYMBOL_PROP_TRADE_STOPS_LEVEL   ?  CMessage::Text(MSG_SYM_PROP_TRADE_STOPS_LEVEL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==SYMBOL_PROP_TRADE_FREEZE_LEVEL  ?  CMessage::Text(MSG_SYM_PROP_TRADE_FREEZE_LEVEL)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==SYMBOL_PROP_TRADE_EXEMODE       ?  CMessage::Text(MSG_SYM_PROP_TRADE_EXEMODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTradeExecDescription()
         )  :
      property==SYMBOL_PROP_SWAP_MODE           ?  CMessage::Text(MSG_SYM_PROP_SWAP_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetSwapModeDescription()
         )  :
      property==SYMBOL_PROP_SWAP_ROLLOVER3DAYS  ?  CMessage::Text(MSG_SYM_PROP_SWAP_ROLLOVER3DAYS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+DayOfWeekDescription(this.SwapRollover3Days())
         )  :
      property==SYMBOL_PROP_MARGIN_HEDGED_USE_LEG  ?  CMessage::Text(MSG_SYM_PROP_MARGIN_HEDGED_USE_LEG)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED)   :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==SYMBOL_PROP_EXPIRATION_MODE     ?  CMessage::Text(MSG_SYM_PROP_EXPIRATION_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetExpirationModeFlagsDescription()
         )  :
      property==SYMBOL_PROP_FILLING_MODE        ?  CMessage::Text(MSG_SYM_PROP_FILLING_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetFillingModeFlagsDescription()
         )  :
      property==SYMBOL_PROP_ORDER_MODE          ?  CMessage::Text(MSG_SYM_PROP_ORDER_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetOrderModeFlagsDescription()
         )  :
      property==SYMBOL_PROP_ORDER_GTC_MODE      ?  CMessage::Text(MSG_SYM_PROP_ORDER_GTC_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetOrderGTCModeDescription()
         )  :
      property==SYMBOL_PROP_OPTION_MODE         ?  CMessage::Text(MSG_SYM_PROP_OPTION_MODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetOptionTypeDescription()
         )  :
      property==SYMBOL_PROP_OPTION_RIGHT        ?  CMessage::Text(MSG_SYM_PROP_OPTION_RIGHT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetOptionRightDescription()
         )  :
      property==SYMBOL_PROP_BACKGROUND_COLOR    ?  CMessage::Text(MSG_SYM_PROP_BACKGROUND_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         #ifdef __MQL5__
         (this.GetProperty(property)==CLR_DEFAULT || this.GetProperty(property)==CLR_NONE ?  ": ("+CMessage::Text(MSG_LIB_PROP_EMPTY)+")" : ": "+::ColorToString((color)this.GetProperty(property),true))
         #else ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED_MQL4) #endif 
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Метод, осуществляющий подписку на стакан цен:

//+------------------------------------------------------------------+
//| Осуществляет подписку на стакан цен                              |
//+------------------------------------------------------------------+
bool CSymbol::BookAdd(void)
  {
   this.m_book_subscribed=(#ifdef __MQL5__ ::MarketBookAdd(this.m_name) #else false #endif);
   if(this.m_book_subscribed)
     {
      this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]=this.m_book_subscribed;
      ::Print(CMessage::Text(MSG_SYM_SYMBOLS_BOOK_ADD)+" "+this.m_name);
     }
   return this.m_book_subscribed;
  }
//+------------------------------------------------------------------+

Записываем в переменную m_book_subscribed, хранящую флаг состояния подписки, результат работы функции MarketBookAdd(), которая обеспечивает открытие стакана цен по указанному инструменту и производит подписку на получение извещений об изменении этого стакана.
Если подписка осуществлена, выводим сообщение об успешности подписки.
Из метода возвращается результат, записанный в m_book_subscribed.

Метод, осуществляющий закрытие стакана цен:

//+------------------------------------------------------------------+
//| Осуществляет закрытие стакана цен                                |
//+------------------------------------------------------------------+
bool CSymbol::BookClose(void)
  {
//--- Если флаг подписки на стакан снят - значит уже (или ещё) нет подписки - возвращаем true
   if(!this.m_book_subscribed)
      return true;
//--- Сохраняем результат отписки от стакана цен
   bool res=( #ifdef __MQL5__ ::MarketBookRelease(this.m_name) #else true #endif );
//--- Если успешно отписались - сбрасываем флаг подписки на стакан и записываем состояние в свойство объекта
   if(res)
     {
      this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]=this.m_book_subscribed=false;
      ::Print(CMessage::Text(MSG_SYM_SYMBOLS_BOOK_DEL)+" "+this.m_name);
     }
//--- Возвращаем результат отписки от стакана цен
   return res;
  }
//+------------------------------------------------------------------+

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

В методе обновления всех свойств символа впишем обновление состояния подписки, соответствующее состоянию флага в переменной m_book_subscribed:

//+------------------------------------------------------------------+
//| Обновляет все данные символа                                     |
//+------------------------------------------------------------------+
void CSymbol::Refresh(void)
  {
//--- Обновление котировочных данных
   if(!this.RefreshRates())
      return;
#ifdef __MQL5__
   ::ResetLastError();
   if(!this.MarginRates())
     {
      this.m_global_error=::GetLastError();
      return;
     }
#endif 
//--- Инициализация событийных данных
   this.m_is_event=false;
   this.m_hash_sum=0;
//--- Обновление целочисленных свойств
   this.m_long_prop[SYMBOL_PROP_SELECT]                                             = ::SymbolInfoInteger(this.m_name,SYMBOL_SELECT);
   this.m_long_prop[SYMBOL_PROP_VISIBLE]                                            = ::SymbolInfoInteger(this.m_name,SYMBOL_VISIBLE);
   this.m_long_prop[SYMBOL_PROP_SESSION_DEALS]                                      = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_DEALS);
   this.m_long_prop[SYMBOL_PROP_SESSION_BUY_ORDERS]                                 = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_BUY_ORDERS);
   this.m_long_prop[SYMBOL_PROP_SESSION_SELL_ORDERS]                                = ::SymbolInfoInteger(this.m_name,SYMBOL_SESSION_SELL_ORDERS);
   this.m_long_prop[SYMBOL_PROP_VOLUMEHIGH]                                         = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMEHIGH);
   this.m_long_prop[SYMBOL_PROP_VOLUMELOW]                                          = ::SymbolInfoInteger(this.m_name,SYMBOL_VOLUMELOW);
   this.m_long_prop[SYMBOL_PROP_SPREAD]                                             = ::SymbolInfoInteger(this.m_name,SYMBOL_SPREAD);
   this.m_long_prop[SYMBOL_PROP_TICKS_BOOKDEPTH]                                    = ::SymbolInfoInteger(this.m_name,SYMBOL_TICKS_BOOKDEPTH);
   this.m_long_prop[SYMBOL_PROP_START_TIME]                                         = ::SymbolInfoInteger(this.m_name,SYMBOL_START_TIME);
   this.m_long_prop[SYMBOL_PROP_EXPIRATION_TIME]                                    = ::SymbolInfoInteger(this.m_name,SYMBOL_EXPIRATION_TIME);
   this.m_long_prop[SYMBOL_PROP_TRADE_STOPS_LEVEL]                                  = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_STOPS_LEVEL);
   this.m_long_prop[SYMBOL_PROP_TRADE_FREEZE_LEVEL]                                 = ::SymbolInfoInteger(this.m_name,SYMBOL_TRADE_FREEZE_LEVEL);
   this.m_long_prop[SYMBOL_PROP_BACKGROUND_COLOR]                                   = this.SymbolBackgroundColor();
   this.m_long_prop[SYMBOL_PROP_BOOKDEPTH_STATE]                                    = this.m_book_subscribed;
   
//--- Обновление вещественных свойств
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE)]                 = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_PROFIT)]          = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_VALUE_LOSS)]            = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_VALUE_LOSS);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_TICK_SIZE)]                  = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_TICK_SIZE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_CONTRACT_SIZE)]              = ::SymbolInfoDouble(this.m_name,SYMBOL_TRADE_CONTRACT_SIZE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MIN)]                       = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MIN);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_MAX)]                       = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_MAX);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_STEP)]                      = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_STEP);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_LIMIT)]                     = ::SymbolInfoDouble(this.m_name,SYMBOL_VOLUME_LIMIT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_LONG)]                        = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_LONG);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SWAP_SHORT)]                       = ::SymbolInfoDouble(this.m_name,SYMBOL_SWAP_SHORT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_INITIAL)]                   = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_INITIAL);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_MAINTENANCE)]               = ::SymbolInfoDouble(this.m_name,SYMBOL_MARGIN_MAINTENANCE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_VOLUME)]                   = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_VOLUME);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_TURNOVER)]                 = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_TURNOVER);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_INTEREST)]                 = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_INTEREST);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_BUY_ORDERS_VOLUME)]        = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_BUY_ORDERS_VOLUME);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_SELL_ORDERS_VOLUME)]       = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_SELL_ORDERS_VOLUME);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_OPEN)]                     = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_OPEN);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_CLOSE)]                    = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_CLOSE);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_AW)]                       = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_AW);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_SETTLEMENT)]         = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_SETTLEMENT);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MIN)]          = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MIN);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_SESSION_PRICE_LIMIT_MAX)]          = ::SymbolInfoDouble(this.m_name,SYMBOL_SESSION_PRICE_LIMIT_MAX);
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUME_REAL)]                      = this.SymbolVolumeReal();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMEHIGH_REAL)]                  = this.SymbolVolumeHighReal();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_VOLUMELOW_REAL)]                   = this.SymbolVolumeLowReal();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_OPTION_STRIKE)]                    = this.SymbolOptionStrike();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_ACCRUED_INTEREST)]           = this.SymbolTradeAccruedInterest();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_FACE_VALUE)]                 = this.SymbolTradeFaceValue();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_TRADE_LIQUIDITY_RATE)]             = this.SymbolTradeLiquidityRate();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_HEDGED)]                    = this.SymbolMarginHedged();
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_INITIAL)]              = this.m_margin_rate.Long.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_INITIAL)]          = this.m_margin_rate.BuyStop.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_INITIAL)]         = this.m_margin_rate.BuyLimit.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_INITIAL)]     = this.m_margin_rate.BuyStopLimit.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_LONG_MAINTENANCE)]          = this.m_margin_rate.Long.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOP_MAINTENANCE)]      = this.m_margin_rate.BuyStop.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_LIMIT_MAINTENANCE)]     = this.m_margin_rate.BuyLimit.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_BUY_STOPLIMIT_MAINTENANCE)] = this.m_margin_rate.BuyStopLimit.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_INITIAL)]             = this.m_margin_rate.Short.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_INITIAL)]         = this.m_margin_rate.SellStop.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_INITIAL)]        = this.m_margin_rate.SellLimit.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_INITIAL)]    = this.m_margin_rate.SellStopLimit.Initial;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SHORT_MAINTENANCE)]         = this.m_margin_rate.Short.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOP_MAINTENANCE)]     = this.m_margin_rate.SellStop.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_LIMIT_MAINTENANCE)]    = this.m_margin_rate.SellLimit.Maintenance;
   this.m_double_prop[this.IndexProp(SYMBOL_PROP_MARGIN_SELL_STOPLIMIT_MAINTENANCE)]= this.m_margin_rate.SellStopLimit.Maintenance;
   
//--- Заполнение текущих данных символа
   for(int i=0;i<SYMBOL_PROP_INTEGER_TOTAL;i++)
      this.m_long_prop_event[i][3]=this.m_long_prop[i];
   for(int i=0;i<SYMBOL_PROP_DOUBLE_TOTAL;i++)
      this.m_double_prop_event[i][3]=this.m_double_prop[i];
   
//--- Обновление данных в базовом объекте и поиск изменений
   CBaseObjExt::Refresh();
   this.CheckEvents();
  }
//+------------------------------------------------------------------+

Это все изменения в классе объекта-символа для возможности в последующих статьях работать с подпиской на стакан цен.


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

Для тестирования возьмём советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part62\ под новым именем TestDoEasyPart62.mq5.

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

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

//--- input variables
input    ushort            InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  150;  // StopLoss in points
input    uint              InpTakeProfit        =  150;  // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpDistancePReq      =  50;   // Distance for Pending Request's activate (points)
input    uint              InpBarsDelayPReq     =  5;    // Bars delay for Pending Request's activate (current timeframe)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
input    uchar             InpTotalAttempts     =  5;    // Number of trading attempts
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)
sinput   uint              InpButtShiftX        =  0;    // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 
input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   ENUM_INPUT_YES_NO InpUseBook           =  INPUT_YES;                       // Use Depth of Market
sinput   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;            // Mode of used timeframes list
sinput   string            InpUsedTFs           =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)
sinput   bool              InpUseSounds         =  true; // Use sounds

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

//+------------------------------------------------------------------+
//| Инициализация библиотеки DoEasy                                  |
//+------------------------------------------------------------------+
void OnInitDoEasy()
  {
//--- Проверка на выбор работы с полным списком
   used_symbols_mode=InpModeUsedSymbols;
   if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)
     {
      int total=SymbolsTotal(false);
      string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов.";
      string en_n="\nThe number of symbols on server "+(string)total+".\nMaximal number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols.";
      string caption=TextByLanguage("Внимание!","Attention!");
      string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списков коллекций символов и таймсерий может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\"";
      string en="Full list mode selected.\nIn this mode, the initial preparation of lists of symbol collections and timeseries can take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
      string message=TextByLanguage(ru,en);
      int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
      int mb_res=MessageBox(message,caption,flags);
      switch(mb_res)
        {
         case IDNO : 
           used_symbols_mode=SYMBOLS_MODE_CURRENT; 
           break;
         default:
           break;
        }
     }
//--- Установим начало отсчёта счётчика для замера примерного времени инициализации библиотеки
   ulong begin=GetTickCount();
   Print(TextByLanguage("--- Инициализация библиотеки \"DoEasy\" ---","--- Initializing the \"DoEasy\" library ---"));
//--- Заполнение массива используемых символов
   CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,InpUsedSymbols,array_used_symbols);
//--- Установка типа используемого списка символов в коллекции символов и заполнение списка таймсерий символов
   engine.SetUsedSymbols(array_used_symbols);
//--- Отображение в журнале выбранного режима работы с коллекцией объектов-символов
   string num=
     (
      used_symbols_mode==SYMBOLS_MODE_CURRENT ? ": \""+Symbol()+"\"" : 
      TextByLanguage(". Количество используемых символов: ",". The number of symbols used: ")+(string)engine.GetSymbolsCollectionTotal()
     );
   Print(engine.ModeSymbolsListDescription(),num);
//--- Вывод списка используемых символов сделаем только для MQL5 - в MQL4 нет функции ArrayPrint()
#ifdef __MQL5__
   if(InpModeUsedSymbols!=SYMBOLS_MODE_CURRENT)
     {
      string array_symbols[];
      CArrayObj* list_symbols=engine.GetListAllUsedSymbols();
      for(int i=0;i<list_symbols.Total();i++)
        {
         CSymbol *symbol=list_symbols.At(i);
         if(symbol==NULL)
            continue;
         ArrayResize(array_symbols,ArraySize(array_symbols)+1,SYMBOLS_COMMON_TOTAL);
         array_symbols[ArraySize(array_symbols)-1]=symbol.Name();
        }
      ArrayPrint(array_symbols);
     }
#endif   
//--- Установка используемых таймфреймов
   CreateUsedTimeframesArray(InpModeUsedTFs,InpUsedTFs,array_used_periods);
//--- Отображение выбранного режима работы с коллекцией объектов-таймсерий
   string mode=
     (
      InpModeUsedTFs==TIMEFRAMES_MODE_CURRENT   ? 
         TextByLanguage("Работа только с текущим таймфреймом: ","Work only with the current Period: ")+TimeframeDescription((ENUM_TIMEFRAMES)Period())   :
      InpModeUsedTFs==TIMEFRAMES_MODE_LIST      ? 
         TextByLanguage("Работа с заданным списком таймфреймов:","Work with a predefined list of Periods:")                                              :
      TextByLanguage("Работа с полным списком таймфреймов:","Work with the full list of all Periods:")
     );
   Print(mode);
//--- Вывод списка используемых таймфреймов сделаем только для MQL5 - в MQL4 нет функции ArrayPrint()
#ifdef __MQL5__
   if(InpModeUsedTFs!=TIMEFRAMES_MODE_CURRENT)
      ArrayPrint(array_used_periods);
#endif 
//--- Создание таймсерий всех используемых символов
   engine.SeriesCreateAll(array_used_periods);

//--- Проверка созданных таймсерий - выводим в журнал описания всех созданных таймсерий
//--- (true - только созданные, false - созданные и объявленные)
   engine.GetTimeSeriesCollection().PrintShort(false); // Краткие описания
   //engine.GetTimeSeriesCollection().Print(true);      // Полные описания

//--- Создание тиковых серий всех используемых символов
   engine.GetTickSeriesCollection().CreateTickSeriesAll();
//--- Проверка созданных тиковых серий - выводим в журнал описания всех созданных тиковых серий
   engine.GetTickSeriesCollection().Print();

//--- Создание тестовых файлов ресурсов
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","The sound of a falling coin 1"),sound_array_coin_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Sound fallen coins"),sound_array_coin_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Sound of coins"),sound_array_coin_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"),sound_array_coin_04);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Click on the button sound 1"),sound_array_click_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Click on the button sound 1"),sound_array_click_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Click on the button sound 1"),sound_array_click_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","The sound of the cash machine"),sound_array_cash_machine_01);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red);

//--- Передача в основной класс библиотеки всех имеющихся коллекций
   engine.CollectionOnInit();

//--- Установка магика по умолчанию для всех используемых инструментов
   engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number));
//--- Установка синхронной передачи приказов для всех используемых символов
   engine.TradingSetAsyncMode(false);
//--- Установка количества торговых попыток при ошибке
   engine.TradingSetTotalTry(InpTotalAttempts);
//--- Установка корректных типов истечения и заливки ордера всем торговым объектам
   engine.TradingSetCorrectTypeExpiration();
   engine.TradingSetCorrectTypeFilling();

//--- Установка стандартных звуков торговым объектам всех используемых символов
   engine.SetSoundsStandart();
//--- Установка общего флага использования звуков
   engine.SetUseSounds(InpUseSounds);
//--- Установка множителя спреда торговым объектам символов в коллекции символов
   engine.SetSpreadMultiplier(InpSpreadMultiplier);
      
//--- Установка контрольных значений для символов
   //--- Получаем список всех символов коллекции
   CArrayObj *list=engine.GetListAllUsedSymbols();
   if(list!=NULL && list.Total()!=0)
     {
      //--- В цикле по списку устанавливаем нужные значения для отслеживаемых свойств символов
      //--- По умолчанию всем свойствам установлены значения LONG_MAX, что означает "Не отслеживать данное свойство" 
      //--- Включить или выключить (задать величину меньше LONG_MAX или наоборот - установить значение LONG_MAX) можно в любое время в любом месте программы

      for(int i=0;i<list.Total();i++)
        {
         CSymbol* symbol=list.At(i);
         if(symbol==NULL)
            continue;
         if(InpUseBook)
            symbol.BookAdd();
         if(symbol.Name()==Symbol())
            symbol.Print();
         /*
         //--- Установка контроля увеличения цены символа на 100 пунктов
         symbol.SetControlBidInc(100000*symbol.Point());
         //--- Установка контроля уменьшения цены символа на 100 пунктов
         symbol.SetControlBidDec(100000*symbol.Point());
         //--- Установка контроля увеличения спреда символа на 40 пунктов
         symbol.SetControlSpreadInc(400);
         //--- Установка контроля уменьшения спреда символа на 40 пунктов
         symbol.SetControlSpreadDec(400);
         //--- Установка контроля размера спреда по значению 40 пунктов
         symbol.SetControlSpreadLevel(400);
         */
        }

     }
//--- Установка контрольных значений для текущего аккаунта
   CAccount* account=engine.GetAccountCurrent();
   if(account!=NULL)
     {
      //--- Установка контроля увеличения значения прибыли на 10
      account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0);
      //--- Установка контроля увеличения значения средств на 15
      account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0);
      //--- Установка контрольного уровня прибыли на 20
      account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0);
     }
     
//--- Получим конец отсчёта времени инициализации библиотеки и выведем его в журнал
   ulong end=GetTickCount();
   Print(TextByLanguage("Время инициализации библиотеки: ","Library initialization time: "),TimeMSCtoString(end-begin,TIME_MINUTES|TIME_SECONDS));
  }
//+------------------------------------------------------------------+

Скомпилируем советник, запустим его на графике символа EURUSD, предварительно выбрав в настройках использовать для работы два символа из списка — EURUSD и AUDUSD, и необходимость подписки на стаканы цен всех выбранных символов:


После запуска советника, в журнал будут выведены сообщения об активированной подписке на стаканы цен двух символов, а затем выведены все свойства символа EURUSD, где будет присутствовать и строка нового свойства о состоянии подписки на стакан цен:

Счёт 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10426.13 USD, 1:100, Hedge, Демонстрационный счёт MetaTrader 5
--- Инициализация библиотеки "DoEasy" ---
Работа с предопределённым списком символов. Количество используемых символов: 2
"AUDUSD" "EURUSD"
Работа только с текущим таймфреймом: H1
Таймсерия символа AUDUSD: 
- Таймсерия "AUDUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 6325
Таймсерия символа EURUSD: 
- Таймсерия "EURUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5806
Тиковая серия "AUDUSD": Запрошенное количество дней: 1, Создано исторических данных: 183398
Тиковая серия "EURUSD": Запрошенное количество дней: 1, Создано исторических данных: 148089
Осуществлена подписка на стакан цен  AUDUSD
Осуществлена подписка на стакан цен  EURUSD
============= Начало списка параметров: "EURUSD" (Euro vs US Dollar) ==================
Статус: Форекс символ-мажор
Индекс в окне "Обзор рынка": 2
Пользовательский символ: Нет
Тип цены для построения баров: Бары строятся по ценам Bid
Символ выбран в Market Watch: Да
Символ отображается в Market Watch: Да
Количество сделок в текущей сессии: 0
Общее число ордеров на покупку в текущий момент: 0
Общее число ордеров на продажу в текущий момент: 0
Объем в последней сделке: 0
Максимальный объём за день: 0
Минимальный объём за день: 0
Время последней котировки: 2021.01.26 22:41:04.852
Количество знаков после запятой: 5
Количество знаков после запятой в значении лота: 2
Размер спреда в пунктах: 2
Плавающий спред: Да
Максимальное количество показываемых заявок в стакане: 10
Подписка на стакан цен: Да
Способ вычисления стоимости контракта: Расчет прибыли и маржи для Форекс
Тип исполнения ордеров: Нет ограничений на торговые операции
Дата начала торгов по инструменту: (Отсутствует)
Дата окончания торгов по инструменту: (Отсутствует)
Минимальный отступ от цены закрытия для установки Stop ордеров: 0
Дистанция заморозки торговых операций: 0
Режим заключения сделок: Торговля по потоковым ценам
Модель расчета свопа: Свопы начисляются в пунктах
День недели для начисления тройного свопа: Среда
Расчет хеджированной маржи по наибольшей стороне: Нет
Флаги разрешенных режимов истечения ордера: 
 - Неограниченно (Да)
 - До конца дня (Да)
 - Срок указывается в ордере (Да)
 - День указывается в ордере (Да)
Флаги разрешенных режимов заливки ордера: 
 - Вернуть (Да)
 - Всё/Ничего (Да)
 - Всё/Частично (Нет)
Флаги разрешённых типов ордеров: 
 - Рыночный ордер (Да)
 - Лимит ордер (Да)
 - Стоп ордер (Да)
 - Стоп-лимит ордер (Да)
 - StopLoss (Да)
 - TakeProfit (Да)
 - Закрытие встречным (Да)
Срок действия StopLoss и TakeProfit ордеров: Отложенные ордеры и уровни Stop Loss/Take Profit действительны неограниченно по времени до явной отмены
Тип опциона: Европейский тип опциона – может быть погашен только в указанную дату
Право опциона: Опцион, дающий право купить актив по фиксированной цене
Цвет фона символа в Market Watch: (Отсутствует)
------
Цена Bid: 1.21665
Максимальный Bid за день: 1.21760
Минимальный Bid за день: 1.21078
Цена Ask: 1.21667
Максимальный Ask за день: 1.21760
Минимальный Ask за день: 1.21081
Реальный объём за день: 0.00
Максимальный реальный объём за день: 0.00
Минимальный реальный объём за день: 0.00
Цена исполнения опциона: 0.00000
Значение одного пункта: 0.00001
Рассчитанная стоимость тика для позиции: 1.00
Рассчитанная стоимость тика для прибыльной позиции: 1.00
Рассчитанная стоимость тика для убыточной позиции: 1.00
Минимальное изменение цены: 0.00001
Размер торгового контракта: 100000.00
Накопленный купонный доход: 0.00
Начальная стоимость облигации, установленная эмитентом: 0.00
Коэффициент ликвидности: 0.00
Минимальный объем для заключения сделки: 0.01
Максимальный объем для заключения сделки: 500.00
Минимальный шаг изменения объема для заключения сделки: 0.01
Максимально допустимый общий объем позиции и отложенных ордеров в одном направлении: 0.00
Значение свопа на покупку: -0.70
Значение свопа на продажу: -1.00
Начальная (инициирующая) маржа: 0.00000000
Поддерживающая маржа по инструменту: 0.00000000
Коэффициент взимания начальной маржи по длинным позициям: 1.00000000
Коэффициент взимания начальной маржи по BuyStop ордерам: : (Значение не задано)
Коэффициент взимания начальной маржи по BuyLimit ордерам: : (Значение не задано)
Коэффициент взимания начальной маржи по BuyStopLimit ордерам: : (Значение не задано)
Коэффициент взимания поддерживающей маржи по длинным позициям: : (Значение не задано)
Коэффициент взимания поддерживающей маржи по BuyStop ордерам: : (Значение не задано)
Коэффициент взимания поддерживающей маржи по BuyLimit ордерам: : (Значение не задано)
Коэффициент взимания поддерживающей маржи по BuyStopLimit ордерам: : (Значение не задано)
Коэффициент взимания начальной маржи по коротким позициям: 1.00000000
Коэффициент взимания начальной маржи по SellStop ордерам: : (Значение не задано)
Коэффициент взимания начальной маржи по SellLimit ордерам: : (Значение не задано)
Коэффициент взимания начальной маржи по SellStopLimit ордерам: : (Значение не задано)
Коэффициент взимания поддерживающей маржи по коротким позициям: : (Значение не задано)
Коэффициент взимания поддерживающей маржи по SellStop ордерам: : (Значение не задано)
Коэффициент взимания поддерживающей маржи по SellLimit ордерам: : (Значение не задано)
Коэффициент взимания поддерживающей маржи по SellStopLimit ордерам: : (Значение не задано)
Cуммарный объём сделок в текущую сессию: 0.00
Cуммарный оборот в текущую сессию: 0.00
Cуммарный объём открытых позиций: 0.00
Общий объём ордеров на покупку в текущий момент: 0.00
Общий объём ордеров на продажу в текущий момент: 0.00
Цена открытия сессии: 1.21371
Цена закрытия сессии: 1.21413
Средневзвешенная цена сессии: 0.00000
Цена поставки на текущую сессию: 0.00000
Минимально допустимое значение цены на сессию: 0.00000
Максимально допустимое значение цены на сессию: 0.00000
Размер контракта или маржи для одного лота перекрытых позиций: 100000.00
------
Имя символа: EURUSD
Имя базового актива для производного инструмента: (Отсутствует)
Базовая валюта инструмента: "EUR"
Валюта прибыли: "USD"
Валюта залоговых средств: "EUR"
Источник текущей котировки: (Отсутствует)
Описание символа: "Euro vs US Dollar"
Имя торгового символа в системе международных идентификационных кодов: (Отсутствует)
Адрес интернет страницы с информацией по символу: "http://www.google.com/finance?q=EURUSD"
Путь в дереве символов: "Forex\EURUSD"
Название категории или сектора, к которой принадлежит торговый символ: (Отсутствует)
Название биржи или площадки, на которой торгуется символ: (Отсутствует)
================== Конец списка параметров: "EURUSD" (Euro vs US Dollar) ==================
 
Время инициализации библиотеки: 00:00:09.953

На графике в комментарии будет выведена строка из метода Refresh() класса тиковой серии для символа AUDUSD — количество вновь скопированных тиков, прошлое время, текущее время и общее количество объектов тиковых данных, присутствующих в списке тиковой серии:



Что дальше

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

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

К содержанию

*Статьи этой серии:

Работа с таймсериями в библиотеке DoEasy (Часть 35): Объект "Бар" и список-таймсерия символа
Работа с таймсериями в библиотеке DoEasy (Часть 36): Объект таймсерий всех используемых периодов символа
Работа с таймсериями в библиотеке DoEasy (Часть 37): Коллекция таймсерий - база данных таймсерий по символам и периодам
Работа с таймсериями в библиотеке DoEasy (Часть 38): Коллекция таймсерий - реалтайм обновление и доступ к данным из программы
Работа с таймсериями в библиотеке DoEasy (Часть 39): Индикаторы на основе библиотеки - подготовка данных и события таймсерий
Работа с таймсериями в библиотеке DoEasy (Часть 40): Индикаторы на основе библиотеки - реалтайм обновление данных
Работа с таймсериями в библиотеке DoEasy (Часть 41): Пример мультисимвольного мультипериодного индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 42): Класс объекта абстрактного индикаторного буфера
Работа с таймсериями в библиотеке DoEasy (Часть 43): Классы объектов индикаторных буферов
Работа с таймсериями в библиотеке DoEasy (Часть 44): Класс-коллекция объектов индикаторных буферов
Работа с таймсериями в библиотеке DoEasy (Часть 45): Мультипериодные индикаторные буферы
Работа с таймсериями в библиотеке DoEasy (Часть 46): Мультипериодные, мультисимвольные индикаторные буферы
Работа с таймсериями в библиотеке DoEasy (Часть 47): Мультипериодные мультисимвольные стандартные индикаторы
Работа с таймсериями в библиотеке DoEasy (Часть 48): Мультипериодные мультисимвольные индикаторы на одном буфере в подокне
Работа с таймсериями в библиотеке DoEasy (Часть 49): Мультипериодные мультисимвольные многобуферные стандартные индикаторы
Работа с таймсериями в библиотеке DoEasy (Часть 50): Мультипериодные мультисимвольные стандартные индикаторы со смещением
Работа с таймсериями в библиотеке DoEasy (Часть 51): Составные мультипериодные мультисимвольные стандартные индикаторы
Работа с таймсериями в библиотеке DoEasy (Часть 52): Кроссплатформенность мультипериодных мультисимвольных однобуферных стандартных индикаторов
Работа с таймсериями в библиотеке DoEasy (Часть 53): Класс абстрактного базового индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 54): Классы-наследники абстрактного базового индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 55): Класс-коллекция индикаторов
Работа с таймсериями в библиотеке DoEasy (Часть 56): Объект пользовательского индикатора, получение данных от объектов-индикаторов в коллекции
Работа с таймсериями в библиотеке DoEasy (Часть 57): Объект данных буфера индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 58): Таймсерии данных буферов индикаторов
Работа с ценами в библиотеке DoEasy (Часть 59): Объект для хранения данных одного тика
Работа с ценами в библиотеке DoEasy (Часть 60): Список-серия тиковых данных символа
Работа с ценами в библиотеке DoEasy (Часть 61): Коллекция тиковых серий символов


Прикрепленные файлы |
MQL5.zip (3919.3 KB)
Работа с ценами в библиотеке DoEasy (Часть 63): Стакан цен, класс абстрактной заявки стакана цен Работа с ценами в библиотеке DoEasy (Часть 63): Стакан цен, класс абстрактной заявки стакана цен
В статье начнём разработку функционала для работы со стаканом цен. Создадим класс объекта абстрактной заявки стакана цен и его наследников.
Практическое применение нейросетей в трейдинге (Часть 2). Компьютерное зрение Практическое применение нейросетей в трейдинге (Часть 2). Компьютерное зрение
Применение компьютерного зрения позволит обучать нейронные сети на визуальном представлении ценового графика и индикаторов. Данный метод позволит нам более свободно оперировать всем комплексом технических индикаторов, так как не требует их цифровой подачи в нейронную сеть.
Нейросети — это просто (Часть 11): Вариации на тему GPT Нейросети — это просто (Часть 11): Вариации на тему GPT
Сегодня, наверное, одной из самых передовых языковых моделей нейросетей является GPT-3, которая в максимальном своем варианте содержит 175 млрд. параметров. Конечно, мы не будем создавать подобного монстра в домашних условиях. Но давайте посмотрим, какие архитектурные решения мы можем использовать в своей работе и какие это нам даст преимущества.
Как заработать $1 000 000 в алготрейдинге? На сервисах MQL5.com! Как заработать $1 000 000 в алготрейдинге? На сервисах MQL5.com!
Каждый трейдер приходит на рынок с целью заработать свой первый миллион долларов. Как это сделать без большого риска и не имея стартового капитала? MQL5 сервисы дают такие возможности разработчикам и трейдерам в любой стране мира.