English 中文 Español Deutsch 日本語 Português
Текстовые файлы для хранения входных параметров советников, индикаторов и скриптов

Текстовые файлы для хранения входных параметров советников, индикаторов и скриптов

MetaTrader 4Индикаторы | 5 июля 2016, 15:21
10 446 4
Andrei Novichkov
Andrei Novichkov

Введение

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

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

  • Если в свойствах индикатора или советника необходимо будет выбрать единственный источник данных среди десятка индикаторов типа RSI, WPR и т.п., то параметры каждого из них нужно будет создать и сделать доступными в окне свойств, несмотря на то, что понадобятся параметры только одного. Еще один пример такого рода — расписание работы советника на неделю. Зачем хранить в среду то, что было актуально в понедельник и вторник, или будет актуально в пятницу? Но это придется сделать, потому что все расписание будет располагаться во входных параметрах. Другими словами, описывать во входных параметрах динамически создаваемые объекты достаточно затруднительно и малоэффективно.

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


Как хранить параметры в текстовых файлах

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

/*размер массива*/
{array_size},5
/*собственно, сам массив*/
{array_init},0,1,2,3,4

В приведенном примере в начале строки записывается «якорь» или «название секции», затем через запятую — содержимое этой секции. «Якорем» может быть любая уникальная строка символов. Такой файл создается и сохраняется в «песочнице» терминала. Далее в блоке инициализации индикатора, советника или в коде скрипта открываем данный файл для чтения, как файл формата CSV.

Когда приходит время прочитать сохраненный массив, ищем в файле «якорь» по известному имени{array_size}. Переставляем файловый указатель в начало файла вызовом FileSeek(handle,0,SEEK_SET). Ищем следующий якорь, также с известным именем {array_init}, а когда находим его — просто прочитываем строки нужное число раз, преобразуя их согласно типу массива, в данном случае — в integer. В подключаемом файле ConfigFiles.mqh содержится несколько простых функций, реализующих процесс поиска «якоря» и чтения данных.

Другими словами, в общем случае объект, который мы сохраняем в файле, должен быть описан «якорем» с последующей строкой с данными, разделенными запятыми, как требует формат CSV. «Якорей» и строк данных, следующих за ними, может быть сколько угодно, но с одним условием: на один «якорь» полагается только одна строка.

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

Вот таким образом можно было бы организовать хранение расписания работы советника, о котором мы говорили выше:

….......
{d0},0,0,22
{d1},0,0,22
{d2},1,0,22
…........
{d6},0,0,22

Здесь имя «якоря» условно отражает то, что речь идет о дне недели под определенным номером (т. е., {d1} — это день номер один, и так далее). Далее идет значение типа bool, определяющее, торгует советник в этот день или нет (в данном случае d1 не торгует). Если торговли не происходит, то последующие значения можно и не читать, но здесь они оставлены. Наконец, последние два значения типа integer означают начало и конец работы советника в часах.

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

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

Разберем фрагменты кода, взятого из полностью рабочего индикатора. Итак, нашему индикатору для работы необходимы данные по нескольким валютным парам. Индикатор запрашивает данные по таймеру, потом обрабатывает их в соответствии со своей логикой (в данном случае особенности этой логики для нас неинтересны). Необходимо помнить, что брокеры иной раз прибавляют к имени валютной пары различные суффиксы и префиксы, так что, к примеру, из EURUSD может получиться #.EURUSD.ch. Это обстоятельство нужно обязательно учесть, чтобы в коде корректно обращаться к другим валютным парам. Последовательность наших действий будет такой.

1. Создаем текстовый файл broker.cfg со следующим содержимым: 

[PREFIX_SUFFIX],#.,.ch
2. Создаем текстовый файл <имя_индикатора>.cfg со следующим содержимым:
/*Количество валютных пар в строке с «якорем» [CHARTNAMES] */
[CHARTCOUNT],7
/*Имена валютных пар, с графиков которых индикатор читает данные*/
[CHARTNAMES],USDCAD,AUDCAD,NZDCAD,GBPCAD,EURCAD,CADCHF,CADJPY
/*Первой или второй в валютной паре идет основная валюта (в данном случае CAD)*/
[DIRECT],0,0,0,0,0,1,1
3. Помещаем оба файла в песочницу.

4. В коде индикатора определяем несколько вспомогательных функций (либо подключаем файл ConfigFiles.mqh):

// Открывает файл по заданному имени, как файл формата CSV и возвращает его хэндл
   int __OpenConfigFile(string name)
     {
      int h= FileOpen(name,FILE_READ|FILE_SHARE_READ|FILE_CSV,',');
      if(h == INVALID_HANDLE) PrintFormat("Invalid filename %s",name);
      return (h);
     }

//+------------------------------------------------------------------+
//| Функция читает одно значение iRes типа long в секции с именем    |
//| "якоря" strSec в файле с хэндлом handle. В случае успеха         |
//| возвращается true, в случае ошибки возвращается false.           |
//+------------------------------------------------------------------+
   bool _ReadIntInConfigSection(string strSec,int handle,long &iRes)
     {
      if(!FileSeek(handle,0,SEEK_SET) ) return (false);
      string s;
      while(!FileIsEnding(handle))
        {
         if(StringCompare(FileReadString(handle),strSec)==0)
           {
            iRes=StringToInteger(FileReadString(handle));
            return (true);
           }
        }
      return (false);
     }

//+------------------------------------------------------------------+
//| Функция читает массив строк sArray размером count в секции с     |
//| именем "якоря" strSec в файле с хэндлом handle. В случае успеха  |
//| возвращается true, в случае ошибки возвращается false.           |
//+------------------------------------------------------------------+
   bool _ReadStringArrayInConfigSection(string strSec,int handle,string &sArray[],int count)
     {
      if(!FileSeek(handle,0,SEEK_SET) ) return (false);
      while(!FileIsEnding(handle))
        {
         if(StringCompare(FileReadString(handle),strSec)==0)
           {
            ArrayResize(sArray,count);
            for(int i=0; i<count; i++) sArray[i]=FileReadString(handle);
            return (true);
           }
        }
      return (false);
     }

//+------------------------------------------------------------------+
//| Функция читает массив bArray типа bool  размером count в секции  |
//| с именем "якоря" strSec в файле с хэндлом handle. В случае       |
//| успеха возвращается true, в случае ошибки возвращается false.    |
//+------------------------------------------------------------------+
   bool _ReadBoolArrayInConfigSection(string strSec,int handle,bool &bArray[],int count)
     {
      string sArray[];
      if(!_ReadStringArrayInConfigSection(strSec, handle, sArray, count) ) return (false);
      ArrayResize(bArray,count);
      for(int i=0; i<count; i++)
        {
         bArray[i]=(bool)StringToInteger(sArray[i]);
        }
      return (true);
     }
Кроме того, включаем и такой код в индикатор:
   …..
   input string strBrokerFname  = "broker.cfg";       // Имя файла с данными о брокере
   input string strIndiPreFname = "some_name.cfg";    // Имя файла с настройками индикатора
   …..

   string strName[];         // Массив с именами валютных пар ([CHARTNAMES])
   int    iCount;            // Количество валютных пар
   bool   bDir[];            // Массив с «порядком следования» ([DIRECT])

   …..
   int OnInit(void) 
     {

      string prefix,suffix;     // Префикс и суффикс. Локальные переменные, обязательные к 
                                // инициализации, но употребляемые только один раз.
      prefix= ""; suffix = "";
      int h = _OpenConfigFile(strBrokerFname);
      // Читаем данные о префиксе и суффиксе из конфигурационного файла. Если фиксируются 
      // ошибки, то продолжаем, оставляя дефолтные значения.
      if(h!=INVALID_HANDLE) 
        {
         if(!_GotoConfigSection("[PREFIX_SUFFIX]",h)) 
           {
            PrintFormat("Error in config file %s",strBrokerFname);
              } else {
            prefix = FileReadString(h);
            suffix = FileReadString(h);
           }
         FileClose(h);
        }
      ….
      // Читаем настройки индикатора. 
      if((h=__OpenConfigFile(strIndiPreFname))==INVALID_HANDLE)
         return (INIT_FAILED);

      // читаем количество валютных пар
      if(!_ReadIntInConfigSection("CHARTCOUNT]",h,iCount)) 
        {
         FileClose(h);
         return (INIT_FAILED);
        }
      // читаем массив с именами валютных пар, уже прочитав их количество
      if(!_ReadStringArrayInConfigSection("[CHARTNAMES]",h,strName,iCount)) 
        {
         FileClose(h);
         return (INIT_FAILED);
        }

      // Приводим имена валютных пар к требуемому виду
      for(int i=0; i<iCount; i++) 
        {
         strName[i]=prefix+strName[i]+suffix;
        }

      // читаем массив параметров типа bool
      if(!_ReadBoolArrayInConfigSection("[DIRECT]",h,bDir,iCount)) 
        {
         FileClose(h);
         return (INIT_FAILED);
        }
      ….
      return(INIT_SUCCEEDED);
     }

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

Возможные области применения. Недостатки и альтернативные варианты

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

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

Еще один интересный способ применения — одновременное хранение переменной в файле и в области свойств. При этом появляется возможность реализации логики некоего приоритета чтения. Для иллюстрации рассмотрим фрагмент псевдокода на примере инициализации значения магического номера:

…..
extern int Magic=0;
…..
int OnInit(void) 
  {
   …..
   if(Magic==0) 
     {
      // Это означает, что пользователь не инициализировал Magic, и осталось
      // значение по дефолту. Тогда ищем значение переменной Magic в файле
      …...
     }
   if(Magic==0) 
     {
      // По-прежнему ноль, следовательно, в файле такой переменной нет
      // Здесь обрабатываем создавшуюся ситуацию,
      // в данном случае выходим с ошибкой
      return (INIT_FAILED);
     }

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

Справедливости ради, перечислим и недостатки метода.

Первый — невысокая скорость.  С этим можно смириться, если данный способ использовать только на этапе инициализации индикатора/советника, либо периодически, например, на открытии новой дневной свечи.

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

Обратим внимание на альтернативные способы решения задач, примеры которых были приведены.

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

….......
[d0]
Allowed=0
BeginWork=0
EndWork=22
.........
[d2]
Allowed=1
BeginWork=0
EndWork=22

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

Недостаток данного формата в том, что придется подключать сторонние библиотеки. В языках MQL нет средств для непосредственной работы с файлами такого формата, поэтому придется импортировать их из kernell32.dll. На данном сайте представлены сразу несколько библиотек, выполняющих эту операцию, поэтому нет смысла писать еще одну и прикреплять её здесь. Такие библиотеки просты, ведь речь идет об импорте всего двух функций. Но всегда нужно допускать, что библиотеки могут содержать ошибки и помнить о том, что неизвестно, будет ли вся конструкция работоспособна под Linux. Те же пользователи, которых не пугают последствия подключения сторонних библиотек, вполне могут использовать INI-файлы наравне с файлами формата CSV, которым посвящена статья.

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

  1. Использовать системный реестр. На мой взгляд, это чрезвычайно спорный, сомнительный вариант. Реестр — это критическая часть для функционирования и быстродействия операционной системы. Неужели будет правильно разрешить писать в него скриптам и индикаторам? Мне кажется,  что это будет стратегически неверное решение.
  2. Использовать базу данных. Да, это проверенный, надежный способ хранения любых данных и в любом объеме. Но нужно ли хранить значительный объем данных советнику или индикатору? В подавляющем большинстве случаев — нет. Базы данных обладают явно избыточной функциональностью для достижения целей, рассматриваемых здесь. Вместе с тем, о базе данных не стоит забывать, если действительно возникнет необходимость хранения нескольких гигабайтов данных.
  3. Использовать XML. По сути, это те же текстовые файлы, но с другим синтаксисом, в котором придется разбираться отдельно. Плюс ко всему,  чтобы получить возможность работать с XML-файлами, нужно будет написать библиотеку (или скачать готовую). Но даже не в этом состоит самая трудная задача: в конце работы нужно будет изготовить сопроводительную документацию. Оправданы ли будут трудозатраты разработчика? В данном случае это сомнительно.

Заключение

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

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

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



Прикрепленные файлы |
ConfigFiles.mqh (7.32 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Stanislav Korotky
Stanislav Korotky | 6 июл. 2016 в 23:16
Не раскрыта очень важная тема, как в эксперте или индикаторе автоматически отслеживать изменения в файлах настроек. Насколько я знаю, встроенных (эффективных) средств для этого нет, и потому было бы логично указать этот нюанс как еще один недостаток.
Andrei Novichkov
Andrei Novichkov | 7 июл. 2016 в 09:29
Stanislav Korotky:
Не раскрыта очень важная тема, как в эксперте или индикаторе автоматически отслеживать изменения в файлах настроек. Насколько я знаю, встроенных (эффективных) средств для этого нет, и потому было бы логично указать этот нюанс как еще один недостаток.
Встроенных средств для этого действительно нет. Но вот является ли этот факт недостатком? Обратите внимание, для реализации задачи, поставленной Вами, необходима работа с объектами ядра. Нужно ли открывать такой доступ для индикаторов и экспертов метатрейдера? Лично я уверен, что этого делать не стоит. С другой стороны, уведомления об изменениях в файловой системе можно получить, написав и подключив DLL. Другими словами, это еще одна из возможностей по применению текстовых файлов.
[Удален] | 21 окт. 2016 в 05:00
Спасибо за статью , как раз вовремя. Работаем над скриптом который будет брать данные из файла. Вот пример использования. Есть у нас индикаторый который  нужно переодически адаптировать под рынок , волотильность и прочие.  Так вот для каждого периода будет создан свой массив с данными . Скрипт будет перебирать эти данные и лучшие выводить на график и создавать set файл. Как то так в кратце. 
Andrei Novichkov
Andrei Novichkov | 21 окт. 2016 в 10:46
Mihail Gorjunov:
Спасибо за статью , как раз вовремя. Работаем над скриптом который будет брать данные из файла. Вот пример использования. Есть у нас индикаторый который  нужно переодически адаптировать под рынок , волотильность и прочие.  Так вот для каждого периода будет создан свой массив с данными . Скрипт будет перебирать эти данные и лучшие выводить на график и создавать set файл. Как то так в кратце. 
Рад, если статья пригодилась. Метод действительно очень удобный, я его часто использую для скриптов с  "памятью" последних введенных параметров, для нескольких индикаторов, которые должны иметь гарантированно одинаковые настройки. Один раз их прописываешь в файл, все индюки оттуда читают и работают одинаково.
Графические интерфейсы VIII: Элемент "Файловый навигатор" (Глава 3) Графические интерфейсы VIII: Элемент "Файловый навигатор" (Глава 3)
В предыдущих главах восьмой части серии наша библиотека пополнилась несколькими классами для создания указателей для курсора мыши, календарей и древовидных списков. В настоящей статье рассмотрим элемент «Файловый навигатор», который тоже можно будет использовать в качестве части графического интерфейса MQL-приложения.
Графические интерфейсы VIII: Элемент "Древовидный список" (Глава 2) Графические интерфейсы VIII: Элемент "Древовидный список" (Глава 2)
В предыдущей главе восьмой части серии о графических интерфейсах рассматривались элементы «Статический календарь» и «Выпадающий календарь». Вторую главу посвятим не менее сложному составному элементу, такому как «Древовидный список», без которого не обходится ни одна полноценная библиотека для создания графических интерфейсов. Представленная в этой статье реализация древовидного списка содержит в себе множество гибких настроек и режимов, что позволит максимально точно настроить этот элемент управления под свои нужды.
Работа с сокетами в MQL, или Как стать провайдером сигналов Работа с сокетами в MQL, или Как стать провайдером сигналов
Сокеты… Что вообще сейчас в нашем информационном мире может без них существовать? Впервые появившиеся в 1982 г. и практически не изменившиеся до настоящего времени, они исправно работают на нас каждую секунду. Это основа сети, нервные окончания нашей Matrix, в которой мы живем.
Универсальный торговый эксперт: Интеграция со стандартными модулями сигналов MetaTrader (часть 7) Универсальный торговый эксперт: Интеграция со стандартными модулями сигналов MetaTrader (часть 7)
Эта часть статьи посвящена интеграции торгового движка CStrategy с модулями сигналов, входящих в стандартную библиотеку MetaTrader. Материал описывает способы работы с сигналами и создание пользовательских стратегий на их основе.