
Отображение уровней поддержки и сопротивления
Введение
Эта статья посвящена поиску и отображению Уровней Поддержки и Сопротивления в программе MetaTrader 4. На основе простого алгоритма строится удобный и универсальный индикатор FindLevels, создающий горизонтальные линии уровней поддержки в окне графика финансового инструмента, как показано на следующем рисунке:
Эта статья затрагивает так же такую полезную тему, как создание простого индикатора, способного отображать в одну рабочую область, результаты с разных периодов времени. Как это выглядит можно увидеть на рисунке снизу.
Первый индикатор выводит уровни поддержки (толстые бежевые линии), основываясь на котировках 30-минутного временного периода. Второй индикатор, запущенный в том же окне, выводит уровни (тонкие пунктирные фиолетовые линии) на основании 15-минутного периода, причем выводит их поверх уровней 30-минутного индикатора. Подробнее смотри раздел «Взаимодействие Индикаторов».
Данная статья по сути является продолжением статьи о создании скрипта для определения уровней поддержки, однако в отличии от нее, написана для людей более продвинутых в программировании и владении платформой MetaTrader 4. Поэтому новичкам, а так же тем, кому эта статья покажется слишком сложной, я рекомендую предварительно ознакомиться с моей предыдущей статьей «Один способ построения уровней поддержки и сопротивления».
Теоретическая часть
Опишем алгоритм поиска уровней поддержки и сопротивления с тем, чтобы потом воплотить его в индикаторе FindLevels. Уровень поддержки и сопротивления - это значение цены, пересечению которой мешают некоторые силы. Это могут быть силы, вызванные психологической отметкой, влияние каких-то крупных игроков или же большое количество стоп-лосов в районе этого уровня. Вполне понятно, что котировки будут пересекать эту линию значительно реже, чем все остальные линии, не являющиеся уровнями поддержки. Подтверждение этому факту можно найти в большом количестве литературы.
Для того чтобы воспользоваться этим фактом, нам необходимо для каждой цены посчитать количество баров пересекающих эту цену. Вот график результатов таких подсчетов из предыдущей статьи:
Горизонтальная ось на этом рисунке - это цена, на вертикальной оси отмечено количество баров, пересекающих эту цену. Как видно на рисунке у этого графика большое количество локальных минимумов. Локальный минимум – точка ненулевого интервала, на котором она является минимумом. Нам надо провести отбор локальных минимумов по некоторому признаку.
Во-первых, зададим некоторую константу MaxR – радиус окрестности. Если локальный минимум не является минимумом в окрестности с радиусом MaxR, то этот локальный минимум нам не подходит. Во-вторых, зададим параметр MaxCrossesLevel. Если в окрестности с радиусом MaxR максимум функции отличается от минимума меньше, чем на MaxCrossesLevel, то мы этот локальный минимум так же не отображаем, так как он является недостаточно выделяющимся.
Вспомогательные функции
Как уже было описано ранее, индикатор FindLevels расчитан на работу с котировками с произвольным временным периодом, заданным пользователем (переменная TimePeriod). Поэтому для простоты кода введем две простые функции, не нуждающиеся в дополнительных объяснениях:
double prLow(int i) { return (iLow(NULL,TimePeriod,i)); } double prHigh(int i) { return (iHigh(NULL,TimePeriod,i)); }
Третья и четвертая функции необходимы нам для удобного отображения уровней поддержки, их толщины, цвета и порядка отображения в зависимости от временного периода:
int Period2Int(int TmPeriod) { switch(TmPeriod) { case PERIOD_M1 : return(0); case PERIOD_M5 : return(1); case PERIOD_M15 : return(2); case PERIOD_M30 : return(3); case PERIOD_H1 : return(4); case PERIOD_H4 : return(5); case PERIOD_D1 : return(6); case PERIOD_W1 : return(7); case PERIOD_MN1 : return(8); } return (0); } string Period2AlpthabetString(int TmPeriod) { return(Alphabet[Period2Int(TmPeriod)]); }
Смысл создания функции Period2AlphabetString() описан в разделе «Взаимодействие индикаторов», а применение функции Period2Int() будет понятно уже в следующем разделе.
Написание индикатора
Начнем с внешних переменных задаваемых пользователем:
extern int MaxLimit = 1000; extern int MaxCrossesLevel = 10; extern double MaxR = 0.001; extern int TimePeriod = 0; extern color LineColor = White; extern int LineWidth = 0; extern int LineStyle = 0;
-
MaxLimit - количество используемых баров истории;
-
MaxCrossesLevel – минимальная разность между локальным максимумом и минимумом (подбробное описание в разделе «Теоретическая часть»);
-
MaxR – радиус окрестности, в которой ищется минимум;
-
TimePeriod – временной период, для которого будет поиск уровней поддержки. По умолчанию – период окна отображения;
-
LineColor – цвет отображаемых линий;
-
LineWidth – толщина линии. По умолчанию 0;
-
LineStyle – стиль линии. По умолчанию 0.
Если значения LineColor, LineWidth или LineStyle оставлены пользователем по умолчанию, то в процедуре Init мы меняем их на другие, зависящие от временного периода. Делается это для того, чтобы вид линий разных периодов не совпадал и их можно было отличить.
int init() { if(TimePeriod == 0) TimePeriod = Period(); if(TimePeriod != 0 && LineWidth == 0) if(Period2Int(TimePeriod) - Period2Int(Period()) >= 0) LineWidth = Widths[Period2Int(TimePeriod) - Period2Int(Period())]; else { LineWidth = 0; if(LineStyle == 0) LineStyle = STYLE_DASH; } if(TimePeriod != 0 && LineColor == White) LineColor = Colors[Period2Int(TimePeriod)]; return(0); }
В первой строчке мы задаем значение TimePeriod’а, если оно задано по умолчанию. Далее определяем ширину линии. Чем больше временной период TimePeriod по отношению к периоду графика (окна отображения), тем шире линии. Если TimePeriod меньше периода графика, то ширина линии 0, а сама линия пунктирная. Цвет для каждого временного периода свой.
Массивы Colors[] и Width[] определяются следующим образом:
color Colors[] = {Red,Maroon, Sienna, OrangeRed, Purple,I ndigo, DarkViolet, MediumBlue, DarkSlateGray}; int Widths[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int CrossBarsNum[]; bool CrossBarsMin[]; double d1Num = 0.0, d2Num = 0.0; datetime TMaxI = 0;
- Массив CrossBarsNum[] - это массив количества баров пересечения для каждой цены;
- CrossBarsMin[] – массив отвечающий за то, является ли линия с заданной ценой локальным минимумом или нет;
- d1Num и d2Num – это минимальная и максимальная цена на интервале баров от 0 до MaxLimit;
- TMaxI – время последнего обработанного бара.
#define MaxLines 1000 string LineName[MaxLines]; int LineIndex = 0;
- MaxLines – максимальное количество линий, которое будет создано;
- LineName[] – массив их имен;
- LineIndex – индекс свободной ячейки в массиве LineName[].
Теперь перейдем к самой функции start():
int counted_bars = IndicatorCounted(); int limit = MathMin(Bars - counted_bars, MaxLimit); double d1 = prLow(iLowest(NULL, TimePeriod, MODE_LOW, limit, 0)); double d2 = prHigh(iHighest(NULL, TimePeriod, MODE_HIGH, limit, 0));
Вычисляем переменную limit, используя количество баров, не измененных после последнего вызова индикатора. d1 и d2 – минимум и максимум цены на промежутке от 0 до limit.
if(d1Num != d1 || d2Num != d2) { ArrayResize(CrossBarsNum, (d2 - d1)*10000); ArrayResize(CrossBarsMin, (d2 - d1)*10000); if(d1Num != d1 && d1Num != 0.0) { ArrayCopy(CrossBarsNum, CrossBarsNum, 0, (d1Num - d1)*10000); ArrayCopy(CrossBarsMin, CrossBarsMin, 0, (d1Num - d1)*10000); } d1Num = d1; d2Num = d2; }
В процессе работы индикатора ценовые промежутки, охватываемые нашими массивами CrossBarsNum[] и CrossBarsMin[], могут меняться. Каждый раз, когда это случается, нам необходимо увеличить число ячеек в массиве и при необходимости сдвинуть его вправо. Происходит это, если новые переменные d1, d2 не совпадают с переменными d1Num, d2Num, полученными на предыдущем запуске функции start().
for(double d = d1; d <= d2; d += 0.0001) { int di = (d - d1)*10000; for(int i = 1; i < limit; i++) if(d > prLow(i) && d < prHigh(i)) CrossBarsNum[di]++; if(Time[limit] != TMaxI&&TMaxI != 0) if(d > prLow(iBarShift(NULL, 0, TMaxI)) && d < prHigh(iBarShift(NULL, 0, TMaxI))) CrossBarsNum[di]--; } TMaxI = Time[limit] - 1;
После того как мы убедились, что наши массивы соответствуют нужным размерам, для каждой цены начинаем обсчитывать новые бары и увеличивать при пересечении баром ценового уровня значение CrossBarsNum[]. Так как постоянно появляются новые бары, то старые бары из интервала [0 : limit] будут исключаться. Поэтому нам надо проверять такие бары и уменьшать значение CrossBarsNum[] в случае пересечения. Затем переменной TMaxI присваивается время последнего обсчитанного бара.
double l = MaxR*10000; for(d = d1 + MaxR; d <= d2 - MaxR; d += 0.0001) { di = (d - d1)*10000; if(!CrossBarsMin[di] && CrossBarsNum[ArrayMaximum(CrossBarsNum, 2*l, di - l)] - CrossBarsNum[ArrayMinimum(CrossBarsNum, 2*l, di - l)] > MaxCrossesLevel && CrossBarsNum[di] == CrossBarsNum[ArrayMinimum(CrossBarsNum, 2*l, di - l)] && CrossBarsNum[di-1] != CrossBarsNum[ArrayMinimum(CrossBarsNum, 2*l, di - l)]) { CrossBarsMin[di] = true; LineName[LineIndex] = Period2AlpthabetString(TimePeriod) + TimePeriod + "_" + d; ObjectCreate(LineName[LineIndex], OBJ_HLINE, 0, 0, d); ObjectSet(LineName[LineIndex], OBJPROP_COLOR, LineColor); ObjectSet(LineName[LineIndex], OBJPROP_WIDTH, LineWidth); ObjectSet(LineName[LineIndex], OBJPROP_STYLE, LineStyle); LineIndex++; } if(CrossBarsMin[di] && CrossBarsNum[di] != CrossBarsNum[ArrayMinimum(CrossBarsNum, 2*l, di - l)]) { CrossBarsMin[di] = false; ObjectDelete(Period2AlpthabetString(TimePeriod) + TimePeriod + "_" + d); } }
И в конце процедуры start() мы второй раз проходим массив CrossBarsMin[] с тем, чтобы найти новые локальные минимумы и удалить старые, которые больше не являются таковыми. Так как возможны целые плато локальных минимумов (несколько значений в массиве CrossBarsMin[] совпадает и все они являются локальными минимумами), то нам нужно вывесьти только один из этих локальных минимумов. Мы будем выводить только наименьший по цене:
CrossBarsNum[di] == CrossBarsNum[ArrayMinimum(CrossBarsNum, 2*l, di - l)] && CrossBarsNum[di-1]!= CrossBarsNum[ArrayMinimum(CrossBarsNum, 2*l, di - l)]
В создании нового графического объекта, горизонтальной линии, ничего сложного нет. Мы задаем этой линии свойства толщины, стиля и цвета, расчитанные заранее в процедуре Init. Нет ничего сложного и в удалении уровней, которые перестали являться уровнями поддержки. Осталось непонятным только, почему мы в имени объекта применяем функцию Period2AlpthabetString(TimePeriod) и зачем нам это надо. Вот этому вопросу и посвящен следующий раздел, на который я прежде несколько раз ссылался.
Взаимодействие индикаторов
Как уже говорилось в начале статьи, индикатор FindLevels предназначен для вывода в одно окно графика уровней поддержки с нескольких временных периодов. Для этого нам необходимо:
-
Чтобы индикатор можно было запустить несколько раз и чтобы у него были входные данные с периодом времени;
-
Чтобы при этом линии отличались и можно было отличить по какому временному периоду построен каждый уровень поддержки;
-
Чтобы все линии были видны, как с более длинного временного периода так и с менее.
Первый пункт выполняется без проблем. Глобальные переменные отсутствуют. Названия графических объектов для каждого временного периода разные, так как в имени объекта присутствует период (например: «f30_1.25600000», 30 – временной период), а значит конфликтов при запуске нескольких индикаторов не будет.
Второй пункт также выполняется, так как у каждой линии свой цвет в зависимости от периода (LineColor=Colors[Period2Int(TimePeriod)]).
И остается только третий пункт. Вполне логично, что если линия оказалась уровнем поддержки скажем для 5-минутного графика, то она окажется и линией поддержки для 30-минутного графика. Если эти линии совпадают по цене и имеют одинаковую толщину, то мы одну из линий просто не увидим! Поэтому надо, чтобы линии с разных временных интервалов отличались по толщине. Уровни поддержки с более длинных временных периодов сделаем толще, чем линии с более коротких. Это вполне оправданно, так как линии, построенные по длинным периодам, имеют большую силу.
Осталось только достичь правильной очередности вывода линий. Более тонкие линии должны выводиться в конце и перекрывать более толстые, чтобы их было видно. В программе MetaTrader 4 объекты выводятся в алфавитном порядке имен. Поэтому необходимо, чтобы имена линий с более длинных периодов шли по алфавиту раньше линий с более коротких. Для этого и создана функция которая возвращает букву латинского алфавита в зависимости от периода:
string Period2AlpthabetString(int TmPeriod) { return (Alphabet[Period2Int(TmPeriod)]); }Alphabet - это массив латинских букв в обратном порядке. Полное имя каждого уровня поддержки имеет вид: Period2AlpthabetString(TimePeriod)+TimePeriod+"_"+d.
Еще раз для понятности вышеописанного приведу здесь скриншот из начала статьи:
Выводы
Проверка индикатора, которую я успел провести, показывает, что индикатор хорошо работает. Пользоваться им удобно, так как он может выводить данные с разных временных периодов. Так же, как показало его недолгое использование, удобно, чтобы индикатор выводил для каждого TimePeriod’а 3-10 уровней поддержки. Для этого необходимо подобрать соответствующие входные данные MaxR и MaxCrossesLevel. MaxR в моих тестах колебался от 0.0003 для более коротких временных периодов до 0.002 для более длинных. MaxCrossesLevel - от 3 до 20. Возможно было бы полезно переделать индикатор так, чтобы он выводил определенное количество наиболее явных уровней поддержки, но это бы усложнило простоту и понятность кода. К тому же те, кому мой индикатор придется по душе, смогут, я думаю, это сделать сами.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Лиза, Вы получили отвeт на свой вопрос? Подeлитeсь опытом!!! А можeт кто сам рeшал эту проблeму?
я так сделал )
int start()
{
double PRICE[120];
ArrayInitialize(PRICE,0);
int i;
string Obj_N;
for(i=ObjectsTotal()-1; i>=0; i--)
{
Obj_N=ObjectName(i);
if (ObjectType(Obj_N)!=OBJ_HLINE) continue;
PRICE[i] = NormalizeDouble(ObjectGet(Obj_N, OBJPROP_PRICE1),Digits);
}
ArraySort(PRICE,WHOLE_ARRAY,0,MODE_DESCEND);
Comment(PRICE[0],"\n",
PRICE[1]);
return(0);
}
Спасибо ! Действительно нужный и полезный индюк. Единственно, я бы пожалуй считал не количество баров, пересекающих уровни цены, а вероятности нахождения цен в узких интервалах поддержки/сопротивления. Это несколько меняет смысл, и мне кажется было бы более верно.
По поводу количества линий поддержки: Вы пробовали изменять MaxR и MaxCrossesLevel?
По поводу тормозов: я на версии 200 не испытывал этот индикатор. У меня в 198й на 1.5Ghz проце тормозов почти нет. Но впринципе некатарая задержка при запуске индикатора есть и вполне понятна. Надо обработать достаточно много баров.
Глеб, интереснейшая тема. Спасибо. Вот только проблема возникла. Индикатор запускается только на инструментах с пятью знаками после запятой. На других просто зависает терминал. Приходится в "наглую" вырубать.