Отображение уровней поддержки и сопротивления

Автор: lexy Среда, Сентябрь 10th, 2014 Нет комментариев

Рубрика: Разное

Эта статья посвящена поиску и отображению Уровней Поддержки и Сопротивления в программе 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 предназначен для вывода в одно окно графика уровней поддержки с нескольких временных периодов. Для этого нам необходимо:

  1. Чтобы индикатор можно было запустить несколько раз и чтобы у него были входные данные с периодом времени;
  2. Чтобы при этом линии отличались и можно было отличить по какому временному периоду построен каждый уровень поддержки;
  3. Чтобы все линии были видны, как с более длинного временного периода так и с менее.

Первый пункт выполняется без проблем. Глобальные переменные отсутствуют. Названия графических объектов для каждого временного периода разные, так как в имени объекта присутствует период (например: «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. Возможно было бы полезно переделать индикатор так, чтобы он выводил определенное количество наиболее явных уровней поддержки, но это бы усложнило простоту и понятность кода. К тому же те, кому мой индикатор придется по душе, смогут, я думаю, это сделать сами.

 

Прикрепленные файлы:
 FindLevels.mq4 (5.2 Kb)
Источник: mql4.com

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты