English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MetaTrader 5 시장 분석에 피셔 변환(Fisher Transform)과 인버스 피셔 변환 적용하기

MetaTrader 5 시장 분석에 피셔 변환(Fisher Transform)과 인버스 피셔 변환 적용하기

MetaTrader 5트레이딩 | 11 10월 2021, 15:46
208 0
investeo
investeo

들어가며

본 문서에서는에서는 금융 시장에 적용되는 피셔 변환과 인버스 피셔 변환에 대하여 소개합니다.

피셔 변환 이론은 "Stocks and Commodities" 잡지 2010년 10월호에 제시된 평활 RSI 피셔 변환 인디케이터의 MQL5 버전을 구현하여 구현되었습니다. 인디케이터 수익성은 피셔 인디케이터 기반 신호를 사용하는 Expert Advisor에 의해 역시험되었습니다.

이 문서는 인터넷에서 발견된 문서들과 J.F.Ehlers의 저서들에 기반두었습니다. 모든 레퍼런스는 이 문서 끝에 있습니다.


1. 가우시안 PDF vs 마켓 사이클

기본적인 전제는 가격이 표준 밀도 함수를 따른다는 것입니다.

이는 평균으로부터의 가격 편차를 잘 알려진 가우스 벨을 이용해 설명할 수 있음을 의미합니다.

1번 그림. 가우스 분포 

1번 그림. 가우스 벨 

제가 표준 확률 밀도 함수에 대해 논했던 바가 있습니다. 표준 확률 밀도 함수에 대해 완전히 이해하실 수 있도록 여러 가지 아이디어와 수학 공식을 소개할 것인데요, 독자 대부분이 이해하실 수 있었으면 좋겠습니다.

메리암-웹스터 사전에서 확률이란 다음과 같이 정의되어있습니다.

  1. 가능한 결과의 총 개수에 대한 특정 사건을 발생시킬 가능성이 동일한 결과의 전체 집합의 결과 개수의 비율
  2. 정해진 사건이 발생할 가능성.

랜덤 변수란 랜덤 처리 과정을 거쳐서 값을 받아오는 변수를 뜻합니다. 우리의 경우 랜덤 변수는 자산의 가격입니다. 

마지막으로, PDF란 확률 밀도 함수 (Probability Density Function - 랜덤 변수 X (다시 말하지만 우리 경우엔 가격입니다)가 특정 구간 내의 가능한 값 중 하나일 확률입니다. 가우스 분포 혹은 정규 분포에서 기인한 랜덤 변수는 하나의 평균 값을 중심으로한 클러스터 내의 현실 랜덤 변수를 설명하는데에 자주 사용됩니다.

수학적인 관점에서 이는 랜덤 변수 X란 [a,b] 인터벌 내에서 존재할 확률이 인테그랄로 존재할 확률의 값이라고 할 수 있습니다.

2번 그림. 확률 밀도 인테그랄

이는 곡선 f(x)의 a에서 b까지의 구간 밑 영역을 나타냅니다. 확률은 0~100% 또는 0~1.00으로 계산됩니다. 따라서 f(x) 곡선 아래의 총 면적이 1(확률의 합)이어야 합니다.

3번 그림. 곡선 밑 총 영역

이제 1번 그림의 하단을 들여다 봅시다.

4번 그림. 가우스 값 하단 

2번 그림. 가우스 벨 표준 편차  

여기서 평균 +/- 1-3 표준 편차(시그마) 아래에 있는 값의 백분율을 확인할 수 있습니다. 가우스 PDF에서는 평균에서 +/- 1 표준 편차 내에 있을 확률이 68.27%, +/- 2 표준 편차 내에 있을 확률이 95.45%, 평균에서 +/- 3 표준 편차 내에 있을 확률은 99.73%입니다.

실제 시장 자료에도 적용될 수 있을 것이라고 생각하십니까? 시장 가격을 살펴보면 저항 또는 지원 수준을 깨고 대규모 주문이 다음 지원/저항 수준으로 상승하거나 하락하는 경향이 있는 차트를 보면 사각파처럼 보일 수 있습니다. 그렇기 때문에 커다란 근사를 취하면 시장이 사각파 또는 사인파로 모델링될 수 있습니다.

밑의 사인파 그림을 보아주십시오.

사인

3번 그림. 사인 그림 

실제로는 대부분의 매매가 지지 및 저항 수준에 비슷하게 배치되므로 매우 자연스럽게 보인다는 점을 느끼실 것입니다. 이제 사인파의 밀도를 그려보겠습니다. 3번 그림을 오른쪽으로 90도 돌려 그림을 구성하는 모든 원이 바닥에 떨어지도록 한다고 상상할 수 있습니다. 

밀도 

4번 그림. 사인 곡선 밀도 그림  

가장 왼쪽 및 가장 오른쪽 위치에서 밀도가 가장 높은 것을 알 수 있습니다. 이는 대부분의 거래가 저항 및 지원 수준에 매우 가깝게 이루어졌다는 이전 이야기와 일치하는 것으로 볼 수 있습니다. 히스토그램을 그려 발생 비율을 확인합니다.

히스토그램

5번 그림. 사인 곡선 밀도 히스토그램

이것이 가우스 벨처럼 보이십니까? 딱히 닮진 않았죠 처음과 마지막 세 개의 막대가 가장 많이 발생한 것으로 보입니다.

J.F. Ehlers는 자신의 저서 "Сybernetic analysis for stocks and futures"에서 미국 T-채권을 15년 동안 연구한 실험에 대해 논한 바 있습니다. Ehler는 10막대 길이의 정규화 채널을 적용해 100 bin 내의 매매가 위치를 측정하고 각 bin에 해당 매매가가 실제로 들어간 횟수를 세었습니다. 이 확률 분포의 결과는 사인파에 유사합니다.  


2. 피셔 변환과 타임시리즈에의 활용

이제 시장 주기의 PDF가 가우스파가 아니라 사인파의 PDF와 비슷하다는 것을 알게 되었지만, 현실에서는 대부분의 인디케이터가 시장 주기 PDF는 가우스 파처럼 행동할 것이라 가정하기 때문에 이를 "수정"할 방법이 필요합니다. 해법은 피셔 변환을 사용하는 것입니다. 피셔 변환은 어떠한 형태의 PDF건 가우스 파 형태로 변환합니다.

피셔 변환을 위한 방정식은:

6번 그림. 피셔 변환 방정식

 5번 그림. 피셔 변환

6번 그림. 피셔 변환  

피셔 변환의 결과는 개략적으로 가우스 PDF와 닮았다고 말씀드린 바 있습니다. 이를 설명하기 위해선 6번 그림을 보아주시면 됩니다.

입력 데이터가 평균값 근처라면 이익량은 거의 같습니다 (차트에서 |X<0.5|를 확인하여주십시오). 반면에 정규화된 입력 데이터가 어느쪽으로건 한계 쪽에 접근하면 결과물이이 크게 증폭됩니다(차트에서 0.5<|x|<1을 확인하여 주십시오). 실전 상황에서는 '거의 가우스스러운' 꼬리를 기르는 방법을 떠올리실 수도 있습니다. 변환된 PDF는 실제로 이 방법을 택합니다. 

그럼 어떻게 피셔 변환을 매매에 적용시킬까요? 먼저 |x|<1 제약 때문에 매매가는 모두 이 범위 안으로 정규화되어야합니다. 정규화된 매매가가 피셔 변환의 영향을 받는 경우 극단적인 가격 이동은 상대적으로 드물게 발생합니다. 이는 피셔 변환이 그러한 극단적인 가격 움직임을 포착하고 그러한 극단값에 따라 매매할 수 있게 해준다는 것을 의미합니다.


3. MQL5에서의 피셔 변환

피셔 변환 인디케이터의 소스 코드는 Ehlers의 저서"Cybernetic Analysis for Stocks and Futures"에서 찾아볼 수 있습니다..

이미 MQL4로 구현되어 있었으며, 제가 MQL5로 포팅했습니다. 이 인디케이터는 중간 가격 (H+L)/2를 사용하고, 저는 iMA() 함수를 이용하여 과거 중간 가격을 추출했습니다.

처음에 가격은 10bar 범위 내에서 정규화되며 정규화된 가격은 피셔 변환을 따릅니다.

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

날카로운 시그널이 생성되는 것을 주목해줏비시오.

신호선은 피셔 변환 가격을 1 바 만큼 늦춘것이나 다름 없습니다.

피셔 변환 인디케이터 

7번 그림. 피셔 변환 인디케이터  

 

4. 역 피셔 변환과 사이클 인디케이터 적용

역 피셔 변환 방정식은 피셔 변환 방정식을 x 대신 y로 풀면 만들어집니다.

8번 그림. 역 피셔 변환 방정식,

6번 그림. 역 피셔 변환 

8번 그림. 역 피셔 변환 

이 함수의 전달 반응은 피셔 변환의 반응과 반대입니다.

|x|>2인 경우에 입력 데이터는 통일성(음수의 경우 -1, 양수의 경우 +1)을 초과하지 않도록 압축되며, |x|<1의 경우 거의 선형 관계인데, 이는 결과값이 입력값과 거의 같은 성질을 가진다는 의미입니다.

그 결과 적절하게 준비된 입력 데이터에 역 피셔 변환을 적용하면 출력이 -1 또는 +1이 될 가능성이 큽니다. 따라서 역 피셔 변환을 오실레이터 인디케이터에 적용하기에 완벽합니다. 역 피셔 변환은 날카로운 매수나 매도 시그널을 입력하는걸로 더 개선시킬 수 있습니다. 

 

5. MQL5에서의 역 피셔 변환 예시

역 피셔 변환을 검증하기 위하여 저는 "Stocks and Commodities" 2010년 10월호에서Sylvain이 선보인 Vervoort 평활 RSI 역 피셔 변환 인디케이터를 MQL5 버전으로 작성하여 매매 시그널 모듈을 만들고 해당 인디케이터를 기반으로 Expert Advisor를 만들어 보았습니다.

역 피셔 변환 인디케이터는 이미 다양한 매매 플랫폼에 구현되어있고, 소스 코드는 traders.com 홈페이지와 MQL5.com 코드 베이스에서 찾아볼 수 있습니다.

MQL5에는 iRSIOnArray 함수가 없기때문에 제가 직접 인디케이터 코드에 추가했습니다. 원래 인디케이터와의 유일한 차이점은 기본 RSPeriod가 21로 설정되고 EMAPeriod가 내 설정(EURD 1H)에 더 잘 작동했기 때문에 34로 설정된 것입니다. 기본 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;             // 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);
  }
//+------------------------------------------------------------------+

 역 피셔 인디케이터

9번 그림. 역 피셔 변환 인디케이터  

제가 여태 변환 방정식만 제시했으므로 피셔 변환과 역 피셔 변환 자체에 대해서는 혼란스러우실 수 있습니다.

제가 이 문서를 작성하기 위해 자료를 모을 적에 어떻게 피셔가 두 변환을 얻었는지 찾아보았지만 딱히 나오는 것이 없었습니다.

그러나 제가 피셔 변환과 역 피셔 변환을 보았을 때, 두 그래프는 삼각함수와 포물선 함수를 연상케 했습니다 (좀 비슷해보이지 않습니까?). 이 함수들 자체가 오일러 방정식에서 만들어질 수 있고 오일러 수 'e'로 표현될 수 있으므로 저는 미적학 책을 다시펴고 다시 확인해 보았습니다. checked that:

9번 그림. Sinh 방정식,

11번 그림. Cosh 방정식,

이제 tanh(x)는 이하를 통해 얻을 수 있는데

12번 그림. Tanh 방정식,

그리고... 

12번 그림. Atanh 방정식 

위에 보여드렸던 것과 완전히 같습니다. 피셔 변환의 미스테리가 풀렸군요! 피셔 변환은 그냥 arctanh(x)이고 역 피셔 변환은 그의 역함수인 tanh(x)이었습니다!


6. 매매 시그널 모듈

역 피셔 변환 I를 검증하기 위해 역 피셔 변환 인디케이터 기반으로 매매 시그널 모듈을 작성합니다.

커스텀 인디케이터에 기반 둔 매매 모듈이 더 쓸만하게 느껴지실 수도 있습니다. 저는 CiCustom 클래스 인스턴스를 이용하여 역 피셔 인디케이터와 오버로드시킨 CExpertSignal함수의 4개 가상 메소드를 보관하는데에 사용했습니다. CheckOpenLong()CheckOpenShort()를 통해 오픈 포지션이 없을 때에 시그널을 생성하였고 CheckReverseLong()CheckReverseShort()를 사용하여 오픈 포지션을 역행시켰습니다. 

//+------------------------------------------------------------------+
//|                               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:  indicators -pointer of indicator collection.             |
//| 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:  indicators -pointer of indicator collection.             |
//| 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:  price - refernce for price.                              |
//| 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. Expert Advisor

역 피셔 변환 I를 검증하기 위해 앞에서 설명한 매매 시그널 모듈을 사용하는 표준 EA를 작성합니다.

또한 "MQL5 Wizard: How to Create a Module of Trailing of Open Positions"문서에서 받아온 추종 손절매 모듈을 추가하였습니다.

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

이 EA가 모든 자산과 모든 타임프레임을 대상에서 수익성이 좋지는 않았다는 것은 인정합니다만, 저는 이를 EURUSD 1H 타임프레임에 좋은 결과를 주기 위해 조정했습니다.

저는 독자들이 시그널 모듈과 인디케이터 설정을 바꿔보시도록 권합니다. 기사에 제시된 것보다 더 수익성이 높은 EA를 찾을 수도 있습니다.

EA 그래프 

10번 그림. 역 피셔 변환 EA

EA 결과

11번 그림. 역 피셔 변환 EA 밸런스 그래프


마치며

본 문서에서 피셔 변환과 역 피셔 변환을 잘 소개하고 맞춤형 인디케이터에 기반한 신호 거래 모듈을 구축하는 방법에 대해 느끼셨으면 좋겠습니다.

저는 Sylvain의 Vervoort Smoothed RSI Inverse Fisher Transform 인디케이터를 사용했습니다만, 굳이 그걸 쓰지 않으셔도 실제로 역 피셔 변환을 아무 오실레이터에나 쉽게 적용하여 이 문서에서 다룬 EA를 만드실 수 있습니다.

또한 제가 제시한 EA를 바탕으로 독자들이 설정을 조정하여 수익성 있는 EA를 만들어보시는 것을 권장합니다. 아래 참조를 위해 외부 링크를 제공합니다.


참조

  1. 피셔 변환
  2. 피셔 변환의 활용
  3. 역 피셔 변환
  4. 평활 RSI 역 피셔 변환

MetaQuotes 소프트웨어 사를 통해 영어가 번역됨
원본 기고글: https://www.mql5.com/en/articles/303

UML 툴을 사용하여 Expert Advisor 개발하기 UML 툴을 사용하여 Expert Advisor 개발하기
이 문서는 객체지향 소프트웨어 시스템의 시각 모델링에 사용되는 UML 그래픽 언어를 사용하여 Expert Advisor를 개발하는 방법에 대해 다뤄볼 것입니다. 이 접근의 장점은 모델링 프로세스를 시각화할 수 있다는 점입니다.. 이 문서에서는 소프트웨어 아이디어 모델러(Software Ideas Modeler)를 통해 Expert Advisor의 구조와 속성을 모델링하는 예시를 보여드릴 것입니다.
결제 및 결제 수단 결제 및 결제 수단
MQL5.커뮤니티 서비스는 트레이더들 뿐만 아니라 MetaTrader 터미널용 어플리케이션을 개발하는 개발자들에게도 최고의 기회를 제공합니다. 이 기사에서는 MQL5 서비스에 대한 결제가 어떻게 수행되는지, 번 돈이 어떻게 인출될 수 있는지, 운영 보안이 어떻게 보장되는지 설명합니다.
MetaTrader 5 플랫폼에 새로운 UI 언어 추가하기 MetaTrader 5 플랫폼에 새로운 UI 언어 추가하기
MetaTrader 5 플랫폼의 사용자 인터페이스는 여러 언어로 번역되었습니다. 만약 당신이 사용하는 언어로는 번역되지 않았더라도 실망하기엔 이릅니다. MetaQuotes Software Corp.이 제공하는 MetaTrader 5 멀티랭귀지 팩 유틸리티를 이용하여 쉽게 번역할 수 있기 때문이죠. 심지어 공짜입니다. 본 문서에서는 MetaTrader 5 플랫폼에 새로운 사용자 인터페이스 언어를 추가하는 예시에 대해서 보여드릴 것입니다.
MQL5에서 WinInet 사용하기 파트 2: POST 리퀘스트 및 파일 MQL5에서 WinInet 사용하기 파트 2: POST 리퀘스트 및 파일
본 문서에서는 우리는 인터넷을 HTTP 리퀘스트를 다루는 법에 대하여 계속하여 알아보고 실제 서버와 정보 교환을 해볼 것입니다. CMqlNet 클래스 내의 신규 함수를 다뤄보고 POST 리퀘스트를 통해 정보를 보내는 메소드, 그리고 Cookies를 이용해 홈페이지 로그인 인증을 처리하는 것을 다뤄봅니다.