Работа по Накоплению/Распределению и что из этого можно сделать

Artyom Trishkin | 27 октября, 2010

Введение

Как известно, индикатор Накопления/Распределения A/D имеет интересное свойство - пробитие трендовой линии, построенной на графике данного индикатора, с определённой долей вероятности говорит нам о скором пробое линии тренда на графике цены:


Решив проверить данный факт и устанавливая все трендовые линии на графике A/D, я быстро пришел к выводу о нецелесообразности ручного подхода к данному вопросу. Поэтому изготовил функцию для автоматической разметки трендовых линий на графике A/D и установки сигнальных указателей на ценовом графике. Теперь хочу шаг за шагом показать, как всё это возможно реализовать посредством MQL4 для использования в своих торговых роботах.

Данная статья будет полезна и интересна людям, только начинающим программировать на MQL4, поэтому я постарался изложить всё в наиболее доступной для понимания форме и использовать самые простые конструкции построения кода.


1. Постановка задачи

Давайте предварительно определимся с постановкой задачи.

Функция должна будет в общем случае находить факт пересечения линии тренда, прочерченной на графике A/D с линией самого индикатора и возвращать значение, указывающее нам, в какую сторону произошло пересечение - сверху вниз или снизу вверх, и для наглядности устанавливать на графике цены сигнальные указатели (стрелки вверх/вниз в зависимости от направления пересечения).

Теперь разложим общую задачу на более конкретные цели:

  1. Функция должна уметь работать с любым символом, на любом таймфрейме;
  2. Так как данная функция предназначена для работы в составе советника, то нахождение индикатора A/D на основном графике вовсе необязательно.
  3. Также необязательно и выводить указатели на график цены - все расчеты происходят внутри функции, а вывод на экран служит только для визуального контроля за работой функции.
  4. Пересечение возможно в нескольких направлениях: сверху вниз восходящего тренда, снизу вверх восходящего тренда, снизу вверх нисходящего тренда, сверху вниз нисходящего тренда. Каждое из этих пересечений функция будет определять.

Это относительно того, что должна уметь функция. Теперь рассмотрим способы реализации нашей задачи.


2. Заполнение массивов данными индикатора A/D

В функцию, при её вызове, будут передаваться некоторые значения: в данный момент - массив для хранения данных индикатора A/D, количество баров истории для поиска экстремумов графика A/D, наименование рабочего инструмента (символа) и таймфрейм.

Для прорисовки трендовых линий необходимо будет найти экстремумы графика A/D и через нижние экстремумы провести трендовую линию вверх, а через верхние экстремумы - трендовую линию вниз.

Условием для прорисовки трендовой линии вверх должно послужить наличие двух нижних экстремумов, причём экстремум, имеющий наименьшее значение должен находиться левее экстремума, находящегося в непосредственной близости от текущего (нулевого) бара. Точно так же и для трендовой линии вниз: экстремум, имеющий наибольшее значение должен находиться левее экстремума, ближнего к текущему бару.

Если экстремумов с такими условиями найдено не будет, то на данном тике трендовые линии не прорисовываются. Такие трендовые линии назовём "глобальными":

Далее к нарисованным "глобальным" трендовым линиям добавим ещё две, но для их прорисовки будем использовать два крайних правых экстремума, причём

Если экстремумов с такими условиями найдено не будет, то на данном тике трендовые линии не прорисовываются.

Такие трендовые линии назовём "локальными":

В итоге получим такую картину:


"Локальные" экстремумы в большинстве случаев нам не потребуются из-за особенностей определения "глобальных" экстремумов и прорисовки трендовых линий по ним. Мы сделаем возможность поиска и прорисовки "локальных" экстремумов отключаемой.

Для работы функции необходимо будет наличие глобальных переменных советника, а именно, массива для хранения данных A/D. Запишем его в глобальные переменные советника:

double      TempIND[];    // Массив для хранения данных индикатора A/D

Теперь нам необходимо считать данные индикатора и заполнить ими массив:

int SignalCrossIND(double &TempIND[], int nBars, string sy, int tf)
{
   if (sy=="" || sy=="0") sy = Symbol();
   ArrayResize(TempIND,nBars);       // Размер массива под переданный в ф-цию размер
   for (int j=0; j<=nBars-1; j++)
      {
         TempIND[j]=iAD(sy,tf,j);    // Запишем данные инд. в цикле в массив
      }
}

Разберём что мы тут написали:

int SignalCrossIND(double &TempIND[], int nBars, string sy, int tf)

Определение самой функции, где передаваемыми в неё параметрами являются TempIND[] - имя массива значений индикатора (передаём по ссылке), nBars - количество баров истории, с которых будем брать значения, sy - наименование рабочего инструмента (символа) и таймфрейм tf.

В общем случае вызов функции может выглядеть так:

SignalCrossIND(TempIND, 30, NULL, 5);

где TempIND - имя массива, 30 - кол-во баров, NULL - текущий символ графика, 5 - таймфрейм М5.

Далее видим строку:

if (sy=="" || sy=="0") sy = Symbol();

здесь проверяется наименование переданного в функцию торгового инструмента и, если оно равно NULL или 0, то используется символ текущего графика.

Далее изменяем размер массива под значение, переданное в функцию посредством переменной nBars и в цикле заполняем его значениями данных индикатора, начиная с нулевого бара и заканчивая баром nBars-1. Так как нумерация ячеек массива начинается с нуля, то первым номером будет не 1, а 0, поэтому и последним номером соответственно будет не 30, а 29. Поэтому используем nBars-1 (для данного примера 30-1=29).

На данном этапе наша функция заполняет массив данными индикатора A/D. Теперь необходимо найти экстремумы в массиве данных графика A/D.


3. Поиск экстремумов в массивах данных индикатора A/D

Приступим-с. Для работы функции нужны не только глобальные переменные, но и переменные, которые использует сама функция.

На данном этапе нам необходимо объявить ещё два (внутренних) массива, в которые будем записывать верхние экстремумы (пички) и нижние экстремумы (донышки), два массива, в которых будем сохранять время экстремумов и некоторые переменные, используемые для нужд функции:

// ------------------------------------------------------------------------------------------                     
// -------------------------------- Переменные функции --------------------------------------
// ------------------------------------------------------------------------------------------                     

   double   PeakUP[], PeakDN[];     // Объявляем массивы пичков/донышек
   datetime TimeDN[], TimeUP[];     // Массивы для хранения времени экстремумов
   int      i, k;                   // "Внутренние" переменные функции

и займёмся, собственно, их поиском:

//------------------------------------------------------------------
// Заполнение массивов данными о пичках и донышках
//------------------------------------------------------------------         
    k=0;                                   // Инициализируем индекс массива донышек
    for (i=2; i<=nBars-1; i++)             // Пробежимся по массиву значений
       {
          if (TempIND[i]<TempIND[i-1] && 
              TempIND[i+1]>=TempIND[i])    // Нашли донышко
             {
                ArrayResize(PeakDN, k+1);  // Размер массива донышек под кол-во найденных донышек
                ArrayResize(TimeDN, k+1);  // Размер массива времени донышек под кол-во донышек
                PeakDN[k]=TempIND[i];      // Заносим значение донышка в массив донышек...
                TimeDN[k]=iTime(sy,tf,i);  // ...и в массив времени
                k++;                       // Увеличиваем индекс массива донышек
             }
        } 
// -----------------------------------------------------------------------------------------------------------                       
    k=0;                                   // Инициализируем индекс массива пичков
    for (i=2; i<=nBars-1; i++)             // Пробежимся по массиву значений
       {
          if (TempIND[i]>TempIND[i-1] && 
              TempIND[i+1]<=TempIND[i])    // Нашли пичёк
             {
                ArrayResize(PeakUP, k+1);  // Размер массива пичков под кол-во найденных пичков
                ArrayResize(TimeUP, k+1);  // Размер массива времени пичков под кол-во пичков
                PeakUP[k]=TempIND[i];      // Заносим его значение в массив пичков... 
                TimeUP[k]=iTime(sy,tf,i);  // ...и в массив времени
                k++;                       // Увеличиваем индекс массива пичков
             }
       }   

Здесь необходимо пояснить, что и как сделано. Во-первых, делаем два раздельных цикла - каждый для поиска верхних и нижних экстремумов соответственно.

Разберем работу первого цикла для поиска донышек (нижних экстремумов):

Сначала инициализируем переменную k нулём - она будет указывать на первый (нулевой) элемент массива, в который будем записывать найденные донышки. Далее организуем цикл по уже заполненному ранее массиву значений индикатора A/D, но отправной точкой будет не первый (нулевой) индекс массива, а третий, соответствующий бару 2 (i=2; ).

Зачем? Так как экстремум - это перелом графика индикатора, при котором значение на первом баре больше значения на втором, а значение на втором баре меньше значения на третьем (донышко), то и устанавливаем индекс на третий элемент массива (не забываем, что нумерация элементов начинается с нуля), предполагая, что это может оказаться экстремумом. Нулевой бар в расчетах не используется для устранения ложных срабатываний.

Далее на первой итерации цикла проверяем,

Если данное условие истинно (нашли экстремум), то сначала увеличиваем размеры массивов донышек и времени донышек на 1:

ArrayResize(PeakDN, k+1);     // Размер массива донышек под кол-во найденных донышек
ArrayResize(TimeDN, k+1);     // Размер массива времени донышек под кол-во донышек

и заносим в эти массивы данные из массива значений индикатора и увеличиваем на один индекс массива донышек:

PeakDN[k]=TempIND[i];         // Заносим значение донышка в массив донышек...
TimeDN[k]=iTime(sy,tf,i);     // ...и в массив времени
k++;                          // Увеличиваем индекс массива донышек

Теперь массивы донышек PeakDN и времени TimeDN содержат нужные нам сведения о найденном экстремуме и индекс массива донышек указывает на следующую ячейку этого массива. На следующей итерации цикла таким же самым образом ищется следующий экстремум, и его данные заносятся в новую ячейку массива донышек, на которую указывает индекс массива донышек (k).

Абсолютно аналогичным образом ищутся пички (верхние экстремумы), только сравнение 1-го, 2-го, и 3-го баров производится противоположным образом: знак меньше и больше меняем местами. На данном этапе наша функция умеет считывать данные индикатора A/D, заполнять ими массив и находить в нём экстремумы, а также сохранять данные о величинах и местоположении экстремумов в четырёх, предназначенных для этого массивах.

Вот теперь уже можно выводить на график индикатора трендовые линии, строя их по найденным экстремумам.

Сначала выведем на график индикатора "глобальные" трендовые линии. Для того чтобы построить их по экстремумам графика, необходимо сначала найти два экстремума - экстремум с наименьшим значением во всём массиве донышек и экстремум, который мы назовём основным. Основной экстремум у нас будет находиться в непосредственной близости от начала массива донышек и задаваться жёстко равным 2. Таким образом, первые два экстремума, которые на графике индикатора самые ближние к текущему бару, мы не будем брать в расчёт (чтобы оставить какую-то свободу движения цены и не реагировать на её малейшие изменения).

Основной экстремум найден, теперь необходимо найти экстремум, имеющий самое меньшее значение в массиве и он должен находиться левее основного. На графике индикатора этот экстремум окажется самым нижним. Это будет отправной точкой для окончательного поиска второго экстремума.

Если мы будем использовать именно наименьший экстремум и проводить трендовую линию от него к основному экстремуму, то очень вероятно, что возникнет ситуация, когда трендовую линию будут пересекать более высокие экстремумы, нежели найденный нами нижний. А это значит, что в такой ситуации необходимо начало трендовой линии перенести на пересекающий её экстремум. На картинках ниже такой случай наглядно изображен:

Здесь видно, как линия, нарисованная только от наименьшего экстремума, пересекается более высокими на пути её следования к основному экстремуму. Поэтому в такой ситуации необходимо последовательно пробежаться по массиву экстремумов и найти тот, при установке на который начала трендовой линии, ни один из последующих экстремумов не будет её пересекать.

Следующий за максимальным экстремум тоже нам не подходит - трендовую продолжают пересекать более высокие экстремумы:

Остальные "пробы" я пропустил и остановился на найденном "нужном" экстремуме:

Вот теперь можно и начать всё это кодировать. Для прорисовки трендовой линии вверх используются нижние экстремумы, для трендовой линии вниз - верхние.

Разберём пример построения восходящей трендовой линии по нижним экстремумам.


4. Поиск двух экстремумов для построения трендовой линии в массиве экстремумов индикатора A/D

Для поиска двух нужных экстремумов нам понадобятся внутренние переменные функции для хранения и передачи значений минимального и основного нижних и верхних экстремумов, для хранения и передачи времени минимального и основного нижних и верхних экстремумов и переменная для индексации массивов.

Добавим их в переменные нашей функции (чтобы больше не возвращаться к данному вопросу, сразу добавим все необходимые переменные функции и далее сразу же будем их использовать):

// ------------------------------------------------------------------------------------------                     
// -------------------------------- Переменные функции ----------------------------
// ------------------------------------------------------------------------------------------                     

   double   PeakUP[], PeakDN[],        // Объявляем массивы пичков/донышек
            yUP, yDN, yGUP, yGDN,      // Значение координат пересечения трендовых и индикатора UP и DN на 1 баре
            yUP2, yDN2, yGUP2, yGDN2,  // Значение координат пересечения трендовых и индикатора UP и DN на 2 баре
            CheckCross,
            PreLastVarDN,LastVarDN,    // Значения предпоследнего и последнего нижнего экстремума
            PreLastVarUP,LastVarUP,    // Значения предпоследнего и последнего верхнего экстремума
            PivotPeakDN,PivotPeakUP,
            LowestPeakDN,HighestPeakDN,// Значения минимального и максимального нижних экстремумов
            LowestPeakUP,HighestPeakUP,// Значения минимального и максимального верхних экстремумов

   datetime TimeDN[], TimeUP[],        // Массивы для хранения баров экстремумов
            PreLastTimeDN, LastTimeDN, // Время предпоследнего и последнего нижнего экстремума
            PreLastTimeUP, LastTimeUP, // Время предпоследнего и последнего верхнего экстремума
            PivotBarDN, PivotTimeDN,   // Бар и время основного нижнего экстремума
            PivotBarUP, PivotTimeUP,   // Бар и время основного верхнего экстремума
            LowestBarDN, LowestTimeDN, // Бар и время минимального нижнего экстремума
            HighestBarUP, HighestTimeUP;// Бар и время максимального верхнего экстремума
   int      i, kup, kdn, pbar=2, 
            m, l, t, asize, Index,      // "Внутренние" переменные
            WinID=WindowFind("A/D");    // Номер окна AD
   bool     CrossDN = false,            // Флаг пересечения вниз лок. тренда
            CrossUP = false,            // Флаг пересечения вверх лок. тренда
            CrossGDN = false,           // Флаг пересечения вниз глоб. тренда
            CrossGUP = false;           // Флаг пересечения вверх глоб. тренда
   
   double   pt=MarketInfo(Symbol(),MODE_POINT); // Размер пункта в валюте котировки
   

и допишем код поиска нижних экстремумов:

//====================================================================================================
// --------------------------- Поиск минимального и максимального нижних DN экстремумов -------------  
//====================================================================================================
          
  PivotTimeDN = TimeDN[pbar];                     // Время основного экстремума
  PivotBarDN  = iBarShift(sy,tf,TimeDN[pbar]);    // Бар основного экстремума
  PivotPeakDN = PeakDN[pbar];                     // Значение основного экстремума
  LowestPeakDN  = ArrayMin(PeakDN);               // Найдём значение минимального нижнего экстремума
  Index = ArraySearchDouble(PeakDN, LowestPeakDN);// Найдём индекс минимального нижнего экстремума в массиве
  LowestBarDN =iBarShift(sy,tf,TimeDN[Index]);    // Найдём бар минимального нижнего экстремума
  LowestTimeDN = TimeDN[Index];                   // Найдём время минимального нижнего экстремума

Бар основного экстремума у нас задан жестко в переменной pbar поэтому время основного экстремума берём из массива времени по индексу, записанному в данной переменной.

PivotTimeDN = TimeDN[pbar];     // Время основного экстремума

Бар основного экстремума берём из массива времени и находим его значение при помощи стандартной функции int iBarShift( string symbol, int timeframe, datetime time, bool exact=false).

К её краткому описанию вернёмся чуть позже при рассмотрении поиска минимального экстремума в массиве экстремумов.

Значение основного экстремума берём из массива значений с указанием на нужное нам значение при помощи переменной pbar (в ней записан индекс основного экстремума):

PivotPeakDN = PeakDN[pbar];     // Значение основного экстремума

Для поиска минимального нижнего экстремума можно использовать стандартную функцию int ArrayMinimum(double array[], int count=WHOLE_ARRAY, int start=0). Её и будем использовать, но оформим этот расчёт в вызываемую пользовательскую функцию. Она и будет нам возвращать результат поиска экстремума.

Чтобы не изобретать велосипед, для поиска значения минимального элемента массива возьмём уже готовую такую функцию, любезно предоставленную во всеобщее пользование Игорем Кимом в своей ветке форума. Она прекрасно подходит для наших нужд и содержит вывод сообщения об ошибке в журнал, если передаваемый в неё массив окажется пуст (это удобно при отладке):

//+----------------------------------------------------------------------------+
//|  Автор    : Ким Игорь В. aka KimIV,  http://www.kimiv.ru                   |
//+----------------------------------------------------------------------------+
//|  Версия   : 17.05.2008                                                     |
//|  Описание : Возвращает значение минимального элемента массива.             |
//+----------------------------------------------------------------------------+
//|  Параметры:                                                                |
//|    x - массив значений числового ряда                                      |
//+----------------------------------------------------------------------------+
double ArrayMin(double& x[]) {
  if (ArraySize(x)>0) return(x[ArrayMinimum(x)]);
  else {
    Print("ArrayMin(): Массив пуст!");
    return(0);
  }
}

Как видите, тут совсем нет ничего сложного - та же стандартная функция ArrayMinimum() с проверкой на наличие данных в передаваемом в неё массиве. Аналогичную функцию мы позаимствуем там же и для поиска индекса минимального экстремума в массиве по его значению:

//+----------------------------------------------------------------------------+
//|  Автор    : Ким Игорь В. aka KimIV,  http://www.kimiv.ru                   |
//+----------------------------------------------------------------------------+
//|  Версия   : 01.09.2005                                                     |
//|  Описание : Выполняет поиск элемента массива по значению                   |
//|             и возвращает индекс найденного элемента или -1.                |
//+----------------------------------------------------------------------------+
//|  Параметры:                                                                |
//|    m - массив элементов                                                    |
//|    e - значение элемента                                                   |
//+----------------------------------------------------------------------------+
int ArraySearchDouble(double& m[], double e) {
  for (int i=0; i<ArraySize(m); i++) {
    if (m[i]==e) return(i);
  }
  return(-1);
}

Здесь организуется цикл по всему массиву с поиском переданной в функцию величины. При совпадении переданной в функцию величины со значением элемента массива, функция возвращает значение переменной i, которая на момент совпадения равна индексу искомой величины в массиве. Если искомая величина не найдена, функция вернёт -1, что можно использовать для контроля ошибок в своей программе.

Теперь необходимо найти бар на графике, который соответствует найденному экстремуму. Для этого используем данные из ранее заполненного массива времени TimeDN[] и только что полученный индекс (Index) нужного нам экстремума:

// Найдём бар минимального нижнего экстремума
LowestBarDN =iBarShift(sy,tf,TimeDN[Index]);    

Для этого используем стандартную функцию int iBarShift( string symbol, int timeframe, datetime time, bool exact=false)

Поиск бара по времени. Функция возвращает смещение бара, которому принадлежит указанное время. Если для указанного времени бар отсутствует ("дыра" в истории), то функция возвращает, в зависимости от параметра exact, -1 или смещение ближайшего бара.

Здесь в функцию первым параметром передаётся текущий символ графика, хранящийся в переменной sy, вторым параметром передаётся текущий таймфрейм, хранящийся в переменной tf, время datetime time, которое мы передаём в iBarShift(), записано у нас в массив времени TimeDN[] и индексируется значением переменной Index, которое мы нашли на предыдущем шаге.

Значение bool exact оставляем по умолчанию, так как если передать значение true, то функция в случае отсутствия бара ("дыра" в истории) вернёт значение -1 и оно будет использовано в дальнейших расчётах, что приведёт к ошибке. При значении по умолчанию false , в случае отсутствия бара в истории, функция вернёт значение ближайшего бара, что тоже не хорошо, но лучше, чем абсолютно неверная величина.

Теперь, для полноты картины и дальнейших расчётов сохраним ещё и время найденного бара. Здесь вообще всё просто: в массиве TimeDN[] мы сохраняли время. Вот мы его и извлечём из массива по всё тому же индексу:

// Найдём время минимального нижнего экстремума
LowestTimeDN = TimeDN[Index];                   

Основной и минимальный экстремумы у нас найдены. Теперь необходимо проверить будет ли трендовая линия, прочерченная через две найденные точки графика, пересекаться другими экстремумами, находящимися между двумя, найденными нами экстремумами (об этом я уже писал выше):

Для поиска таких пересечений будем использовать виртуальную линию, прочерченную от минимального экстремума к основному. В цикле пробежимся по массиву экстремумов, следующих по направлению от минимального к основному и, если найдём пересечение экстремума с линией, то поставим начало линии на этот экстремум и опять пробежимся в цикле по массиву, только начиная уже от только что найденного к основному.

Так будем проверять пересекающие линию экстремумы до тех пор, пока таковых не останется. Это будет означать, что мы нашли тот экстремум, который нужен и остальные экстремумы, следующие за найденным к основному, нашу трендовую линию уже не пересекают.

if (LowestBarDN>PivotBarDN)                           // Если наименьший экстремум справа от первого ... 
                                                      // ... (который в 0 ячейке массива времени донышек)
for (m=Index-1; m>pbar; m--)                          // Цикл от следующего за наименьшим к первому
  {
// --------- Начертим виртуальную трендовую линию и проверим её пересечение с другими экстремумами -----                                  
  CheckCross=EquationDirect(iBarShift(sy,tf,TimeDN[m+1]),
                            PeakDN[m+1],                      // Первая координата линии
                            PivotBarDN, PivotPeakDN,          // Вторая координата линии
                            iBarShift(sy,tf,TimeDN[m],false));// Точка пересечения на следующем экстремуме
      if (TempIND[iBarShift(sy,tf,TimeDN[m])]<CheckCross)     // Если на ней экстремум ниже линии
         {
            if (PeakDN[m]<PeakDN[pbar])                       // если этот экстремум ниже основного
               {
                  LowestBarDN =iBarShift(sy,tf,TimeDN[m]);    // Значит на него и нужно встать...
                  LowestPeakDN  = PeakDN[m];                  // Новые координаты следующ. экстремума
                  LowestTimeDN = TimeDN[m];                             
               }
         }                       
   }

Здесь мы организовали цикл от следующего за наименьшим экстремумом к первому экстремуму в массиве экстремумов. Это сделано потому что от наименьшего мы прочертим линию, а вот проверку пересечений начнём от следующего экстремума.

Для прорисовки виртуальной линии будем использовать функцию уравнения прямой, которая вычисляет значение Y для X в точке пересечения с прямой:

double EquationDirect(double x1, double y1, double x2, double y2, double x) {
if (x2==x1) return(y1);
return((y2-y1)/(x2-x1)*(x-x1)+y1);

x1, y1 - координаты первой точки; x2, y2 - координаты второй точки; x - значение, для которого вычислить y. Данная функция позаимствована у И.Кима в его ветке форума.

Итак:

CheckCross=EquationDirect(iBarShift(sy,tf,TimeDN[m+1]), PeakDN[m+1], // Первая координата линии
                          PivotBarDN, PivotPeakDN,                   // Вторая координата линии                                  
                          iBarShift(sy,tf,TimeDN[m],false));         // Точка пересечения на следующем экстремуме

Подставляем в передаваемые параметры функции в качестве координаты x первой точки бар наименьшего экстремума, в качестве координаты y первой точки - значение наименьшего экстремума. В качестве второй координаты точки виртуальной линии используем бар и значение "основного" экстремума.

Последним значением, передаваемым в функцию, является номер бара, который мы передаём в цикле в функцию для вычисления значений координаты y и сравнения полученного значения со значением экстремума, находящегося в данной точке.

if (TempIND[iBarShift(sy,tf,TimeDN[m])]<CheckCross)  // Если на ней экстремум ниже линии
   {
    if (PeakDN[m]<PeakDN[pbar])                      // если этот экстремум ниже основного
       {
         LowestBarDN  =iBarShift(sy,tf,TimeDN[m]);   // Значит на него и нужно встать...
         LowestPeakDN = PeakDN[m];                   // Новые координаты следующ. экстремума
         LowestTimeDN = TimeDN[m];                             
        }
   }            

Таким образом, если значение, возвращённое функцией, будет больше значения экстремума, находящегося в данной точке, значит экстремум пересекает виртуальную линию и нужно линию рисовать от этого экстремума. Запоминаем новые значения координат и переходим к следующей итерации цикла для определения пересечения со следующим экстремумом.

Как только будет найден тот экстремум, после которого нет пересечений с последующими экстремумами, значит, найден нужный экстремум для прорисовки трендовой линии. Остаётся только узнать, что этот экстремум имеет значение меньше основного и находится слева от него, а значит и линия, прочерченная от него к основному, будет восходящей и направлена в нужную сторону.

if (LowestBarDN>PivotBarDN && LowestPeakDN<PivotPeakDN)// Если наименьший экстремум слева и ниже основного

А это означает, что теперь имеем все необходимые данные для построения линии восходящего тренда на графике A/D.


5. Построение трендовых линий на графике A/D

Для идентификации наличия окна индикатора A/D в окне основного чарта, введём ещё одну переменную WinID - она будет хранить номер подокна графика A/D:

 WinID=WindowFind("A/D");               // Номер окна AD

Здесь стандартная функция WindowFind("name"); возвращает номер подокна графика, содержащего индикатор с указанным именем name, если он найден, иначе возвращается -1. Трендовую линию на графике A/D, в принципе, рисовать нет никакой необходимости - она для расчётов не нужна, но для наглядности и понимания, что всё верно, мы её изобразим:

if (WinID>0)
   {
    if (ObjectFind("Trend_GLine_DN")<0)
    ObjectCreate("Trend_GLine_DN",OBJ_TREND,WinID,LowestTimeDN,LowestPeakDN,PivotTimeDN,PivotPeakDN);
    ObjectSet("Trend_GLine_DN",OBJPROP_COLOR,Lime);
    ObjectSet("Trend_GLine_DN",OBJPROP_TIME1,LowestTimeDN);
    ObjectSet("Trend_GLine_DN",OBJPROP_PRICE1,LowestPeakDN);
    ObjectSet("Trend_GLine_DN",OBJPROP_TIME2,PivotTimeDN);
    ObjectSet("Trend_GLine_DN",OBJPROP_PRICE2,PivotPeakDN);
   }

Тут мы сперва проверим наличие окна индикатора A/D прикреплённого к основному окну. Если его нет, то и рисовать негде:

if (WinID>0)

Всё просто: если индикатор не прикреплён к окну чарта, то функция WindowFind() вернёт -1. Значение ноль всегда имеет окно основного графика - т.е. сам чарт, поэтому и проверяем значение на "больше нуля". Так как в окне чарта может находиться не один график A/D, но и другие индикаторы, значит, доподлинно неизвестно, какой номер будет присвоен нужному нам A/D, поэтому и проверяем не сам номер, а величину больше нуля.

Продолжим... Если идентификатор окна (WinID) больше нуля, то график A/D присутствует, и можно на нём и порисовать. Далее проверяем наличие на графике уже нарисованной трендовой линии по её имени:

if (ObjectFind("Trend_GLine_DN")<0)

и, если её ещё нет (функция ObjectFind() вернула -1, а при наличии линии она вернёт индекс окна A/D), то можно рисовать. Для этого воспользуемся функцией:

ObjectCreate("Trend_GLine_DN",OBJ_TREND,WinID,LowestTimeDN,LowestPeakDN,PivotTimeDN,PivotPeakDN);

bool ObjectCreate(string name, int type, int window, datetime time1, double price1, datetime time2=0, double price2=0, datetime time3=0, double price3=0) производит создание объекта с указанным именем, тип и начальные координаты в указанном подокне графика. Число координат, связываемых с объектом, может быть от 1 до 3 в зависимости от типа. При успешном создании объекта функция возвращает TRUE, иначе FALSE.

Разберём подробнее:

Следующими параметрами, передаваемыми в функцию, будут координаты двух точек, по которым строится трендовая линия:

Остальные параметры функции ObjectCreate() для построения трендовой линии не используются.

Итак, на данном этапе объект трендовой линии создан. Теперь необходимо изменить некоторые его параметры. А именно: цвет линии и координаты линии. Цвет линии будет оставаться постоянным, а вот координаты необходимо менять каждый раз при изменении данных о новых экстремумах и прорисовывать трендовую линию уже по ним:

ObjectSet("Trend_GLine_DN",OBJPROP_COLOR,Lime);

bool ObjectSet( string name, int prop_id, double value) производит изменение значения указанного свойства объекта. В случае успеха функция возвращает TRUE, иначе FALSE. В данной строке кода мы задаём цвет восходящей трендовой линии, присваивая ей стандартную цветовую константу Lime, используемую в наборе Web-цветов.

Параметрами, передаваемыми в функцию, являются:

С изменением остальных свойств объекта думаю нет никаких сложностей - всё по аналогии с вышеприведённым примером по изменению цвета объекта:

 ObjectSet("Trend_GLine_DN",OBJPROP_TIME1,LowestTimeDN);
 ObjectSet("Trend_GLine_DN",OBJPROP_PRICE1,LowestPeakDN);
 ObjectSet("Trend_GLine_DN",OBJPROP_TIME2,PivotTimeDN);
 ObjectSet("Trend_GLine_DN",OBJPROP_PRICE2,PivotPeakDN);

Cначала меняем первую координату времени экстремума (OBJPROP_TIME1), затем первую координату значения (OBJPROP_PRICE1), далее меняем вторую координату времени экстремума (OBJPROP_TIME2) и вторую координату значения (OBJPROP_PRICE1). Значения всех координат у нас записаны в переменных LowestTimeDN, LowestPeakDN, PivotTimeDN и PivotPeakDN.

Трендовая линия у нас нарисована на графике индикатора A/D (при условии наличия самого окна индикатора). Теперь необходимо определить пересечение линией графика A/D линии тренда, построенной по экстремумам графика индикатора. Размышляя над данной задачей, я пришел к выводу, что проще (мне) рассчитать данное пересечение, используя уравнение прямой.


6. Определение пересечения линией графика A/D трендовой линии, построенной по экстремумам графика индикатора

Имея две точки, от которых построена трендовая линия, рассчитываем по уравнению прямой воображаемую линию, которая будет проецироваться на первый бар линии индикатора A/D. (работаем по первому бару для уменьшения ложных срабатываний).

Точка, рассчитанная по уравнению прямой и спроецированная на первый бар индикатора, и будет точкой, в которой будем проверять пересечения. Далее останется только отметить на графике A/D эти рассчитанные точки и проверить сам факт их пересечения линией индикатора либо сверху вниз, либо снизу вверх.

Ещё одно "но". Так как будем определять именно пересечение, а не выше/ниже, значит, в наших расчётах нам потребуется ещё и второй бар графика индикатора A/D. Если линия индикатора выше точки пересечения на первом баре и ниже или равна точки пересечения на втором баре, имеем пересечение снизу вверх. Значит, будем рассчитывать две линии по экстремумам. Первую будем проецировать на первый бар индикатора, вторую - на второй.

В точках воображаемого пересечения поставим для наглядности метки, чтобы визуализировать итог наших расчётов.

Приступим...


7. Определение и установка точек возможного пересечения

Нам потребуются дополнительные переменные для хранения рассчитанных значений точек возможных пересечений.

Мы их уже добавили заранее в список переменных функции:

// ------------------------------------------------------------------------------------------                     
// -------------------------------- Переменные функции ----------------------------
// ------------------------------------------------------------------------------------------                     
   double   PeakUP[], PeakDN[],           // Объявляем массивы пичков/донышек
            yUP, yDN, yGUP, yGDN,         // Значение координат пересечения трендовых и индикатора UP и DN на 1 баре
            yUP2, yDN2, yGUP2, yGDN2,     // Значение координат пересечения трендовых и индикатора UP и DN на 2 баре
            LowestPeakDN,HighestPeakDN,   // Значения минимального и максимального нижних экстремумов
            LowestPeakUP,HighestPeakUP;   // Значения минимального и максимального верхних экстремумов
   datetime TimeDN[], TimeUP[],           // Массивы для хранения баров экстремумов
            LowestTimeDN, HighestTimeDN,  // Бар минимального и максимального нижних экстремумов
            LowestTimeUP, HighestTimeUP,  // Бар минимального и максимального верхних экстремумов
            LowestTDN, HighestTDN,        // Время минимального и максимального нижних экстремумов
            LowestTUP, HighestTUP;        // Время минимального и максимального верхних экстремумов
   int      i, k, Index,                  // "Внутренние" переменные
            WinID=WindowFind("A/D");      // Номер окна AD

Забегая вперёд, скажу, что мы добавили не 4 переменных, а 8, так как нам потребуются ещё 4 такие же переменные, но для расчёта "локальных" трендовых линий и точек пересечения. Сейчас будем использовать yGUP, yGDN, yGUP2 и yGDN2, которые будут хранить точки пересечения с:

Давайте ещё раз воспользуемся функцией уравнения прямой, любезно предоставленной Игорем Кимом:

//+----------------------------------------------------------------------------+
//|  Автор    : Ким Игорь В. aka KimIV,  http://www.kimiv.ru                   |
//+----------------------------------------------------------------------------+
//|  Версия   : 12.10.2007                                                     |
//|  Описание : Уравнение прямой.                                              |
//|             Вычисляет значение Y для X в точке пересечения с прямой.       |
//+----------------------------------------------------------------------------+
//|  Параметры:                                                                |
//|    x1,y1 - координаты первой точки,                                        |
//|    x2,y2 - координаты второй точки,                                        |
//|    x     - значение, для которого вычислить Y                              |
//+----------------------------------------------------------------------------+
double EquationDirect(double x1, double y1, double x2, double y2, double x) 
{
  if (x2==x1) return(y1);
  return((y2-y1)/(x2-x1)*(x-x1)+y1);
}

В функцию в качестве отправных точек расчёта будем передавать 5 параметров: два на координаты двух точек экстремумов графика и последний - бар, на котором нужно найти точку пересечения:

yGDN =EquationDirect(LowestBarDN,LowestPeakDN,PivotBarDN,PivotPeakDN,iBarShift(sy,tf,Time[1],false));
yGDN2=EquationDirect(LowestBarDN,LowestPeakDN,PivotBarDN,PivotPeakDN,iBarShift(sy,tf,Time[2],false));

Здесь в качестве координат первой точки используем координаты наименьшего экстремума: x1 = LowestTimeDN, y1 = LowestPeakDN, а в качестве координат второй точки используем координаты основного экстремума: x2 = PivotBarDN, y2 = PivotPeakDN.

В качестве значения, для которого вычисляется уравнение передаём смещение нужного нам бара: iBarShift(sy, tf, Time[1], false), где sy - символьное имя валютного инструмента, tf - таймфрейм, Time[1] - Массив-таймсерия, содержащий время открытия каждого бара текущего графика. Данные типа datetime представляют собой время в секундах, прошедшее с 00:00 1 января 1970 года.

Остальные параметры данной функции нами уже разбирались ранее. Хочется отметить, что если наша функция будет использована в мультивалютном советнике, то вместо Time[1] нужно использовать стандартную функцию iTime() (что, впрочем, сделаем в окончательном варианте функции), в которую явно передаются значения валютного инструмента и рабочего таймфрейма, тогда как Time[] работает только с текущим графиком.

Уровни пересечения у нас посчитаны и хранятся в переменных. Для наглядности теперь можно отметить их на графике индикатора A/D:

  if (WinID>0)
     {
         if (ObjectFind("PointGDN"+Time[1])<0)
         ObjectCreate("PointGDN"+Time[1],OBJ_ARROW,WinID,Time[1],yGDN);
         ObjectSet("PointGDN"+Time[1],OBJPROP_ARROWCODE,4);
         ObjectSet("PointGDN"+Time[1],OBJPROP_COLOR,Lime);
         ObjectSet("PointGDN"+Time[1],OBJPROP_TIME1,Time[1]);
         ObjectSet("PointGDN"+Time[1],OBJPROP_PRICE1,yGDN);
     }

Здесь то же самое, что мы делали для прорисовки трендовой линии. Остановимся на небольших отличиях.

Первое отличие - в имени объекта:

ObjectCreate("PointGDN"+Time[1],OBJ_ARROW,WinID,Time[1],yGDN);

Так как на каждом новом баре мы будем строить не один объект, а целое множество, то и имена всех объектов должны быть уникальными. Для этого к имени объекта будем добавлять ещё и время бара, на котором строится объект:

"PointGDN"+Time[1]

Второе отличие - идентификатор типа графического объекта. Если для трендовой линии мы использовали OBJ_TREND, то теперь будем использовать OBJ_ARROW, который позволяет рисовать стрелки (символы). Далее WinID - номер подокна, в котором будем рисовать объект, это у нас номер окна A/D, ну и две координаты - координата времени Time[1] - время первого бара; и координата цены yGDN - рассчитанное нами место пересечения трендовой линии с первым баром.

Следующие строки устанавливают свойства объекта:

ObjectSet("PointGDN"+Time[1],OBJPROP_ARROWCODE,4);

Идентификатор свойств объекта OBJPROP_ARROWCODE устанавливает код объекта и может быть одним из символов wingdings или один из предопределенных кодов стрелок. Для точной установки объекта на рассчитанный уровень существуют специальные коды стрелок, которые точно указывают на цену и время. Воспользуемся символом черточки (–), который имеет код 4. Остальные строки задают цвет объекта и его постоянно меняющиеся координаты на каждом баре.

ObjectSet("PointGDN"+Time[1],OBJPROP_COLOR,Lime);
ObjectSet("PointGDN"+Time[1],OBJPROP_TIME1,Time[1]);
ObjectSet("PointGDN"+Time[1],OBJPROP_PRICE1,yGDN);

Теперь у нас нарисованы точки вероятного пересечения линии индикатора с линией восходящего "глобального" тренда, построенной по экстремумам. Именно в этих точках мы и будем искать пересечения. Если оно будет найдено, то на основной график поставим сигнальный указатель - стрелку вниз, чем, впрочем, прямо сейчас и займёмся.


8. Проверка пересечений линии A/D с рассчитанными точками пересечений и установка сигнальных указателей

Для определения пересечения линии индикатора A/D с точками пересечений просто сравним их значения на больше/меньше и, если значение линии индикатора на данном баре меньше значения точки пересечения, а на предыдущем баре значение индикатора больше значения точки пересечения - значит, имеем пересечение вниз:

 if (NormalizeDouble(TempIND[1],8) < NormalizeDouble(yGDN,8) && 
     NormalizeDouble(TempIND[2],8) >= NormalizeDouble(yGDN2,8))
    {
        CrossGDN = true;           // Установим флаг пересечения вниз
        CrossGUP = false;          // Снимем флаг пересечения вверх
                                    
        if (ObjectFind("ArrowGDN"+Time[1])<0)
        ObjectCreate("ArrowGDN"+Time[1],OBJ_ARROW, 0 ,Time[1], iHigh(sy,tf,1)+5*pt);
        ObjectSet("ArrowGDN"+Time[1],OBJPROP_ARROWCODE,242);
        ObjectSet("ArrowGDN"+Time[1],OBJPROP_COLOR,OrangeRed);
        ObjectSet("ArrowGDN"+Time[1],OBJPROP_WIDTH,1);
        ObjectSet("ArrowGDN"+Time[1],OBJPROP_TIME1,Time[1]);
        ObjectSet("ArrowGDN"+Time[1],OBJPROP_PRICE1,iHigh(sy,tf,1)+5*pt);
    }

Для сравнения данных типа double их значения необходимо нормализовать до нужной точности стандартной функцией double NormalizeDouble(double value, int digits). Округление числа с плавающей запятой до указанной точности.

Рассчитываемые значения StopLoss, TakeProfit, а также значения цены открытия отложенных ордеров должны быть нормализованы с точностью, значение которой хранится в предопределенной переменной Digits:

if (NormalizeDouble(TempIND[1],8) < NormalizeDouble(yGDN,8) && 
    NormalizeDouble(TempIND[2],8) >= NormalizeDouble(yGDN2,8))

что мы и сделали при сравнении значений линии индикатора и расчитанных точек пересечения. При выполнении данного условия задаём значения переменным типа bool CrossGDN и CrossGUP значения true (истина) и false (ложь) соответственно.

Следующий блок кода выполняет установку стрелки вниз в окне основного графика терминала:

  if (ObjectFind("ArrowGDN"+Time[1])<0)
  ObjectCreate("ArrowGDN"+Time[1],OBJ_ARROW, 0 ,Time[1], iHigh(sy,tf,1)+5*pt);
  ObjectSet("ArrowGDN"+Time[1],OBJPROP_ARROWCODE,242);
  ObjectSet("ArrowGDN"+Time[1],OBJPROP_COLOR,OrangeRed);
  ObjectSet("ArrowGDN"+Time[1],OBJPROP_WIDTH,1);
  ObjectSet("ArrowGDN"+Time[1],OBJPROP_TIME1,Time[1]);
  ObjectSet("ArrowGDN"+Time[1],OBJPROP_PRICE1,iHigh(sy,tf,1)+5*pt);

Думаю, что этот блок кода в особых пояснениях уже не нуждается - мы уже рассматривали установку значков и линий в окна терминала. Единственное отличие, не рассматриваемое нами ранее, это указание окна основного графика терминала. Это окно всегда имеет номер 0, поэтому в строке

ObjectCreate("ArrowGDN"+Time[1],OBJ_ARROW, 0 ,Time[1], iHigh(sy,tf,1)+5*pt);

мы в качестве идентификатора окна поставили ноль, а значит дали указание ставить стрелки в главное окно терминала.

Ещё пара моментов: в качестве координаты y стрелки вниз (её код 242) мы используем величину значения максимальной цены того бара, над которым будем ставить стрелку, а расстояние отступа зададим равной пяти пунктам:

iHigh(sy,tf,1)+5*pt

double iHigh( string symbol, int timeframe, int shift) Возвращает значение максимальной цены указанного параметром shift бара с соответствующего графика (symbol, timeframe).

Значение pt получаем из строки, прописанной сразу же за объявлением переменных функции:

double pt=MarketInfo(Symbol(),MODE_POINT);

Это стандартная функция double MarketInfo( string symbol, int type). Возвращает различную информацию о финансовых инструментах, перечисленных в окне "Обзор рынка". Идентификатор запроса функции MODE_POINT возвращает нам размер пункта в валюте котировки. Для текущего инструмента хранится в предопределенной переменной Point. В строке:

ObjectSet("ArrowGDN"+Time[1],OBJPROP_WIDTH,1);

мы задаём размер выводимой стрелки = 1.


9. Проверка пересечения линии восходящего тренда в сторону направления движения валютной пары

Пересечение вниз восходящей трендовой линии говорит нам о возможной смене направления движения валютной пары в скором времени, а вот пересечение этой же линии восходящего тренда вверх может сигнализировать о продолжении движения, поэтому на всякий случай сделаем проверку и такого пересечения, но она будет отключаемой.

Для этого изменим первую строку функции, добавив в неё ещё два передаваемых параметра функции:

int SignalCrossIND(double &TempIND[], int nBars, string sy, int tf, bool local=false, bool add=false)

Здесь мы добавили две переменные типа bool local и add. Переменная local отвечает за расчёт отображение "локальных" трендовых линий, а переменная add - за проверку пересечения снизу вверх линии восходящего тренда и проверку пересечения сверху вниз линии нисходящего тренда. По умолчанию эти переменные имеют значение false, а значит, если их опустить при вызове функции, то функция не будет рассчитывать и выводить линии локального тренда и не будет проверять данные пересечения.

 if (add)
    {
       if (NormalizeDouble(TempIND[1],8) > NormalizeDouble(yGDN,8) && 
           NormalizeDouble(TempIND[2],8) <= NormalizeDouble(yGDN2,8))
           {
              CrossGUP = true;
              CrossGDN = false;        
              if (ObjectFind("ArrowGUP"+Time[1])<0)
              ObjectCreate("ArrowGUP"+Time[1],OBJ_ARROW, 0 ,Time[1], iLow(sy,tf,1));
              ObjectSet("ArrowGUP"+Time[1],OBJPROP_ARROWCODE,241);
              ObjectSet("ArrowGUP"+Time[1],OBJPROP_COLOR,Lime);
              ObjectSet("ArrowGUP"+Time[1],OBJPROP_WIDTH,0);
              ObjectSet("ArrowGUP"+Time[1],OBJPROP_TIME1,Time[1]);
              ObjectSet("ArrowGUP"+Time[1],OBJPROP_PRICE1,iLow(sy,tf,1));
           }
    }

В этом блоке кода всё уже нам знакомо и рассматривать его в принципе и не нужно. Заострим внимание только на проверке переменной add:

 if (add)

эта запись совершенно идентична записи:

 if (add==true)

и она выполняет проверку, что данная переменная установлена в значение "истина". Если да, то и выполняются все строки, заключённые в парные скобки {}, следующие за этой строкой. Остались для рассмотрения две строчки:

ObjectSet("ArrowGUP"+Time[1],OBJPROP_ARROWCODE,241);

Здесь последним параметром, передаваемым в функцию является код стрелки "вверх" - 241, и

ObjectSet("ArrowGUP"+Time[1],OBJPROP_WIDTH,0);

где последнее значение, передаваемое в функцию, отвечает за размер рисуемой стрелки. В данном случае мы выводим маленькую стрелочку, размером 0.

На данный момент мы имеем полностью готовый код, который считывает данные индикатора A/D, заполняет ими массив, ищет экстремумы графика индикатора, прорисовывает на нём линию восходящего тренда, проверяет пересечение этой линии линией индикатора A/D и при обнаружении такого пересечения ставит стрелки вверх или вниз (в зависимости от направления пересечения) на основном графике валютного инструмента в окне терминала.

Ниже приведу код для такой же проверки, но уже для линии нисходящего тренда. Код предлагается для самостоятельного разбора:

//====================================================================================================
// -------------------- Поиск минимального и максимального верхних UP экстремумов --------------------            
//====================================================================================================
    PivotTimeUP = TimeUP[pbar];                           // Время основного экстремума
    PivotBarUP  = iBarShift(sy,tf,TimeUP[pbar]);          // Бар основного экстремума
    PivotPeakUP = PeakUP[pbar];                           // Значение основного экстремума

    HighestPeakUP = ArrayMax(PeakUP);                     // Найдём значение максимального верхнего экстремума
    Index = ArraySearchDouble(PeakUP, HighestPeakUP);     // Найдём индекс максимального верхнего экстремума в массиве
    HighestBarUP =iBarShift(sy,tf,TimeUP[Index]);         // Найдём бар максимального верхнего экстремума
    HighestTimeUP = TimeUP[Index];                        // Найдём время максимального верхнего экстремума

    if (HighestBarUP>PivotBarUP)                          // Если наибольший экстремум слева от первого ... 
                                                          // ... (который в 0 ячейке массива времени пичков)
   for (m=Index-1; m>pbar; m--)                           // Цикл от следующего за наибольшим к первому
      {
// --------- Начертим виртуальную трендовую линию и проверим её пересечение с другими экстремумами ----------  
      CheckCross=EquationDirect(iBarShift(sy,tf,TimeUP[m+1]), 
                                PeakUP[m+1],                      // Первая координата линии
                                PivotBarUP, PivotPeakUP,          // Вторая координата линии
                                iBarShift(sy,tf,TimeUP[m],false));// Точка пересечения на следующем экстремуме
      if (TempIND[iBarShift(sy,tf,TimeUP[m])]>CheckCross)         // Если на ней экстремум выше линии
         {
            if (PeakUP[m]>PeakUP[pbar])                           // если этот экстремум выше основного
               {
                  HighestBarUP =iBarShift(sy,tf,TimeUP[m]);       // Значит на него и нужно встать...
                  HighestPeakUP = PeakUP[m];                      // Новые координаты следующ. экстремума
                  HighestTimeUP = TimeUP[m];                             
               }
         }                       
   }

   if (HighestBarUP>PivotBarUP && HighestPeakUP>PivotPeakUP)      // Если наибольший экстремум слева от основного     
      {
// ---------------- Начертим нисходящую трендовую линию (UP - экстремумы) ---------------------                         
          if (WinID>0)
             {
                if (ObjectFind("Trend_Line_GUP")<0)
                ObjectCreate("Trend_Line_GUP",OBJ_TREND,WinID,HighestTimeUP,HighestPeakUP,PivotTimeUP,PivotPeakUP);
                ObjectSet("Trend_Line_GUP",OBJPROP_COLOR,OrangeRed);
                ObjectSet("Trend_Line_GUP",OBJPROP_TIME1,HighestTimeUP);
                ObjectSet("Trend_Line_GUP",OBJPROP_PRICE1,HighestPeakUP);
                ObjectSet("Trend_Line_GUP",OBJPROP_TIME2,PivotTimeUP);
                ObjectSet("Trend_Line_GUP",OBJPROP_PRICE2,PivotPeakUP);
             }        
//---------------- Рассчёт уровней нисходящего тренда (UP - экстремумы) ------------------            
    yGUP =EquationDirect(HighestBarUP, HighestPeakUP, PivotBarUP, PivotPeakUP, iBarShift(sy,tf,Time[1],false));
    yGUP2=EquationDirect(HighestBarUP, HighestPeakUP, PivotBarUP, PivotPeakUP, iBarShift(sy,tf,Time[2],false));

//---------------- Начертим уровни нисходящего тренда (UP - экстремумы) ------------------            
         if (WinID>0)
            {
               if (ObjectFind("PointGUP"+Time[1])<0)
               ObjectCreate("PointGUP"+Time[1],OBJ_ARROW,WinID,Time[1],yGUP);
               ObjectSet("PointGUP"+Time[1],OBJPROP_ARROWCODE,4);
               ObjectSet("PointGUP"+Time[1],OBJPROP_COLOR,OrangeRed);
               ObjectSet("PointGUP"+Time[1],OBJPROP_TIME1,Time[1]);
               ObjectSet("PointGUP"+Time[1],OBJPROP_PRICE1,yGUP);
            }
         
// --------------- Проверим пересечение вверх нисходящего тренда -----------------   
         if (NormalizeDouble(TempIND[1],8) > NormalizeDouble(yGUP,8) && 
             NormalizeDouble(TempIND[2],8) <= NormalizeDouble(yGUP2,8))
               {
                  CrossGUP = true;                 // Установим флаг пересечения вверх
                  CrossGDN = false;                // Снимем флаг пересечения вниз
               
                  if (ObjectFind("ArrowGUP"+Time[1])<0)
                  ObjectCreate("ArrowGUP"+Time[1],OBJ_ARROW, 0 ,Time[1], iLow(sy,tf,1));
                  ObjectSet("ArrowGUP"+Time[1],OBJPROP_ARROWCODE,241);
                  ObjectSet("ArrowGUP"+Time[1],OBJPROP_COLOR,Lime);
                  ObjectSet("ArrowGUP"+Time[1],OBJPROP_WIDTH,1);
                  ObjectSet("ArrowGUP"+Time[1],OBJPROP_TIME1,Time[1]);
                  ObjectSet("ArrowGUP"+Time[1],OBJPROP_PRICE1,iLow(sy,tf,1));
               }
            
// --------------- Проверим пересечение вниз нисходящего тренда -----------------   

         if (add)
            {
               if (NormalizeDouble(TempIND[1],8) < NormalizeDouble(yGUP,8) && 
                   NormalizeDouble(TempIND[2],8) >= NormalizeDouble(yGUP2,8))
                  {
                     CrossGDN = true;
                     CrossGUP = false;
                              
                     if (ObjectFind("ArrowGDN"+Time[1])<0)
                     ObjectCreate("ArrowGDN"+Time[1],OBJ_ARROW, 0 ,Time[1], iHigh(sy,tf,1)+15*pt);
                     ObjectSet("ArrowGDN"+Time[1],OBJPROP_ARROWCODE,242);
                     ObjectSet("ArrowGDN"+Time[1],OBJPROP_COLOR,OrangeRed);
                     ObjectSet("ArrowGDN"+Time[1],OBJPROP_WIDTH,0);
                     ObjectSet("ArrowGDN"+Time[1],OBJPROP_TIME1,Time[1]);
                     ObjectSet("ArrowGDN"+Time[1],OBJPROP_PRICE1,iHigh(sy,tf,1)+15*pt);
                  }
            }
      }  
                                   


10. Поиск "локальных" экстремумов на графике A/D, прорисовка трендовых линий по ним и проверка их пересечения

Что ж, дело осталось за малым: организовать всё то же самое, но для "локальных" экстремумов.

Единственное отличие от "глобальных" экстремумов - это способ их нахождения. Будем их искать по двум крайним правым экстремумам и, если (для восходящего тренда) первый экстремум окажется выше второго, находящегося слева от первого, то будем считать, что локальную трендовую линию можно рисовать. Для нисходящей трендовой линии будет проверяться нахождение второго экстремума выше и левее первого.

Так как код абсолютно идентичен тому, что уже рассмотрели (практически, за исключением некоторых моментов), то я его приведу полностью для локальной восходящей трендовой линии:

// --------------------------- Поиск двух крайних нижних (локальных) DN экстремумов ------------           
   if (local)
      {     
         asize=ArraySize(PeakDN);
         for (l=asize-1; l>=1; l--)
            {
               if (PeakDN[l]<PeakDN[l-1])
                  {
                     LastTimeDN     =TimeDN[l-1];
                     LastVarDN      =PeakDN[l-1];
                     PreLastTimeDN  =TimeDN[l];
                     PreLastVarDN   =PeakDN[l];
                  }
            }
// --------------- Начертим восходящую локальную трендовую линию (DN - экстремумы) -----------------          
         if (WinID>0)
            {
               if (ObjectFind("Trend_Line_DN")<0)
               ObjectCreate("Trend_Line_DN",OBJ_TREND,WinID,TimeDN[1],PeakDN[1],TimeDN[0],PeakDN[0]);
               ObjectSet("Trend_Line_DN",OBJPROP_COLOR,MediumSeaGreen);
               ObjectSet("Trend_Line_DN",OBJPROP_TIME1,PreLastTimeDN);
               ObjectSet("Trend_Line_DN",OBJPROP_PRICE1,PreLastVarDN);
               ObjectSet("Trend_Line_DN",OBJPROP_TIME2,LastTimeDN);
               ObjectSet("Trend_Line_DN",OBJPROP_PRICE2,LastVarDN);
            }
//---------------- Рассчёт уровней восходящего локального тренда (DN - экстремумы) ----------------            
  yDN =EquationDirect(iBarShift(sy,tf,PreLastTimeDN,false), PreLastVarDN, 
                      iBarShift(sy,tf,LastTimeDN,false), LastVarDN, iBarShift(sy,tf,Time[1],false));
  yDN2=EquationDirect(iBarShift(sy,tf,PreLastTimeDN,false), PreLastVarDN, 
                      iBarShift(sy,tf,LastTimeDN,false), LastVarDN, iBarShift(sy,tf,Time[2],false));
               
//---------------- Начертим уровни восходящего локального тренда (DN - экстремумы) ----------------          
         if (WinID>0)
            {
               if (ObjectFind("PointDN"+Time[1])<0)
               ObjectCreate("PointDN"+Time[1],OBJ_ARROW,WinID,Time[1],yDN);
               ObjectSet("PointDN"+Time[1],OBJPROP_ARROWCODE,4);
               ObjectSet("PointDN"+Time[1],OBJPROP_COLOR,MediumSeaGreen);
               ObjectSet("PointDN"+Time[1],OBJPROP_TIME1,Time[1]);
               ObjectSet("PointDN"+Time[1],OBJPROP_PRICE1,yDN);
            }
// --------------- Проверим пересечение вниз локального восходящего тренда -----------------   
         if (NormalizeDouble(TempIND[1],8) < NormalizeDouble(yDN,8) && 
             NormalizeDouble(TempIND[2],8) >= NormalizeDouble(yDN2,8))
               {
                  CrossDN = true;
                  CrossUP = false;
                  if (Arrow)                 // Если разрешены сигнальные указатели
                     {
                        if (ObjectFind("ArrowDN"+Time[1])<0)
                        ObjectCreate("ArrowDN"+Time[1],OBJ_ARROW, 0 ,Time[1], iHigh(sy,tf,1)+15*pt);
                        ObjectSet("ArrowDN"+Time[1],OBJPROP_ARROWCODE,242);
                        ObjectSet("ArrowDN"+Time[1],OBJPROP_COLOR,Chocolate);
                        ObjectSet("ArrowDN"+Time[1],OBJPROP_WIDTH,1);
                        ObjectSet("ArrowDN"+Time[1],OBJPROP_TIME1,Time[1]);
                        ObjectSet("ArrowDN"+Time[1],OBJPROP_PRICE1,iHigh(sy,tf,1)+15*pt);
                     }
               }      
// --------------- Проверим пересечение вверх локального восходящего тренда -----------------   
         if (add)
            {
               if (NormalizeDouble(TempIND[1],8) > NormalizeDouble(yDN,8) && 
                   NormalizeDouble(TempIND[2],8) <= NormalizeDouble(yDN2,8))
                  {
                     CrossUP = true;
                     CrossDN = false;
                     if (Arrow)                 // Если разрешены сигнальные указатели
                        {
                           if (ObjectFind("ArrowUP"+Time[1])<0)
                           ObjectCreate("ArrowUP"+Time[1],OBJ_ARROW, 0 ,Time[1], iLow(sy,tf,1));
                           ObjectSet("ArrowUP"+Time[1],OBJPROP_ARROWCODE,241);
                           ObjectSet("ArrowUP"+Time[1],OBJPROP_COLOR,MediumSeaGreen);
                           ObjectSet("ArrowUP"+Time[1],OBJPROP_WIDTH,0);
                           ObjectSet("ArrowUP"+Time[1],OBJPROP_TIME1,Time[1]);
                           ObjectSet("ArrowUP"+Time[1],OBJPROP_PRICE1,iLow(sy,tf,1));
                        }
                  }                  
            }
      }            

Отличие здесь только в поиске экстремумов, по которым строится трендовая линия:

if (local)

В данной строчке проверяется разрешение на прорисовку трендовой линии и вычисления пересечений с графиком A/D.

Далее, в цикле ищем крайние два экстремума:

asize=ArraySize(PeakDN);
  for (l=asize-1; l>=1; l--)
      {
       if (PeakDN[l]<PeakDN[l-1])
          {
            LastTimeDN     =TimeDN[l-1];
            LastVarDN      =PeakDN[l-1];
            PreLastTimeDN  =TimeDN[l];
            PreLastVarDN   =PeakDN[l];
          }
      }

и их значения заносим в переменные LastTimeDN, LastVarDN, PreLastTimeDN и PreLastVarDN, где

Далее код практически идентичен уже рассмотренному ранее, за исключением переменных, которые используются для прорисовки трендовых линий и поиска пересечений с ними. Последним штрихом нужно организовать возврат значений функции.

В общем случае он может выглядеть так:

   if (CrossGUP || CrossUP) return(1);
   else
   if (CrossGDN || CrossDN) return(-1); 
   else return(0);

Здесь совсем всё просто: при пересечении восходящей трендовой линии сверху вниз, функция возвращает -1, при пересечении нисходящей трендовой линии сверху вниз, функция возвращает 1, в остальных случаях функция вернёт 0.

Конечно же, лучше проверять все пересечения восходящей и нисходящей трендовых линий как вверх, так и вниз, и, в зависимости от комбинаций пересечений, присваивать "веса" этим пересечениям и возвращать нужные значения в вызывающую функцию для дальнейшей обработки сигнала.

В заключении описания функции приведу полный её код, но с уже добавленными небольшими изменениями (проверка разрешения установки сигнальных указателей при обнаружении пересечения трендовой линии линией графика A/D и с использованием стандартной функции iTime() вместо Time для обеспечения универсальности функции).

Предлагаемый код, надеюсь, будет доступен для понимания после достаточно подробного его рассмотрения:

//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
// Поиск пересечения трендовой линии с линией графика A/D и установка сигнальных указателей
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
// TempIND[] - (объявление на глобальном уровне) массив для хранения данных индикатора A/D
// nBars - количество баров для поиска экстремумов
// sy - символ, с которым работать: "" или NULL - текущий символ графика
// tf - таймфрейм, с которым работать: 0 - текущий, 
// local - рассчитывать ли по локальным трендам: по-умолчанию - false
// add - реагировать ли на пересечение тренда в его же направлении: по умолчанию - false
// Arrow - устанавливать ли сигнальные указатели на график валютного инстркмента 
//         при найденном пересечении линии тренда с линией графика A/D: по умолчанию - true
//==========================================================================================
int SignalCrossIND(double &TempIND[], int nBars, string sy, int tf, 
                   bool local=false, bool add=false, bool Arrow=true)
{
   if (sy=="" || sy=="0") sy = Symbol();
   ArrayResize(TempIND,nBars);                     // Размер массива под переданный в ф-цию размер
   for (int j=0; j<=nBars-1; j++)
      {
         TempIND[j]=iAD(sy,tf,j);                  // Запишем данные индикатора в цикле в массив
      }
// ==========================================================================================               
// ------------------------------------ Переменные функции ---------------------------------+
// ==========================================================================================               

   double   PeakUP[], PeakDN[],                    // Объявляем массивы пичков/донышек
            yUP, yDN, yGUP, yGDN,                  // Значение координат пересечения трендовых и индикатора UP и DN на 1 баре
            yUP2, yDN2, yGUP2, yGDN2,              // Значение координат пересечения трендовых и индикатора UP и DN на 2 баре
            CheckCross,
            PreLastVarDN,LastVarDN,                // Значения предпоследнего и последнего нижнего экстремума
            PreLastVarUP,LastVarUP,                // Значения предпоследнего и последнего верхнего экстремума
            PivotPeakDN,PivotPeakUP,
            LowestPeakDN,HighestPeakDN,            // Значения минимального и максимального нижних экстремумов
            LowestPeakUP,HighestPeakUP,            // Значения минимального и максимального верхних экстремумов

   datetime TimeDN[], TimeUP[],                    // Массивы для хранения баров экстремумов
            PreLastTimeDN, LastTimeDN,             // Время предпоследнего и последнего нижнего экстремума
            PreLastTimeUP, LastTimeUP,             // Время предпоследнего и последнего верхнего экстремума
            PivotBarDN, PivotTimeDN,               // Бар и время основного нижнего экстремума
            PivotBarUP, PivotTimeUP,               // Бар и время основного верхнего экстремума
            LowestBarDN, LowestTimeDN,             // Бар  и время минимального нижнего экстремума
            HighestBarUP, HighestTimeUP;           // Бар  и время максимального верхнего экстремума
   int      i, kup, kdn, pbar=2, 
            m, l, t, asize, Index,                 // "Внутренние" переменные
            WinID=WindowFind("A/D");               // Номер окна AD
   bool     CrossDN = false,                       // Флаг пересечения вниз лок. тренда
            CrossUP = false,                       // Флаг пересечения вверх лок. тренда
            CrossGDN = false,                      // Флаг пересечения вниз глоб. тренда
            CrossGUP = false;                      // Флаг пересечения вверх глоб. тренда
   
   double pt=MarketInfo(Symbol(),MODE_POINT);      // Размер пункта в валюте котировки

// ==========================================================================================               
// ------------------ Заполнение массивов данными о пичках и донышках ----------------------+
// ==========================================================================================               
         kdn=0;                                    // Инициализируем индекс массива донышек
         for (i=2; i<=nBars-1; i++)                // Пробежимся по массиву значений от 2-го бара вглубь истории
            {
               if (TempIND[i]<TempIND[i-1] && 
                   TempIND[i+1]>=TempIND[i])       // Нашли донышко БЫЛО >=
                  {
                     ArrayResize(PeakDN, kdn+1);   // Размер массива донышек под кол-во найденных донышек
                     ArrayResize(TimeDN, kdn+1);   // Размер массива времени донышек под кол-во донышек
                     PeakDN[kdn]=TempIND[i];       // Заносим значение донышка в массив донышек...
                     TimeDN[kdn]=iTime(sy,tf,i);   // ...и в массив времени
                     kdn++;                        // Увеличиваем индекс массива донышек
                  }
            } 
// -----------------------------------------------------------------------------------------------------------                       
         kup=0;                                    // Инициализируем индекс массива пичков
         for (i=2; i<=nBars-1; i++)                // Пробежимся по массиву значений от 2-го бара вглубь истории
            {
               if (TempIND[i]>TempIND[i-1] &&      // БЫЛО >
                   TempIND[i+1]<=TempIND[i])       // Нашли пичёк БЫЛО <=
                  {
                     ArrayResize(PeakUP, kup+1);   // Размер массива пичков под кол-во найденных пичков
                     ArrayResize(TimeUP, kup+1);   // Размер массива времени пичков под кол-во пичков
                     PeakUP[kup]=TempIND[i];       // Заносим его значение в массив пичков... 
                     TimeUP[kup]=iTime(sy,tf,i);   // ...и в массив времени
                     kup++;                        // Увеличиваем индекс массива пичков
                  }
            }   
//====================================================================================================
// --------------------------- Поиск минимального и основного нижних DN экстремумов -----------------+
//====================================================================================================
         PivotTimeDN = TimeDN[pbar];                        // Время основного экстремума
         PivotBarDN  = iBarShift(sy,tf,TimeDN[pbar]);       // Бар основного экстремума
         PivotPeakDN = PeakDN[pbar];                        // Значение основного экстремума
         LowestPeakDN  = ArrayMin(PeakDN);                  // Найдём значение минимального нижнего экстремума
         Index = ArraySearchDouble(PeakDN, LowestPeakDN);   // Найдём индекс минимального нижнего экстремума в массиве
         LowestBarDN =iBarShift(sy,tf,TimeDN[Index]);       // Найдём бар минимального нижнего экстремума
         LowestTimeDN = TimeDN[Index];                      // Найдём время минимального нижнего экстремума

   if (LowestBarDN>PivotBarDN)                              // Если наименьший экстремум слева от первого ... 
                                                            // ... (который в 0 ячейке массива времени донышек)
   for (m=Index-1; m>pbar; m--)                             // Цикл от следующего за наименьшим к первому
      {
// --------- Начертим виртуальную трендовую линию и проверим её пересечение с другими экстремумами ----------  
                                  
         CheckCross=EquationDirect(iBarShift(sy,tf,TimeDN[m+1]),
                                   PeakDN[m+1],                      // Первая координата линии
                                   PivotBarDN, PivotPeakDN,          // Вторая координата линии
                                   iBarShift(sy,tf,TimeDN[m],false));// Точка пересечения на следующем экстремуме
         if (TempIND[iBarShift(sy,tf,TimeDN[m])]<CheckCross)         // Если на ней экстремум ниже линии
            {
               if (PeakDN[m]<PeakDN[pbar])                           // если этот экстремум ниже основного
                  {
                     LowestBarDN =iBarShift(sy,tf,TimeDN[m]);        // Значит на него и нужно встать...
                     LowestPeakDN  = PeakDN[m];                      // Новые координаты следующ. экстремума
                     LowestTimeDN = TimeDN[m];                             
                  }
            }                       
      }

      if (LowestBarDN>PivotBarDN && LowestPeakDN<PivotPeakDN)        // Если наименьший экстремум слева и ниже основного
            {
// --------------- Начертим восходящую трендовую линию для отладки (DN - экстремумы) ---------------------                          
               if (WinID>0)
                  {
                     if (ObjectFind("Trend_GLine_DN")<0)
                     ObjectCreate("Trend_GLine_DN",OBJ_TREND,WinID,LowestTimeDN,LowestPeakDN,PivotTimeDN,PivotPeakDN);
                     ObjectSet("Trend_GLine_DN",OBJPROP_COLOR,Lime);
                     ObjectSet("Trend_GLine_DN",OBJPROP_TIME1,LowestTimeDN);
                     ObjectSet("Trend_GLine_DN",OBJPROP_PRICE1,LowestPeakDN);
                     ObjectSet("Trend_GLine_DN",OBJPROP_TIME2,PivotTimeDN);
                     ObjectSet("Trend_GLine_DN",OBJPROP_PRICE2,PivotPeakDN);
                  }
//---------------- Расчёт уровней восходящего тренда (DN - экстремумы) ----------------         
yGDN =EquationDirect(LowestBarDN, LowestPeakDN,PivotBarDN, PivotPeakDN, iBarShift(sy,tf,iTime(sy,tf,1),false));
yGDN2=EquationDirect(LowestBarDN, LowestPeakDN,PivotBarDN, PivotPeakDN, iBarShift(sy,tf,iTime(sy,tf,2),false));

//---------------- Начертим уровни восходящего тренда (DN - экстремумы) ------------------            
         if (WinID>0)
            {
               if (ObjectFind("PointGDN"+iTime(sy,tf,1))<0)
               ObjectCreate("PointGDN"+iTime(sy,tf,1),OBJ_ARROW,WinID,iTime(sy,tf,1),yGDN);
               ObjectSet("PointGDN"+iTime(sy,tf,1),OBJPROP_ARROWCODE,4);
               ObjectSet("PointGDN"+iTime(sy,tf,1),OBJPROP_COLOR,Lime);
               ObjectSet("PointGDN"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
               ObjectSet("PointGDN"+iTime(sy,tf,1),OBJPROP_PRICE1,yGDN);
            }
// --------------- Проверим пересечение вниз восходящего тренда -----------------   
         if (NormalizeDouble(TempIND[1],8) < NormalizeDouble(yGDN,8) && 
             NormalizeDouble(TempIND[2],8) >= NormalizeDouble(yGDN2,8))
               {
                  CrossGDN = true;           // Установим флаг пересечения вниз
                  CrossGUP = false;          // Снимем флаг пересечения вверх
                  if (Arrow)                 // Если разрешены сигнальные указатели
                     {
                        if (ObjectFind("ArrowGDN"+iTime(sy,tf,1))<0)
                        ObjectCreate("ArrowGDN"+iTime(sy,tf,1),OBJ_ARROW, 0 ,iTime(sy,tf,1), iHigh(sy,tf,1)+5*pt);
                        ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_ARROWCODE,242);
                        ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_COLOR,OrangeRed);
                        ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_WIDTH,1);
                        ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
                        ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_PRICE1,iHigh(sy,tf,1)+5*pt);
                     }
               }
// --------------- Проверим пересечение вверх восходящего тренда -----------------   
         
         if (add)
            {
               if (NormalizeDouble(TempIND[1],8) > NormalizeDouble(yGDN,8) && 
                   NormalizeDouble(TempIND[2],8) <= NormalizeDouble(yGDN2,8))
                  {
                     CrossGUP = true;
                     CrossGDN = false;
                     if (Arrow)                 // Если разрешены сигнальные указатели
                        {
                           if (ObjectFind("ArrowGUP"+iTime(sy,tf,1))<0)
                           ObjectCreate("ArrowGUP"+iTime(sy,tf,1),OBJ_ARROW, 0 ,iTime(sy,tf,1), iLow(sy,tf,1));
                           ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_ARROWCODE,241);
                           ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_COLOR,Lime);
                           ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_WIDTH,0);
                           ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
                           ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_PRICE1,iLow(sy,tf,1));
                        }
                  }
             }
        }                  
//====================================================================================================
// -------------------- Поиск максимального и основного верхних UP экстремумов ----------------------+            
//====================================================================================================
         PivotTimeUP = TimeUP[pbar];                           // Время основного экстремума
         PivotBarUP  = iBarShift(sy,tf,TimeUP[pbar]);          // Бар основного экстремума
         PivotPeakUP = PeakUP[pbar];                           // Значение основного экстремума
         
         HighestPeakUP = ArrayMax(PeakUP);                     // Найдём значение максимального верхнего экстремума
         Index = ArraySearchDouble(PeakUP, HighestPeakUP);     // Найдём индекс максимального верхнего экстремума в массиве
         HighestBarUP =iBarShift(sy,tf,TimeUP[Index]);         // Найдём бар максимального верхнего экстремума
         HighestTimeUP = TimeUP[Index];                        // Найдём время максимального верхнего экстремума

         if (HighestBarUP>PivotBarUP)                          // Если наибольший экстремум слева от первого ... 
                                                               // ... (который в 0 ячейке массива времени пичков)
   for (m=Index-1; m>pbar; m--)                                // Цикл от следующего за наибольшим к первому
      {
// --------- Начертим виртуальную трендовую линию и проверим её пересечение с другими экстремумами ----------  
    CheckCross=EquationDirect(iBarShift(sy,tf,TimeUP[m+1]), PeakUP[m+1], // Первая координата линии
                              PivotBarUP, PivotPeakUP,                   // Вторая координата линии
                              iBarShift(sy,tf,TimeUP[m],false));         // Точка пересечения на следующем экстремуме
         if (TempIND[iBarShift(sy,tf,TimeUP[m])]>CheckCross)             // Если на ней экстремум выше линии
            {
               if (PeakUP[m]>PeakUP[pbar])                                // если этот экстремум выше основного
                  {
                     HighestBarUP =iBarShift(sy,tf,TimeUP[m]);            // Значит на него и нужно встать...
                     HighestPeakUP = PeakUP[m];                           // Новые координаты следующ. экстремума
                     HighestTimeUP = TimeUP[m];                             
                  }
            }                       
      }
      if (HighestBarUP>PivotBarUP && HighestPeakUP>PivotPeakUP)           // Если наибольший экстремум слева от основного
         
            {
// ---------------- Начертим нисходящую трендовую линию (UP - экстремумы) ---------------------            
               if (WinID>0)
                  {
                     if (ObjectFind("Trend_Line_GUP")<0)
                     ObjectCreate("Trend_Line_GUP",OBJ_TREND,WinID,HighestTimeUP,HighestPeakUP,PivotTimeUP,PivotPeakUP);
                     ObjectSet("Trend_Line_GUP",OBJPROP_COLOR,OrangeRed);
                     ObjectSet("Trend_Line_GUP",OBJPROP_TIME1,HighestTimeUP);
                     ObjectSet("Trend_Line_GUP",OBJPROP_PRICE1,HighestPeakUP);
                     ObjectSet("Trend_Line_GUP",OBJPROP_TIME2,PivotTimeUP);
                     ObjectSet("Trend_Line_GUP",OBJPROP_PRICE2,PivotPeakUP);
                  }
//---------------- Расчёт уровней нисходящего тренда (UP - экстремумы) ------------------            
         yGUP =EquationDirect(HighestBarUP, HighestPeakUP, PivotBarUP, PivotPeakUP, iBarShift(sy,tf,iTime(sy,tf,1),false));
         yGUP2=EquationDirect(HighestBarUP, HighestPeakUP, PivotBarUP, PivotPeakUP, iBarShift(sy,tf,iTime(sy,tf,2),false));

//---------------- Начертим уровни нисходящего тренда (UP - экстремумы) ------------------            
         if (WinID>0)
            {
               if (ObjectFind("PointGUP"+iTime(sy,tf,1))<0)
               ObjectCreate("PointGUP"+iTime(sy,tf,1),OBJ_ARROW,WinID,iTime(sy,tf,1),yGUP);
               ObjectSet("PointGUP"+iTime(sy,tf,1),OBJPROP_ARROWCODE,4);
               ObjectSet("PointGUP"+iTime(sy,tf,1),OBJPROP_COLOR,OrangeRed);
               ObjectSet("PointGUP"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
               ObjectSet("PointGUP"+iTime(sy,tf,1),OBJPROP_PRICE1,yGUP);
            }
// --------------- Проверим пересечение вверх нисходящего тренда -----------------   
         if (NormalizeDouble(TempIND[1],8) > NormalizeDouble(yGUP,8) && 
             NormalizeDouble(TempIND[2],8) <= NormalizeDouble(yGUP2,8))
               {
                  CrossGUP = true;                 // Установим флаг пересечения вверх
                  CrossGDN = false;                // Снимем флаг пересечения вниз
                  if (Arrow)                       // Если разрешены сигнальные указатели
                     {
                        if (ObjectFind("ArrowGUP"+iTime(sy,tf,1))<0)
                        ObjectCreate("ArrowGUP"+iTime(sy,tf,1),OBJ_ARROW, 0 ,iTime(sy,tf,1), iLow(sy,tf,1));
                        ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_ARROWCODE,241);
                        ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_COLOR,Lime);
                        ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_WIDTH,1);
                        ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
                        ObjectSet("ArrowGUP"+iTime(sy,tf,1),OBJPROP_PRICE1,iLow(sy,tf,1));
                     }
               }
            
// --------------- Проверим пересечение вниз нисходящего тренда -----------------   
         if (add)
            {
               if (NormalizeDouble(TempIND[1],8) < NormalizeDouble(yGUP,8) && 
                   NormalizeDouble(TempIND[2],8) >= NormalizeDouble(yGUP2,8))
                  {
                     CrossGDN = true;
                     CrossGUP = false;
                     if (Arrow)                    // Если разрешены сигнальные указатели
                        {
                           if (ObjectFind("ArrowGDN"+iTime(sy,tf,1))<0)
                           ObjectCreate("ArrowGDN"+iTime(sy,tf,1),OBJ_ARROW, 0 ,iTime(sy,tf,1), iHigh(sy,tf,1)+15*pt);
                           ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_ARROWCODE,242);
                           ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_COLOR,OrangeRed);
                           ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_WIDTH,0);
                           ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
                           ObjectSet("ArrowGDN"+iTime(sy,tf,1),OBJPROP_PRICE1,iHigh(sy,tf,1)+15*pt);
                        }
                  }
            }
      }  
                                   
//жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж
// ==========================================================================================               
// ------------------ Поиск двух крайних нижних (локальных) DN экстремумов -----------------+      
// ==========================================================================================               
   if (local)
      {     
         asize=ArraySize(PeakDN);
         for (l=asize-1; l>=1; l--)
            {
               if (PeakDN[l]<PeakDN[l-1])
                  {
                     LastTimeDN     =TimeDN[l-1];
                     LastVarDN      =PeakDN[l-1];
                     PreLastTimeDN  =TimeDN[l];
                     PreLastVarDN   =PeakDN[l];
                  }
            }
// --------------- Начертим восходящую локальную трендовую линию (DN - экстремумы) ---------------------            
         if (WinID>0)
            {
               if (ObjectFind("Trend_Line_DN")<0)
               ObjectCreate("Trend_Line_DN",OBJ_TREND,WinID,TimeDN[1],PeakDN[1],TimeDN[0],PeakDN[0]);
               ObjectSet("Trend_Line_DN",OBJPROP_COLOR,MediumSeaGreen);
               ObjectSet("Trend_Line_DN",OBJPROP_TIME1,PreLastTimeDN);
               ObjectSet("Trend_Line_DN",OBJPROP_PRICE1,PreLastVarDN);
               ObjectSet("Trend_Line_DN",OBJPROP_TIME2,LastTimeDN);
               ObjectSet("Trend_Line_DN",OBJPROP_PRICE2,LastVarDN);
            }
//---------------- Расчёт уровней восходящего локального тренда (DN - экстремумы) ----------------            
 yDN =EquationDirect(iBarShift(sy,tf,PreLastTimeDN,false), PreLastVarDN, 
                     iBarShift(sy,tf,LastTimeDN,false), LastVarDN, iBarShift(sy,tf,iTime(sy,tf,1),false));
 yDN2=EquationDirect(iBarShift(sy,tf,PreLastTimeDN,false), PreLastVarDN, 
                     iBarShift(sy,tf,LastTimeDN,false), LastVarDN, iBarShift(sy,tf,iTime(sy,tf,2),false));
               
//---------------- Начертим уровни восходящего локального тренда (DN - экстремумы) ------------------            
         if (WinID>0)
            {
               if (ObjectFind("PointDN"+iTime(sy,tf,1))<0)
               ObjectCreate("PointDN"+iTime(sy,tf,1),OBJ_ARROW,WinID,iTime(sy,tf,1),yDN);
               ObjectSet("PointDN"+iTime(sy,tf,1),OBJPROP_ARROWCODE,4);
               ObjectSet("PointDN"+iTime(sy,tf,1),OBJPROP_COLOR,MediumSeaGreen);
               ObjectSet("PointDN"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
               ObjectSet("PointDN"+iTime(sy,tf,1),OBJPROP_PRICE1,yDN);
            }
// --------------- Проверим пересечение вниз локального восходящего тренда -----------------   
         if (NormalizeDouble(TempIND[1],8) < NormalizeDouble(yDN,8) && 
             NormalizeDouble(TempIND[2],8) >= NormalizeDouble(yDN2,8))
               {
                  CrossDN = true;
                  CrossUP = false;
                  if (Arrow)                 // Если разрешены сигнальные указатели
                     {
                        if (ObjectFind("ArrowDN"+iTime(sy,tf,1))<0)
                        ObjectCreate("ArrowDN"+iTime(sy,tf,1),OBJ_ARROW, 0 ,iTime(sy,tf,1), iHigh(sy,tf,1)+15*pt);
                        ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_ARROWCODE,242);
                        ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_COLOR,Chocolate);
                        ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_WIDTH,1);
                        ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
                        ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_PRICE1,iHigh(sy,tf,1)+15*pt);
                     }
               }
// --------------- Проверим пересечение вверх локального восходящего тренда -----------------   
         if (add)
            {
               if (NormalizeDouble(TempIND[1],8) > NormalizeDouble(yDN,8) && 
                   NormalizeDouble(TempIND[2],8) <= NormalizeDouble(yDN2,8))
                  {
                     CrossUP = true;
                     CrossDN = false;
                     if (Arrow)                 // Если разрешены сигнальные указатели
                        {
                           if (ObjectFind("ArrowUP"+iTime(sy,tf,1))<0)
                           ObjectCreate("ArrowUP"+iTime(sy,tf,1),OBJ_ARROW, 0 ,iTime(sy,tf,1), iLow(sy,tf,1));
                           ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_ARROWCODE,241);
                           ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_COLOR,MediumSeaGreen);
                           ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_WIDTH,0);
                           ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
                           ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_PRICE1,iLow(sy,tf,1));
                        }
                  }
            }
      }
//====================================================================================================
// ------------------------- Поиск двух крайних (локальных) верхних UP экстремумов ------------------+       
//====================================================================================================
   if (local)
      {
         asize=ArraySize(PeakUP);
         for (l=asize-1; l>=1; l--)
            {
               if (PeakUP[l]>PeakUP[l-1])
                  {
                     LastTimeUP     =TimeUP[l-1];
                     LastVarUP      =PeakUP[l-1];
                     PreLastTimeUP  =TimeUP[l];
                     PreLastVarUP   =PeakUP[l];
                  }
            }
// ---------------- Начертим нисходящую локальную трендовую линию (UP - экстремумы) ---------------------            
         if (WinID>0)
            {
               if (ObjectFind("Trend_Line_UP")<0)
               ObjectCreate("Trend_Line_UP",OBJ_TREND,WinID,TimeUP[1],PeakUP[1],TimeUP[0],PeakUP[0]);
               ObjectSet("Trend_Line_UP",OBJPROP_COLOR,Chocolate);
               ObjectSet("Trend_Line_UP",OBJPROP_TIME1,PreLastTimeUP);
               ObjectSet("Trend_Line_UP",OBJPROP_PRICE1,PreLastVarUP);
               ObjectSet("Trend_Line_UP",OBJPROP_TIME2,LastTimeUP);
               ObjectSet("Trend_Line_UP",OBJPROP_PRICE2,LastVarUP);
            }
//---------------- Расчёт уровней нисходящего локального тренда (UP - экстремумы) ------------------            
 yUP =EquationDirect(iBarShift(sy,tf,PreLastTimeUP,false), PreLastVarUP, 
                     iBarShift(sy,tf,LastTimeUP,false), LastVarUP, iBarShift(sy,tf,iTime(sy,tf,1),false));
 yUP2=EquationDirect(iBarShift(sy,tf,PreLastTimeUP,false), PreLastVarUP, 
                     iBarShift(sy,tf,LastTimeUP,false), LastVarUP, iBarShift(sy,tf,iTime(sy,tf,2),false));

//---------------- Начертим уровни нисходящего локального тренда (UP - экстремумы) ------------------            
         if (WinID>0)
            {
               if (ObjectFind("PointUP"+iTimeiTimesy,tf,1))<0)
               ObjectCreate("PointUP"+iTime(sy,tf,1),OBJ_ARROW,WinID,iTime(sy,tf,1),yUP);
               ObjectSet("PointUP"+iTime(sy,tf,1),OBJPROP_ARROWCODE,4);
               ObjectSet("PointUP"+iTime(sy,tf,1),OBJPROP_COLOR,Chocolate);
               ObjectSet("PointUP"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
               ObjectSet("PointUP"+iTime(sy,tf,1),OBJPROP_PRICE1,yUP);
            }
// --------------- Проверим пересечение вверх нисходящего локального тренда -----------------   
         if (NormalizeDouble(TempIND[1],8) > NormalizeDouble(yUP,8) && 
             NormalizeDouble(TempIND[2],8) <= NormalizeDouble(yUP2,8))
               {
                  CrossUP = true;
                  CrossDN = false;
                  if (Arrow)                 // Если разрешены сигнальные указатели
                     {
                        if (ObjectFind("ArrowUP"+iTime(sy,tf,1))<0)
                        ObjectCreate("ArrowUP"+iTime(sy,tf,1),OBJ_ARROW, 0 ,iTime(sy,tf,1), iLow(sy,tf,1));
                        ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_ARROWCODE,241);
                        ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_COLOR,MediumSeaGreen);
                        ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_WIDTH,1);
                        ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
                        ObjectSet("ArrowUP"+iTime(sy,tf,1),OBJPROP_PRICE1,iLow(sy,tf,1));
                     }
               }           
// --------------- Проверим пересечение вниз нисходящего локального тренда -----------------   
         if (add)
            {
               if (NormalizeDouble(TempIND[1],8) < NormalizeDouble(yUP,8) && 
                   NormalizeDouble(TempIND[2],8) >= NormalizeDouble(yUP2,8))
                  {
                     CrossDN = true;
                     CrossUP = false;
                     if (Arrow)               // Если разрешены сигнальные указатели
                        {
                           if (ObjectFind("ArrowDN"+iTime(sy,tf,1))<0)
                           ObjectCreate("ArrowDN"+iTime(sy,tf,1),OBJ_ARROW, 0 ,iTime(sy,tf,1), iHigh(sy,tf,1)+15*pt);
                           ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_ARROWCODE,242);
                           ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_COLOR,Chocolate);
                           ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_WIDTH,0);
                           ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_TIME1,iTime(sy,tf,1));
                           ObjectSet("ArrowDN"+iTime(sy,tf,1),OBJPROP_PRICE1,iHigh(sy,tf,1)+15*pt);
                        }
                  }                         
            }
      }            
// -----------------------------------------------------------------------------------------------------------            
// Здесь условиями могут быть как CrossGUP и CrossGDN, так и CrossUP и CrossDN.
// В первом случае - "глобальный тренд" по всем заданным барам,
// во втором - локальные тренды по двум последним экстремумам.
// Также можно комбинировать сигналы пересечения "глобального" и "локального" трендов графика A/D
/*   
   if (CrossGUP && CrossUP)   return(11);    // Пересечение нисходящих обоих трендов вверх
   else
   if (CrossGDN && CrossDN)   return(-11);   // Пересечение восходящих обоих трендов вниз
   else
   if (CrossGUP)              return(10);    // Пересечение нисходящего "глобального" тренда вверх
   else
   if (CrossGDN)              return(-10);   // Пересечение восходящего "глобального" тренда вниз
   else
   if (CrossUP)               return(1);     // Пересечение нисходящего "локального" тренда вверх
   else
   if (CrossDN)               return(-1);    // Пересечение восходящего "локального" тренда вниз
*/   
   if (CrossGUP || CrossUP) return(1);
   else
   if (CrossGDN || CrossDN) return(-1); 
   else return(0);
                      
}   

Код практически такой же, как и уже рассмотренный нами, поэтому остановлюсь только на входных параметрах функции и способе её вызова.

int   SignalCrossIND(double &TempIND[], int nBars, string sy, int tf, bool local=false, bool add=false, bool Arrow=true)

Здесь, в отличие от уже рассмотренного кода, добавлена ещё одна переменная типа bool Arrow и отвечает она за вывод сигнальных указателей в главное окно терминала. Значение по умолчанию true, т.е., если необходимо выводить стрелки на график, то при вызове функции указание этого параметра можно опустить.

В общем случае вызов функции может выглядеть следующим образом. В глобальные переменные советника вписываем строчку:

double      TempIND[];           // Массив для хранения данных индикатора

Далее, в функции получения торговых сигналов прописываем необходимое количество баров истории для поиска экстремумов графика индикатора A/D

   int nBrs=30;

или, например

   int nBrs=200;

Здесь всё зависит от того, на каком ТФ собираемся строить трендовые линии и какую глубину истории будем обрабатывать. Чем большее значение, тем более "стабильно" будут удерживаться трендовые линии на графике A/D, но и тем более запаздывающим будет сигнал о пересечении.

Ну и сам вызов функции:

   int sig=0;
   sig=SignalCrossIND(TempIND, nBrs, NULL, 5, false, false, true);
   
   if (sig==1)
      {Код открытия позиции Buy}
   if (sig==-1)
      {Код открытия позиции Sell}

Здесь в передаваемых параметрах указано, что данные брать с текущего графика валютного инструмента (NULL), таймфрейм использовать М5 (5), локальные трендовые линии не строить и пересечения по ним не искать (false), также не искать пересечения в направлении тренда (false) и сигнальные указатели (стрелки вверх/вниз) в окно основного графика устанавливать (true).

Так как это есть значения по умолчанию, то совершенно идентичным будет такой вызов функции:

sig=SignalCrossIND(TempIND, nBrs, NULL, 5);


Заключение

В заключение хочется сказать, что данная функция никак не является самостоятельной торговой стратегией. Это - всего лишь функция определения пересечений трендовых линий, построенных на графике индикатора A/D с линией данного индикатора, она вполне может использоваться в составе советника наряду с другими функциями.

Для выявления её возможностей я провёл тест. Тестирование проводил на промежутке от 01.01.2009 по 01.01.2010 гг. Тест я выполнил, используя только показания, возвращаемые данной функцией. Я отключил в советнике сигнальный модуль и вместо него поставил только эту функцию. Количество баров для поиска экстремумов = 250. ТФ = 5, символ EURUSD, стартовый депозит 10000, постоянный лот 0.1.

Отключил использование SL и TP. Оставил лишь трал, который закрывает позы частями за три раза при достижении заданного уровня профита (для каждой из трёх ступеней, соответственно, свой уровень прибыли). Позиции открывались при поступлении сигнала от функции.

Противоположная поза при этом не закрывалась. При следующем сигнале в сторону уже имеющейся позиции я определял время после открытия предыдущей позы и, если прошло больше 7-ми минут, открывалась ещё одна позиция. Короче, грузил депозит по полной... :) Чуть не забыл - для закрытия всех позиций разом я использовал уровень увеличения эквити на заданное количество процентов. В данном тесте я поставил 5 %.

В результате я получил вот такой график работы за год:


На реал конечно же ни в коем случае нельзя, но, думаю, есть в этом рациональное зерно (тем более, это всего лишь просто сигнальная функция).

И наконец, хочу выразить истинную глубокую благодарность Виктору (Vinin) и Алексею (Mathemat) за их бескорыстную помощь и настоящую дружескую поддержку, а также всем, кто так или иначе помогает при решении различных вопросов программирования.