[MQL4] Realized Volatility

Realized Volatility (実現ボラティリティ、以下RV) についての詳細は下記リンクをご参照ください。

<a href="http://hermes-ir.lib.hit-u.ac.jp/rs/bitstream/10086/20318/1/keizaikenkyu05804352.pdf">Realized Volatility - サーベイと日本の株式市場への応用</a>
<a href="http://www.imes.boj.or.jp/research/papers/japanese/07-J-14.pdf">高頻度データによるボラティリティの推定</a>

数年前に国内でもボラティリティ研究家の間でホットなトピックだったらしく、探せば日本語でいろいろ読めます。
それらよりRVの特徴をざっくり列挙すると、

・例えば日次なら日中の5分足や1分足等、より高頻度な価格データの対数化リターンの2乗値を足し合わせたもの
・単純に積み上げるだけなのでモデルは存在せず、従ってパラメーターも存在しない
・理論上の真のボラティリティ (瞬間的なボラティリティを積分したIntegrated Volatility、現実には観測不可能) の高精度な推定量となる
・対数化RVまたはRVにより基準化された日次リターンは正規分布に従う (かもしれない)
・長期記憶性自己相関を持つ (かもしれない)
・データの解像度を上げ過ぎるとノイズの影響が大きくなり過ぎるかも

なかなか夢のあるプロフィールですが、
さすがに手持ちの為替データでの検定では、正規分布に従うかどうかはかなり怪しいです。
やはり裾が厚過ぎますし、中心付近の密度も高過ぎます。
が、適切に外れ値を考慮すれば大部分のデータは・・・いや、でもそれを言い出したら・・・。
直近約1年の日経やダウの先物でJB検定等行った場合には正規性が棄却されませんでした。
もっと長い期間で見てみたいのですがそれ以前のインデックスの分足データが手元に無く。

また、自己相関はとくに対数化RV系列ではたしかに有意かつ比較的きれいな減衰が見られることがありますが、
こちらは対数化により非定常な単位根過程となっている場合が多いので取り扱い注意です。

私としては、

・計算がとても簡単
・無理筋な仮定や恣意的な要素がないので解釈や二次利用が楽
・値幅だけでなく値動きの活発さも含まれている

といった点が好きです。
最後の点については、だったらVolume値も計算に利用したらいいんじゃないかとも思いますが、
信頼できる1分足価格データの収集だけでも困難なのにVolume値まではちょっと・・・ということで。

パラメーターの説明です。

HiFreq: 高頻度データとして利用するタイムフレームを直接数値で入力 ※1
PriceMode: リターンとして用いる価格算出方法の指定 ※2
CalcMode: logRVは対数化RV、Standardizedは挿入したチャートの各足の対数化リターンを同じ足のRVの平方根で割って基準化したリターン

※1 不正な数値に対するエラー処理は実装していません。
デタラメな数値を入れた場合、おそらく何も表示されないでしょう。
※2 PriceModeのVolumeはあまり意味の無い機能です。
実はこのインジケーターを作成していたターミナルでヒストリカルデータの管理に失敗していて、
その不整合の確認に役立ったので残してあります。
マシン語のチェックサムみたいな・・・。

```mql4:MQL4

//+------------------------------------------------------------------+
//| RealizedVolatility.mq4                                           |
//|                                         http://qiita.com/LitopsQ |
//+------------------------------------------------------------------+
#property strict
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrTurquoise
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

extern int HiFreq = 1;
extern int PriceMode = 0; // 0_Close, 1_Close-Open, 2_High-Low, 3_Volume
extern int CalcMode = 1; // 0_RV, 1_logRV, 2_Standardized

//--- indicator buffers
double RVBuf[];
double CalcBuf[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){
   if(PriceMode > 3 || CalcMode > 2) return(INIT_PARAMETERS_INCORRECT);

//--- indicator Buffers mapping
   IndicatorBuffers(2);
   SetIndexBuffer(0, RVBuf);
   SetIndexBuffer(1, CalcBuf);
   
//---
   if(PriceMode == 3) CalcMode = 1;
   IndicatorShortName("RV " + (string)HiFreq + "/" + (string)PriceMode + "/" + (string)CalcMode);

//---
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 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 limit = rates_total - prev_calculated;
   int i;
   int HiFreqStart;
   int HiFreqEnd;
   int h;
   static int LastHFBar = 0;
         
   if(prev_calculated == 0 || CalcBuf[1] == EMPTY_VALUE){
      limit = iBarShift(NULL, 0, iTime(NULL, HiFreq, iBars(NULL, HiFreq) - 1));
      LastHFBar = 0;
   }
   
   if(iBars(NULL, HiFreq) != LastHFBar){
      for(i = limit; i >= 0; i--){
         CalcBuf[i] = 0;
         HiFreqStart = iBarShift(NULL, HiFreq, Time[i]);
         if(LastHFBar == 0 && PriceMode == 0) HiFreqStart--;
         if(i == 0) HiFreqEnd = 0;
         else HiFreqEnd = iBarShift(NULL, HiFreq, Time[i - 1]) + 1;
                  
         for(h = HiFreqStart; h >= HiFreqEnd; h--){
            switch(PriceMode){
               case 0: CalcBuf[i] += pow(log(iClose(NULL, HiFreq, h) / iClose(NULL, HiFreq, h + 1)) * 100, 2); break;
               case 1: CalcBuf[i] += pow(log(iClose(NULL, HiFreq, h) / iOpen(NULL, HiFreq, h)) * 100, 2);      break;
               case 2: CalcBuf[i] += pow(log(iHigh(NULL, HiFreq, h) / iLow(NULL, HiFreq, h)) * 100, 2);        break;
               case 3: CalcBuf[i] += (double)iVolume(NULL, HiFreq, h);                                         break;
            }
         }
         
         switch(CalcMode){
            case 0:                     RVBuf[i] = CalcBuf[i];                                            break;
            case 1: if(CalcBuf[i] == 0) RVBuf[i] = EMPTY_VALUE;
                                   else RVBuf[i] = log(CalcBuf[i]);                                       break;
            case 2: if(PriceMode == 0)  RVBuf[i] = log(Close[i] / Close[i + 1]) * 100 / sqrt(CalcBuf[i]);
                                   else RVBuf[i] = log(Close[i] / Open[i]) * 100 / sqrt(CalcBuf[i]);      break;
         }
      }
      LastHFBar = iBars(NULL, HiFreq);
   }
   
//--- return value of prev_calculated for next call
   return(rates_total);
}

//+------------------------------------------------------------------+

```

もっと効率的な書き方があるんじゃないかと思います・・・。

挿入時、および高頻度チャートの足が増えた時のみ計算を実行します。
挿入したチャートと高頻度チャートとのデータの不整合には注意してください。
例えば、高頻度チャートのデータに、他の期間に比べ大量に欠落している期間があると極端に低い値が出ます。

紹介した論文には週明けの窓のようなギャップの取り扱いについても考察があるのですが、
そこはスルーしています。
PriceModeがCloseの場合に影響します。。。

これをいじっている間、分布の正規性の可能性ということで<a href="http://expertadviser-bighope.blogspot.jp/2014/06/blog-post.html">bighopeさんのこちら</a>を何度も思い出しました。
何か関連をうまく説明できると面白いんじゃないかと思ったりもしたんですが・・・思っただけです。