
Создаем новую торговую стратегию с использованием технологии разложения входов на индикаторы
Введение
Не секрет, что только 5% трейдеров стабильно получают доход на финансовых рынках, при этом попасть в заветную цифру хотят все 100%.
Для успешного трейдинга нужна прибыльная стратегия торговли. На тематических сайтах и в литературе по трейдингу описаны сотни различных торговых стратегий, ко всем индикаторам прилагается развернутая трактовка их сигналов, но статистика остается неизменной: 5% не превратились ни в 100, ни хотя бы в 10. Идеологи трейдинга сетуют на изменчивость рынка, из-за которой ранее прибыльные стратегии теряют эффективность.
В своей предыдущей статье я уже рассказывал о разложении входов на индикаторы и показал пример совершенствования существующей стратегии. Теперь я предлагаю создать свою собственную стратегию "с чистого листа", используя описанную технологию. Это позволит взглянуть на известные вам индикаторы "новыми глазами", собрать свой шаблон индикаторов и переосмыслить их сигналы. Использование предлагаемой технологии подразумевает творческий подход к трактовке сигналов индикаторов, что позволяет каждому пользователю создать свою уникальную стратегию.
1. Создание модели для тестирования и анализа
Первое, что мы видим в торговом терминале, — постоянное движение цены. Потенциально, открыв сделку в любой момент, мы можем получить прибыль. Но как определить, куда и с какой интенсивностью цена направится в следующий момент? Ответ на этот вопрос трейдеры пытаются найти в техническом и фундаментальном анализе. Для проведения технического анализа постоянно изобретаются и совершенствуются различные индикаторы. Ими я и предлагаю воспользоваться, и в этом нет ничего нового. Новизна здесь — в трактовке сигналов этих индикаторов, которая может отличаться от привычной.
Итак, технология разложения входов на индикаторы подразумевает сопоставление открытых позиций с показаниями индикаторов. Повторюсь, потенциально прибыль мы можем получить в любой момент. Исходя из этих вводных, в начале каждой свечи мы будем открывать две разнонаправленные позиции с заданными параметрами. Затем проанализируем, как прибыльность каждой сделки зависит от показаний индикаторов.
Чтобы решить эту задачу, проведем небольшую подготовительную работу.
1.1. Создание класса виртуальных ордеров
Я использую нетинговый счет. Поэтому для открытия разнонаправленных ордеров мне нужно будет создать виртуальные ордера, которые будут отслеживаться не терминалом (в соответствии с настройками счета), а советником. Для этого создадим класс CDeal. При инициализации экземпляра класса мы будем передавать в него наименование инструмента, тип позиции, время и цену открытия, а также стоп-лосс и тейк-профит. Объем позиции я опустил намеренно, так как в данной работе он нам неинтересен. Нам важно движение цены, поэтому прибыль/убыток будем считать не в денежном выражении, а в пунктах.
Для обслуживания класс был дополнен функциями проверки статуса позиции:
- IsClosed — возвращает логическое значение, закрыта позиция или нет;
- Type — возвращает тип позиции;
- GetProfit — возвращает прибыль закрытой позиции (для убыточной позиции значение будет отрицательным);
- GetTime — возвращает время открытия позиции.
class CDeal : public CObject { private: string s_Symbol; datetime dt_OpenTime; // Time of open position double d_OpenPrice; // Price of opened position double d_SL_Price; // Stop Loss of position double d_TP_Price; // Take Profit of position ENUM_POSITION_TYPE e_Direct; // Direct of opened position double d_ClosePrice; // Price of close position int i_Profit; // Profit of position in pips //--- double d_Point; public: CDeal(string symbol, ENUM_POSITION_TYPE type,datetime time,double open_price,double sl_price, double tp_price); ~CDeal(); //--- Check status bool IsClosed(void); ENUM_POSITION_TYPE Type(void) { return e_Direct; } double GetProfit(void); datetime GetTime(void) { return dt_OpenTime; } //--- void Tick(void); };
Поступающие тики будут обрабатываться в функции Tick, которая проверит и в случае необходимости закроет позицию по стоп-лоссу или тейк-профиту и сохранит накопленную прибыль.
void CDeal::Tick(void) { if(d_ClosePrice>0) return; double price=0; switch(e_Direct) { case POSITION_TYPE_BUY: price=SymbolInfoDouble(s_Symbol,SYMBOL_BID); if(d_SL_Price>0 && d_SL_Price>=price) { d_ClosePrice=price; i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point); } else { if(d_TP_Price>0 && d_TP_Price<=price) { d_ClosePrice=price; i_Profit=(int)((d_ClosePrice-d_OpenPrice)/d_Point); } } break; case POSITION_TYPE_SELL: price=SymbolInfoDouble(s_Symbol,SYMBOL_ASK); if(d_SL_Price>0 && d_SL_Price<=price) { d_ClosePrice=price; i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point); } else { if(d_TP_Price>0 && d_TP_Price>=price) { d_ClosePrice=price; i_Profit=(int)((d_OpenPrice-d_ClosePrice)/d_Point); } } break; } }
1.2. Создание класса для работы с индикаторами
Для сохранения и анализа данных индикаторов я использовал классы, описанные в предыдущей статье. Здесь же я создал класс CDealsToIndicators, обобщающий все индикаторные классы. Он будет хранить массивы индикаторных классов и организовывать их работу.
class CDealsToIndicators { private: CADX *ADX[]; CAlligator *Alligator[]; COneBufferArray *OneBuffer[]; CMACD *MACD[]; CStaticOneBuffer *OneBufferStatic[]; CStaticMACD *MACD_Static[]; CStaticADX *ADX_Static[]; CStaticAlligator *Alligator_Static[]; template<typename T> void CleareArray(T *&array[]); public: CDealsToIndicators(); ~CDealsToIndicators(); //--- bool AddADX(string symbol, ENUM_TIMEFRAMES timeframe, int period, string name); bool AddADX(string symbol, ENUM_TIMEFRAMES timeframe, int period, string name, int &handle); bool AddAlligator(string symbol,ENUM_TIMEFRAMES timeframe,uint jaw_period, uint jaw_shift, uint teeth_period, uint teeth_shift, uint lips_period, uint lips_shift, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price, string name); bool AddAlligator(string symbol,ENUM_TIMEFRAMES timeframe,uint jaw_period, uint jaw_shift, uint teeth_period, uint teeth_shift, uint lips_period, uint lips_shift, ENUM_MA_METHOD method, ENUM_APPLIED_PRICE price, string name, int &handle); bool AddMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price, string name); bool AddMACD(string symbol, ENUM_TIMEFRAMES timeframe, uint fast_ema, uint slow_ema, uint signal, ENUM_APPLIED_PRICE applied_price, string name, int &handle); bool AddOneBuffer(int handle, string name); //--- bool SaveNewValues(long ticket); //--- bool Static(CArrayObj *deals); };
1.3. Создание советника для тестирования
Мы всё подготовили. Теперь перейдем к созданию советника для работы в тестере стратегий. Сначала определимся с перечнем используемых индикаторов и их параметрами. Для демонстрации технологии я взял следующие индикаторы:
- ADX;
- Alligator;
- CCI;
- Chaikin;
- Force Index;
- MACD.
Для каждого из них созданы по 3 набора параметров, данные отслеживаются на трех таймфреймах.
Стоп-лосс и тейк-профит сделок я привязал к значениям индикатора ATR и задал через соотношение прибыли к риску.
//--- input parameters input double Reward_Risk = 1.0; input int ATR_Period = 288; input ENUM_TIMEFRAMES TimeFrame1 = PERIOD_M5; input ENUM_TIMEFRAMES TimeFrame2 = PERIOD_H1; input ENUM_TIMEFRAMES TimeFrame3 = PERIOD_D1; input string s1 = "ADX" ; //--- input uint ADX_Period1 = 14 ; input uint ADX_Period2 = 28 ; input uint ADX_Period3 = 56 ; input string s2 = "Alligator" ; //--- input uint JAW_Period1 = 13 ; input uint JAW_Shift1 = 8 ; input uint TEETH_Period1 = 8 ; input uint TEETH_Shift1 = 5 ; input uint LIPS_Period1 = 5 ; input uint LIPS_Shift1 = 3 ; input uint JAW_Period2 = 26 ; input uint JAW_Shift2 = 16 ; input uint TEETH_Period2 = 16 ; input uint TEETH_Shift2 = 10 ; input uint LIPS_Period2 = 10 ; input uint LIPS_Shift2 = 6 ; input uint JAW_Period3 = 42 ; input uint JAW_Shift3 = 32 ; input uint TEETH_Period3 = 32 ; input uint TEETH_Shift3 = 20 ; input uint LIPS_Period3 = 20 ; input uint LIPS_Shift3 = 12 ; input ENUM_MA_METHOD Alligator_Method = MODE_SMMA ; input ENUM_APPLIED_PRICE Alligator_Price = PRICE_MEDIAN ; input string s5 = "CCI" ; //--- input uint CCI_Period1 = 14 ; input uint CCI_Period2 = 28 ; input uint CCI_Period3 = 56 ; input ENUM_APPLIED_PRICE CCI_Price = PRICE_TYPICAL ; input string s6 = "Chaikin" ; //--- input uint Ch_Fast_Period1 = 3 ; input uint Ch_Slow_Period1 = 14 ; input uint Ch_Fast_Period2 = 6 ; input uint Ch_Slow_Period2 = 28 ; input uint Ch_Fast_Period3 = 12 ; input uint Ch_Slow_Period3 = 56 ; input ENUM_MA_METHOD Ch_Method = MODE_EMA ; input ENUM_APPLIED_VOLUME Ch_Volume = VOLUME_TICK ; input string s7 = "Force Index" ; //--- input uint Force_Period1 = 14 ; input uint Force_Period2 = 28 ; input uint Force_Period3 = 56 ; input ENUM_MA_METHOD Force_Method = MODE_SMA ; input ENUM_APPLIED_VOLUME Force_Volume = VOLUME_TICK ; input string s8 = "MACD" ; //--- input uint MACD_Fast1 = 12 ; input uint MACD_Slow1 = 26 ; input uint MACD_Signal1 = 9 ; input uint MACD_Fast2 = 24 ; input uint MACD_Slow2 = 52 ; input uint MACD_Signal2 = 18 ; input uint MACD_Fast3 = 48 ; input uint MACD_Slow3 = 104 ; input uint MACD_Signal3 = 36 ; input ENUM_APPLIED_PRICE MACD_Price = PRICE_CLOSE ;
В блоке глобальных переменных объявим:
- массив для хранения классов сделок Deals,
- экземпляр класса для работы с индикаторами IndicatorsStatic,
- переменную для хранения хэндла индикатора ATR,
- 2 служебные переменные для хранения времени последнего обработанного бара (last_bar) и последнего закрытого ордера (last_closed_deal). Последняя нам потребуется, чтобы не перебирать на каждом тике уже закрытые ранее позиции.
В функции OnInit проведем инициализацию глобальных переменных и требуемых индикаторных классов.
int OnInit() { //--- last_bar=0; last_closed_deal=0; //--- Deals = new CArrayObj(); if(CheckPointer(Deals)==POINTER_INVALID) return INIT_FAILED; //--- IndicatorsStatic = new CDealsToIndicators(); if(CheckPointer(IndicatorsStatic)==POINTER_INVALID) return INIT_FAILED; //--- atr=iATR(_Symbol,TimeFrame1,ATR_Period); if(atr==INVALID_HANDLE) return INIT_FAILED; //--- AddIndicators(TimeFrame1); AddIndicators(TimeFrame2); AddIndicators(TimeFrame3); //--- return(INIT_SUCCEEDED); }
Мы будем использовать одинаковый набор индикаторов на трех разных таймфреймах. Поэтому есть смысл вывести инициализацию индикаторных классов в отдельную функцию AddIndicators. В ее параметрах будем передавать требуемый таймфрейм.
bool AddIndicators(ENUM_TIMEFRAMES timeframe) { if(CheckPointer(IndicatorsStatic)==POINTER_INVALID) { IndicatorsStatic = new CDealsToIndicators(); if(CheckPointer(IndicatorsStatic)==POINTER_INVALID) return false; } string tf_name=StringSubstr(EnumToString(timeframe),7); string name="ADX("+IntegerToString(ADX_Period1)+") "+tf_name; if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period1, name)) return false; name="ADX("+IntegerToString(ADX_Period2)+") "+tf_name; if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period2, name)) return false; name="ADX("+IntegerToString(ADX_Period3)+") "+tf_name; if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period3, name)) return false; name="Alligator("+IntegerToString(JAW_Period1)+","+IntegerToString(TEETH_Period1)+","+IntegerToString(LIPS_Period1)+") "+tf_name; if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period1, JAW_Shift1, TEETH_Period1, TEETH_Shift1, LIPS_Period1, LIPS_Shift1, Alligator_Method, Alligator_Price, name)) return false; name="Alligator("+IntegerToString(JAW_Period2)+","+IntegerToString(TEETH_Period2)+","+IntegerToString(LIPS_Period2)+") "+tf_name; if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period2, JAW_Shift2, TEETH_Period2, TEETH_Shift2, LIPS_Period2, LIPS_Shift2, Alligator_Method, Alligator_Price, name)) return false; name="Alligator("+IntegerToString(JAW_Period3)+","+IntegerToString(TEETH_Period3)+","+IntegerToString(LIPS_Period3)+") "+tf_name; if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period3, JAW_Shift3, TEETH_Period3, TEETH_Shift3, LIPS_Period3, LIPS_Shift3, Alligator_Method, Alligator_Price, name)) return false; name="MACD("+IntegerToString(MACD_Fast1)+","+IntegerToString(MACD_Slow1)+","+IntegerToString(MACD_Signal1)+") "+tf_name; if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast1, MACD_Slow1, MACD_Signal1, MACD_Price, name)) return false; name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name; if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name)) return false; name="MACD("+IntegerToString(MACD_Fast3)+","+IntegerToString(MACD_Slow3)+","+IntegerToString(MACD_Signal3)+") "+tf_name; if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast3, MACD_Slow3, MACD_Signal3, MACD_Price, name)) return false; name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name; int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price); if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; name="CCI("+IntegerToString(CCI_Period2)+") "+tf_name; handle = iCCI(_Symbol, timeframe, CCI_Period2, CCI_Price); if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iCCI(_Symbol, timeframe, CCI_Period3, CCI_Price); name="CCI("+IntegerToString(CCI_Period3)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iForce(_Symbol, timeframe, Force_Period1, Force_Method, Force_Volume); name="Force("+IntegerToString(Force_Period1)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iForce(_Symbol, timeframe, Force_Period2, Force_Method, Force_Volume); name="Force("+IntegerToString(Force_Period2)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume); name="Force("+IntegerToString(Force_Period3)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; name="CHO("+IntegerToString(Ch_Slow_Period1)+","+IntegerToString(Ch_Fast_Period1)+") "+tf_name; handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period1, Ch_Slow_Period1, Ch_Method, Ch_Volume); if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume); name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period3, Ch_Slow_Period3, Ch_Method, Ch_Volume); name="CHO("+IntegerToString(Ch_Slow_Period3)+","+IntegerToString(Ch_Fast_Period3)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; return true; }
Операции, выполняемые в функции OnTick, можно разделить на 2 блока: проверка открытых позиций и открытие новых позиций.
Первый блок операций выполняется на каждом тике. В нем из массива поочередно извлекаются все ранее открытые сделки, и для каждой из них вызывается функция Tick. Она проверяет срабатывание стоп-лосса и тейк-профита позиции и, при необходимости, ордер закрывается по текущей цене с сохранением накопленной прибыли. Чтобы снова не проверять ранее закрытые сделки, в переменной last_closed_deal сохраняется номер сделки, предшествующей первой незакрытой.
void OnTick() { //--- int total=Deals.Total(); CDeal *deal; bool found=false; for(int i=last_closed_deal;i<total;i++) { deal = Deals.At(i); if(CheckPointer(deal)==POINTER_INVALID) continue; if(!found) { if(deal.IsClosed()) { last_closed_deal=i; continue; } else found=true; } deal.Tick(); }
Второй блок операций начинается с проверки наступления нового бара. В начале каждого бара загружаем значение индикатора ATR на последней закрытой свече, рассчитываем уровни стоп-лосса и тейк-профита в соответствии с заданными параметрами и открываем виртуальные позиции. Для каждой позиции сохраняем данные индикаторов, вызвав функцию SaveNewValues нашего класса работы с индикаторами.
//--- datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE); datetime cur_time=TimeCurrent(); if(cur_bar==last_bar || (cur_time-cur_bar)>10) return; double atrs[]; if(CopyBuffer(atr,0,1,1,atrs)<=0) return; last_bar=cur_bar; double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point); double sl=NormalizeDouble(stops,_Digits); double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits); deal = new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); deal = new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); return; }
В функции OnTester будем собирать результаты тестового прохода и строить графики для анализа. Для этого вызовем функцию Static класса работы с индикаторами.
Не забываем подчистить память в функции OnDeinit!
С полным кодом советника и используемых классов можно ознакомиться во вложении.
2. Анализ результатов тестирования
Итак, мы создали тестовый советник. Теперь определимся с анализируемым периодом. При выборе периода надо учесть, что он должен быть достаточно долгим, чтобы обеспечить объективность анализа. И еще одно требование к периоду: он должен включать не только однонаправленные движения, но и периоды разнонаправленных трендовых движений, и боковых (флэтовых). Такой подход позволит создать торговую стратегию, способную генерировать прибыль в периоды любых движений. В моем примере проанализирована пара EURUSD за период с 1/01/2016 по 1/10/2017.
Поскольку наш процесс будет носить итерационный характер, я бы рекомендовал после установки всех необходимых параметров тестового советника сохранить set-файл параметров для дальнейшей работы.
Каждый этап тестирования будем проводить в 2 прохода с соотношением прибыль/риск равным 1/1 и 15/1. По первому проходу будем оценивать вероятность направленного движения, а по второму проходу — силу движения.
Советник выводит очень много графиков для анализа, поэтому я не привожу их в статье полностью — все отчеты есть во вложении. Здесь я лишь покажу те графики, по которым принимались решения об использовании индикаторов в новой стратегии.
2.1. Первый этап
Как и ожидалось, первый этап тестирования не показал нам четких прибыльных зон. Но в то же время стоит обратить внимание на индикатор индекса силы. На таймфрейме М5 график зависимости прибыли сделок от значений индикатора резко падает в нулевой зоне. О важности этого наблюдения говорит тот факт, что это явление проявляется на аналитических графиках индикатора со всеми используемыми для тестирования параметрами. Для своего шаблона мы выбираем параметры с наиболее выраженным характером явления (максимальной просадкой).
Увеличим масштаб анализируемого графика. Видим, что влияние этого фактора наблюдается в диапазоне от -0.01 до 0.01. Наблюдаемое явление одинаково верно как для сделок на покупку, так и на продажу.
Это наблюдение можно объяснить отсутствием волатильности в наблюдаемом диапазоне значений. Мы же для своей стратегии отметим запрет на открытие каких-либо ордеров в данном диапазоне.
Добавим этот фильтр в наш советник. Для этого сначала добавим глобальную переменную для хранения хэндла индикатора.
int force;
Поскольку индикатор, необходимый нам в качестве фильтра, уже используется в советнике, мы не будем прикреплять его к графику повторно. Просто скопируем его хэндл в нашу глобальную переменную в функции AddIndicators. Но следует помнить, что эта функция вызывается трижды для инициализации индикаторов на разных таймфреймах. Следовательно, перед копированием хэндла индикатора нам нужно проверить соответствие таймфрейма.
handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume); if(timeframe==TimeFrame1) force=handle;
Теперь добавим фильтр непосредственно в функцию OnTick. При построении фильтра следует помнить, что в функции построения аналитического графика данные были округлены. Поэтому при фильтрации сделок мы также должны предварительно округлить показания индикатора.
double atrs[]; double force_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0) return; // Some error of load indicator's data last_bar=cur_bar; double d_Step=_Point*1000; if(MathAbs(NormalizeDouble(force_data[0]/d_Step,0)*d_Step)<=0.01) return; // Filtered by Force Index
Полный код советника приведен во вложении к статье.
После добавления фильтра в советник проводим второй этап тестирования. Перед тестированием не забываем загрузить сохраненные ранее параметры.
2.2. Второй этап
После повторного тестирования советника я обратил внимание на индикатор MACD. На графике появились прибыльные зоны.
На графике с соотношением прибыль/риск 15/1 эти зоны более ярко выражены, что может свидетельствовать о потенциале сигналов в этих диапазонах.
Добавим и этот фильтр в код нашего советника. Логика добавления фильтра аналогична приведенной в описании первого этапа.
В глобальные переменные:
int macd;
В функцию AddIndicators:
name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name; if(timeframe==TimeFrame1) { if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name, macd)) return false; } else { if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name)) return false; }
В функцию OnTick:
double macd_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0) return;
и
double macd_Step=_Point*50; macd_data[0]=NormalizeDouble(macd_data[0]/macd_Step,0)*macd_Step; if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035) { deal = new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); } if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035)) { deal = new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); }
После добавления фильтра переходим к третьему этапу тестирования.
2.3. Третий этап
На третьем этапе я обратил внимание на осциллятор Чайкина. На аналитических графиках осциллятора на тайфрейме D1 видим рост прибыли по длинным позициям при снижении значений, а при росте значений индикатора растет прибыль по коротким позициям.
Мое наблюдение подтверждается и при анализе графиков с соотношением прибыль/риск равным 15/1.
Добавим наше наблюдение в код советника.
В глобальные переменные:
int cho;
В функцию AddIndicators:
handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume); name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name; if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; if(timeframe==TimeFrame3) cho=handle;
В функцию OnTick:
double cho_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0 || CopyBuffer(cho,0,1,2,cho_data)<2) return;
и
if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0) { deal = new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); } if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0) { deal = new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); }
Переходим к следующему этапу.
2.4. Четвертый этап
После очередного тестирования советника мое внимание вновь привлек таймфрейм D1. На этот раз я рассмотрел индикатор CCI. Его аналитические графики показали рост прибыли по коротким позициям при снижении значений индикатора и рост прибыли длинных позиций — с ростом значений индикатора. Эта тенденция наблюдалась на всех трех исследуемых периодах, но максимальная прибыль достигалась при использовании периода 14, стандартного для этого осциллятора.
Аналитические графики, полученные при тестировании с соотношением прибыль/риск, равным 15/1, подтверждают наше наблюдение.
Добавим и это наблюдение в код тестового советника.
В глобальные переменные:
int cci;
В функцию AddIndicators:
name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name; int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price); if(handle<0 || !IndicatorsStatic.AddOneBuffer(handle, name) ) return false; if(timeframe==TimeFrame3) cci=handle;
В функцию OnTick:
double cci_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0 || CopyBuffer(cho,0,1,2,cho_data)<2 || CopyBuffer(cci,0,1,2,cci_data)<2) return;
и
if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0 && (cci_data[1]-cci_data[0])>0) { deal = new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); } if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0 && (cci_data[1]-cci_data[0])<0) { deal = new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp); if(CheckPointer(deal)!=POINTER_INVALID) if(Deals.Add(deal)) IndicatorsStatic.SaveNewValues(Deals.Total()-1); }
С полным кодом советников на всех этапах можно ознакомиться во вложении к статье.
3. Создание и тестирования советника по выбранным сигналам
Нет пределов совершенству, можно и далее анализировать и добавлять фильтры для повышения прибыльности стратегии. Но я думаю, что продемонстрированных четырех этапов вполне достаточно для демонстрации технологии. Следующим шагом мы создадим простой советник для проверки нашей стратегии в тестере. Это позволит оценить доходность и просадки нашей стратегии, а также изменение баланса в динамике.
В стратегии мы использовали 4 индикатора для принятия решения о сделке и индикатор ATR для установки стоп-лосса и тейк-профита. Следовательно, во входных параметрах советника мы должны задать всю необходимую для индикаторов исходную информацию. Мани-менеджмент на данном этапе мы создавать не будем, все ордера будут использовать фиксированный объем.
//--- input parameters input double Lot = 0.1 ; input double Reward_Risk = 15.0 ; input ENUM_TIMEFRAMES ATR_TimeFrame = PERIOD_M5 ; input int ATR_Period = 288 ; input string s1 = "CCI" ; //--- input ENUM_TIMEFRAMES CCI_TimeFrame = PERIOD_D1 ; input uint CCI_Period = 14 ; input ENUM_APPLIED_PRICE CCI_Price = PRICE_TYPICAL ; input string s2 = "Chaikin" ; //--- input ENUM_TIMEFRAMES Ch_TimeFrame = PERIOD_D1 ; input uint Ch_Fast_Period = 6 ; input uint Ch_Slow_Period = 28 ; input ENUM_MA_METHOD Ch_Method = MODE_EMA ; input ENUM_APPLIED_VOLUME Ch_Volume = VOLUME_TICK ; input string s3 = "Force Index" ; //--- input ENUM_TIMEFRAMES Force_TimeFrame = PERIOD_M5 ; input uint Force_Period = 56 ; input ENUM_MA_METHOD Force_Method = MODE_SMA ; input ENUM_APPLIED_VOLUME Force_Volume = VOLUME_TICK ; input string s4 = "MACD" ; //--- input ENUM_TIMEFRAMES MACD_TimeFrame = PERIOD_M5 ; input uint MACD_Fast = 12 ; input uint MACD_Slow = 26 ; input uint MACD_Signal = 9 ; input ENUM_APPLIED_PRICE MACD_Price = PRICE_CLOSE ;
В глобальных переменных объявим:
- экземпляр класса для совершения торговых операций,
- переменные для хранения хэндлов используемых индикаторов,
- вспомогательные переменные для запоминания дат последнего обработанного бара и последней сделки,
- переменные для хранения максимального и минимального таймфрейма.
В функции OnInit инициализируем индикаторы и зададим начальные значения переменных.
int OnInit() { //--- last_bar=0; last_deal=0; //--- atr=iATR(_Symbol,ATR_TimeFrame,ATR_Period); if(atr==INVALID_HANDLE) return INIT_FAILED; //--- force=iForce(_Symbol,Force_TimeFrame,Force_Period,Force_Method,Force_Volume); if(force==INVALID_HANDLE) return INIT_FAILED; //--- macd=iMACD(_Symbol,MACD_TimeFrame,MACD_Fast,MACD_Slow,MACD_Signal,MACD_Price); if(macd==INVALID_HANDLE) return INIT_FAILED; //--- cho=iChaikin(_Symbol,Ch_TimeFrame,Ch_Fast_Period,Ch_Slow_Period,Ch_Method,Ch_Volume); if(cho==INVALID_HANDLE) return INIT_FAILED; //--- cci=iCCI(_Symbol,CCI_TimeFrame,CCI_Period,CCI_Price); if(cci==INVALID_HANDLE) return INIT_FAILED; //--- MaxPeriod=fmax(Force_TimeFrame,MACD_TimeFrame); MaxPeriod=fmax(MaxPeriod,Ch_TimeFrame); MaxPeriod=fmax(MaxPeriod,CCI_TimeFrame); MinPeriod=fmin(Force_TimeFrame,MACD_TimeFrame); MinPeriod=fmin(MinPeriod,Ch_TimeFrame); MinPeriod=fmin(MinPeriod,CCI_TimeFrame); //--- return(INIT_SUCCEEDED); }
В функции OnDeinit закрываем используемые индикаторы.
void OnDeinit(const int reason) { //--- if(atr!=INVALID_HANDLE) IndicatorRelease(atr); //--- if(force==INVALID_HANDLE) IndicatorRelease(force); //--- if(macd==INVALID_HANDLE) IndicatorRelease(macd); //--- if(cho==INVALID_HANDLE) IndicatorRelease(cho); //--- if(cci==INVALID_HANDLE) IndicatorRelease(cci); }
Основные действия будут проводиться в функции OnTick. В начале функции проверим наступление нового бара. Новая позиция будет открываться только на открытии нового бара по минимальному таймфрейму (я ограничил 10 секунд от открытия бара) и только если еще не открывалась позиция в пределах текущего бара по максимальному тайфрейму. Так я ограничил открытие только одного ордера на один сигнал.
void OnTick() { //--- datetime cur_bar=(datetime)SeriesInfoInteger(_Symbol,MinPeriod,SERIES_LASTBAR_DATE); datetime cur_max=(datetime)SeriesInfoInteger(_Symbol,MaxPeriod,SERIES_LASTBAR_DATE); datetime cur_time=TimeCurrent(); if(cur_bar<=last_bar || (cur_time-cur_bar)>10 || cur_max<=last_deal) return;
Далее получаем данные используемых индикаторов. В случае ошибки получения данных хотя бы одного из индикаторов выходим из функции.
last_bar=cur_bar; double atrs[]; double force_data[]; double macd_data[]; double cho_data[]; double cci_data[]; if(CopyBuffer(atr,0,1,1,atrs)<=0 || CopyBuffer(force,0,1,1,force_data)<=0 || CopyBuffer(macd,0,1,1,macd_data)<=0 || CopyBuffer(cho,0,1,2,cho_data)<2 || CopyBuffer(cci,0,1,2,cci_data)<2) { return; }
Затем, в соответствии с нашей стратегией, проверяем значение индекса силы. Если оно не удовлетворяет нашему фильтру, выходим из функции до открытия следующего бара.
double force_Step=_Point*1000; if(MathAbs(NormalizeDouble(force_data[0]/force_Step,0)*force_Step)<=0.01) return;
Следующим этапом проверяем сигнал на открытие длинной позиции. Если есть положительный сигнал, проверяем, нет ли уже открытой позиции. Если есть, и она короткая, закрываем ее. Если уже открыта длинная позиция, которая находится в убытке, то игнорируем сигнал и выходим из функции.
После этого рассчитываем параметры для новой позиции и отправляем ордер.
Аналогичные операции проводим для короткой позиции.
double macd_Step=_Point*50; macd_data[0]=NormalizeDouble(macd_data[0]/macd_Step,0)*macd_Step; if(macd_data[0]>=0.0015 && macd_data[0]<=0.0035 && (cho_data[1]-cho_data[0])<0 && (cci_data[1]-cci_data[0])>0) { if(PositionSelect(_Symbol)) { switch((int)PositionGetInteger(POSITION_TYPE)) { case POSITION_TYPE_BUY: if(PositionGetDouble(POSITION_PROFIT)<=0) return; break; case POSITION_TYPE_SELL: Trade.PositionClose(_Symbol); break; } } last_deal=cur_max; double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point); double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double sl=NormalizeDouble(stops,_Digits); double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits); double SL=NormalizeDouble(bid-sl,_Digits); double TP=NormalizeDouble(ask+tp,_Digits); if(!Trade.Buy(Lot,_Symbol,ask,SL,TP,"New Strategy")) Print("Error of open BUY ORDER "+Trade.ResultComment()); } if(macd_data[0]<=(-0.0015) && macd_data[0]>=(-0.0035) && (cho_data[1]-cho_data[0])>0 && (cci_data[1]-cci_data[0])<0) { if(PositionSelect(_Symbol)) { switch((int)PositionGetInteger(POSITION_TYPE)) { case POSITION_TYPE_SELL: if(PositionGetDouble(POSITION_PROFIT)<=0) return; break; case POSITION_TYPE_BUY: Trade.PositionClose(_Symbol); break; } } last_deal=cur_max; double stops=MathMax(2*atrs[0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point); double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); double sl=NormalizeDouble(stops,_Digits); double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits); double SL=NormalizeDouble(ask+sl,_Digits); double TP=NormalizeDouble(bid-tp,_Digits); if(!Trade.Sell(Lot,_Symbol,bid,SL,TP,"New Strategy")) Print("Error of open SELL ORDER "+Trade.ResultComment()); } return; }
С полным кодом советника можно ознакомиться во вложении.
После подготовки советника мы можем провести тестирование нашей стратегии. Чтобы отойти от "подгонки стратегии под период", расширим тестируемый период: протестируем стратегию с 1/01/2015 по 1/12/2017. Стартовый капитал тестирования будет 10 000 USD, размер сделки — 1 лот.
По результатам тестирования советник показал прибыль в 74.8% при максимальных просадках по балансу в 12.4% и по эквити в 23.8%. Всего было произведено 44 трейда (22 коротких позиции и 22 длинных). Доля прибыльных позиций составляет 18.2% и одинакова как для коротких, так и для длинных позиций. Такой низкий процент прибыльных позиций обусловлен использованием высокого соотношения ожидаемой прибыли к риску (15:1) и оставляет возможности для дальнейшего совершенствования стратегии.
Заключение
В статье продемонстрирована технология создания торговой стратегии "с чистого листа" с использованием метода разложения входов на индикаторы. Получившаяся в итоге стратегия способна генерировать прибыль в течение длительного периода, что подтверждено тестированием на протяжении 3 лет. Несмотря на то, что при создании стратегии использовались индикаторы из стандартной поставки MetaTrader, сигналы на совершение сделок далеки от сигналов, описанных в литературе для взятых индикаторов. Предложенная технология дает возможность творческого подхода для использования индикаторов в торговых стратегиях и не ограничивается взятыми индикаторами. Можно использовать любые пользовательские индикаторы и варианты оценки качества их сигналов.
Ссылки
Программы, используемые в статье:
# |
Имя |
Тип |
Описание |
---|---|---|---|
New_Strategy_Gizlyk.zip | |||
1 | NewStrategy1.mq5 | Советник | Советник для проведения первого этапа создания стратегии |
2 | NewStrategy2.mq5 | Советник | Советник для проведения второго этапа создания стратегии |
3 | NewStrategy3.mq5 | Советник | Советник для проведения третьего этапа создания стратегии |
4 | NewStrategy4.mq5 | Советник | Советник для проведения четвертого этапа создания стратегии |
5 | NewStrategy_Final.mq5 | Советник | Советник тестирования стратегии |
6 | DealsToIndicators.mqh | Библиотека класса | Класс для работы с индикаторными классами |
7 | Deal.mqh | Библиотека класса | Класс для сохранения информации о сделке |
8 | Value.mqh | Библиотека класса | Класс для сохранения данных о состоянии индикаторного буфера |
9 | OneBufferArray.mqh | Библиотека класса | Класс для сохранения истории данных одно буферного индикатора |
10 | StaticOneBuffer.mqh | Библиотека класса | Класс для сбора и анализа статистики одно буферного индикатора |
11 | ADXValue.mqh | Библиотека класса | Класс для сохранения данных о состоянии индикатора ADX |
12 | ADX.mqh | Библиотека класса | Класс для сохранения истории данных индикатора ADX |
13 | StaticADX.mqh | Библиотека класса | Класс для сбора и анализа статистики индикатора ADX |
14 | AlligatorValue.mqh | Библиотека класса | Класс для сохранения данных о состоянии индикатора Alligator |
15 | Alligator.mqh | Библиотека класса | Класс для сохранения истории данных индикатора Alligator |
16 | StaticAlligator.mqh | Библиотека класса | Класс для сбора и анализа статистики индикатора Alligator |
17 | MACDValue.mqh | Библиотека класса | Класс для сохранения данных о состоянии индикатора MACD |
18 | MACD.mqh | Библиотека класса | Класс для сохранения истории данных индикатора MACD |
19 | StaticMACD.mqh | Библиотека класса | Класс для сбора и анализа статистики индикатора MACD |
Common.zip | |||
20 | NewStrategy1_Report_1to1_2016-17.html | Файл интернет | Аналитические графики первого этапа создания стратегии, прибыль/риск = 1/1 |
21 | NewStrategy1_Report_15to1_2016-17.html | Файл интернет | Аналитические графики первого этапа создания стратегии, прибыль/риск = 15/1 |
22 | NewStrategy2_Report_1to1_2016-17.html | Файл интернет | Аналитические графики второго этапа создания стратегии, прибыль/риск = 1/1 |
23 | NewStrategy2_Report_15to1_2016-17.html | Файл интернет | Аналитические графики второго этапа создания стратегии, прибыль/риск = 15/1 |
24 | NewStrategy3_Report_1to1_2016-17.html | Файл интернет | Аналитические графики третьего этапа создания стратегии, прибыль/риск = 1/1 |
25 | NewStrategy3_Report_15to1_2016-17.html | Файл интернет | Аналитические графики третьего этапа создания стратегии, прибыль/риск = 15/1 |
26 | NewStrategy4_Report_1to1_2016-17.html | Файл интернет | Аналитические графики четвертого этапа создания стратегии, прибыль/риск = 1/1 |
27 | NewStrategy4_Report_15to1_2016-17.html | Файл интернет | Аналитические графики четвертого этапа создания стратегии, прибыль/риск = 15/1 |
28 | NewStrategy_Final_Report.html | Файл интернет | Отчет тестирования стратегии |





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования