Scriptong
26-11-2009, 20:56
В книге Александра Элдера "Как играть и выигрывать на бирже" была предложена очень простая и по убеждению автора эффективная стратегия торговли. Буквально она описана так: "Проведите в любом месте графика горизонтальную линию и торгуйте от нее". Поначалу подобное предложение ничего, кроме улыбки, не вызывает. Но это только поначалу и без подключения "серого вещества".
На самом деле, если подойти к этому вопросу конструктивно, можно заметить, что очень часто цена останавливается при достижении определенных ценовых уровней, а при пробитии других уровней с легкостью продолжает движение. Попробуем же проверить это наблюдение на истории и подобрать "правильные" ценовые уровни.
Сначала нам потребуется индикатор, чтобы можно было визуально оценить расположение необходимых уровней. Название индикатора емко описывает его суть - AutoLevels. Входных параметров у него всего лишь три - StartPoints, StepPoints и LevelColor:
StartPoints задает начальное значение уровня в пределах фигуры (100 пунктов), от которого будут отсчитываться последующие уровни.
StepPoints - количество пунктов, которое будет прибавляться к текущему значению ценового уровня для вычисления следующего.
LevelColor - цвет горизонтальных линий, обозначающих ценовой уровень.
В начале работы индикатор вычисляет минимальное и максимальное значение цены за весь доступный исторический период. Затем найденное минимальное значение приводится к ближайшему значению StartPoints. Например, минимальная котировка валютной пары EURUSD 0.8227. В результате, границы фигуры получаются 0.82 - 0.83. В этом диапазоне нужно найти начальное значение. При значении StartPoints, равного 40, первый ценовой уровень будет определен как 0.8240. Для нахождения следующего значения к 0.8240 необходимо прибавить значение StepPoints. Если оно равно 50 пунктам, то следующий уровень окажется равным цене 0.8290. По такому алгоритму уровни будут рассчитываться, пока не будет достигнуто максимальное значение цены на текущей валютной паре.
Необходимо заметить, что если значение StepPoints не будет без остатка укладываться в одну фигуру, то повторения уровней в пределах каждой фигуры не выйдет, так как отсчет производится от минимального найденного значения цены. Например, при StartPoints = 30 и StepPoints = 70 получим такие ценовые уровни, начиная от минимального: 0.8230, 0.8300, 0.8370, 0.8440, 0.8510, 0.8580, 0.8650, 0.8730 и т. д. Таким образом, в пределах каждой новой фигуры будем получать новое значение уровня. Но это еще полбеды. Основная проблема будет заключаться в получении разных ценовых уровней при выборе различных точек отсчета (минимального значения). Поэтому значение StepPoints нужно всегда выбирать кратным 100 пунктам. Таких значений оказывается не очень то и много: 1, 2, 4, 5, 10, 20, 25, 50 и 100.
На рис. 1 показан общий вид индикатора AutoLevels со значениями StartPoints = 0 и StepPoints = 50, а также схематично представлена стратегия торговли по ценовым уровням.
http://www.forextrade.ru/media/Image/MQLabs/42_ag/figure_1.gif
Рис. 1. - Принципы открытия сделок по индикатору AutoLevels.
Красной стрелкой, указывающей вниз, показано открытие короткой сделки. Это происходит после пробития ценой уровня 1.4550 сверху вниз. Почему именно 1.4550, а не более ранние пробития 1.4600 и 1.4650? Дело в том, что перед совершением первой сделки необходимо найти два ближайших уровня для совершения длинных и коротких сделок. Эти уровни всегда находятся на расстоянии в три ценовых уровня друг от друга, в нашем случае это 150 пунктов. После пробития одного из них происходит перемещение своеобразного канала вверх или вниз. Поэтому перед стартом достаточно найти два ближайших однонаправленных пробития уровней ценой. Следующий по ходу цены ценовой уровень будет являться сигнальным.
Итак, мы определили, что произошло два пробития уровней (1.4650 и 1.4600) подряд. В результате уровень для открытия длинных сделок (назовем его BuyLevel) будет 1.4700, а для коротких (SellLevel) - 1.4550. Как только цена пробивает 1.4550 сверху вниз, необходимо отложить ордер Sell Limit в 25 пунктах (половина расстояния между соседними уровнями) от пробитого уровня в расчете на откат цены. Чаще всего такой откат происходит и ордер открывается.
В результате пробития нового уровня вниз, уровни BuyLevel и SellLevel также смещаются вниз на 50 пунктов (1.4650 и 1.4500 соответственно). SellLevel становится целевым для короткой сделки, а BuyLevel - защитным, на него устанавливается стоп-приказ. В случае, если бы цена после пробития 1.4550 дошла до 1.4500 без отката, не захватив ордер Sell Limit, то нужно было бы переместить Sell Limit на 50 пунктов ниже и так до тех пор, пока ордер не сработает.
В приведенной же ситуации все складывается наилучшим образом - ордер захватывается рынком на откате, после чего цена достигает уровня 1.4500, где срабатывает профит. К тому же, пробитие очередного уровня вниз приводит к смещению BuyLevel и SellLevel на 50 пунктов ниже. Поэтому, когда цена разворачивается и пробивает уровень 1.4600 снизу вверх, необходимо отложить ордер Buy Limit и, также как в случае с Sell Limit, на 25 пунктов ниже пробитого уровня. BuyLevel смещается вверх к 1.4650, а SellLevel следует за ним на расстоянии 150 пунктов - 1.4500. И вновь рынок производит откат на половину значения StepPoints, захватывая Buy Limit. Дальше все вновь происходит без форс-мажоров, и цена достигает намеченной цели 1.4650, приводя BuyLevel и SellLevel к значениям 1.4700 и 1.4550 соответственно.
Ограничением стратегии является отсутствие реакции на последующее пробитие уровней BuyLevel до тех пор, пока не будет достигнут SellLevel. Это, конечно, минус, когда идет мощный тренд, но большой плюс, когда цена долго колеблется в пределах выбранных нами 150 пунктов.
Несмотря на то, что в основе эксперта будет лежать ряд часто используемых нами функций, реализацию начнем не с сигнальной части, а с функции инициализации, так как для корректной работы сигнальной части потребуются некоторые данные.
Всю функцию приводить не имеет смысла. Поэтому ограничимся лишь новшествами:
// - 3 - ==================== Расчет новых предельных значений ==========================
StartLevel = MathFloor(Low[iLowest(Symbol(), 0, MODE_LOW, Bars)]/Point/100)*100 +
StartPoints; // минимальное значение в соответствии с сеткой
FinishLevel = High[iHighest(Symbol(), 0, MODE_HIGH, Bars)]/Point; // максимальное
// - 3 - ==================== Окончание блока ===========================================
// - 4 - ==================== Поиск уровней покупки и продажи ===========================
BuyLimitPrice = 0;
SellLimitPrice = 0;
GetLevels(); // Расчет ближайших значений сетки - верхнего и нижнего
int i = 0;
SellLevel = DnLevel; // нижний непробитый уровень
BuyLevel = UpLevel; // верхний непробитый уровень
// - 4.1 - ======== Поиск пересечения двух уровней подряд в одном направлении ========
while (iHigh(Symbol(), PERIOD_M1, i) < UpLevel+StepPoints*Point &&
iLow(Symbol(), PERIOD_M1, i) > DnLevel-StepPoints*Point &&
i < iBars(Symbol(), PERIOD_M1))
{
if (ND(iHigh(Symbol(), PERIOD_M1, i) - BuyLevel) >= 0)
BuyLevel += StepPoints*Point;
else
if (ND(SellLevel - iLow(Symbol(), PERIOD_M1, i)) >= 0)
SellLevel -= StepPoints*Point;
i++;
}
// - 4.1 - ======================== Окончание блока ==================================
// - 4.2 - ======== При нехватке истории продолжать работу не имеет смысла ===========
if (i == iBars(Symbol(), PERIOD_M1))
{
Comment("Недостаточная глубина истории М1 для работы советника.");
Print("Недостаточная глубина истории М1 для работы советника.");
return(0);
}
// - 4.2 - ======================== Окончание блока ==================================
if (SellLevel == DnLevel)
BuyLevel = SellLevel+3*StepPoints*Point;
if (BuyLevel == UpLevel)
SellLevel = BuyLevel-3*StepPoints*Point;
// - 4 - ==================== Окончание блока ===========================================
// - 5 - =============== Нахождение своей сделки в истории ==============================
TypeOrder = -1;
datetime LastTime = 0;
for (i = 0; i < OrdersHistoryTotal(); i++)
if (OrderSelect(i, SELECT_BY_POS))
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
if (OrderCloseTime() > LastTime)// находим сделку с наибольшим временем закрытия
{
LastTime = OrderCloseTime();
TypeOrder = OrderType();
}
// - 5 - =============== Окончание блока ================================================
// - 6 - =============== Нахождение своей сделки среди открытых =========================
OwnOrder = -1;
for (i = 0; i < OrdersTotal(); i++)
if (OrderSelect(i, SELECT_BY_POS))
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
OwnOrder = OrderTicket(); // тикет своей сделки
TypeOrder = OrderType(); // тип - лимитный ордер или уже позиция
PriceOrder = OrderOpenPrice(); // цена открытия сделки
}
// - 6 - =============== Окончание блока ================================================
В блоке 3, подобно индикатору AutoLevels, рассчитываются минимальное и максимальное исторические значения с приведением минимального значения к ближайшему уровню, заданного параметром StartPoints.
На основании этих значений в начале четвертого блока рассчитываются ближайшие к текущей цене Bid верхний и нижний уровни. Производится это при помощи функции GetLevels, скромный код которой будет приведен чуть ниже. Результатом вызова GetLevels являются значения переменных UpLevel (верхний уровень) и DnLevel (нижний уровень). На начальном этапе эти значения присваиваются переменным BuyLevel и SellLevel соответственно.
Подблок четвертого блока - 4.1 - выполняет поиск двух пересечений уровней подряд в одном направлении. Алгоритм этого поиска основан на том, что два пересечения подряд - это достижение уровня выше UpLevel или ниже DnLevel. Поэтому цикл while будет выполняться, пока один из внешних уровней не будет достигнут ценой или пока не закончится доступная история котировок. Исходя из того, что для более высокой точности расчетов используются данные таймфрейма М1, истории в некоторых случаях хватать не будет. При использовании данных более высокого таймфрейма повышается вероятность пересечения двух уровней одной свечей, что приведет к неоднозначному окончанию расчетов. Достижение конца истории как раз проверяется в подблоке 4.2.
В конце четвертого блока проверяется равенство разницы между BuyLevel и SellLevel трем расстояниям между соседними уровнями. Если разница меньше, то значение одного из уровней исправляется.
Пятый и шестой блоки занимаются восстановлением работоспособности эксперта после перерыва в работе (переключение таймфрейма, выключение эксперта или терминала). Наиболее необходимыми параметрами являются:
TypeOrder - тип текущей или последней закрытой сделки
OwnOrder - номер тикета текущей сделки
PriceOrder - цена открытия текущей сделки
Если текущей открытой сделки нет, то в любом случае необходимо иметь данные по последней закрытой сделке. Этим и занимается блок 5, находя в истории счета последнюю закрытую сделку. Вне зависимости от результата поисков, шестой блок перезаписывает значение всех переменных, ответственных за характеристики сделки, если среди открытых позиций и отложенных ордеров находится ордер, открытый экспертом.
Дальше, как уже упоминалось выше, рассмотрим простую функцию GetLevels:
//+-------------------------------------------------------------------------------------+
//| Расчет двух ближайших уровней по текущей цене |
//+-------------------------------------------------------------------------------------+
void GetLevels()
{
DnLevel = NP((MathFloor((MathFloor(Bid/Point) - StartLevel)/StepPoints)*StepPoints +
StartLevel)*Point); // ближайший уровень снизу
UpLevel = NP(DnLevel + StepPoints*Point); // ближайший уровень сверху
}
Текущий нижний уровень находится умножением StepPoints на целый остаток от деления разницы между Bid и минимальным уровнем StartLevel на StepPoints. К полученному результату прибавляется начальное значение сетки StartLevel. Расчет верхнего уровня после расчета нижнего становится и вовсе простым - прибавляем шаг сетки StepPoints.
На данном этапе мы всего лишь подготовили начальные данные, но ведь с течением времени BuyLevel и SellLevel должны изменяться при пробитии одного из уровней. Отслеживание таких пробитий будет происходить в теле функции 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); // текущий уровень стопов
// - 2 - == Окончание блока ============================================================
// - 3 - =================== Расчет новых значений уровней покупки и продажи ============
if (ND(Bid - BuyLevel) >= 0)
{
BuyLimitPrice = NP(BuyLevel - Offset*Point + Spread); // Цена установки BuyLimit
SellLimitPrice = 0; // SellLimit ставить не нужно
BuyLevel += StepPoints*Point;
SellLevel = BuyLevel - 3*StepPoints*Point;
}
if (ND(SellLevel - Bid) >= 0)
{
SellLimitPrice = NP(SellLevel + Offset*Point); // Цена установки SellLimit
BuyLimitPrice = 0; // BuyLimit ставить не нужно
SellLevel -= StepPoints*Point;
BuyLevel = SellLevel + 3*StepPoints*Point;
}
// - 3 - ==================== Окончание блока ===========================================
// - 4 - ==================== Принятие решений об установке ордеров =====================
Transactions();
// - 4 - ===================================== Окончание блока =========================
return(0);
}
Первый и второй блоки являются стандартными. Поэтому не комментируем.
Отслеживание пересечений происходит в третьем блоке. При пересечении уровня BuyLevel переменной BuyLimitPrice присваивается значение цены открытия будущего ордера Buy Limit. Для этого из значения BuyLevel вычитается Offset (один из входных параметров эксперта, задающий глубину отката) пунктов и прибавляется спрэд, так как все длинные позиции открываются по цене Ask. В то же время цена открытия ордера Sell Limit сбрасывается в ноль - переменной SellLimitPrice присваивается 0. И только после этого значения BuyLevel и SellLevel увеличиваются на StepPoints пунктов. То же самое, но в немного другом направлении, происходит при пересечении уровня SellLevel.
Заканчивает функцию start вызов Transactions, где и производится установка отложенных ордеров BuyLimit и SellLimit:
//+-------------------------------------------------------------------------------------+
//| Принятие решений об установке ордеров |
//+-------------------------------------------------------------------------------------+
void Transactions()
{
// - 1 - ==================== Существует ли еще свой ордер? =============================
if (OwnOrder > 0)
if (OrderSelect(OwnOrder, SELECT_BY_TICKET))
if (OrderCloseTime() != 0)
OwnOrder = -1;
else
TypeOrder = OrderType();
// - 1 - ==================== Окончание блока ===========================================
// - 2 - ==================== Сигнал открытия ордера Buy Limit ==========================
if (BuyLimitPrice != 0)
{
if (OwnOrder > 0 && TypeOrder == OP_BUYLIMIT) // Если ордер Buy Limit уже установлен
if (MathAbs(BuyLimitPrice - PriceOrder) >= Tick) // то проверяем его цену открытия
if (WaitForTradeContext())
if (!OrderModify(OwnOrder, NP(BuyLimitPrice), NP(SellLevel), // и если цена
NP(BuyLevel), 0)) // открытия не та, то модифицируем ордер
{
Print("Попытка модификации ордера BuyLimit сорвалась. Ошибка №",
GetLastError());
return;
}
if (OwnOrder < 0 && TypeOrder != OP_BUY)//Если ордера нет и последняя сделка была не
if (OpenOrderCorrect(OP_BUYLIMIT, NP(BuyLimitPrice), NP(SellLevel), // BUY, то
NP(BuyLevel)) != 0) // устанавливаем ордер
return;
BuyLimitPrice = 0; // при успешной установке сбрасываем флаг
}
// - 2 - ==================== Окончание блока ===========================================
// - 3 - =================== Сигнал открытия ордера Sell Limit ==========================
if (SellLimitPrice != 0)
{
if (OwnOrder > 0 && TypeOrder == OP_SELLLIMIT) // Если Sell Limit уже установлен
if (MathAbs(SellLimitPrice - PriceOrder) >= Tick)// то проверяем его цену открытия
if (WaitForTradeContext())
if (!OrderModify(OwnOrder, NP(SellLimitPrice), NP(BuyLevel+Spread), // и если
NP(SellLevel+Spread), 0))// цена не та, то модифицируем ордер
{
Print("Попытка модификации ордера SellLimit сорвалась. Ошибка №",
GetLastError());
return;
}
if (OwnOrder < 0 && TypeOrder != OP_SELL) // Если ордера нет и последняя сделка не
if (OpenOrderCorrect(OP_SELLLIMIT, NP(SellLimitPrice), NP(BuyLevel+Spread),//SELL,
NP(SellLevel+Spread)) != 0) // то устанавливаем ордер
return;
SellLimitPrice = 0; // при успешной установке сбрасываем флаг
}
// - 3 - ==================== Окончание блока ===========================================
}
Первый блок занимается мониторингом единственной используемой экспертом сделки. Для этого достаточно проверить существование тикета OwnOrder. Если ордер с таким тикетом существует и время его закрытия равно нулю, то позиция или ордер находится среди открытых позиций. В этом случае значение TypeOrder изменяется в соответствии с типом открытого ордера. Это нужно для контроля срабатывания лимитного ордера. Если же время закрытия ордера не равно нулю, то ордер был закрыт и поэтому переменной OwnOrder присваивается значение -1.
Второй блок выполняется при активном сигнале открытия ордера BuyLimit. В качестве сигнала используется ненулевое значение переменной BuyLimitPrice. Здесь возможно всего два варианта: ордер Buy Limit уже существует или вообще нет ордеров и позиций. В первом случае проверяется цена открытия ордера. Если она не равна значению BuyLimitPrice, то значения цены открытия, стоп-приказа и профита модифицируются, что приводит к перемещению ордера. Во втором случае просто открывается отложенный ордер. При успешной обработке обоих вариантов переменной BuyLimitPrice присваивается ноль, что приводит к дезактивации сигнала открытия.
По схожему алгоритму работает третий блок. При ненулевом значении SellLimitPrice проверяется существование ордера Sell Limit и, при необходимости, изменяются его параметры. Если же ордер не существует, то производится установка отложенного ордера Sell Limit.
После детального описания функций, не входящих во все разрабатываемые нами советники, приступаем к тестированию стратегии. Выбор таймфрейма в данном случае очевиден - М1. Этот таймфрейм способствует наиболее точному моделированию тиков, в то время как все более высокие таймфреймы приводят к небольшим неточностям. Это замечание касается только режима тестирования. Использование же эксперта онлайн возможно абсолютно на любом таймфрейме, так как для расчетов используются лишь значения цены, параметры свечей в расчетах не участвуют. По причине использования наименьшего таймфрейма качество моделирования тиков во всех результатах тестов равно 25%. Большего на М1 достичь невозможно.
В качестве исторического периода выберем 2009-й год 01.01.2009 - 21.11.2009. Входные параметры: StartPoints = 0 (граница фигуры), StepPoints = 50 (в фигуре будет находится два уровня - 0 и 50), Offset = 25 (откат равен половине сетки). Результаты приведены на рис. 2-5:
http://www.forextrade.ru/media/Image/MQLabs/42_ag/EURUSD.gif
Рис. 2. - Результаты тестирования эксперта AutoLevels_Expert на валютной паре EURUSD.
При первом же тесте мы получили положительный результат, что не может не радовать. Чистая прибыль 1309 долларов, а максимальная просадка всего лишь 658 долларов. В итоге получаем фактор восстановления почти 2 - 1.99. Результат действительно неплохой, если учесть, что стартовый капитал можно было бы ограничить 2000 долларов (это тройной запас по максимальной просадке!) и на сегодняшний день получить прирост капитала на 65%.
http://www.forextrade.ru/media/Image/MQLabs/42_ag/USDCHF.gif
Рис. 3. - Результаты тестирования эксперта AutoLevels_Expert на валютной паре USDCHF.
А вот USDCHF не поддерживает своего собрата, хотя больших просадок и не показывает - 994 доллара. Но что нам от этого, если прибыли все равно нет?
http://www.forextrade.ru/media/Image/MQLabs/42_ag/GBPUSD.gif
Рис. 4 - Результаты тестирования эксперта AutoLevels_Expert на валютной паре GBPUSD.
Фунт вообще показал уникальный результат - 2385 долларов чистой прибыли при максимальной просадке чуть больше, чем у евро - 699 долларов. То есть фактор восстановления зашкаливает за три - 3.41. Предметом для беспокойства в данном случае является неравномерное распределение накопления прибыли - только в третьей трети теста. С другой стороны, особых спадов в первых двух третях не наблюдается, что можно отнести к устойчивости советника. При тройном запасе по максимальной просадке начальный депозит мог бы быть 2100 долларов. В результате к данному моменту имели бы 113% прибыли.
http://www.forextrade.ru/media/Image/MQLabs/42_ag/USDJPY.gif
Рис. 5. - Результаты тестирования эксперта AutoLevels_Expert на валютной паре USDJPY.
Несмотря на положительный конечный результат (чистая прибыль 543 доллара) о чем-то судить по результатам теста трудно, та как количество проведенных сделок очень мало - 95. А для минимальной репрезентативности требуется хотя бы 100. В основном же, как можно заметить, в наших исследованиях фигурирует количество сделок близкое к двумстам.
Эту же стратегию, по обычаю, реализуем для AutoGraf 4.0. Из трех необходимых нам параметров - StartPoint, StepPoints и Offset - один придется сделать неизменным для пользователя. В данной ситуации это будет шаг сетки StepPoints. Параметру StartPoints будет соответствовать шаг модификации St, а параметру Offset - дистанция Ds.
Для запуска стратегии необходимо проделать такие шаги:
Воспользоваться ссылкой Файлы стратегий для Autograf 4.0 (http://www.forextrade.ru/media/Image/MQLabs/42_ag/AG_AutoLevels.zip)Файлы стратегий для Autograf 4.0 и полученный архив распаковать в папку MT4\experts\libraries.
Запустить AutoGraf.
Для получения похожих результатов работы выставить объем открываемой позиции (Lots) 0.1, значение Ds - 0, значение St - 25. При запуске в тестере нужно иметь в виду, что подкачиваемая тестером история ограничена 1000 баров, чего может не хватить на минутной истории для вычисления начальных значений BuyLevel и SellLevel. В этом случае необходимо подобрать начальную дату тестирования сразу после резких движений цены.
Выбрать стратегию №3. Для этого передвинуть вверх значок So и среди названий стратегий найти S3
Запустить функцию автоматической торговли, передвинув значок AT в верхнее положение.
Подводя итоги проведенного опыта, стоит обратить внимание на соотношение стопов и профитов позиций, открываемых экспертом. Оно приблизительно равно 1. При этом количество прибыльных сделок порядка 53-54%. То есть мы можем исходить их того, что стратегия оперирует небольшим статистическим преимуществом при прочих равных условиях. Как видим, такого небольшого преимущества вполне достаточно для получения значительной прибыли.
Использование полученного советника рекомендуется только в полуавтоматическом режиме под присмотром трейдера и после всестороннего изучения слабых и сильных сторон стратегии.
Советник AutoLevels_Expert (http://www.forextrade.ru/media/Image/MQLabs/42_ag/AutoLevels_Expert.mq4)
Индикатор AutoLevels (http://www.forextrade.ru/media/Image/MQLabs/42_ag/AutoLevels.mq4)
Развернутые результаты тестирования эксперта (http://www.forextrade.ru/media/Image/MQLabs/42_ag/Test.zip)
На самом деле, если подойти к этому вопросу конструктивно, можно заметить, что очень часто цена останавливается при достижении определенных ценовых уровней, а при пробитии других уровней с легкостью продолжает движение. Попробуем же проверить это наблюдение на истории и подобрать "правильные" ценовые уровни.
Сначала нам потребуется индикатор, чтобы можно было визуально оценить расположение необходимых уровней. Название индикатора емко описывает его суть - AutoLevels. Входных параметров у него всего лишь три - StartPoints, StepPoints и LevelColor:
StartPoints задает начальное значение уровня в пределах фигуры (100 пунктов), от которого будут отсчитываться последующие уровни.
StepPoints - количество пунктов, которое будет прибавляться к текущему значению ценового уровня для вычисления следующего.
LevelColor - цвет горизонтальных линий, обозначающих ценовой уровень.
В начале работы индикатор вычисляет минимальное и максимальное значение цены за весь доступный исторический период. Затем найденное минимальное значение приводится к ближайшему значению StartPoints. Например, минимальная котировка валютной пары EURUSD 0.8227. В результате, границы фигуры получаются 0.82 - 0.83. В этом диапазоне нужно найти начальное значение. При значении StartPoints, равного 40, первый ценовой уровень будет определен как 0.8240. Для нахождения следующего значения к 0.8240 необходимо прибавить значение StepPoints. Если оно равно 50 пунктам, то следующий уровень окажется равным цене 0.8290. По такому алгоритму уровни будут рассчитываться, пока не будет достигнуто максимальное значение цены на текущей валютной паре.
Необходимо заметить, что если значение StepPoints не будет без остатка укладываться в одну фигуру, то повторения уровней в пределах каждой фигуры не выйдет, так как отсчет производится от минимального найденного значения цены. Например, при StartPoints = 30 и StepPoints = 70 получим такие ценовые уровни, начиная от минимального: 0.8230, 0.8300, 0.8370, 0.8440, 0.8510, 0.8580, 0.8650, 0.8730 и т. д. Таким образом, в пределах каждой новой фигуры будем получать новое значение уровня. Но это еще полбеды. Основная проблема будет заключаться в получении разных ценовых уровней при выборе различных точек отсчета (минимального значения). Поэтому значение StepPoints нужно всегда выбирать кратным 100 пунктам. Таких значений оказывается не очень то и много: 1, 2, 4, 5, 10, 20, 25, 50 и 100.
На рис. 1 показан общий вид индикатора AutoLevels со значениями StartPoints = 0 и StepPoints = 50, а также схематично представлена стратегия торговли по ценовым уровням.
http://www.forextrade.ru/media/Image/MQLabs/42_ag/figure_1.gif
Рис. 1. - Принципы открытия сделок по индикатору AutoLevels.
Красной стрелкой, указывающей вниз, показано открытие короткой сделки. Это происходит после пробития ценой уровня 1.4550 сверху вниз. Почему именно 1.4550, а не более ранние пробития 1.4600 и 1.4650? Дело в том, что перед совершением первой сделки необходимо найти два ближайших уровня для совершения длинных и коротких сделок. Эти уровни всегда находятся на расстоянии в три ценовых уровня друг от друга, в нашем случае это 150 пунктов. После пробития одного из них происходит перемещение своеобразного канала вверх или вниз. Поэтому перед стартом достаточно найти два ближайших однонаправленных пробития уровней ценой. Следующий по ходу цены ценовой уровень будет являться сигнальным.
Итак, мы определили, что произошло два пробития уровней (1.4650 и 1.4600) подряд. В результате уровень для открытия длинных сделок (назовем его BuyLevel) будет 1.4700, а для коротких (SellLevel) - 1.4550. Как только цена пробивает 1.4550 сверху вниз, необходимо отложить ордер Sell Limit в 25 пунктах (половина расстояния между соседними уровнями) от пробитого уровня в расчете на откат цены. Чаще всего такой откат происходит и ордер открывается.
В результате пробития нового уровня вниз, уровни BuyLevel и SellLevel также смещаются вниз на 50 пунктов (1.4650 и 1.4500 соответственно). SellLevel становится целевым для короткой сделки, а BuyLevel - защитным, на него устанавливается стоп-приказ. В случае, если бы цена после пробития 1.4550 дошла до 1.4500 без отката, не захватив ордер Sell Limit, то нужно было бы переместить Sell Limit на 50 пунктов ниже и так до тех пор, пока ордер не сработает.
В приведенной же ситуации все складывается наилучшим образом - ордер захватывается рынком на откате, после чего цена достигает уровня 1.4500, где срабатывает профит. К тому же, пробитие очередного уровня вниз приводит к смещению BuyLevel и SellLevel на 50 пунктов ниже. Поэтому, когда цена разворачивается и пробивает уровень 1.4600 снизу вверх, необходимо отложить ордер Buy Limit и, также как в случае с Sell Limit, на 25 пунктов ниже пробитого уровня. BuyLevel смещается вверх к 1.4650, а SellLevel следует за ним на расстоянии 150 пунктов - 1.4500. И вновь рынок производит откат на половину значения StepPoints, захватывая Buy Limit. Дальше все вновь происходит без форс-мажоров, и цена достигает намеченной цели 1.4650, приводя BuyLevel и SellLevel к значениям 1.4700 и 1.4550 соответственно.
Ограничением стратегии является отсутствие реакции на последующее пробитие уровней BuyLevel до тех пор, пока не будет достигнут SellLevel. Это, конечно, минус, когда идет мощный тренд, но большой плюс, когда цена долго колеблется в пределах выбранных нами 150 пунктов.
Несмотря на то, что в основе эксперта будет лежать ряд часто используемых нами функций, реализацию начнем не с сигнальной части, а с функции инициализации, так как для корректной работы сигнальной части потребуются некоторые данные.
Всю функцию приводить не имеет смысла. Поэтому ограничимся лишь новшествами:
// - 3 - ==================== Расчет новых предельных значений ==========================
StartLevel = MathFloor(Low[iLowest(Symbol(), 0, MODE_LOW, Bars)]/Point/100)*100 +
StartPoints; // минимальное значение в соответствии с сеткой
FinishLevel = High[iHighest(Symbol(), 0, MODE_HIGH, Bars)]/Point; // максимальное
// - 3 - ==================== Окончание блока ===========================================
// - 4 - ==================== Поиск уровней покупки и продажи ===========================
BuyLimitPrice = 0;
SellLimitPrice = 0;
GetLevels(); // Расчет ближайших значений сетки - верхнего и нижнего
int i = 0;
SellLevel = DnLevel; // нижний непробитый уровень
BuyLevel = UpLevel; // верхний непробитый уровень
// - 4.1 - ======== Поиск пересечения двух уровней подряд в одном направлении ========
while (iHigh(Symbol(), PERIOD_M1, i) < UpLevel+StepPoints*Point &&
iLow(Symbol(), PERIOD_M1, i) > DnLevel-StepPoints*Point &&
i < iBars(Symbol(), PERIOD_M1))
{
if (ND(iHigh(Symbol(), PERIOD_M1, i) - BuyLevel) >= 0)
BuyLevel += StepPoints*Point;
else
if (ND(SellLevel - iLow(Symbol(), PERIOD_M1, i)) >= 0)
SellLevel -= StepPoints*Point;
i++;
}
// - 4.1 - ======================== Окончание блока ==================================
// - 4.2 - ======== При нехватке истории продолжать работу не имеет смысла ===========
if (i == iBars(Symbol(), PERIOD_M1))
{
Comment("Недостаточная глубина истории М1 для работы советника.");
Print("Недостаточная глубина истории М1 для работы советника.");
return(0);
}
// - 4.2 - ======================== Окончание блока ==================================
if (SellLevel == DnLevel)
BuyLevel = SellLevel+3*StepPoints*Point;
if (BuyLevel == UpLevel)
SellLevel = BuyLevel-3*StepPoints*Point;
// - 4 - ==================== Окончание блока ===========================================
// - 5 - =============== Нахождение своей сделки в истории ==============================
TypeOrder = -1;
datetime LastTime = 0;
for (i = 0; i < OrdersHistoryTotal(); i++)
if (OrderSelect(i, SELECT_BY_POS))
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
if (OrderCloseTime() > LastTime)// находим сделку с наибольшим временем закрытия
{
LastTime = OrderCloseTime();
TypeOrder = OrderType();
}
// - 5 - =============== Окончание блока ================================================
// - 6 - =============== Нахождение своей сделки среди открытых =========================
OwnOrder = -1;
for (i = 0; i < OrdersTotal(); i++)
if (OrderSelect(i, SELECT_BY_POS))
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
OwnOrder = OrderTicket(); // тикет своей сделки
TypeOrder = OrderType(); // тип - лимитный ордер или уже позиция
PriceOrder = OrderOpenPrice(); // цена открытия сделки
}
// - 6 - =============== Окончание блока ================================================
В блоке 3, подобно индикатору AutoLevels, рассчитываются минимальное и максимальное исторические значения с приведением минимального значения к ближайшему уровню, заданного параметром StartPoints.
На основании этих значений в начале четвертого блока рассчитываются ближайшие к текущей цене Bid верхний и нижний уровни. Производится это при помощи функции GetLevels, скромный код которой будет приведен чуть ниже. Результатом вызова GetLevels являются значения переменных UpLevel (верхний уровень) и DnLevel (нижний уровень). На начальном этапе эти значения присваиваются переменным BuyLevel и SellLevel соответственно.
Подблок четвертого блока - 4.1 - выполняет поиск двух пересечений уровней подряд в одном направлении. Алгоритм этого поиска основан на том, что два пересечения подряд - это достижение уровня выше UpLevel или ниже DnLevel. Поэтому цикл while будет выполняться, пока один из внешних уровней не будет достигнут ценой или пока не закончится доступная история котировок. Исходя из того, что для более высокой точности расчетов используются данные таймфрейма М1, истории в некоторых случаях хватать не будет. При использовании данных более высокого таймфрейма повышается вероятность пересечения двух уровней одной свечей, что приведет к неоднозначному окончанию расчетов. Достижение конца истории как раз проверяется в подблоке 4.2.
В конце четвертого блока проверяется равенство разницы между BuyLevel и SellLevel трем расстояниям между соседними уровнями. Если разница меньше, то значение одного из уровней исправляется.
Пятый и шестой блоки занимаются восстановлением работоспособности эксперта после перерыва в работе (переключение таймфрейма, выключение эксперта или терминала). Наиболее необходимыми параметрами являются:
TypeOrder - тип текущей или последней закрытой сделки
OwnOrder - номер тикета текущей сделки
PriceOrder - цена открытия текущей сделки
Если текущей открытой сделки нет, то в любом случае необходимо иметь данные по последней закрытой сделке. Этим и занимается блок 5, находя в истории счета последнюю закрытую сделку. Вне зависимости от результата поисков, шестой блок перезаписывает значение всех переменных, ответственных за характеристики сделки, если среди открытых позиций и отложенных ордеров находится ордер, открытый экспертом.
Дальше, как уже упоминалось выше, рассмотрим простую функцию GetLevels:
//+-------------------------------------------------------------------------------------+
//| Расчет двух ближайших уровней по текущей цене |
//+-------------------------------------------------------------------------------------+
void GetLevels()
{
DnLevel = NP((MathFloor((MathFloor(Bid/Point) - StartLevel)/StepPoints)*StepPoints +
StartLevel)*Point); // ближайший уровень снизу
UpLevel = NP(DnLevel + StepPoints*Point); // ближайший уровень сверху
}
Текущий нижний уровень находится умножением StepPoints на целый остаток от деления разницы между Bid и минимальным уровнем StartLevel на StepPoints. К полученному результату прибавляется начальное значение сетки StartLevel. Расчет верхнего уровня после расчета нижнего становится и вовсе простым - прибавляем шаг сетки StepPoints.
На данном этапе мы всего лишь подготовили начальные данные, но ведь с течением времени BuyLevel и SellLevel должны изменяться при пробитии одного из уровней. Отслеживание таких пробитий будет происходить в теле функции 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); // текущий уровень стопов
// - 2 - == Окончание блока ============================================================
// - 3 - =================== Расчет новых значений уровней покупки и продажи ============
if (ND(Bid - BuyLevel) >= 0)
{
BuyLimitPrice = NP(BuyLevel - Offset*Point + Spread); // Цена установки BuyLimit
SellLimitPrice = 0; // SellLimit ставить не нужно
BuyLevel += StepPoints*Point;
SellLevel = BuyLevel - 3*StepPoints*Point;
}
if (ND(SellLevel - Bid) >= 0)
{
SellLimitPrice = NP(SellLevel + Offset*Point); // Цена установки SellLimit
BuyLimitPrice = 0; // BuyLimit ставить не нужно
SellLevel -= StepPoints*Point;
BuyLevel = SellLevel + 3*StepPoints*Point;
}
// - 3 - ==================== Окончание блока ===========================================
// - 4 - ==================== Принятие решений об установке ордеров =====================
Transactions();
// - 4 - ===================================== Окончание блока =========================
return(0);
}
Первый и второй блоки являются стандартными. Поэтому не комментируем.
Отслеживание пересечений происходит в третьем блоке. При пересечении уровня BuyLevel переменной BuyLimitPrice присваивается значение цены открытия будущего ордера Buy Limit. Для этого из значения BuyLevel вычитается Offset (один из входных параметров эксперта, задающий глубину отката) пунктов и прибавляется спрэд, так как все длинные позиции открываются по цене Ask. В то же время цена открытия ордера Sell Limit сбрасывается в ноль - переменной SellLimitPrice присваивается 0. И только после этого значения BuyLevel и SellLevel увеличиваются на StepPoints пунктов. То же самое, но в немного другом направлении, происходит при пересечении уровня SellLevel.
Заканчивает функцию start вызов Transactions, где и производится установка отложенных ордеров BuyLimit и SellLimit:
//+-------------------------------------------------------------------------------------+
//| Принятие решений об установке ордеров |
//+-------------------------------------------------------------------------------------+
void Transactions()
{
// - 1 - ==================== Существует ли еще свой ордер? =============================
if (OwnOrder > 0)
if (OrderSelect(OwnOrder, SELECT_BY_TICKET))
if (OrderCloseTime() != 0)
OwnOrder = -1;
else
TypeOrder = OrderType();
// - 1 - ==================== Окончание блока ===========================================
// - 2 - ==================== Сигнал открытия ордера Buy Limit ==========================
if (BuyLimitPrice != 0)
{
if (OwnOrder > 0 && TypeOrder == OP_BUYLIMIT) // Если ордер Buy Limit уже установлен
if (MathAbs(BuyLimitPrice - PriceOrder) >= Tick) // то проверяем его цену открытия
if (WaitForTradeContext())
if (!OrderModify(OwnOrder, NP(BuyLimitPrice), NP(SellLevel), // и если цена
NP(BuyLevel), 0)) // открытия не та, то модифицируем ордер
{
Print("Попытка модификации ордера BuyLimit сорвалась. Ошибка №",
GetLastError());
return;
}
if (OwnOrder < 0 && TypeOrder != OP_BUY)//Если ордера нет и последняя сделка была не
if (OpenOrderCorrect(OP_BUYLIMIT, NP(BuyLimitPrice), NP(SellLevel), // BUY, то
NP(BuyLevel)) != 0) // устанавливаем ордер
return;
BuyLimitPrice = 0; // при успешной установке сбрасываем флаг
}
// - 2 - ==================== Окончание блока ===========================================
// - 3 - =================== Сигнал открытия ордера Sell Limit ==========================
if (SellLimitPrice != 0)
{
if (OwnOrder > 0 && TypeOrder == OP_SELLLIMIT) // Если Sell Limit уже установлен
if (MathAbs(SellLimitPrice - PriceOrder) >= Tick)// то проверяем его цену открытия
if (WaitForTradeContext())
if (!OrderModify(OwnOrder, NP(SellLimitPrice), NP(BuyLevel+Spread), // и если
NP(SellLevel+Spread), 0))// цена не та, то модифицируем ордер
{
Print("Попытка модификации ордера SellLimit сорвалась. Ошибка №",
GetLastError());
return;
}
if (OwnOrder < 0 && TypeOrder != OP_SELL) // Если ордера нет и последняя сделка не
if (OpenOrderCorrect(OP_SELLLIMIT, NP(SellLimitPrice), NP(BuyLevel+Spread),//SELL,
NP(SellLevel+Spread)) != 0) // то устанавливаем ордер
return;
SellLimitPrice = 0; // при успешной установке сбрасываем флаг
}
// - 3 - ==================== Окончание блока ===========================================
}
Первый блок занимается мониторингом единственной используемой экспертом сделки. Для этого достаточно проверить существование тикета OwnOrder. Если ордер с таким тикетом существует и время его закрытия равно нулю, то позиция или ордер находится среди открытых позиций. В этом случае значение TypeOrder изменяется в соответствии с типом открытого ордера. Это нужно для контроля срабатывания лимитного ордера. Если же время закрытия ордера не равно нулю, то ордер был закрыт и поэтому переменной OwnOrder присваивается значение -1.
Второй блок выполняется при активном сигнале открытия ордера BuyLimit. В качестве сигнала используется ненулевое значение переменной BuyLimitPrice. Здесь возможно всего два варианта: ордер Buy Limit уже существует или вообще нет ордеров и позиций. В первом случае проверяется цена открытия ордера. Если она не равна значению BuyLimitPrice, то значения цены открытия, стоп-приказа и профита модифицируются, что приводит к перемещению ордера. Во втором случае просто открывается отложенный ордер. При успешной обработке обоих вариантов переменной BuyLimitPrice присваивается ноль, что приводит к дезактивации сигнала открытия.
По схожему алгоритму работает третий блок. При ненулевом значении SellLimitPrice проверяется существование ордера Sell Limit и, при необходимости, изменяются его параметры. Если же ордер не существует, то производится установка отложенного ордера Sell Limit.
После детального описания функций, не входящих во все разрабатываемые нами советники, приступаем к тестированию стратегии. Выбор таймфрейма в данном случае очевиден - М1. Этот таймфрейм способствует наиболее точному моделированию тиков, в то время как все более высокие таймфреймы приводят к небольшим неточностям. Это замечание касается только режима тестирования. Использование же эксперта онлайн возможно абсолютно на любом таймфрейме, так как для расчетов используются лишь значения цены, параметры свечей в расчетах не участвуют. По причине использования наименьшего таймфрейма качество моделирования тиков во всех результатах тестов равно 25%. Большего на М1 достичь невозможно.
В качестве исторического периода выберем 2009-й год 01.01.2009 - 21.11.2009. Входные параметры: StartPoints = 0 (граница фигуры), StepPoints = 50 (в фигуре будет находится два уровня - 0 и 50), Offset = 25 (откат равен половине сетки). Результаты приведены на рис. 2-5:
http://www.forextrade.ru/media/Image/MQLabs/42_ag/EURUSD.gif
Рис. 2. - Результаты тестирования эксперта AutoLevels_Expert на валютной паре EURUSD.
При первом же тесте мы получили положительный результат, что не может не радовать. Чистая прибыль 1309 долларов, а максимальная просадка всего лишь 658 долларов. В итоге получаем фактор восстановления почти 2 - 1.99. Результат действительно неплохой, если учесть, что стартовый капитал можно было бы ограничить 2000 долларов (это тройной запас по максимальной просадке!) и на сегодняшний день получить прирост капитала на 65%.
http://www.forextrade.ru/media/Image/MQLabs/42_ag/USDCHF.gif
Рис. 3. - Результаты тестирования эксперта AutoLevels_Expert на валютной паре USDCHF.
А вот USDCHF не поддерживает своего собрата, хотя больших просадок и не показывает - 994 доллара. Но что нам от этого, если прибыли все равно нет?
http://www.forextrade.ru/media/Image/MQLabs/42_ag/GBPUSD.gif
Рис. 4 - Результаты тестирования эксперта AutoLevels_Expert на валютной паре GBPUSD.
Фунт вообще показал уникальный результат - 2385 долларов чистой прибыли при максимальной просадке чуть больше, чем у евро - 699 долларов. То есть фактор восстановления зашкаливает за три - 3.41. Предметом для беспокойства в данном случае является неравномерное распределение накопления прибыли - только в третьей трети теста. С другой стороны, особых спадов в первых двух третях не наблюдается, что можно отнести к устойчивости советника. При тройном запасе по максимальной просадке начальный депозит мог бы быть 2100 долларов. В результате к данному моменту имели бы 113% прибыли.
http://www.forextrade.ru/media/Image/MQLabs/42_ag/USDJPY.gif
Рис. 5. - Результаты тестирования эксперта AutoLevels_Expert на валютной паре USDJPY.
Несмотря на положительный конечный результат (чистая прибыль 543 доллара) о чем-то судить по результатам теста трудно, та как количество проведенных сделок очень мало - 95. А для минимальной репрезентативности требуется хотя бы 100. В основном же, как можно заметить, в наших исследованиях фигурирует количество сделок близкое к двумстам.
Эту же стратегию, по обычаю, реализуем для AutoGraf 4.0. Из трех необходимых нам параметров - StartPoint, StepPoints и Offset - один придется сделать неизменным для пользователя. В данной ситуации это будет шаг сетки StepPoints. Параметру StartPoints будет соответствовать шаг модификации St, а параметру Offset - дистанция Ds.
Для запуска стратегии необходимо проделать такие шаги:
Воспользоваться ссылкой Файлы стратегий для Autograf 4.0 (http://www.forextrade.ru/media/Image/MQLabs/42_ag/AG_AutoLevels.zip)Файлы стратегий для Autograf 4.0 и полученный архив распаковать в папку MT4\experts\libraries.
Запустить AutoGraf.
Для получения похожих результатов работы выставить объем открываемой позиции (Lots) 0.1, значение Ds - 0, значение St - 25. При запуске в тестере нужно иметь в виду, что подкачиваемая тестером история ограничена 1000 баров, чего может не хватить на минутной истории для вычисления начальных значений BuyLevel и SellLevel. В этом случае необходимо подобрать начальную дату тестирования сразу после резких движений цены.
Выбрать стратегию №3. Для этого передвинуть вверх значок So и среди названий стратегий найти S3
Запустить функцию автоматической торговли, передвинув значок AT в верхнее положение.
Подводя итоги проведенного опыта, стоит обратить внимание на соотношение стопов и профитов позиций, открываемых экспертом. Оно приблизительно равно 1. При этом количество прибыльных сделок порядка 53-54%. То есть мы можем исходить их того, что стратегия оперирует небольшим статистическим преимуществом при прочих равных условиях. Как видим, такого небольшого преимущества вполне достаточно для получения значительной прибыли.
Использование полученного советника рекомендуется только в полуавтоматическом режиме под присмотром трейдера и после всестороннего изучения слабых и сильных сторон стратегии.
Советник AutoLevels_Expert (http://www.forextrade.ru/media/Image/MQLabs/42_ag/AutoLevels_Expert.mq4)
Индикатор AutoLevels (http://www.forextrade.ru/media/Image/MQLabs/42_ag/AutoLevels.mq4)
Развернутые результаты тестирования эксперта (http://www.forextrade.ru/media/Image/MQLabs/42_ag/Test.zip)