Доброго времени суток!
В этом уроке мы напишем простенький индикатор, цель которого — отображение в отдельном окне значений среднего и максимального спреда по каждой свече с помощью линий. Это нужно, чтобы понять общую тенденцию изменения спреда у вашего брокера по заданной торговой паре. Приступим!
С помощью меню «Файл» создаем новый пользовательский индикатор. Назовем его незамысловато «2.6 Spread Indicator». Внешние параметры задавать не нужно, жмем кнопку «Далее». Обработчики событий оставляем по умолчанию, жмем «Далее».
Теперь давайте попробуем настроить параметры отображения пользовательского индикатора. В первую очередь нам нужно, чтобы данные отображались в отдельном окне, так называемом «подвале«. Для этого устанавливаем галочку «Индикатор в отдельном окне». У данного типа индикатора есть возможность выставить пределы (границы) отображения. Как нам известно, спред не может быть отрицательным, иначе бы брокер платил нам его со своего кармана, такого в нашей вселенной быть не может. Поэтому установим галочку «Минимум» и выставим значение 0. Кнопку «Максимум» мы не трогаем, так как не знаем предела жадности брокера и поставщиков ликвидности на счетах с плавающим спредом.
Теперь нам нужно создать два буфера для хранения и отображения данных о спреде. На прошлом уроке мы сделали это вручную, сейчас давайте воспользуемся возможностями конструктора MetaEditor. Нажмем кнопку «Добавить» и создадим два буфера типа «линия» разных цветов.
Все, жмем кнопку «Готово». Созданный код индикатора будет иметь вид:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#property copyright "Copyright (c) DaVinci FX Group" #property link "https://www.davinci-fx.com/" #property version "1.00" #property strict #property indicator_separate_window #property indicator_minimum 0 #property indicator_buffers 2 #property indicator_plots 2 //--- plot AvrSpread #property indicator_label1 "AvrSpread" #property indicator_type1 DRAW_LINE #property indicator_color1 clrSpringGreen #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- plot MaxSpread #property indicator_label2 "MaxSpread" #property indicator_type2 DRAW_LINE #property indicator_color2 clrRed #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- indicator buffers double AvrSpreadBuffer[]; double MaxSpreadBuffer[]; |
По данным свойств мы видим, что индикатор будет отображаться в отдельном окне indicator_separate_window. У него есть предел минимума, равный нулю: indicator_minimum 0. Индикатор имеет 2 буфера для выдачи информации в окне данных indicator_buffers 2. Далее идут настройки для каждого буфера отдельно:
- indicator_label1 — имя буфера для отображения в окне данных
- indicator_type1 — тип отрисовки индикатора, в нашем случае это линия (DRAW_LINE)
- indicator_color1 — цвет графического объекта (линии) индикатора.
- indicator_style1 — стиль отрисовки линии. По умолчанию указана сплошная линия STYLE_SOLID
- indicator_width1 — ширина отображаемого графического объекта. По умолчанию равна единице.
Аналогичные настройки заданы и для второго буфера. При желании их можно изменять по вашему усмотрению, например увеличить толщину линии до 2, но в этом случае нужно не забывать, что линия с толщиной более единицы может отображаться только сплошного типа. Все эти настройки можно указать в функции OnInit, как мы делали на прошлом уроке. Разницы тут нет никакой, все зависит от удобства работы для каждого программиста.
В самом конце этого кода у нас объявлены два динамических массива: AvrSpreadBuffer и MaxSpreadBuffer. В функции OnInit появились две строчки, которые привяжут их к буферам:
0 1 2 3 4 5 |
int OnInit() { SetIndexBuffer(0,AvrSpreadBuffer); SetIndexBuffer(1,MaxSpreadBuffer); return(INIT_SUCCEEDED); } |
Теперь нам нужно сделать расчет спреда для того, чтобы передать его значения в буферы. Но предварительно мы должны указать еще одно условия в функции OnInit — перевод показаний спреда в старый вид отображения пунктов. О старых и новых пунктах я писал свое отступление в этом уроке. Я привык все значения в пунктах отображать в старых единицах измерения, т.е. 2 и 4 знака после запятой. Поэтому добавим в код переменную PipsDivided, на которую будем делить значение спреда. Объявить ее нужно на глобальном уровне вне функций.
0 |
int PipsDivided = 1; |
Далее пишем условие:
0 1 2 3 4 5 6 7 |
int OnInit() { SetIndexBuffer(0,AvrSpreadBuffer); SetIndexBuffer(1,MaxSpreadBuffer); if(Digits == 3 || Digits == 5) PipsDivided = 10; return(INIT_SUCCEEDED); } |
Соответственно, если у валютной пары 3 (для JPY) или 5 знаков после запятой, значит данный счет у брокера учитывает новые пункты и множитель PipsDivided становится равным 10.
Переходим к функции обработки событий OnCalculate. Узнаем спред с помощью функции MarketInfo.
0 1 2 3 4 5 6 7 8 9 10 11 12 |
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { double SPREAD = NormalizeDouble(MarketInfo(NULL,MODE_SPREAD)/PipsDivided,1); } |
Полученное значение спреда делим на 10, если счет в новых пунктах, либо на единицу (т.е. не делим), если в старых. Полученное значение нужно округлить до десятых, для этого нужна функция NormalizeDouble.
Стоит заметить, что значение спреда можно также получить с помощью текущих цен Ask и Bid, сделав вычитание и поделить полученное значение на пункты Point.
0 |
double SPREAD = NormalizeDouble((Ask-Bid)/Point/PipsDivided,1); |
Либо же такой вариант написания, выбирать вам.
0 |
double SPREAD = NormalizeDouble((MarketInfo(NULL,MODE_ASK)-MarketInfo(NULL,MODE_BID))/Point/PipsDivided,1); |
Теперь, когда мы знаем спред на каждом тике, нам нужно рассчитать его среднее значение на свечу. Для этого нужно суммировать весь полученный спред и разделить его на количество этих значений. Для этого введем две переменных счетчика counter и sumspread. Также нам необходимо знать, когда откроется новая свеча, чтобы обнулить значения этих переменных. Для этого нужна еще одна переменная TimeNewBar типа datetime. Все три переменные нужно объявить вне функций на глобальном уровне.
0 1 2 |
int counter = 0; double sumspread = 0; datetime TimeNewBar = 0; |
Теперь напишем условие в функции OnCalculate, которое будет сравнивать время открытия текущей свечи и сохраненное время в переменной TimeNewBar. Если оно не равно, значит уже открылась новая свеча и счетчики нужно обнулить, а значение переменной времени TimeNewBar переписать.
0 1 2 3 4 |
if(TimeNewBar != iTime(NULL,PERIOD_CURRENT,0)) { TimeNewBar = iTime(NULL,PERIOD_CURRENT,0); counter = 0; sumspread = 0; } |
Данное условие будет выполнять только 1 раз в свечу, что нам и нужно. Теперь нужно активировать запись значений счетчика и суммарного спреда каждый тик. Пишем ниже:
0 1 |
counter++; sumspread += SPREAD; |
Полученных данных достаточно, чтобы рассчитать среднее значение спреда для текущей свечи, поэтому ниже пишем формулу для буфера AvrSpreadBuffer:
0 |
AvrSpreadBuffer[0] = NormalizeDouble(sumspread/counter,1); |
Ноль в квадратных скобках массива обозначает, что буфер записывается для текущей свечи. Полученное значение нужно также округлить до десятых с помощью функции NormalizeDouble. Соответственно запись для текущей свечи прекратиться после открытия новой и будет сохранена в буфере в истории.
Теперь нам необходимо узнать значение максимального спреда, которое было зафиксирован для каждой свечи. Для этого нужно написать условие, которое сравнивает текущий спред с уже сохраненным в памяти значением максимального спреда.
0 1 2 |
if(SPREAD > MaxSpreadBuffer[0] || MaxSpreadBuffer[0] == EMPTY_VALUE) { MaxSpreadBuffer[0] = NormalizeDouble(SPREAD,1); } |
Т.е. если текущий спред превышает максимальный, либо же буфер максимального спреда не содержит никакого значения (EMPTY_VALUE), то его значение переписывается.
Полученный код уже можно применять на графике и следить за изменениями значений линий буферов. Но для большей наглядности давайте добавим к каждой линии текст, который будет отображать значение среда на каждом тике. Для этого создадим пользовательскую функцию TextCreate:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
bool TextCreate(const long chart_ID=0, // ID графика const string name="Text", // имя объекта const int sub_window=0, // номер подокна datetime time=0, // время точки привязки double price=0, // цена точки привязки const string text="Text", // сам текст const string font="Arial", // шрифт const int font_size=9, // размер шрифта const color clr=clrRed, // цвет const double angle=0.0, // наклон текста const ENUM_ANCHOR_POINT anchor=ANCHOR_LEFT_UPPER, // способ привязки const bool back=false, // на заднем плане const bool selection=false, // выделить для перемещений const bool hidden=false, // скрыт в списке объектов const long z_order=0) // приоритет на нажатие мышью { ResetLastError(); if(ObjectFind(chart_ID,name) == -1){ if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price)) { Print(__FUNCTION__,": не удалось создать объект \"Текст\"! Код ошибки = ",GetLastError()); return(false); } } ObjectSetDouble(chart_ID,name,OBJPROP_PRICE,price); ObjectSetInteger(chart_ID,name,OBJPROP_TIME,time); ObjectSetString(chart_ID,name,OBJPROP_TEXT,text); ObjectSetString(chart_ID,name,OBJPROP_FONT,font); ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size); ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle); ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor); ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden); ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order); return(true); } |
Все параметры функции расписаны в этом коде, функция взята из справочника mql. Единственное отличие — мы добавили проверку на существование объекта на графике ObjectFind. Т.е. если объект не найден, мы его создаем заново. Если же он уже имеется, то процесс создания пропускается и к нему применяются новые свойства. Если эту проверку не сделать, то будет нагромождение из вновь создаваемого текста. В нашем случае мы выбрали объект «Текст» (OBJ_TEXT), потому что его расположение привязывается к цене и ко времени, а не к координатам Х и Y на графике.
Осталось вызвать эту пользовательскую функцию в функции OnCalculate. Для этого мы должны знать номер подокна, в котором будущий объект должен располагаться, иначе по умолчанию он будет отрисовываться на главном графике, а не в пределах отдельного окна индикатора.
0 |
int wnd=ChartWindowFind(); |
Вызываем функцию для линии среднего спреда индикатора:
0 1 |
TextCreate(0,"DV|Current Spread",wnd,Time[0]+_Period*60,AvrSpreadBuffer[0], DoubleToString(AvrSpreadBuffer[0],1),"Arial",8,clrSpringGreen,0,ANCHOR_LEFT); |
В ней указываем только нужные нам параметры объекта, нет нужды переписывать их все, если параметры функции имеют тип const. Поэтому перечисляем по очереди:
- Номер графика для отображения. Ноль обозначает текущий график.
- Имя объекта. Должно быть уникальным.
- Номер подокна, который мы выявили выше.
- Значение времени к которому этот объект будет привязан. Чтобы избежать наложение текста на линию, сделаем отступ вправо на одну свечу с помощью переменной _Period*60.
- Значение цены для привязки. В нашем случае это значение текущего среднего спреда.
- Текст, который будет отображаться. Для правильного отображение мы меняем формат значения double на string.
- Имя шрифта. Не будем оригинальничать и укажем Arial.
- Размер шрифта в пикселях.
- Цвет текста укажем такой же, как и цвет линии.
- Угол наклона текста нам не нужен, оставляем ноль.
- Сторона привязки текста. Чтобы избежать наложения текста на линию, привяжемся к левому углу текста ANCHOR_LEFT
Остальные значения не передаем, оставляем их по умолчанию, возможно в другом коде они вам понадобятся.
Все тоже самое делаем и для текста линии максимального спреда:
0 1 |
TextCreate(0,"DV|Max Spread",wnd,Time[0]+_Period*60,MaxSpreadBuffer[0], DoubleToString(MaxSpreadBuffer[0],1),"Arial",8,clrRed,0,ANCHOR_LEFT); |
Код готов. Полностью функция обработки событий OnCalculate будет выглядеть так:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { double SPREAD = NormalizeDouble(MarketInfo(NULL,MODE_SPREAD)/PipsDivided,1); if(TimeNewBar != iTime(NULL,PERIOD_CURRENT,0)) { TimeNewBar = iTime(NULL,PERIOD_CURRENT,0); counter = 0; sumspread = 0; } counter++; sumspread += SPREAD; AvrSpreadBuffer[0] = NormalizeDouble(sumspread/counter,1); if(SPREAD > MaxSpreadBuffer[0] || MaxSpreadBuffer[0] == EMPTY_VALUE) { MaxSpreadBuffer[0] = NormalizeDouble(SPREAD,1); } int wnd=ChartWindowFind(); TextCreate(0,"DV|Current Spread",wnd,Time[0]+_Period*60,AvrSpreadBuffer[0], DoubleToString(AvrSpreadBuffer[0],1),"Arial",8,clrSpringGreen,0,ANCHOR_LEFT); TextCreate(0,"DV|Max Spread",wnd,Time[0]+_Period*60,MaxSpreadBuffer[0], DoubleToString(MaxSpreadBuffer[0],1),"Arial",8,clrRed,0,ANCHOR_LEFT); return(rates_total); } |
Написание индикатора завершено. Теперь необходимо перенести его на график. Для проверки используйте ТФ М1, чтобы данные по свечам обновлялись каждую минуту. Подождите несколько минут и индикатор начнет приобретать подобный вид:
Соответственно зеленая линия будет всегда ниже или равна красной, т.к. среднее значение спреда всегда ниже максимального. Если вы наведете мышь на любую свечу, где построена кривая спреда, вы увидите значения двух буферов в окне данных:
Ниже скриншот отображения большего периода времени:
Спред на скриншоте примерно одинаковый, потому что не был затронут период ночного расширения спреда в ролловер. Вы можете оставить индикатор на графике на несколько дней и увидеть более полную картину по валютной паре. Такой метод может вам помочь в правильном выборе брокера с самыми подходящими условиями по спредам.
Написанный нами индикатор имеет один изъян. При закрытии терминала, дисконнектах с сервером или сменой таймфрейма все уже построенные линии пропадут и сбор статистики начнется заново. Это связано с тем, что буфер индикатора не хранит эти значения в памяти постоянно. Избежать подобные неудобства можно только сохраняя значения спреда в отдельный файл csv в каталоге терминала и последующем чтением этих данных из него. Это не самое простое занятие, поэтому на эту тему будет отдельный урок в 4 разделе данного курса. Чтобы не пропустить — подписывайтесь на новые статьи.
Данный код это только оболочка, которую вы можете улучшить под себя. К примеру — изменить тип линии на гистограмму, добавить значения минимального и текущего спреда, сделать информационную панель с итоговыми значениями и т.д.
Мы же в свою очереди уже давно написали очень удобный индикатор для определения спреда Spread Detected по просьбам трейдеров и сами постоянно его используем в нашей торговле. Он умеет отображать разные гистограммы спреда, выводить Алерт и записывать данные в файл, чтобы сохранить их после дисконнекта. Рекомендуем ознакомится.
Заключение
В этом уроке мы научились создавать индикатор в отдельном окне, рассчитывать значения среднего и максимального спреда для дальнейшего его отображения. Еще раз закрепили работу с буферами данных и создания текстовых объектов. Открытый код с комментариями как всегда во вложении.
Если эта информация была для вас полезна, подпишитесь на рассылку и посмотрите раздел Статьи на сайте, мы там высказываем свое мнение по поводу Форекса и даем различные советы. Также вступайте в наши сообщества Вконтакте и Instagram, там мы стараемся выкладывать только интересную и полезную информацию для вас.
[download url=»http://www.davinci-fx.com/wp-content/uploads/2021/01/2.6-Spread-Indicator.rar» title=»Скачать код урока»]