Aplicar la transformada de Fisher y su transformada inversa al análisis de mercado en Meta Trader 5
investeo | 8 mayo, 2014
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. 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
- 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
- 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:
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):
Vamos ahora a volver a la parte inferior de la Figura 1:
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:
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:
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:
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. 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:
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. 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); } //+------------------------------------------------------------------+
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:
,
,
y como sabemos que la tanh(x) puede obtenerse por:
,
y ...
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.
Figura 10. Asesor experto de la transformada inversa de Fisher
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
- The Fisher Transform
- Using the Fisher Transform
- The Inverse Fisher Transform
- Smoothed RSI Inverse Fisher Transform