Trade Like A Pro | Софт / Программирование http://tradelikeapro.ru/ |

MQL5: Пишем индикатор и советник на основе Индекса Корреляции

  6 октября 2016
RAR
Начните торговлю и получите $ 30 бонус в подарок!
Открывая позицию на форекс, мы предполагаем, что спрос на одну из валют пары будет расти и, как следствие, изменение обменного курса приведет к увеличению плавающей прибыли. Грубо говоря, перед входом в рынок мы определяем, какая из валют «сильнее», соответственно, какие факторы будут двигать цену в первую очередь. Форекс по своей специфике не дает нужных инструментов для подобного анализа, поэтому трейдерами была придумана масса способов обоснования самого понятия силы на основе второстепенных признаков. Некоторые из них даже породили отдельное учение, недалеко ушедшее от астрологических прогнозов, чисто по ошибке не названное «гуманитарным анализом».

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

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

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

Закон сохранения энергии

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

Если бы на форекс существовала идеальная замкнутая система, мы бы наблюдали точно настроенную взаимосвязь – тут ушло, там прибыло. Это означает, что рост одной валюты должен провоцировать падение другой, и так везде. Таким образом, построив синтетический инструмент из «корзины рынка», мы получаем беспроигрышную торговую систему, основанную на естественном переливании денег. К сожалению, движение валютных пар не следует точным законам, и реальный рынок далек от понятия замкнутой системы.

Источники информации

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

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

Реакция рынка во многих случаях прямо противоположна имевшимся прогнозам, что как бы наталкивает на мысль, а что мы собственно изучаем? В связи с этим, в ордене каменщиков данный вид анализа принято связывать не с макроэкономическими событиями, а с определением основных мировых тенденций.

В отличие от фондового рынка, на форекс все несколько усложнено. Да, возможно, мы можем определить некоторый потенциал валюты, и если прогноз сбудется, то произойдет пробой важного уровня, либо смена краткосрочной тенденции. Уповать на опыт и чувство рынка здесь не стоит – трейдеры, обладающие подобными способностями, давно ушли с рынка и преподают свою опытность менее опытным индивидам.

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

Типы существующих индексов

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

Отметим три таких способа:
Средневзвешенный индекс, соответствующий показателям внешнеторгового оборота. Тут все просто: казалось бы, берем в расчет валюты всех стран, ведущих наиболее активную торговлю и вычисляем среднее геометрическое взвешенное полученной корзины. Данным метод является официально признанным и полагается на реальное количество валюты в обороте.
Расчет среднего геометрического всех кроссов. Так как рассчитать торговые объемы на форекс мы не в состоянии, мы заранее принимаем все участвующие в расчете пары за инструменты с примерно равным объемом торгов. Опять же, мы не берем в расчет все валюты, а только наиболее популярные.
Вычисление индекса валюты по скорости роста родственных валютных пар, используя для этого вспомогательный индикатор. Это самый распространенный способ вычисления силы валют на основе имеющихся цен. Для расчета можно использовать любой доступный технический индикатор, как правило, на основе скользящей средней, который мы впоследствии будем использовать как отправную точку нашего расчета.

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

Инструментарий

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

Итак, у нас в наличии есть список основных торговых инструментов, которые и будут принимать участие в расчетах. Количество инструментов не должно кардинально влиять на результат, а лишь повышать его точность. То есть, добавляя или удаляя валюты, прогноз должен оставаться примерно схожим. Разумеется, наиболее популярные валюты все же будут сильно влиять на результат, поэтому некоторый минимум все равно стоит иметь.

Пишем индикатор

Правильная подготовка исходных данных крайне важный процесс. Любая неточность на данном этапе может кардинально повлиять на итоговый результат, сделав его бесполезным. Нам необходимо решить две проблемы – это доступность истории и синхронизация по времени. Загрузкой истории мы займемся чуть позже, но, для начала, индикатор необходимо инициализировать, то есть обработать входные параметры и разметить индикаторные буферы.

int OnInit()
  {
   currenciesToSymbols(Currencies);
   …
  }


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

void currenciesToSymbols(string currs)
  {
   string arr[];
   StringSplit(currs,’,’,arr);
   int sz=ArraySize(arr);
   amountCurrs=sz;
   for(int i=0; i<sz; i++)
     {
      // убираем пробелы
      StringTrimLeft(arr[i]);
      StringTrimRight(arr[i]);
      // переводим в верхний регистр
      StringToUpper(arr[i]);
      pairs[i].currency=arr[i];
     }
   // инициализируем счетчик
   int amount[];
   ArrayResize(amount,sz);
   ArrayInitialize(amount,0);
   string s;
   for(int i=0; i<sz; i++)
     {
      for(int j=0; j<sz; j++)
        {
         // собираем все возможные пары из разных валют
         if(i != j)
           {
            s=pairs[i].currency+pairs[j].currency;
            // если символ реальный, запоминаем
            if(SymbolSelect(s,true))
              {
               pairs[i].symbols[amount[i]].name = s;
               pairs[j].symbols[amount[j]].name = s;
               amountReal=++amount[i];
               amount[j]++;
              }
           }
        }
     }
  }


Дальше идет инициализация индикаторных буферов.

int OnInit()
  {
   …
   // по буферу на каждую валюту
   for(int i=0; i<amountCurrs; i++)
     {
      SetIndexBuffer(i,buffer[i].history);
      PlotIndexSetInteger(i,PLOT_DRAW_TYPE,DRAW_LINE);
      PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,0.0);
      PlotIndexSetString(i,PLOT_LABEL,pairs[i].currency);
      // заполняем пустыми значениями буфер–таймсерию
      ArrayInitialize(buffer[i].history,0.0);
      ArraySetAsSeries(buffer[i].history,true);
     }
   // фиксируем масштаб
   IndicatorSetDouble(INDICATOR_MAXIMUM,1.0);
   IndicatorSetDouble(INDICATOR_MINIMUM,-1.0);
   …
  }


Учитывая сложность расчетов, на больших объемах данных индикатор может застрять на приличное время. Чтобы этого не происходило, отрисовку мы будем делать через таймер, выводя результат по частям, начиная от первого (крайнего) бара на графике.

EventSetMillisecondTimer(10);


Из-за аппаратных ограничений таймер не может запускаться быстрее раза в несколько десятков миллисекунд. Поэтому, расчеты мы будем разбивать на куски по 50 мс. Для синхронизации берем за ориентир время открытия последнего бара таймсерии и идем вглубь истории, пока не будет достигнут предел по времени. После чего перерисовываем график с помощью ChartRedraw(). Таким образом, отрисовка будет происходить на сразу, а постепенно, справа налево, почти моментально открывая доступ к самым свежим данным.

void OnTimer()
  {
   ulong st=GetMicrosecondCount();
   // если первый запуск
   if(currTime==0)
     {
      // получаем время крайнего бара на графике
      currTime= (datetime) SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
      for(int l=0; l<amountCurrs; l++)
        {
         ArrayInitialize(buffer[l].history,0.0);
        }
      // находим дату начала отрисовки
      if(Count>0)
        {
         datetime tm[];
         CopyTime(_Symbol,_Period,Count-1,1,tm);
         startTime=tm[0];
        }
      else
         startTime=StartDate;
     }
   // когда все отрисуем, продолжаем следить за текущим баром
   if(currTime<startTime)
     {
      startTime=(datetime) SeriesInfoInteger(_Symbol,_Period,SERIES_LASTBAR_DATE);
      currTime = startTime;
     }
   // за один раз не тратим больше 50 мс
   while(currTime>=startTime
         && GetMicrosecondCount()-st<MAX_TIMER)
     {
      for(int t=0; t<amountCurrs; t++)
        {
         // получаем бар по времени
         int ind=getBarIndexByTime(currTime);
         // main возвращает среднюю корреляцию по валюте за указанный бар
         buffer[t].history[ind]=main(ind,t);
        }
      currTime-=PeriodSeconds();
     }
   // форсим отрисовку
   ChartRedraw();
   // если лимит в 50 мс не вычерпан, ждем еще чуть-чуть
   int diff=(int)(GetMicrosecondCount()-st);
   if(diff<MAX_TIMER)
     {
      Sleep(diff);
     }
  }


Индикаторный таймер, к сожалению, не работает в тестере стратегий, а на малых периодах и вовсе избыточен. Поэтому, для совместимости мы все же оставим функцию OnCalculate, которая будет включаться специальным флагом TestMode. Этот флаг понадобится для использования индикатора в эксперте, для реального использования лучше оставить false.

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   if(TestMode)
     {
      int pos=rates_total-prev_calculated;
      if(prev_calculated==0)
        {
         if(Count>0)
           {
            datetime tm[];
            CopyTime(_Symbol,_Period,Count-1,1,tm);
            startTime=tm[0];
            pos=Count-1;
           }
         else
            pos=getBarIndexByTime(StartDate);
         for(int l=0; l<MAX_SYMBOLS; l++)
           {
            ArrayInitialize(buffer[l].history,0.0);
           }
        }
      for(int ind=pos; ind>=0; ind—)
        {
         for(int t=0; t<amountCurrs; t++)
           {
            buffer[t].history[ind]=main(ind,t);
           }
        }
     }
   return rates_total;
  }


Итак, основной расчет происходит в функции main, куда передается индекс бара и номер валюты. Если в терминале недостаточно истории, индикатор попытается ее загрузить, после чего произведет попытку копирования данных. Далее, при необходимости, мы переворачиваем ценовые данные и выбираем способ расчета корреляции. Функция возвращает среднее геометрическое всех корреляций.

double main(int index,int currency)
  {
   for(int k=0; k<amountReal; k++)
     {
      // пытаемся загрузить историю
      if(historyLoaded(pairs[currency].symbols[k].name,_Period,startTime)<0)
        {
         Print(«Ждем загрузки истории.»);
         return 0;
        }
      // пытаемся скопировать историю
      if(CopyClose(pairs[currency].symbols[k].name,_Period,index,Depth,pairs[currency].symbols[k].history)<Depth)
        {
         Print(«Ошибка при загрузке истории.»);
         return 0;
        }
     }
   double c=1;
   int sz=0;
   for(int j=0; j<amountReal; j++)
     {
      for(int k=0; k<amountReal; k++)
        {
         if(j!=k)
           {
            // переворачиваем цены, если базовая валюта не соответствует выбранной
            if(StringSubstr(pairs[currency].symbols[j].name,0,3)!=pairs[currency].currency)
              {
               sz=ArraySize(pairs[currency].symbols[j].history);
               for(int v=0; v<sz; v++)
                 {
                  pairs[currency].symbols[j].history[v]=1.0/pairs[currency].symbols[j].history[v];
                 }
              }
            if(StringSubstr(pairs[currency].symbols[k].name,0,3)!=pairs[currency].currency)
              {
               sz=ArraySize(pairs[currency].symbols[k].history);
               for(int v=0; v<sz; v++)
                 {
                  pairs[currency].symbols[k].history[v]=1.0/pairs[currency].symbols[k].history[v];
                 }
              }
            // расчет корреляции Пирсона и Спирмена
            // Spearman
            if(Type==Spearman)
               c*=(1+getSpearmanRankCorr(pairs[currency].symbols[j].history,pairs[currency].symbols[k].history));
            // Pearson
            else if(Type==Pearson)
               c*=(1+getPearsonCorr(pairs[currency].symbols[j].history,pairs[currency].symbols[k].history));
           }
        }
     }
   // возвращаем среднее геометрическое коэффициентов
   return pow(c, 1.0 / (amountReal * (amountReal — 1))) — 1;
  }


Количество линий соответствует количеству валют, указанных во входных параметрах. Название валюты можно подглядеть во всплывающей подсказке, при наведении стрелки мыши на линию.

MQL5: Пишем индикатор и советник на основе Индекса Корреляции


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

void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      // запоминаем координаты и конвертируем пиксели в время/деньги
      int x = (int) lparam;
      int y = (int) dparam;
      datetime time;
      double price;
      int sub;
      ChartXYToTimePrice(0,x,y,sub,time,price);
      int lines=0;
      int bar=getBarIndexByTime(time);
      int sz;
      if(bar>=0)
        {
         for(int t=0; t<amountCurrs; t++)
           {
            sz=ArraySize(buffer[t].history);
            if(bar>=sz) break;
            // Проверяем, если линия находится близко от курсора, выделяем ее
            if(buffer[t].history[bar]+0.03>price
               && buffer[t].history[bar]-0.03<price)
              {
               PlotIndexSetInteger(t,PLOT_LINE_WIDTH,2);
               PlotIndexSetInteger(t,PLOT_LINE_STYLE,STYLE_SOLID);
               lines++;
              }
            // все остальные линии затемняем
            else
              {
               PlotIndexSetInteger(t,PLOT_LINE_WIDTH,1);
               PlotIndexSetInteger(t,PLOT_LINE_STYLE,STYLE_DOT);
              }
           }
        }
      if(lines==0)
        {
         for(int t=0; t<amountCurrs; t++)
           {
            PlotIndexSetInteger(t,PLOT_LINE_WIDTH,2);
            PlotIndexSetInteger(t,PLOT_LINE_STYLE,STYLE_SOLID);
           }
        }
      ChartRedraw();
     }
  }


Теперь при наведении мышки на график, линия индекса под курсором будет выделяться на фоне других, упрощая тем самым его анализ.

MQL5: Пишем индикатор и советник на основе Индекса Корреляции


Пишем робота

Так как индикатор не показывает направление движения, использовать его можно только как фильтр для уже имеющейся системы. Для проверки возьмем простую торговую стратегию на RSI. Правила входа – выход из зоны перекупленности/перепроданности. Решение о том, входить или нет, будет принимать на себя фильтр в виде корреляционного индикатора.

Для начала протестируем советника без каких-либо фильтров. Это даст нам необходимый ориентир, на фоне которого должно быть заметно улучшение или ухудшение результатов второго теста. Как видим, зарабатывать советник не умеет, и одного RSI явно недостаточно для стабильной торговой системы.

MQL5: Пишем индикатор и советник на основе Индекса Корреляции


Затем, пробуем добавить фильтр. Условия также просты – когда коэффициент корреляции превышает 0.5, мы считаем, что валюта достаточно сильная и даем зеленый свет на вход. Условия для входа в рынок при этом остаются такими же. Как видим, количество сделок значительно поубавилось. А вот сам график выглядит более стабильным, и по итогу даже показывает прибыль.

MQL5: Пишем индикатор и советник на основе Индекса Корреляции


Учитывая, что это очень поверхностное тестирование, результаты могли бы быть даже лучше. Разница же между двумя тестами очевидна, учет влияния валютных пар убирает из торговли краткосрочные тренды, поэтому ставки идут только на сильные валюты. Тем самым, мы избавляемся от заведомо ложных ставок, торгуя только в одном направлении.

Заключение

Расчет линейной корреляции не является самым надежным способом для определения закономерностей. То, что пары с евро какое-то время ходят синхронно не означает, что между ними есть прямая взаимосвязь, но, это предполагается. В методе есть здравое звено, это подтверждается тестами, но способ определения зависимостей не является единственно правильным. Весь расчет происходит в функции main, вы можете изменить ее вывод, добавив собственную формулу – для экспериментов пространства достаточно. В данном случае, анализ средней корреляции помогает определить долгосрочность тренда.

http://tradelikeapro.ru/ | Подробнее об использовании информации.