English 中文 Español Deutsch 日本語 Português
Как писать быстрые неперерисовывающиеся зигзаги

Как писать быстрые неперерисовывающиеся зигзаги

MetaTrader 4Примеры | 29 июля 2008, 08:28
4 006 9
Candid
Candid

Введение

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

Общий подход

Для начала попробуем сформулировать общий подход к написанию произвольного индикатора. Итак:

- Функция start() любого индикатора (как впрочем, и советника) представляет из себя callback-функцию, то есть функцию, вызываемую для обработки конкретного события. А именно - для обработки тика.

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

  • Рассчитывая на новом тике новые значения переменных, функция start() рассчитывает новое состояние индикатора.
  • Таким образом, фактически функция start() является оператором, переводящим индикатор из одного состояния в другое.

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

О каких зигзагах идёт речь

Как было сказано выше, в этой статье нас интересуют зигзаги с переключением по пробою отстающего уровня. Что понимается под отстающим уровнем? Допустим мы хотим написать зигзаг, для которого фиксация вершины происходит при отходе цены от неё на H пунктов. Фиксация вершины означает переключение направления отрезка (сегмента) зигзага на противоположное. Пусть мы только что зафиксировали минимум и теперь находимся в восходящем сегменте. Введём переменную для временного максимума цены незаконченного восходящего сегмента, TempMax. Мы зафиксируем этот максимум(и переключим направление), если цена пробьёт уровень:

SwitchLevel = TempMax - Н *Point .

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

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

SwitchLevel = TempMin + Н *Point .

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

Модель зигзага

Определим переменные состояния зигзага.

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

Очевидно, что мы должны включить в список введенные выше TempMax и TempMin . Добавим также их временнЫе координаты. Здесь, однако, у нас появляется некоторая степень свободы в определении единиц измерения. Мы будем использовать в качестве временнОй координаты номер бара от начала графика, то есть используем обратную принятой в МТ4 систему нумерации. Это не только упростит код, но и увеличит скорость его выполнения. Таким образом, список пополняется переменными TempMaxBar и TempMinBar.

Мы планируем не только нарисовать зигзаг на графике, но и как-то им пользоваться. Поэтому добавим в список координаты последних зафиксированных вершин зигзага: CurMax, CurMaxBar, CurMin, CurMinBar.

На этом список закроем. Конкретный автор конкретного зигзага волен пополнять его в соответствии с тем, что он с этим зигзагом планирует делать. Например, может оказаться целесообразным добавление координат предыдущих вершин: PreMax, PreMaxBar, PreMin, PreMinBar. Или потребуется добавлять координаты заданного числа предыдущих вершин, в таком случае ему понадобятся массивы.

Оператор перехода

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

// Сначала обработаем случай восходящего сегмента
    if (UpZ) {
// Проверим, не изменился ли текущий максимум
      if (High[pos]>TempMax) {
// Если да, скорректируем соответствующие переменные
        TempMax = High[pos];
        TempMaxBar = Bars-pos;  // Здесь переход к прямой нумерации
      } else {
// Если нет, проверим был ли пробит отстающий уровень
        if (Low[pos]<SwitchLevel()) {
// Если да, зафиксируем максимум
          CurMax = TempMax;
          CurMaxBar = TempMaxBar;
// И нарисуем вершину
          ZZ[Bars-CurMaxBar]=CurMax;  // Здесь возврат к обратной нумерации
// Скорректируем соответствующие переменные
          UpZ = false;
          TempMin = Low[pos];
          TempMinBar = Bars-pos;  // Здесь переход к прямой нумерации
        }
      }
    }  else {
// Теперь обработаем случай нисходящего сегмента
// Проверим, не изменился ли текущий минимум
      if (Low[pos]<TempMin) {
// Если да, скорректируем соответствующие переменные
        TempMin = Low[pos];
        TempMinBar = Bars-pos;  // Здесь переход к прямой нумерации
      } else {
// Если нет, проверим был ли пробит отстающий уровень
        if (High[pos]>SwitchLevel()) {
// Если да, зафиксируем минимум
          CurMin = TempMin;
          CurMinBar = TempMinBar;
// И нарисуем вершину
          ZZ[Bars-CurMinBar]=CurMin;  // Здесь возврат к обратной нумерации
// Скорректируем соответствующие переменные
          UpZ = true;
          TempMax = High[pos];
          TempMaxBar = Bars-pos;  // Здесь переход к прямой нумерации
       }
      }
    }

Оператор перехода готов. Теперь мы можем обращаться к переменным состояния индикатора в любой момент.

Однако такой оператор имеет особенность, которая может восприниматься как ошибочная отрисовка зигзага. Рассмотрим подробнее фрагмент

      if (High[pos]>TempMax) {
// Если да, скорректируем соответствующие переменные
        TempMax = High[pos];
        TempMaxBar = Bars-pos;  // Здесь переход к прямой нумерации
      } else {
// Если нет, проверим был ли пробит отстающий уровень
        if (Low[pos]<SwitchLevel()) {
// Если да, зафиксируем максимум
          CurMax = TempMax;
          CurMaxBar = TempMaxBar;

Использование пары if - else означает, что из рассмотрения выпадет Low бара, содержащего TempMax. Ситуации, когда эта цена окажется ниже следующего зафиксированного минимума, могут восприниматься как ошибка отрисовки зигзага. Является ли это на самом деле ошибкой?

Исходя из соображений идентичности работы на истории и в реальном времени автор считает, что это ошибкой не является. В самом деле, не выходя за рамки таймфрейма мы никогда не будем знать, что было на истории раньше - максимум или минимум бара. Применение здесь конструкции if - else означает принятие сознательного решения: мы отдаём предпочтение инерции. То есть для сегмента "вверх" жертвуем минимумом, для сегмента "вниз" - максимумом. Понятно, что чем меньше таймфрейм, тем реже будет возникать такая дилемма.

И ещё один требующий комментария фрагмент:

// Скорректируем соответствующие переменные
          UpZ = false;
          TempMin = Low[pos];
          TempMinBar = Bars-pos;  // Здесь переход к прямой нумерации
        }
      }

Здесь фактически точка начала контроля временного минимума устанавливается на момент переключения сегмента. Это оправдано для данного примера, но в общем случае так делать нельзя. Правильнее установить временный минимум на минимум интервала от зафиксированного максимума до текущей позиции (то есть до момента переключения). Код может быть таким:

   // Скорректируем соответствующие переменные
          UpZ = false;
          TempMinBar = CurMaxBar+1;
          TempExtPos = Bars - TempMinBar;  // Здесь переход к обратной нумерации
          TempMin = Low[TempExtPos];
          for (i=TempExtPos-1;i>=pos;i--) {
            if (Low[i]<TempMin) {
              TempMin = Low[i];
              TempMinBar = Bars-i;  // Здесь переход к прямой нумерации
            }
          }

Здесь опять исключается из рассмотрения Low бара, на котором был зафиксирован максимум.

Эти два замечания симметричным образом относятся и к обработке нисходящих сегментов.

Индикатор

Осталось доукомплектовать индикатор до рабочего состояния. Комментировать шапку, init() и deinit() необходимости нет, там всё достаточно стандартно. А вот относительно функции start() будет принято важное решение. Мы будем работать только по закончившимся барам. Главная причина - это позволяет добиться простой и компактной структуры кода.

Но есть и другое важное соображение. Серьёзная работа над торговой системой предполагает обязательный сбор статистики на истории. Эта статистика будет валидна (корректна) только при точном соответствии получаемых в реальном времени характеристик характеристикам, получаемым на истории. Истории реальных тиков у нас нет, следовательно, точного соответствия мы можем добиться лишь работая и в реальном времени только по закончившимся барам. Максимум что можно сделать для уменьшения задержки - перейти на более мелкий таймфрейм, вплоть до минутного.

Другая существенная особенность - отказ от функции IndicatorCounted(). Основная причина состоит в том, что использованный подход требует ещё одного важного действия - инициализации переменных состояния индикатора. Это нельзя делать в функции init(), потому что использование прямой нумерации требует пересчёта индикатора при докачке истории и, следовательно, переинициализации переменных состояния. А init() при докачке истории не запускается.

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

int start() {
//  Работаем только по закончившимся барам
  if (Bars == PreBars) return(0);  
//  Проверим, достаточно ли баров на графике
  if (Bars < MinBars) {
    Alert(": Недостаточно баров на графике");
    return(0);
  }  
//  Если не было докачки истории, обсчитываем только закончившийся бар
  if (Bars-PreBars == 1 && BarTime==Time[1]) StartPos = 1;
//  Иначе пересчитываем заданное в функции Reset() количество баров 
  else StartPos = Reset();
// Модифицируем контрольные переменные
  PreBars = Bars;  
  BarTime=Time[0];
// Цикл по истории
  for (pos=StartPos;pos>0;pos--) {

Функция Reset() выглядит так

int Reset() {
  if (MinBars == 0) MinBars = Bars-1;
  StartPos = MinBars;
  PreBars = 0;
  BarTime = 0;
  dH = H*Point;
  UpZ = true;
  TempMaxBar = Bars-StartPos;
  TempMinBar = Bars-StartPos;
  TempMax = High[StartPos];
  TempMin = Low[StartPos];
  StartPos++;
  return(StartPos);
}

Здесь можно обратить внимание на дополнительную переменную dH, которой раз и навсегда присваивается значение порога переключения зигзага (H ) переведенное в ценовый масштаб. Может возникнуть вопрос, почему UpZ = true а не false ? Ответ прост - через небольшое число сегментов индикатор выйдет на одну и ту же разметку, независимо от начального значения UpZ.

Ну и наконец расчёт отстающего уровня

double SwitchLevel() {
  double SwLvl;
  if (UpZ) SwLvl = TempMax - dH;
  else SwLvl = TempMin + dH;
  return(SwLvl);
}

Здесь всё должно быть понятно.

Заключение

К статье прилагается шаблон для написания зигзагов ZZTemplate. Всё что нужно сделать - дописать нужный код в функцию SwitchLevel(). Чтобы превратить шаблон в послуживший примером зигзаг достаточно разыскать и раскомментировать следующие строчки

//extern int H = 33;
//double dH;
//  dH = H*Point;
//  if (UpZ) SwLvl = TempMax - dH;
//  else SwLvl = TempMin + dH;

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

Общая рекомендация такова: всюду, где можно, переносить операции внутрь операторов if. Как пример (но не эталон) оптимизации к статье прилагается индикатор HZZ - альтернативная реализация служившего примером в статье зигзага. Простота задачи позволила отказаться не только от функции SwitchLevel(), но и от части переменных состояния. В качестве мелкого бонуса в HZZ добавлена запись вершин зигзага в файл и контроль "на лету" некоторых статистических характеристик зигзага.


Прикрепленные файлы |
HZZ.mq4 (4.22 KB)
ZZTemplate.mq4 (5.67 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (9)
Candid
Candid | 1 авг. 2008 в 08:20
BigeR:

А как реализовать фиксацию последней точки при коррекции от нее на заданное число процентов предыдущего импульса? Например Есть нижняя точка + восходящий отрезок. И для того чтобы зафиксировать верхнюю точку мы ловим от нее коррекцию, скажем на 50% от восходящего отрезка.

В любой момент времени, в зависимости от UpZ, текущий импульс или CurMax-TempMin или TempMax-CurMin. Делите пополам и прибавляете к TempMin или отнимаете от TempMax. Если я правильно понял вопрос, это и будет требуемый отстающий уровень.


P.S. Это вторая редакция ответа, в первой я недостаточно въехал в вопрос.

[Удален] | 6 авг. 2008 в 09:06
Картинка бы не помешала! :)
Candid
Candid | 6 авг. 2008 в 10:14
Картинку легко посмотреть в своём терминале :) . Любую из переменных состояния можно визуализировать добавив для неё в код отдельный индикаторный буфер.
[Удален] | 19 мар. 2010 в 12:59
Введем переменную bs. Как получить на ней значение -1, если сегмент вверх, соответственно получить значение 1, если сегмент вниз.
Candid
Candid | 20 мар. 2010 в 22:29
Luchiy:
Введем переменную bs. Как получить на ней значение -1, если сегмент вверх, соответственно получить значение 1, если сегмент вниз.

Не совсем понятен смысл вопроса, ведь информация о направлении уже содержится в переменной UpZ.
Материалы Automated Trading Championship: Регистрация Материалы Automated Trading Championship: Регистрация
В данной статье собраны полезные материалы, которые помогут вам узнать больше о процедуре регистрации на Automated Trading Championship.
Построение горизонтальных уровней пробития при помощи фракталов Построение горизонтальных уровней пробития при помощи фракталов
В статье описывается создание индикатора, который отображает уровни поддержки/сопротивления на основе фракталов вверх и вниз.
Материалы Automated Trading Championship: Статистические отчеты Материалы Automated Trading Championship: Статистические отчеты
Создание прибыльной и устойчивой торговой системы всегда связано с обработкой статистических данных. Мы подобрали в данной статье статистические отчеты с чемпионатов по автотрейдингу 2006 - 2007 годов. Возможно, что информация, предоставленная в них, поможет вам найти новые торговые идеи или скорректировать уже существующие. Анализируйте и экономьте свое время с их помощью.
Групповые файловые операции Групповые файловые операции
Иногда требуется проделать одинаковые операции для некоторой группы файлов. Если у вас есть список файлов, входящих в эту группу, то это не проблема. Но если этот список нужно получить самостоятельно, то возникает вопрос: "Каким образом?" В статье предлагается сделать это с помощью функций FindFirstFile() и FindNextFile(), входящих в библиотеку kernel32.dll.