[MQL4] MT4でSharpe Ratio

※ MT5方式の方、間違いに気付いたので修正しました・・・。 (2015-02-05 15:40)

最近のMQL4にはOnTester()関数やTesterStatistics()関数が実装されていて、とても重宝しています。

リファレンスにはTesterStatistics()でシャープレシオ等も取得できるかのように書かれていますが、現状ではこれら従来のテスター出力に含まれていない指標が機能していないのは<a href="http://forex.toyolab.com/?p=355">こちらの記事</a>にもある通りです。
いくらなんでも中途半端なので、将来きちんと実装されると思いますが、その場合のシャープレシオはおそらく<a href="http://system-trading.jp/toyoshima/index.php?ID=136">MT5方式</a>のものになる可能性が高いでしょう。

そこで一般的な月次の収益率を元にしたシャープレシオをバックテストまたは最適化時に計算する関数を作ってみました。

先に、収益率の計算に初期資金額が必要になりますのでOnInit()内で取得してグローバル変数に格納しておきます。
終わったらOnDeinit()内で削除します。

```mql4:MQL4

int OnInit(){
   if(IsTesting() || IsOptimization()) GlobalVariableSet("InitialBalance", AccountBalance());
   
   return(0);
}

void OnDeinit(const int reason){
   if(IsTesting() || IsOptimization()) GlobalVariableDel("InitialBalance");
}

```

で、こちらがシャープレシオ算出関数です。
EAのソースに貼り付けるなり、ライブラリに書いてインクルードするなりして使います。

```mql4:MQL4

double SharpeRatio(double Balance){   
   int i;
   int CalcMonth = 0;
   int TradeMonths = 0;
   double MonthlyProfit[];

   for(i = 0; i < OrdersHistoryTotal(); i++){
      if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) == false) break;
            
      int CloseMonth = TimeMonth(OrderCloseTime());
      if(CalcMonth != CloseMonth){
         CalcMonth = CloseMonth;
         TradeMonths++;
         ArrayResize(MonthlyProfit, TradeMonths);
      }
      MonthlyProfit[TradeMonths - 1] += OrderProfit();
   }
   
   double MonthlyEarningRate[];   
   ArrayResize(MonthlyEarningRate, ArraySize(MonthlyProfit));
   double SumMER = 0;
   
   for(i = 0; i < ArraySize(MonthlyProfit); i++){
      MonthlyEarningRate[i] = MonthlyProfit[i] / Balance;
      SumMER += MonthlyEarningRate[i];
      Balance += MonthlyProfit[i];
   }

   double MER_Average = SumMER / TradeMonths;
   double MER_SD = iStdDevOnArray(MonthlyEarningRate, 0, TradeMonths, 0, 0, 0);
   double SR = 1;
   if(MER_SD != 0) SR = MER_Average / MER_SD; // ゼロ割を回避
   
   return SR;
}

```

テスト期間が1ヶ月以下で標準偏差がゼロになってしまうとゼロ割でエラーになるので、その場合の戻り値を 1 にしています。
なぜ 1 なのかというと個人的な都合ですので、気に入らなければ変更してください。
また、開始日や終了日が月途中でトレード数が少ない場合のことは考慮していませんのでご注意を。

ついでにMT5方式でトレードごとの収益率から計算するものも作りました。

```mql4:MQL4

double SharpeRatio_MT5(double Balance){   
   int TotalTrades = OrdersHistoryTotal();
   int Trades = 0;
   double SumER = 0;

   double EarningRate[];
   ArrayResize(EarningRate, TotalTrades);

   for(int i = TotalTrades - 1; i >= 0; i--){
      if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) == false) break;

      double Profit = OrderProfit();

      EarningRate[Trades] = Profit / Balance;
      SumER += EarningRate[Trades];
      Balance += Profit;
      Trades++;
   }
   
   double ER_Average = SumER / TotalTrades;
   double ER_SD = iStdDevOnArray(EarningRate, 0, TotalTrades, 0, 0, 0);
   double SR_MT5 = 0;
   if(ER_SD != 0) SR_MT5 = ER_Average / ER_SD;
   
   return SR_MT5;
}

```

「MT5方式」とは言ったものの、これで本当にMT5のものと計算結果が一致するかどうかは未確認です。
だってMT4とMT5で同じヒストリカルデータを食わせることができないんですもの・・・。
スプレッドの扱いも違うし。
なんとなくそれっぽい値になることは確認しています。
こちらはゼロ割回避の戻り値が 0 になっていますがこれもお好みでどうぞ。

これらをOnTester()内で呼んでやります。
引数は先程の初期資金額です。
下記の例では標準的な方のシャープレシオがテスターに返ります。

```mql4:MQL4

double OnTester(){
   double SR = SharpeRatio(GlobalVariableGet("InitialBalance"));
   Print("SharpeRatio = " + (string)SR);
   
   double SR5 = SharpeRatio_MT5(GlobalVariableGet("InitialBalance"));
   Print("SharpeRatio_MT5 = " + (string)SR5);

   return SR;
}

```

OnTester()でテスターに返せる変数は1つだけなので、私は最適化時に他にもいろいろ計算させた結果をOnTester()実行ごとにcsvファイルに書き込んで利用しています。

公開にあたりできるだけ現行のMQL4に準拠した書き方、strictモードでも警告が出ないような書き方を心がけたつもりですが、普段はそれほど意識していませんのであしからず・・・。