English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Aplicar la transformada de Fisher y su transformada inversa al análisis de mercado en Meta Trader 5

Aplicar la transformada de Fisher y su transformada inversa al análisis de mercado en Meta Trader 5

MetaTrader 5Trading | 8 mayo 2014, 10:08
3 632 0
investeo
investeo

Introducción

El siguiente artículo presenta la transformada de Fisher y su transformada inversa aplicadas a los mercados financieros.

La teoría de la transformada de Fisher es llevada a la práctica implementando la versión MQL5 del indicador de la transformada inversa de Fisher RSI presentado en octubre de 2010 en la revista "Stocks and Commodities". La rentabilidad del indicador se prueba mediante un asesor experto que utiliza señales basadas en el indicador de Fisher.

El artículo está basado en los libros de J.F. Ehlers y los artículos encontrados en internet. Toda la bibliografía se cita al final del artículo.


1. FDP (Función de densidad de probabilidad) gausiana vs. ciclos del mercado

Se asume normalmente que los precios tienen una función de densidad de probabilidad normal.

Esto significa que las desviaciones de la media producidas en el precio pueden describirse como la conocida campana gausiana:

Figura 1. Distribución gausiana
 

Figura 1. Campana gausiana 

He mencionado la función de densidad de probabilidad normal. Para comprender esto en toda su extensión vamos a presentar algunas ideas y fórmulas matemáticas que espero que sean, en su mayoría, comprensibles para la mayoría de lectores.

En el diccionario Merriam-Webster la probabilidad se define como

  1. La proporción del número de resultados en un conjunto exhaustivo de resultados igualmente probables que genera un evento dado con respecto al total de resultados posibles o
  2. La posibilidad de que un evento dado ocurra.

Una variable aleatoria es aquella cuyo valor resulta de la medida de algún tipo de proceso aleatorio. En nuestro caso, la variable aleatoria es el precio de un activo. 

Por último, FDP es un acrónimo de Función de Densidad de Probabilidad, una función que describe la probabilidad de que una variable aleatoria X (de nuevo, en el caso de nuestro precio) asuma un valor dentro de un cierto rango de valores. El valor de una variable aleatoria que resulta de una distribución gausiana o distribución normal es una distribución de probabilidad que se usa a menudo para describir las variables del mundo real que tienden a agruparse en torno a un único valor medio.

En términos matemáticos, la probabilidad de que una variable aleatoria X adopte un valor dentro del intervalo [a,b] se define como una integral:

Figura 2. Integral de densidad de probabilidad

Esta representa el área bajo la curva f(x) desde a hasta b. La probabilidad se considera un valor comprendido entre 0 y 100% o desde 0 a 1. Por tanto, hay un límite en el que el área total bajo la curva f(x) debe ser igual a 1 (suma de probabilidades):

Figura 3. Área total bajo la curva

Vamos ahora a volver a la parte inferior de la Figura 1:

Figura 4. Parte inferior del gráfico gausiano
 

Figura 2. Desviaciones estándar de la campana gausiana  

Puede ver aquí qué porcentajes de los valores se encuentran bajo el valor medio +/- 1-3 desviaciones estándar (sigmas). Con una FDP gausiana de 68,27% de casos dentro de más/menos una desviación estándar desde la media, 95,45% dentro de más/menos dos desviaciones estándar y 99,73% dentro de más/menos tres desviaciones estándar desde la media.

¿Cree que es este el caso de los datos de mercado del mundo real? No tanto. Cuando miramos los precios del mercado podemos asumir que el gráfico parece una onda cuadrada en la que, después de romper la resistencia o los niveles de apoyo en los que se agrupan las grandes órdenes, los precios tienden a subir o bajar hasta el siguiente nivel de apoyo/resistencia. Por eso el mercado puede modelarse con gran aproximación como una onda cuadrada o senoidal.

Por favor, observe el trazado senoidal siguiente:

seno

Figura 3. Trazado senoidal 

Observará que, en realidad, la mayoría de transacciones se realizan de forma similar cerca de los niveles de apoyo y resistencia, lo que parece bastante natural. Ahora trazaré la densidad de una onda senoidal. Puede imaginar que estamos girando la Figura 3 unos 90 grados hacia la derecha y dejamos que todos los círculos caigan al suelo: 

Densidad
 

Figura 4. Densidad de la curva senoidal  

Puede observar que la densidad es mayor en las posiciones más a la izquierda y más a la derecha. Esto parece ser coherente con la afirmación anterior de que la mayoría de transacciones se realizan muy cerca de los niveles de apoyo y resistencia. Vamos a comprobar cuál es el porcentaje de casos dibujando un histograma:

Histograma

Figura 5. Histograma de densidad de la curva senoidal

¿Parece una campana gausiana? No exactamente. Las primeras y últimas tres barras parecen tener la mayoría de casos.

J.F. Ehlers en su libro "Сybernetic analysis for stocks and futures" describió un experimento en el que analizaba los bonos T de EE.UU. sobre un periodo de 15 años. Aplicó un canal normalizado de 10 barras y midió la ubicación del precio con 100 binarios y contó el número de veces que el precio se encontraba en cada binario. Los resultados de su distribución de probabilidad recordaba mucho a los de una onda senoidal.  


2. Transformada de Fisher y su aplicación a las series de tiempo

Ahora sabemos que la función densidad de probabilidad (FDP) de un ciclo de mercado no recuerda a una gausiana sino más bien a una FDP de una onda senoidal y la mayoría de indicadores asumen que la FDP del ciclo del mercado es gausiana, por lo que necesitamos conocer una forma de "corregir" esto. La solución es usar la transformada de Fisher. La transformada de Fisher cambia una FDP de cualquier forma de onda en otra aproximadamente gausiana.

La ecuación para la transformada de Fisher es:

Figura 6. Ecuación de la transformada de Fisher

 Figura 5. Transformada de Fisher

Figura 6. Transformada de Fisher  

He mencionado que el resultado de la transformada de Fisher es una FDP gausiana. Para explicar esto merece la pena observar la figura 6.

Cuando los datos de entrada se acercan a su media, la ganancia es aproximadamente la unidad (vea el gráfico para |X<0.5|). Por otro lado, cuando la entrada normalizada se acerca al límite el resultado se amplifica en gran medida (ver el gráfico para 0.5<|x|<1). En la práctica puede pensar en una creciente cola "casi gausiana". Esto es lo que le ocurre exactamente a la transformada FDP. 

¿Cómo aplicamos la transformada de Fisher al trading? Primero, debido a la limitación de |x|<1, los precios deben ser normalizados en este rango. Cuando los precios normalizados están sujetos a la transformada de Fisher, los movimientos de precio extremos son relativamente extraños. Esto significa que la transformada de Fisher captura estos movimientos de precio extremos y nos permite operar según dichos extremos.


3. Transformada de Fisher en MQL5

El código fuente del indicador de la transformada de Fisher se describe en el libro de Ehler "Cybernetic Analysis for Stocks and Futures".

Ya ha sido implementado en MQL4 y lo he convertido a MQL5. El indicador utiliza precios medios (H+L)/2, yo he usado la función iMA() para extraer los precios medios del historial.

Primero, los precios se normalizan dentro del rango de 10 barras y los precios normalizados están sujetos a la transformada de Fisher.

//+------------------------------------------------------------------+
//|                                              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)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iMA indicator for the symbol %s/%s, error code %d",
                 _Symbol,
                 EnumToString(PERIOD_CURRENT),
                 GetLastError());
      //--- the indicator is stopped early, if the returned value is negative
      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];
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Por favor, tenga en cuenta que se generan señales pronunciadas.

La línea de señal es simplemente el precio transformado de Fisher retrasado una barra:

Indicador de la transformada de Fisher 

Figura 7. Indicador de la transformada de Fisher  

 

4. Transformada inversa de Fisher y su aplicación a los indicadores de ciclo

La ecuación de la transformada inversa de Fisher se obtiene resolviendo la ecuación de la transformada de Fisher para x en función de y:

Figura 8. Ecuación de la transformada inversa de Fisher,

Figura 6. Transformada inversa de Fisher 

Figura 8. Transformada inversa de Fisher 


La respuesta de transferencia de esta función es inversa a la de la transformada de Fisher.

Para |x|>2 la entrada se comprime para que no exceda la unidad (para números negativos -1 y para positivos +1) y para |x|<1 es una relación casi lineal, lo que significa que el resultado tiene más o menos las mismas características que la entrada.

El resultado es que cuando se aplica la transformada inversa de Fisher a datos de entrada preparados adecuadamente, el resultado tiene grandes probabilidades de ser -1 o +1. Esto hace que la transformada inversa de Fisher sea perfecta para aplicarla a los indicadores de oscilador. La transformada inversa de Fisher puede mejorarlos al proporcionar señales de compra o venta pronunciadas. 

 

5. Ejemplo de la transformada inversa de Fisher en MQL5

Para verificar la transformada inversa de Fisher he implementado una versión en MQL5 del indicador de la transformada inversa RSI ajustado de Sylvain Vervoort, presentado en la edición de octubre de 2010 de la revista "Stocks and Commodities", y he construido un módulo de señal de trading y un asesor experto basado en dicho indicador.

El indicador de la transformada inversa de Fisher ya ha sido implementado para muchas plataformas de trading y los códigos fuente están disponibles en el sitio web de traders.com y en la Base de código de MQL5.com.

Como no había una función iRSIOnArray en MQL5 la he añadido al código del indicador. La única diferencia con el indicador original es RSIPeriod por defecto establecido en 21 y EMAPeriod establecido en 34, ya que se han comportado mejor con mis ajustes (EURUSD 1H). Puede que quiera cambiarlo al RSIPeriod 4 y EMAPeriod 4 por defecto.

//+------------------------------------------------------------------+
//|                            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;             // period of ma
int                  ma_shift=0;               // shift
ENUM_MA_METHOD       ma_method=MODE_LWMA;        // type of smoothing
ENUM_APPLIED_PRICE   applied_price=PRICE_CLOSE// type of price

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);
//--- sets drawing line empty value
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
//--- digits
   IndicatorSetInteger(INDICATOR_DIGITS,2);

   hwma0=iMA(_Symbol,PERIOD_CURRENT,2,ma_shift,ma_method,applied_price);
   if(hwma0==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iMA indicator for the symbol %s/%s, error code %d",
                  _Symbol,
                  EnumToString(PERIOD_CURRENT),
                  GetLastError());
      //--- the indicator is stopped early, if the returned value is negative
      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;         
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
///                        Calculating 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;
//--- check for data
   ArrayResize(bneg,rates_total);
   ArrayResize(bpos,rates_total);

   if(period<=1 || rates_total-begin<period) return(0);
//--- save as_series flags
   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;
//--- check for rates count
   if(rates_total<=period)
      return(0);
//--- preliminary calculations
   int ppos=prev_calculated-1;
   if(ppos<=begin+period)
     {
      //--- first RSIPeriod values of the indicator are not calculated
      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);
        }
      //--- calculate first visible value
      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);
      //--- prepare the position value for main calculation
      ppos=begin+period+1;
     }
//--- the main loop of calculations

   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]);
     }
//--- restore as_series flags
   if(as_series_price) ArraySetAsSeries(price,true);
   if(as_series_buffer) ArraySetAsSeries(buffer,true);

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

 Indicador inverso de Fisher

Figura 9. Indicador de la transformada inversa de Fisher  

Como solo he mostrado ecuaciones de transformadas, puede que esté confuso con los orígenes de la transformada de Fisher y la transformada inversa de Fisher.

Cuando estaba recopilando materiales para escribir el artículo me interesé en cómo Fisher obtuvo ambas transformadas pero no encontré nada en internet.

Aunque miré a ambas, la transformada de Fisher y la transformada inversa de Fisher y me recordaron a un tipo de funciones trigonométricas o hiperbólicas (¿puede ver algún tipo de parecido?). Como estas funciones pueden obtenerse a partir de la fórmula de Euler y expresarse en términos del número de Euler "e", volví a los libros de cálculo y lo comprobé de nuevo:

Figura 9. Ecuación del seno hiperbólico,

Figura 11. Ecuación del coseno hiperbólico,

y como sabemos que la tanh(x) puede obtenerse por:

Figura 12. Ecuación de la tangente hiperbólica,

y ... 

Figura 12. Ecuación de la arcotangente hiperbólica 

Sí, son exactamente las mismas ecuaciones que mostré anteriormente. ¡La ecuación de la transformada de Fisher desmitificada! ¡La transformada de Fisher es simplemente la arctanh(x) y la transformada inversa de Fisher es su inversa, la tanh(x)!


6. Módulo de señales de trading

Para verificar la transformada inversa de Fisher construyo un módulo de señales de trading basado en el indicador de la transformada inversa de Fisher.

Puede ser útil ver el módulo de trading basado en un indicador personalizado. He usado la instancia de clase CiCustom para mantener el indicador de Fisher inverso y los cuatro métodos virtuales pasados por alto de la clase CExpertSignal: CheckOpenLong() y CheckOpenShort() se encargan de generar señales cuando no hay una posición abierta y CheckReverseLong() y CheckReverseShort() se encargan de invertir la posición abierta. 

//+------------------------------------------------------------------+
//|                               InverseFisherRSISmoothedSignal.mqh |
//|                                    Copyright © 2011, Investeo.pl |
//|                                               http://Investeo.pl |
//|                                                      Version v01 |
//+------------------------------------------------------------------+
#property tester_indicator "SmoothedRSIInverseFisherTransform.ex5"
//+------------------------------------------------------------------+
//| include files                                                    |
//+------------------------------------------------------------------+
#include <Expert\ExpertSignal.mqh>
//+------------------------------------------------------------------+
//| Class CSignalInverseFisherRSISmoothed.                           |
//| Description: Class generating InverseFisherRSISmoothed signals   |
//|              Derived from CExpertSignal.                         |
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| CSignalInverseFisherRSISmoothed class                            |
//| Purpose: A class of a module of trade signals,                   |
//| on InverseFisherRSISmoothed                                      |
//+------------------------------------------------------------------+
class CSignalInverseFisherRSISmoothed : public CExpertSignal
  {
protected:
   CiCustom          m_invfish;
   double            m_stop_loss;
   
public:
                     CSignalInverseFisherRSISmoothed();
   //--- methods initialize protected data
   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)); }
  };
//+------------------------------------------------------------------+
//| Constructor CSignalInverseFisherRSISmoothed.                                    |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CSignalInverseFisherRSISmoothed::CSignalInverseFisherRSISmoothed()
  {
//--- initialize protected data
  }
//+------------------------------------------------------------------+
//| Validation settings protected data.                              |
//| INPUT:  no.                                                      |
//| OUTPUT: true-if settings are correct, false otherwise.           |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CSignalInverseFisherRSISmoothed::ValidationSettings()
  {
//--- initial data checks
 if(!CExpertSignal::ValidationSettings()) return(false);
//--- ok
   return(true);
  }
  
//+------------------------------------------------------------------+
//| Create Inverse Fisher custom indicator.                          |
//| INPUT:  indicadores, punteros de la recogida del indicador.             |
//| OUTPUT: true-if successful, false otherwise.                     |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
  bool CSignalInverseFisherRSISmoothed::InitInvFisher(CIndicators *indicators)
  {
//--- check pointer
   printf(__FUNCTION__+": initializing Inverse Fisher Indicator");
   if(indicators==NULL) return(false);
//--- add object to collection
   if(!indicators.Add(GetPointer(m_invfish)))
     {
      printf(__FUNCTION__+": error adding object");
      return(false);
     }
     MqlParam invfish_params[];
   ArrayResize(invfish_params,2);
   invfish_params[0].type=TYPE_STRING;
   invfish_params[0].string_value="SmoothedRSIInverseFisherTransform";
   //--- applied price
   invfish_params[1].type=TYPE_INT;
   invfish_params[1].integer_value=PRICE_CLOSE;
//--- initialize object
   if(!m_invfish.Create(m_symbol.Name(),m_period,IND_CUSTOM,2,invfish_params))
     {
      printf(__FUNCTION__+": error initializing object");
      return(false);
     }
   m_invfish.NumBuffers(18);
//--- ok
   return(true);
  }
//+------------------------------------------------------------------+
//| Create indicators.                                               |
//| INPUT:  indicadores, punteros de la recogida del indicador.             |
//| OUTPUT: true-if successful, false otherwise.                     |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CSignalInverseFisherRSISmoothed::InitIndicators(CIndicators *indicators)
  {
//--- check pointer
   if(indicators==NULL) return(false);
//--- initialization of indicators and timeseries of additional filters
   if(!CExpertSignal::InitIndicators(indicators)) return(false);
//--- create and initialize SAR indicator
   if(!InitInvFisher(indicators)) return(false);
   m_stop_loss = 0.0010;
//--- ok
   printf(__FUNCTION__+": all inidicators properly initialized.");
   return(true);
  }
//+------------------------------------------------------------------+
//| Check conditions for long position open.                         |
//| INPUT:  price      - reference for price,                        |
//|         sl         - reference for stop loss,                    |
//|         tp         - reference for take profit,                  |
//|         expiration - reference for expiration.                   |
//| OUTPUT: true-if condition performed, false otherwise.            |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CSignalInverseFisherRSISmoothed::CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration)
  {
   printf(__FUNCTION__+" checking signal");
   
   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;
  }
//+------------------------------------------------------------------+
//| Check conditions for long position close.                        |
//| INPUT:  precio, referencia para el precio.                              |
//| OUTPUT: true-if condition performed, false otherwise.            |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Check conditions for short position open.                        |
//| INPUT:  price      - refernce for price,                         |
//|         sl         - refernce for stop loss,                     |
//|         tp         - refernce for take profit,                   |
//|         expiration - refernce for expiration.                    |
//| OUTPUT: true-if condition performed, false otherwise.            |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Check conditions for short position close.                       |
//| INPUT:  price - refernce for price.                              |
//| OUTPUT: true-if condition performed, false otherwise.            |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
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. Asesor experto

Para verificar la transformada inversa de Fisher construyo un asesor experto estándar que usa el módulo de señales de trading descrito anteriormente.

También he añadido un módulo trailing stop-loss tomado del artículo "MQL5 Wizard: Cómo crear un módulo de rastreo de posiciones abiertas".

//+------------------------------------------------------------------+
//|                                                 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();
  }
//+------------------------------------------------------------------+

Debo admitir que el asesor experto no fue rentable para cada activo y para cada periodo de tiempo, pero lo he modificado para que diese buenos resultados para el periodo de tiempo de EURUSD H1.

Animo a los lectores a intentar cambiar el módulo de señal y la configuración del indicador, puede que encuentre un asesor experto más rentable que el que he descrito en el artículo.

Gráfico del asesor experto 

Figura 10. Asesor experto de la transformada inversa de Fisher

Resultado del asesor experto

Figura 11. Gráfico de saldo del asesor experto de la transformada inversa de Fisher


Conclusión

Espero que el artículo haya proporcionado una buena introducción a la transformada de Fisher y a la transformada inversa de Fisher, y que haya mostrado una forma de construir un módulo de trading de señal basado en un indicador personalizado.

He usado el indicador de Fisher inverso RSI ajustado de Sylvain Vervoort, pero de hecho puede aplicar fácilmente la transformada inversa de Fisher a cualquier oscilador y construir un asesor experto basado en este artículo.

También animo a los lectores a que modifiquen los ajustes para conseguir asesores expertos rentables en base al que he descrito. Proporciono enlaces externos para posteriores consultas a continuación.


Bibliografía

  1. The Fisher Transform
  2. Using the Fisher Transform
  3. The Inverse Fisher Transform
  4. Smoothed RSI Inverse Fisher Transform

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/303

Archivos adjuntos |
Cómo desarrollar un asesor experto usando las herramientas de UML Cómo desarrollar un asesor experto usando las herramientas de UML
Este artículo trata sobre la creación de asesores expertos utilizando el lenguaje gráfico UML, usado para el modelado visual de sistemas de software orientados a objetos. La principal ventaja de este enfoque es la visualización del proceso de modelado. El artículo contiene un ejemplo que muestra el modelado de la estructura y propiedades de un asesor experto usando el modelador de ideas de software.
Usar WinInet en MQL5. Parte 2: solicitudes y archivos POST Usar WinInet en MQL5. Parte 2: solicitudes y archivos POST
En este artículo seguimos estudiando los principios del trabajo con internet usando solicitudes HTTP e intercambiando información con el servidor. El artículo describe nuevas funciones de la clase CMqlNet, métodos para enviar información desde formularios y envío de archivos usando solicitudes POST así como autorización en sitios web bajo un registro de usuario usando cookies.
Cómo añadir nuevos idiomas de interfaz de usuario a la plataforma de Meta Trader 5 Cómo añadir nuevos idiomas de interfaz de usuario a la plataforma de Meta Trader 5
La interfaz de usuario de la plataforma de Meta Trader 5 está traducida a varios idiomas. No se preocupe si su idioma no se encuentra entre los soportados. Puede fácilmente completar la traducción usando la utilidad especial del paquete multilenguaje de Meta Trader 5, puesta a disposición de todos los usuarios de manera gratuita por MetaQuotes Software Corp. En este artículo mostraremos algunos ejemplos sobre cómo añadir nuevos idiomas de interfaz de usuario a la plataforma de Meta Trader 5.
Documentación generada automáticamente para el código de MQL5 Documentación generada automáticamente para el código de MQL5
La mayoría de desarrolladores de código Java estarán familiarizados con la documentación generada automáticamente que puede obtenerse con JavaDocs. La idea es añadir comentarios al código de forma semiestructurada para que pueda ser extraída en forma de una archivo de ayuda por el que sea fácil navegar. El mundo de C++ tiene también una serie de autogeneradores de documentación, siendo líderes SandCastle y Doxygen, ambos de Microsoft. El artículo describe el uso de Doxygen para crear archivos de ayuda de html a partir de comentarios estructurados en código MQL5. El experimento funcionó muy bien y creo que la documentación de ayuda que ofrece Doxygen a partir del código MQL5 añadirá una gran cantidad de valor a este.