Как преодолеть ограничения тестера стратегий при тестировании хеджевых советников
Введение
В данной статье приводятся идеи по тестированию хеджевых экспертов в тестере стратегий. Как Вы знаете, в тестере стратегий имеются ограничения, не позволяющие открывать ордера по другим символам (нет портфельного тестирования). Если есть необходимость протестировать собственный хеджевый эксперт, придется тестировать его на реальном счете. Значит ли это что наши возможности ограничены? Естественно, хеджевым трейдерам необходимо тестировать собственные эксперты перед тем, как использовать их на реальном счете. В данной статье приводятся идеи по созданию виртуального эмулятора тестера стратегий. Надеюсь, это поможет нам преодолеть ограничения в тестере стратегий MetaTrader 4 и окажется полезным при дальнейшем использовании.
Концепция виртуального тестера
Идея создания виртуального тестера возникла у меня при работе с функцией "Файлы" в mq4. Мне в голову пришла идея об использовании важных данных из файла для создания виртуальной торговой системы. Может ли это быть ответом на вопрос о тестировании собственного хеджевого советника? Давайте попробуем.
Мой виртуальный тестер не требует никакого дополнительного программного обеспечения. Достаточно параметров mq4. Концепция этого виртуального тестера заключается в том, чтобы данные параметры открытия или закрытия хеджевых ордеров указывали на сбор необходимых данных. Таких как цена открытия, время открытия, цена закрытия, время закрытия и другие важные данные. После того как необходимые данные собраны, они будут использованы для сравнения со значениями последних тиков любых приемлемых типов, например цена открытия и последний Bid или цена открытия и последний известный Ask. Эти значения приведут нас к способу вычисления прибыли, который будет указывать на сбор новой группы данных после выполнения условия закрытия хеджа.
Эти группы данных будут экспортированы в файл для дальнейшего использования. После окончания тестирования, когда все данные будут собраны в файл, мы сможем увидеть, как выполнен хеджевый эксперт. Использовав данные из файла в качестве индикатора для выстраивания кривой, я думаю, мы сможем завершить виртуальное тестирование нашего хеджевого эксперта.
Я полагаю, используя данную концепцию, мы можем получить результаты тестирования, схожие с результатами реального тестера стратегий. Кстати, это только идея по созданию тестера хеджевых советников. Я не могу гарантировать, что он будет в точности таким же, как реальный тестер. Но, надеюсь, он окажется полезным при дальнейшем использовании.
Итак, начнем.
Простое значение хеджевой торговли
Прежде чем начать, давайте немного поговорим о хеджевой торговле.
Самый простой способ рассказать о хеджинге - это одновременно открыть две противоположные позиции по двум валютным парам. Это позволяет уменьшить риск: если одна пара идет вверх, то ожидается, что вторая пойдет вниз. Однако нет повода для беспокойства - так как у нас одновременно открыты ордеры на покупку и продажу, то даже если один ордер убыточный, второй обязательно прибыльный. Поэтому мы называем это стратегией с низким уровнем риска. Вообще в мире Форекс существует множество видов противоположных стилей торговли.
- Для двух валютных пар, которые всегда движутся в одном направлении, например, EURUSD и GBPUSD, одновременное открытие buy-EURUSD и sell-GBPUSD является хеджем.
- Для двух валютных пар, которые всегда движутся в противоположных направлениях, как EURUSD и USDCHF, открытие buy-EURUSD и buy-USDCHF также является хеджем.
- Или даже одновременное открытие продажи и покупки EURUSD также является хеджем, но иногда это называется "арбитраж".
В хеджевой торговле есть несколько неоспоримых фактов:
- Корреляция: это статистическая единица измерения отношений между двумя валютами. Коэффициент корреляции находится в диапазоне между -1 и +1. Корреляция +1 подразумевает, что две валютные пары будут двигаться в одном направлении 100% времени. Корреляция -1 подразумевает, что две валютные пары будут двигаться в противоположных направлениях 100% времени. Корреляция нуля означает, что отношения между валютными парами является абсолютно случайными (больше информации здесь).
- Соотношение размеров лота: Для торговли двумя валютными парам, которые не двигаются ни в одном, ни в противоположных направлениях, необходимо соотношение лотов, потому что их собственная волатильность и способность двигаться различается так же, как и у черепахи и кролика, если одну пару принять за черепаху, а другую за кролика. Размер лота может уменьшить риск от пары с более сильным движением, т.е. пары-кролика, установив больший размер лота для пары-черепахи, чтобы обеспечить меньшие потери на случай негативного движения пары-кролика. Тогда можно получить большую прибыль от положительного движенья черепахи, другими словами убыточный кролик перекрывается прибыльной черепахой. Таким образом, хеджевые инструменты гарантируют, что Вы не потеряете больше, чем при открытии только одной позиции в отрицательном направлении.
Кстати, Вы никогда не задумывались, как трейдеры, использующие хеджевые стратегии, получают прибыль при подобном стиле торговли? Не переживайте, две пары всегда будут частично перекрываться, корреляция по природе своей не является постоянной, одна из двух пар всегда будет запаздывать - одна начинает движение, вторая следует за ней. Как в истории про кролика и черепаху: кролик приляжет отдохнуть, а черепаха обгонит его и выиграет. Поэтому трейдеры, использующие хеджевые стратегии, нередко получают хорошую прибыль от подобных стратегий. Сейчас многие трейдеры используют хеджевые стратегии для получения прибыли на рынках Форекс. Открываем хедж, ждем, закрываем, когда покажется неплохая прибыль. Вот и все.
Концепция хеджирования
Перед тем, как начать писать код виртуального тестера, попробуем понять концепцию хеджа, поставив эксперимент. Не поняв концепции хеджа, мы не узнаем, какие данные необходимо экспортировать, записывать и высчитывать. Эти данные покажут нам, какие типы ордеров надо открывать виртуально. В эксперименте будем использовать следующие правила хеджирования:
- Открывать хедж ежедневно в начале дня
- Закрывать при достижении $100 (размер лота установим 1 и 2)
- Ежечасно собирать данные о тиковых ценах***
- Очищать данные ежедневно в начале дня, даже если не достигнут установленный профит
- Покупать только EURJPY 2 лота и продавать GBPJPY 1 лот
Согласно этим правилам, для виртуальных ордеров нужны данные о ежедневных ценах открытия (для обеих пар), которые будут использоваться как цены открытия ордеров. Для вычисления внутри-дневной прибыли цены каждого часа, тиковые цены, должны записываться в качестве данных для цен закрытия ордеров (ask для продажи и bid для покупки). Также необходимо записывать время тиков (чтобы удостовериться, что тиковые цены взяты для одних и тех же временных значений). И согласно концепции ежедневного открытия хеджа, я разделю все необходимые данные в два типа файлов: ежедневные данные открытия и тиковые значения обеих пар. Оба типа данных будут экспортироваться в виде текстовых фалов с разными именами, например, GBPJPYD1.csv и GBPJPYTick.csv.
И так как мы хотели, чтобы тиковые данные виртуального тестера были максимально схожими с реальными данными, необходимо пройти следующие два шага:
- Создание скрипта для экспорта в файл ежедневных цен открытия GBPJPY
- Создание скрипта для экспорта в файл ежедневных тиковых цен GBPJPY
Оба шага также должны быть пройдены для пары EURJPY.
Но я думаю мы можем объединить их в один советник. Этот советник должен экспортировать оба типа данных в два отдельных файла. После того как данный советник завершит процесс записи данных, другой эксперт воспользуется данными о GBPJPY и EURJPY из всех экспортированных файлов для создания виртуального тестирования.
Три шага преодоления ограничений тестера
Согласно вышеизложенной идее, можно сделать вывод, что преодолеть ограничения тестера можно в три шага:
- Выбрать необходимые ценовые данные и сохранить их в файлы с помощью советника.
- Организовать виртуальную торговлю с помощью другого советника, который также экспортирует результаты в виде файла.
- Отобразить результаты в виде индикатора в отдельном окне.
Начнем с первого шага.
Шаг 1: Экспортируем ценовые данные
Здесь представлен советник, экспортирующий ежедневные цены открытия соответствующего инструмента в файл "GBPJPYD1.csv" для GBPJPY и в файл "EURJPYD1.csv" для EURJPY, между тем он также будет экспортировать тиковые цены в файл "symbolT. csv" (по аналогии с D1 файлами). Работа эксперта раскрыта в комментариях.
Обратите внимание: все файлы, созданные данным экспертом, будут экспортироваться в директорию "MetaTrader 4/tester/files".
//+------------------------------------------------------------------+ //| symbol-D1.mq4 | //| A Sexy Trader | //| http://pipsmaker.wordpress.com/ | //+------------------------------------------------------------------+ #property copyright "A Sexy Trader" #property link "http://pipsmaker.wordpress.com/" #include <stdlib.mqh> extern string StartDate = "2007.03.17" //установите начальную дату для получения одинаковых данных о времени ,StopDate = "2007.06.28"; //установите ограничение времени тестирования, чтобы избежать //запись излишних данных extern bool For_OP_SELL = true;/*Указывает на тип собираемых данных ->если For_OP_SELL = true ежедневный Open записывается в качестве цены открытия ордера ->если For_OP_SELL = false , означает для OP_BUY, записывается ежедневный Open+SPREAD. */ string name,tname; //+------------------------------------------------------------------+ //| функция инициализации эксперта | //+------------------------------------------------------------------+ int init() { //---- //---- return(0); } //+------------------------------------------------------------------+ //| функция деинициализации эксперта | //+------------------------------------------------------------------+ int deinit() { //---- //---- return(0); } //-------------------------------------------------------------------+ //| Некоторые важные параметры данного эксперта | //-------------------------------------------------------------------+ int day // переменная для обозначения, что сегодня данные Open записаны ,ho // идентификатор файла, записывающего цены открытия ,ht // идентификатор файла, записывающего тиковые цены ,x=1 /* порядок файла D1, увеличивающийся каждый раз, если D1 равен 4086 знакам и создается новый файл записи*/ ,xt=1 // так же, как и для x, но для тиковых данных ,bartime // переменная для обозначения, что данные Open для данного бара записаны ; double ot // время последнего открытия ,op // цена последнего открытия ,lt // время последнего тика ,ltk // цена последнего тика ; string OStr // строка для сбора дневных данных открытия ,TStr // строка для сбора тиковых значений ; //+------------------------------------------------------------------+ //| функция запуска эксперта | //+------------------------------------------------------------------+ int start() { /*-------------------Собирать данные только в определенные периоды времени.--------------------*/ if(TimeToStr(TimeCurrent(),TIME_DATE)>=StartDate && TimeToStr(TimeCurrent(),TIME_DATE)<=StopDate) { name=Symbol()+x+"D1.csv"; // установить имя файла с ежедневными ценами открытия tname=Symbol()+xt+"T.csv" // имя файла с тиковыим ценами ; //---- if(day!=TimeDay(Time[0])) // наступил новый день { ot=Time[0]; // берем время нового бара if(For_OP_SELL)op=Open[0]; // берем цену открытия нового ордера для SELL символа else op=Open[0]+MarketInfo(Symbol(),MODE_SPREAD)*Point; // иначе собираем данные для BUY символа OStr=OStr+TimeToStr(Time[0],TIME_DATE)+",";//запись новых данных времени, отделяем знаком "," OStr=OStr+DoubleToStr(op,Digits)+","; //новая цена открытия //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ экспортируем в файла ho=FileOpen(name ,FILE_CSV|FILE_WRITE); // открываем файл для записи дневного open if(ho>0) // если Symbol()+x+"D1.csv" существует { FileWrite(ho,OStr); // записываем собранные данные FileClose(ho); // закрываем файл каждый раз после завершения процесса if(StringLen(OStr)==4086){x++;OStr="";} /* если данные содержат 4086 знаков, устанавливаем новый x*/ } /* для создания нового файла, готовим новую строку для нового файла */ Print(TimeToStr(Time[0],TIME_DATE)); // распечатываем собранное время int thex=FileOpen(Symbol()+"x.csv",FILE_CSV|FILE_WRITE); // создаем файл "сколько x?" для файла D1 для дальнейшего исп-я if(thex>0) // если файл существует { string xs=DoubleToStr(x,0); // преобразуем x в строку FileWrite(thex,xs); // записываем значение x FileClose(thex); // закрываем файл (после каждого окончания) } day=TimeDay(Time[0]); // помечаем сегодня как собранное } //--------------------------------ДЛЯ ТИКОВЫХ ЗНАЧЕНИЙ /*Мы решили собирать тиковые данные ежечасно*/ if(bartime!=Time[0]) // открылся новый часовой бар // сделать его более гибким, если вы решили // собирать данные на другом таймфрейме { lt=TimeCurrent(); // получаем тиковое время if(!For_OP_SELL) ltk=Bid; // тиковые цены для ордеров на продажу else ltk=Ask; // в случае ордера на покупку TStr=TStr+TimeToStr(lt,TIME_DATE|TIME_MINUTES)+","; // вставляем данные в собранную строку TStr=TStr+DoubleToStr(ltk,Digits)+","; // //~~~~~~~~ // экспортируем данные ht=FileOpen(tname,FILE_CSV|FILE_WRITE); // открываем файл для записи тиковых значений if(ht>0) // если файл Symbol+xt+"T.csv" существует { FileWrite(ht,TStr); // записываем собранные тики и время FileClose(ht); // закончили. if(StringLen(TStr)==4080){xt++;TStr="";}// готовим новый файл, если в этом 4086 знаков } int thext=FileOpen(Symbol()+"xt.csv",FILE_CSV|FILE_WRITE); // записываем значение xt .. как D1 файл if(thext>0) // если файл существует { string xts=DoubleToStr(xt,0); // преобразуем в строку FileWrite(thext,xts); // записываем xt FileClose(thext); // готово } bartime=Time[0]; // отмечаем что цена Open текущего часового бара записана } } else if(TimeToStr(TimeCurrent(),TIME_DATE)>StopDate) // если вне указанного периода Print("Done."); // оповестить, когда все готово. //---- return(0); } //+------------------------------------------------------------------+
Шаг 2 : Организация виртуальной торговли
Этот шаг самый интересный - создание хеджевого эксперта, который будет тестироваться тестером стратегий. Далее представлен скрипт. И не забывайте читать комментарии. Так же, как и в первом советнике, результаты будут экспортироваться в директорию "MetaTrader 4/tester/files".
//| VirtualHedge.mq4 | //| A Sexy Trader | //| http://pipsmaker.wordpress.com/ | //+------------------------------------------------------------------+ #property copyright "A Sexy Trader" #property link "http://pipsmaker.wordpress.com/" #include <stdlib.mqh> extern string StartDate = "2007.03.17"; // указать период тестирования как и для собранных данных extern string StopDate = "2007.06.27"; extern string BaseSymbol = "GBPJPY"; // основной символ extern string HedgeSymbol = "EURJPY"; // хеджевый символ extern int Base_OP = OP_SELL; // по правилам - всегда продавать GBPJPY extern int Hedge_OP = OP_BUY; // и всегда покупать EURJPY extern double BaseLotSize = 1.0; // размер лота - 1 для GBPJPY extern double HedgeLotSize = 2.0; // и 2 лота для EURJPY extern double ExpectProfit$ = 100; // всегда закрывать при достижении $100 extern bool PrintDetails = true; // выводить детали открытия и закрытия ? int BSP // спред GBPJPY ,HSP // спред EURJPY ,BOP=-1 // тип ордера GBPJPY ,HOP=-1 // тип ордера EURJPY ,day=0 // показывает, что сегодняшний хедж был исполнен ,hr // идентификатор файла для экспорта результатов хеджирования ,p=1 ,BC // размер контракта основного символа ,HC // размер контракта хеджевого символа ,floating=0 // показывает, что в данный момент есть открытые ордера ,Pointer=0 // указатель всех строковых данных ,AL // Плечо счета ,on // номер одрдера ; double BOpen // цена открытия основного символа ,HOpen // цена открытия хеджевого символа ,BLots // размер лота основного символа ,HLots // размер лота хеджевого символа ,lastTick // отмечает последний тик для расчета текущего профита ,BPF // профит ордера основного символа ,HPF // профит ордера хеджевого символа ,TPF // общая прибыль ,CurBalance // массив для сбора результатов хеджирования ,CurB=0 // текущий баланс ,BTick // Основной тик ,HTick // Хеджевый тик ,BD1Time // Основной ежедневное время открытия ,HD1Time // Хеджевое ежедневное время открытия ,BTTime // Время основного тика ,HTTime // Время хеджевого тика ; string CurTrade // строка, показывающая текущее исполнение в виде комментария ,BORD // строка, определяющая тип стрелок и текст основного символа ,HORD // то же для хеджевого символа ,hobstr // целая строка данных о цене открытия основного символа ,bstr // строка всех дневных данных основного символа ,hstr // строка всех дневных данных хеджевого символа ,btstr // строка тиковых данных основного символа ,htstr // строка тиковых данных хеджевого символа ,pstr // строка для экспорта результатов ; color SELLCL=DeepSkyBlue // цвет стрелок, обозначающих что ордер на продажу был исполнен ,BUYCL=HotPink // цвет стрелок, обозначающих что ордер на покупку был исполнен ,BCL // цвет основного символа, меняется параметром Base_OP ,HCL // цвет хеджевого символа, меняется параметром Hedge_OP ; bool closed=true // обозначить, что все сделки были закрыты ,trimb=true // очистить собранные данные основного символа или нет? ,trimh=true // очистить собранные данные хеджевого символа или нет? ,trimbd1=true // очистить собранные дневные данные основного символа или нет?? ,trimhd1=true // очистить собранные дневные данные хеджевого символа или нет? ; //+------------------------------------------------------------------+ //| функция инициализации эксперта | //+------------------------------------------------------------------+ int init() { //---- CurBalance=AccountBalance(); //обозначим стартовый депозит CurB=AccountBalance(); //обозначим текущий баланс pstr=pstr+DoubleToStr(CurBalance,2)+","; //собираем строку характеристик AL=AccountLeverage(); //получаем плечо счета BSP=MarketInfo(BaseSymbol ,MODE_SPREAD); //получаем спред HSP=MarketInfo(HedgeSymbol ,MODE_SPREAD); BC =MarketInfo(BaseSymbol ,MODE_LOTSIZE); //получаем размер контракта для высчитывания прибыли HC =MarketInfo(HedgeSymbol ,MODE_LOTSIZE); BOP=Base_OP; //получаем тип ордера HOP=Hedge_OP; BLots=BaseLotSize; //получаем размер лота HLots=HedgeLotSize; string RName=BaseSymbol+"_"+HedgeSymbol+"_result"+p+".csv"; //присваиваем имя файлу исполнения //этот файл надо скопировать в /* Program files/MetaTrader 4 /Experts/files */ hr =FileOpen(RName ,FILE_CSV|FILE_WRITE); //открываем файл для экспорта стартового депозита if(hr>0) //если файл существует { FileWrite(hr,pstr); //экспортируем стартовый депозит FileClose(hr); //закрываем файл } if(Base_OP==OP_SELL){BCL=SELLCL;BORD="sell";} //указываем параметры, по которым создадутся else {BCL=BUYCL; BORD="buy";} // стрелки и текст сделки if(Hedge_OP==OP_BUY){HCL=BUYCL; HORD="buy";} else {HCL=SELLCL;HORD="sell";} getdata(BaseSymbol); //берем все данные основного символа getdata(HedgeSymbol); //все данные хеджевого символа //функция, расположенная внизу return(0); } //+------------------------------------------------------------------+ //| функция деинициализации эксперта | //+------------------------------------------------------------------+ int deinit() { //---- //---- return(0); } //+------------------------------------------------------------------+ //| функция запуска эксперта | //+------------------------------------------------------------------+ int start() { string RName=BaseSymbol+"_"+HedgeSymbol+"_result"+p+".csv"; //присваиваем имя файлу исполнения //--------------------------Показать только указанный период--------------------------// if(TimeToStr(TimeCurrent(),TIME_DATE)>=StartDate && TimeToStr(TimeCurrent(),TIME_DATE)<=StopDate) //помечаем, что сейчас нет ордеров { if(day!=TimeDay(Time[0])) //наступил новый день { { if(BOpen!=0 && HOpen!=0) //проверить, не открыт ли вчерашний хедж { if(Base_OP==OP_BUY) //текущий профит основного символа { BPF=((BTick-BOpen)*BLots*BC)/BOpen; } else { BPF=((BOpen-BTick)*BLots*BC)/BOpen; } if(Hedge_OP==OP_BUY) //текущий профит хеджевого символа { HPF=((HTick-HOpen)*HLots*HC)/HOpen; } else { HPF=((HOpen-HTick)*HLots*HC)/HOpen; } TPF=BPF+HPF; //общий текущий профит CurB+=TPF; CurBalance=CurB; //получаем последний баланс счета pstr=pstr+DoubleToStr(CurBalance,2)+","; //вставляем в строку характеристик floating=0; //помечаем, что сейчас нет ордеров BOpen=0; //блокируем процесс подсчета профита HOpen=0; if(BOpen==0 && HOpen==0) //удостоверимся, что больше нет ордеров { closed=true; //помечаем, что все ордеры были закрыты CreateObject("R : "+on,OBJ_TEXT,Time[0],Close[0],0,0,DarkViolet,"","Cleared With Profit Of : "+DoubleToStr(TPF,2)); //создаем текст об очистке ордеров if(PrintDetails)Print("Cleared Hedge With Profit : "+DoubleToStr(TPF,2)); //выводим последние действия если включено PrintDetails hr =FileOpen(RName ,FILE_CSV|FILE_WRITE); //открываем файл с результатами для подготовки к записи файла if(hr>0) { FileWrite(hr,pstr); //экспортируем данные FileClose(hr); } if(StringLen(pstr)>4086){p++;pstr="";} //устанавливаем имя нового файла, если в pstr больше 4086 int thep=FileOpen("p.csv",FILE_CSV|FILE_WRITE); //этот файл необходимо скопировать в /* Program files/MetaTrader 4 /Experts/files */ //также // записываем значение p в виде файла if(thep>0) // если файл существует { string ps=DoubleToStr(p,0); // преобразуем в строку FileWrite(thep,ps); // записываем p FileClose(thep); // готово } } } if(floating==0) //теперь все вчерашние хеджы были очищены { trimb=true; trimh=true; //разрешить сбор тиковых данных для сегодняшней работы //----------GETTING TODAY ORDER OPEN PRICE AND TIME----------// if(trimbd1) //разрешен сбор основных дневных цен { Pointer=StringFind(bstr,",",0); //находим знак "," ближайший к началу строки BD1Time=StrToTime(StringSubstr(bstr,0,Pointer)); //берем значение времени, расположенное перед знаком "," bstr=StringSubstr(bstr,Pointer+1,0); //обрезаем полученные данные Pointer=StringFind(bstr,",",0); //находим знак "," ближайший к строке обрезанных данных BOpen=StrToDouble(StringSubstr(bstr,0,Pointer)); //берем значение ЦЕНЫ, расположенное перед знаком "," bstr=StringSubstr(bstr,Pointer+1,0); //снова обрезаем данные для подготовки к следующему разу } if(trimhd1) //разрешен сбор данных о дневной цене хеджа { Pointer=StringFind(hstr,",",0); //повторяется как описано выше в bstr HD1Time=StrToTime(StringSubstr(hstr,0,Pointer)); //... hstr=StringSubstr(hstr,Pointer+1,0); //... Pointer=StringFind(hstr,",",0); //... HOpen=StrToDouble(StringSubstr(hstr,0,Pointer)); //... hstr=StringSubstr(hstr,Pointer+1,0); //... } //--------СБОР ДАННЫХ О ЦЕНЕ И ВРЕМЕНИ ОТКРЫТИЯ СЕГОДНЯШНЕГО ОРДЕРА ЗАВЕРШЕН--------// if(BOpen!=0 && HOpen!=0 && CurBalance>(BLots+HLots)*BC/AL) //убедимся, что полученные данные не равны нулю и величина маржевых требования достпна { floating=1; //отметим, что хедж отправлен closed=false; //отметим, что все ордера открыты on++; //открылись новые хеджевые ордера if(PrintDetails) //если включены PrintDetails { Print(on+" Opened : "+BaseSymbol+" "+DoubleToStr(BLots,2)+" lots @ "+DoubleToStr(BOpen,Digits)+"."); Print(on+" Opened : "+HedgeSymbol+" "+DoubleToStr(HLots,2)+" lots @ "+DoubleToStr(HOpen,Digits)+"."); } } else //если не удается отправить хедж { Comment("Can Not Open The Trade : No Margin Available"); } //нас оповестят if(closed==false) //хедж отправлен { //создаем стрелки и текст buy и sell //эта функция расположена внизу советника CreateObject("B : "+on,OBJ_ARROW,Time[0],Open[0]-20*Point,0,0,BCL,BORD,""); CreateObject("H : "+on,OBJ_ARROW,Time[0],Open[0]+30*Point,0,0,HCL,HORD,""); } } } day=TimeDay(Time[0]); //отмечено продолжение } //-------------------------Для каждого тика----------------------------// if(lastTick!=Hour()) //наступил новый час { if(trimb && StringFind(btstr,",",0)>0) //разрешен сбор основных тиков { Pointer=StringFind(btstr,",",0); //процесс схожий с получением дневных данных выше BTTime=StrToTime(StringSubstr(btstr,0,Pointer)); btstr=StringSubstr(btstr,Pointer+1,0); Pointer=StringFind(btstr,",",0); BTick=StrToDouble(StringSubstr(btstr,0,Pointer)); btstr=StringSubstr(btstr,Pointer+1,0); } if(trimh && StringFind(htstr,",",0)>0) //если разрешен сбор хеджевых тиков { Pointer=StringFind(htstr,",",0); HTTime=StrToTime(StringSubstr(htstr,0,Pointer)); htstr=StringSubstr(htstr,Pointer+1,0); Pointer=StringFind(htstr,",",0); HTick=StrToDouble(StringSubstr(htstr,0,Pointer)); htstr=StringSubstr(htstr,Pointer+1,0); } if(TimeDay(BD1Time)==TimeDay(BTTime) && TimeDay(HD1Time)==TimeDay(HTTime)) //только если тик того же дня { trimbd1=true; trimhd1=true; //разрешен сбор значений следующего дня if(TimeHour(BTTime)==TimeHour(HTTime)) //тот же час { trimb=true; trimh=true; //разрешен сбор значений следующего тика if(BOpen!=0 && HOpen!=0) //если разрешен процесс подсчета { if(Base_OP==OP_BUY) { BPF=((BTick-BOpen)*BLots*BC)/BOpen; //текущий профит основного символа } else { BPF=((BOpen-BTick)*BLots*BC)/BOpen; } if(Hedge_OP==OP_BUY) { HPF=((HTick-HOpen)*HLots*HC)/HOpen; //текущий профит хеджевого символа } else { HPF=((HOpen-HTick)*HLots*HC)/HOpen; } TPF=BPF+HPF; //общий текущий профит CurTrade=DoubleToStr(TPF,2); //показать текущий профит if(TPF > ExpectProfit$) //когда их надо закрывать { BOpen=0; //устанавливаем новое значение цены открытия ордера HOpen=0; //и блокируем процесс подсчета профита CurTrade="No Any Hedge Order Now."; //Хедж был закрыт floating=0; //отмечаем, что хедж закрыт CurB+=TPF; CurBalance=CurB; //берем последний балансовый эквити pstr=pstr+DoubleToStr(CurBalance,2)+","; //вставляем в строку исполнения CreateObject("R : "+on,OBJ_TEXT,Time[0],Close[0],0,0,YellowGreen,"", "Close With Profit Of : "+DoubleToStr(TPF,2)); //создаем текст о закрытии if(PrintDetails) //выводим детали о последнем закрытии { Print(on+" Closed "+BaseSymbol+" @ "+DoubleToStr(BTick,Digits)); Print(on+" Closed "+HedgeSymbol+" @ "+DoubleToStr(HTick,Digits)); Print(on+" Closed Hedge With Profit : "+DoubleToStr(TPF,2)); } hr =FileOpen(RName ,FILE_CSV|FILE_WRITE); //снова открываем для подготовки записи файла if(hr>0) { FileWrite(hr,pstr); //экспортируем данные FileClose(hr); } if(StringLen(pstr)>4086){p++;pstr="";} //присваиваем новое имя файла, если в pstr больше 4086 знаков thep=FileOpen("p.csv",FILE_CSV|FILE_WRITE); // снова записываем значение p if(thep>0) // если файл существует { ps=DoubleToStr(p,0); // преобразуем в строку FileWrite(thep,ps); // записываем p FileClose(thep); // готово } } } } else //если значения не с одного тикового времени { if(BTTime>HTTime){trimb=false;} //блокируем ненужные данные, чтобы они не были обрезаны else {trimh=false;} } } else //если тиковые данные не входят в сегодняшние { if(BTTime>BD1Time){trimb=false;} //блокируем ненужные данные, чтобы они не были обрезаны else if(BTTime<BD1Time){trimbd1=false;} if(HTTime>HD1Time){trimh=false;} else if(HTTime<HD1Time){trimhd1=false;} } } lastTick=Hour(); //отмечаем, сто последний тик продолжается } Comment("\nBOpen : "+DoubleToStr(BOpen,Digits) //показываем текущую ситуацию ,"\nHOpen : "+DoubleToStr(HOpen,Digits) ,"\nBOT : "+TimeToStr(BD1Time,TIME_DATE) ,"\nHOT : "+TimeToStr(HD1Time,TIME_DATE) ,"\nBTick : "+DoubleToStr(BTick,Digits) ,"\nHTick : "+DoubleToStr(HTick,Digits) ,"\nBTT : "+TimeToStr(BTTime,TIME_DATE|TIME_MINUTES) ,"\nHTT : "+TimeToStr(HTTime,TIME_DATE|TIME_MINUTES) ,"\nfloating : "+floating ,"\nclosed : "+closed ,"\ntrimb : "+trimb ,"\ntrimh : "+trimh ,"\n" ,"\nCurOrderNo. : "+on ,"\nCurProfit : "+CurTrade ,"\nCurBalance : "+DoubleToStr(CurBalance,2) ); //---- return(0); //ВСЕ ГОТОВО. } //+----------------------------------------------------------------------+ //| Функция, позволяющая сделать виртуальный тестер похожим на настоящий | //+----------------------------------------------------------------------+ void CreateObject(string name,int type, int time1, double price1, int time2, double price2, color cl,string ordtype,string txt) { if(type==OBJ_TREND) { ObjectCreate(name,type,0,time1,price1,time2,price2); ObjectSet(name,OBJPROP_COLOR,HotPink); } if(type==OBJ_ARROW) { ObjectCreate(name,type,0,time1,price1); ObjectSet(name,OBJPROP_COLOR,cl); if(ordtype=="sell")ObjectSet(name,OBJPROP_ARROWCODE,221); else ObjectSet(name,OBJPROP_ARROWCODE,222); } if(type==OBJ_TEXT) { ObjectCreate(name,type,0,time1,price1); ObjectSetText(name,txt,8,"Comic Sans MS",cl); } } //+------------------------------------------------------------------+ //| ФУНКЦИЯ ИЗВЛЕЧЕНИЯ ДАННЫХ ИЗ ФАЙЛОВ | //+------------------------------------------------------------------+ void getdata(string sym) { Comment("Collecting Data.","\n\nPlease Wait........"); //оповестить, что сбор данных продолжается int x =FileOpen(sym+"x.csv",FILE_CSV|FILE_READ) //получаем количество файлов D1 и тиковых ,xt=FileOpen(sym+"xt.csv",FILE_CSV|FILE_READ) ,pter=0,s=0,v=0 ,lastME=0,t=0 ; double ME,U; string str,str2; int xa=StrToInteger(FileReadString(x)) ,xta=StrToInteger(FileReadString(xt)) ,xtc=1 ; FileClose(x); FileClose(xt); if(xta>xa)xtc=xta; //устанавливаем один проход для цикла else xtc=xa; pter=0;s=0; for(int i=1;i<=xtc;i++) //цикл для извлечения всех строковых данных { string name=sym+i+"T.csv" //присваиваем имя файлу данных ,d1 =sym+i+"D1.csv" ; int h=FileOpen(name,FILE_CSV|FILE_READ) //открываем все файлы для чтения ,d=FileOpen(d1 ,FILE_CSV|FILE_READ); //------------------------------------------------------------ string source=FileReadString(h); //читаем тиковую строку FileClose(h); if(sym==BaseSymbol) //если взяты данные для основного символа { btstr=btstr+source; } else //если для хеджевого символа { htstr=htstr+source; } //------------------------------------------------------------ if(d>0) //читаем дневные данные { string d1s =FileReadString(d);FileClose(d); if(sym==BaseSymbol) //если взяты основные дневные данные { bstr=bstr+d1s; } else //если хеджевые данные { hstr=hstr+d1s; } } } } //------------------------------------ВСЕ ГОТОВО---------------------------------------------//
Шаг 3: Отображение результатов
После исполнения виртуальных ордеров и записи результатов хеджирования, используем полученные данные для отображения хеджевой концепции. Я решила экспортировать все полученные данные в качестве индикатора для построения кривой в отдельном окне, как и во многих других индикаторах (например, CCI, RSI или ATR и др.). Все файлы из второго советника необходимо скопировать в директорию "MetaTrader 4/experts/files".
Для построения кривой нам нужен следующий индикатор:
//+------------------------------------------------------------------+ //| performance.mq4 | //| A Sexy Trader | //| http://pipsmaker.wordpress.com/ | //+------------------------------------------------------------------+ #property copyright "A Sexy Trader" #property link "http://pipsmaker.wordpress.com/" #property indicator_separate_window #property indicator_buffers 1 #property indicator_color1 Goldenrod //---- исходные параметры extern string BaseSymbol="GBPJPY"; extern string HedgeSymbol="EURJPY"; //---- буферы double ExtMapBuffer1[] //это буфер индикатора ,curve[8888888] //это массив для сбора результатов из файлов исполнения ; int handle; //возможно, нет необходимости объяснять далее string data; int len=0 ,i=0 ,j=0 ,p ,pv ,pter=0 ; //+------------------------------------------------------------------+ //| Функция инициализации пользовательского индикатора | //+------------------------------------------------------------------+ int init() { //---- индикаторы SetIndexStyle(0,DRAW_LINE); SetIndexBuffer(0,ExtMapBuffer1); IndicatorShortName(BaseSymbol+"~"+HedgeSymbol+" "); //---- p =FileOpen("p.csv",FILE_CSV|FILE_READ); //получаем кол-во экспортированных файлов с результатами pv=StrToInteger(FileReadString(p)); FileClose(p); for(int i=1;i<=pv;i++) //цикл для получения экспортированных результатов в виде строки { string name = BaseSymbol+"_"+HedgeSymbol+"_result"+p+".csv";//имя файла исполнения handle=FileOpen(name,FILE_CSV|FILE_READ); //ищем файл, открываем для чтения if(handle>0) //если файл существует { data=data+FileReadString(handle); //берем все данные FileClose(handle); //закрываем } } //---- return(0); } //+------------------------------------------------------------------+ //| Функция деинициализации пользовательского индикатора | //+------------------------------------------------------------------+ int deinit() { //---- //---- return(0); } //+------------------------------------------------------------------+ //| Функция повторения пользовательского индикатора | //+------------------------------------------------------------------+ int start() { int counted_bars=IndicatorCounted(),i=0,s=-1; //---- len=StringLen(data); //получаем длину строки данных pter=0; //устанавливаем указатель для поиска "," /* знайте, что файл csv содержит результаты в виде подобной строки 1000.54,1100.54,1200.54,1300.54,1400.54 фактически нам нужны реальные данные подобного вида 1000.54 1100.54 1200.54 1300.54 1400.54 поэтому знак "," - это разделитель для получения необходимых данных */ for(i=len;i>=0;i--) /*цикл для заполнения массива, содержащего данные с характеристиками */ if(StringFind(data,",",0)>0) //если есть ближайший от начала знак "," { s++; //устанавливаем счетчик pter=StringFind(data,",",0); //место, где стоит первый знак "," curve[s]=StrToDouble(StringSubstr(data,0,pter));//вставляем первые данные целой строки data=StringSubstr(data,pter+1,0); //обрезаем вставленные данные } else break; //больше нет данных для подсчета, прерываем цикл ArrayResize(curve,s+1); //изменяем размер массива кривой для дальнейшего использования //---- for(i=0,j=s;i<=s;i++,j--) //начало процесса построения { if(curve[j]>0)ExtMapBuffer1[i]=curve[j]; //строим кривую характеристик } //---- //все готово. return(0); } //+------------------------------------------------------------------+
Как это использовать
Перед тем как Вы скачаете копию моего кода, давайте подведем небольшие итоги в виде краткой инструкции по использованию.
Чтобы наши ожидания воплотились в жизнь, необходимо пройти пять простых шагов:
- В тестере (нет необходимости использовать визуальный режим) выбрать symbol-D1.mq4 в меню "Expert Advisor:", а в меню "Symbol:" выбрать первый хеджевый символ вашей любимой хеджевой пары, установить период дата-время и значение "For_OP_SELL" как true, если этот символ предназначен для sell-ордеров или как false, если этот символ используется для buy-ордеров, выбрать период ежечасно в меню" Period:", нажмите "Start" для запуска процесса записи.
- Пройти алгоритм, описанный в шаге 1, для второго хеджевого символа, ***НЕ ЗАБУДЬТЕ ИЗМЕНИТЬ ПАРАМЕТР "For_OP_SELL" *** под тип ордера данного символа.
- Выбрать VirtualHedge.mq4, установить переменные, выбрать инструмент тестирования (любой символ). Здесь нужен визуальный режим для наблюдения за исполнением хеджа.
- Скопировать соответствующие файлы отображения исполнения хеджа из директории "program files/metatrader 4/tester/files" в директорию "program files/metatrader 4/experts/files". (возможно, включая файлы GBPJPY_EURJPY_result1.csv и p.csv; если файлов с результатами больше, чем один, необходимо скопировать их все.)
- Прикрепить performance.mq4 к любому графику, активному в настоящий момент. Появится исполнение хеджа, похожее на реальное.
А вот кривая, полученная в результате моих экспериментальных правил.
Выглядит ужасно. Не так ли? Надеюсь, у Вас получится лучше.
Заключение
Я рада, что мы смогли пройти эти шаги тестирования хеджевых советников. Ограничения в тестере больше не являются проблемой трейдеров, использующих хеджевые стратегии. Но, кстати, концепция хеджа в данной статье является только примером, она приведена для сокращения процесса тестирования. Чтобы виртуальный тестер работал с Вашей хеджевой стратегией, Вам необходимо перечислить все необходимые данные, такие как: ежедневные цены открытия и закрытия, максимальные и минимальные цены и другие. Если Вы торгуете с корреляцией, Вам нужно экспортировать все значения корреляции для каждого определенного времени. Этот перечень подскажет Вам, какие данные должны быть записаны, какие - выссчитаны, а какие - выведены в качестве результата. Чтобы сократить время подготовки данных, я рекомендую разделить тестирование на небольшие периоды - это лучше. чем производить все это за один раз. Например, если Вам необходимо протестировать советник за 1 год, лучше разделить его на 4 части по 3 месяца. Надеюсь, Ваша кривая выглядит гораздо лучше, нежели моя. Также надеюсь, что данная статья поможет трейдерам, использующим хеджевые стратегии, хотя бы частично; или же сподвигнет Вас на получение превосходных результатов хеджирования.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/1493
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Если проанализировать хеджирование коррелированных инструментов с точки зрения математики, то получается, что открытие разнонаправленных позиций по двум положительно коррелированным инструментам и ононаправленным - по отрицательно коррелированным, равносильно торговле синтетического инструмента вида (в данном случае):
EUR/JPY-k*GBP/JPY, где k - некоторая константа призванная "выровнять" волатильность исходных пар.
Возможно, новый синтетический инструмент обладает рядом свойств, эксплуатация которых позволяет получать прибыль... На верхней картинке показаны два временных ряда (ВР): GBP/JPY и k*EUR/JPY+constanta. Действительно, можно отметить хорошую положительную коррелированность рядов.
Теперь построим синтетический ряд вида EUR/JPY-k*GBP/JPY см. нижний рис. синий цвет. Ничего не напоминает? Для сравнения, на этот график наложен инструмент k1*EUR/GBP+constanta1 (красный цвет).
Неожиданный результат! Неправда ли? На самом деле, этот результат получается и строго математически если пренебречть в выкладках малыми вторго порядка, т.е. с точностью до этих малых, выполняется равенство:
EUR/JPY-k*GBP/JPY = k1*EUR/GBP+constanta1.
Выходит, что хеджирование по описанной в данной статье методике, равносильно торговле на инструменте EUR/GBP но с "двойным" спредом! Получаются те же яйца (вид c другой стороны). Или я что-то упускаю?
Конечно, данная методика позволяет синтезировать различные экзотические инструменты которых нет в природе (или в данном ДЦ) и работать с ними. Но, увеличенная комиссия за каждую транзакцию сведёт на НЕТ весь потенциальный арбитраж синтетического инструмента. Вобще, наличие арбитража в том или ином инструменте это скорее редкое исключение, чем правило, а уж попытка его увеличения путём сложения двух ВР скорее приведёт к потере всякой арбитражности.
Странное какое-то у тебя выражение: EUR/JPY-k*GBP/JPY = k1*EUR/GBP+constanta1
Чисто математически должно быть так: EURJPY / GPBJPY = EURGBP. Это если говорить о конкретной котировке.
Если же рассматривать движение пар, то получаем: k1*EURJPY / (k2*GBPJPY) = k1/k2 * EURGBP. Логично?
Отсюда простой вывод: если EURGBP движется прямолинейно (k1/k2=const), то EURJPY и GBPJPY будут двигаться синхронно, с коэффициентом пропорциональности = k1/k2.
Если же EURGBP выписывает кренделя, то никакой синхронности не будет.
В этом то и состоит всё коварство форекса, в отличие от других рынков. Валютные пары увязаны между собой математически, поэтому торговля по одной из них неизбежно влияет на движение других.
Если же где-то и образуется расхождение, то арбитражёры быстро устраняют это
эту идею я реализовал немного по другому. Я собираюсь торговать на одной валюте, но меня сильно интересует, как поведет себя мой советник на других валютах, убедиться в его устойчивости так сказать. Вот я и слепил девять пар друг за другом, получилось 35 лет истории. Теперь после оптимизации на евре он проходит такой вот тест на выносливость.
Как Вы это сделали ? Можете обьяснить?
Это полезная статья. Предлагаю немного более прозрачный код для использования тестера со многими валютами.
Идея та же - заменить MarketInfo(..) соответствующим файлом, т.к. тестер не может взять инфо из других пар.
1. Сначала создаём небольшой советник который записывает эти файлы:
extern string mini = "";
string stmp;
int ho; // идентификатор файла, записывающего bid & ask
string fname;
void putBidAsk() {
FileWrite(ho, TimeToStr(TimeCurrent(),TIME_DATE|TIME_SECONDS), Bid, Ask);
}
//+--------------------------------------------------------------------+
int init() {
//+----------------pairs div ----------------------------------------+
fname = Symbol() + "bidAsk";
ho = FileOpen(fname,FILE_CSV|FILE_WRITE); // открываем файл для bid/ask
if (ho < 1) Print("***Can't open ", fname);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
int deinit() {
FileFlush(ho);
FileClose(ho);
return(0);
}
//+------------------------------------------------------------------+
int start() {
if (!IsTradeAllowed() || IsTradeContextBusy()) return;
RefreshRates();
putBidAsk();
return(0);
}
Мы можем заменить/добавить параметры в FileWrite(ho, TimeToStr(TimeCurrent(),TIME_DATE|TIME_SECONDS), Bid, Ask);
на любые другие из MarketInfo
2. В советнике, который должен работать со многими парами добавляем в начале код:
//+---------------- работа с файлами bid/ask ----------------------------------+
extern string mini = "";
int pairCnt = 6;
string pairs[6][2] = {"EUR","USD",
"EUR","GBP",
"EUR","JPY",
"GBP","USD",
"USD","JPY",
"GBP","JPY"};
string stmp;
string stime[5]; //строки времени из файлов pairCnt - 1
double rates[6][2]; // pairCnt
int ho[5]; // идентификаторs файлов с записанными bid & ask, кроме 1-го (pairCnt - 1)
bool test = true; //для тестера нужны файлы с bid & ask
void getRates() {
for (cnt =0;cnt < pairCnt;cnt++) {
stmp = pairs[cnt,0] + pairs[cnt,1] + mini;
if (test && cnt > 0) getBidAsk(); //для тестера взять bid & ask из файлов
else {
rates[cnt,0] = MarketInfo(stmp, MODE_BID);
rates[cnt,1] = MarketInfo(stmp, MODE_ASK);
}
//Print("*rate ",stmp," bid ",rates[cnt,0]," ask ",rates[cnt,1]);
}
}
//+------------------------------------------------------------------+
void getBidAsk() {
string s = TimeToStr(TimeCurrent(),TIME_DATE|TIME_SECONDS);
while (stime[cnt - 1] < s) {
if (FileIsEnding(ho[cnt - 1])) return;
getNextBidAsk();
}
}
//+------------------------------------------------------------------+
void getNextBidAsk() {
if (FileIsEnding(ho[cnt - 1])) return;
stime[cnt - 1] = FileReadString(ho[cnt - 1]);
string s = FileReadString(ho[cnt - 1]);
rates[cnt,0] = StrToDouble(s);
s = FileReadString(ho[cnt - 1]);
rates[cnt,1] = StrToDouble(s);
}
//+------------------------------------------------------------------+
Список пар может быть любым. Надо поменять pairCnt и размеры соответствующих массивов (см. комментарии)
3. В конце init добавляем код:
if (!test) return; //файлы нужны только в режиме тестера
for (cnt = 1; cnt < pairCnt; cnt++) {
stmp = pairs[cnt,0] + pairs[cnt,1] + "bidAsk";
ho[cnt - 1] = FileOpen(stmp,FILE_CSV|FILE_READ); // открываем файлы для чтения bid/ask
if (ho[cnt - 1] < 1) Print("***Can't open ", stmp);
getNextBidAsk();
4. Для работы с парами добавляем в start вызов getRates()
Значения bid/ask (или чего-то другого в зависимости от FileWrite(ho, ...) в writeBidAsk советнике и
процедуры getNextBidAsk()) помещены в rates для соответствующих пар
5. Запускаем советник writeBidAsk для всех используемых пар.
6. Тестируем целевой советник.
Если есть вопросы - echashnik@comcast.net