PDA

View Full Version : Ступени рынка



Scriptong
21-12-2009, 21:26
Вряд ли можно встретить человека, которому ни разу в жизни не приходилось наблюдать за облаками. Не с какой-либо конкретной целью, а просто так, расслабленно лежа на спине, давая волю фантазии и освобождая мысли от проблем насущных. В таком состоянии хаотичное сплетение облаков различных форм и размеров становится как никогда четким и ясным. Без труда можно найти очертания животных, людей и всяческих предметов. Причем все это не стоит нам никаких усилий, будто кто-то со стороны подсунул книжку с картинками, да еще и сам перелистывает страницы.

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

С таким же успехом можно увидеть и совсем другие структуры. Например, всю рыночную чехарду легко представить как лестницы со ступенями. Вот цена забирается на ступеньку и какое-то время находится на ней, затем совершает следующий прыжок, чтобы добраться до еще более высокой ступеньки. И так до тех пор, пока не появляются ступени, ведущие вниз. Потом опять вверх и так до бесконечности, что в итоге выливается в ценовой график.

Чтобы избавиться от субъективности при нахождении ценового уровня очередной ступени и придать ступеням более четкое описание, прибегнем к довольно старому индикатору DSM. Он был разработан еще во времена платформы Meta Trader 3. В таком виде (на языке MQL2) этот индикатор и оказался у меня. Так как код его был прост, то перевод на MQL4 много времени не занял и теперь не DSM, а уже DSM_Correct, можно использовать в Meta Trader 4.

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

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

Графически эти правила представлены на рис. 1:
http://www.forextrade.ru/media/Image/MQLabs/46_ag/figure_1.gif
Рис. 1. - Ступени как индикатор направления рынка.

Рост или падение ступеней очень явно указывает на текущий тренд. О том, что тренд сложился, нам скажут две восходящие или две нисходящие ступени подряд. Хотя, это число можно варьировать, в зависимости от агрессивности торговли. В левой части рис. 1. синими цифрами показаны первые восходящие ступени. Как только стало возможным определить начало второй ступени, можно получить ценовой уровень для входа в рынок, а именно для покупки. Первый и последующие уровни показаны зелеными горизонтальными отрезками. Они соответствуют уровню предыдущих ступеней. То есть вход всегда должен быть хотя бы на одну ступень ниже текущей ступени и технически будет осуществлен лимитным ордером. Конечно, чем дальше этот уровень, тем лучше. Главное, не упустить основное движение ко времени срабатывания ордера.

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

Для выхода из открывшейся сделки будем использовать обратный сигнал. Алгоритм графически показан в правой части рис. 1. Нам нужно дождаться двух нисходящих ступеней подряд (также помечены синими цифрами) и установить ордер Sell Limit на уровне ступени 1 (помечен зеленым горизонтальным отрезком). На этом же уровне устанавливается цель текущей длинной позиции. На этот раз переносить уровень не потребовалось, так как цена сразу же захватила профит позиции Buy и ордер Sell Limit.

Дальнейший алгоритм торговли подобен описанному, повторяясь во всех деталях.

Перейдем к формализации алгоритма, результатом чего станет робот DSM_FootStep_Expert. Сначала оформим сигнальную часть:

//+-------------------------------------------------------------------------------------+
//| Расчет значений DSM и ценовых уровней входа |
//+-------------------------------------------------------------------------------------+
void GetSignal()
{
Signal = 0;
// - 1 - ========= Поиск TrendFootSteps ступеней подряд =================================
double DSMCur = MathRound(iMA(Symbol(), 0, MAperiod, iShift, MODE_EMA, AppliedPrice, 1)
/TickSet)*TickSet; // Ближайшее значение DSM
double DSMMin = DSMCur, DSMMax = DSMCur;
for (int i = 2; i < Bars && MathAbs(Signal) < TrendFootSteps; i++)
{
double DSMPrev = MathRound(iMA(Symbol(), 0, MAperiod, iShift, MODE_EMA, AppliedPrice,
i)/TickSet)*TickSet; // предыдущее значение DSM
if (DSMPrev - DSMCur >= Tick) // Предыдущее значение DSM больше текущего - падение
{
if (Signal > 0) // Сбрасываем значение Signal в нейтральное
Signal = 0;
Signal--;
}
if (DSMCur - DSMPrev >= Tick) // Предыдущее значение DSM меньше текущего - падение
{
if (Signal < 0) // Сбрасываем значение Signal в нейтральное
Signal = 0;
Signal++;
}
DSMCur = DSMPrev; // Предыдущее значение DSM становится текущим
DSMMin = MathMin(DSMCur, DSMMin); // Отслеживание минимального значения
DSMMax = MathMax(DSMCur, DSMMax); // Отслеживание максимального значения
}
// - 1 - == Окончание блока =============================================================

// - 2 - ========================== Расчет цены открытия ордера =========================
if (i == Bars) return;

if (Signal == TrendFootSteps)
Price = ND(DSMMax - BackFootSteps*TickSet + Spread); // Цена открытия длинной позиции

if (Signal == -TrendFootSteps)
Price = ND(DSMMin + BackFootSteps*TickSet); // Цена открытия короткой позиции
// - 2 - == Окончание блока =============================================================
}
Сердцем функции является первый блок. На каждом шаге цикла показания DSM на двух соседних свечах сравниваются друг с другом. Равенство значений не приводит ни к каким действиям и передает управление следующей итерации цикла.

Если же значение DSM на текущей (имеется в виду ближайшая сформированная свеча - №1, а предыдущая - №2) свече меньше, чем на предыдущей, то это свидетельствует о нисходящем движении, что находит отражение в уменьшении значения переменной Signal. И наоборот, если DSM на текущей свече больше предыдущего, то движение восходящее и значение Signal растет. Каждая смена направления движения приводит к обнулению Signal. Поэтому выполнение цикла продолжится до тех пор, пока не будет найдено двух ступеней подряд, идущих в одну сторону. Две ступени подряд или больше - это можно задать во входном параметре эксперта TrendFootSteps.

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

Обработка значений переменных Signal и Price происходит в сердце эксперта - функции start:

//+-------------------------------------------------------------------------------------+
//| Функция START эксперта |
//+-------------------------------------------------------------------------------------+
int start()
{
// - 1 - == Разрешено ли советнику работать? ===========================================
if (!Activate || FatalError) // Отключается работа советника, если функция
return(0); // init завершилась с ошибкой или имела место фатальная ошибка
// - 1 - == Окончание блока ============================================================

// - 2 - == Сбор информации об условиях торговли ========================================
Spread = ND(MarketInfo(Symbol(), MODE_SPREAD)*Point); // текущий спрэд
StopLevel = ND(MarketInfo(Symbol(), MODE_STOPLEVEL)*Point); // текущий уровень стопов
FreezeLevel = ND(MarketInfo(Symbol(), MODE_FREEZELEVEL)*Point); // Уровень заморозки
// - 2 - == Окончание блока ============================================================

// - 3 - ======== Обработка существующей позиции и контроль открытия нового бара ========
if (LastBar == Time[0])
return(0);
// - 3 - == Окончание блока ============================================================

// - 4 - ======================== Расчет сигнала и мониторинг своих ордеров =============
GetSignal();
OwnOrders();
// - 4 - == Окончание блока ============================================================

// - 5 - ============================== Установка ордеров ===============================
if (Signal == -TrendFootSteps) // Откладываем SELLLIMIT
if (!SellLimit())
return(0);

if (Signal == TrendFootSteps) // Откладываем BUYLIMIT
if (!BuyLimit())
return(0);
// - 5 - == Окончание блока ============================================================

LastBar = Time[0];

return(0);
}
Отличие от стандартных экспертов в приведенной функции start начинается с четвертого блока, где вместе собраны вызовы двух функций - GetSignal и OwnOrders. Нетрудно догадаться, что должностными обязанностями OwnOrders является мониторинг ордеров и позиций, которые были открыты экспертом.

Пятый, заключительный блок start, производит передачу управления функциям SellLimit или BuyLimit, в зависимости от значения переменной Signal. Если результат, который вернули эти функции, равен False, то выполнение функции start прерывается, что заставляет эксперт пройти весь алгоритм еще раз на следующем тике.

OwnOrders производит такие действия:

//+-------------------------------------------------------------------------------------+
//| Мониторинг своих ордеров |
//+-------------------------------------------------------------------------------------+
void OwnOrders()
{
// - 1 - =============== Нахождение своей сделки ========================================
BuyLimitTicket = -1; SellLimitTicket = -1;
BuyTicket = -1; SellTicket = -1;
for (int i = 0; i < OrdersTotal(); i++)
if (OrderSelect(i, SELECT_BY_POS))
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
// - 1 - =============== Окончание блока ================================================
{
// - 2 - =============== Обработка позиций ==============================================
if (OrderType() < 2)
{
LastDir = OrderType();
if (OrderType() == OP_BUY)
{
BuyTicket = OrderTicket();
BuyTP = OrderTakeProfit();
}
else
{
SellTicket = OrderTicket();
SellTP = OrderTakeProfit();
}
}
// - 2 - =============== Окончание блока ================================================
else
// - 3 - =============== Обработка ордеров ==============================================
if (OrderType() == OP_BUYLIMIT)
{
BuyLimitTicket = OrderTicket();
BLPrice = OrderOpenPrice();
}
else
if (OrderType() == OP_SELLLIMIT)
{
SellLimitTicket = OrderTicket();
SLPrice = OrderOpenPrice();
}
// - 3 - =============== Окончание блока ================================================
}
}
Когда первый блок приостановил свое выполнение на ордере или позиции, открытой экспертом, управление передается второму или третьему блоку, в зависимости от типа ордера.

Второй блок будет выпонен, если тип ордера 0 (позиция BUY) или 1 (позиция SELL). Здесь происходит сохранение тикета позиции в соответствующую переменную BuyTicket или SellTicket. Также сохраняется уровень профита позиции. Назначение переменной LastDir - указание типа последней выполненной сделки. Это позволяет контролировать очередность осуществления сделок при закрытии их по стопу или профиту. После длинной позиции обязательно должна следовать короткая, а после короткой - длинная.

Третий блок подобен второму, с тем отличием, что обрабатывает лимитные ордера, сохраняя тикеты ордеров и цены их открытия.

Установка ордеров производится в функциях BuyLimit и SellLimit:

//+-------------------------------------------------------------------------------------+
//| Установка ордера Buy Limit |
//+-------------------------------------------------------------------------------------+
bool BuyLimit()
{
// - 1 - =========== Изменение профита существующей короткой позиции ====================
if (SellTicket > 0 && MathAbs(SellTP - Price) >= Tick && Ask - Price > StopLevel)
{
if (WaitForTradeContext())
if (OrderSelect(SellTicket, SELECT_BY_TICKET))
OrderModify(SellTicket, 0, OrderStopLoss(), Price, 0);
return(False); // В любом случае вернем False, так как еще не все операции выполнены
}
// - 1 - =========== Окончание блока ====================================================
else
if (LastDir != 0) // Последняя позиция была не длинная
{
// - 2 - =========== В случае существования ордера SellLimit удаляем его ================
if (SellLimitTicket > 0)
{
DeleteOrder(SellLimitTicket);
return(False); // Снова вернем False, так как еще не все операции выполнены
}
// - 2 - =========================== Окончание блока ====================================
RefreshRates();
// - 3 - =========== Если существует Buy Limit, то просто изменим его параметры =========
if (BuyLimitTicket > 0)
{
if (MathAbs(BLPrice - Price) >= Tick)
{
double SL = ND(Price - StopLoss*Tick);
double TP = ND(Price + TakeProfit*Tick);
if (Ask - Price > StopLevel)
if (!OrderModify(BuyLimitTicket, Price, SL, TP, 0))
return(False);
}
}
// - 3 - =========================== Окончание блока ====================================
else
// - 4 - =========== Если нет позиций и ордеров, то устанавливаем Buy Limit =============
{
SL = ND(Price - StopLoss*Tick);
TP = ND(Price + TakeProfit*Tick);
if (OpenOrderCorrect(OP_BUYLIMIT, Price, SL, TP, False) != 0) //открытие позиции
return(False); //если не удалось открыть, то попытка переносится
}
}
// - 4 - =========================== Окончание блока ====================================
return(True); // Все операции выполнены
}
Установка ордера вроде бы простое дело, но перед его выполнением требуется совершить целый ряд проверок.

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

После прохождения первого блока все дальнейшие действия будут совершаться, только если последняя совершенная сделка была короткой или ее не было вовсе. В этом случае сначала выполняется блок 2. В нем устраивается охота на ордер Sell Limit. В случае обнаружения ордера (SellLimitTicket имеет положительное значение) вызывается функция DeleteOrder, которая и удаляет ордер.

Последнюю возможную ситуацию рассматривает блок 3. Это случай, когда уже существует ордер Buy Limit. При этом достаточно проверить соответствие цены его открытия значению Price. Если обнаруживается несоответствие, то производится изменение цены открытия вместе с уровнями стоп-приказа и профита.

Когда пройдены все проверки и не найдены позиция BUY или ордер Buy Limit, выполняется блок 4, который устанавливает ордер Buy Limit и сигнализирует о выполнении всех проверок возвратом значения True.

Все с точностью до наоборот происходит в функции SellLimit:

//+-------------------------------------------------------------------------------------+
//| Установка ордера Sell Limit |
//+-------------------------------------------------------------------------------------+
bool SellLimit()
{
// - 1 - =========== Изменение профита существующей длинной позиции =====================
if (BuyTicket > 0 && MathAbs(BuyTP - Price) >= Tick && Price - Bid > StopLevel)
{
if (WaitForTradeContext())
if (OrderSelect(BuyTicket, SELECT_BY_TICKET))
OrderModify(BuyTicket, 0, OrderStopLoss(), Price, 0);
return(False); // В любом случае вернем False, так как еще не все операции выполнены
}
// - 1 - =========== Окончание блока ====================================================
else
if (LastDir != 1) // Последняя позиция была не короткая
{
// - 2 - =========== В случае существования ордера BuyLimit удаляем его =================
if (BuyLimitTicket > 0)
{
DeleteOrder(BuyLimitTicket);
return(False); // Снова вернем False, так как еще не все операции выполнены
}
// - 2 - =========================== Окончание блока ====================================
RefreshRates();
// - 3 - =========== Если существует Sell Limit, то просто изменим его параметры ========
if (SellLimitTicket > 0)
{
if (MathAbs(SLPrice - Price) >= Tick) // Цена открытия Sell Limit не равна новой
{
double SL = ND(Price + StopLoss*Tick);
double TP = ND(Price - TakeProfit*Tick);
if (Price - Bid > StopLevel)
if (!OrderModify(SellLimitTicket, Price, SL, TP, 0))
return(False);
}
}
// - 3 - =========================== Окончание блока ====================================
else
// - 4 - =========== Если нет позиций и ордеров, то устанавливаем Sell Limit ============
{
SL = ND(Price + StopLoss*Tick);
TP = ND(Price - TakeProfit*Tick);
if (OpenOrderCorrect(OP_SELLLIMIT, Price, SL, TP, False) != 0)//открытие позиции
return(False); //если не удалось открыть, то попытка переносится
}
}
// - 4 - =========================== Окончание блока ====================================
return(True); // Все операции выполнены
}
Первый блок при нахождении позиции BUY изменяет ее уровень профита, а второй блок удаляет ордер BuyLimit, если позиция BUY так и не была открыта.

Третий блок обрабатывает ситуацию с наличием ордера Sell Limit, изменяя все три самых важных его параметра. А четвертый блок устанавливает Sell Limit, если до сих пор ордер не был установлен.

В итоге полученный эксперт обладает такими входными параметрами, при помощи которых пользователь может корректировать работу стратегии: TakeProfit = 2000 - уровень профита в пунктах. По умолчанию специально завышен, чтобы эксперт закрывал сделки по обратному сигналу. StopLoss = 2000 - уровень стоп-приказа в пунктах. По умолчанию специально завышен, чтобы эксперт закрывал сделки по обратному сигналу. Lots = 0.1 - фиксированный объем сделки. AppliedPrice = 0 - цена, по которой рассчитывается DSM. 0 - Close, 1 - Open, 2 - High, 3 - Low, 4 - Median Price, 5 - Typical Price, 6 - Weigthed Close. MAPeriod = 34 - период средней скользящей, на основании которой рассчитывается DSM. Setting = 1 - степень округления значения MA. При 0 не округляется. iShift = 0 - сдвиг значений DSM вправо по графику на указанное количество баров. BackFootSteps = 1 - количество ступеней, на которое отодвигается уровень входа назад после идентификации тренда. TrendFootSteps = 2 - количество ступеней подряд, которые сигнализируют о начале нового тренда.

Тестирование эксперта проводилось на данных текущего года и таймфрейме М15. Все значения входных параметров взяты по умолчанию (см. рис. 2 - 5):
http://www.forextrade.ru/media/Image/MQLabs/46_ag/EURUSD.gif
Рис. 2. - Результаты тестирования эксперта DSM_FootStep_Expert на валютной паре EURUSD.
http://www.forextrade.ru/media/Image/MQLabs/46_ag/USDCHF.gif
Рис. 3. - Результаты тестирования эксперта DSM_FootStep_Expert на валютной паре USDCHF.
http://www.forextrade.ru/media/Image/MQLabs/46_ag/GBPUSD.gif
Рис. 4. - Результаты тестирования эксперта DSM_FootStep_Expert на валютной паре GBPUSD.
http://www.forextrade.ru/media/Image/MQLabs/46_ag/USDJPY.gif
Рис. 5. - Результаты тестирования эксперта DSM_FootStep_Expert на валютной паре USDJPY.

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

Чистая прибыль по USDCHF составила 956 долларов, в то время как максимальная просадка достигла отметки 1560 долларов. Таким образом, фактор восстановления оказывается ниже единицы, что не входит в сферу наших интересов.

График валютной пары GBPUSD половину теста провел в положительной части баланса, но, в конце концов, дал чистую прибыль 307 долларов при максимальной просадке 2733. Вновь отличие фактора восстановления от нуля можно рассмотреть лишь под микроскопом.

А вот тестирование на валютной паре USDJPY дало более утешительные результаты. Чистая прибыль 2721 доллар, а максимальная просадка 835 долларов, что дает фактор восстановления более трех (3.26).

Вновь, даже не прибегая к оптимизации параметров, мы получили очень неплохие результаты. Проведение же оптимизации и ее результаты на этот раз оставим в качестве упражнения для читателей. Скажу лишь одно - эти результаты не менее потрясающие, чем последствия оптимизации эксперта в статье "Дивергенция. Миф или полезный инструмент?". (http://fxtrade.ru/mqlabs/15.12.2009-divergenciya-mif-ili-poleznyy-instrument)


Доработка эксперта для работы в среде AutoGraf 4.0.

Входные параметры эксперта MAperiod, Setting, iShift и AppliedPrice для AG было решено превратить в константы. В результате для изменения их значений потребуется производить корректировку непосредственно в коде эксперта. Наши "ступенчатые" параметры TrendFootSteps и BackFootSteps будут соответствовать параметрам дистанции Ds и шагу модификации St.

Для запуска стратегии необходимо проделать такие шаги:
Воспользоваться ссылкой Файлы стратегий для Autograf 4.0 и полученный архив распаковать в папку MT4\experts\libraries. Запустить AutoGraf. Для получения похожих результатов работы выставить объем открываемой позиции (Lots) 0.1, настроить значение Ds = 2, St = 1, SL = 2000, TP = 2000. Выбрать стратегию №3. Для этого передвинуть вверх значок So и среди названий стратегий найти S3 Запустить функцию автоматической торговли, передвинув значок AT в верхнее положение.
Советник DSM_FootStep_Expert (http://www.forextrade.ru/media/Image/MQLabs/46_ag/DSM_FootStep_Expert.mq4)
Индикатор DSM_Correct (http://www.forextrade.ru/media/Image/MQLabs/46_ag/DSM_Correct.mq4)
Файлы стратегий для Autograf 4.0 (http://www.forextrade.ru/media/Image/MQLabs/46_ag/AG_DSM_FootStep.zip)
Развернутые отчеты тестирования и оптимизации эксперта (http://www.forextrade.ru/media/Image/MQLabs/46_ag/Test.zip)

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