Синхронизация работы экспертов, скриптов и индикаторов

Сергей Ковалев | 16 июня, 2006

Введение

Различают три вида программ, написанных на языке MQL 4 и исполняемых в клиентском терминале MetaTrader 4:
- эксперты;
- скрипты;
- индикаторы.

Каждая из них предназначена для решения определённого круга задач. Дадим краткую характеристику этим программам.

1. Краткая характеристика пользовательских MQL4-программ

1.1. Эксперты
Эксперты - это основной вид программ, используемых для реализации прибыльных стратегий. К отличительным характеристикам эксперта относятся следующие:
1. Возможность использовать встроенные функции, поддерживающие торговые операции.
2. Возможность ручного изменения внешних настроек.
3. Наличие правил запуска специальной функции start(). Она запускается потиково. В момент поступления нового тика обновляются параметры всего окружения, доступного этой функции. Например, принимают новые значения такие переменные, как bid и ask. Завершив исполнение кода, а именно - достигнув оператора return, функция start() заканчивает свою работу и переходит в режим ожидания нового тика.
1.2. Скрипты
Скрипты очень похожи на эксперты, но их характеристики несколько отличаются. Ниже приводятся основные характеристики скриптов:
1. Скрипты также могут использовать функции торговых операций.
2. В скриптах нет возможности изменять параметры внешних настроек.
3. Основной особенностью скриптов является правило, согласно которому специальная функция start() скриптов запускается всего один раз, сразу же после прикрепления к графику и инициализации.

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

1.3. Индикаторы
В отличие от экспертов и скриптов, индикаторы имеют другое назначение:
1. Основным свойством индикаторов является возможность изображения непрерывных кривых линий, отражающих ту или иную закономерность в соответствии с заложенной в них идеей.
2. В индикаторах запрещено использование торговых функций.
3. Индикаторы запускаются потиково.
4. В зависимости от заложенных параметров индикатор может выполнять своё назначение в основном окне финансового инструмента, а также иметь своё подокно и выводить изображение в его рамках.

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

Как видно из представленного описания, ни одна из пользовательских программ не обладает свойствами их всех: эксперт и скрипт не умеют рисовать, индикатор не имеет права торговать, и так далее.

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

2. Постановка задачи

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

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

Можно организовать программу таким образом, чтобы основной код эксперта исполнялся постоянно, без пауз между тиками. Для этого необходимо в специальной функции start() эксперта организовать бесконечный цикл, в который следует вставить весь основной код программы. Если в начале каждого цикла принудительно обновлять информацию об окружении, то вся конструкция может успешно работать. Недостатком зацикленного эксперта является невозможность открыть панель настроек. Попробуйте зациклить эксперт - и вы не сможете его настроить.

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

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

2.2. Информированность
В ряде случаев возникает необходимость получить некоторую информацию о ходе торгов. Например, любому трейдеру хотелось бы знать, что в некоторый момент, например, за две минуты до выхода важных новостей, дилинговый центр изменил минимально допустимую дистанцию для установки отложенных ордеров с обычных 10 п. до 20 п. Кроме того, как правило, трейдер хочет знать причину отказа сервера исполнять торговые приказы. Эту и другую полезную информацию можно отображать текстом в окне индикатора, а по мере поступления новой информации - поднимать строки старых сообщений, освобождая место для новых сообщений торговой системы. В этом случае необходимо объединить торгующий эксперт или скрипт с отображающим индикатором.

2.3. Элементы управления
Если вы используете торговую систему, предполагающую расширенный интерфейс, то элементы управления (графические объекты) удобнее всего располагать в окне индикатора. Так мы можем быть уверены, что свечной тренд не наползёт на наши управляющие значки и таким образом не будет мешать управлению.

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

скрипт - основной код, содержащий аналитику и торговые функции;

эксперт - предоставление панели для настроек;

индикатор - предоставление поля подокна для отображения управляющих элементов и информации.

3. Программные решения

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

3.1. Эксперт
Рассмотрим подробно, из чего состоит и как работает эксперт.

// Expert.mq4
//--------------------------------------------------- include ----
#include <stdlib.mqh>
#include <stderror.mqh> 
#include <WinUser32.mqh> 
//-----------------------------------------------------------------
#include <Peremen_exp.mq4>  // Описание переменных эксперта.
#include <Metsenat_exp.mq4> // Предопределение переменных эксперта.
#include <Del_GV_exp.mq4>   
// Удаление всех  GlobalVariable, созданных экспертом.
#include <Component_exp.mq4> // Проверка наличия компонентов.
#include <Component_uni.mq4> 
// Сообщение в индикаторе об отсутствии компонентов.
//------------------------------------------------------------------
//
// 
//------------------------------------------------------------------
int init()  
  {
    Fishka=1;          // Находимся в init()  
    Metsenat_exp();   // Предопределение переменных эксперта.
    Component_exp();  // Проверка наличия компонентов
    return;
 } 
//------------------------------------------------------------------
int start() 
  { 
    Fishka=2;         // Находимся в start()  
    Component_exp();  // Проверка наличия компонентов
    return;                                                          
 }
//-------------------------------------------------------------------
int deinit() 
  {   
    Fishka=3;         // Находимся в deinit()  
    Component_exp();  // Проверка наличия компонентов
    Del_GV_exp();     // Удаление GlobalVariable экспертa.
    return;
 }
//-------------------------------------------------------------------

В специальной функции init() работают две функции - Metsenat_exp() и Component_exp()

Metsenat_exp() - функция предопределения некоторых переменных.
// Metsenat_exp.mq4
//-----------------------------------------------------------------
int Metsenat_exp()
 {
//--------------------------------------------- Предопределения ---
   Symb     = "_"+Symbol();
   GV       = "MyGrafic_GV_";
//--------------------------------------------- GlobalVariable ----
   GV_Ind_Yes = GV+"Ind_Yes"   +Symb;      
// 0/1 удостоверяет факт загруженности индикатора
   GV_Scr_Yes = GV+"Scr_Yes"   +Symb;      
// 0/1 удостоверяет факт загруженности скрипта
//---------------------------------------------- Обнародование ----
   GV_Exp_Yes = GV+"Exp_Yes"   +Symb;     
   GlobalVariableSet(GV_Exp_Yes, 1 ); 
   GV_Extern  = GV+"Extern"    +Symb;     
   GlobalVariableSet(GV_Extern,  1 ); 
//  AAA используется в качестве примера:
   GV_AAA     = GV+"AAA"       +Symb;     
GlobalVariableSet(GV_AAA,   AAA ); 
//------------------------------------------------------------------
   return;
 }
//------------------------ Конец модуля --------------------------------

Одной из задач для поддержания работоспособности всего приложения является задача отслеживания наличия всех составляющих. Поэтому все компоненты (скрипт, эксперт и индикатор) должны следить друг за другом, в случае отсутствия любого из компонентов - прекратить работу и сообщить об этом пользователю. Для этой цели на начальном этапе работы каждая программа заявляет о своём присутствии посредством публикования глобальной переменной. В данном случае, в функции Metsenat_exp() эксперта, это делается в строке:
   GV_Exp_Yes = GV+"Exp_Yes"   +Symb;     
   GlobalVariableSet(GV_Exp_Yes, 1 );

Функция Metsenat_exp() подчинена специальной функции init() эксперта, то есть используется всего один раз в период загрузки или изменения значений extern-переменных. Факт изменения настроек должен быть известен скрипту, поэтому эксперт сообщает скрипту об этом посредством изменения значения глобальной переменной GV_Extern:

   GV_Extern  = GV+"Extern"    +Symb;     
   GlobalVariableSet(GV_Extern,  1 );

Component_exp()
- функция, предназначенная для слежения за комплектностью. В зависимости от того, в какой специальной функции эксперта используется Component_exp(), ситуация получает то или иное развитие.
// Component_exp.mq4 
//----------------------------------------------------------------
int Component_exp()
 {
//----------------------------------------------------------------
   while( Fishka < 3 &&     // Находимся в init() или start() и ..
      (GlobalVariableGet(GV_Ind_Yes)!=1 || 
       GlobalVariableGet(GV_Scr_Yes)!=1)) 
    {                            // ..до тех пор, пока кого-то нет.
      Complect=0;                // Раз кого-то нет, то некомплект
      GlobalVariableSet(GV_Exp_Yes, 1); 
// Заявим о присутствии эксперта
//-----------------------------------------------------------------
      if(GlobalVariableGet(GV_Ind_Yes)==1 && 
         GlobalVariableGet(GV_Scr_Yes)!=1)
        {//Если есть индикатор,но нет срипта,то..
         Graf_Text = "Не установлен скрипт Script.";  
// Текст сообщения
         Component_uni();                             
// Пропишем текст. сообщ. в окно инд.
        }
//-----------------------------------------------------------------
      Sleep(300);
    }
//-----------------------------------------------------------------
     if(Complect==0)
    {
      ObjectDelete("Necomplect_1"); 
// Удаление уже не нужных сообщений об отсутствии компонентов  
      ObjectDelete("Necomplect_2");       
      ObjectsRedraw();              // Для быстрого удаления 
      Complect=1;        // Если мы вышли из цикла, то все есть
    }
//-----------------------------------------------------------------
   if(Fishka == 3 && GlobalVariableGet(GV_Ind_Yes)==1)
// Находимся в deinit() и есть куда прописать (индикатор)
    { 
//-----------------------------------------------------------------
      if(GlobalVariableGet(GV_Scr_Yes)!=1)  // Если  нет срипта
       {
         Graf_Text = "Не установлены компоненты Expert и Script";
// Сообщение (выгружаемся же)
         Component_uni();     // Пропишем текст. сообщ. в окно инд.
       }
//----------------------------------------------------------------
    }
//----------------------------------------------------------------
   return;
 }
//--------------------- Конец модуля ------------------------------

Факт наличия скрипта и индикатора отслеживается на основе считывания значений соответствующих глобальных переменных - GV_Scr_Yes и GV_Ind_Yes. Если нет одного из компонентов, то управление передаётся в бесконечный цикл до тех пор, пока не будет достигнут полный комплект, то есть пока не будут установлены и скрипт, и индикатор. О текущей ситуации приложение сообщает пользователю посредством функции Component_uni(). Это универсальная функция, входящая в состав всех компонентов.
// Component_uni.mq4
//----------------------------------------------------------------
int Component_uni()
 {
//----------------------------------------------------------------
//----------------------------------------------------------------
   Win_ind = WindowFind("Indicator");                 
// Какой номер окна у нашего индикатора?
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   ObjectCreate ( "Necomplect_1", OBJ_LABEL, Win_ind, 0, 0  );
// Создаём объект в окне индикат
   ObjectSet    ( "Necomplect_1", OBJPROP_CORNER,        3  );
// с коорд. от прав. нижн. угла
   ObjectSet    ( "Necomplect_1", OBJPROP_XDISTANCE,   450  );
// с координатами по Х..
   ObjectSet    ( "Necomplect_1", OBJPROP_YDISTANCE,    16  );
// с координатами по Y..
   ObjectSetText("Necomplect_1", Graf_Text,10,"Courier New",Tomato);
// текст, шрифт и цвет
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
   Graf_Text = "Приложение не работает.";
 // Текст сообщения
   ObjectCreate ( "Necomplect_2", OBJ_LABEL, Win_ind, 0, 0);
// Создаём объект в окне индикат
   ObjectSet    ( "Necomplect_2", OBJPROP_CORNER,        3);
// с коорд. от прав. нижн. угла
   ObjectSet    ( "Necomplect_2", OBJPROP_XDISTANCE,   450);
// с координатами по Х..
   ObjectSet    ( "Necomplect_2", OBJPROP_YDISTANCE,     2);
// с координатами по Y..
   ObjectSetText("Necomplect_2", Graf_Text,10,"Courier New",Tomato);
// текст, шрифт и цвет
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
   ObjectsRedraw();                                // Перерисуемся.
   return;
//----------------------------------------------------------------
 }
//--------------------- Конец модуля -----------------------------

По достижении комплектности управление в эксперте передаётся из цикла к последующему коду, где стираются теперь уже ненужные сообщения о неукомплектованности.

При выгрузке эксперта специальная функция deinit() тоже вызывает Component_exp(), но уже для другой цели - сообщить о выгрузке в текущий момент.

В deinit() эксперта также вызывается функция Del_GV_exp(). Она используется для удаления всех глобальных переменных GlobalVariable, открытых экспертом. В соответствии с неписаным правилом каждая программа при выгрузке должна "почистить за собой", то есть удалить созданные ею глобальные переменные и графические объекты.
// Del_GV_exp.mq4
//----------------------------------------------------------------
int Del_GV_exp()
 {
//----------------------------------------------------------------
   GlobalVariableDel(GV_Exp_Yes      ); 
   GlobalVariableDel(GV_Extern       ); 
   GlobalVariableDel(GV_AAA          ); 
//----------------------------------------------------------------
   return;
 }
//--------------------- Конец модуля -----------------------------


Таким образом, эксперт включается в работу и на всех этапах отслеживает наличие двух других компонентов: один раз в init(), один раз в deinit() и на каждом тике в start(). При такой конструкции эксперта удаётся использовать программу для решения своей задачи - предоставление панели для настроек. В составе файла описания переменных в качестве примера используется переменная ААА и поставленная ей в соответствие глобальная переменная GV_AAA, значение которой считывается из скрипта.

Чтобы понять, каким образом это происходит, рассмотрим как устроен скрипт.

3.2. Скрипт
Код скрипта:

// Script.mq4
//-------------------------------------------------- include ----
#include <stdlib.mqh> 
#include <stderror.mqh>
#include <WinUser32.mqh>
//----------------------------------------------------------------
#include <Peremen_scr.mq4>       
// Файл  описания  переменных скрипта. 
#include <Metsenat_scr.mq4>      
// Предопределение переменных скрипта.   
#include <Mess_graf_scr.mq4>     
// Список графических сообщений.
#include <Novator_scr.mq4>       
// Опрос окружения, получение новых значений некот переменных
#include <Del_GV_scr.mq4>        
// Удаление всех  GlobalVariable, созданных скриптом.
#include <Component_scr.mq4>     
// Проверка наличия компонентов.
#include <Component_uni.mq4>     
// Сообщение в индикаторе об отсутствии компонентов.
#include <Del_Obj_scr.mq4>       
// Удаление всех объектов, созданных программным комплексом.
#include <Work_scr.mq4>          
// Главная рабочая функция скрипта. 
//----------------------------------------------------------------
//
// 
//----------------------------------------------------------------
int init()  
 {
   Fishka = 1;              // Находимся в init()  
   Metsenat_scr();          // Предопределение переменных скрипта.
   return;
 }
//----------------------------------------------------------------
int start()  
 {
   Fishka = 2;                              // Находимся в start()  
   while(true)
    {
      Component_scr();      // Проверка наличия компонентов
      Work_scr();           // Главная рабочая функция скрипта.
    }
   return;
 }
//----------------------------------------------------------------
int deinit() 
 {
   Fishka = 3;               // Находимся в deinit()  
   Component_scr();          // Проверка наличия компонентов
   Del_Obj_scr();            // Удаление созданных графич. объектов
   Del_GV_scr();             // Удаление GlobalVariable скрипта.
   return;
 }
//-----------------------------------------------------------------

Основой представленного кода является наличие бесконечного цикла в специальной функции start(). В коде скрипта применяются функции с похожими названиями и содержанием. Остановимся подробнее на их особенностях. В начале каждого цикла вызывается функция Component_scr().
// Component_scr.mq4   
//----------------------------------------------------------------
int Component_scr()
 {
//----------------------------------------------------------------
   Iter=0;                             // Обнулим счётчик итераций
   while (Fishka <3 &&           // Находимся в init() или start() 
      (GlobalVariableGet(GV_Ind_Yes)!=1 || 
       GlobalVariableGet(GV_Exp_Yes)!=1)) 
    {                              // До тех пор, пока кого-то нет
      GlobalVariableSet(GV_Scr_Yes, 1);               
// Заявим о присутствии скрипта
//-----------------------------------------------------------------
      Iter++;                                   // Счётчик итераций
      if(Iter==1)                     // Первую итерацию пропускаем
       {
         Sleep(500);
         continue;
       }
//-----------------------------------------------------------------
      if(Iter==2)              // На второй итерации принимаем меры
       {
         Complect=0;              // Раз кого-то нет, то некомплект
         for (i=0;i<=31;i++)ObjectDelete(Name_Graf_Text[i]);
// Удаление всех строк
// Сюда можно вставить функцию, обнуляющую очередь торговых операций.
       }
//-----------------------------------------------------------------
      if(GlobalVariableGet(GV_Ind_Yes)==1 && 
          GlobalVariableGet(GV_Exp_Yes)!=1)
       {                    // Если есть индикатор ,но нет эксперта
         Graf_Text = "Не установлен эксперт Expert."; 
// Текст сообщения 
         Component_uni();                             
// Пропишем текст. сообщ. в окно инд.
       }
//-----------------------------------------------------------------
      Sleep(300);
    }
//-----------------------------------------------------------------
   if(Complect==0)         // Это отрабатываем 1 раз при укомплект.
    {
      ObjectDelete("Necomplect_1"); 
// Удаление уже не нужных сообщений..
      ObjectDelete("Necomplect_2"); 
// ..об отсутствии компонентов        
      Mess_graf_scr(1);
// Сообщим юзеру о комплектности
      if( IsExpertEnabled()) 
// Кнопка включена
       {
         Mess_graf_scr(3000);
         Knopka_Old = 1;
       }
      if(!IsExpertEnabled()) 
// Кнопка выключена
       {
         Mess_graf_scr(4000);
         Knopka_Old = 0;
       }
      Complect=1; 
// Достигнут миним.установочний копмлект
      Redraw = 1; 
// Для быстрого удаления 
    }
//-----------------------------------------------------------------
   if(Fishka == 3 && GlobalVariableGet(GV_Ind_Yes)==1)      
// Находимся в deinit()  
    {
      for(i=0;i<=31;i++)ObjectDelete(Name_Graf_Text[i]);    
// Удаление всех строк
//-----------------------------------------------------------------
      if(GlobalVariableGet(GV_Exp_Yes)!=1)                 
// Есть индикатор, но нет эксперта
         Graf_Text="Не установлены компоненты Expert и Script.";
// Сообщение (выгружаемся же)
      if(GlobalVariableGet(GV_Exp_Yes)==1)
// Есть индикатор и эксперт, то..
         Graf_Text="Не установлен скрипт Script."; 
// Сообщение (выгружаемся же)
      Component_uni();             // Пропишем сообщ. в окно индик.
//----------------------------------------------------------------
      ObjectsRedraw();                    // Для быстрого удаления 
    }
//-----------------------------------------------------------------
   return;
 }
//------------------------- Конец модуля ---------------------------

Первым требованием к скрипту является непрерывность его работы. В период обновления extern-переменных эксперт проходит полный установочный цикл. При нажатии на клавишу ОК на панели настроек экперта он выгружается, передавая управление в deinit(), и сразу же после этого загружается снова, проходя последовательно через init() и start(). В результате этого, хоть и ненадолго, но эксперт удаляет из deinit() глобальную переменную, свидетельствующую о его присутствии.

Чтобы в скрипте не было принято ошибочное решение о том, что эксперт не загружен вовсе, в функции Component_scr() имеется небольшой блок, запрещающий принимать это решение на первой итерации:

      Iter++;                         // Счётчик итераций
      if(Iter==1)           // Первую итерацию пропускаем
        {
          Sleep(500);
          continue;
        }

Пятисот милисекунд в подавляющем большинстве случаев будет вполне достаточно, чтобы произошла полная загрузка эксперта. В случае если вы используете более затратный код в init() эксперта, то время необходимо увеличить. Если на второй итерации эксперт всё ещё не заявил о себе, то принимается решение, что его нет вовсе, и скрипт прекращает работу.

      Complect = 0;    // Раз кого-то нет, то некомплект


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

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

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

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

В бесконечном цикле скрипта имеется также функция Work_scr(). Это - основная функция скрипта, в которой должен быть собран собственно весь его основной код.

// Work_scr.mq4
//--------------------------------------------------------------------------------------------------------------------------------жжжжжжж
int Work_scr()  
 {
   Novator_scr();
//-----------------------------------------------------------------
   // Основной код всего приложения.
//------------------------------------------------ Для примера ----
   if(New_Tick==1)                          // На каждом новом тике
    {                                                              
      Alert ("Текущее значение ААА = ", AAA);
    }                                                              
//-----------------------------------------------------------------
   if(Redraw==1)
    {
      ObjectsRedraw();             // Для моментального отображения
      Redraw=0;              // Снимаем флажок перерисовки объектов
    }
//-----------------------------------------------------------------
   Mess_graf_scr(0);
   Sleep(1);                      // На всякий случай от перегрузки
   return;
 }
//------------------------- Конец модуля ---------------------------


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

// Novator_scr.mq4  
//--------------------------------------------------------------------------------------------------------------------------------жжжжжжж
int Novator_scr()  
 {
//----------------------------------------------------------------=
//---------------------------------------- Обновление настроек ----
   if(GlobalVariableGet(GV_Extern)==1) 
// Произошло обновление в эксперте
    {
      Metsenat_scr();         // Обновление переменных скрипта.
      Mess_graf_scr(5000);    // Сообщение о новой настройке.
      Redraw=1;               // Вконце цикла перерисуемся.
    }                                                              
//--------------------------------- Состояние кнопки советника ----
   Knopka = 0;                // Предустановка
   if( IsExpertEnabled()) Knopka = 1; 
// Выясним истинное состояние кнопки
 
   if(Knopka==1 && Knopka_Old==0) 
// Если состояние изменилось на ВКЛ
    {
      Knopka_Old = 1;                // Старое теперь будет такое
      Mess_graf_scr(3);              // Сообщим юзеру об изменениях
    }
   if(Knopka==0 && Knopka_Old==1) 
// Если состояние изменилось на ВЫКЛ
    {
      Knopka_Old = 0;                // Старое теперь будет такое
      Mess_graf_scr(4);              // Сообщим юзеру об изменениях
    }
//-------------------------------------------------- Новый тик ----
   New_Tick=0;                              // Для начала обнулимся
   if (RefreshRates()==true) New_Tick=1; 
// Поймать новый тик легко, если знать как
//-----------------------------------------------------------------
//----------------------------------------------------------------=
   return;
 }
//------------------------------ Конец модуля ----------------------------


Рассмотрим подробнее необходимость этой функции. В начале статьи упоминалось, что всякий раз при загрузке эксперта, а также при обновлении его переменных подчинённая ему функция Metsenat_exp() устанавливает значение переменной GV_Extern равным 1. Для скрипта это означает, что необходимо обновить настройки. Для этого в функции Novator_scr() имеется следующий блок:

//---------------------------------------- Обновление настроек ----
   if (GlobalVariableGet(GV_Extern)==1) 
// Произошло обновление в эксперте
    {
      Metsenat_scr();              // Обновление переменных скрипта.
      Mess_graf_scr(5000);         // Сообщение о новой настройке.
      Redraw=1;                    // Вконце цикла перерисуемся.
    }

Здесь анализируется значение упомянутой переменной, и, в случае если надо обновиться, вызывается функция Metsenat_scr(), которая и производит обновление (считывание новых значений глобальных переменных).
// Metsenat_scr.mq4
//--------------------------------------------------------------------
int Metsenat_scr()
  {
//------------------------------------------------------- int -------
//------------------------------------------------------- double ----
//------------------------------------------------------- string ----
    MyGrafic    = "MyGrafic_";
    Mess_Graf   = "Mess_Graf_";
    Symb        = "_"+Symbol();
    GV          = "MyGrafic_GV_";
//----------------------------------------------- GlobalVariable ----
    GV_Ind_Yes  = GV+"Ind_Yes" +Symb;       
// 0/1 удостоверяет факт загруженности индикатора
     GV_Exp_Yes  = GV+"Exp_Yes" +Symb;       
// 0/1 удостоверяет факт загруженности эксперта
//------------------------------------------------ Обнародование ----
     GV_Scr_Yes  = GV+"Scr_Yes" +Symb;     
    GlobalVariableSet(GV_Scr_Yes,          1 ); 
    GV_Extern   = GV+"Extern"  +Symb;     
    GlobalVariableSet(GV_Extern,           0 ); 
//--------------------------------------------------- Считывание ----
    //  AAA используется в качестве примера:
    GV_AAA      = GV+"AAA"     +Symb;     
    AAA  = GlobalVariableGet(GV_AAA); 
//----------------------------------------------------------------===
     return;
 }
//------------------------  Конец модуля ----------------------------------

Функция Metsenat_scr() в свою очередь устанавливает значение глобальной переменной GV_Extern равным 0. В последующей истории эта переменная остаётся равной 0 до тех пор, пока пользователь снова не откроет окно настроек эксперта.

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

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

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

В составе функции Work_scr() не показаны функции, составляющие основное содержание программы. В статье Учёт ордеров в большой программе мы рассматривали функцию учёта ордеров Terminal(). Если в вашей торговой системе будет использован такой принцип учёта, то функцию Terminal() необходимо включить в состав функции Work_scr() непосредственно после функции Novator_scr().

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

// Mess_graf_scr.mq4
//----------------------------------------------------------------
int Mess_graf_scr(int Mess_Number)
 {
//----------------------------------------------------------------=
   if(Mess_Number== 0)        // Это происходит в каждом цикле Work
    {
      if(Time_Mess>0 && GetTickCount()-Time_Mess>15000) 
// За последние 15 сек цветная печать
       {                       // ..устарела, покрасим строки серым
         ObjectSet(Name_Graf_Text[1],OBJPROP_COLOR,Gray);
// Последние 2 строки
         ObjectSet(Name_Graf_Text[2],OBJPROP_COLOR,Gray);
// Последние 2 строки
         Time_Mess=0;         // Дополн.флажок, чтоб зря не красить
         Redraw=1;            // Потом перерисуемся
       }
      return;                 // Это был такой маленький заход 
    }
//-----------------------------------------------------------------
   Time_Mess=GetTickCount(); // Запоминаем время публикации сообщения
   Por_Nom_Mess_Graf++;      // Считаем строки. Это будет часть имени.
   Stroka_2=0;            // Исходим из того, что сообщ в одну строку
   if(Mess_Number>1000)      
// Но если встретился огромный номер, то номер приводим в чувство, 
// понимаем, что предыдущая строка из этого же сообщения, т.е её не 
// красить серым 
    {
      Mess_Number=Mess_Number/1000; 
      Stroka_2=1; 
    } 
//----------------------------------------------------------------=
   switch(Mess_Number)
    {
//-----------------------------------------------------------------
      case 1:
         Graf_Text = "Необходимые компоненты установлены.";
         Color_GT = LawnGreen; 
         break;
//-----------------------------------------------------------------
      case 2:
         Graf_Text = " ";
         break;
//-----------------------------------------------------------------
      case 3:
         Graf_Text = "Кнопка советников включена.";
         Color_GT = LawnGreen; 
         break;
//-----------------------------------------------------------------
      case 4:
         Graf_Text = "Кнопка советников выключена.";
         Color_GT = Tomato; 
         break;
//-----------------------------------------------------------------
      case 5:
         Graf_Text = "Произошло обновление настроек эксперта.";
         Color_GT = White; 
         break;
//---------------------------------------------------- default ----
      default:
         Graf_Text = "Строка default "+ DoubleToStr( Mess_Number, 0);
         Color_GT = Tomato;
         break;
    }
//----------------------------------------------------------------=
   ObjectDelete(Name_Graf_Text[30]); 
// 30й объект вытесняется, удаляем его
   int Kol_strok=Por_Nom_Mess_Graf;
   if(Kol_strok>30) Kol_strok=30;
//-----------------------------------------------------------------
   for(int lok=Kol_strok;lok>=2;lok--)
// Пройдёмся по именам граф текстов
    {
      Name_Graf_Text[lok]=Name_Graf_Text[lok-1];        
// Переприсваиваем их (поднимаем)
      ObjectSet(Name_Graf_Text[lok],OBJPROP_YDISTANCE,2+14*(lok-1));
//Меняем коор Y(поднимаем)
      if(lok==3 || lok==4 || (lok==2 && Stroka_2==0))
         ObjectSet(Name_Graf_Text[lok],OBJPROP_COLOR,Gray);
//Старые строки красим в серое.. фэ
    }
//-------------------------------------------------------------------
   Graf_Text_Number=DoubleToStr( Por_Nom_Mess_Graf, 0); 
//Уник. часть имени объ = номер сообщ.
   Name_Graf_Text[1] = MyGrafic + Mess_Graf + Graf_Text_Number;
// Формируем имя сообщения.
   Win_ind= WindowFind("Indicator");                    
//Какой номер окна у нашего индикат.?
 
   ObjectCreate ( Name_Graf_Text[1],OBJ_LABEL, Win_ind,0,0);
// Созд. объект в окне индикатора
   ObjectSet    ( Name_Graf_Text[1],OBJPROP_CORNER, 3   );  
// ..с коорд от прв. нижн. угла..
   ObjectSet    ( Name_Graf_Text[1],OBJPROP_XDISTANCE,450); 
// ..с координатами по Х..
   ObjectSet    ( Name_Graf_Text[1],OBJPROP_YDISTANCE, 2);  
// ..с координатами по Y..
   ObjectSetText(Name_Graf_Text[1],Graf_Text,10,"Courier New",
                 Color_GT);
//текст, шрифт, цвет
   Redraw=1;    // Потом перерисуемся
//----------------------------------------------------------------
   return;
 }
//---------------------- Конец модуля ----------------------------
Рассматривать подробно эту функцию нет необходимости. Можно упомянуть лишь о некоторых её особенностях.

1. Все сообщения отображаются графическими средствами.
2. Формальный параметр, передаваемый функции, соответствует номеру сообщения.
3. Если передаваемый параметр имеет значение от 1 до 999, то предыдущая строка текста в окне индикатора обесцвечивается. Если этот параметр более 1000, то печатается сообщение, номер которого равен передаваемому значению, делённому на 1000 и в этом случае предыдущая строка не обесцвечивается.
4. По прошествии пятнадцати секунд после последнего сообщения обесцвечиваются все строки.
5. Для поддержания возможности обесцвечивания строк функцию необходимо время от времени активизировать, поэтому в конце функции Work_scr() стоит обращение:

   Mess_graf_scr(0);


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

3.3. Индикатор

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

// Indicator.mq4
//--------------------------------------------------- include ----
#include <stdlib.mqh>
#include <stderror.mqh>
#include <WinUser32.mqh>
//----------------------------------------------------------------=
#include <Peremen_ind.mq4>       
// Описание переменных индикатора.
#include <Metsenat_ind.mq4>      
// Предопределение переменных индикатора. 
#include <Del_GV_ind.mq4>        
// Удаление всех  GlobalVariable, созданных индикатором.
#include <Component_ind.mq4>     
// Проверка наличия компонентов.
#include <Component_uni.mq4>     
// Сообщение в индикаторе об отсутствии компонентов.
//----------------------------------------------------------------
//
// 
//----------------------------------------------------------------
#property indicator_separate_window
//----------------------------------------------------------------
//
//
//----------------------------------------------------------------
int init()  
 {
   Metsenat_ind();
   return;
 }
//----------------------------------------------------------------=
int start() 
 {
   if(Component_ind()==0) return; // Проверка наличия компонентов
   //...
   return;                                                           
 }
//----------------------------------------------------------------=
int deinit() 
 {
   Del_GV_ind();             // Удаление GlobalVariable индикатора.
   return;
 }
//----------------------------------------------------------------


Из принципиальных особенностей индикатора следует выделить только одну: индикатор проявляется через отдельное подокно:

#property indicator_separate_window

Содержание функций Metsenat_ind() и Del_GV_ind() аналогично содержанию рассмотренных ранее функций, используемых в эксперте и скрипте.

Простое содержание и у функции Component_ind():

// Component_ind.mq4
//--------------------------------------------------------------- 
int Component_ind()
 {
//----------------------------------------------------------------
   if(GlobalVariableGet(GV_Exp_Yes)==1 && 
      GlobalVariableGet(GV_Scr_Yes)==1)
//Если все на месте
    {// Заявляем о присутствии индикатора
      if(GlobalVariableGet(GV_Ind_Yes)!=1)
          GlobalVariableSet(GV_Ind_Yes,1);
      return(1);
    }
//-----------------------------------------------------------------
   if(GlobalVariableGet(GV_Scr_Yes)!=1 && 
      GlobalVariableGet(GV_Exp_Yes)!=1)
    { // Если нет ни скрипта ни эксперта 
      Graf_Text = "Не установлены компоненты Expert и Script.";            
// Текст сообщения
      Component_uni();    / Пропишем текст. сообщ. в окно инд
    }
//-----------------------------------------------------------------
   return(0);
 }
//---------------- Конец модуля -----------------------------------
Как видно из кода, функция Component_ind() делает сообщение только в том случае, если не загружены оба других компонента - и скрипт, и эксперт. В случае отсутствия всего одной из программ никаких действий не производится. Этим полагается, что в случае присутствия в окне финансового инструмента эти программы сами будут отслеживать состав программного комплекса и сами сообщат об этом пользователю.

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

4. Практическое использование

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

Итак, для демонстрации работы приложения необходимо проделать следующее.

1. Закрепить на окне финансового инструмента индикатор. Это явление сразу отзовётся сообщением в окне индикатора:

Не установлены компоненты Expert и Script
Приложение не работает.


2. Закрепить на окне финансового инструмента эксперт. В результате срабатывания функции Component_exp() эксперта в окне индикатора появится сообщение:

Не установлен скрипт Script
Приложение не работает.


3. Закрепить на окно финансового инструмента скрипт. Это событие будет обработано в функции Component_scr() скрипта и отражено в окне индикатора:


Необходимые компоненты установлены.
Кнопка советников включена.


Если же при этом кнопка советников была в режиме запрета торговли, то сообщение будет таким:

Необходимые компоненты установлены.
Кнопка советников выключена.


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

Кнопка советников выключена.
Кнопка советников включена..
Кнопка советников выключена.
Кнопка советников включена.
Кнопка советников выключена.


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

В качестве примера в функцию Work_scr() вставлено потиковое отображение с помощью функции Alert() некоторой переменной из состава настроек эксперта.



Обратим внимание на эту особенность. Функция Work_scr() является составной частью скрипта. В период между тиками основной цикл скрипта успевает обернуться сотни раз, в то время как сообщение с помощью функции Alert() выдаётся кратно тикам.

5. Откройте панель настроек эксперта и измените значение ААА на 3. Скрипт отследит это событие и выдаст в окно индикатора сообщение:

Кнопка советников включена..
Кнопка советников выключена.
Кнопка советников включена.
Кнопка советников выключена.
Произошло обновление настроек эксперта.


А в окне функции Alert() потиково будет отображаться новое значение переменной ААА:




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

Заключение

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

Рассмотренный принцип построения программного комплекса в несколько ином программном исполнении применён в реально работающем приложении AutoGraf, описание которого вы можете найти в статье Графический эксперт AutoGraf .

SK. Днепропетровск. 2006.