Волновая теория
Эллиотта волнует умы многих трейдеров. Виной тому отсутствие научного
подтверждения или опровержения этой теории, ждать которого в ближайшее время
не следует, т.к. волновая теория не является формализованной методикой. То
есть, как и семьдесят восемь лет назад, когда Р.Н. Эллиотт впервые выставил
волновую концепцию рынка на суд общественности, так и по сей день, теория
осталась теорией, которую каждый трейдер трактует так, как ему хочется. Именно
по этой причине среди "волновиков" (трейдеров и аналитиков, использующих
волновую теорию в практической торговле) не бывает единого мнения относительно
одной и той же рыночной ситуации.
Волновая теория
так и не превратилась в методику потому, что в ней нет явного определения начала
и окончания волны. Существуют лишь рекомендуемые соотношения высот волн,
указано, что такие то вершины таких то волн должны быть выше или ниже других
вершин других волн. Выделение границ каждой волновой формации возложено на
почитателя волновой теории.
Перед тем, как
предложить очередной вариант правил построения волн, вкратце рассмотрим основные
постулаты волновой теории. Они гласят, что поведение общества и финансовых
рынков развивается и изменяется в виде распознаваемых моделей. Эллиотт выделил
восемь волн, которые постоянно повторяются. Причем первые пять волн движутся в
направлении тренда, а три другие - против него. К примеру, для восходящего
движения общая волновая картина должна выглядеть так, как показано на рис. 1.
Рис. 1. Волновая формация при росте рынка.
Вторая и четвертая
волны движения называют коррекционными. Обязательным условием является
расположение минимума второй волны выше минимума первой волны и, соответственно,
минимума четвертой волны выше минимума третьей волны. В оригинале теории
используются соотношения высот волн 2 и 1, 3 и 1, 4 и 3, 5 и 3, которые должны
стремиться к соотношению "золотого сечения", но мы в данном материале не будем
рассматривать эти соотношения. Также следует обратить внимание на тройку волн -
A-B-C. Волна А может иметь минимум как выше, так и
ниже минимума волны 5, в идеале же ее минимум должен соответствовать минимуму
волны 5. Волна В, несмотря на то, что имеет направление "по тренду", в данном
случае является коррекционной. В коррекционных волнах торговать не
рекомендуется, т.к. они самые коварные и непредсказуемые. Торговлю в волнах А и
С можно вести, хотя и с оглядкой на то, что они развиваются против основного
движения рынка.
Антиподом
восходящей волновой формации является набор волн, соответствующий падению рынка
(см. рис. 2).
Рис. 2. Волновая формация при падении рынка.
Волны 1, 3 и 5
вновь расположены "по тренду". В этих волнах как раз и предлагается осуществлять
торговлю. Волны 2, 4 и В - коррекционные.
Формализация алгоритма определения начала и окончания волн
В отличие от
большинства существующих трактовок волновой теории, предлагается развить свою
методику, которая явно и однозначно определяет начало и окончание каждой из
волн. Эта методика, конечно же, не претендует на роль истины в последней
инстанции и так же, как и вся волновая теория в целом, имеет собственные
недостатки. Тем не менее, использование методики, по крайней мере, на
исторических данных не приводит к двузначным выводам.
Сразу отметим, что
идентификация окончания одной волны и начала другой волны возможна только с
некоторым опозданием, т.к. безоговорочно утверждать, что "формирующаяся свеча
будет минимумом или максимумом", нельзя. Это можно лишь угадать, но определить
со стопроцентной точностью - невозможно. Чтобы лучше понять, почему дело обстоит
именно так, рассмотрим рис. 3, на котором представлена предлагаемая методика.
Рис. 3. Волна определяется во время смены направления PSAR.
Идентификация волн
производится при помощи стандартного индикатора Meta Trader 4
- Parabolic Support And Reverse
(PSAR). Момент идентификации окончания одной волны и начала другой волны
наступает при смене направления PSAR, т.е. когда точки
индикатора перемещаются из положения "под ценой" в положение "над ценой" и
наоборот. Положение точек "над ценой" будем называть нисходящей волной, а
положение "под ценой" - восходящей волной. Фактические границы
волн, как отмечалось выше, всегда будут отличаться от моментов идентификации
волн, т.к. идентификация производится с опозданием. Фактическое начало
восходящей волны совпадает с фактическим окончанием предыдущей нисходящей волны
и устанавливается на минимальной цене, которая действовала с момента
идентификации предыдущей волны до момента идентификации текущей волны.
Соответственно, фактическое окончание восходящей волны - это максимум цены за
период от начала до конца идентификации волны. Аналогичным образом определяются
фактические начало и окончание нисходящей волны.
Направление тренда
для некоторого набора волн определяется по направлению прибыльных волн.
Прибыльной нисходящей волной считается волна, у которой последняя (справа по
графику) точка PSAR находится ниже последней точки
PSAR предыдущей волны. Соответственно, прибыльной
восходящей волной считается волна, у которой последняя точка
PSAR находится выше последней точки PSAR
предыдущей волны. Все остальные волны считаются убыточными. Так, на рис. 3 волны
1, 3 и 5 - восходящие прибыльные, 2 и 4 - нисходящие убыточные, а волна А -
нисходящая прибыльная. Все это согласуется с волновой теорией и означает, что мы
имеем дело с восходящим трендом.
В процессе
идентификации волн мы никогда не можем заранее определить первую волну. Она
всегда определяется, когда произошло начало формирования волны 2. Таким образом,
первая волна - единственная из всех волн, которую мы будем видеть только
сформированной. О начале формирования остальных волн мы можем хотя бы
догадываться, т.к. перед ними уже существует несколько окончательно
сформированных волн. К примеру, начало формирования волны 2 подтверждается
сформированной волной 1, начало волны 3 - сформированными волнами 1 и 2, и т.д.
Но, к сожалению, количество сформированных волн вовсе не означает, что
повышается вероятность правильного определения типа следующей волны. То есть,
если у нас уже имеется шесть сформированных волн (1, 2, 3, 4, 5 и А), то это
вовсе не означает, что следующая волна В обязательно будет коррекционной
(убыточной). Она вполне может оказаться первой волной нового волнового рисунка.
Поэтому следует понимать, что номер формирующейся волны - это лишь предположение
о ее характере. Точный тип волны можно определить только после ее завершения.
Разработка кода индикатора
Код индикатора
начнем создавать с определения необходимых индикаторных буферов. Для получения
внешнего вида индикатора, близкого к тому, который приведен на рис. 3, мы не
сможем использовать ни один из типов индикаторных буферов, т.к. типы
DRAW_SECTION и DRAW_ZIGZAG
не позволяют оставлять пустыми те пространства, где не нужно ничего отображать.
Они обязательно отобразят линию, соединяющую два непустых значения. Поэтому вся
графика индикатора будет основана на графических объектах: линии - трендовые
линии без продолжения в бесконечность, подписи волн - текстовые объекты.
Индикаторные
буфера нам понадобятся для сохранения множества необходимых данных, что позволит
избежать их повторного вычисления. Они не будут видимы пользователю. Два первых
буфера отведены для сохранения времени идентификации волн и фактического времени
начала (окончания) волн. Время идентификации сохраняется в буфере
changeRegData. Новая информация в элементы буфера
записывается в момент смены тренда (перемещение точек PSAR относительно цены). При
продолжении тренда информация из последнего элемента буфера переписывается в
новый элемент. Фактическое время начала волны сохраняется в буфере
changeFactData. Информация в его элементах также
передается последовательно от одного к другому, если не образуются новые
экстремумы цены. Если же во время развития нисходящей волны образуется новый
минимум или во время развития восходящей волны образуется новый максимум, то в
текущий элемент буфера записывается время открытия бара нового экстремума. Этот
процесс напоминает попытку буфера все время предсказывать время начала следующей
волны.
В момент
формирования нового экстремума цены значение экстремума сохраняется в третий
буфер индикатора - minMaxBuf. Он работает по тому же
принципу, что и первые два. Если нет новых данных, то в текущий элемент буфера
записывается значение из предыдущего элемента буфера, а если новые данные есть,
то они попадают прямиком в новый элемент.
Номера
сформированных и предполагаемых волн записываются в четвертом буфере индикатора
- waveNumber. Последовательность записи в этот буфер
полностью совпадает с последовательностью записи в буфер
changeRegData, т.е. номер новой волны формируется и записывается только в
момент ее идентификации. Элемент буфера содержит 0 в начальном состоянии, когда
до него еще не дошла очередь записи, или тогда, когда волна не может быть
отнесена к какому-либо типу. В любой другой момент времени элемент
содержит значение от 1 до 8. Значениям 6, 7 и 8 соответствуют волны А, В и С. В
этот буфер значения могут быть записаны "задним числом", если изначально тип
волны был определен неправильно. Например, волна в момент идентификации была
определена как вторая, но после окончания ее формирования оказалось, что это
первая волна нового движения.
Волнам и
направлениям тренда соответствуют именованные константы индикатора:
Для определения
рыночной ситуации, существующей на одном конкретном баре, нам потребуется целая
функция, которая будет определять одно из четырех состояний тренда, приведенных
выше. Эта функция называется GetBarSituation:
//+-------------------------------------------------------------------------------------+
//| Определение ситуации на заданной свече: смена тренда или продолжение |
//+-------------------------------------------------------------------------------------+
int GetBarSituation(int index)
{
// - 1 - == Получение информации по параболику ==========================================
double psar1 = iSAR(NULL, 0, PSARStep, PSARMax, index);
double psar2 = iSAR(NULL, 0, PSARStep, PSARMax, index+1);
// - 1 - == Окончание блока =============================================================
// - 2 - == Закрытие текущей свечи выше точки параболика ================================
if (Close[index] > psar1)
{ // Возможно три случая:
if (Close[index+1] < psar2) // 1 - предыдущая свеча закрылась
return(TREND_CHANGE_FROM_DOWN_TO_UP); // ..ниже параболика - смена тренда
if (Close[index+1] > psar2) // 2 - предыдущая свеча закрылась
return(TREND_FOLLOW_UP); // ..выше параболика - продолжение
for (int i = index+2; i < Bars; i++) // 3 - закрытие свечи равно точке..
{ // ..параболика - ищем свечу,..
double psarN = iSAR(NULL, 0, PSARStep, PSARMax, i);// ..закрытие которой не..
if (Close[i] != psarN) // ..равно точке параболика.
break;
}
if (i == Bars || Close[i] > psarN) // Найдя такую свечу, определяем,..
return(TREND_FOLLOW_UP); // ..продолжение или..
return(TREND_CHANGE_FROM_DOWN_TO_UP); // ..смену тренда
}
// - 2 - == Окончание блока =============================================================
// - 3 - == Закрытие текущей свечи ниже точки параболика ================================
if (Close[index] < psar1)
{ // Вновь возможно три случая:
if (Close[index+1] > psar2) // 1 - предыдущая свеча закрылась
return(TREND_CHANGE_FROM_UP_TO_DOWN); // ..выше параболика - смена тренда
if (Close[index+1] < psar2) // 2 - предыдущая свеча закрылась
return(TREND_FOLLOW_DOWN); // ..ниже параболика - продолжение
for (i = index+2; i < Bars; i++) // 3 - закрытие свечи равно точке..
{ // ..параболика - ищем свечу,..
psarN = iSAR(NULL, 0, PSARStep, PSARMax, i);// ..закрытие которой не..
if (Close[i] != psarN) // ..равно точке параболика.
break;
}
if (i == Bars || Close[i] < psarN) // Найдя такую свечу, определяем,..
return(TREND_FOLLOW_DOWN); // ..продолжение или..
return(TREND_CHANGE_FROM_UP_TO_DOWN); // ..смену тренда
}
// - 3 - == Окончание блока =============================================================
// - 4 - == Закрытие текущей свечи равно точке параболика ===============================
for (i = index+1; i < Bars; i++) // Находим свечу, закрытие котрой не..
{ // ..равно точке параболика
psarN = iSAR(NULL, 0, PSARStep, PSARMax, i);
if (Close[i] != psarN)
break;
}
if (i == Bars || Close[i] < psarN) // Найдя такую свечу, определяем,..
return(TREND_FOLLOW_DOWN); // ..продолжение нисходящего или..
return(TREND_FOLLOW_UP); // ..восходящего тренда
// - 4 - == Окончание блока =============================================================
}
Такая сложность
определения в, казалось бы, простой ситуации, требуется по той причине, что для
определения тренда на одном баре далеко не всегда достаточно получения данных
только об этом и соседнем баре. В идеале это, конечно, так. Идеальные ситуации -
когда цена закрытия свечи не равна точке индикатора PSAR.
В этом случае все просто: если цена закрытия на указанном баре выше
точки, а на предыдущем баре ниже точки, то произошла смена тренда с нисходящего
на восходящий; если закрытие на указанном баре ниже точки, а на предыдущем -
выше, то произошла смена тренда с восходящего на нисходящий; если же цена
закрытия на указанном и предыдущем барах находится с одной и той же стороны от
точки, то тренд продолжается.
Но как
определить рыночную ситуацию, когда цена закрытия свечи на предыдущем баре равна
точке PSAR? Или еще хуже - цена закрытия на указанном
баре тоже равна цене PSAR? В этой ситуации поможет
только нахождение последней свечи, где цена закрытия не равна точке
PSAR. По положению такой свечи относительно точки
PSAR и можно сделать вывод о тренде.
Функция
GetBarSituation рассматривает три основных случая,
обрабатываемых в блоках 2 - 4.
Во втором блоке
обрабатывается случай нахождения цены закрытия указанной свечи выше точки
PSAR. То есть круг ситуаций сужается до двух: смена
тренда с нисходящего на восходящий или продолжение восходящего тренда. Если на
предыдущей свече цена закрытия ниже точки PSAR, то
получаем первую ситуацию, если на предыдущей свече цена закрытия выше точки
PSAR, то получаем вторую ситуацию. В том случае, если
цена закрытия предыдущей свечи равна точке PSAR, то
идем влево по графику в поисках неравенства цен закрытия и точки
PSAR. Как только нашли такую свечу, то действуем по
описанному алгоритму для предыдущей свечи.
Третий блок
обрабатывает случай нахождения цены закрытия указанной свечи ниже точки
PSAR. Алгоритм блока зеркален по отношению к алгоритму
второго блока. Третий блок возвращает два состояния: смена тренда с восходящего
на нисходящий и продолжение нисходящего тренда.
Четвертый блок
обрабатывает случай равенства цены закрытия указанной свечи точке
PSAR. Здесь также возможны только две ситуации,
ведущие к продолжению какого-либо тренда. Чтобы определить, какой именно тренд
продолжается, производится поиск ближайшей свечи, у которой цена закрытия не
совпадает с точкой PSAR.
Результат, возвращаемый функцией GetBarSituation,
используется в
функции ChangeTrend. В виду объемности функции,
рассмотрим ее по блокам. Первый блок:
// - 1 - == Подготовка информации =======================================================
int barSituation = GetBarSituation(index); // Определение ситуации на баре
if (changeRegData[index+1] == EMPTY_VALUE) // Если пришли сюда первый раз, то..
{ // ..занесем в массивы корректные..
changeRegData[index+1] = Time[index+1]; // ..значения
changeFactData[index+1] = Time[index+1];
minMaxBuf[index+1] = High[index+1];
}
changeRegData[index] = changeRegData[index+1]; // По умолчанию считаем, что ничего..
changeFactData[index] = changeFactData[index+1];// ..в текущей ситуации не изменится
minMaxBuf[index] = minMaxBuf[index+1];
waveNumber[index] = waveNumber[index+1];
// - 1 - == Окончание блока =============================================================
Переменная
barSituation получает результат исполненияфункции GetBarSituation на баре
index (аргумент функции ChangeTrend).
Далее происходит инициализация элементов index всех
четырех буферов индикатора путем копирования предыдущего значения. Если
предыдущего значения нет (первое исполнение функции), то элементы буферов
инициализируются параметрами свечи index+1.
Второй блок
исполняется при смене тренда на текущем баре (barSituation
содержит значение TREND_CHANGE_FROM_DOWN_TO_UP
или TREND_CHANGE_FROM_UP_TO_DOWN):
// - 2 - == Зафиксирована смена тенденции ===============================================
if (barSituation < TREND_FOLLOW_UP) // Смена тренда с восходящего на..
{ // ..нисходящий или с нисходящего..
// ..на восходящий
// - 2.1 - == Подготовка общих данных при смене тренда ===============================
changeRegData[index] = Time[index]; // Регистрируем новую волну
changeFactData[index] = Time[index]; // А здесь регистрируем начало..
// ..следующей (возможное, т.к. ..
// ..будет отодвигаться)
double psar2 = iSAR(NULL, 0, PSARStep, // Значение PSAR на предыдущем баре
PSARMax, index+1);
int prevChangeIndex = iBarShift(NULL, 0, // Определяем индекс бара..
changeRegData[index+1]);// ..предыдущей смены тренда
double psarN = iSAR(NULL, 0, PSARStep, // Значение PSAR на баре перед..
PSARMax, prevChangeIndex+1);// ..предыдущей сменой тренда
// - 2.1 - == Окончание блока ========================================================
// - 2.2 - == Смена тренда с нисходящего на восходящий ===============================
if (barSituation == TREND_CHANGE_FROM_DOWN_TO_UP)// Новая волна - восходящая
{
minMaxBuf[index] = High[index]; // Инициализация ее максимума
waveNumber[index] = GetNumberOfWave(psar2,// Определяем номер новой волны
psarN, prevChangeIndex, index+1, false);
DrawOneWave(index, prevChangeIndex); // Отображаем новую волну
}
// - 2.2 - == Окончание блока ========================================================
// - 2.3 - == Смена тренда с восходящего на нисходящий ===============================
if (barSituation == TREND_CHANGE_FROM_UP_TO_DOWN)// Новая волна - нисходящая
{
minMaxBuf[index] = Low[index]; // Инициализация ее минимума
waveNumber[index] = GetNumberOfWave(psar2,// Определяем номер новой волны
psarN, prevChangeIndex, index+1);
DrawOneWave(index, prevChangeIndex, false);// Отображаем новую волну
}
// - 2.3 - == Окончание блока ========================================================
return;
}
// - 2 - == Окончание блока =============================================================
В блоке 2.1
регистрируется факт начала новой волны путем записи значения в буфер
changeRegData,и тут же указывается окончание этой волны путем записи
данных в буфер changeFactData. В случае продолжения
волны значение не будет копироваться, обновляясь с каждым новым экстремумом.
Чтобы определить
прибыльность или убыточность сформированной волны, нам, как мы помним,
необходимо сравнить последнее значение
PSAR закончившейся волны и последнее значение
PSAR предшествующей ей волны. Точка
PSAR закончившейся волны - это значение PSAR на
баре index+1 (переменная psar2).
А для получения значения PSAR предшествующей волны
необходимо взять значение элемента index+1 буфера
changeRegData, в котором записано время открытия бара,
соответствующее началу сформированной волны. По этому времени находим номер бара
prevChangeIndex, а по номеру бара получаем значение
точки PSAR, предшествующей этому бару
(psarN).
В блоке 2.2
обрабатывается смена тренда с нисходящего на восходящий. В буфер
minMaxBuf записывается максимум текущего бара, а в
буфер waveNumber записывается номер новой волны,
вычисленный при помощи функции GetNumberOfWave. Далее
функция DrawOneWave отображает формирующуюся волну.
Аналогично работает блок 2.3, обрабатывающий смену тренда с восходящего на
нисходящий.
Третий и
четвертый блоки функции ChangeTrend имеют дело с
продолжением тренда:
// - 3 - == Продолжение нисходящей тенденции ============================================
if (barSituation == TREND_FOLLOW_DOWN)
if (minMaxBuf[index+1] == EMPTY_VALUE || // Предыдущий минимум не определен или
Low[index] <= minMaxBuf[index+1]) // ..новый минимум ниже
{
minMaxBuf[index] = Low[index]; // Обновляем минимум
changeFactData[index] = Time[index]; // Изменяем фактическое начало..
// ..следующей волны
RefreshWave(changeRegData[index], // Обновляем отображение волны
Time[index], Low[index]);
return;
}
// - 3 - == Окончание блока =============================================================
// - 4 - == Продолжение восходящей тенденции ============================================
if (barSituation == TREND_FOLLOW_UP)
if (minMaxBuf[index+1] == EMPTY_VALUE || // Предыдущий максимум не определен..
High[index] >= minMaxBuf[index+1]) // ..или новый максимум выше
{
minMaxBuf[index] = High[index]; // Обновляем максимум
changeFactData[index] = Time[index]; // Изменяем фактическое начало..
// ..следующей волны
RefreshWave(changeRegData[index], // Обновляем отображение волны
Time[index], High[index]);
}
// - 4 - == Окончание блока =============================================================
Эти блоки
обновляют значения в буферах minMaxBuf и
changeFactData, описывающих точку окончания
формирующейся волны в случае регистрации нового экстремума. После этого
вызывается функция RefreshWave, которая обновляет
отображение формирующейся волны.
Определение
номера волны происходит в теле функции GetNumberOfWave:
int GetNumberOfWave(double latePSAR, double earlyPSAR, int index1, int index2,
bool isPrevWaveUpward = true)
{
// - 1 - == Предыдущая волна была прибыльной ============================================
if ((latePSAR >= earlyPSAR && isPrevWaveUpward) ||// Предыдущая восходящая волна или..
(latePSAR <= earlyPSAR && !isPrevWaveUpward))// ..нисходящая волна была прибыльной
{
if (waveNumber[index1] == WAVE_1 || // Если предыдущая волна изначально..
waveNumber[index1] == WAVE_3 || // ..была определена как прибыльная,..
waveNumber[index1] == WAVE_5 || // ..то предполагается, что новая..
waveNumber[index1] == WAVE_A) // ..волна будет коррекционной (2, 4,
return(waveNumber[index1] + 1); // ..B) или прибыльной (A)
if (waveNumber[index1] == WAVE_C) // Если была волна С, то ожидается..
return(WAVE_1); // ..формирование новой волновой..
// ..формации
FillTheBufferWithNewData(index1, index2, WAVE_1);// В противном случае волна была..
return(WAVE_2); // ..ошибочно определена как..
// ..коррекционная. Поэтому считаем..
// ..предыдущую волну первой..
// ..волной новой волновой формации
}
// - 1 - == Окончание блока =============================================================
// - 2 - == Предыдущая волна была убыточной (коррекционной) =============================
if (waveNumber[index1] == WAVE_2 || // Предыдущая вона была правильно..
waveNumber[index1] == WAVE_4 || // ..определена как коррекционная.
waveNumber[index1] == WAVE_B) // Поэтому ожидаем, что новая волна..
return(waveNumber[index1] + 1); // ..будет прибыльной
FillTheBufferWithNewData(index1, index2, NO_WAVE);// В противном случае предыдущая..
// ..волна считалась прибыльной, что..
// ..в итоге оказалось не так.
// Исправим это недоразумение и..
return(WAVE_1); // ..будем считать новую волну первой
// - 2 - == Окончание блока =============================================================
}
Функция получает
пять аргументов: latePSAR - значение поздней точки
PSAR (последней точки сформированной волны),
earlyPSAR - значение ранней точки PSAR
(последней точки предшествующей волны), index1 и
index2 - индексы баров, между которыми находится
сформированная волна, isPrevWaveUpward - признак
направления волны (true - восходящая,
false - нисходящая).
Первый блок
имеет дело со сформированной прибыльной волной (поздняя точка
PSAR восходящей волны выше ранней точки PSAR
или поздняя точка PSAR нисходящей волны ниже ранней
точки PSAR). Посмотрим на
рис. 1 и 2 и определим, что прибыльными могут быть только волны 1, 3, 5, А и С.
Если это так, то новая волна ожидается убыточная, т.е. 2, 4 или
B. В случае с окончанием волны C
мы должны сигнализировать о завершении текущего тренда и ждать
формирования нового тренда, т.е. новая волна может быть первой или вообще не
определенной (возвращаем №1). Если же предыдущая волна изначально была
определена не как прибыльная (2, 4 или В), то это свидетельствует об ошибке
начального определения. Ставим сформированной волне в соответствие №1, заполняя
весь диапазон ее существования значением 1 (записывается в буфер
waveNumber при помощи функции
FillTheBufferWithNewData), и возвращаем номер
новой волны - 2.
Второй блок
обрабатывает формирование убыточной волны. Если сформированная волна действительно была
определена как убыточная (2, 4 или B),
то новая волна ожидается прибыльной (3, 5 или C).
В противном случае предыдущая волна рассматривается как неопределенная
(записывается значение 0 на всем ее протяжении), а новая волна ожидается с
номером 1.
Отображение
новой волны производится путем вызова функции DrawOneWave:
void DrawOneWave(int lateIndex, int earlyIndex, bool isUpwardWave = true)
{
// - 1 - == Для первой волны индивидуального отображения нет, только в паре со 2-ой =====
if (waveNumber[lateIndex] < WAVE_2)
return;
// - 1 - == Окончание блока =============================================================
// - 2 - == Отображение первой волны ====================================================
color clr = GetWaveColor(waveNumber[lateIndex], isUpwardWave);// Цвет движения
if (waveNumber[lateIndex] == WAVE_2) // Если требуется отобразить вторую..
CreateWave(changeRegData[lateIndex+1], // ..волну, то сначала позаботимся об
changeFactData[earlyIndex+1], // ..отображении первой волны
minMaxBuf[earlyIndex+1],
changeFactData[lateIndex+1],
minMaxBuf[lateIndex+1],
WAVE_1,
clr);
// - 2 - == Окончание блока =============================================================
// - 3 - == Отображение второй волны ====================================================
CreateWave(changeRegData[lateIndex], // Время для имени волны
changeFactData[lateIndex+1], // Время левой (по графику) точки
minMaxBuf[lateIndex+1], // Цена левой точки
changeFactData[lateIndex], // Время правой точки
minMaxBuf[lateIndex], // Цена правой точки
waveNumber[lateIndex], // Номер волны
clr); // Цвет волны
// - 3 - == Окончание блока =============================================================
}
Функция в
качестве аргументов получает: lateIndex - индекс бара,
на котором была зарегистрирована новая волна,
earlyIndex - индекс бара, на котором была зарегистрирована сформированная
волна, isUpwardWave - признак направления волны (true
- восходящая, false - нисходящая).
Для получения
номера новой волны достаточно обратиться к элементу буфера
waveNumber с индексом lateIndex. Если номер
новой волны 0 или 1, то отображать нечего. Если номер новой волны 2, то нужно
отобразить сформированную первую волну, а только за ней - новую вторую.
Отображение первой волны создается путем передачи функции
CreateWave времени регистрации первой волны (это будет идентификатор
объекта), времени фактического начала первой волны (элемент с индексом на один
больше earlyIndex), экстремума бара фактического
начала волны, времени фактического окончания первой волны (фактическое начало
второй - предыдущий бар lateIndex),
экстремума бара фактического окончания первой волны и номера волны 1.
Третий блок
функции аналогичным образом создает отображение заданной новой волны.
Функция
CreateWave отображает трендовую линию волны и ее
текстовое описание:
void CreateWave(datetime createTime, datetime time1, double price1, datetime time2,
double price2, int waveNumber, color clr)
{
// - 1 - == Отображение линии волны =====================================================
string name = prefix + "line_" + createTime; // Имя объекта
if (ObjectFind(name) < 0) // Если объект не существует, то..
{ // ..создаем его
ObjectCreate(name, OBJ_TREND, 0, time1, price1, time2, price2);
ObjectSet(name, OBJPROP_RAY, false);
ObjectSet(name, OBJPROP_COLOR, clr);
}
else // Если объект существует, то..
{ // ..перемещаем его на новое место
ObjectMove(name, 0, time1, price1);
ObjectMove(name, 1, time2, price2);
ObjectSet(name, OBJPROP_COLOR, clr);
}
// - 1 - == Окончание блока =============================================================
// - 2 - == Отображение текстового описания волны =======================================
name = prefix + "letter_" + createTime; // Имя объекта
datetime letterTime = time1 + MathFloor((time2 - time1)/2);// Координата Х объекта -
// ..середина между началом и..
// ..окончанием волны по горизонтали
double letterPrice = (price1 + price2)/2; // Координата Y - середина между..
// ..начальной и конечной ценой волны
if (ObjectFind(name) < 0) // Если объект не существует, то..
{ // ..создадим его
ObjectCreate(name, OBJ_TEXT, 0, letterTime, letterPrice);
ObjectSetText(name, GetWaveFromIndex(waveNumber), textSize, textFont, textColor);
return;
} // Если объект существует, то..
ObjectMove(name, 0, letterTime, letterPrice); // ..переместим его на новое место
ObjectSetText(name, GetWaveFromIndex(waveNumber), textSize, textFont, textColor);
// - 2 - == Окончание блока =============================================================
}
В первом блоке функции формируется имя трендовой линии -
prefix графических элементов индикатора, признак линии и время
регистрации волны. По последней составляющей имени в дальнейшем происходит
определение нужной линии в функции RefreshWave,
работающей по сходному принципу. Если линия с именем name
не существует, то она создается, отображаясь по координатам
time1, price1, time2, price2 и цветом
clr. В противном случае линия просто передвигается по
новым координатам.
Во втором блоке создается текстовая надпись, определяющая тип волны.
Индикатор пытается поместить надпись строго посередине линии волны как
горизонтально, так и вертикально. Это не всегда удается, т.к. время на графиках
терминала Meta Trader 4 изменяется не равномерно, в
нем присутствуют "дыры" (выходные или отсутствие торгов). Время отображения
надписи вычисляется как среднее время между началом и окончанием линии волны.
Цена отображения надписи вычисляется как средняя цена между началом и окончанием
линии волны. Далее алгоритм повторяет первый блок - создание надписи или
передвижение ее, если надпись уже существует.
Подобным образом работает функция RefreshWave с той
лишь разницей, что данные для расчета координат объектов она берет из уже
существующих объектов.
Результат работы индикатора
Разработанный
индикатор WavesOnPSAR отображает волновые формации,
основываясь на показаниях индикатора PSAR. Поэтому
результат его работы будет разный при различных настройках
PSAR. Изменить параметры PSAR можно путем
указания данных в настроечных параметрах индикатора
WavesOnPSAR PSARStepи PSARMax,
соответствующим параметрам индикатора PSAR step (шаг)и max (максимум).
Остальные
настроечные параметры индикатора отвечают за цвета восходящего
(colorUpwardWave) и нисходящего (colorDownwardWave)трендов, а также за внешний вид текстовой надписи:
textColor - цвет текста, textSize -
размер шрифта текста, textFont - наименование
шрифта.
Внешний вид
индикатор приведен на рис. 4.
Рис. 4. Внешний вид индикатора WavesOnPSAR.
При работе с
индикатором следует помнить, что формирующаяся волна может быть определена
неправильно и будет переопределена после своего окончания. Также не стоит
удивляться наличию нескольких первых волн подряд. Это нормально, т.к. первая
волна отображается в момент начала формирования второй волны. По условию
разработки индикатора перерисовка двух последних волн запрещена. Поэтому в
будущем может быть изменена только вторая волна (всегда на первую).
Сформированная волна №1 навсегда остается на своем месте без формирования
продолжения.
Аналогичным
образом остаются без продолжения волновые формации после любой другой волны,
если последующие волны не соответствуют условиям волновой теории. Яркий пример
приведен на рис. 4: восходящий тренд был закончен на волне №3. После этого
началось нисходящее движение.
Комментарии
Отправить комментарий