Эксперты на основе популярных торговых систем и алхимия оптимизации торгового робота (Часть 3)
Nikolay Kositsin | 1 апреля, 2008
Введение
Поступило предложение от читателя предыдущей статьи немного механизировать сам процесс бэктестинга, чтобы за один общий заход можно было получить результаты всех оптимизаций одновременно. Да и всё время передвигать период тестирования в бэктестинге тоже не так чтобы и шибко удобно, лучше это как-то сделать в автопилотном режиме. Сама идея просто великолепная - благо возможностей для её реализации в MQL4 более чем достаточно, так что с решения этой задачи я и начну эту статью.
Механизация бэктестинга
Для решения этой задачи следует:
1. Вписать под шапкой любого, интересующего вас эксперта строку следующего содержания:
//+==================================================================+ //| Custom BackTesting function | //+==================================================================+ #include <IsBackTestingTime.mqh>
Посредством этой директивы мы включаем в код эксперта функцию IsBackTestingTime(). Вполне естественно следует не забыть поместить файл IsBackTestingTime.mqh в папку INCLUDE. Эта функция:
bool IsBackTestingTime()
{
}
предназначена для определения времени, в течении которого происходит бэктестерная оптимизация или бэктестинг. В эти периоды времени эта функция всегда возвращает true, в остальное время эта функция возвращает false. Помимо этой функции в код эксперта этой директивой добавляются внешние экспертные переменные:
//---- Объявления внешних переменных для бэктестинга extern datetime Start_Time = D'2007.01.01'; // время старта нулевой оптимизации extern int Opt_Period = 3; // период оптимизации в месяцах, если значение периода меньше нуля, то все параметры измеряются в днях extern int Test_Period = 2; // период тестирования в месяцах extern int Period_Shift = 1; // шаг сдвига периода оптимизации в месяцах extern int Opt_Number = 0; // номер оптимизации
Я надеюсь, что смысл этих переменных будет абсолютно понятен всем, кто читал мою предыдущую статью, и пояснять их смысл второй раз несколько излишне!
2. В блок стартовой функции перед кодом эксперта необходимо поместить простейший универсальный код обращения к функции IsBackTestingTime(), ограничивающий работу эксперта определёнными временными рамками, в зависимости от номера бэктестерной оптимизации.
//----+ Выполнение условий бэктестинга if (!IsBackTestingTime()) return(0);
Схематично это будет выглядеть следующим образом:
//+==================================================================+ //| Exp_BackTest.mq4 | //| Copyright © 2008, Nikolay Kositsin | //| Khabarovsk, farria@mail.redcom.ru | //+==================================================================+ #property copyright "Copyright © 2008, Nikolay Kositsin" #property link "farria@mail.redcom.ru" //+==================================================================+ //| Custom BackTesting function | //+==================================================================+ #include <IsBackTestingTime.mqh> //---- ВХОДНЫЕ ПАРАМЕТРЫ ЭКСПЕРТА //---- ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ЭКСПЕРТА //+==================================================================+ //| ПОЛЬЗОВАТЕЛЬСКИЕ ФУНКЦИИ ЭКСПЕРТА | //+==================================================================+ //+==================================================================+ //| Custom Expert initialization function | //+==================================================================+ int init() { //----+ +------------------------------------------------------------+ //---- КОД ДЛЯ ИНИЦИАЛИЗАЦИИ ЭКСПЕРТА //----+ +------------------------------------------------------------+ //---- завершение инициализации return(0); } //+==================================================================+ //| Custom Expert iteration function | //+==================================================================+ int start() { //----+ Выполнение условий бэктестинга if (!IsBackTestingTime()) return(0); //----+ +---------------------------------------------------------+ //----+ КОД АЛГОРИТМА ЭКСПЕРТА //----+ +---------------------------------------------------------+ //----+ return(0); } //+------------------------------------------------------------------+
Кому интерсно решение подобной задачи на примере готового эксперта, могут посмотреть код эксперта Exp_5_1.mq4, являющегося модифицированным для бэктестинга экспертом Exp_5.mq4 из предыдущей статьи. По большому счёту какой-то значительной разницы в оптимизации подобного эксперта по сравнению с обычным экспертом нет. Разве что, на мой взгляд, не стоит оптимизировать бэктестерные переменные, за исключением переменной Opt_Number, хотя как знать, тут у каждого свой подход должен быть.
Самое главное не забывать, что после оптимизации при тестировании мы получаем результаты уже не внутри периода оптимизации, а после него, за правой границей! И всё-таки делать все бэктестерные оптимизации за один заход с помощью генетического алгоритма не совсем сподручно, гораздо более интересно делать более углублённое исследование каждой бэктестерной оптимизации отдельно без оптимизации входной переменной Opt_Number.
Но даже в этом случае подобный подход очень сильно ускоряет понимание поведения эксперта. Ну и при подобном мероприятии следует помнить, что значение внешней переменной Opt_Number может изменяться от нуля и до некоторого максимального значения, которое можно определить следующим образом: от общего периода, на котором проводятся все бэктестерные оптимизации взятого в месяцах, отнимается период бэктестерной оптимизации в месяцах (Opt_Period) и отнимается период бэктестинга (Test_Period). К полученной величине добавить единицу. Полученный результат и будет максимумом для переменной Opt_Number, в случае, ежели Period_Shift равен единице.
Торговые системы, основанные на пересечениях двух мувингов
Этот вариант торговых систем достаточно распространён, и алгоритм, лежащий в их основе давно следовало рассмотреть поподробнее. Для длинных позиций алгоритм входа в рынок будет выглядеть следующим образом:
Для коротких позиций алгоритм входа в рынок аналогично будет таким:
В качестве индикаторов могут использоваться два одинаковых мувинга с разными значениями параметров, определяющих усреднение в индикаторах. Предполагается, что параметр, определяющий усреднение у мувинга MovA имеет всегда меньшее значение, чем этот же параметр у мувинга MovB. Таким образом мувинг MovA оказывается в этой торговой системе быстрым мувингом, а мувинг MovB медленным. Вот вариант реализации данной торговой системы на двух JMA мувингах:
//+==================================================================+ //| Exp_6.mq4 | //| Copyright © 2007, Nikolay Kositsin | //| Khabarovsk, farria@mail.redcom.ru | //+==================================================================+ #property copyright "Copyright © 2007, Nikolay Kositsin" #property link "farria@mail.redcom.ru" //----+ +-------------------------------------------------------------------------------+ //---- ВХОДНЫЕ ПАРАМЕТРЫ ЭКСПЕРТА ДЛЯ BUY СДЕЛОК extern bool Test_Up = true;//фильтр направления расчётов сделок extern int Timeframe_Up = 240; extern double Money_Management_Up = 0.1; extern int LengthA_Up = 4; // глубина сглаживания быстрого мувинга extern int PhaseA_Up = 100; // параметр, изменяющийся в пределах //-100 ... +100, влияет на качество переходного процесса быстрого мувинга; extern int IPCA_Up = 0;/* Выбор цен, по которым производится расчёт индикатора быстрым мувингом (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 14-Heiken Ashi Close.) */ extern int LengthB_Up = 4; // приращение глубины сглаживания медленного мувинга к быстрому extern int PhaseB_Up = 100; // параметр, изменяющийся в пределах //-100 ... +100, влияет на качество переходного процесса медленного мувинга; extern int IPCB_Up = 0;/* Выбор цен, по которым производится расчёт индикатора медленным мувингом (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 14-Heiken Ashi Close.) */ extern int STOPLOSS_Up = 50; // стоплосс extern int TAKEPROFIT_Up = 100; // тейкпрофит extern bool ClosePos_Up = true; // разрешение принудительного закрывания позиции //----+ +-------------------------------------------------------------------------------+ //---- ВХОДНЫЕ ПАРАМЕТРЫ ЭКСПЕРТА ДЛЯ SELL СДЕЛОК extern bool Test_Dn = true;//фильтр направления расчётов сделок extern int Timeframe_Dn = 240; extern double Money_Management_Dn = 0.1; extern int LengthA_Dn = 4; // глубина сглаживания быстрого мувинга extern int PhaseA_Dn = 100; // параметр, изменяющийся в пределах // -100 ... +100, влияет на качество переходного процесса быстрого мувинга; extern int IPCA_Dn = 0;/* Выбор цен, по которым производится расчёт индикатора быстрым мувингом (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 14-Heiken Ashi Close.) */ extern int LengthB_Dn = 4; // приращение глубины сглаживания медленного мувинга к быстрому extern int PhaseB_Dn = 100; // параметр, изменяющийся в пределах // -100 ... +100, влияет на качество переходного процесса медленного мувинга; extern int IPCB_Dn = 0;/* Выбор цен, по которым производится расчёт индикатора медленным мувингом(0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 14-Heiken Ashi Close.) */ extern int STOPLOSS_Dn = 50; // стоплосс extern int TAKEPROFIT_Dn = 100; // тейкпрофит extern bool ClosePos_Dn = true; // разрешение принудительного закрывания позиции //----+ +-------------------------------------------------------------------------------+ //---- Целые переменные для минимума расчётных баров int MinBar_Up, MinBar_Dn; //+==================================================================+ //| Custom Expert functions | //+==================================================================+ #include <Lite_EXPERT1.mqh> //+==================================================================+ //| Custom Expert initialization function | //+==================================================================+ int init() { //---- Проверка значения переменной Timeframe_Up на корректность if (Timeframe_Up != 1) if (Timeframe_Up != 5) if (Timeframe_Up != 15) if (Timeframe_Up != 30) if (Timeframe_Up != 60) if (Timeframe_Up != 240) if (Timeframe_Up != 1440) Print(StringConcatenate("Параметр Timeframe_Up не может ", "быть равным ", Timeframe_Up, "!!!")); //---- Проверка значения переменной Timeframe_Dn на корректность if (Timeframe_Dn != 1) if (Timeframe_Dn != 5) if (Timeframe_Dn != 15) if (Timeframe_Dn != 30) if (Timeframe_Dn != 60) if (Timeframe_Dn != 240) if (Timeframe_Dn != 1440) Print(StringConcatenate("Параметр Timeframe_Dn не может ", "быть равным ", Timeframe_Dn, "!!!")); //---- Инициализация переменных MinBar_Up = 4 + 30; MinBar_Dn = 4 + 30; //---- завершение инициализации return(0); } //+==================================================================+ //| expert deinitialization function | //+==================================================================+ int deinit() { //----+ //---- Завершение деинициализации эксперта return(0); //----+ } //+==================================================================+ //| Custom Expert iteration function | //+==================================================================+ int start() { //----+ Объявление локальных переменных int bar; double MovA[2], MovB[2]; //----+ Объявление статических переменных static int LastBars_Up, LastBars_Dn; static bool BUY_Sign, BUY_Stop, SELL_Sign, SELL_Stop; //----++ КОД ДЛЯ ДЛИННЫХ ПОЗИЦИЙ if (Test_Up) { int IBARS_Up = iBars(NULL, Timeframe_Up); if (IBARS_Up >= MinBar_Up) { if (LastBars_Up != IBARS_Up) { //----+ Иницмализация переменных BUY_Sign = false; BUY_Stop = false; LastBars_Up = IBARS_Up; //----+ ВЫЧИСЛЕНИЕ ИНДИКАТОРНЫХ ЗНАЧЕНИЙ И ЗАГРУЗКА ИХ В БУФЕРЫ for(bar = 1; bar < 3; bar++) MovA[bar - 1] = iCustom(NULL, Timeframe_Up, "JJMA", LengthA_Up, PhaseA_Up, 0, IPCA_Up, 0, bar); for(bar = 1; bar < 3; bar++) MovB[bar - 1] = iCustom(NULL, Timeframe_Up, "JJMA", LengthA_Up + LengthB_Up, PhaseB_Up, 0, IPCB_Up, 0, bar); //----+ ОПРЕДЕЛЕНИЕ СИГНАЛОВ ДЛЯ СДЕЛОК if ( MovA[1] < MovB[1]) if ( MovA[0] > MovB[0]) BUY_Sign = true; if ( MovA[0] > MovB[0]) BUY_Stop = true; } //----+ СОВЕРШЕНИЕ СДЕЛОК if (!OpenBuyOrder1(BUY_Sign, 1, Money_Management_Up, STOPLOSS_Up, TAKEPROFIT_Up)) return(-1); if (ClosePos_Up) if (!CloseOrder1(BUY_Stop, 1)) return(-1); } } //----++ КОД ДЛЯ КОРОТКИХ ПОЗИЦИЙ if (Test_Dn) { int IBARS_Dn = iBars(NULL, Timeframe_Dn); if (IBARS_Dn >= MinBar_Dn) { if (LastBars_Dn != IBARS_Dn) { //----+ Иницмализация переменных SELL_Sign = false; SELL_Stop = false; LastBars_Dn = IBARS_Dn; //----+ ВЫЧИСЛЕНИЕ ИНДИКАТОРНЫХ ЗНАЧЕНИЙ И ЗАГРУЗКА ИХ В БУФЕРЫ for(bar = 1; bar < 3; bar++) MovA[bar - 1] = iCustom(NULL, Timeframe_Dn, "JJMA", LengthA_Dn, PhaseA_Dn, 0, IPCA_Dn, 0, bar); for(bar = 1; bar < 3; bar++) MovB[bar - 1] = iCustom(NULL, Timeframe_Dn, "JJMA", LengthA_Dn + LengthB_Dn, PhaseB_Dn, 0, IPCB_Dn, 0, bar); //----+ ОПРЕДЕЛЕНИЕ СИГНАЛОВ ДЛЯ СДЕЛОК if ( MovA[1] > MovB[1]) if ( MovA[0] < MovB[0]) SELL_Sign = true; if ( MovA[0] < MovB[0]) SELL_Stop = true; } //----+ СОВЕРШЕНИЕ СДЕЛОК if (!OpenSellOrder1(SELL_Sign, 2, Money_Management_Dn, STOPLOSS_Dn, TAKEPROFIT_Dn)) return(-1); if (ClosePos_Dn) if (!CloseOrder1(SELL_Stop, 2)) return(-1); } } //----+ return(0); } //+------------------------------------------------------------------+
Торговые системы, основанные на пересечениях двух осцилляторов
Абсолютно аналогично подобная торговая система может использоваться не только с мувингами, но и с осцилляторами, но в этой ситуации, как я уже писал в предыдущей статье, оказывается при получении сигналов гораздо логичнее не сразу входить в рынок, а ставить отложенные ордера. В качестве самого наглядного примера можно использовать диаграмму MACD.
Для выставления отложенных ордеров BuyLimit алгоритм будет выглядеть следующим образом:
Аналогичный алгоритм для ордеров типа SellLimit получим в следующем виде:
Код этого эксперта во многом аналогичен коду предыдущего эксперта:
//+==================================================================+ //| Exp_7.mq4 | //| Copyright © 2007, Nikolay Kositsin | //| Khabarovsk, farria@mail.redcom.ru | //+==================================================================+ #property copyright "Copyright © 2007, Nikolay Kositsin" #property link "farria@mail.redcom.ru" //----+ +---------------------------------------------------------------------------+ //---- ВХОДНЫЕ ПАРАМЕТРЫ ЭКСПЕРТА ДЛЯ BUY СДЕЛОК extern bool Test_Up = true; //фильтр направления расчётов сделок extern int Timeframe_Up = 240; extern double Money_Management_Up = 0.1; extern int FST_period_Up = 12; // период быстрого мувинга extern int SLO_period_Up = 22; // приращение периода медленного мувинга к быстрому extern int SIGN_period_Up = 8; // период сигнальной линии extern int Price_Up = 0; // выбор цен, по которым производится расчёт MACD extern int STOPLOSS_Up = 50; // стоплосс extern int TAKEPROFIT_Up = 100; // тейкпрофит extern int PriceLevel_Up =40; // разница между текущей ценой и // ценой срабатывания отложенного ордера extern bool ClosePos_Up = true; // разрешение принудительного закрывания позиции //----+ +---------------------------------------------------------------------------+ //---- ВХОДНЫЕ ПАРАМЕТРЫ ЭКСПЕРТА ДЛЯ SELL СДЕЛОК extern bool Test_Dn = true; //фильтр направления расчётов сделок extern int Timeframe_Dn = 240; extern double Money_Management_Dn = 0.1; extern int FST_period_Dn = 12; // период быстрого мувинга extern int SLO_period_Dn = 22; // приращение периода медленного мувинга к быстрому extern int SIGN_period_Dn = 8; // период сигнальной линии extern int Price_Dn = 0; // выбор цен, по которым производится расчёт MACD extern int STOPLOSS_Dn = 50; // стоплосс extern int TAKEPROFIT_Dn = 100; // тейкпрофит extern int PriceLevel_Dn =40; // разница между текущей ценой и // ценой срабатывания отложенного ордера extern bool ClosePos_Dn = true; // разрешение принудительного закрывания позиции //----+ +---------------------------------------------------------------------------+ //---- Целые переменные для минимума расчётных баров int MinBar_Up, MinBar_Dn; //+==================================================================+ //| Custom Expert functions | //+==================================================================+ #include <Lite_EXPERT1.mqh> //+==================================================================+ //| Custom Expert initialization function | //+==================================================================+ int init() { //---- Проверка значения переменной Timeframe_Up на корректность if (Timeframe_Up != 1) if (Timeframe_Up != 5) if (Timeframe_Up != 15) if (Timeframe_Up != 30) if (Timeframe_Up != 60) if (Timeframe_Up != 240) if (Timeframe_Up != 1440) Print(StringConcatenate("Параметр Timeframe_Up не может ", "быть равным ", Timeframe_Up, "!!!")); //---- Проверка значения переменной Timeframe_Dn на корректность if (Timeframe_Dn != 1) if (Timeframe_Dn != 5) if (Timeframe_Dn != 15) if (Timeframe_Dn != 30) if (Timeframe_Dn != 60) if (Timeframe_Dn != 240) if (Timeframe_Dn != 1440) Print(StringConcatenate("Параметр Timeframe_Dn не может ", "быть равным ", Timeframe_Dn, "!!!")); //---- Инициализация переменных MinBar_Up = 4 + FST_period_Up + SLO_period_Up + SIGN_period_Up; MinBar_Dn = 4 + FST_period_Dn + SLO_period_Dn + SIGN_period_Dn; //---- завершение инициализации return(0); } //+==================================================================+ //| expert deinitialization function | //+==================================================================+ int deinit() { //----+ //---- Завершение деинициализации эксперта return(0); //----+ } //+==================================================================+ //| Custom Expert iteration function | //+==================================================================+ int start() { //----+ Объявление локальных переменных int bar; double MovA[2], MovB[2]; //----+ Объявление статических переменных static int LastBars_Up, LastBars_Dn; static datetime StopTime_Up, StopTime_Dn; static bool BUY_Sign, BUY_Stop, SELL_Sign, SELL_Stop; //----++ КОД ДЛЯ ДЛИННЫХ ПОЗИЦИЙ if (Test_Up) { int IBARS_Up = iBars(NULL, Timeframe_Up); if (IBARS_Up >= MinBar_Up) { if (LastBars_Up != IBARS_Up) { //----+ Иницмализация переменных BUY_Sign = false; BUY_Stop = false; LastBars_Up = IBARS_Up; StopTime_Up = iTime(NULL, Timeframe_Up, 0) + 60 * Timeframe_Up; //----+ ВЫЧИСЛЕНИЕ ИНДИКАТОРНЫХ ЗНАЧЕНИЙ И ЗАГРУЗКА ИХ В БУФЕРЫ for(bar = 1; bar < 3; bar++) MovA[bar - 1] = iMACD(NULL, Timeframe_Up, FST_period_Up, FST_period_Up + SLO_period_Up, SIGN_period_Up, Price_Up, 0, bar); for(bar = 1; bar < 3; bar++) MovB[bar - 1] = iMACD(NULL, Timeframe_Up, FST_period_Up, FST_period_Up + SLO_period_Up, SIGN_period_Up, Price_Up, 1, bar); //----+ ОПРЕДЕЛЕНИЕ СИГНАЛОВ ДЛЯ СДЕЛОК if ( MovA[1] < MovB[1]) if ( MovA[0] > MovB[0]) BUY_Sign = true; if ( MovA[0] > MovB[0]) BUY_Stop = true; } //----+ СОВЕРШЕНИЕ СДЕЛОК if (!OpenBuyLimitOrder1(BUY_Sign, 1, Money_Management_Up, STOPLOSS_Up, TAKEPROFIT_Up, PriceLevel_Up, StopTime_Up)) return(-1); if (ClosePos_Up) if (!CloseOrder1(BUY_Stop, 1)) return(-1); } } //----++ КОД ДЛЯ КОРОТКИХ ПОЗИЦИЙ if (Test_Dn) { int IBARS_Dn = iBars(NULL, Timeframe_Dn); if (IBARS_Dn >= MinBar_Dn) { if (LastBars_Dn != IBARS_Dn) { //----+ Иницмализация переменных SELL_Sign = false; SELL_Stop = false; LastBars_Dn = IBARS_Dn; StopTime_Dn = iTime(NULL, Timeframe_Dn, 0) + 60 * Timeframe_Dn; //----+ ВЫЧИСЛЕНИЕ ИНДИКАТОРНЫХ ЗНАЧЕНИЙ И ЗАГРУЗКА ИХ В БУФЕРЫ for(bar = 1; bar < 3; bar++) MovA[bar - 1] = iMACD(NULL, Timeframe_Dn, FST_period_Dn, FST_period_Dn + SLO_period_Dn, SIGN_period_Dn, Price_Dn, 0, bar); for(bar = 1; bar < 3; bar++) MovB[bar - 1] = iMACD(NULL, Timeframe_Dn, FST_period_Dn, FST_period_Dn + SLO_period_Dn, SIGN_period_Dn, Price_Dn, 1, bar); //----+ ОПРЕДЕЛЕНИЕ СИГНАЛОВ ДЛЯ СДЕЛОК if ( MovA[1] > MovB[1]) if ( MovA[0] < MovB[0]) SELL_Sign = true; if ( MovA[0] < MovB[0]) SELL_Stop = true; } //----+ СОВЕРШЕНИЕ СДЕЛОК if (!OpenSellLimitOrder1(SELL_Sign, 2, Money_Management_Dn, STOPLOSS_Dn, TAKEPROFIT_Dn, PriceLevel_Dn, StopTime_Dn)) return(-1); if (ClosePos_Dn) if (!CloseOrder1(SELL_Stop, 2)) return(-1); } } //----+ return(0); } //+------------------------------------------------------------------+
Заключение
Очередная статья подходит к своему концу. Очередная торговая система была абсолютно без проблем реализована в экспертах на абсолютно разных вариантах индикаторов. Я надеюсь, что данная статья окажется полезной начинающим экспертописателям для дальнейшего развития навыков превращения первоначально правильно формализованных алгоритмов, лежащих в основе торговых систем, в готовый и абсолютно рабочий код ваших экспертов.