Изменение внешних параметров MQL4-программ без перезагрузки

Alexey Koshevoy | 7 июля, 2008

Введение


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

Суть проблемы

Мы больше коснемся решения проблемы для советников, а не для индикаторов. Для индикаторов проблема не стоит так остро, хотя методика универсальна, и применять её можно во всех видах программ.

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

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

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

Пример 1.
У нас есть среди открытых ордеров несколько таких, которые обслуживаются по особым правилам. Т.е. нам нужно их выделять среди остальной массы ордеров, открытых нашим советником. Для этого можно использовать несколько параметров: Ticket, Magic Number, Currency, Comment. Magic Number используется для того, чтобы отделить наши ордера от остальных, которые открыты в системе другими советниками или вручную. Currency в дополнение к Magic Number позволяет нам использовать один и тот де советник на разных валютных парах. Остаются только два параметра, которые могут быть уникальными для каждого ордера. Comment – наиболее подходящий, может содержать любую текстовую информацию, но имеет один недостаток – он может быть изменен на стороне сервера, например, при частичном закрытии. Ticket является уникальным для каждого ордера, но одного номера недостаточно для того чтобы разделить ордера между собой на классы, соответственно номера ордеров нужно сопоставлять с дополнительной информацией. Как мы видим, в данной ситуации удобнее всего было бы использовать параметр Ticket и хранить в советнике эти номера в соответствии с классами ордеров. Соответственно при перезагрузке массив с номерами будет утерян, и мы не сможем восстановить информацию о виртуальных классах ордеров.

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

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

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

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

Применение

Для решения рассмотренных ранее проблем, описанных в примерах, разработаем комплекс для управления внешними параметрами MQL4 программ. Что же можно делать с помощью такой системы?

По определению мы имеем возможность изменять внешние параметры всех типов:

• Integer
• Bool
• Double
• String
• Datetime

Однако останавливаться на достигнутом не стоит. Для того чтобы осознать весь потенциал данного подхода, стоит рассмотреть принципы работы системы. Рассмотрим самый простой пример.

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

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

Рассмотрим несколько примеров, раскрывающих дополнительные возможности данного подхода. Так как диалог у нас двусторонний, то MQL4 программа может давать ответы на запросы из внешней программы.

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

Попробуем реализовать функцию «действия». Нам нужно чтобы изменение переменной во внешней программе приводило не к изменению соответствующей переменной в MQL4 программе, а к исполнению определенного действия единожды. Реализовать это можно так. Во внешней программе происходит изменение переменной. Переменная может принимать значение «0» и «1». Если значение переменной «1», то MQL4 программа выполняет определенное действие и присваивает переменной значение «0». Теперь это действие можно выполнить снова из внешней программы. Это уже больше чем изменение переменных. С помощью действий можно реализовать много полезных функций. Действия очень похожи на MQL4 скрипты, но с одним значительным отличием. Выполнение скрипта возможно в любой момент работы MetaTrader4 клиента, а выполнение действия произойдет только с вызовом функции start, т.е. в момент изменения текущей цены.

Напоследок можно рассмотреть пример использования метода для передачи данных из внешнего источника в MQL4 программу. Допустим, что нам не нужно изменять внешние переменные, но управление MQL4 программой, а точнее торговым советником, должно производиться из внешнего источника. Внешний источник обрабатывает данные, производит вычисления, и на основании полученных результатов принимает решение об открытии или закрытии ордеров. Для того чтобы открыть ордер, нужно передать в программу основные параметры и команду на открытие. Параметры ордера имеют статус «ожидание», так как их изменение не должно обязывать программу на выполнение действий. В данном примере именно переменная действия является событием на считывание параметров открытия ордера.

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


Разработка комплекса.

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

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

Для хранения информации наиболее универсальным подходом было бы использование технологии XML. Однако возникает ряд проблем. Во-первых, язык MQL4 не имеет встроенных средств для работы с XML, во-вторых, если использовать работу с XML через DLL, то тогда вся система в разы усложнится. Будем использовать обычный текстовый файл и свою собственную структуру представления данных.

Главный объект документа – это переменная или значение. Значение имеет четко определенную структуру, состоящую из следующих элементов:

• Title
• Type
• Status
• Value

Рассмотрим по порядку:

Title – название переменной. Используется программами для идентификации.

Type – тип переменной. В соответствии с типом переменной меняется логика обработки. Типы задаются числовым значением.

Тип Описание Аналог в MQL Значение
0Логическийbool0 – ложь, 1 - истина
1СтроковыйstringНабор символов.
2Знаковый целочисленныйintГраничные значения соответствуют правилам MQL4.
3Знаковый вещественныйdoubleГраничные значения соответствуют правилам MQL4. Разделитель – точка.
4ДатаdatetimeФормат записи соответствует правилам MQL4.
5Действиенет аналога0 – не требует исполнения, 1 - требует исполнения.


Status – переменная статуса. Задается числовым значением. Рассмотрим таблицу возможных вариантов:

Значение Описание
0Ожидание. Не требует обработки.
1Запрос на изменение. MQL4 программа должна отреагировать на это значение статуса.
2Не требует обработки. Это значение для переменных, которые должны быть обработаны по иному событию,
чем непосредственное изменение переменной.

Value – значение переменной. Правила записи значения зависят от типа переменной.
Взаимодействие параметров.

Внешняя программа. Изменение значения.

Если Status=0, то при изменении значения Value значение Status=1.
Если Status=1, то при изменении значения Value значение Status не изменяется.
Если Status=2, то при изменении значения Value значение Status не изменяется.

Внешняя программа. Отмена изменения значения.

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

Если Status=1, то Value изменяется на предыдущее, а Status=0.
Если Status=2, то Value изменяется на предыдущее, а значение Status не изменяется.

MQL4 программа. Обработка изменений.

MQL4 программа учитывает изменения только в том случае, если переменная Status равна 1. По определению, не имеет значения, какой тип переменной был изменен, потому что мы вызываем функцию-обработчик для каждого изменения. Разница состоит лишь в том, будет иметь Value значение или нет. Т.е. является изменение переменной изменением или запросом на выполнение действия. Рассмотрим детально.

Если Status=1 и Type в пределах 0-4, то используем Value и изменяем Status на 0.
Если Status=1 и Type=5, то выполняем действие и изменяем Status на 0, а так же изменяем Value на 0.
Если в ходе выполнения действия нам мы используем дополнительные значения, где Status=2, то после использования значение Status не меняется.

Рассмотрим несколько примеров.

Пример №1. Изменение переменной.
Имеем переменную Var1 типа Integer со значением 10. Переменная находится в состоянии «ожидания».

Title=Var1
Type=2
Status=0
Value=10

Во внешней программе изменяем значение Var1 на 30. И имеем:
Title=Var1
Type=2
Status=1
Value=30

Изменилось значение Value, а так же значение Status на «запрос обработки изменений».

MQL4 программа определяет изменения, вызывает нужную функцию и изменяет документ:
Title=Var1
Type=2
Status=0
Value=30

Значение Value принято и изменилось значение Status на состояние «ожидание».

Пример №2. Выполнение действия.
Имеем переменную Var2 типа Action. Переменная находится в состоянии «ожидания».

Title=Var2
Type=5
Status=0
Value=0

Во внешней программе изменяем значение Var2 на 1. Забегая немного вперед можно сказать, что для того, чтобы избежать ошибок при присвоении неправильного значения переменной действия во внешней программе, мы используем кнопку, а не поле ввода значения. Имеем:
Title=Var2
Type=5
Status=1
Value=1

Изменилось значение Value, а так же значение Status на «запрос обработки изменений».

MQL4 программа определяет изменения, вызывает нужную функцию выполнения действия и изменяет документ:
Title=Var2
Type=5
Status=0
Value=0

Значение Value изменилось на 0 и изменилось значение Status на состояние «ожидание».
Мы можем видеть, что при выполнении действия изменяется не только значение Status, но и значение Value. При изменении переменной меняется только Status, значение переменной остается неизменным.

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

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

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

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

Адаптация для языка MQL4

Переменные

int    ExVH_VarCnt;
string ExVH_FileName;
string ExVH_Project;
 
string ExVH_Title[];
string ExVH_Type[];
string ExVH_Status[];
string ExVH_Value[];

Функции для пользователя

bool ExVH_Open(string FileName)
{
   bool Result=true;
   ExVH_FileName=FileName;
   if (!ExVH_i_Load())
      Result=false;
   if (Result)
      if (!ExVH_i_GetVarCnt())
         Result=false;
   if (Result)
      ExVH_i_Disassemble();
   return(Result);
}
 
int ExVH_Close()
{
   ExVH_i_Assemble();
   ExVH_i_Save();
 
}
 
int ExVH_GetStatus(int Id)
{
   if ((Id<1)||(Id>ExVH_VarCnt))
      return(-1);
   else
      return(StrToInteger(ExVH_Status[Id-1]));
}
 
int ExVH_SetStatus(int Id, int Status)
{
   int Result;
   if ((Id<1)||(Id>ExVH_VarCnt))
      Result=-1;
   else
   {
      Result=1;
      ExVH_Status[Id-1]=Status;
   }
   return(Result);
}
 
string ExVH_GetValue(int Id)
{
   if ((Id<1)||(Id>ExVH_VarCnt))
      return("N/A");
   else
      return(ExVH_Value[Id-1]);
}

Функции внутреннего использования

bool ExVH_i_Load()
{
   bool Result=true;
   ExVH_Project="";
   int i=0;
   int FS=0;
   int handle;
   int Buf[];
   handle=FileOpen(ExVH_FileName,FILE_BIN|FILE_READ);
   if(handle>0)
   {
      FS=FileSize(handle);
      ArrayResize(Buf,FS);
      while(!FileIsEnding(handle)) 
      {
         Buf[i] = FileReadInteger(handle, CHAR_VALUE);
         i++;
      }
      
      FileClose(handle);
      string Str="";
      for (i=0;i<FS;i++)
         Str=Str+CharToStr(Buf[i]);  
      ExVH_Project=Str;
   } else 
      Result=false;
   return(Result);
}
 
bool ExVH_i_Save()
{
   bool Result=true;
   int handle=FileOpen(ExVH_FileName,FILE_BIN|FILE_WRITE);
   if(handle>0)
   {
      FileWriteString(handle,ExVH_Project,StringLen(ExVH_Project));
      FileClose(handle);
   } else
      Result=false;
   return(Result);
}
 
bool ExVH_i_GetVarCnt()
{
   bool Result=true;
   string Value=ExVH_i_GetVarValue("Var_Cnt");
   if (Value=="N/A")
      Result=false;
   else
      ExVH_VarCnt=StrToInteger(Value);
   return(Result);
}
 
void ExVH_i_Disassemble()
{
   int i;
   ArrayResize(ExVH_Title, ExVH_VarCnt);
   ArrayResize(ExVH_Type, ExVH_VarCnt);
   ArrayResize(ExVH_Status, ExVH_VarCnt);
   ArrayResize(ExVH_Value, ExVH_VarCnt);
   
   for (i=0;i<ExVH_VarCnt;i++)
   {
      ExVH_Title[i]=ExVH_i_GetVarValue("Var"+(i+1)+"_Title");
      ExVH_Type[i]=ExVH_i_GetVarValue("Var"+(i+1)+"_Type");
      ExVH_Status[i]=ExVH_i_GetVarValue("Var"+(i+1)+"_Status");
      ExVH_Value[i]=ExVH_i_GetVarValue("Var"+(i+1)+"_Value");
   } 
}
 
void ExVH_i_Assemble()
{
   ExVH_Project="[ExVH 1.0]\r\n\r\n";
   ExVH_Project=ExVH_Project+"Var_Cnt="+ExVH_VarCnt+"\r\n\r\n";
 
   int i;
   for (i=0;i<ExVH_VarCnt;i++)
   {
      ExVH_Project=ExVH_Project+"Var"+(i+1)+"_Title="+ExVH_Title[i]+"\r\n";
      ExVH_Project=ExVH_Project+"Var"+(i+1)+"_Type="+ExVH_Type[i]+"\r\n";
      ExVH_Project=ExVH_Project+"Var"+(i+1)+"_Status="+ExVH_Status[i]+"\r\n";
      ExVH_Project=ExVH_Project+"Var"+(i+1)+"_Value="+ExVH_Value[i]+"\r\n\r\n";
   } 
}
 
string ExVH_i_GetVarValue(string VarName)
{
   string Result="N/A";
   int Start,Stop;
   VarName=VarName+"=";
   Start=StringFind(ExVH_Project,VarName,0);
   if (Start!=-1)
   {
      Start=Start+StringLen(VarName);
      Stop=StringFind(ExVH_Project,CharToStr('\n'),Start);
      if (Stop!=-1)
      {
         Stop=Stop-1;
         Result=StringSubstr(ExVH_Project,Start,Stop-Start);
      }
   }
   return(Result);
}

Адаптация для языка С++

Проект написан на C++ Builder 2007. Архив с сходными кодами с именем ExVH_CPP.zip прикреплен к статье.

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

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


[ExVH 1.0]

Var_Cnt=5

Var1_Title=Boolean test
Var1_Type=0
Var1_Status=2
Var1_Value=0

Var2_Title=String test
Var2_Type=1
Var2_Status=2
Var2_Value=Hello world!

Var3_Title=Integer test
Var3_Type=2
Var3_Status=0
Var3_Value=12345

Var4_Title=Double test
Var4_Type=3
Var4_Status=0
Var4_Value=123.45

Var5_Title=Action test
Var5_Type=5
Var5_Status=0
Var5_Value=0

Сохраним документ под названием ExVH.evh в папку [MetaTrader]/experts/files/

Сигнатура [ExVH 1.0] позволяет нам распознать документ при открытии файла, а так же говорит о версии используемой структуры документа. Если структура документа будет меняться, то сигнатура должна быть изменена тоже. Это позволит избежать путаницы, учитывая, что расширение файла документа при этом останется неизменным.

Var_Cnt=5. Данная запись говорит нам о том, что документ содержит 5 переменных.

Далее идут однотипные записи, в которых указана информация по каждой переменной. Записи сделаны в соответствии с вышеописанными спецификациями.

VarX_Title=
VarX_Type=
VarX_Status=
VarX_Value=

Итак, имеем две переменные (Var1 и Var2), которые должны считываться по возникновению действия (Var5), и две переменные (Var3 и Var4), изменения которых должны учитываться независимо.
MQL4 программа должна выводить соответствующее сообщение на экран при изменениях в переменных.


Тестовый MQL4 код.

int init()
{
   return(0);
}
 
int deinit()
{
   return(0);
}
 
int start()
{
   if (!ExVH_Open("ExVH.evh"))
      return(0);
   
   // Checking for action status
   if (ExVH_GetStatus(5)==1)
   {
      Alert("Actioned!");
      string VarValue;
      if (ExVH_GetValue(1)=="1")
         VarValue="true";
      else 
         VarValue="false";
      
      // Boolean variable value
      Alert("Boolean test variable="+VarValue);
      
      // String variable value
      Alert("String test variable="+ExVH_GetValue(2));
      
      ExVH_SetStatus(5,0);
   }
   
   // Integer variable value
   if (ExVH_GetStatus(3)==1)
   {
      Alert("Integer test variable has been changed. New value="+ExVH_GetValue(3));
      ExVH_SetStatus(3,0);
   }
   
   // Double variable value
   if (ExVH_GetStatus(4)==1)
   {
      Alert("Double test variable has been changed. New value="+ExVH_GetValue(4));
      ExVH_SetStatus(4,0);
   }
   
   ExVH_Close();
   return(0);
}


Запуск и тестирование.
Не имеет значения, какую часть комплекса запускать в первую очередь. Пусть в данном случае это будет MetaTrader 4. Предварительно копируем файл ExVH_Demo.mq4 в папку [MetaTrader]/experts/ и затем запускаем Terminal. В коде программы уже прописан документ ExVH.evh. Запускаем советник. Ничего не должно произойти, советник будет ожидать изменений в файле.

Запускаем предварительно установленный на компьютер ExVH.exe.


Общий вид программы



Открываем меню Project и выбираем пункт Open…


Открытие документа



Программа загружает переменные, и теперь мы можем их изменять.

В колонке статус отображается два значения idle (ожидание) и Changed by ExVH (изменения были сделаны во внешней программе). Вот так выглядит экран после того как мы активировали наш Action.


Общий вид программы с активированным Action



Изменения для разных видов переменных происходят по разному.

Для переменной типа boolean программа выдает одно из двух сообщений



 



Для активации Action такое





Для всех остальных типов переменных унифицированная отдельная форма.

Форма для изменения значений переменных



После нескольких изменений экран Alerts в терминале может иметь такой вид

Пример сообщений при работе программы


Недостатки

Каждая хорошая идея имеет как достоинства, так и недостатки.


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

Что можно сделать дополнительно

Возможности системы можно расширять в нескольких направлениях. Поле для деятельности практически безгранично.

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

Заключение


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

Файлы, прикрепленные к статье

Файл Описание
ExVH_Demo.mq4Тестовый торговый советник
ExVH_Project.zipПодготовленный файл проект
ExVH_CPP.zipИсходные коды внешней программы
ExVH_Install.zipУстановочная версия внешней программы

Примечание. Установочная версия ExVH сделана при помощи демо версии программы Advanced Installer.