Скачать MetaTrader 5

Использование прямого и обратного преобразований Фишера для анализа рынков в MetaTrader 5

16 августа 2011, 18:41
investeo
6
8 038

Введение

В данной статье рассматривается использование прямого и обратного преобразований Фишера (Fisher Transform, Inverse Fisher Transform) для анализа финансовых рынков.

Для иллюстрации практического использования реализован индикатор Smoothed RSI Inverse Fisher Transform, опубликованный в журнале "Stocks and Commodities" (Октябрь, 2010). Прибыльность индикатора проверена советником, сгенерированным при помощи модуля торговых сигналов, основанном на индикаторе Фишера.

Статья основана на материалах книги "Cybernetic Analysis for Stocks and Futures: Cutting-Edge DSP Technology to Improve Your Trading" и статьях, найденных в сети Internet. Список литературы приведен в конце статьи.


1. Гауссова плотность распределения вероятности и рыночные циклы

Обычно предполагают, что цены имеют нормальную плотность распределения вероятности.

Это означает, что отклонения цен от среднего значения могут быть описаны при помощи хорошо известной колоколообразной кривой Гаусса:

Рис 1. Нормальное распределение (распределение Гаусса) 

Рис 1. Нормальное распределение (распределение Гаусса)

Было упомянуто о нормальной плотности распределения вероятности. Для более глубокого понимания мы рассмотрим некоторые идеи и математические формулы, я надеюсь, большинству читателей они будут понятны.

Вероятность определяется как

  1. Численная мера возможности наступления некоторого события или
  2. Отношение количества тех наблюдений, при которых рассматриваемое событие наступило, к общему количеству наблюдений.

Случайной величиной называется переменная, значения которой являются результатом измерения некоторого случайного процесса. В нашем случае случайной переменной является цена актива.

Плотность вероятности (Probability Density Function) - функция, описывающая вероятность того, что значение случайной переменная X (в нашем случае цены) будет лежать в пределах некоторого заданного диапазона возможных значений. В реальном мире большинство случайных величин имеет гауссово (нормальное) распределение, при измерениях большинство их значений локализуются вокруг одного значения (среднее).

С математической точки зрения вероятность того, что значение случайной величины окажется в интервале [a,b] определяется интегралом функции распределения.


Это значение представляет собой площадь фигуры под кривой f(x) от a до b. Вероятность можно выразить в процентах (от 0% до 100%) или численным значением от 0 до 1, условие нормировки приводит к тому, что общая площадь под всей кривой должна быть равной 1 (сумма всех вероятностей).


Рассмотрим подробнее нижнюю часть рис. 1:

Рис. 2. Стандартные отклонения колоколообразной кривой Гаусса 

Рис. 2. Стандартные отклонения колоколообразной кривой Гаусса

На рис. 2 приведен процент значений, величины которых имеют +/- 1-3 стандартных отклонений (sigma). Для нормального распределения 68.27% значений лежат в пределах одного стандартного отклонения, 95.45% значений попадают в диапазон двух стандартных отклонений и 99.73% значений находятся в диапазоне трех стандартных отклонений от среднего значения.

Справедливо ли это для рыночных данных? Не совсем.  На рыночных графиках цены выглядят подобно волнам: после достижения уровней сопротивления или поддержки (на которых располагается большое количество ордеров) цены разворачиваются и направляются к следующему уровню поддержки/сопротивления. По этой причине рынок можно смоделировать в виде синусоиды.

На рис. 3 изображена синусоида:

sine

Рис. 3. График функции sin(x)

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

Рис. 4. Плотность распределения функции sin(x) 

Рис. 4. Плотность распределения функции sin(x)

Отметим, что полученная плотность распределения принимает наибольшие значения по краям. Это согласуется с замечанием о том, что наибольшее число сделок совершается очень близко к уровням сопротивления и поддержки. При помощи гистограммы рассмотрим плотность распределения значений в процентах:

Рис. 5. Гистограмма плотности распределения значений sin(x)

Рис. 5. Гистограмма плотности распределения значений sin(x)

Разве похоже это распределение на колоколообразную кривую Гаусса? Конечно, нет. Наиболее вероятные значения имеют три первых и три последних бара гистограммы.

В книге "Cybernetic Analysis for Stocks and Futures: Cutting-Edge DSP Technology to Improve Your Trading" Дж. Элерса описан эксперимент по анализу американских казначейских облигаций (U.S. T-Bonds) за 15 лет. Взяв нормализованные ценовые данные канала из 10 баров и разделив гистограмму на 100 делений, он вычислил количество попаданий цены в каждое из делений.

Полученная в результате кривая распределения вероятности оказалось очень похожей на рассмотренную нами гистограмму плотности распределения sin(x).


2. Преобразование Фишера и его приложение для анализа временных рядов

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

Решением является использование преобразования Фишера (Fisher Transform). Преобразование Фишера изменяет плотность распределения любой волны таким образом, что оно приближается к нормальному (гауссову) распределению.

Преобразование Фишера имеет вид:

Рис. 6. Преобразование Фишера (Fisher Transform)

 Рис. 6. Преобразование Фишера (Fisher Transform)

Рис. 6. Преобразование Фишера (Fisher Transform)

После использования преобразования Фишера (Fisher transform) на выходе получается величина, плотность распределения которой приближенно является гауссовой (Gaussian PDF).  Для понимания этого факта достаточно посмотреть на рис. 6.

В случае, когда значения входных данных близки к среднему, масштабирующий множитель близок к единице (участок графика для |X|<0.5). С другой стороны, для нормализованных значений на границах интервала масштабирующий множитель больше и выходные значения увеличиваются больше (участок графика для 0.5<|x|<1).

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

Как мы можем использовать преобразование Фишера в торговле? Во-первых, из-за ограничения |x|<1 цены следует нормализовать в этот интервал. Далее к нормализованным ценам применяется преобразование Фишера, случаи экстремальных движений цен становятся относительно редкими. Это означает, что преобразование Фишера отлавливает эти экстремальные движения цен, позволяя нам торговать в соответствии с ними.


3. Реализация преобразования Фишера в MQL5

Исходный код индикатора Fisher Transform описан в книге Элерса "Cybernetic Analysis for Stocks and Futures: Cutting-Edge DSP Technology to Improve Your Trading".

Он уже был реализован в MQL4 и я сконвертировал его в MQL5. Индикатор использует средние значения цен (median) (H+L)/2, для работы с историческими значениями средних цен я использовал индикатор iMA().

Сначала цены нормализуются в ценовой диапазон 10 баров, далее к нормализованным значениям цен применяется преобразование Фишера.

//+------------------------------------------------------------------+
//|                                              FisherTransform.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"
#property indicator_separate_window
#property description "MQL5 version of Fisher Transform indicator"
#property indicator_buffers 4
#property indicator_level1 0
#property indicator_levelcolor Silver
#property indicator_plots 2
#property indicator_type1         DRAW_LINE
#property indicator_color1        Red
#property indicator_width1 1
#property indicator_type2         DRAW_LINE
#property indicator_color2        Blue
#property indicator_width2 1

double Value1[];
double Fisher[];
double Trigger[];

input int Len=10;

double medianbuff[];
int hMedian;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,Fisher,INDICATOR_DATA);
   SetIndexBuffer(1,Trigger,INDICATOR_DATA);
   SetIndexBuffer(2,Value1,INDICATOR_CALCULATIONS);
   SetIndexBuffer(3,medianbuff,INDICATOR_CALCULATIONS);
   ArraySetAsSeries(Fisher,true);
   ArraySetAsSeries(Trigger,true);
   ArraySetAsSeries(Value1,true);
   ArraySetAsSeries(medianbuff,true);
   
   hMedian = iMA(_Symbol,PERIOD_CURRENT,1,0,MODE_SMA,PRICE_MEDIAN);
   if(hMedian==INVALID_HANDLE)
     {
      //--- вывести сообщение об ошибке и ее код
      PrintFormat("Ошибка создания индикатора iMA для символа %s/%s, код ошибки %d",
                 _Symbol, EnumToString(PERIOD_CURRENT),GetLastError());
      //--- индикатор завершает работу если возвращаемое значение отрицательно
      return(-1);
     }
//---
   return(0);
  }
  
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
   int  nLimit = MathMin(rates_total-Len-1,rates_total-prev_calculated);
   int copied = CopyBuffer(hMedian,0,0,nLimit,medianbuff);
   if (copied!=nLimit) return (-1);
   nLimit--;
   for(int i=nLimit; i>=0; i--) 
     {
      double price=medianbuff[i];
      double MaxH = price;
      double MinL = price;
      for(int j=0; j<Len; j++) 
        {
         double nprice=medianbuff[i+j];
         if (nprice > MaxH) MaxH = nprice;
         if (nprice < MinL) MinL = nprice;
        }
      Value1[i]=0.5*2.0 *((price-MinL)/(MaxH-MinL)-0.5)+0.5*Value1[i+1];
      if(Value1[i]>0.9999) Value1[i]=0.9999;
      if(Value1[i]<-0.9999) Value1[i]=-0.9999;
      Fisher[i]=0.25*MathLog((1+Value1[i])/(1-Value1[i]))+0.5*Fisher[i+1];
      Trigger[i]=Fisher[i+1];
     }
//--- возвращаем значение prev_calculated для следующего вызова
   return(rates_total);
  }
//+------------------------------------------------------------------+

Обратите внимание на то, что сгенерированные сигналы являются четкими.

В качестве сигнальной линии взято значение индикатора с задержкой на 1 бар:

Рис. 7. Индикатор Fisher Transform 

Рис. 7. Индикатор Fisher Transform

 

4. Обратное преобразование Фишера и его использование в циклических индикаторах

Выражение для обратного преобразования Фишера получается решением уравнения преобразования Фишера:


,

Рис. 8. Обратное преобразование Фишера (Inverse Fisher Transform) 

Рис. 8. Обратное преобразование Фишера (Inverse Fisher Transform)

Данная функция является обратной по отношению к функции, осуществляющей преобразование Фишера.

Для |x|>2 коэффициент умножения входных данных не превышает единицу (-1 и +1 для отрицательных и положительных значений соответственно), для |x|<1 коэффициент преобразования почти линейный, поэтому выходные значения будут почти совпадать с входными.

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

 

5. Пример реализации обратного преобразования на MQL5

Для проверки обратного преобразования Фишера я реализовал на MQL5 индикатор Sylvain's Vervoort Smoothed RSI Inverse Fisher Transform, опубликованный в журнале "Stocks and Commodities" (Октябрь, 2010). Обратное преобразование Фишера реализовано для множества торговых платформ, исходные коды доступны на сайте traders.com и Code Basе на mql5.com.

Поскольку в языке MQL5 нет функции iRSIOnArray, я добавил ее в код индикатора. Единственным отличием является то, что для параметров RSIPeriod и EMAPeriod установлены значения по умолчанию RSIPeriod=21 и EMAPeriod=34, поскольку они оказались наилучшими для котировок EURUSD, 1H.

При необходимости можно указать параметры оригинальной версии: RSIPeriod=4 и EMAPeriod=4.

//+------------------------------------------------------------------+
//|                            SmoothedRSIInverseFisherTransform.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"
#property indicator_separate_window
#include <MovingAverages.mqh>
#property description "MQL5 version of Silvain Vervoort's Inverse RSI"
#property indicator_minimum -10
#property indicator_maximum 110
#property indicator_buffers 16
#property indicator_level1 12
#property indicator_level2 88
#property indicator_levelcolor Silver
#property indicator_plots 1
#property indicator_type1         DRAW_LINE
#property indicator_color1        LightSeaGreen
#property indicator_width1 2

int                 ma_period=10;             // Период MA
int                 ma_shift=0;               // Сдвиг
ENUM_MA_METHOD       ma_method=MODE_LWMA;       // Метод сглаживания
ENUM_APPLIED_PRICE   applied_price=PRICE_CLOSE// Тип цен

double wma0[];
double wma1[];
double wma2[];
double wma3[];
double wma4[];
double wma5[];
double wma6[];
double wma7[];
double wma8[];
double wma9[];
double ema0[];
double ema1[];
double rainbow[];
double rsi[];
double bufneg[];
double bufpos[];
double srsi[];
double fish[];

int hwma0;

int wma1weightsum;
int wma2weightsum;
int wma3weightsum;
int wma4weightsum;
int wma5weightsum;
int wma6weightsum;
int wma7weightsum;
int wma8weightsum;
int wma9weightsum;

extern int     RSIPeriod=21;
extern int     EMAPeriod=34;
 
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0,fish,INDICATOR_DATA);
   SetIndexBuffer(1,wma0,INDICATOR_CALCULATIONS);
   SetIndexBuffer(2,wma1,INDICATOR_CALCULATIONS);
   SetIndexBuffer(3,wma2,INDICATOR_CALCULATIONS);
   SetIndexBuffer(4,wma3,INDICATOR_CALCULATIONS);
   SetIndexBuffer(5,wma4,INDICATOR_CALCULATIONS);
   SetIndexBuffer(6,wma5,INDICATOR_CALCULATIONS);
   SetIndexBuffer(7,wma6,INDICATOR_CALCULATIONS);
   SetIndexBuffer(8,wma7,INDICATOR_CALCULATIONS);
   SetIndexBuffer(9,wma8,INDICATOR_CALCULATIONS);
   SetIndexBuffer(10,wma9,INDICATOR_CALCULATIONS);
   SetIndexBuffer(11,rsi,INDICATOR_CALCULATIONS);
   SetIndexBuffer(12,ema0,INDICATOR_CALCULATIONS);
   SetIndexBuffer(13,srsi,INDICATOR_CALCULATIONS);
   SetIndexBuffer(14,ema1,INDICATOR_CALCULATIONS);
   SetIndexBuffer(15,rainbow,INDICATOR_CALCULATIONS);

   ArraySetAsSeries(fish,true);
   ArraySetAsSeries(wma0,true);
   ArraySetAsSeries(wma1,true);
   ArraySetAsSeries(wma2,true);
   ArraySetAsSeries(wma3,true);
   ArraySetAsSeries(wma4,true);
   ArraySetAsSeries(wma5,true);
   ArraySetAsSeries(wma6,true);
   ArraySetAsSeries(wma7,true);
   ArraySetAsSeries(wma8,true);
   ArraySetAsSeries(wma9,true);
   ArraySetAsSeries(ema0,true);
   ArraySetAsSeries(ema1,true);
   ArraySetAsSeries(rsi,true);
   ArraySetAsSeries(srsi,true);
   ArraySetAsSeries(rainbow,true);

   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,0);
//--- установка empty value
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
//--- точность отображения
   IndicatorSetInteger(INDICATOR_DIGITS,2);

   hwma0=iMA(_Symbol,PERIOD_CURRENT,2,ma_shift,ma_method,applied_price);
   if(hwma0==INVALID_HANDLE)
     {
      //--- вывод сообщения об ошибке и ее коде
      PrintFormat("Ошибка создания индикатора iMA для символа %s/%s, код ошибки %d",
                 _Symbol, EnumToString(PERIOD_CURRENT), GetLastError());
      //--- работа индикатора завершается если возвращаемое значение отрицательное
      return(-1);
     }

   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
   int nLimit;

   if(rates_total!=prev_calculated)
     {
      CopyBuffer(hwma0,0,0,rates_total-prev_calculated+1,wma0);
      LinearWeightedMAOnBuffer(rates_total,prev_calculated,0,2,wma0,wma1,wma1weightsum);
      LinearWeightedMAOnBuffer(rates_total,prev_calculated,0,2,wma1,wma2,wma2weightsum);
      LinearWeightedMAOnBuffer(rates_total,prev_calculated,0,2,wma2,wma3,wma3weightsum);
      LinearWeightedMAOnBuffer(rates_total,prev_calculated,0,2,wma3,wma4,wma4weightsum);
      LinearWeightedMAOnBuffer(rates_total,prev_calculated,0,2,wma4,wma5,wma5weightsum);
      LinearWeightedMAOnBuffer(rates_total,prev_calculated,0,2,wma5,wma6,wma6weightsum);
      LinearWeightedMAOnBuffer(rates_total,prev_calculated,0,2,wma6,wma7,wma7weightsum);
      LinearWeightedMAOnBuffer(rates_total,prev_calculated,0,2,wma7,wma8,wma8weightsum);
      LinearWeightedMAOnBuffer(rates_total,prev_calculated,0,2,wma8,wma9,wma9weightsum);

      if(prev_calculated==0) nLimit=rates_total-1;
      else nLimit=rates_total-prev_calculated+1;
      
      for(int i=nLimit; i>=0; i--)
         rainbow[i]=(5*wma0[i]+4*wma1[i]+3*wma2[i]+2*wma3[i]+wma4[i]+wma5[i]+wma6[i]+wma7[i]+wma8[i]+wma9[i])/20.0;

      iRSIOnArray(rates_total,prev_calculated,11,RSIPeriod,rainbow,rsi,bufpos,bufneg);

      ExponentialMAOnBuffer(rates_total,prev_calculated,12,EMAPeriod,rsi,ema0);
      ExponentialMAOnBuffer(rates_total,prev_calculated,13,EMAPeriod,ema0,ema1);

      for(int i=nLimit; i>=0; i--)
         srsi[i]=ema0[i]+(ema0[i]-ema1[i]);

      for(int i=nLimit; i>=0; i--)
         fish[i]=((MathExp(2*srsi[i])-1)/(MathExp(2*srsi[i])+1)+1)*50;         
     }
//--- возвращаем значение prev_calculated для следующего вызова
   return(rates_total);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
///                        Расчет RSI
//+------------------------------------------------------------------+
int iRSIOnArray(const int rates_total,const int prev_calculated,const int begin,
                const int period,const double &price[],double &buffer[],double &bpos[],double &bneg[])
  {
   int        i;
   ArrayResize(bneg,rates_total);
   ArrayResize(bpos,rates_total);

   if(period<=1 || rates_total-begin<period) return(0);
//--- сохраняем флаги таймсерий
   bool as_series_price=ArrayGetAsSeries(price);
   bool as_series_buffer=ArrayGetAsSeries(buffer);
   if(as_series_price) ArraySetAsSeries(price,false);
   if(as_series_buffer) ArraySetAsSeries(buffer,false);

   double diff=0.0;
//--- проверка количества данных
   if(rates_total<=period)
      return(0);
//--- предварительные расчеты
   int ppos=prev_calculated-1;
   if(ppos<=begin+period)
     {
      //--- первые RSIPeriod значений не вычисляются
      for (i=0; i<begin; i++)
      {
      buffer[i]=0.0;
      bpos[i]=0.0;
      bneg[i]=0.0;
      }
      double SumP=0.0;
      double SumN=0.0;
      for(i=begin;i<=begin+period;i++)
        {
         buffer[i]=0.0;
         bpos[i]=0.0;
         bneg[i]=0.0;
         //PrintFormat("%f %f\n", price[i], price[i-1]);
         diff=price[i]-price[i-1];
         SumP+=(diff>0?diff:0);
         SumN+=(diff<0?-diff:0);
        }
      //--- расчитываем первое отображаемое значение
      bpos[begin+period]=SumP/period;
      bneg[begin+period]=SumN/period;
      if (bneg[begin+period]>0.0000001)
      buffer[begin+period]=0.1*((100.0-100.0/(1+bpos[begin+period]/bneg[begin+period]))-50);
      //--- подготавливаем индекс позиции для расчета
      ppos=begin+period+1;
     }
//--- основной цикл расчета
   for(i=ppos;i<rates_total && !IsStopped();i++)
     {
      diff=price[i]-price[i-1];
      bpos[i]=(bpos[i-1]*(period-1)+((diff>0.0)?(diff):0.0))/period;
      bneg[i]=(bneg[i-1]*(period-1)+((diff<0.0)?(-diff):0.0))/period;
      if (bneg[i]>0.0000001)
      buffer[i]=0.1*((100.0-100.0/(1+bpos[i]/bneg[i]))-50);
      //Print(buffer[i]);
     }
//--- восстанавливаем флаги as_series flags
   if(as_series_price) ArraySetAsSeries(price,true);
   if(as_series_buffer) ArraySetAsSeries(buffer,true);

   return(rates_total);
  }
//+------------------------------------------------------------------+

 Рис. 9. Индикатор Smoothed RSI Inverse Fisher Transform

Рис. 9. Индикатор Smoothed RSI Inverse Fisher Transform

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

При подготовке материалов статьи я заинтересовался, каким образом Фишер пришел к этим преобразованиям, но в сети Internet этой информации не нашел.

Графики прямого и обратного преобразования Фишера напомнили мне гиперболические функции. Эти функции можно получить из формулы Эйлера для чисто мнимого аргумента и выразить их через через экспоненты. Заглянув в книжки по математике и дважды проверив расчеты, я обнаружил, что:

,

,

поскольку теперь tanh(x) может быть представлен как:

,

имеем:

 

Да, это как раз те самые выражения, которые были приведены выше. Тайна преобразований Фишера раскрыта! Прямое преобразование Фишера - это просто arctanh(x), а обратное - tanh(x)!


6. Модуль торговых сигналов

Для проверки обратного преобразования Фишера был написан модуль торговых сигналов, основанный на индикаторе Inverse Fisher Transform.

На его примере легко понять структуру модуля торговых сигналов, основанного на пользовательском индикаторе.

Для хранения индикатора Inverse Fisher indicator был использован экземпляр класса CiCustom с переопределенными виртуальными функциями класса CExpertSignal: CheckOpenLong() и CheckOpenShort(), ответственных за генерацию торговых сигналов в случае отсутствия открытых позиций), а также CheckReverseLong() и CheckReverseShort(), ответственных за переворот позиций.

//+------------------------------------------------------------------+
//|                               InverseFisherRSISmoothedSignal.mqh |
//|                                    Copyright © 2011, Investeo.pl |
//|                                               http://Investeo.pl |
//|                                                      Version v01 |
//+------------------------------------------------------------------+
#property tester_indicator "SmoothedRSIInverseFisherTransform.ex5"
//+------------------------------------------------------------------+
//| включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Expert\ExpertSignal.mqh>
//+------------------------------------------------------------------+
//| Класс CSignalInverseFisherRSISmoothed.                           |
//| Описание: Класс генерации торговых сигналов                      |
//|           на базе индикатора InverseFisherRSISmoothed            |
//|           Наследник класса CExpertSignal.                        |
//+------------------------------------------------------------------+

// wizard description start
//+------------------------------------------------------------------+
//| Description of the class                                         |
//| Title=Signal based on the Inverse Fisher RSI Smoothed Indicator  |
//| Type=SignalAdvanced                                              |
//| Name=InverseFisherRSISmoothed                                    |
//| Class=CSignalInverseFisherRSISmoothed                            |
//| Page=                                                            |
//+------------------------------------------------------------------+
// wizard description end
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Класс CSignalInverseFisherRSISmoothed                            |
//| Назначение: Класс модуля торговых сигналов, основанный           |
//|             на индикаторе InverseFisherRSISmoothed               |
//+------------------------------------------------------------------+
class CSignalInverseFisherRSISmoothed : public CExpertSignal
  {
protected:
   CiCustom          m_invfish;
   double            m_stop_loss;
   
public:
                     CSignalInverseFisherRSISmoothed();
   virtual bool      InitIndicators(CIndicators *indicators);
   virtual bool      ValidationSettings();
   //---
   virtual bool      CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration);
   virtual bool      CheckReverseLong(double &price,double &sl,double &tp,datetime &expiration);
   virtual bool      CheckOpenShort(double &price,double &sl,double &tp,datetime &expiration);
   virtual bool      CheckReverseShort(double &price,double &sl,double &tp,datetime &expiration);
   
protected:
   bool              InitInvFisher(CIndicators *indicators);
   double            InvFish(int ind) { return(m_invfish.GetData(0,ind)); }
  };
//+------------------------------------------------------------------+
//| Конструктор класса CSignalInverseFisherRSISmoothed.              |
//| INPUT:  нет.                                                     |
//| OUTPUT: нет.                                                     |
//| REMARK: нет.                                                     |
//+------------------------------------------------------------------+
void CSignalInverseFisherRSISmoothed::CSignalInverseFisherRSISmoothed()
  {
//--- инициализация защищенных переменных класса (при необходимости)
  }
//+------------------------------------------------------------------+
//| Проверка настроек параметров                                     |
//| INPUT:  нет.                                                     |
//| OUTPUT: true- если настройки верные, иначе false.                |
//| REMARK: нет.                                                     |
//+------------------------------------------------------------------+
bool CSignalInverseFisherRSISmoothed::ValidationSettings()
  {
//--- первоначальная проверка настроек
 if(!CExpertSignal::ValidationSettings()) return(false);
//--- ok
   return(true);
  }
  
//+------------------------------------------------------------------+
//| Создание пользовательского индикатора                            | 
//| Inverse Fisher custom indicator.                                 |
//| INPUT:  indicators - указатель на коллекцию индикаторов          |
//| OUTPUT: true-в случае успеха, иначе false.                       |
//| REMARK: нет.                                                     |
//+------------------------------------------------------------------+
  bool CSignalInverseFisherRSISmoothed::InitInvFisher(CIndicators *indicators)
  {
//--- проверка указателя
   printf(__FUNCTION__+": инициализация Inverse Fisher Indicator");
   if(indicators==NULL) return(false);
//--- добавление объекта в коллекцию
   if(!indicators.Add(GetPointer(m_invfish)))
     {
      printf(__FUNCTION__+": ошибка добавления объекта");
      return(false);
     }
     MqlParam invfish_params[];
   ArrayResize(invfish_params,2);
   invfish_params[0].type=TYPE_STRING;
   invfish_params[0].string_value="SmoothedRSIInverseFisherTransform";
   //--- цена
   invfish_params[1].type=TYPE_INT;
   invfish_params[1].integer_value=PRICE_CLOSE;
//--- инициализация объекта
   if(!m_invfish.Create(m_symbol.Name(),m_period,IND_CUSTOM,2,invfish_params))
     {
      printf(__FUNCTION__+": ошибка инициализации объекта");
      return(false);
     }
   m_invfish.NumBuffers(18);
//--- ok
   return(true);
  }
//+------------------------------------------------------------------+
//| Метод создания индикаторов и таймсерий.                          |
//| INPUT:  indicators -pointer of indicator collection.             |
//| OUTPUT: true-if successful, false otherwise.                     |
//| REMARK: нет.                                                     |
//+------------------------------------------------------------------+
bool CSignalInverseFisherRSISmoothed::InitIndicators(CIndicators *indicators)
  {
//--- проверка указателя
   if(indicators==NULL) return(false);
//--- инициализация инидикаторов и таймсерий дополнительных фильтров
   if(!CExpertSignal::InitIndicators(indicators)) return(false);
//--- создание и инициализация индикатора Inverse Fisher indicator
   if(!InitInvFisher(indicators)) return(false);
   m_stop_loss = 0.0010;
//--- ok
   printf(__FUNCTION__+": все индикаторы успешно инициализированы.");
   return(true);
  }
//+------------------------------------------------------------------+
//| Проверка условий для открытия длинной позиции.                   |
//| INPUT:  price      - ссылка на переменную price,                 |
//|         sl         - ссылка на переменную stop loss,             |
//|         tp         - ссылка на переменную take profit,           |
//|         expiration - ссылка на переменную expiration.            |
//| OUTPUT: true-если условие выполнено, иначе false.                |
//| REMARK: нет.                                                     |
//+------------------------------------------------------------------+
bool CSignalInverseFisherRSISmoothed::CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration)
  {
   printf(__FUNCTION__+" Проверка сигнала");
   
   int idx=StartIndex();
//---
   price=0.0;
   tp   =0.0;
//---
   if(InvFish(idx+2)<12.0 && InvFish(idx+1)>12.0)
   { 
      printf(__FUNCTION__ + " BUY SIGNAL");
      return true;
   } else printf(__FUNCTION__ + " NO SIGNAL");
//---
   return false;
  }
//+------------------------------------------------------------------+
//| Проверка условий для закрытия длинной позиции.                   |
//| INPUT:  price - ссылка на переменную price.                      |
//| OUTPUT: true-если условие выполнено, иначе false.                |
//| REMARK: нет.                                                     |
//+------------------------------------------------------------------+
bool CSignalInverseFisherRSISmoothed::CheckReverseLong(double &price,double &sl,double &tp,datetime &expiration)
  {
   long tickCnt[1];
   int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt);
   if (ticks!=1 || tickCnt[0]!=1) return false;
   
   int idx=StartIndex();
   
   price=0.0;
// sl   =m_symbol.NormalizePrice(m_symbol.Bid()+20*m_stop_level);
//---
   if((InvFish(idx+1)>88.0 && InvFish(idx)<88.0)  || 
     (InvFish(idx+2)>88.0 && InvFish(idx+1)<88.0) ||
     (InvFish(idx+2)>12.0 && InvFish(idx+1)<12.0))
  {
   printf(__FUNCTION__ + " REVERSE LONG SIGNAL");
   return true;
   } else printf(__FUNCTION__ + " NO SIGNAL");
   return false;
  }
//+------------------------------------------------------------------+
//| Проверка условий для открытия короткой позиции.                  |
//| INPUT:  price      - ссылка на переменную price,                 |
//|         sl         - ссылка на переменную stop loss,             |
//|         tp         - ссылка на переменную take profit,           |
//|         expiration - ссылка на переменную expiration.            |
//| OUTPUT: true-если условие выполнено, иначе false.                |
//| REMARK: нет.                                                     |
//+------------------------------------------------------------------+
bool CSignalInverseFisherRSISmoothed::CheckOpenShort(double &price,double &sl,double &tp,datetime &expiration)
  {
   printf(__FUNCTION__+" checking signal");
   int idx=StartIndex();
//---
   price=0.0;
   sl   = 0.0;
//---
   if(InvFish(idx+2)>88.0 && InvFish(idx+1)<88.0)
   {printf(__FUNCTION__ + " SELL SIGNAL");
      return true;} else printf(__FUNCTION__ + " NO SIGNAL");
      
//---
   return false;
  }
//+------------------------------------------------------------------+
//| Проверка условий для закрытия короткой позиции.                  |
//| INPUT:  price - ссылка на переменную price.                      |
//| OUTPUT: true-если условие выполнено, иначе false.                |
//| REMARK: нет.                                                     |
//+------------------------------------------------------------------+
bool CSignalInverseFisherRSISmoothed::CheckReverseShort(double &price,double &sl,double &tp,datetime &expiration)
  {
   long tickCnt[1];
   int ticks=CopyTickVolume(Symbol(), 0, 0, 1, tickCnt);
   if (ticks!=1 || tickCnt[0]!=1) return false;
   
   int idx=StartIndex();
  
   price=0.0;
//---
   
   if((InvFish(idx+1)<12.0 && InvFish(idx)>12.0) ||
    (InvFish(idx+2)<12.0 && InvFish(idx+1)>12.0) ||
    (InvFish(idx+2)<88.0 && InvFish(idx+1)>88.0)) 
  {
   printf(__FUNCTION__ + " REVERSE SHORT SIGNAL");
   return true;
   } else printf(__FUNCTION__ + " NO SIGNAL");
   return false;
  }

 

7. Торговый советник

Для проверки обратного преобразования Фишера при помощи MQL5 Wizard я сгенерировал стандартный советник, использующий модуль торговых сигналов, представленный выше.

Также я добавил модуль сопровождения открытых позиций (трейлинга) из статьи "Мастер MQL5: Как написать свой модуль сопровождения открытых позиций".

//+------------------------------------------------------------------+
//|                                                 InvRSIFishEA.mq5 |
//|                        Copyright 2011, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
//--- available signals
#include <Expert\Signal\MySignal\InverseFisherRSISmoothedSignal.mqh>
//--- available trailing
#include <Expert\Trailing\SampleTrailing.mqh>
//--- available money management
#include <Expert\Money\MoneyFixedLot.mqh>
//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
//--- inputs for expert
input string Expert_Title         ="InvRSIFishEA";   // Document name
ulong        Expert_MagicNumber   =7016; // 
bool         Expert_EveryTick     =true; // 
//--- inputs for main signal
input int    Signal_ThresholdOpen =10;    // Signal threshold value to open [0...100]
input int    Signal_ThresholdClose=10;    // Signal threshold value to close [0...100]
input double Signal_PriceLevel    =0.0;   // Price level to execute a deal
input double Signal_StopLevel     =0.0;   // Stop Loss level (in points)
input double Signal_TakeLevel     =0.0;   // Take Profit level (in points)
input int    Signal_Expiration    =0;    // Expiration of pending orders (in bars)
input double Signal__Weight       =1.0;   // InverseFisherRSISmoothed Weight [0...1.0]
//--- inputs for money
input double Money_FixLot_Percent =10.0;  // Percent
input double Money_FixLot_Lots    =0.2;   // Fixed volume
//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Initializing expert
   if(!ExtExpert.Init(Symbol(),Period(),Expert_EveryTick,Expert_MagicNumber))
     {
      //--- failed
      printf(__FUNCTION__+": error initializing expert");
      ExtExpert.Deinit();
      return(-1);
     }
//--- Creating signal
   CSignalInverseFisherRSISmoothed *signal=new CSignalInverseFisherRSISmoothed;
   if(signal==NULL)
     {
      //--- failed
      printf(__FUNCTION__+": error creating signal");
      ExtExpert.Deinit();
      return(-2);
     }
//---
   ExtExpert.InitSignal(signal);
   signal.ThresholdOpen(Signal_ThresholdOpen);
   signal.ThresholdClose(Signal_ThresholdClose);
   signal.PriceLevel(Signal_PriceLevel);
   signal.StopLevel(Signal_StopLevel);
   signal.TakeLevel(Signal_TakeLevel);
   signal.Expiration(Signal_Expiration);

//--- Creation of trailing object
   CSampleTrailing *trailing=new CSampleTrailing;
   trailing.StopLevel(0);
   trailing.Profit(20);
   
   if(trailing==NULL)
     {
      //--- failed
      printf(__FUNCTION__+": error creating trailing");
      ExtExpert.Deinit();
      return(-4);
     }
//--- Add trailing to expert (will be deleted automatically))
   if(!ExtExpert.InitTrailing(trailing))
     {
      //--- failed
      printf(__FUNCTION__+": error initializing trailing");
      ExtExpert.Deinit();
      return(-5);
     }
//--- Set trailing parameters
//--- Creation of money object
   CMoneyFixedLot *money=new CMoneyFixedLot;
   if(money==NULL)
     {
      //--- failed
      printf(__FUNCTION__+": error creating money");
      ExtExpert.Deinit();
      return(-6);
     }
//--- Add money to expert (will be deleted automatically))
   if(!ExtExpert.InitMoney(money))
     {
      //--- failed
      printf(__FUNCTION__+": error initializing money");
      ExtExpert.Deinit();
      return(-7);
     }
//--- Set money parameters
   money.Percent(Money_FixLot_Percent);
   money.Lots(Money_FixLot_Lots);
//--- Check all trading objects parameters
   if(!ExtExpert.ValidationSettings())
     {
      //--- failed
      ExtExpert.Deinit();
      return(-8);
     }
//--- Tuning of all necessary indicators
   if(!ExtExpert.InitIndicators())
     {
      //--- failed
      printf(__FUNCTION__+": error initializing indicators");
      ExtExpert.Deinit();
      return(-9);
     }
//--- ok
   return(0);
  }
//+------------------------------------------------------------------+
//| Deinitialization function of the expert                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ExtExpert.Deinit();
  }
//+------------------------------------------------------------------+
//| "Tick" event handler function                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
   ExtExpert.OnTick();
  }
//+------------------------------------------------------------------+
//| "Trade" event handler function                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
   ExtExpert.OnTrade();
  }
//+------------------------------------------------------------------+
//| "Timer" event handler function                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   ExtExpert.OnTimer();
  }
//+------------------------------------------------------------------+

Должен признать, что советник не оказался прибыльным для любого инструмента и таймфрейма, но я оптимизировал его для наилучших результатов на паре EURUSD, 1H.

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

Рис. 10. Результаты торговли по индикатору Smoothed RSI Inverse Fisher Transform 

Рис. 10. Результаты торговли по индикатору Smoothed RSI Inverse Fisher Transform

Рис. 11. График тестирования на истории советника, основанного на индикаторе Smoothed RSI Inverse Fisher Transform

Рис. 11. График тестирования на истории советника, основанного на индикаторе Smoothed RSI Inverse Fisher Transform


Выводы

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

В статье в качестве примера использован индикатор Smoothed RSI Inverse Fisher Transform, разработанный Sylvain's Vervoort, но фактически вы легко можете применить обратное преобразование Фишера для любых осцилляторов и построить свои советники.

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


Литература

  1. The Fisher Transform
  2. Using the Fisher Transform
  3. The Inverse Fisher Transform

Перевод с английского произведен MetaQuotes Software Corp.
Оригинальная статья: https://www.mql5.com/en/articles/303

Прикрепленные файлы |
Nikolay Demko
Nikolay Demko | 17 авг 2011 в 09:35

Интересная статья, спасибо автору.

Пару лет назад пытался вникнуть в преобразование Фишера, но руки не дошли до кодов.


Dmitry Fedoseev
Dmitry Fedoseev | 17 авг 2011 в 17:32

Наконец-то появился нормальный Фишер, а не задом наперед.

 

Андрей
Андрей | 20 авг 2011 в 18:33

Во втором разделе(сразу после рис 6) надо понимать в тексте опечатка: "После использования обратного преобразования Фишера..."?

Automated-Trading
Automated-Trading | 22 авг 2011 в 11:02
Antitak:

Во втором разделе(сразу после рис 6) надо понимать в тексте опечатка: "После использования обратного преобразования Фишера..."?

Спасибо, исправлено.
Vitalie Postolache
Vitalie Postolache | 27 сен 2014 в 15:30

Советник уже не работает, как починить?

MT5 b.975.

2014.09.27 16:26:44     Core 1  tester stopped because OnInit failed
2014.09.27 16:26:44     Core 1  2014.01.02 00:00:00   OnInit: error initializing indicators
2014.09.27 16:26:44     Core 1  2014.01.02 00:00:00   CExpert::InitIndicators: error initialization indicators of trailing object
2014.09.27 16:26:44     Core 1  2014.01.02 00:00:00   CExpertBase::InitIndicators: parameters of setting are not checked
2014.09.27 16:26:44     Core 1  2014.01.02 00:00:00   CExpertBase::SetOtherSeries: changing of timeseries is forbidden
2014.09.27 16:26:44     Core 1  2014.01.02 00:00:00   CExpertBase::SetPriceSeries: changing of timeseries is forbidden
Пользовательские графические элементы управления. Часть 2. Библиотека элементов управления Пользовательские графические элементы управления. Часть 2. Библиотека элементов управления

Во второй статье серии "Пользовательские графические элементы управления" представлена библиотека элементов управления для решения основных задач, возникающих при обеспечении взаимодействия между программой (советником, скриптом, индикатором) и ее пользователем. Библиотека содержит множество классов (CInputBox, CSpinInputBox, CCheckBox, CRadioGroup, CVSсrollBar, CHSсrollBar, CList, CListMS, CComBox, CHMenu, CVMenu, CHProgress, CDialer, CDialerInputBox, CTable) и примеров их использования.

Пользовательские графические элементы управления.  Часть 1. Создание простого элемента управления Пользовательские графические элементы управления. Часть 1. Создание простого элемента управления

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

Пользовательские графические элементы управления. Часть 3. Формы Пользовательские графические элементы управления. Часть 3. Формы

Этой последняя из трех статей, посвященных графическим элементам управления. В ней рассматривается создание главного элемента графического интерфейса, формы, и ее совместное использование с другими элементами управления. Кроме классов формы библиотека элементов управления дополнена классами CFrame, CButton, CLabel.

Андрей Войтенко (avoitenko): "Разработчики что-то имеют от попавших к ним в разработку идей? Абсурд!" Андрей Войтенко (avoitenko): "Разработчики что-то имеют от попавших к ним в разработку идей? Абсурд!"

Разработчик с Украины Андрей Войтенко (avoitenko) активно участвует в сервисе "Работа" на сайте mql5.com, помогая трейдерам со всего мира в реализации их идей. В прошлом году эксперт Андрея на Чемпионате Automated Trading Championship 2010 занял 4-е место, лишь немного уступив бронзовому призеру. На этот раз мы поговорим с Андреем о сервисе "Работа".