English 中文 Español Deutsch 日本語 Português
Язык MQL4 для "чайников". Пользовательские индикаторы (часть 2)

Язык MQL4 для "чайников". Пользовательские индикаторы (часть 2)

MetaTrader 4Примеры | 26 декабря 2007, 13:18
27 056 36
Antoniuk Oleg
Antoniuk Oleg

Введение

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


Что такое графические объекты?

Вы часто сталкиваетесь с ними, когда работаете в терминале Meta Trader 4. Вы можете использовать графические объекты для совершенно разных целей. Трейдеры расставляют уровни поддержки и сопротивления, точки разворота, уровни Фибоначчи и многое другое. Давайте посмотрим на простой пример использования объектов:

На этот график было добавлено 4 графических объекта:

  • 2 горизонтальные линии
  • текстовый объект
  • объект-символ (стрелка)

Сегодня мы научимся добавлять такие объекты с помощью MQL4. Представьте себе, сколько рутинных действий вы можете автоматизировать, используя объекты! Например, вам когда-нибудь приходилось рассчитывать точку разворота, уровни поддержки и сопротивления, а потом вручную их рисовать? Да, работы там немного, но автоматизировав этот процесс на MQL4, терминал сам все рассчитает и нарисует соответствующие уровни. Все что вам нужно будет сделать – это дважды кликнуть по названию скрипта, чтобы все сделали за вас. Кроме того, используя графические объекты, можно писать очень полезные сигнальные индикаторы.

Концепция работы с объектами

В MQL4 с любыми графическими объектами работают примерно в такой последовательности:

  • создание объекта
  • изменение его параметров (перемещение, изменение цвета, стиля и т.д.)
  • уничтожение объекта

Получается вот такой «жизненный цикл». Сейчас мы рассмотрим каждую из стадий более детально.

Создание графического объекта

Чтобы нарисовать любой графический объект используют одну универсальную функцию – ObjectCreate(). Вот ее прототип:

bool ObjectCreate(string name, int type, int window, datetime time1, 
                  double price1, datetime time2=0,double price2=0, 
                  datetime time3=0, double price3=0)

Функция возвращает true, если все нормально, и false, если объект не может быть создан и возникла ошибка. Чтобы узнать код ошибки, нужно использовать функцию GetLastError():

if(ObjectCreate(/* аргументы */)==false)
{
   // возникла ошибка, выведем код ошибки в журнал
   Print("Ошибка при вызове ObjectCreate():",GetLastError());
}

Зачем нужен код ошибки? По нему вы сможете найти описание ошибки и устранить ее при возможности. Все описания кодов находятся: Справочник MQL4 -> Стандартные константы -> Коды ошибок.

Рассмотрим все аргументы функции ObjectCreate():

  • name – уникальное название объекта. Вы не можете создать 2 объекта с одним названием. Это название потом будет использоваться в других функциях, чтобы изменять параметры отображения объекта, перемещать его.
  • type – тип объекта. Все типы объектов, которые можно создать находятся: Справочник MQL4 -> Стандартные константы -> Типы объектов. Очень важно отметить, что от типа объекта зависит, нужно ли использовать последние аргументы функции. Посмотрите еще раз на прототип. Последним 4 аргументам присвоены значения по-умолчанию. И вот почему: разные объекты требуют различное количество информации, чтобы создать их. Смотрите, все очень просто. Допустим, вам сейчас нужно нарисовать точку. Какая вам информация потребуется? Очевидно, координаты точки. Например, сколько отложить клеток слева и сверху. Все. Этого будет достаточно, не так ли? А чтобы нарисовать прямоугольник, сколько понадобится точек? Правильно, уже целых 2. Это координаты верхней левой точки и правой нижней. Тоже самое и с функцией ObjectCreate(). Она ведь универсальна, поэтому, если она рисует горизонтальную линию, то ей нужны координаты только одной точки, а если это отрезок прямой, то понадобится уже 2. А если вам захочется нарисовать еще и треугольник, то вы задействуете уже 3 точки. Поэтому, создавая какой-то объект, внимательно изучите, сколько точек нужно задать, чтобы его нарисовать.
  • window – номер окна, в котором рисовать. Если нужно нарисовать объект на графике цен, то есть в главном окне, то использовать нужно только значение 0.
  • time1 – координата Х первой точки. Так как по оси Х в терминале находится время, то нужно указывать значение времени. Например, чтобы узнать время последнего доступного бара, можно использовать предопределенный массив Time[], вот так: Time[0].
  • price1 – координата У первой точки. На этой оси в терминале отображается цена, поэтому нужно использовать значения цен. Например, использовать предопределенные массивы Open[], Close[] и т.д.
  • остальные аргументы – это всего лишь еще 2 пары аналогичных координат, которые определяют точки для рисования более сложных объектов. Если объект простой, то эти параметры не используются.


Пример создания объектов. Рисуем линии

Теперь, чтобы хорошенько разобраться, нарисуем парочку линий. Отметим максимальную и минимальную цену за последний день. Для этого создадим новый скрипт и изменим функцию start():

int  start()
{
   double price=iHigh(Symbol(),PERIOD_D1,0);
   // эта полезная функция возвращает максимальную цену на:
   // * указанном финансовом инструменте, у нас - это Symbol() - 
   //   активный финансовый инструмент
   // * указанном периоде, у нас - это PERIOD_D1 (дневной)
   // * указанном баре, у нас - это 0, последний бар
 
   ObjectCreate("highLine",OBJ_HLINE,0,0,price);
   // рассмотрим все параметры: 
   // "highLine" - уникальное название объекта
   // OBJ_HLINE - тип объекта, который соответствует горизонтальной линии
   // 0 - рисуем объект в главном окне (график цен)
   // 0 - координата по оси Х (время), так как мы выводим горизонтальную
   //     линию, то указывать эту координату не нужно
   // price - координата по оси Y (цена). У нас это максимальная цена
   
   price=iLow(Symbol(),PERIOD_D1,0);
   // функция по аргументам полностью совпадает с iHigh, но возвращает
   // минимальную цену
   
   ObjectCreate("lowLine",OBJ_HLINE,0,0,price);
 
   return(0);
}

Конечно, мы упустили проверку на ошибки. Так что, если вы введете 2 одинаковых названия для обоих объектов, я не виноват. На деле, когда запустите скрипт, это должно выглядеть примерно так:

Линии нарисованы, но есть нечто, что мне совершенно не нравится! Этот насыщенный красный, посмотрите, просто ужасный цвет, всегда используйте только оттенки. Шутка. Конечно, вам еще много чего может не понравиться. Например, толщина линий, их стиль.

Изменение свойств объекта. Настраиваем внешний вид линий

Поэтому существует другая универсальная функция, которая позволяет настроить все параметры любого уже созданного ранее графического объекта. Это – ObjectSet(). Прототип:

bool ObjectSet( string name, int index, double value);

Как и предыдущая функция, эта возвращает true, если все прошло нормально и false, если возникли проблемы. Например, вы указали несуществующее название объекта. Рассмотрим все аргументы этой функции:

  • name – название уже созданного объекта. Убедитесь, что объект с таким названием уже создан, перед тем как пытаться что-то изменить.
  • index – индекс свойства объекта, которое нужно изменить. Все индексы свойств можно найти: Справочник MQL4 -> Стандартные константы -> Свойства объектов. Это ведь тоже универсальная функция. Она работает по такому простому принципу: вы указываете, что изменить, какое свойство, а потом указываете, какое значение присвоить этому свойству.
  • value – а это и есть, то самое значение, на которое нужно изменить выбранное свойство. Например, если вы изменяете цвет, то здесь должно быть указано значение какого-то цвета, логично, не так ли?

Теперь давайте изменим наши линии, а именно, цвет, толщину и стиль. Измените функцию start() того же скрипта:

int  start()
{
   double price=iHigh(Symbol(),PERIOD_D1,0);
 
   ObjectCreate("highLine",OBJ_HLINE,0,0,price);
   price=iLow(Symbol(),PERIOD_D1,0);
   ObjectCreate("lowLine",OBJ_HLINE,0,0,price);
   
   ObjectSet("highLine",OBJPROP_COLOR,LimeGreen);
   // изменяем цвет верхней линии
   ObjectSet("highLine",OBJPROP_WIDTH,3);
   // теперь линия будет толщиной в 3 пикселя
   
   ObjectSet("lowLine",OBJPROP_COLOR,Crimson);
   // изменяем цвет нижней линии
   ObjectSet("lowLine",OBJPROP_STYLE,STYLE_DOT);
   // теперь нижняя линия будет пунктирной   
 
   return(0);
}

На графике вы должны увидеть что-то похожее на это:


Удаление объектов

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

bool ObjectDelete(string name);

Эта функция удаляет объект с указанным названием. Если вы укажите название объекта, который существует лишь в вашем воображении, то вам будет возвращено false.

int ObjectsDeleteAll(int window=EMPTY,int type=EMPTY);

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

ObjectDeleteAll();
// удаляем все подряд

Если вы создали объекты в подокне (например, в окне какого-то индикатора), то указав в первом аргументе номер подокна, можно полностью избавится от объектов в нем. Сейчас указывайте в первом аргументе всегда 0, так как с подокнами мы разберемся позже.

Если вам нужно удалить все объекты определенного типа, то укажите во втором аргументе тип объектов-смертников:

ObjectsDeleteAll(0, OBJ_ARROW);
// удаляем все стрелочки

Как этим всем правильно пользоваться?

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

Смотрите. Для начала откройте Инструментарий (CTRL+T). Там внизу есть много закладок, выберите Справка. Теперь представим, что вы хотите нарисовать какой-то новый графический объект, но пока не умеете этого делать. Для этого вы используете функцию ObjectCreate(), напишите ее, аргументы пока пропустите. Теперь курсор подведите внутрь названия функции и нажмите F1. Вуаля! Посмотрите, в справке отобразилась информация об этой функции. То есть даже ничего искать не нужно. Теперь посмотрите на описание функции. После него следует описание всех аргументов, как в этом уроке. Обратите внимание на описание аргумента type:

Да, это обычная ссылка. Просто кликаем по ней и смотрим, какие вообще существуют графические объекты. Допустим, вам понравился эллипс:

Внимательно читаем описание: нам пишут, что нужно 2 координаты. Приступаем:

int  start()
{
   ObjectCreate("ellipse",OBJ_ELLIPSE,0,Time[100],Low[100],Time[0],High[0]);
   // указываем 2 точки для создания эллипса:
   // * певрая - левая нижняя точка
   // * вторая - правая верхняя 
 
   return(0);
}
Также сказано, что свойство OBJPROP_SCALE устанавливает соотношение сторон, поэтому если выставить его в единицу, то получится обычный круг:

int  start()
{
   ObjectsDeleteAll();
   // очистим график перед рисованием
   
   ObjectCreate("ellipse",OBJ_ELLIPSE,0,Time[100],Low[100],Time[0],High[0]);
   
   ObjectSet("ellipse",OBJPROP_SCALE,1.0);
   // изменяем соотношение сторон
   ObjectSet("ellipse",OBJPROP_COLOR,Gold);
   // заодно и цвет поменяем
 
   return(0);
}

Уверен, что такого круга у вас не получилось, так как сначала нужно выставить единичный масштаб в свойствах графика (Нажимаем правую кнопку мыши на любом пустом месте графика и выбираем Свойства):



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

Создание графических объектов в подокнах

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

//+------------------------------------------------------------------+
//|                                   creatingObjectsInSubWindow.mq4 |
//|                                                     Antonuk Oleg |
//|                                            antonukoleg@gmail.com |
//+------------------------------------------------------------------+
#property copyright "Antonuk Oleg"
#property link      "antonukoleg@gmail.com"
 
#property indicator_separate_window
// индикатор будет рисоваться в отдельном подокне
#property indicator_minimum 1
// минимальное значение индикатора равно 1
#property indicator_maximum 10
// максимальное - 10
 
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
{
   IndicatorShortName("NiceLine");
   // эта простая функция устанавливает короткое название индиктора,
   // то, которое вы видите в левом верхнем углу любого индикатора.
   // Зачем это нужно? Дело в том, что функция WindowFind ищет подокно
   // с указанным ей коротким названием и возвращает его номер.
 
   int windowIndex=WindowFind("NiceLine");
   // находим номер подокна нашего индикатора
   
   if(windowIndex<0)
   {
      // если номер подокна равен -1, то возникла ошибка
      Print("Can\'t find window");
      return(0);
   }  
 
   ObjectCreate("line",OBJ_HLINE,windowIndex,0,5.0);
   // рисуем линию в подокне нашего индикатора
               
   ObjectSet("line",OBJPROP_COLOR,GreenYellow);
   ObjectSet("line",OBJPROP_WIDTH,3);
 
   WindowRedraw();      
   // перерисовываем окно, чтобы увидить линиию
 
   return(0);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
{
   ObjectsDeleteAll();
   // удаляем все объекты
   
   return(0);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
{
   return(0);
}

Запустите индикатор. И о ужас, никакой линии нет!



Спокойно, поменяйте период графика.



Появилась… Что происходит? Дело в том, что узнать номер подокна в функции init(), когда она запускается первый раз индикатором, нельзя. Возможно, это связано с тем, что подокно еще не создано терминалом во время инициализации. Звучит умно, но как это исправить? Нужно просто все делать в фукции start(), когда окно уже создано, например так:

//+------------------------------------------------------------------+
//|                                   creatingObjectsInSubWindow.mq4 |
//|                                                     Antonuk Oleg |
//|                                            antonukoleg@gmail.com |
//+------------------------------------------------------------------+
#property copyright "Antonuk Oleg"
#property link      "antonukoleg@gmail.com"
 
#property indicator_separate_window
#property indicator_minimum 1
#property indicator_maximum 10
 
bool initFinished=false;
// добавляем переменную, которая будет запоминать состояние инициализации.
// false - инициализации еще не было
// true - была
 
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
{
   return(0);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
{
   ObjectsDeleteAll();
   // удаляем все объекты
   
   return(0);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
{
   if(initFinished==false)
   {
      IndicatorShortName("NiceLine");
 
      int windowIndex=WindowFind("NiceLine");
   
      if(windowIndex<0)
      {
         // если номер подокна равен -1, то возникла ошибка
         Print("Can\'t find window");
         return(0);
      }  
 
      ObjectCreate("line",OBJ_HLINE,windowIndex,0,5.0);
      // рисуем линию в подокне нашего индикатора
               
      ObjectSet("line",OBJPROP_COLOR,GreenYellow);
      ObjectSet("line",OBJPROP_WIDTH,3);
 
      WindowRedraw();      
      // перерисовываем окно, чтобы увидить линиию   
      
      initFinished=true;
      // рисование закончено
   }
   
   return(0);
}

Теперь все будет рисоваться с первого раза. Из этого всего вы должны запомнить, что номер подокна нужно узнавать в функции start(), а не init().

Потренируйтесь

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


Пишем сигнальный индикатор. А что это такое?

Представьте себе ситуацию. Трейдер использует несколько индикаторов для принятия решений о входе на рынок: Moving Average, Parabolic SAR и Williams’ Percent Range. Это все встроенные в терминал индикаторы, которые вместе выглядят примерно так:



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

  • Если быстрая средняя поднимается выше медленной, то это сигнал на покупку. Если наоборот, то на продажу.
  • Если цена ниже показателя Parabolic SAR, то это сигнал на продажу и наоборот.
  • Если WPR больше -20, то это сигнал на покупку. Если WPR меньше -80, то это сигнал на продажу.

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

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


Основа

При написании подобного индикатора возникает проблема с рисованием. Ведь все графические объекты рисуются, используя такие координаты, как цена и время. Из-за этого довольно сложно добиться того, чтобы все, что мы рисуем, все время оставалось на своем месте. Пришлось бы постоянно изменять координаты всех объектов. При этом, если вы захотите посмотреть, что происходило раньше и прокрутили бы график, то и вся таблица сигналов также сдвинулась бы. Но из любого правила есть исключение. В арсенале графических объектов есть OBJ_LABEL. Это текстовая метка, которая использует для позиционирования не цену и время, а координаты относительно окна в пикселях. Это очень просто, смотрите:



Мы видим обычную текстовую метку «Х». Если зайти в свойства этого объекта, то вы увидите, что координаты задаются в пикселях. Пиксель – это мельчайшая точка на экране. Обратите внимание, что левый верхний угол окна имеет координаты x=0 и y=0 (0,0). Если увеличивать x, то объект будет двигаться вправо, если уменьшать – влево. Аналогично с координатой y. Если ее увеличивать, то объект будет двигаться вниз, а если уменьшать – вверх. Важно понять и запомнить этот принцип. Чтобы быстрее разобраться, создайте одну метку, передвигайте ее и смотрите, как изменяются координаты в свойствах. Также посмотрите старые котировки, пролистав график. Обратите внимание, что при этом метка неподвижна. Используя такие метки как основу, мы можем создать сигнальный индикатор, который будет избавлен от всех недостатков, о которых писалось выше.


Возможности текстовой метки

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

int init()
{
   // сейчас мы создадим текстовую метку.
   // для этого, как всегда, используем функцию ObjectCreate.
   // координаты указывать не нужно
   ObjectCreate("signal",OBJ_LABEL,0,0,0,0,0);
 
   // изменяем координату х
   ObjectSet("signal",OBJPROP_XDISTANCE,50);
 
   // изменяем координату у
   ObjectSet("signal",OBJPROP_YDISTANCE,50);
 
   // чтобы указать текст метки, используем эту функцию
   ObjectSetText("signal","lambada",14,"Tahoma",Gold);
   // "signal" - название объекта
   // "lambada" - текст метки
   // 14 - размер шрифта
   // Gold - цвет
 
   return(0);
}

Как видите, все довольно просто. Функцию ObjectCreate() мы будем использовать только при инициализации для создания всех необходимых объектов. А с помощью ObjectSetText() будем изменять внешний вид объектов при каждом изменении цены в функции start(). Также сейчас нужно изменить функцию deinit():

int deinit()
{
   // при удалении нашего индикатора, удаляем все объекты
   ObjectsDeleteAll();
 
   return(0);
}

Теперь запустите индикатор и посмотрите на результат:

Мы будем использовать следующие возможности метки:

  • изменим шрифт на Wingdings, чтобы нам стали доступны специальные символы-значки (от квадратиков и кружочков до смайликов):

  • будем изменять цвет и текст метки
  • будем менять расположение и размер метки

Используем шрифт Wingdings

Сейчас давайте создадим метки с использованием шрифта Wingdings. Изменим функцию start():

int init()
{
 
   ObjectCreate("signal",OBJ_LABEL,0,0,0,0,0);
   ObjectSet("signal",OBJPROP_XDISTANCE,50);
   ObjectSet("signal",OBJPROP_YDISTANCE,50);
 
   // используем значки из шрифта Wingdings
   ObjectSetText("signal","lambada",14,"Tahoma",Gold);
   // CharToString() - эта функция возвращает строку с единственным
   // символом, код которого вы указываете в единственном аргументе.
   // Просто выберите понравившийся вам значок из таблички выше и
   // впишите его номер в этой функции
   // 60 - используем крупный шрифт
   // "Wingdings" - используем шрифт Wingdings
 
   return(0);
}

Смотрим на результат:


Рисуем макет таблицы сигналов

Сейчас давайте нарисуем макет таблицы сигналов. Это будет просто куча квадратиков:

int init()
{
   // используем 2 цикла. Первый цикл, со счетчиком "x" рисует поочередно
   // каждый столбец слева направо. Второй цикл рисует значки каждого
   // столбца сверху вниз. На каждой итерации цикла будет создавать по метке.
   // Эти 2 цикла создают 9 столбцов (9 периодов) по 3 метки (3 типа сигналов)
   // в каждом.
   for(int x=0;x<9;x++)
      for(int y=0;y<3;y++)
      {
         ObjectCreate("signal"+x+y,OBJ_LABEL,0,0,0,0,0);
         // создаем очередную метку. Обратите внимание, что название метки
         // создается "на лету" и зависит от счетчиков "x" и "y"
 
         ObjectSet("signal"+x+y,OBJPROP_XDISTANCE,x*20);
         // изменяем координату Х.
         // x*20 - каждая метка создается с интервалом в 20 пикселей по
         // горизонтали и напрямую зависит от счетчика "x"
 
         ObjectSet("signal"+x+y,OBJPROP_YDISTANCE,y*20);
         // изменяем координату Y.
         // y*20 - каждая метка создается с интервалом в 20 пикселей по
         // вертикали и напрямую зависит от счетчика "y"
 
         ObjectSetText("signal"+x+y,CharToStr(110),20,"Wingdings",Gold);
         // используем 110-й код символа, который соответствует квадрату
      }
   
   return(0);
}



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

int init()
{
   for(int x=0;x<9;x++)
      for(int y=0;y<3;y++)
      {
         ObjectCreate("signal"+x+y,OBJ_LABEL,0,0,0,0,0);
         ObjectSet("signal"+x+y,OBJPROP_XDISTANCE,x*20+12);
         // добавим горизонтальный отступ в 12 пикселей
         ObjectSet("signal"+x+y,OBJPROP_YDISTANCE,y*20+20);
         // добавим вертикальный отступ в 20 пикселей
         ObjectSetText("signal"+x+y,CharToStr(110),20,"Wingdings",Gold);
      }
 
   return(0);
}



Оживляем макет

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

int start()
{
   // если быстрая скользящая средняя (период - 13) больше чем медленная,
   // то это сигнал на покупку. Проверяем последний бар
   if(iMA(Symbol(),1,13,0,0,0,0)>iMA(Symbol(),1,24,0,0,0,0))
      ObjectSetText("signal00",CharToStr(110),20,"Wingdings",YellowGreen);
   // изменяем цвет метки с названием "signal00" (самая левая верхняя)
   // на зеленый
 
   else
   // иначе, если быстрая средняя меньше чем медленная, то это сигнал на продажу
      ObjectSetText("signal00",CharToStr(110),20,"Wingdings",Tomato); 
      // изменяем цвет метки на красный
 
   return(0);
}



Оживляем верхний ряд

Продолжим оживление. Самому левому квадратику соответствует самый маленький таймфрейм – M1. Теперь давайте сделаем так, чтобы каждый следующий квадратик (по горизонтали) отвечал за более крупный таймфрейм. Так, например, второй квадрат будет показывать сигналы на M5, третий – на M15 и так далее до MN1. Конечно, все это мы будем делать в цикле. Подумайте сами. Ведь все что изменяется, это только название метки, к которой мы обращаемся и период. Квадратиков всего 9, поэтому используем один счетчик. Но тут возникает проблема с периодами, так как он изменяется без закономерности. Смотрите сами:


Первая мысль: цикл тут не прокатит. А вот и нет! Все что нужно сделать – это объявить специальный массив в самом начале кода индикатора, смотрите:

//////////////////////////////////////////////////////////////////////
//
//                                                  signalTable.mq4 
//                                                     Antonuk Oleg 
//                                            antonukoleg@gmail.com 
//
//////////////////////////////////////////////////////////////////////
#property copyright "Antonuk Oleg"
#property link      "antonukoleg@gmail.com"
 
#property indicator_chart_window
 
int period[]={1,5,15,30,60,240,1440,10080,43200};

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

int start()
{
   // используем цикл, чтобы оживить все квадратики первой линии
   for(int x=0;x<9;x++)
   {
      if(iMA(Symbol(),period[x],13,0,0,0,0)>iMA(Symbol(),period[x],24,0,0,0,0))
         ObjectSetText("signal"+x+"0",CharToStr(110),20,"Wingdings",YellowGreen);
         // "signal"+x+"0" - создаем название метки динамически в зависимости от
         // счетчика "х"
      else
         ObjectSetText("signal"+x+"0",CharToStr(110),20,"Wingdings",Tomato); 
   }
 
   return(0);
}

Мы используем массив period[] как таблицу соответствия значения счетчика «х» и периода. Представьте себе, сколько кода пришлось бы написать, если бы не этот маленький массивчик! Смотрим на результат, первый ряд сигнальных квадратиков готов:





Добавляем надписи

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

#property indicator_chart_window
 
int period[]={1,5,15,30,60,240,1440,10080,43200};  
 
string periodString[]={"M1","M5","M15","M30","H1","H4","D1","W1","MN1"};

Надписи будем создавать в init() используя цикл:

int init()
{
   for(int x=0;x<9;x++)
      for(int y=0;y<3;y++)
      {
         ObjectCreate("signal"+x+y,OBJ_LABEL,0,0,0,0,0);
         ObjectSet("signal"+x+y,OBJPROP_XDISTANCE,x*20+12);
         ObjectSet("signal"+x+y,OBJPROP_YDISTANCE,y*20+20);
         ObjectSetText("signal"+x+y,CharToStr(110),20,"Wingdings",Gold);
      }
 
   // создаем надписи периодов слева направо
   for(x=0;x<9;x++)
   {
      // все как обычно
      ObjectCreate("textPeriod"+x,OBJ_LABEL,0,0,0,0,0);
      ObjectSet("textPeriod"+x,OBJPROP_XDISTANCE,x*20+12);
      ObjectSet("textPeriod"+x,OBJPROP_YDISTANCE,10);
      ObjectSetText("textPeriod"+x,periodString[x],8,"Tahoma",Gold);
      // используем массив periodString[], чтобы указать надписи
   }
   
   return(0);
}




Добавим немного параметров

Пора сделать индикатор более гибким, добавить парочку параметров, чтобы пользователь мог сам настраивать внешний вид индикатора:

#property copyright "Antonuk Oleg"
#property link      "antonukoleg@gmail.com"
 
#property indicator_chart_window
 
extern int scaleX=20, // горизонтальный интервал, с которым создаются квадратики
           scaleY=20, // вертикальный интервал
           offsetX=35, // горизонтальный отступ всех квадратиков
           offsetY=20, // вертикальный отступ
           fontSize=20; // размер шрифта
           
int period[]={1,5,15,30,60,240,1440,10080,43200};
string periodString[]={"M1","M5","M15","M30","H1","H4","D1","W1","MN1"};

Также нужно изменить код функций init() и start(), чтобы все работало:

int init()
{
   for(int x=0;x<9;x++)
      for(int y=0;y<3;y++)
      {
         ObjectCreate("signal"+x+y,OBJ_LABEL,0,0,0,0,0);
         ObjectSet("signal"+x+y,OBJPROP_XDISTANCE,x*scaleX+offsetX);
         ObjectSet("signal"+x+y,OBJPROP_YDISTANCE,y*scaleY+offsetY);
         ObjectSetText("signal"+x+y,CharToStr(110),fontSize,"Wingdings",Gold);
      }
 
   for(x=0;x<9;x++)
   {
      ObjectCreate("textPeriod"+x,OBJ_LABEL,0,0,0,0,0);
      ObjectSet("textPeriod"+x,OBJPROP_XDISTANCE,x*scaleX+offsetX);
      ObjectSet("textPeriod"+x,OBJPROP_YDISTANCE,offsetY-10);
      ObjectSetText("textPeriod"+x,periodString[x],8,"Tahoma",Gold);
   }
   
   return(0);
}
 
int start()
{
   for(int x=0;x<9;x++)
   {
      if(iMA(Symbol(),period[x],13,0,0,0,0)>iMA(Symbol(),period[x],24,0,0,0,0))
         ObjectSetText("signal"+x+"0",CharToStr(110),fontSize,"Wingdings",YellowGreen);
      else
         ObjectSetText("signal"+x+"0",CharToStr(110),fontSize,"Wingdings",Tomato); 
   }
 
   return(0);
}

Оживляем остальные ряды

Второй ряд у нас будет отвечать за сигналы от Williams’ Percent Range, а третий – от Parabolic SAR. Изменяем функцию start():

int start()
{
   for(int x=0;x<9;x++)
   {
      if(iMA(Symbol(),period[x],13,0,0,0,0)>iMA(Symbol(),period[x],24,0,0,0,0))
         ObjectSetText("signal"+x+"0",CharToStr(110),fontSize,"Wingdings",YellowGreen);
      else
         ObjectSetText("signal"+x+"0",CharToStr(110),fontSize,"Wingdings",Tomato); 
   }
 
   // оживляем второй ряд
   for(x=0;x<9;x++)
   {
      // если абсолютное значение WPR меньше 20, то это сигнал на покупку
      if(MathAbs(iWPR(Symbol(),period[x],13,0))<20.0)
         ObjectSetText("signal"+x+"1",CharToStr(110),fontSize,"Wingdings",YellowGreen);   
      // если абсолютное значение WPR больше 80, то это сигнал на продажу
      else if(MathAbs(iWPR(Symbol(),period[x],13,0))>80.0)
         ObjectSetText("signal"+x+"1",CharToStr(110),fontSize,"Wingdings",Tomato);   
      // иначе, если нет сигналов, то закрашиваем квадратик в серый цвет
      else
         ObjectSetText("signal"+x+"1",CharToStr(110),fontSize,"Wingdings",DarkGray);      
   }
 
   // оживляем третий ряд
   for(x=0;x<9;x++)
   {
      // если текущая цена больше значения SAR, то это сигнал на покупку
      if(iSAR(Symbol(),period[x],0.02,0.2,0)<Close[0])
         ObjectSetText("signal"+x+"2",CharToStr(110),fontSize,"Wingdings",YellowGreen);
      // иначе, это сигнал на продажу
      else
         ObjectSetText("signal"+x+"2",CharToStr(110),fontSize,"Wingdings",Tomato);
   }
 
   return(0);
}




Добавляем названия сигналов

Пришло время подписать каждый ряд. Создадим 3 надписи слева, используя массив, как и раньше:

int period[]={1,5,15,30,60,240,1440,10080,43200};
string periodString[]={"M1","M5","M15","M30","H1","H4","D1","W1","MN1"},
       // создаем еще один массив с названиями индикаторов
       signalNameString[]={"MA","WPR","SAR"};

Изменяем функцию init():

int init()
{
   for(int x=0;x<9;x++)
      for(int y=0;y<3;y++)
      {
         ObjectCreate("signal"+x+y,OBJ_LABEL,0,0,0,0,0);
         ObjectSet("signal"+x+y,OBJPROP_XDISTANCE,x*scaleX+offsetX);
         ObjectSet("signal"+x+y,OBJPROP_YDISTANCE,y*scaleY+offsetY);
         ObjectSetText("signal"+x+y,CharToStr(110),fontSize,"Wingdings",Gold);
      }
 
   for(x=0;x<9;x++)
   {
      ObjectCreate("textPeriod"+x,OBJ_LABEL,0,0,0,0,0);
      ObjectSet("textPeriod"+x,OBJPROP_XDISTANCE,x*scaleX+offsetX);
      ObjectSet("textPeriod"+x,OBJPROP_YDISTANCE,offsetY-10);
      ObjectSetText("textPeriod"+x,periodString[x],8,"Tahoma",Gold);
   }
   
   // рисуем названия сигналов сверху вниз
   for(y=0;x<3;y++)
   {
      ObjectCreate("textSignal"+y,OBJ_LABEL,0,0,0,0,0);
      ObjectSet("textSignal"+y,OBJPROP_XDISTANCE,offsetX-25);
      ObjectSet("textSignal"+y,OBJPROP_YDISTANCE,y*scaleY+offsetY+8);
      ObjectSetText("textSignal"+y,signalNameString[y],8,"Tahoma",Gold);
   }
   
   return(0);
}




Добавляем возможность изменять угол привязки

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

  • 0 – левый верхний угол
  • 1 – правый верхний
  • 2 – левый нижний
  • 3 – правый нижний

Поэтому мы добавляем новый параметр – corner:

#property indicator_chart_window
 
extern int scaleX=20,
           scaleY=20, 
           offsetX=35, 
           offsetY=20, 
           fontSize=20,
           corner=0; // добавляем параметр для выбора угла привязки

Осталось изменить функцию init():

int init()
{
   // таблица сигналов
   for(int x=0;x<9;x++)
      for(int y=0;y<3;y++)
      {
         ObjectCreate("signal"+x+y,OBJ_LABEL,0,0,0,0,0);
         ObjectSet("signal"+x+y,OBJPROP_CORNER,corner);
         // изменяем угол привязки
         ObjectSet("signal"+x+y,OBJPROP_XDISTANCE,x*scaleX+offsetX);
         ObjectSet("signal"+x+y,OBJPROP_YDISTANCE,y*scaleY+offsetY);
         ObjectSetText("signal"+x+y,CharToStr(110),fontSize,"Wingdings",Gold);
      }
 
   // названия таймфреймов
   for(x=0;x<9;x++)
   {
      ObjectCreate("textPeriod"+x,OBJ_LABEL,0,0,0,0,0);
      ObjectSet("textPeriod"+x,OBJPROP_CORNER,corner);
      // изменяем угол привязки
      ObjectSet("textPeriod"+x,OBJPROP_XDISTANCE,x*scaleX+offsetX);
      ObjectSet("textPeriod"+x,OBJPROP_YDISTANCE,offsetY-10);
      ObjectSetText("textPeriod"+x,periodString[x],8,"Tahoma",Gold);
   }
 
   // названия индикаторов
   for(y=0;x<3;y++)
   {
      ObjectCreate("textSignal"+y,OBJ_LABEL,0,0,0,0,0);
          ObjectSet("textSignal"+y,OBJPROP_CORNER,corner);
      // изменяем угол привязки
      ObjectSet("textSignal"+y,OBJPROP_XDISTANCE,offsetX-25);
      ObjectSet("textSignal"+y,OBJPROP_YDISTANCE,y*scaleY+offsetY+8);
      ObjectSetText("textSignal"+y,signalNameString[y],8,"Tahoma",Gold);
   }
   
   return(0);
}

Добавляем новые параметры

Осталось добавить еще несколько параметров для гибкой настройки внешнего вида нашего индикатора. Вынесем в параметры:

  • все доступные цвета
  • все коды символов

Сначала объявим все эти параметры в начале кода:

extern int scaleX=20,
           scaleY=20,
           offsetX=35,
           offsetY=20,
           fontSize=20,
           corner=0,
           symbolCodeBuy=110, // код символа для сигнала на покупку
           symbolCodeSell=110, // на продажу
           symbolCodeNoSignal=110; // если сигнала нет
           
extern color signalBuyColor=YellowGreen, // цвет смвола для сигнала на покупку
             signalSellColor=Tomato, // на продажу
             noSignalColor=DarkGray, // нет сигнала
             textColor=Gold; // цвет всех надписей

Изменяем функцию init():

int init()
{
   // таблица сигналов
   for(int x=0;x<9;x++)
      for(int y=0;y<3;y++)
      {
         ObjectCreate("signal"+x+y,OBJ_LABEL,0,0,0,0,0);
         ObjectSet("signal"+x+y,OBJPROP_CORNER,corner);
         ObjectSet("signal"+x+y,OBJPROP_XDISTANCE,x*scaleX+offsetX);
         ObjectSet("signal"+x+y,OBJPROP_YDISTANCE,y*scaleY+offsetY);
         ObjectSetText("signal"+x+y,CharToStr(symbolCodeNoSignal),
                       fontSize,"Wingdings",noSignalColor);
      }
 
   // названия таймфреймов
   for(x=0;x<9;x++)
   {
      ObjectCreate("textPeriod"+x,OBJ_LABEL,0,0,0,0,0);
      ObjectSet("textPeriod"+x,OBJPROP_CORNER,corner);
      ObjectSet("textPeriod"+x,OBJPROP_XDISTANCE,x*scaleX+offsetX);
      ObjectSet("textPeriod"+x,OBJPROP_YDISTANCE,offsetY-10);
      ObjectSetText("textPeriod"+x,periodString[x],8,"Tahoma",textColor);
   }
 
   // названия индикаторов
   for(y=0;x<3;y++)
   {
      ObjectCreate("textSignal"+y,OBJ_LABEL,0,0,0,0,0);
      ObjectSet("textSignal"+y,OBJPROP_CORNER,corner);
      ObjectSet("textSignal"+y,OBJPROP_XDISTANCE,offsetX-25);
      ObjectSet("textSignal"+y,OBJPROP_YDISTANCE,y*scaleY+offsetY+8);
      ObjectSetText("textSignal"+y,signalNameString[y],8,"Tahoma",textColor);
   }
   
   return(0);
}

Изменяем функцию start():

int start()
{
   for(int x=0;x<9;x++)
   {
      if(iMA(Symbol(),period[x],13,0,0,0,0)>iMA(Symbol(),period[x],24,0,0,0,0))
         ObjectSetText("signal"+x+"0",CharToStr(symbolCodeBuy),fontSize,
         "Wingdings",signalBuyColor);
      else
         ObjectSetText("signal"+x+"0",CharToStr(SymbolCodeSell),fontSize,
         "Wingdings",signalSellColor); 
   }
 
   for(x=0;x<9;x++)
   {
      if(MathAbs(iWPR(Symbol(),period[x],13,0))<20.0)
         ObjectSetText("signal"+x+"1",CharToStr(symbolCodeBuy),fontSize,
         "Wingdings",signalBuyColor);   
      else if(MathAbs(iWPR(Symbol(),period[x],13,0))>80.0)
         ObjectSetText("signal"+x+"1",CharToStr(symbolCodeSell),fontSize,
         "Wingdings",signalSellColor);   
      else
         ObjectSetText("signal"+x+"1",CharToStr(symbolCodeNoSignal),fontSize,
         "Wingdings",noSignalColor);      
   }
 
   for(x=0;x<9;x++)
   {
      if(iSAR(Symbol(),period[x],0.02,0.2,0)<Close[0])
         ObjectSetText("signal"+x+"2",CharToStr(symbolCodeBuy),fontSize,
         "Wingdings",signalBuyColor);
      else
         ObjectSetText("signal"+x+"2",CharToStr(symbolCodeSell),fontSize,
         "Wingdings",signalSellColor);
   }
 
   return(0);
}

Изменяем внешний вид

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

extern int scaleX=20,
           scaleY=20,
           offsetX=35,
           offsetY=20,
           fontSize=20,
           corner=2,
           symbolCodeBuy=67, 
           symbolCodeSell=68, 
           symbolCodeNoSignal=73; 
           
extern color signalBuyColor=Gold,
             signalSellColor=MediumPurple,
             noSignalColor=WhiteSmoke,
             textColor=Gold;



Домашнее задание

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


Заключение

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

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/1503

Прикрепленные файлы |
creatingLines.mq4 (1.71 KB)
settingLines.mq4 (1.29 KB)
signalTable.mq4 (4.34 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (36)
Vitaliy Kostrubko
Vitaliy Kostrubko | 19 авг. 2016 в 11:47
Vitalii Ananev:

Это все от того что вы не правильно пишите логические условия. Если CCI ниже -150 то он же одновременно ниже и -100.

Надо писать так:

    if(iCCI(Symbol(),0, 14, PRICE_CLOSE,0)<-100 && iCCI(Symbol(),0, 14, PRICE_CLOSE,0)>-150 )    ObjectSetText("signal"+x+"2",CharToStr(symbolCodeSell_2),fontSize,"Wingdings",signalSellColor);

Для других условий аналогично надо задать некие граничные условия, иными словами  диапазон значений CCI  [-100 ... -150].

Кроме того я вам не рекомендую использовать значение CCI на нулевом баре, так как это значение будет меняться каждый  новый тик пока не закроется текущая свеча. А за это время CCI может несколько раз пересечь заданный уровень и вернутся назад . Лучше использовать первый бар (предыдущий) который уже закрыт и значение его уже не изменятся.

Спасибо тебе, Добрый человек на добром слове ))
Vitaliy Kostrubko
Vitaliy Kostrubko | 19 авг. 2016 в 18:17

Vitalii Ananev:

Для других условий аналогично надо задать некие граничные условия, иными словами  диапазон значений CCI  [-100 ... -150].

Увы! сдался ... эту науку без 100 гр НЕ ПОБЕДИТЬ, а я трезвенник ]
Эдуард Климуш
Эдуард Климуш | 1 дек. 2016 в 16:33

Здравствуйте!

Спасбо большое за статью, буду садиться за написание. У меня вопрос такой:

Можно ли с помощью советника/скрипта/индикатора каким-то образом собирать информацию о текущих состояниях "пальцев" (другими словами - есть ли сигнал  и какой он, если есть) и каждые несколько секунд переводить ее в xml или json? 

Alexey Volchanskiy
Alexey Volchanskiy | 1 дек. 2016 в 20:47
Эдуард Климуш:

Здравствуйте!

Спасбо большое за статью, буду садиться за написание. У меня вопрос такой:

Можно ли с помощью советника/скрипта/индикатора каким-то образом собирать информацию о текущих состояниях "пальцев" (другими словами - есть ли сигнал  и какой он, если есть) и каждые несколько секунд переводить ее в xml или json? 

Удивительное рядом! Для ответа на вопрос достаточно набрать в строке поиска JSON!
Эдуард Климуш
Эдуард Климуш | 7 дек. 2016 в 09:11

Скажите, пожалуйста, а есть ли такая статья (про такое использование графических объектов) в mql5? Просто начинаю изучать этот язык и сразу возникают вопросы типа

В mql4 у нас есть такая штука:

bool ObjectSet( string name, int index, double value);

 А каким образом в mql5 это делать? Допустим, применительно к текущей задаче - каким образом записать

int init()
{
   // сейчас мы создадим текстовую метку.
   // для этого, как всегда, используем функцию ObjectCreate.
   // координаты указывать не нужно
   ObjectCreate("signal",OBJ_LABEL,0,0,0,0,0);
 
   // изменяем координату х
   ObjectSet("signal",OBJPROP_XDISTANCE,50);
 
   // изменяем координату у
   ObjectSet("signal",OBJPROP_YDISTANCE,50);
 
   // чтобы указать текст метки, используем эту функцию
   ObjectSetText("signal","lambada",14,"Tahoma",Gold);
   // "signal" - название объекта
   // "lambada" - текст метки
   // 14 - размер шрифта
   // Gold - цвет
 
   return(0); 
}

 Посмотрел по документации - есть ObjectSetString и ObjectSetInteger есть функции, но каким образом их использовать в данном контексте?

Спасибо большое

P.S. Решил реализовать через класс - туда засунул необходимые функции - этот принцип правильный? 

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