Scriptong
16-02-2010, 21:46
Наиболее часто употребляемыми в биржевой торговле терминами являются "тренд" и "коррекция". Тот факт, что за каждым трендом следует коррекция, сомнений ни у кого не вызывает. Только вот точно предсказать, в какой момент сложившееся движение сделает паузу и даже вернется назад, собираясь с новыми силами, мало кому удается.
За решение этой непростой задачи мы и возьмемся. Отправной точкой в наших рассуждениях будет предположение о существовании некоторой критической продолжительности тренда, которую без коррекции преодолеть невозможно. К примеру, возьмем валютную пару GBPUSD. Какое максимальное значение пунктов она может пройти без коррекций? По моим наблюдениям, не больше трехсот. Большее значение даже вспомнить трудно. Таким образом, при идентификации подобного мощного движения пары можно смело открывать позицию в обратную сторону. Даже если вход был произведен слишком рано и позиция сразу же стала приносить убыток, можно усреднить цену открытия, открыв еще одну позицию в том же направлении, но по лучшей цене.
Усреднение, конечно же, является очень опасным делом, злоупотребление которым зачастую приводит к плачевным последствиям. Поэтому при первых же признаках консолидации цены нужно наметить точки выхода из полученной пирамиды позиций. Одна точка выхода будет расположена в убыточной зоне на последнем экстремуме, достигнутом за время тренда. Вторая точка является лишь предполагаемым прибыльным уровнем, которого может достичь цена в ходе наметившейся коррекции. Здесь как раз и возникает дилемма. Каким образом рассчитать глубину коррекции? В этом случае на помощь придет обыкновенная статистика.
Сбор большого объема статистических данных нецелесообразно проводить вручную. Этим в последнее время занимается разнообразная вычислительная техника. Поэтому напишем специальную программу (скрипт), которая будет определять импульсные движения цены как несколько свечей подряд, движущихся в одном направлении (см. скрипт TrendStatistic). Минимальное количество свечей, принимаемых во внимание, определим равным трем. Меньшее количество, например, две однонаправленных свечи, на рынке встречаются очень часто и в качестве импульсного движения их рассматривать нельзя.
Алгоритм работы скрипта рассмотрим на примере восходящего движения. Три бычьих свечи подряд (эквивалент фигуры теханализа "Три белых солдата"), у которых максимум выше максимума предыдущей, а минимум выше минимума предыдущей свечи, образуют минимальное импульсное движение. Если к ним прибавляется еще одна такая же свеча, то в качестве паттерна берется уже четыре свечи и так далее, пока не встретится свеча, которая не удовлетворяет указанным условиям. Это может быть медвежья свеча. А может быть даже бычья свеча, но имеющая один из экстремумов ниже предыдущей свечи. Такая свеча сигнализирует об окончании формирования паттерна и с этого момента скрипт начинает подсчитывать глубину коррекции относительно определенного импульсного движения. Подсчет будет производиться до тех пор, пока не будет достигнута верхняя точка движения или пока не образуется новый восходящий паттерн.
Точно также скрипт идентифицирует нисходящее импульсное движение. В этом случае экстремумы свечей должны располагаться ниже соответствующих экстремумов предыдущих свечей, а точкой "стопа" будет нижняя граница зафиксированного паттерна. Пример работы скрипта приведен на рис. 1.
http://www.forextrade.ru/media/Image/MQLabs/53_ag/figure1.gif
Рис. 1 - Пример работы скрипта.
Медвежьи паттерны выделены красными прямоугольниками. Соответствующая им коррекция обведена красным пунктиром, образующим треугольник. Глубина коррекции, выраженная в процентах от высоты паттерна, указана зеленым цветом шрифта. Бычьи паттерны отмечены синими прямоугольниками, а соответствующая им коррекция выделена синим пунктиром, также образующим треугольник.
Как видно из рисунка, допускается вложение противоположных паттернов друг в друга. То есть импульсное восходящее движение может рассматриваться как коррекция предыдущего нисходящего движения и наоборот. Также видно, что паттерны могут состоять из различного количества свечей. Например, первый же медвежий паттерн состоит из четырех свечей, а следующий за ним бычий паттерн ограничивается лишь тремя свечами. Управление минимальным количеством свечей, на основании которых можно идентифицировать паттерн, для удобства пользователей вынесено во входные параметры. Параметр называется MinCandles.
Еще одной удобной функцией скрипта является возможность записи данных по найденным паттернам в csv-файл. Этот файл впоследствии можно открыть в Excel, где и произвести все аналитические действия. Для разрешения сохранения данных нужно присвоить параметру WriteInFile значение True, а в параметре FileName указать имя нового файла. В случае существования файла с таким именем, он будет перезаписан. После окончания работы программы, полученные данные можно найти в папке терминала experts\files.
Следует заметить, что скрипт ничего не пытается предсказывать, а лишь выделяет свершившиеся движения на истории.
На основании описанной методики можно создать эксперт, который при нахождении трех однонаправленных свечей подряд будет открывать позицию в противоположном направлении. Если впоследствии паттерн окажется более широким (а, следовательно, и высоким), то для подстраховки откроется еще одна позиция в том же направлении, что и первая. Уровни стопа на этом этапе установить еще невозможно, так как паттерн считать сформировавшимся нельзя. Но с первой же свечей, сигнализирующей об окончании паттерна, стоп-приказ устанавливается за ближайшим локальным экстремумом, а новые позиции не открываются. С этой минуты также рассчитывается окончательный уровень цели.
Первым делом оформляем сигнальную часть программы:
//+-------------------------------------------------------------------------------------+
//| Генерация сигнала по последнему паттерну |
//+-------------------------------------------------------------------------------------+
void GetSignal()
{
// - 1 - ============================= Инициализация переменных =========================
Signal = 0; // Текущий сигнал не определен
int i = 1; // Начинаем с последнего посчитанного бара
int Pattern = 0; // Текущий паттерн не определен
// - 1 - ================================== Окончание блока =============================
// - 2 - ============================= Поиск паттерна ===================================
while (i < Bars)
{
if (Open[i] < Close[i]) // Бычий бар
{
if (Pattern < 0) // Если до этого имела место нисходящая серия, то прерываем цикл
break;
if (High[i] > High[i+1] && Low[i] > Low[i+1]) // В восходящем паттерне все
Pattern++; // экстремумы свечей должны быть выше экстремумов предыдущих свечей
else
break;
}
if (Open[i] > Close[i]) // Медвежий бар
{
if (Pattern > 0) // Если до этого имела место восходящая серия, то выходим
break;
if (High[i] < High[i+1] && Low[i] < Low[i+1]) // В нисходящем паттерне все
Pattern--; // экстремумы свечей должны быть ниже экстремумов предыдущих свечей
else
break;
}
i++;
}
// - 2 - ================================== Окончание блока =============================
// - 3 - ==================== Предварительный анализ и сохранение данных ================
if (i == Bars || MathAbs(Pattern) < MinCandlesInPattern)//Не найден ни один из паттернов
{ // покидаем функцию
if (LastPattern != 0) // Но предварительно рассчитываем уровень стопа по последнему
// найденному паттерну
{
int Countbar = iBarShift(Symbol(), 0, LastEnd);
if (LastPattern > 0) // Стоп будет выше цены
StopOrder = High[iHighest(Symbol(), 0, MODE_HIGH, Countbar, 1)] + Tick + Spread;
if (LastPattern < 0) // Стоп будет ниже цены
StopOrder = Low[iLowest(Symbol(), 0, MODE_LOW, Countbar, 1)] - Tick;
}
return;
}
int BeginBar = MathAbs(Pattern);
LastEnd = Time[1];
LastPattern = Pattern;
StopOrder = 0; // При нахождении нового паттерна стоп еще неизвестен
// - 3 - ================================== Окончание блока =============================
// - 4 - ================= Медвежий паттерн - сигнал для покупок ========================
if (Pattern < 0) // текущее направление паттерна нисходящее - сигнал для входа вверх
{
Signal = 1; // Сигнал для покупки
Target = Low[1] + (High[-Pattern] - Low[1])*Percent/100;
if (ShowPatterns)
ShowLevel(Time, High[BeginBar], Time[1], Low[1], Target, BearPattern);
}
// - 4 - ================================== Окончание блока =============================
// - 5 - ================== Бычий паттерн - сигнал для продаж ===========================
if (Pattern > 0) // текущее направление паттерна восходящее - сигнал для входа вниз
{
Signal = -1; // Сигнал для продажи
Target = High[1] - (High[1] - Low[Pattern])*Percent/100 + Spread;
if (ShowPatterns)
ShowLevel(Time[BeginBar], Low[BeginBar], Time[1], High[1], Target - Spread,
BullPattern);
}
// - 5 - ================================== Окончание блока =============================
}
Логика нахождения паттерна реализована в блоке 2. Начиная с бара №1, производится последовательное сравнение цен открытия и закрытия свечей для определения типа свечи (бычья или медвежья). Если свеча бычья и ее экстремумы выше соответствующих экстремумов следующей свечи (например, №2), то происходит прирост значения переменной Pattern. Если же свеча медвежья и ее экстремумы ниже соответствующих экстремумов предыдущей свечи, то происходит уменьшение значения Pattern. В результате после окончания цикла можно судить о найденном паттерне по знаку переменной. Положительное значение соответствует бычьему паттерну, а отрицательное - медвежьему. Количество свечей в паттерне определяется абсолютным значением переменной Pattern.
Блок 3 при малом значении Pattern (недостаточно свечей в паттерне) определяет уровень стоп-приказа, так как, скорее всего, текущее ценовое движение находится в стадии коррекции. Во внимание берутся данные по предыдущему найденному паттерну. Эти данные хранятся в переменных LastPattern (направление и ширина последнего паттерна) и LastEnd (время окончания последнего паттерна). Если последний паттерн был медвежьим, то уровень стопа (переменная StopOrder) находится как наименьшее значение цены от паттерна до текущего времени. Если же паттерн был бычьим, то уровнем стопа будет максимальное значение цены. В случае успешного нахождения паттерна все его данные сохраняются в соответствующих переменных, а уровень стопа становится равным нулю.
Блоки 4 и 5 похожи. Разница лишь в значении генерируемого сигнала и целей. При отрицательном значении Pattern генерируется сигнал открытия длинных позиций, а уровень цели (переменная Target) рассчитывается в процентах от текущей высоты паттерна. При положительном значении Pattern генерируется сигнал открытия коротких позиций. Процент задается во внешней переменной советника Percent пользователем. Далее, для обеспечения визуального контроля трейдером действий советника, в окне графика отображается ближайший зафиксированный паттерн. Переключение режимов отображения производится при помощи внешней переменной ShowPatterns, а цвета паттернов задаются в переменных BullPattern и BearPattern.
Связующая функция start является практически точной копией функции start, рассмотренной в предыдущей статье "Летучая мышь" (http://fxtrade.ru/mqlabs/06.02.2010-letuchaya-mysh). Разница лишь в названии функции открытия позиций - вместо CheckOrders используется OpenOrders. А вот содержание вызываемых из start функций FindOrders и OpenOrders значительно изменилось. В FindOrders вместо простого подсчета своих ордеров и позиций производится закрытие позиций по обратному сигналу и модификация уровней стопа и профита:
//+-------------------------------------------------------------------------------------+
//| Нахождение всех своих ордеров, установка стопа, профита или закрытие позиции. |
//+-------------------------------------------------------------------------------------+
bool FindOrders()
{
LastNum = 0;
// - 1 - ============================ Выбираем свои ордера и позиции ====================
for (int i = OrdersTotal()-1; i >= 0; i--)
if (OrderSelect(i, SELECT_BY_POS))
if (OrderSymbol() == Symbol() && MathFloor(OrderMagicNumber()/100) == MagicNumber)
// - 1 - ================================ Окончание блока ===============================
{
LastNum = MathMax(LastNum, MathMod(OrderMagicNumber(), 100));
LastOpen = MathMax(LastOpen, OrderOpenTime());
double Price = 0;
double SL = 0;
double TP = 0;
// - 2 - =============== Принятие решений о действии с позицией =========================
if (OrderType() == OP_BUY) // Найден BUY-ордер
{
if (Signal < 0) // В случае сигнала SELL позиция закрывается
Price = Bid;
else // иначе проверяется уровень стоп-приказа и профита
{
if (StopOrder != 0 && OrderStopLoss() == 0)
if (Bid - StopOrder > StopLevel)
SL = StopOrder;
if (MathAbs(OrderTakeProfit() - Target) >= Tick)
if (Target - Bid > StopLevel)
TP = Target;
}
}
else // найден Sell-ордер
{
if (Signal > 0) // В случае сигнала BUY позиция закрывается
Price = Ask;
else // иначе проверяется уровень стоп-приказа и профита
{
if (StopOrder != 0 && OrderStopLoss() == 0)
if (StopOrder - Ask > StopLevel)
SL = StopOrder;
if (MathAbs(OrderTakeProfit() - Target) >= Tick)
if (Ask - Target > StopLevel)
TP = Target;
}
}
// - 2 - ================================ Окончание блока ===============================
// - 3 - =============== Закрытие позиций, если значение Price не равно нулю ============
if (Price != 0)
{
if (WaitForTradeContext())
if (!OrderClose(OrderTicket(), OrderLots(), NP(Price), 3))
return(False);
RefreshRates();
}
// - 3 - ================================ Окончание блока ===============================
// - 4 - ========== Модификация позиций, если значение SL или TP не равно нулю ==========
if (SL != 0 || TP != 0)
{
if (SL == 0) SL = OrderStopLoss();
if (TP == 0) TP = OrderTakeProfit();
if (WaitForTradeContext())
if (!OrderModify(OrderTicket(), 0, NP(SL), NP(TP), 0))
return(False);
RefreshRates();
}
// - 4 - ================================ Окончание блока ===============================
}
return(True);
}
Подобные функции мы уже разбирали, поэтому многое должно быть знакомо. Следует лишь отметить, что переменная LastNum используется для определения номера последней открытой позиции в серии, который участвует в формировании MagicNumber. А переменная LastOpen по окончании работы функции будет содержать время открытия последней позиции в серии.
Блок 2 разделяет найденные позиции на длинные и короткие. Для каждого из типов позиций алгоритм примерно одинаков. При наличии сигнала открытия коротких позиций найденная длинная позиция должна быть закрыта и наоборот. Оповещение системы закрытия позиций, расположенной в блоке 3, производится с помощью присвоения переменной Price цены закрытия.
Аналогичным образом подаются сигналы изменения уровня стопа и профита позиции. Стоп будет изменен, если найдена позиция без уровня стопа, а переменная StopOrder не равна нулю, то есть, содержит значение стопа. В этом случае переменной SL будет присвоено значение нового уровня стопа. Немного отличается критерий изменения уровня профита - текущий профит должен просто отличаться от значения переменной Target на пункт и более. В результате, в переменную TP будет занесено новое значение уровня профита.
Блок 3, как упоминалось выше, определяет необходимость закрытия позиции. Условием закрытия является значение Price, не равное нулю.
Блок 4 осуществляет модификацию позиции, если хотя бы одно из значений SL или TP не равно нулю.
Во всех случаях результаты операций проверяются и, если хотя бы одна из операций завершилась с ошибкой, то результат функции FindOrders будет False, что приведет к возвращению сюда же на следующем тике.
Последняя значимая функция программы - OpenOrders. Как следует из ее названия, задачей функции является открытие новых позиций:
//+-------------------------------------------------------------------------------------+
//| Открытие позиций при наличии сигнала |
//+-------------------------------------------------------------------------------------+
bool OpenOrders()
{
if (LastOpen >= Time[0]) return(True); // Если на текущей свече позиция уже открывалась,
// то больше не открываем
// - 1 - ==================== Активен сигнал открытия длинных позиций ===================
if (Signal > 0)
if (Target - Ask > StopLevel) // Есть ли вообще смысл открывать позицию
{
if (Bid - StopOrder > StopLevel)
if (OpenOrderCorrect(OP_BUY, NP(Ask), NP(StopOrder), NP(Target),
MagicNumber*100+LastNum+1) == 0)
return(True);
return(False);
}
// - 1 - ================================ Окончание блока ===============================
// - 2 - =================== Активен сигнал открытия коротких позиций ===================
if (Signal < 0)
if (Bid - Target > StopLevel) // Есть ли вообще смысл открывать позицию
{
if (StopOrder - Ask > StopLevel || StopOrder == 0)
if (OpenOrderCorrect(OP_SELL, NP(Bid), NP(StopOrder), NP(Target),
MagicNumber*100+LastNum+1) == 0)
return(True);
return(False);
}
// - 2 - ================================ Окончание блока ===============================
return(True);
}
В самом начале функции размещается проверка существования открытой на текущей свече позиции. В этом помогает значение переменной LastOpen. Если оно меньше времени открытия формирующейся свечи, то можно открывать новую позицию.
Блок 1 занимается открытием длинной позиции, если значение Signal положительное. При этом проверяется удаленность цели от текущей цены. Если цель слишком близко (меньше, чем StopLevel), то нет смысла открывать позицию, так как соотношение риск/прибыль будет неоправданно большим.
Блок 2 открывает короткую позицию, если значение Signal отрицательное. Точно так же, как в блоке 2, производится проверка целесообразности открытия позиции.
Если хотя бы одна из проведенных за время выполнения функции операций завершилась с ошибкой, то результатом OpenOrders будет False, что обеспечит возвращение управления на следующем тике. Если результатом OpenOrders будет True, то советник перестанет функционировать до открытия следующей свечи.
Описанные функции образуют советник TradeInCorrection, пользуясь готовностью которого, можно переходить к тестированию. Все показанные результаты были получены на историческом промежутке 01.01.2008 - 13.20.2010, используя таймфрейм Н1. Различия касаются лишь параметра Percent, который был индивидуально подобран для каждой валютной пары в ходе оптимизации (см. рис. 2 - 5).
http://www.forextrade.ru/media/Image/MQLabs/53_ag/EURUSD.gif
Рис. 2. - График кривой баланса, получаемый при тестировании советника на валютной паре EURUSD.
EURUSD. Значение Percent взято равным 13, из-за чего количество сделок достигло необходимого уровня, но не дотянуло до достаточного. Несмотря на красивый вид кривой баланса, радоваться нечему. Чистая прибыль 457 долларов против максимальной просадки 315 долларов (ФВ = 1.45). Просадка неестественно мала, но, с другой стороны, прибыль тоже не взлетела до небес. Для двух лет стараний 450 пунктов прибыли являются очень скромным результатом. Такая торговля не может быть названа эффективной. Проще уж положить деньги в банк.
http://www.forextrade.ru/media/Image/MQLabs/53_ag/USDCHF.gif
Рис. 3. - График кривой баланса, получаемый при тестировании советника на валютной паре USDCHF.
Похожая участь, но с худшими показателями, постигла валютную пару USDCHF, для которой значение Percent было подобрано на таком же уровне - 13. Чистая прибыль 290 долларов против 342 долларов максимальной просадки (ФВ = 0.85).
http://www.forextrade.ru/media/Image/MQLabs/53_ag/GBPUSD.gif
Рис. 4. - График кривой баланса, получаемый при тестировании советника на валютной паре GBPUSD.
Волатильность валютной пары GBPUSD принесла свои плоды при значении Percent = 27. Вид полученной кривой баланса очень близок к прямой линии, что говорит о надежности сигналов. К тому же, результаты можно считать статистически подтвержденными, так как количество проведенных сделок оказалось велико - 641. При этом чистая прибыль достигла 2646 долларов, а максимальная просадка добралась лишь до скромного значения 698 долларов, что дает фактор восстановления 3.79. Вся прибыль была получена благодаря значительному превосходству прибыльных сделок над убыточными - 73% против 27%, в то время как в абсолютном значении убыточные сделки показали двойной перевес - 18 долларов средняя прибыльная против 35 долларов средней убыточной.
http://www.forextrade.ru/media/Image/MQLabs/53_ag/USDJPY.gif
Рис. 5. - График кривой баланса, получаемый при тестировании советника на валютной паре USDJPY.
Не совсем красивый вид кривой баланса получился у валютной пары USDJPY при значении Percent = 234. Тем не менее, общая тенденция явно восходящая. К тому же, конечный результат в численном выражении вполне приемлем. Чистая прибыль 3118 долларов против максимальной просадки 1565 долларов. В данном случае прибыль достигается за счет превосходства прибыльных сделок в абсолютном выражении - 73.69 долларов средняя прибыльная и 23.76 средняя убыточная. При этом убыточных сделок выходит 72%, а прибыльных лишь 28%.
Подводя итог, отметим изначальное преимущество описанной системы - все сделки осуществляются по известной аксиоме "покупай дешевле, продавай дороже". Единственным минусом является риск открытия позиции на невероятно продолжительном тренде против движения. Панацеей от таких входов является четкое соблюдение правил управления капиталом, когда величины депозита хватает, чтобы выдержать неожиданные спурты рынка.
[B]Доработка стратегии для использования в AutoGraf 4.0
Изменение кода эксперта TradeInCorrection для работы стратегии в среде AutoGraf 4.0 привело к тому, что все входные параметры, кроме Lots, пришлось вынести во входные параметры самого AutoGraf. Так, значение цели сделки Percent теперь нужно указывать во входном параметре AT_1, значение минимального количества свечей в паттерне MinCandlesInPattern - в AT_2, а управление отображением паттерна ShowPatterns - в AT_3. Чтобы паттерн не отображался, в AT_3 необходимо указать 0, а для показа - любое положительное число. Управление объемом сделки осуществляется с панели инструментов AutoGraf.
Для запуска советника из-под AutoGraf 4.0 совершите такие шаги:
Воспользуйтесь ссылкой Файлы стратегий для Autograf 4.0 (http://www.forextrade.ru/media/Image/MQLabs/53_ag/AG_TradeInCorrection.zip) и полученный архив распакуйте в папку MT4\experts\libraries. Запустите AutoGraf. Для работы советника в ключе приведенных результатов тестирования (на примере GBPUSD) выставьте значения AT_1 = 27, AT_2 = 3, AT_3 = 1 (полное повторение результатов при этом не гарантируется). Выберите стратегию №3. Для этого передвиньте вверх значок So и среди названий стратегий найдите значок S3, который также потяните вверх. Запустите функцию автоматической торговли, передвинув значок AT в верхнее положение.
Использование полученного советника рекомендуется только в полуавтоматическом режиме под присмотром трейдера и после всестороннего изучения слабых и сильных сторон стратегии.
Советник TradeInCorrection (http://www.forextrade.ru/media/Image/MQLabs/53_ag/TradeInCorrection_Expert.mq4)
Скрипт TrendStatistic (http://www.forextrade.ru/media/Image/MQLabs/53_ag/TrendStatistic.mq4)
Файлы стратегии для AutoGraf 4.0 (http://www.forextrade.ru/media/Image/MQLabs/53_ag/AG_TradeInCorrection.zip)
Развернутые результаты тестирования эксперта (http://www.forextrade.ru/media/Image/MQLabs/53_ag/Test.zip)
За решение этой непростой задачи мы и возьмемся. Отправной точкой в наших рассуждениях будет предположение о существовании некоторой критической продолжительности тренда, которую без коррекции преодолеть невозможно. К примеру, возьмем валютную пару GBPUSD. Какое максимальное значение пунктов она может пройти без коррекций? По моим наблюдениям, не больше трехсот. Большее значение даже вспомнить трудно. Таким образом, при идентификации подобного мощного движения пары можно смело открывать позицию в обратную сторону. Даже если вход был произведен слишком рано и позиция сразу же стала приносить убыток, можно усреднить цену открытия, открыв еще одну позицию в том же направлении, но по лучшей цене.
Усреднение, конечно же, является очень опасным делом, злоупотребление которым зачастую приводит к плачевным последствиям. Поэтому при первых же признаках консолидации цены нужно наметить точки выхода из полученной пирамиды позиций. Одна точка выхода будет расположена в убыточной зоне на последнем экстремуме, достигнутом за время тренда. Вторая точка является лишь предполагаемым прибыльным уровнем, которого может достичь цена в ходе наметившейся коррекции. Здесь как раз и возникает дилемма. Каким образом рассчитать глубину коррекции? В этом случае на помощь придет обыкновенная статистика.
Сбор большого объема статистических данных нецелесообразно проводить вручную. Этим в последнее время занимается разнообразная вычислительная техника. Поэтому напишем специальную программу (скрипт), которая будет определять импульсные движения цены как несколько свечей подряд, движущихся в одном направлении (см. скрипт TrendStatistic). Минимальное количество свечей, принимаемых во внимание, определим равным трем. Меньшее количество, например, две однонаправленных свечи, на рынке встречаются очень часто и в качестве импульсного движения их рассматривать нельзя.
Алгоритм работы скрипта рассмотрим на примере восходящего движения. Три бычьих свечи подряд (эквивалент фигуры теханализа "Три белых солдата"), у которых максимум выше максимума предыдущей, а минимум выше минимума предыдущей свечи, образуют минимальное импульсное движение. Если к ним прибавляется еще одна такая же свеча, то в качестве паттерна берется уже четыре свечи и так далее, пока не встретится свеча, которая не удовлетворяет указанным условиям. Это может быть медвежья свеча. А может быть даже бычья свеча, но имеющая один из экстремумов ниже предыдущей свечи. Такая свеча сигнализирует об окончании формирования паттерна и с этого момента скрипт начинает подсчитывать глубину коррекции относительно определенного импульсного движения. Подсчет будет производиться до тех пор, пока не будет достигнута верхняя точка движения или пока не образуется новый восходящий паттерн.
Точно также скрипт идентифицирует нисходящее импульсное движение. В этом случае экстремумы свечей должны располагаться ниже соответствующих экстремумов предыдущих свечей, а точкой "стопа" будет нижняя граница зафиксированного паттерна. Пример работы скрипта приведен на рис. 1.
http://www.forextrade.ru/media/Image/MQLabs/53_ag/figure1.gif
Рис. 1 - Пример работы скрипта.
Медвежьи паттерны выделены красными прямоугольниками. Соответствующая им коррекция обведена красным пунктиром, образующим треугольник. Глубина коррекции, выраженная в процентах от высоты паттерна, указана зеленым цветом шрифта. Бычьи паттерны отмечены синими прямоугольниками, а соответствующая им коррекция выделена синим пунктиром, также образующим треугольник.
Как видно из рисунка, допускается вложение противоположных паттернов друг в друга. То есть импульсное восходящее движение может рассматриваться как коррекция предыдущего нисходящего движения и наоборот. Также видно, что паттерны могут состоять из различного количества свечей. Например, первый же медвежий паттерн состоит из четырех свечей, а следующий за ним бычий паттерн ограничивается лишь тремя свечами. Управление минимальным количеством свечей, на основании которых можно идентифицировать паттерн, для удобства пользователей вынесено во входные параметры. Параметр называется MinCandles.
Еще одной удобной функцией скрипта является возможность записи данных по найденным паттернам в csv-файл. Этот файл впоследствии можно открыть в Excel, где и произвести все аналитические действия. Для разрешения сохранения данных нужно присвоить параметру WriteInFile значение True, а в параметре FileName указать имя нового файла. В случае существования файла с таким именем, он будет перезаписан. После окончания работы программы, полученные данные можно найти в папке терминала experts\files.
Следует заметить, что скрипт ничего не пытается предсказывать, а лишь выделяет свершившиеся движения на истории.
На основании описанной методики можно создать эксперт, который при нахождении трех однонаправленных свечей подряд будет открывать позицию в противоположном направлении. Если впоследствии паттерн окажется более широким (а, следовательно, и высоким), то для подстраховки откроется еще одна позиция в том же направлении, что и первая. Уровни стопа на этом этапе установить еще невозможно, так как паттерн считать сформировавшимся нельзя. Но с первой же свечей, сигнализирующей об окончании паттерна, стоп-приказ устанавливается за ближайшим локальным экстремумом, а новые позиции не открываются. С этой минуты также рассчитывается окончательный уровень цели.
Первым делом оформляем сигнальную часть программы:
//+-------------------------------------------------------------------------------------+
//| Генерация сигнала по последнему паттерну |
//+-------------------------------------------------------------------------------------+
void GetSignal()
{
// - 1 - ============================= Инициализация переменных =========================
Signal = 0; // Текущий сигнал не определен
int i = 1; // Начинаем с последнего посчитанного бара
int Pattern = 0; // Текущий паттерн не определен
// - 1 - ================================== Окончание блока =============================
// - 2 - ============================= Поиск паттерна ===================================
while (i < Bars)
{
if (Open[i] < Close[i]) // Бычий бар
{
if (Pattern < 0) // Если до этого имела место нисходящая серия, то прерываем цикл
break;
if (High[i] > High[i+1] && Low[i] > Low[i+1]) // В восходящем паттерне все
Pattern++; // экстремумы свечей должны быть выше экстремумов предыдущих свечей
else
break;
}
if (Open[i] > Close[i]) // Медвежий бар
{
if (Pattern > 0) // Если до этого имела место восходящая серия, то выходим
break;
if (High[i] < High[i+1] && Low[i] < Low[i+1]) // В нисходящем паттерне все
Pattern--; // экстремумы свечей должны быть ниже экстремумов предыдущих свечей
else
break;
}
i++;
}
// - 2 - ================================== Окончание блока =============================
// - 3 - ==================== Предварительный анализ и сохранение данных ================
if (i == Bars || MathAbs(Pattern) < MinCandlesInPattern)//Не найден ни один из паттернов
{ // покидаем функцию
if (LastPattern != 0) // Но предварительно рассчитываем уровень стопа по последнему
// найденному паттерну
{
int Countbar = iBarShift(Symbol(), 0, LastEnd);
if (LastPattern > 0) // Стоп будет выше цены
StopOrder = High[iHighest(Symbol(), 0, MODE_HIGH, Countbar, 1)] + Tick + Spread;
if (LastPattern < 0) // Стоп будет ниже цены
StopOrder = Low[iLowest(Symbol(), 0, MODE_LOW, Countbar, 1)] - Tick;
}
return;
}
int BeginBar = MathAbs(Pattern);
LastEnd = Time[1];
LastPattern = Pattern;
StopOrder = 0; // При нахождении нового паттерна стоп еще неизвестен
// - 3 - ================================== Окончание блока =============================
// - 4 - ================= Медвежий паттерн - сигнал для покупок ========================
if (Pattern < 0) // текущее направление паттерна нисходящее - сигнал для входа вверх
{
Signal = 1; // Сигнал для покупки
Target = Low[1] + (High[-Pattern] - Low[1])*Percent/100;
if (ShowPatterns)
ShowLevel(Time, High[BeginBar], Time[1], Low[1], Target, BearPattern);
}
// - 4 - ================================== Окончание блока =============================
// - 5 - ================== Бычий паттерн - сигнал для продаж ===========================
if (Pattern > 0) // текущее направление паттерна восходящее - сигнал для входа вниз
{
Signal = -1; // Сигнал для продажи
Target = High[1] - (High[1] - Low[Pattern])*Percent/100 + Spread;
if (ShowPatterns)
ShowLevel(Time[BeginBar], Low[BeginBar], Time[1], High[1], Target - Spread,
BullPattern);
}
// - 5 - ================================== Окончание блока =============================
}
Логика нахождения паттерна реализована в блоке 2. Начиная с бара №1, производится последовательное сравнение цен открытия и закрытия свечей для определения типа свечи (бычья или медвежья). Если свеча бычья и ее экстремумы выше соответствующих экстремумов следующей свечи (например, №2), то происходит прирост значения переменной Pattern. Если же свеча медвежья и ее экстремумы ниже соответствующих экстремумов предыдущей свечи, то происходит уменьшение значения Pattern. В результате после окончания цикла можно судить о найденном паттерне по знаку переменной. Положительное значение соответствует бычьему паттерну, а отрицательное - медвежьему. Количество свечей в паттерне определяется абсолютным значением переменной Pattern.
Блок 3 при малом значении Pattern (недостаточно свечей в паттерне) определяет уровень стоп-приказа, так как, скорее всего, текущее ценовое движение находится в стадии коррекции. Во внимание берутся данные по предыдущему найденному паттерну. Эти данные хранятся в переменных LastPattern (направление и ширина последнего паттерна) и LastEnd (время окончания последнего паттерна). Если последний паттерн был медвежьим, то уровень стопа (переменная StopOrder) находится как наименьшее значение цены от паттерна до текущего времени. Если же паттерн был бычьим, то уровнем стопа будет максимальное значение цены. В случае успешного нахождения паттерна все его данные сохраняются в соответствующих переменных, а уровень стопа становится равным нулю.
Блоки 4 и 5 похожи. Разница лишь в значении генерируемого сигнала и целей. При отрицательном значении Pattern генерируется сигнал открытия длинных позиций, а уровень цели (переменная Target) рассчитывается в процентах от текущей высоты паттерна. При положительном значении Pattern генерируется сигнал открытия коротких позиций. Процент задается во внешней переменной советника Percent пользователем. Далее, для обеспечения визуального контроля трейдером действий советника, в окне графика отображается ближайший зафиксированный паттерн. Переключение режимов отображения производится при помощи внешней переменной ShowPatterns, а цвета паттернов задаются в переменных BullPattern и BearPattern.
Связующая функция start является практически точной копией функции start, рассмотренной в предыдущей статье "Летучая мышь" (http://fxtrade.ru/mqlabs/06.02.2010-letuchaya-mysh). Разница лишь в названии функции открытия позиций - вместо CheckOrders используется OpenOrders. А вот содержание вызываемых из start функций FindOrders и OpenOrders значительно изменилось. В FindOrders вместо простого подсчета своих ордеров и позиций производится закрытие позиций по обратному сигналу и модификация уровней стопа и профита:
//+-------------------------------------------------------------------------------------+
//| Нахождение всех своих ордеров, установка стопа, профита или закрытие позиции. |
//+-------------------------------------------------------------------------------------+
bool FindOrders()
{
LastNum = 0;
// - 1 - ============================ Выбираем свои ордера и позиции ====================
for (int i = OrdersTotal()-1; i >= 0; i--)
if (OrderSelect(i, SELECT_BY_POS))
if (OrderSymbol() == Symbol() && MathFloor(OrderMagicNumber()/100) == MagicNumber)
// - 1 - ================================ Окончание блока ===============================
{
LastNum = MathMax(LastNum, MathMod(OrderMagicNumber(), 100));
LastOpen = MathMax(LastOpen, OrderOpenTime());
double Price = 0;
double SL = 0;
double TP = 0;
// - 2 - =============== Принятие решений о действии с позицией =========================
if (OrderType() == OP_BUY) // Найден BUY-ордер
{
if (Signal < 0) // В случае сигнала SELL позиция закрывается
Price = Bid;
else // иначе проверяется уровень стоп-приказа и профита
{
if (StopOrder != 0 && OrderStopLoss() == 0)
if (Bid - StopOrder > StopLevel)
SL = StopOrder;
if (MathAbs(OrderTakeProfit() - Target) >= Tick)
if (Target - Bid > StopLevel)
TP = Target;
}
}
else // найден Sell-ордер
{
if (Signal > 0) // В случае сигнала BUY позиция закрывается
Price = Ask;
else // иначе проверяется уровень стоп-приказа и профита
{
if (StopOrder != 0 && OrderStopLoss() == 0)
if (StopOrder - Ask > StopLevel)
SL = StopOrder;
if (MathAbs(OrderTakeProfit() - Target) >= Tick)
if (Ask - Target > StopLevel)
TP = Target;
}
}
// - 2 - ================================ Окончание блока ===============================
// - 3 - =============== Закрытие позиций, если значение Price не равно нулю ============
if (Price != 0)
{
if (WaitForTradeContext())
if (!OrderClose(OrderTicket(), OrderLots(), NP(Price), 3))
return(False);
RefreshRates();
}
// - 3 - ================================ Окончание блока ===============================
// - 4 - ========== Модификация позиций, если значение SL или TP не равно нулю ==========
if (SL != 0 || TP != 0)
{
if (SL == 0) SL = OrderStopLoss();
if (TP == 0) TP = OrderTakeProfit();
if (WaitForTradeContext())
if (!OrderModify(OrderTicket(), 0, NP(SL), NP(TP), 0))
return(False);
RefreshRates();
}
// - 4 - ================================ Окончание блока ===============================
}
return(True);
}
Подобные функции мы уже разбирали, поэтому многое должно быть знакомо. Следует лишь отметить, что переменная LastNum используется для определения номера последней открытой позиции в серии, который участвует в формировании MagicNumber. А переменная LastOpen по окончании работы функции будет содержать время открытия последней позиции в серии.
Блок 2 разделяет найденные позиции на длинные и короткие. Для каждого из типов позиций алгоритм примерно одинаков. При наличии сигнала открытия коротких позиций найденная длинная позиция должна быть закрыта и наоборот. Оповещение системы закрытия позиций, расположенной в блоке 3, производится с помощью присвоения переменной Price цены закрытия.
Аналогичным образом подаются сигналы изменения уровня стопа и профита позиции. Стоп будет изменен, если найдена позиция без уровня стопа, а переменная StopOrder не равна нулю, то есть, содержит значение стопа. В этом случае переменной SL будет присвоено значение нового уровня стопа. Немного отличается критерий изменения уровня профита - текущий профит должен просто отличаться от значения переменной Target на пункт и более. В результате, в переменную TP будет занесено новое значение уровня профита.
Блок 3, как упоминалось выше, определяет необходимость закрытия позиции. Условием закрытия является значение Price, не равное нулю.
Блок 4 осуществляет модификацию позиции, если хотя бы одно из значений SL или TP не равно нулю.
Во всех случаях результаты операций проверяются и, если хотя бы одна из операций завершилась с ошибкой, то результат функции FindOrders будет False, что приведет к возвращению сюда же на следующем тике.
Последняя значимая функция программы - OpenOrders. Как следует из ее названия, задачей функции является открытие новых позиций:
//+-------------------------------------------------------------------------------------+
//| Открытие позиций при наличии сигнала |
//+-------------------------------------------------------------------------------------+
bool OpenOrders()
{
if (LastOpen >= Time[0]) return(True); // Если на текущей свече позиция уже открывалась,
// то больше не открываем
// - 1 - ==================== Активен сигнал открытия длинных позиций ===================
if (Signal > 0)
if (Target - Ask > StopLevel) // Есть ли вообще смысл открывать позицию
{
if (Bid - StopOrder > StopLevel)
if (OpenOrderCorrect(OP_BUY, NP(Ask), NP(StopOrder), NP(Target),
MagicNumber*100+LastNum+1) == 0)
return(True);
return(False);
}
// - 1 - ================================ Окончание блока ===============================
// - 2 - =================== Активен сигнал открытия коротких позиций ===================
if (Signal < 0)
if (Bid - Target > StopLevel) // Есть ли вообще смысл открывать позицию
{
if (StopOrder - Ask > StopLevel || StopOrder == 0)
if (OpenOrderCorrect(OP_SELL, NP(Bid), NP(StopOrder), NP(Target),
MagicNumber*100+LastNum+1) == 0)
return(True);
return(False);
}
// - 2 - ================================ Окончание блока ===============================
return(True);
}
В самом начале функции размещается проверка существования открытой на текущей свече позиции. В этом помогает значение переменной LastOpen. Если оно меньше времени открытия формирующейся свечи, то можно открывать новую позицию.
Блок 1 занимается открытием длинной позиции, если значение Signal положительное. При этом проверяется удаленность цели от текущей цены. Если цель слишком близко (меньше, чем StopLevel), то нет смысла открывать позицию, так как соотношение риск/прибыль будет неоправданно большим.
Блок 2 открывает короткую позицию, если значение Signal отрицательное. Точно так же, как в блоке 2, производится проверка целесообразности открытия позиции.
Если хотя бы одна из проведенных за время выполнения функции операций завершилась с ошибкой, то результатом OpenOrders будет False, что обеспечит возвращение управления на следующем тике. Если результатом OpenOrders будет True, то советник перестанет функционировать до открытия следующей свечи.
Описанные функции образуют советник TradeInCorrection, пользуясь готовностью которого, можно переходить к тестированию. Все показанные результаты были получены на историческом промежутке 01.01.2008 - 13.20.2010, используя таймфрейм Н1. Различия касаются лишь параметра Percent, который был индивидуально подобран для каждой валютной пары в ходе оптимизации (см. рис. 2 - 5).
http://www.forextrade.ru/media/Image/MQLabs/53_ag/EURUSD.gif
Рис. 2. - График кривой баланса, получаемый при тестировании советника на валютной паре EURUSD.
EURUSD. Значение Percent взято равным 13, из-за чего количество сделок достигло необходимого уровня, но не дотянуло до достаточного. Несмотря на красивый вид кривой баланса, радоваться нечему. Чистая прибыль 457 долларов против максимальной просадки 315 долларов (ФВ = 1.45). Просадка неестественно мала, но, с другой стороны, прибыль тоже не взлетела до небес. Для двух лет стараний 450 пунктов прибыли являются очень скромным результатом. Такая торговля не может быть названа эффективной. Проще уж положить деньги в банк.
http://www.forextrade.ru/media/Image/MQLabs/53_ag/USDCHF.gif
Рис. 3. - График кривой баланса, получаемый при тестировании советника на валютной паре USDCHF.
Похожая участь, но с худшими показателями, постигла валютную пару USDCHF, для которой значение Percent было подобрано на таком же уровне - 13. Чистая прибыль 290 долларов против 342 долларов максимальной просадки (ФВ = 0.85).
http://www.forextrade.ru/media/Image/MQLabs/53_ag/GBPUSD.gif
Рис. 4. - График кривой баланса, получаемый при тестировании советника на валютной паре GBPUSD.
Волатильность валютной пары GBPUSD принесла свои плоды при значении Percent = 27. Вид полученной кривой баланса очень близок к прямой линии, что говорит о надежности сигналов. К тому же, результаты можно считать статистически подтвержденными, так как количество проведенных сделок оказалось велико - 641. При этом чистая прибыль достигла 2646 долларов, а максимальная просадка добралась лишь до скромного значения 698 долларов, что дает фактор восстановления 3.79. Вся прибыль была получена благодаря значительному превосходству прибыльных сделок над убыточными - 73% против 27%, в то время как в абсолютном значении убыточные сделки показали двойной перевес - 18 долларов средняя прибыльная против 35 долларов средней убыточной.
http://www.forextrade.ru/media/Image/MQLabs/53_ag/USDJPY.gif
Рис. 5. - График кривой баланса, получаемый при тестировании советника на валютной паре USDJPY.
Не совсем красивый вид кривой баланса получился у валютной пары USDJPY при значении Percent = 234. Тем не менее, общая тенденция явно восходящая. К тому же, конечный результат в численном выражении вполне приемлем. Чистая прибыль 3118 долларов против максимальной просадки 1565 долларов. В данном случае прибыль достигается за счет превосходства прибыльных сделок в абсолютном выражении - 73.69 долларов средняя прибыльная и 23.76 средняя убыточная. При этом убыточных сделок выходит 72%, а прибыльных лишь 28%.
Подводя итог, отметим изначальное преимущество описанной системы - все сделки осуществляются по известной аксиоме "покупай дешевле, продавай дороже". Единственным минусом является риск открытия позиции на невероятно продолжительном тренде против движения. Панацеей от таких входов является четкое соблюдение правил управления капиталом, когда величины депозита хватает, чтобы выдержать неожиданные спурты рынка.
[B]Доработка стратегии для использования в AutoGraf 4.0
Изменение кода эксперта TradeInCorrection для работы стратегии в среде AutoGraf 4.0 привело к тому, что все входные параметры, кроме Lots, пришлось вынести во входные параметры самого AutoGraf. Так, значение цели сделки Percent теперь нужно указывать во входном параметре AT_1, значение минимального количества свечей в паттерне MinCandlesInPattern - в AT_2, а управление отображением паттерна ShowPatterns - в AT_3. Чтобы паттерн не отображался, в AT_3 необходимо указать 0, а для показа - любое положительное число. Управление объемом сделки осуществляется с панели инструментов AutoGraf.
Для запуска советника из-под AutoGraf 4.0 совершите такие шаги:
Воспользуйтесь ссылкой Файлы стратегий для Autograf 4.0 (http://www.forextrade.ru/media/Image/MQLabs/53_ag/AG_TradeInCorrection.zip) и полученный архив распакуйте в папку MT4\experts\libraries. Запустите AutoGraf. Для работы советника в ключе приведенных результатов тестирования (на примере GBPUSD) выставьте значения AT_1 = 27, AT_2 = 3, AT_3 = 1 (полное повторение результатов при этом не гарантируется). Выберите стратегию №3. Для этого передвиньте вверх значок So и среди названий стратегий найдите значок S3, который также потяните вверх. Запустите функцию автоматической торговли, передвинув значок AT в верхнее положение.
Использование полученного советника рекомендуется только в полуавтоматическом режиме под присмотром трейдера и после всестороннего изучения слабых и сильных сторон стратегии.
Советник TradeInCorrection (http://www.forextrade.ru/media/Image/MQLabs/53_ag/TradeInCorrection_Expert.mq4)
Скрипт TrendStatistic (http://www.forextrade.ru/media/Image/MQLabs/53_ag/TrendStatistic.mq4)
Файлы стратегии для AutoGraf 4.0 (http://www.forextrade.ru/media/Image/MQLabs/53_ag/AG_TradeInCorrection.zip)
Развернутые результаты тестирования эксперта (http://www.forextrade.ru/media/Image/MQLabs/53_ag/Test.zip)