7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【MQL言語】初心者から自走できるトレーダーになるまで、バックテストを取りシグナルツールを作るまでのロードマップ

Last updated at Posted at 2021-02-08

MQL言語の勉強はできても、「じゃあどうやって実装するのか。何をどうしたら自分の手法の勝率が知れるのか。」

勉強し始めにむずむずしてたことをふと文章にしてみようと思ってこの記事の執筆に至った。

概要

様々ある投機の種類の中に、バイナリーオプションというものがある。似て非なるFXとの違いを簡潔に述べると、

  • FX:買い注文と売り注文の時間的自由がある。値幅がそのまま損益になり、レバレッジを利かせてトレードする
  • BO:エントリーする通貨のレートが、短期間後に自分の予測した方向にいくかどうかを予測して、当たるとベット額の1.85~2.30倍、外れると0倍になって返ってくる

と、このような違いがある。ここ数年で、バイナリー業界にもプログラミングが必要という流れが根付いてきたのか、「MQL 独学」、「バックテスト 取り方」のような検索結果が増えてきたように思える。ただ、このような記事を調べる人たちの目的は、バックテストを取ることではなく、お金を稼ぐことの様に思える。そのような方向けに、初心者から自走できるトレーダーになるまでの簡単なロードマップを組んでみたのがこの記事の内容である。

##流れ

今回はバイナリーオプション限定で

  1. 過去相場のヒストリカルデータを取得
  2. テストを取る手法を言語化及び数値化
  3. コーディングしてバックテストを取得
  4. 取得したデータをもとにコードを改善する
  5. シグナルツールとしてパッケージングする

このような流れで進めていこうと思う。

(*)なお、今回のバックテスト取得の流れは完全に無料で行えるものだが、ヒストリカルデータに関しては有料のものを使用しないとデータの欠損があることが分かっているほか、バックテストの方法もだいぶ原始的なため、本格的に分析を始める場合はMTFA等検索をかけてみることをお勧めする。

(**)今回の記事の主題は流れや「ルール改善 → シグナルツール化」に重点を置くため、記事内容を一部省略し、参考になるURLを貼り付けることにする。

(***)また、本記事は投資行為や商品購入促すものではなく、あくまでもバックテストを取る技術とテクニカル分析をする第一歩の流れをかいつまんで説明するものである。

##準備
今回のロードマップを達成するのに必要なものは以下。

  • Windows OS搭載のPC(Mac OSでも可能だが文字化けするため初心者はキツい)
  • MT4

以上。
MT4はXMトレーディングがわかりやすくかつ無料なので、調べてダウンロードしてあること。MT5ではなくMT4(ミス多発)

1. 過去相場のヒストリカルデータを取得

こちらのリンク(https://www.fxddtrading.com/bm/jp/resources/mt4-one-minute-data) にアクセスして、取得したい通貨ペアを選択。

本来はバイナリーオプション採用通貨の全18通貨ペアを取得すべきだが、今回は例としてUSD/JPYを選択(ドルと日本円の相関通貨)

その後の前準備はこちらを参照(https://fx-wintrade.com/backtest/ )

2. テストを取る手法の言語化及び数値化

ココが一番楽しく悩ましいところである。バイナリーオプションは5分取引を選択する場合、損益分立が54.05%なので、最低でも55%の勝率が担保されている手法でないと勝ち続けることは難しい。

ここでは例として、初期MT4に搭載されているインジケーターRSIについて考察していく。

これも含め、今から紹介するインジケーターだけで勝てるようになることは99%ありえないので、安心して心を落ち着けて見ていてほしい。

RSI(Relative strength Index)
rsi.mq4

/+------------------------------------------------------------------+
//|                                                          RSI.mq4 |
//|                   Copyright 2005-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+
</summary>
#property copyright   "2005-2014, MetaQuotes Software Corp."
#property link        "http://www.mql4.com"
#property description "Relative Strength Index"
#property strict

#property indicator_separate_window
#property indicator_minimum    0
#property indicator_maximum    100
#property indicator_buffers    1
#property indicator_color1     DodgerBlue
#property indicator_level1     30.0
#property indicator_level2     70.0
#property indicator_levelcolor clrSilver
#property indicator_levelstyle STYLE_DOT
//--- input parameters
input int InpRSIPeriod=14; // RSI Period
//--- buffers
double ExtRSIBuffer[];
double ExtPosBuffer[];
double ExtNegBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   string short_name;
//--- 2 additional buffers are used for counting.
   IndicatorBuffers(3);
   SetIndexBuffer(1,ExtPosBuffer);
   SetIndexBuffer(2,ExtNegBuffer);
//--- indicator line
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtRSIBuffer);
//--- name for DataWindow and indicator subwindow label
   short_name="RSI("+string(InpRSIPeriod)+")";
   IndicatorShortName(short_name);
   SetIndexLabel(0,short_name);
//--- check for input
   if(InpRSIPeriod<2)
     {
      Print("Incorrect value for input variable InpRSIPeriod = ",InpRSIPeriod);
      return(INIT_FAILED);
     }
//---
   SetIndexDrawBegin(0,InpRSIPeriod);
//--- initialization done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Relative Strength Index                                          |
//+------------------------------------------------------------------+
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    i,pos;
   double diff;
//---
   if(Bars<=InpRSIPeriod || InpRSIPeriod<2)
      return(0);
//--- counting from 0 to rates_total
   ArraySetAsSeries(ExtRSIBuffer,false);
   ArraySetAsSeries(ExtPosBuffer,false);
   ArraySetAsSeries(ExtNegBuffer,false);
   ArraySetAsSeries(close,false);
//--- preliminary calculations
   pos=prev_calculated-1;
   if(pos<=InpRSIPeriod)
     {
      //--- first RSIPeriod values of the indicator are not calculated
      ExtRSIBuffer[0]=0.0;
      ExtPosBuffer[0]=0.0;
      ExtNegBuffer[0]=0.0;
      double sump=0.0;
      double sumn=0.0;
      for(i=1; i<=InpRSIPeriod; i++)
        {
         ExtRSIBuffer[i]=0.0;
         ExtPosBuffer[i]=0.0;
         ExtNegBuffer[i]=0.0;
         diff=close[i]-close[i-1];
         if(diff>0)
            sump+=diff;
         else
            sumn-=diff;
        }
      //--- calculate first visible value
      ExtPosBuffer[InpRSIPeriod]=sump/InpRSIPeriod;
      ExtNegBuffer[InpRSIPeriod]=sumn/InpRSIPeriod;
      if(ExtNegBuffer[InpRSIPeriod]!=0.0)
         ExtRSIBuffer[InpRSIPeriod]=100.0-(100.0/(1.0+ExtPosBuffer[InpRSIPeriod]/ExtNegBuffer[InpRSIPeriod]));
      else
        {
         if(ExtPosBuffer[InpRSIPeriod]!=0.0)
            ExtRSIBuffer[InpRSIPeriod]=100.0;
         else
            ExtRSIBuffer[InpRSIPeriod]=50.0;
        }
      //--- prepare the position value for main calculation
      pos=InpRSIPeriod+1;
     }
//--- the main loop of calculations
   for(i=pos; i<rates_total && !IsStopped(); i++)
     {
      diff=close[i]-close[i-1];
      ExtPosBuffer[i]=(ExtPosBuffer[i-1]*(InpRSIPeriod-1)+(diff>0.0?diff:0.0))/InpRSIPeriod;
      ExtNegBuffer[i]=(ExtNegBuffer[i-1]*(InpRSIPeriod-1)+(diff<0.0?-diff:0.0))/InpRSIPeriod;
      if(ExtNegBuffer[i]!=0.0)
         ExtRSIBuffer[i]=100.0-100.0/(1+ExtPosBuffer[i]/ExtNegBuffer[i]);
      else
        {
         if(ExtPosBuffer[i]!=0.0)
            ExtRSIBuffer[i]=100.0;
         else
            ExtRSIBuffer[i]=50.0;
        }
     }
//---
   return(rates_total);
  }
//+------------------------------------------------------------------+


こちらはバイナリーオプションでかなり有名になったインジケーターのような気がする。ただ、
業界の話は置いておくと、これは相場のある期間の売られすぎ買われすぎ水準をグラフ化したインジケーターである。

このインジケーターの計算式は
RSI=A÷(A+B)×100
A:直近14本について、終値で前の足から上昇した値上がり幅の平均
B:直近14本について、終値で前の足から下落した値下がり幅の平均

であり、これがデフォルトで設定されている。

ここから、エントリーの条件を以下の様に定義し、言語化してみる。

買い条件「前足のRSI>=30の時 && 現在足がRSI<=30の時」
売り条件「前足のRSI<=70の時 && 現在足がRSI>=70の時」

条件の良し悪しは置いておいて、いったんこの手法をコードで表現する。

3. コーディングしてバックテストを取得

rsi_signal.c
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=Bars-IndicatorCounted()-1;
  int sumdown=0;
  int sumup=0;
  int windown=0;
  int winup=0;
 
  for(int i=limit; i>=0; i--)
 	{
 	double rsi1=iRSI(NULL, 0, RSIPeriod, PRICE_CLOSE, i+1);	//rsiで呼び出した関数を定義
 	double rsi2=iRSI(NULL, 0, RSIPeriod, PRICE_CLOSE, i+2);	//上記同様
 
 	if(rsi2<RSILine1 && rsi1>RSILine1)//rsiの値が70以下の時で次足で70のラインを超えた時
    	{
    	ArrowDown[i]=High[i];		//下矢印を出す
    	}
 	else if(rsi2>RSILine2 && rsi1<RSILine2)  //rsiの値が30以上で次足で30を下回る時
    	{
    	ArrowUp[i]=Low[i]; 	//上矢印を出す
    	}
 	if(ArrowDown[i]!=0)	//下矢印がある場合
    	{
    	sumdown++; 	//lowエントリーを1カウント
    	if(Open[i]>Close[i-MINUTE])	//終値が前足よりも大きい時
       	{
       	windown++;	//lowでの勝ちをカウント
       	}
    	}
 	else if(ArrowUp[i]!=0) 			 //上矢印がある場合
    	{
    	sumup++;  //highエントリーを1カウント
    	if(Open[i]<Close[i-MINUTE])  	//終値が前足よりも小さい時
       	{
       	winup++;				//highでの勝ちをカウント
       	}
    	}
 	}

途中まではこのように書くことができる。
ただgooogle検索かけてみるとわかるのだが、この手のソースコードはバイナリーの業界なぜか全然置いてない。というわけで、自分もコードの全公開は控えるが、これで8割型完成。

また、mql言語はマイナー言語で、ハイラインが出ないので拡張子はcとしているが、本来の拡張子は.mq4である。
(c言語に非常に似ている言語なので、cで設定すると綺麗に表示された。)

あとは、作った変数をよく考え、しかるべきものの商をとれば、勝率が出る。

これをMetaEditorで作成し、コンパイルした後、MT4画面でストラテジーテスターをいじっていく。

12.png

実際にMT4を開いたら、画像の①の虫眼鏡をクリックし、適切なものを選択していく。
今回作ったインジケーターのファイル名を選択し、各項目を埋めていってスタートを押すとバックテストを取得してくれる。

おそらくこの手法が一番原始的なのでは?

バックテストをとると、
//1month
// ex1 USDJPY,M5: 矢印合計:119.0 勝ち矢印数:69.0 負け矢印数:50.0 勝率:57.98%
// ex1 USDJPY,M5: 下矢印勝率:54.39% 上矢印勝率:61.29%
//3months
// ex1 USDJPY,M5: 矢印合計:417.0 勝ち矢印数:225.0 負け矢印数:192.0 勝率:53.96%
// ex1 USDJPY,M5: 下矢印勝率:52.94% 上矢印勝率:54.93%
//1year
// ex1 USDJPY,M5: 矢印合計:1898.0 勝ち矢印数:1031.0 負け矢印数:867.0 勝率:54.32%
// ex1 USDJPY,M5: 下矢印勝率:53.83% 上矢印勝率:54.83%
//9years
// ex1 USDJPY,M5: 矢印合計:17131.0 勝ち矢印数:9311.0 負け矢印数:7820.0 勝率:54.35%
// ex1 USDJPY,M5: 下矢印勝率:53.54% 上矢印勝率:55.18%

と、このように出る。

さて、ここからが本題である。

4. 取得したデータをもとにコードを改善する

上の数字を見る限り、直近一年間での勝率は損益分立を行ったり来たりする用である。

改善案は実際無限に出てくるのだが、飽きるまで挙げてみると

  • RSIの値を20&80にしてみる(縛りを強くする)
  • RSIの値を10&90にしてみる
  • RSIの値を5&95にしてみる
  • RSIの期間を7に変えてみる
  • RSIの期間を21に変えてみる
  • ロウソク足実体の大きさ割合を制御に加えてみる
  • MAを用いてトレンドを表現して・・・
  • 他のインジと組み合わせてみる
  • 実体の平均値制御をしてみる

等々・・・

冗談抜きで無限にある。これを面倒くさがらず1つ1つ取っていき、それを全通貨確認し、自分のエントリーの期待値と根拠を明確に言語化していって初めて投資として成り立つのがバイナリーオプションだと思う。

期待値やリスクリワード、メンタル、資金管理を1つでもおろそかにした瞬間にギャンブリング要素が顔を出すため、生半可に初めていいものではないと強く感じる。

ただ、ここを完全にコントロールできれば、バイナリーはおいしいし、投機性をより投資的指向に傾かせることができる。

あまり例を挙げると、バイナリー業界の首を絞めることになりかねないので、無償のページではここまでとする。

5. シグナルツールとしてパッケージングする

これはやってもやらなくてもいいのだが、もし他人にシグナルをプレゼントするようなことがある場合は、これをパッケージングしなければいけない。シグナルを搭載した状態のMT4を渡すのが一番手っ取り早いが、それらが面倒な場合は、

作ったMQL4のsource fileを圧縮して送信すれば完了である。

まとめ

かなり強引にはなったが、以上で自分の手法がどれくらいあるのかのバックテストを取る流れとする。

しかし、自分はこの方法はもう取っておらず、先に述べたMTFAという非常に便利なツールを使用している。

ただこれは有料なので、面倒なことを受け入れてバイナリーで結果を出したい。ギャンブリング要素を減らした投機がしたいと思えるならば検討してみてもいいのではないかと思う。

他にも、バックテストをとれる人に代行を依頼する。テスト結果を教えてくれるサロン等に入るなど、自分で取れずともいくらでもやりようはあるということをお伝えしておく。

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?