PDA

View Full Version : Эмуляция гэпа



Scriptong
06-02-2011, 14:44
При рассмотрении рыночных графиков нередко можно наблюдать ценовые разрывы - гэпы (от англ. "gap" - пролом, брешь, дыра). Гэпы наиболее характерны для фондовых и товарных рынков, которые функционируют не круглосуточно. В результате каждые сутки можно наблюдать за открытием и закрытием торгов. Так как между временем окончания торгов и временем их следующего начала проходит достаточно продолжительное время, то настроение участников рынка меняется, что приводит к отличию цены открытия следующих торгов от цены закрытия предыдущих.
На рынке Форекс, в виду его круглосуточной работы, наблюдать гэп доводится значительно реже. Наиболее часто это случается при переходе от пятницы к понедельнику. Тем не менее, можно заметить гэп и в течение торгового дня, в то время, когда выходят какие-нибудь важные новости.
Пристальный интерес участников рынка к гэпам вызван следующим свойством: рано или поздно цена заполняет гэп, т.к. их природа не терпит пустот. Ключевыми словами в приведенном выражении являются "рано или поздно", т.е. нельзя утверждать, что открытие сделки в направлении гэпа гарантирует получение прибыли. До заполнения гэпа цена вполне может совершить продолжительную прогулку в противоположном направлении, что приведет к элементарной нехватке средств для поддержания позиции. В таких случаях любители торговли на гэпах всегда должны помнить о непреложном законе рынка - устанавливать стоп-приказы, без которых торговля напоминает езду на автомобиле с неисправными тормозами.
Если рассматривать все рыночные движения через призму гэпов, то любое резкое изменение цены в течение торговой сессии можно отнести к своеобразному гэпу. Доказательством права на жизнь такого утверждения является тот факт, что после резких движений цены очень часто следует откат. Именно такой взгляд на ценовые скачки предложил некто Фоменко (Fomenko) - автор индикатора Val_Bands (см. рис. 1).

http://www.forextrade.ru/media/Image/MQLabs/100_ag/figure1.png
Рис. 1. Индикатор Val_Bands и интерпретация его сигналов.

Индикатор Val_Bands располагается в отдельном окне под графиком цен и представляет собой гистограмму, на которую нанесен индикатор Bollinger Bands. Для расчета средней линии Bollinger Bands используются значения гистограммы, которые, в свою очередь, являются отображением единичной волатильности каждой свечи. Признаком свечи, имитирующей появление гэпа, автор индикатора Val_Bands считает превышение гистограммой верхней линии индикатора Bollinger Bands. Дальнейшие действия необходимо производить такие же, какие были бы предприняты при фиксации гэпа, т.е. открытие сделки в направлении ценового разрыва. Если "гэп" был сформирован бычьей свечей, то необходимо открыть короткую сделку, а если "гэп" образован медвежьей свечей, то необходимо открыть длинную сделку. Целью любой сделки предлагается считать цену открытия предыдущей свечи, хотя в некоторых случаях профит можно располагать на противоположном экстремуме свечи (минимуме или максимуме). Как показано на рис. 1, из 17-ти совершенных сделок только 6 получились убыточными, 2 сделки не смогли достичь намеченной цели (были закрыты по противоположному сигналу), а 9 были закрыты по достижению профита (цена открытия сигнальной свечи). Заявленное соотношение явно выигрышное, что требует проверки на более длительном временном участке. Для этой цели потребуется разработка эксперта, выполняющего торговые действия по описанному принципу.
Название советника (GapEmulation - эмуляция гэпа) наиболее близко отражает принцип его работы. Советник имеет семь настроечных параметров:

Lots - фиксированный объем сделок
BandsPeriod - период усреднения средней линии Bollinger Bands
BandsDeviation - величина стандартного отклонения полос Боллинджера от средней линии
TargetPrice - цена, которая считается целью сделки. 1 - открытие предыдущей свечи, 2 - максимум предыдущей медвежьей свечи или минимум предыдущей бычьей свечи
SLtoTP - отношение размера стопа к профиту
OpenOrderSound - имя звукового файла, проигрывающегося при открытии очередной сделки
MagicNumber - уникальный идентификатор ордеров, открываемых экспертом

Принятие решения об открытии сделки в коде эксперта осуществляется при помощи функции GetSignal:

//+-------------------------------------------------------------------------------------+
//| Генерация сигналов открытия сделки |
//+-------------------------------------------------------------------------------------+
void GetSignal()
{
Signal = 0;
// - 1 - ==================== Расчет значений индикатора Val_Bands ======================

// - 1.1 - ==================== Расчет значений буфера Val_BarsBuffer ================
for (int i = BandsPeriod*2; i > 0; i--) // По барам с номерами до BansPeriod*2
Val_BarsBuffer[i] = (High[i] - Low[i])/Point;// Волатильность свечи в пунктах
// - 1.1 - =============================== Окончание блока ===========================

// - 1.2 - =================== Расчет значений буфера MovingBuffer ===================
for (i = BandsPeriod; i > 0; i--) // По барам с номерами до BandsPeriod
MovingBuffer[i] = iMAOnArray(Val_BarsBuffer, // Расчет среднего значения среди..
0, BandsPeriod, // ..последних BandsPeriod баров
0, MODE_SMA, i);
// - 1.2 - =============================== Окончание блока ===========================

// - 1.3 - ==== Расчет суммы квадратов отклонений от текущего значения средней =======
double sum = 0.0; // Начальное значение суммы - 0
int k = BandsPeriod; // Количество значений
double oldval = MovingBuffer[1]; // Текущее значение средней, от..
// ..которого считаются отклонения
while(k > 0) // Пока не достигнут бар №1
{
double newres = Val_BarsBuffer[k] - oldval; // Значение отклонения
sum += newres*newres; // Добавляется квадрат значения
k--; // Переход к следующему бару
}
// - 1.3 - =============================== Окончание блока ===========================

// - 1.4 - ==== Вычисление текущих значений верхней средней и гистограммы ============
double Up = oldval + BandsDeviation*MathSqrt(sum/BandsPeriod);//Верхняя средняя
double Hist = Val_BarsBuffer[1]; // Гистограмма
// - 1.4 - =============================== Окончание блока ===========================

// - 1 - ================================ Окончание блока ===============================

// - 2 - =============================== Генерация сигнала ==============================
if (Hist > Up) // Если гистограмма выше уровня..
{ // ..границы полос Боллинджера, то..
// ..имеется сигнал открытия сделки
if (Open[1] > Close[1]) // Бар медвежий, значит,..
{
Signal = 1; // ..имеем сигнал открытия Buy
if (TargetPrice == 1) // Цель сделки - цена открытия..
TP = Open[1]; // ..предыдущего бара или..
else
TP = High[1]; // ..максимум предыдущего бара
}
if (Open[1] < Close[1]) // Бар бычий, значит,..
{
Signal = -1; // ..имеем сигнал открытия Sell
if (TargetPrice == 1) // Цель сделки - цена открытия..
TP = Open[1]; // ..предыдущего бара или..
else
TP = Low[1]; // ..минимум предыдущего бара
}
}
// - 2 - ================================ Окончание блока ===============================
}
Непосредственно генерация сигнала производится во втором блоке функции, а расчет данных, используемых при принятии решения об открытии сделки, совершается в первом блоке.
Первый блок копирует алгоритм расчета значений индикатора Val_Bands за исключением расчета нижней линии, значение которой в рассматриваемой стратегии не учитывается. Метод воспроизведения алгоритма расчета индикатора является более выигрышным, по сравнению с вариантом использования вызова расчета при помощи функции iCustom. Во-первых, повышается быстродействие программы, а, во-вторых, это позволяет программе быть независимой от наличия индикатора Val_Bands в папке терминала.
Расчет производится следующим образом. Сначала во вложенном блоке 1.1 рассчитываются значения буфера Val_BarsBuffer. Это гистограмма индикатора. Буфер организован как таймсерия, т.е. наиболее новые элементы имеют меньший индекс, по сравнению со старыми. Размер буфера равен BandsPeriod*2 + 1 элементам. Первый элемент (индекс 0) не используется. Причина, по которой буфер содержит не BandsPeriod элементов, а в два раза больше, становится ясной при рассмотрении блока 1.2, в котором производится расчет буфера MovingBuffer - средней линии Bollinger Bands. Среднее значение рассчитывается для каждого элемента на основании предыдущих BandsPeriod элементов буфера Val_BarsBuffer. Таким образом, чтобы получить среднее значение элемента BandsPeriod необходимо располагать еще BandsPeriod элементами, находящимися слева от него. Поэтому размер Val_BarsBuffer превосходит размер MovingBuffer в два раза.
После расчета значений указанных буферов необходимо произвести расчет суммы квадратов отклонений предыдущих значений единичной волатильности от средней волатильности, что производится во вложенном блоке 1.3. Переменная newres получает значение отклонения одного значения от среднего, переменная sum - накапливает сумму квадратов значений newres, а k - переменная цикла - ведет учет количества элементов. Во вложенном блоке 1.4 для расчета верхней полосы индикатора Bollinger Bands используется значение переменной sum. Сумма квадратов отклонений становится среднеквадратичным отклонением, из которого вычисляется квадратный корень, а потом умножается на значение BandsDeviation. Полученная величина прибавляется к значению средней полосы Bollinger Bands.
Блок 2 функции GetSignal исполняется только в тех случаях, когда значение гистограммы Hist превышает значение верхней границы Bollinger Bands (переменная Up). Сигнал генерируется в зависимости от типа предшествующего бара, символизирующего собой гэп. Если бар бычий (открытие ниже закрытия), то сигнал будет медвежий (Signal = -1), а цель TP будет указывать на цену открытия бара или на его минимум. Если бар медвежий (открытие выше закрытия), то сигнал будет бычий (Signal = 1), а цель TP будет указывать на цену открытия бара или его максимум.
Использование значений переменных Signal и TP, которые были сформированы в теле функции GetSignal, производится в теле торговой функции Trade:

//+-------------------------------------------------------------------------------------+
//| Открытие позиций |
//+-------------------------------------------------------------------------------------+
bool Trade()
{
// - 1 - ======================= Сигнал открытия длинной сделки =========================
if (Signal > 0) // Необходимо открыть длинную
{
if (Type == OP_SELL) // Если имеется короткая сделка, то..
if (!CloseDeal(Ticket)) // ..закроем ее. При неудаче вернем..
return(false); // ..ошибку
RefreshRates(); // Обновляем значения Bid и Ask
double sl = Ask - SLtoTP*(TP - Ask); // Расчет уровня стопа, исходя из..
// ..размера профита
if (Type == OP_BUY) // Если имеется длинная сделка, то..
if (CheckLevels(Ticket, TP, sl)) // ..проверим правильность ее стопа..
return(true); // ..и профита. При удачной..
else // ..модификации вернем true, а при..
return(false); // ..неудачной - false
if (OpenOrderCorrect(OP_BUY, Lots, NP(Ask), // Открытие сделки Buy
NP(sl), NP(TP)) != 0)
return(false);
}
// - 1 - ================================== Окончание блока =============================

// - 2 - ======================= Сигнал открытия короткой сделки ========================
if (Signal < 0) // Необходимо открыть короткую
{
if (Type == OP_BUY) // Если имеется длинная сделка, то..
if (!CloseDeal(Ticket)) // ..закроем ее. При неудаче вернем..
return(false); // ..ошибку
RefreshRates(); // Обновляем значения Bid и Ask
TP += Spread; // К цели добавляем спрэд, т.к...
// ..короткие закрываются по Ask
sl = Bid + SLtoTP*(Bid - TP); // Расчет уровня стопа, исходя из..
// ..размера профита
if (Type == OP_SELL) // Если имеется короткая сделка, то..
if (CheckLevels(Ticket, TP, sl)) // ..проверим правильность ее стопа..
return(true); // ..и профита. При удачной..
else // ..модификации вернем true, а при..
return(false); // ..неудачной - false
if (OpenOrderCorrect(OP_SELL, Lots, NP(Bid), // Открытие сделки Sell
NP(sl), NP(TP)) != 0)
return(false);
}
// - 2 - ============================= Окончание блока ==================================

return(True); // Все операции завершены успешно
}
В зависимости от значения переменной Signal выполняется один из блоков функции. При положительном значении переменной будет произведена попытка открытия длинной сделки, а при отрицательном - короткой.
Первое действие первого блока - это проверка существования короткой сделки. Осуществляется проверка при помощи сравнения значения переменной Type со значением именованной константы OP_SELL. Формирование значения переменной Type производится в теле функции FindOrders (взята из советника ToCatchAPriceExtremum, статья "Поймать ценовой экстремум" (http://www.forextrade.ru/mqlabs/30.01.2011-mqlabs-poymat-cenovoy-ekstremum)), которая вызывается перед обращением к функции Trade. Переменная Type содержит тип имеющегося ордера, который был открыт экспертом. Наряду с Type формируется значение переменной Ticket, в которой хранится тикет найденного ордера. Отрицательные значения переменных свидетельствуют об отсутствии ордеров, открытых экспертом.
Если значение Type равно OP_SELL, что свидетельствует о наличии короткой сделки, то перед открытием длинной сделки необходимо произвести закрытие короткой. Закрытие совершается при помощи вызова функции CloseDeal (взята из упомянутого выше советника ToCatchAPriceExtremum). При успешном закрытии, выполнение функции Trade продолжается, а при неудачном закрытии - прерывается с ошибкой. При продолжении выполнения функции производится расчет уровня стоп-приказа будущей сделки с участием значения входного параметра советника SLtoTP. После расчета стопа проверяется существование длинной сделки. Если таковая найдена, то вызывается функция CheckLevels (представлена ниже), при помощи которой проверяется соответствие стоп-приказа и профита текущей сделки новым значениям. При любом исходе функции CheckLevels выполнение функции Trade прерывается. Разница лишь в результате - с ошибкой или без нее. Если ни длинная, ни короткая сделки не обнаружены, то совершается открытие длинной сделки с заранее рассчитанными значениями стоп-приказа и профита.
Подобный алгоритм действий реализован во втором блоке. При обнаружении длинной сделки производится попытка ее закрытия. При обнаружении короткой сделки совершается проверка соответствия уровней ее стоп-приказа и профита новым значениям. После проверки выполнение функции Trade заканчивается. В случае если не было найдено открытых советником сделок, то выполнение продолжается совершением операции открытия короткой сделки.
Функция CheckLevels располагает таким кодом:

//+-------------------------------------------------------------------------------------+
//| Проверка правильности уровней профита и стоп-приказа |
//+-------------------------------------------------------------------------------------+
bool CheckLevels(int ticket, double tp, double sl)
{
if (OrderSelect(ticket, SELECT_BY_TICKET) && // Существует ордер с заданным..
OrderCloseTime() == 0) // ..тикетом и ордер не закрыт
if (WaitForTradeContext()) // Свободен ли торговый поток?
{
RefreshRates();
// - 1 - ========================= Нуждаются ли уровни в изменении? =====================
double newtp = 0, newsl = 0; // 0 - уровни менять не нужно

// - 1.1 - ================== Проверка нового уровня профита ===================
if ((OrderType() == OP_BUY && // Если сделка длинная, то проверяется
tp - Bid > StopLevel) || // ..расстояние нового tp от Bid
(OrderType() == OP_SELL && // Если сделка короткая, то..
Ask - tp > StopLevel) && // ..проверяется расстояние от нового..
// ..tp до Ask
MathAbs(tp - OrderTakeProfit()) >= Tick) // Новый tp не равен старому
newtp = NP(tp); // Если условия выполнены, то профит..
// ..принимает новое значение
// - 1.1 - ===================== Окончание блока ===============================

// - 1.2 - ================== Проверка нового уровня стопа =====================
if ((OrderType() == OP_BUY && // Если сделка длинная, то проверяется
Bid - sl > StopLevel) || // ..расстояние от нового sl до Bid
(OrderType() == OP_SELL && // Если сделка короткая, то..
sl - Ask > StopLevel) && // ..проверяется расстояние от нового
// ..sl до Ask
MathAbs(sl - OrderStopLoss()) >= Tick) // Новый sl не равен старому
newsl = NP(sl); // Если условия выполнены, то стоп..
// ..принимает новое значение
// - 1.2 - ===================== Окончание блока ===============================
// - 1 - ================================== Окончание блока =============================

// - 2 - ============================ Модификация ордера ================================
if (newtp != 0 || newsl != 0) // Если один из уровней нуждается в..
{ // ..изменении, то производится..
// ..модификация
if (newsl == 0) // Если стоп не нужно менять, то..
newsl = OrderStopLoss(); // ..подставляем старое значение стопа
if (newtp == 0) // Если профит не нужно менять, то..
newtp = OrderTakeProfit(); // ..подставляем старое значение
if (!OrderModify(ticket, 0, newsl, newtp, 0))// Модификация уровней
return(false); // Если неудачное изменение, то вернем
} // ..ошибку
// - 2 - ================================== Окончание блока =============================
}
return(true);
}
В начале функции по переданному ей тикету ticket выбирается необходимый ордер и проверяется время его закрытия. Проверка требуется для того, чтобы можно было убедиться в наличии ордера в списке открытых ордеров, а не списке "История счета". Затем при помощи вызова функции WaitForTradeContext ожидается освобождение торгового потока. Следующие действия в случае свободности торгового потока производятся в блоках 1 и 2.
В коде блока 1 выясняется необходимость изменения одного или обоих уровней (стоп-приказа и профита) ордера. Во вложенном блоке 1.1 проверяется необходимость изменения профита, а в блоке 1.2 - стоп-приказа. Признаком необходимости изменения уровня профита является отличное от нуля значение переменной newtp, а уровня стоп-приказа - newsl. Перед осуществлением модификации новые уровни проходят проверку возможности изменения, а именно: проверку близости к текущей цене.
Код блока 2 исполняется, если одна из переменных newsl или newtp не содержит значение нуль. В том случае, если требуется изменить только один из двух уровней (т.е. одна из переменных содержит нуль), переменной с нулевым значением присваивается значение имеющегося уровня. После этого вызывается функция модификации ордера OrderModify.
Результатом выполнения функции является true (успешное выполнение), если модификация прошла успешно или уровни ордера не нуждаются в изменении. Результат false (неудачное выполнение) возвращается в тех случаях, когда произошла ошибка при попытке изменения уровней ордера.


Тестирование советника
Тестирование советника GapEmulation проведем с использованием таймфрейма Н1 на историческом диапазоне 01.01.2009 - 29.01.2011. Из семи настроечных параметров эксперта три будут иметь различные значения для каждого инструмента. Это BandsPeriod, BandsDeviation и SLtoTP. Остальные четыре параметра (Lots, TargetPrice, OpenOrderSound и MagicNumber) примут значения, используемые по умолчанию. Результаты тестирования показаны на рис. 2-5.

EURUSD. Значения настроечных параметров: BandsPeriod = 8, BandsDeviation = 2.4, SLtoTP = 1.7. Кривая баланса очень непостоянна. Также стоит заметить, что до положительной области кривая добралась лишь после двух третей тестирования. Чистая прибыль 612 долларов, максимальная просадка 1 438 долларов. Как видим, результаты говорят сами за себя.

http://www.forextrade.ru/media/Image/MQLabs/100_ag/EURUSD.gif
Рис. 2. Результаты тестирования эксперта GapEmulation на валютной паре EURUSD.

USDCHF. Значения настроечных параметров: BandsPeriod = 97, BandsDeviation = 2, SLtoTP = 4.9. Кривая баланса более стабильна, чем в предыдущем случае, но смущает малое количество сделок - 167. Тем не менее, даже на основании такого количества сделок можно делать некоторые выводы. Чистая прибыль 1 552 доллара, максимальная просадка 501 доллар, фактор восстановления 3.1. Достаточно неплохой результат.

http://www.forextrade.ru/media/Image/MQLabs/100_ag/USDCHF.gif
Рис. 3. Результаты тестирования эксперта GapEmulation на валютной паре USDCHF.

GBPUSD. Значения настроечных параметров: BandsPeriod = 61, BandsDeviation = 2.8, SLtoTP = 4.7. Кривая баланса в первой половине тестирования выглядит очень нестабильно, во второй - получше, но все равно далеко до идеала. То же самое показывает статистика: чистая прибыль 1 548 долларов с точно такой же максимальной просадкой, т.е. фактор восстановления равен ровно 1. Для фунта это очень низкий показатель.

http://www.forextrade.ru/media/Image/MQLabs/100_ag/GBPUSD.gif
Рис. 4. Результаты тестирования эксперта GapEmulation на валютной паре GBPUSD.

USDJPY. Значения настроечных параметров: BandsPeriod = 30, BandsDeviation = 2.9, SLtoTP = 2.1. Вид кривой баланса немного уступает по стабильности виду, полученному после тестирования советника на валютной паре USDCHF, но здесь срабатывает фактор количества сделок - их больше на треть, что вызывает большее доверие. Чистая прибыль 2 530 долларов при максимальной просадке 746 долларов. Фактор восстановления 3.39, также больше, чем на валютной паре USDCHF.

http://www.forextrade.ru/media/Image/MQLabs/100_ag/USDJPY.gif
Рис. 5. Результаты тестирования эксперта GapEmulation на валютной паре USDJPY.

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


Доработка стратегии для использования в AutoGraf 4.0
Из семи входных параметров советника GapEmulation только четыре нуждаются в преобразовании в настроечные параметры приложения AutoGraf 4.0. Параметр Lots можно настраивать во время выполнения стратегии при помощи панели настроек (нижняя часть графика, параметр Lot). Параметры OpenOrderSound и MagicNumber в стратегии, работающей в среде AutoGraf, недоступны пользователю для изменения. Их можно менять только путем редактирования исходного кода программы. Соответствие настраиваемых параметров выглядит следующим образом: BandsPeriod - AT_1, BandsDeviation - AT_2, TargetPrice - AT_3, SLtoTP - AT_4.
Запуск стратегии "Эмуляция гэпа" в среде AutoGraf 4.0 состоит из следующих шагов:

Получить файл по ссылке Файлы стратегий для AutoGraf 4.0 (http://www.forextrade.ru/media/Image/MQLabs/100_ag/AG_GapEmulation.zip) и распаковать полученный архив в папку MT4\experts\libraries (с перезаписью файлов AG_AT.ex4 и AG_AT.mq4).
Запустить AutoGraf (прикрепить индикатор AG_ind, а затем эксперт AG_exp).
Для работы стратегии в ключе приведенных результатов в окне настроек AutoGraf (закладка "Входные параметры") установить нужные значения параметров AT_1 - AT_4. Полное повторение результатов при этом не гарантируется.
Выбрать стратегию №5. Для этого необходимо передвинуть вверх значок So и среди названий стратегий найти значок S5, который также потянуть вверх.
Запустить функцию автоматической торговли, передвинув значок AT в верхнее положение.


Индикатор Val_Bands (http://www.forextrade.ru/media/Image/MQLabs/100_ag/Val_Bands.mq4)
Советник GapEmulation (http://www.forextrade.ru/media/Image/MQLabs/100_ag/GapEmulation_Expert.mq4)
Файлы стратегий для AutoGraf 4.0 (http://www.forextrade.ru/media/Image/MQLabs/100_ag/AG_GapEmulation.zip)
Развернутые результаты тестирования эксперта (http://www.forextrade.ru/media/Image/MQLabs/100_ag/Test.zip)

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