English 中文 Español Deutsch 日本語 Português
Отображение уровней поддержки и сопротивления

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

MetaTrader 4Примеры | 24 ноября 2006, 10:31
15 049 18
Slobodov Gleb
Slobodov Gleb

Введение


Эта статья посвящена поиску и отображению Уровней Поддержки и Сопротивления в программе 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.21 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (18)
Aleksey Golovin
Aleksey Golovin | 29 янв. 2016 в 14:24
MQL4 Comments:

Лиза, Вы получили отв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);

  } 

iJSmile
iJSmile | 17 нояб. 2016 в 22:28
Очень интересная статья. Спасибо автору.
[Удален] | 17 нояб. 2016 в 22:39
Отличный индикатор. Автору респект !
Al-mobil
Al-mobil | 22 дек. 2016 в 06:55
eugenk1:
Спасибо ! Действительно нужный и полезный индюк. Единственно, я бы пожалуй считал не количество баров, пересекающих уровни цены, а вероятности нахождения цен в узких интервалах поддержки/сопротивления. Это несколько меняет смысл, и мне кажется было бы более верно.
Здравствуйте. eugenk1, не подскажите это необходимо читать мат статьи по вероятностям попадания в интервалы?
Vitaliy Fedyaev
Vitaliy Fedyaev | 26 февр. 2019 в 14:48
Slobodov Gleb:
По поводу количества линий поддержки: Вы пробовали изменять MaxR и MaxCrossesLevel?
По поводу тормозов: я на версии 200 не испытывал этот индикатор. У меня в 198й на 1.5Ghz проце тормозов почти нет. Но впринципе некатарая задержка при запуске индикатора есть и вполне понятна. Надо обработать достаточно много баров.

Глеб, интереснейшая тема. Спасибо. Вот только проблема возникла. Индикатор запускается только на инструментах с пятью знаками после запятой. На других просто зависает терминал. Приходится в "наглую" вырубать.

Фильтр на основании истории торговли Фильтр на основании истории торговли
В статье рассматривается использование виртуальной торговли, как составной части фильтра открытия сделок.
Один способ построения уровней поддержки и сопротивления Один способ построения уровней поддержки и сопротивления
В данной статье описывается процесс создания простейшего скрипта для вычисления уровней поддержки и сопротивления. Статья ориентирована на новичков, поэтому каждый момент процесса разобран очень подробно. Однако, несмотря на всю простоту скрипта, изучение данной статьи, вероятно, будет полезным и для людей более продвинутых в трейдинге и владении платформой MetaTrader 4, так как содержит в себе пример экспорта данных в текстовую таблицу, импорта ее в Microsoft Excel и построения графиков для дальнейшего подробного анализа.
Трейдинг под Linux Трейдинг под Linux
Использование программ-индикаторов для оперативного отслеживания ситуации на мировых финансовых биржах.
Индивидуальная психология трейдера Индивидуальная психология трейдера
Описание поведения трейдера на финансовом рынке. Личная подборка автора из книги А.Элдера "Как играть и выигрывать на бирже".