| / | Статьи |
Cтатьи
Возможности
Конструктор трейдера: Украшение индикаторов
Авторизуйтесь или зарегистрируйтесь , чтобы добавить новую статью
|
|
Конструктор трейдера: Украшение индикаторов [ en ]Введение Что такое индикатор? Это инструмент, предназначенный для отображения некоего рода информации. Чаще всего этой информацией являются свойства ценового ряда, именно о таких индикаторах пойдет дальше речь. Каждый индикатор также имеет свойства или характеристики: например, область значений, зоны перекупленности\перепроданности, пересечения линий, вершины и впадины... Их много, и они могут быть успешно использованы наряду с основными показателями индикатора. Однако не всегда эти свойства могут быть определены "на глазок". Причин этому может быть много - начиная от маленьких размеров окна индикатора, заканчивая снижением общего внимания. Целью этой статьи является увеличение наглядности и информативности индикаторов, а также частичная автоматизация и упрощение процесса воплощения этого в коде, чтобы снизить планку для тех, кто собирается реализовывать раскрашивание индикаторов. Надеюсь, что код, описанный и представленный ниже, сможет быть с легкостью использован как профи, так и начинающими программистами, а также теми, для кого программирование является непрофильным занятием. Статья рассчитана на тех, кто имеет хотя бы начальный уровень владения языком MQL4 и способен реализовывать простые идеи и алгоритмы в коде, а также имеет представление о структуре хранения кода в терминале и умеет пользоваться библиотеками (experts/libraries) и заголовочными файлами (experts/include). 1. Постановка задачиСреди всех характеристик индикаторов хочется выделить те, которые, вне сомнения, являются информативными и наиболее часто отображаются:
Про них и поговорим.
2. Базовые понятияДля того чтобы не возникло недопонимания с самого начала, отвлечемся на строение индикатора как такового. #property indicator_separate_window // количество видимых буферов индикатора #property indicator_buffers 3 // установка области значений индикатора #property indicator_minimum 0 #property indicator_maximum 100 // установка цветов индикатора #property indicator_color1 White #property indicator_color2 Red #property indicator_color3 Blue // внешние настройки extern int RSIPeriod = 9; extern int AppliedPrice = 0; extern int MAPeriod = 5; // объявление буферов индикатора. Здесь они могут быть объявлены в любом порядке. // Имена буферам можно давать любые, лучше осмысленные double Values[]; // Собственно значения double SmoothedValues[]; // Сглаженные значения double Crosses[]; // пересечения // Значимое количество знаков после запятой в значениях индикатора int DigitsUsed = 5; // Используемое пустое значение. В языке MQL есть два пустых значения -- EMPTY (-1) // -- используется в качестве пустого параметра при вызове функций // EMPTY_VALUE (0x7FFFFFFF) -- используется в качестве недопустимого значения // (или значения по умолчанию) переменной в индикаторах и при вызове функций. // Дело в том, что большинство встроенных индикаторов возвращают 0 при отсутствии значения // Кроме того, в пользовательских (iCustom) индикаторах пустое значение может быть // установлено вообще любое, это надо учитывать. int EmptyValueUsed = 0; // Функция инициализации. int init() { // Количество используемых буферов может быть больше, чем отображаемых, некоторые // могут содержать промежуточные расчеты и вспомогательную информацию. Здесь указывается // общее количество буферов, включая вспомогательные. // Если вспомогательных нет, // эта строчка не нужна. Общее количество не должно превышать 8 // IndicatorBuffers(3); // ассоциируем буферы. Важно, чтобы индексы шли от 0 до заявленного количества (не включая) // буферы отрисовываются в порядке возрастания индексов, это важно и может быть и будет // использовано при написании индикаторов ниже. // Это значит, что буфер с бОльшим индексом рисуется поверх буфера с меньшим SetIndexBuffer(0, Values); SetIndexBuffer(1, SmoothedValues); SetIndexBuffer(2, Crosses); // кроме того, важно, чтобы вспомогательные буферы располагались за отображаемыми // (т.е. имели бОльший индекс) иначе могут быть проблемы с отображнением, // причем иногда понять причину ошибки при отрисовке в таких случаях непросто // эта функция устанавливает пустое значение для буфера с заданным индексом // настоятельно не рекомендую использовать эту функцию, дабы не усложнять жизнь себе и людям // Пустое значение по умолчанию для буферов -- EMPTY_VALUE. // На графике пустые значения буфера не рисуются (кроме DRAW_ZIGZAG) // SetIndexEmptyValue(0, EMPTY_VALUE); // задаем настройки для буферов SetIndexStyle(0, DRAW_LINE); // Основной сигнал будет сплошной линией SetIndexStyle(1, DRAW_LINE, STYLE_DASH); // Сглаженный -- штриховой SetIndexStyle(2, DRAW_ARROW, STYLE_SOLID, 2); // Перечения -- крестиками размером 2 SetIndexArrow(2, 251); // код крестика в Wingdings IndicatorDigits(DigitsUsed); // устанавливаем количество значимых знаков после запятой // Установка для каждого буфера начальной точки рисования. Если по отношению к текущему индексу // глубина истории // будет меньше записанного здесь значения, значение буфера с этим индексом прорисовано не будет. SetIndexDrawBegin(0, RSIPeriod); SetIndexDrawBegin(1, RSIPeriod + MAPeriod); SetIndexDrawBegin(2, RSIPeriod + MAPeriod + 1); return(0); } int start() { // вычисление количества баров для перерасчета int toCount = Bars - IndicatorCounted(); // Считаем значения // считаем от начала истории к текущему моменту for (int i = toCount - 1; i >=0; i--) { // понял, насколько это удобно только когда начал использовать // рекомендую производить нормализацию данных сразу, // чтобы потом без проблем и головной боли сравнивать Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed); } // Считаем сглаженные значения for (i = toCount - 1; i >=0; i--) { SmoothedValues[i] = NormalizeDouble(iMAOnArray(Values, 0, MAPeriod, 0, MODE_EMA, i), DigitsUsed); } // ... return(0); } 3. ХарактеристикиРассмотрим характеристики подробней. 3.1. Пересечение линийНаверное, каждый кодер попробовал реализовать алгоритм торговли на пересечении двух МА(скользящих средних). Или таким же образом на MACD между основной и сигнальной линией. Попробуем сделать этот алгоритм визуально наглядным, отобразив на индикаторе точки пересечения. В качестве "подопытного кролика" здесь и далее взят индикатор Relative Strength Index, т.к. именно он изначально являлся жертвой и объектом совершенствования. 3.1.1. Формализация задачиНеобходимо в отдельном буфере помечать те бары, на которых присутствовало пересечение двух заданных линий. 3.1.2. ПроблемыКазалось бы, все просто и прозрачно. Задача несложная и решается в пару строчек. Необходимо всего лишь описать, что такое пересечение:
if ((x1 > y1 && x2 < y2) || (x1 < y1 && x2 > y2))
{
// имеем пересечение
}
Или, если записать более лаконично: if ((x1 - y1)*(x2 - y2) < 0)
{
// имеем пересечение
}
Однако рассмотрим еще одну ситуацию:
Точки, помеченные зеленым цветом, имеют одинаковые значения. В данном случае пересечения нет, имеет место касание. А тут:
определить пересечение будет далеко не просто, а такая ситуация вполне возможна. А также необходимо корректно отличать касание от пересечения, учитывая, что при переборе можно встретить пустое значение в буфере или конец истории. 3.1.3. РешениеЗдесь и далее я не буду приводить функцию init(), поскольку в этом нет необходимости. Полный вариант всегда можно посмотреть, заглянув в исходники. Решение приводится для простого и сглаженного значений индикатора Relative Strength Index. //| RSI_Crosses_Sample.mq4 | extern int RSIPeriod = 9; extern int AppliedPrice = 0; extern int MAPeriod = 5; // buffers double Values[]; // Собственно значения double SmoothedValues[]; // Сглаженные значения double Crosses[]; // пересечения int DigitsUsed = 5; int EmptyValueUsed = 0; int start() { int toCount = Bars - IndicatorCounted(); // Считаем значения, причем сразу нормализуем их до нужной нам величины // ... // Метим пересечения for (i = toCount - 1; i >=0; i--) { // надо история на 1 бар назад, иначе нет смысла проверять if (i + 1 >= Bars) { continue; } // если хотя бы одно значение пусто, проверять нет смысла if ( Values[i] == EmptyValueUsed || Values[i + 1] == EmptyValueUsed || SmoothedValues[i] == EmptyValueUsed || SmoothedValues[i + 1] == EmptyValueUsed || Values[i] == EMPTY_VALUE || Values[i + 1] == EMPTY_VALUE || SmoothedValues[i] == EMPTY_VALUE || SmoothedValues[i + 1] == EMPTY_VALUE ) { continue; } // чистим текущее значение Crosses[i] = EMPTY_VALUE; // проверка на пересечение (простой случай) if ((Values[i] - SmoothedValues[i])*(Values[i + 1] - SmoothedValues[i + 1]) < 0) { Crosses[i] = SmoothedValues[i]; continue; } // условие пересечения -- сложный случай -- когда пересечение включает в себя // несколько баров с одинаковыми значениями if (Values[i + 1] == SmoothedValues[i + 1] && Values[i] != SmoothedValues[i]) { // имеется потенциальное пересечение -- проверяем на вшивость // для этого находим второй ее конец. int index = i + 1; bool found = false; while ( index < Bars && // выход за диапазон Values[index] != EmptyValueUsed && // проверка на пустое значение Values[index] != EMPTY_VALUE && // проверка на пустое значение SmoothedValues[index] != EmptyValueUsed && // проверка на пустое значение SmoothedValues[index] != EMPTY_VALUE) // проверка на пустое значение { if (Values[index] != SmoothedValues[index]) { // ура, второй конец найден found = true; break; } index++; } if (!found) { // или дошли до конца истории, или наткнулись на пустое значение // в обоих случаях считаем, что пересечения нет continue; } // проверяем концы на пересечение if ((Values[i] - SmoothedValues[i])*(Values[index] - SmoothedValues[index]) < 0) { // пересечение есть Crosses[i] = SmoothedValues[i]; } // else имеет место касание -- не помечаем } } return(0); } 3.1.4. АвтоматизацияЗдесь и далее в этом подразделе показывается решение аналогичной задачи с использованием библиотеки Indicator_Painting. //| RSI_Crosses_Lib_Sample.mq4 | #include <Indicator_Painting.mqh> extern int RSIPeriod = 9; extern int AppliedPrice = 0; extern int MAPeriod = 5; // buffers double Values[]; // Собственно значения double SmoothedValues[]; // Сглаженные значения double Crosses[]; // пересечения int DigitsUsed = 5; int EmptyValueUsed = 0; int start() { // Считаем значения // ... // Метим пересечения MarkCrosses ( Values, // быстрый буфер со значениями для проверки SmoothedValues, // медленный буфер со значениями для проверки Crosses, // буфер, в который будут заноситься пересечения toCount - 1, // начальный индекс проверки 0, // конечный индекс проверки CROSS_ALL, // CROSS_UP пересечение вверх CROSS_DOWN вниз CROSS_ALL все 0); // используемое пустое значение return(0); } 3.2. Пометка уровняУ тех осцилляторов, которые имеют ограниченную и жестко заданную область значений (RSI, Stochastic Oscillator, DeMarker, Money Flow Indedx, Williams' Percent Range), да и у других тоже, очень часто выделяют зоны или уровни. Зона флета, зона перекупленности\перепроданности, зона тренда... Попробуем выделить заданный уровень другим цветом. 3.2.1. Формализация задачиНеобходимо в отдельном буфере помечать те бары, значения которых находятся за пределами некоторого уровня. 3.2.2. ПроблемыОпять же, несмотря на кажущуюся простоту, здесь есть подводные камни. Первая проблема это прорисовка тех баров, на которых происходит пересечение с уровнем. Итак, в чем она состоит? Вот простой код решения. //| RSI_Cut_Levels_Sample.mq4 | extern int RSIPeriod = 9; extern int AppliedPrice = 0; extern int HigherLevel = 70; extern int LowerLevel = 30; // buffers double Higher[]; // Перепокупка double Lower[]; // Перепродажа double Values[]; // Собственно значения int DigitsUsed = 5; int EmptyValueUsed = 0; int start() { int toCount = Bars - IndicatorCounted(); // Считаем значения // ... // Метим уровни -- верхний for (i = toCount - 1; i >=0; i--) { // проверяем на пустые значения if (Values[i] == EMPTY_VALUE || Values[i] == EmptyValueUsed) { continue; } // обнуляем текущее значение Higher[i] = EMPTY_VALUE; // больше уровня if (Values[i] >= HigherLevel) { Higher[i] = Values[i]; } } // Метим уровни -- нижний // код практически идентичный return(0); } Этот код решает поставленную задачу, но имеет одну проблему:
Визуально уровень воспринимается плохо, т.к. рисование сигналов ведется с того значения, которое непосредственно больше (меньше) уровня. Поэтому некоторые сигнальные бары могут быть вообще не восприняты, т.к. из-за резкого изменения значений соседних баров помечены только куцым обрывком линии. Решение состоит в том, чтобы метить не только бары выше (ниже) уровня, а также бар, предшествующий уже помеченным барам и следующий за ними. Причем метить их надо не собственными значениями, а значениями уровня. Вторая проблема появляется при решении первой, а именно в том, что в результате усложнения алгоритма в сигнальном буфере может находиться мусор - псевдопометка "ложных" пробоев уровня. Это значит, что при формировании бара цена заходила за уровень, однако в результате сформированный бар так его и не смог преодолеть. Из-за этого может получиться такая картинка:
Проблема появляется только при использовании индикатора на реальных котировках. Решается она просто - при обработке проверяется минимум два бара - нулевой и первый, остальные по необходимости. После решения этих проблем получается вот такая картинка для RSI:
3.2.3. РешениеИтак, запишем вышеизложенное в коде: //| RSI_Levels_Sample.mq4 | extern int RSIPeriod = 9; extern int AppliedPrice = 0; extern int HigherLevel = 70; extern int LowerLevel = 30; // buffers double Higher[]; // Перепокупка double Lower[]; // Перепродажа double Values[]; // Собственно значения int DigitsUsed = 5; int EmptyValueUsed = 0; // просматриваем всегда минимум 2 бара -- нулевой и первый int Depth = 2; int start() { int toCount = Bars - IndicatorCounted(); // Считаем значения // ... // Чтобы не оставалось мусора на 1 баре, заглядываем на 1 бар назад toCount = MathMax(toCount, Depth); // Метим уровни -- верхний for (i = toCount - 1; i >=0; i--) { if (Values[i] == EMPTY_VALUE || Values[i] == EmptyValueUsed) continue; Higher[i] = EMPTY_VALUE; // больше уровня if (Values[i] >= HigherLevel) { Higher[i] = Values[i]; // если предыдущий меньше if (Values[i + 1] < HigherLevel && Values[i + 1] != EmptyValueUsed) { // метим и его тоже, но значением уровня Higher[i + 1] = HigherLevel; } } // если текущий меньше else { // если предыдущий больше if (Values[i + 1] >= HigherLevel && Values[i + 1] != EMPTY_VALUE) { // метим и его тоже, но значением уровня Higher[i] = HigherLevel; } } } // Метим уровни -- нижний // код практически аналогичен // ... return(0); } 3.2.4. АвтоматизацияРешение той же задачи с помощью библиотеки Indicator_Painting. //| RSI_Levels_Lib_Sample.mq4 | #include <Indicator_Painting.mqh> extern int RSIPeriod = 9; extern int AppliedPrice = 0; extern int HigherLevel = 70; extern int LowerLevel = 30; // buffers double Higher[]; // Перепокупка double Lower[]; // Перепродажа double Values[]; // Собственно значения int DigitsUsed = 5; int EmptyValueUsed = 0; int Depth = 2; int start() { int toCount = Bars - IndicatorCounted(); // Считаем значения for (int i = toCount - 1; i >= 0; i--) { Values[i] = NormalizeDouble(iRSI(Symbol(), 0, RSIPeriod, AppliedPrice, i), DigitsUsed); } // Метим уровни -- верхний MarkLevel(Values, Higher, 0, toCount - 1, HigherLevel, GREATER_THAN, EmptyValueUsed); // Метим уровни -- нижний MarkLevel(Values, Lower, 0, toCount - 1, LowerLevel, LESS_THAN, EmptyValueUsed); return(0); } 3.3. Вершины и впадиныЭкстремумы индикаторов также вполне могут быть использованы в качестве сигналов или их составляющих. В данной статье термин "экстремум" понимается в наиболее простой форме - если значение бара больше(меньше) значений своих непосредственных соседей, значение считается экстремумом.
3.3.1. Формализация задачиНеобходимо в отдельном буфере помечать те бары, значения которых являются экстремумами. |
![]() Взаимодействие между MеtaTrader 4 и MATLAB Engine (виртуальная машина MATLAB)
В данной статье рассматривается вопрос создания DLL библиотеки - обертки, которая позволит взаимодействовать MetaTrader 4 с математическим рабочим столом пакета MATLAB. Описаны "подводные камни" и пути их преодоления. Статья рассчитана на подготовленных программистов С/С++, использующих компилятор Borland C++ Builder 6. |
![]() Alert и Comment для внешних индикаторов
В практической работе трейдер иногда сталкивается с такой ситуацией: нужно получить «alert» или текстовое сообщение на экране монитора, (в окне графика) сообщение или информацию о появившемся сигнале от какого-либо индикатора. В статье приводится пример вывода информации о графических объектах, созданных сторонним индикатором. |
| Предыдущая | Следующая |
Добавлена функция MarkReducing для пометки убывающих значений.
_________
Еще одна вещь касательно библиотеки -- при маркировании уровней нельзя использовать одни и те же буферы для маркировки похожих, но разных уровней (критериев). Это чревато неправильной работой.
Еще раз спасибо granit77
В индикаторе RSI Directions Lib Sample по-моему обнаружил "недокументированную возможность".
Если в настройках цвета первую и вторую (верхнюю красную) линии сделать толстыми (2), то индикатор показывает места предпочтительного входа, места, в которых выше потенциал движения по сигналу.
К сожалению, эта информация хорошо поможет только тем, кто понимает как работают осцилляторы, остальным же могу сказать, что начинать рассматривать такие сигналы покупки нужно только отталкиваясь от мест потенциального разворота (вблизи уровня 30 + желательно на сильных технических уровнях).
.
Увы, на продажу этот метод не работает, т.к. не выделяются аналогичные нижние отрезки. Но для продажи тоже кое-что есть - дивергенции по вершинам индикатора, образованным с учасием толстых красных отрезков, работают надежней, чем другие вершины (без толстых линий). Т.е. толстые отрезки как правило находятся на ключевых экстремумах цены.
Хорошие индикаторы - спасибо!
Ориентируясь по RSI_Extremums_Sample - уже два дня, в ручную, на реале выхватую профит.
Может прикол, а может и нет - время покажет!
Создателю спасибо!!!
Красивые индикаторы, спасибо )
И вам спасибо, за отзывы :) .
Теперь такие индикаторы можно рисовать пачками :) .
С проблемами, предложениями и замечаниями милости прошу в личку или сюда.