見出し画像

【バックテスト】やってみよう【FX】

では、MovingAverageを使ってバックテストを取ってみようと思います。

正直、説明が下手なので、何言ってるかわっかんね。ってなるかもしれませんが、そうなったらすいません🐱💦

さっそくバックテスト取得

まず、MT4を開きます

その後、テスターというのを表示されてなかったら、表示します。表示方法は

「表示(V)→ストラテジーテスター」です🐱ゝそうすると下記画像の欄が出てきます。

画像1

画像2

□で囲んだ部分をある程度設定します🐱

上部のMovingAverageについては、中身を少しいじるので名前を変更してあります。

簡単に説明しまうと、

「エキスパートアドバイザー」の、「MovingAverage_講義つけたし」条件で、「ユロドル」を、「2020年10月26日から2021年10月26日までの期間」、「始値のみ」を基準にして「スプ13」でエントリーした場合のバックテストを取りますよ、って感じです。

ちなみに、少し広げると、ヴィジュアルモードっていう欄が出てきます。

画像3

これはバックテスト回してるときに、チャートを表示して、取得スピードの設定もできるので、チャートの流れを見ながらエントリー状況を確認することができます。

それで右下の「スタート」を押して、バックテスト回した結果がこれだよ!


画像4

はい、そのまま使うと、見事に右肩上がりでど損失製造EAですw

下のタブ、「セッティング」とか「結果」とか「グラフ」を見るといろいろ確認できますね🐱ゝ

世間でよく見るのが、上の画像のグラフと、下記画像の表ですね

画像5

まあなんにせよ、これでバックテストの取得は完了という感じです。

バックテストの取得自体ならこのように簡単です🐱ゝ

問題は、MQL言語を用いて、例えば「今のローソク足が陽線で、MAがこのくらいで、RSIがこのくらいの数値の時に、こうなったらこうして、どうするこうする」みたいな指示をすることです。

一個一個覚えていくしかないのですが、やりたいことを一個一個やっていけばそこまで難しくはありませんし、正直僕もまだ全然わかんないことだらけだけど、なんとなく多少のバックテストはとれるくらいのレベルではありますので、全部覚えなきゃいけないわけじゃありませんので、割と敷居は低いのかな。

というイメージです🐱💦


バックテストの前に【MAの中身】が大事だと思うけど、どうなってるの?

MovingAverageの中身を説明します🐱💦

MQLという言語で書かれています。

僕も全部わかってるわけじゃありません。全部分かったほうがいいのですが、全部わかんなくても条件さえ書ければ、バックテストはとれる。っていう見本にもなるので、全部わかんなくても大丈夫ですwとはいえ、まあ超簡単かって言われたら超簡単ではないので、一個一個みていくしかありませんw

まずFXのバックテストに必要な条件は、

0、EA(自動売買)のオンオフ条件

1,エントリー条件

2,価格の設定の条件(ロット数)

3,決済の条件(トレーリングストップの有無、pointで決済などなど)

が必要です。それらが「メタエディタのMAの中」に書いてあります。

ではさっそくコードを見てみましょう🐱

うわぁ、面倒だなぁって思うやつがこの後表示されますw



//+------------------------------------------------------------------+
//|                                               Moving Average.mq4 |
//|                   Copyright 2005-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright   "2005-2014, MetaQuotes Software Corp."
#property link        "http://www.mql4.com"
#property description "Moving Average sample expert advisor"

#define MAGICMA  20131111

//--- Inputs
input double Lots          =0.1;
input double MaximumRisk   =0.02;
input double DecreaseFactor=3;
input int    MovingPeriod  =12;
input int    MovingShift   =6;



//+------------------------------------------------------------------+
//| 1  Calculate open positions                                         |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
 {
  int buys=0,sells=0;
//---
  for(int i=0;i<OrdersTotal();i++)
    {
     if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
     if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA)
       {
        if(OrderType()==OP_BUY)  buys++;
        if(OrderType()==OP_SELL) sells++;
       }
    }
//--- return orders volume
  if(buys>0) return(buys);
  else       return(-sells);
 }



//+------------------------------------------------------------------+
//|  2 Calculate optimal lot size                                       |
//+------------------------------------------------------------------+
double LotsOptimized()
 {
  double lot=Lots;
  int    orders=HistoryTotal();     // history orders total
  int    losses=0;                  // number of losses orders without a break
//--- select lot size
  lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1);
//--- calcuulate number of losses orders without a break
  if(DecreaseFactor>0)
    {
     for(int i=orders-1;i>=0;i--)
       {
        if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false)
          {
           Print("Error in history!");
           break;
          }
        if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL)
           continue;
        //---
        if(OrderProfit()>0) break;
        if(OrderProfit()<0) losses++;
       }
     if(losses>1)
        lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
    }
//--- return lot size
  if(lot<0.1) lot=0.1;
  return(lot);
 }



//+------------------------------------------------------------------+
//| 3  Check for open order conditions                                  |
//+------------------------------------------------------------------+
void CheckForOpen()
 {
  double ma;
  int    res;
//--- go trading only for first tiks of new bar
  if(Volume[0]>1) return;
//--- get Moving Average 
  ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//--- sell conditions
  if(Open[1]>ma && Close[1]<ma)
    {
     res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);
     return;
    }
//--- buy conditions
  if(Open[1]<ma && Close[1]>ma)
    {
     res=OrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,0,0,"",MAGICMA,0,Blue);
     return;
    }
//---
 }



//+------------------------------------------------------------------+
//| 4  Check for close order conditions                                 |
//+------------------------------------------------------------------+
void CheckForClose()
 {
  double ma;
//--- go trading only for first tiks of new bar
  if(Volume[0]>1) return;
//--- get Moving Average 
  ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---
  for(int i=0;i<OrdersTotal();i++)
    {
     if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
     if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue;
     //--- check order type 
     if(OrderType()==OP_BUY)
       {
        if(Open[1]>ma && Close[1]<ma)
          {
           if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White))
              Print("OrderClose error ",GetLastError());
          }
        break;
       }
     if(OrderType()==OP_SELL)
       {
        if(Open[1]<ma && Close[1]>ma)
          {
           if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))
              Print("OrderClose error ",GetLastError());
          }
        break;
       }
    }
//---
 }



//+------------------------------------------------------------------+
//| 5  OnTick function                                                  |
//+------------------------------------------------------------------+
void OnTick()
 {
//--- check for history and trading
  if(Bars<100 || IsTradeAllowed()==false)
     return;
//--- calculate open orders by current symbol
  if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
  else                                    CheckForClose();
//---
 }
//+------------------------------------------------------------------+

出ましたねぇ🐱

わけわかめ🐱これ見ただけでマジで挫折しますよねぇ・・・

僕も正直、なんだこれ、マジで。って感じです。

さて、一個一個見てきましょう。なんなら僕も忘れてる部分もあるのでググりながら書いてますw


処理スタートの文(EAスタートしてね。って命令文)

ここでは

1,チャートのローソク足が充分にあるか。

2,自動売買がオンかオフか

3,エントリーがあるのかないのか

4,エントリーがあったらどうする。なかったらどうする。

という命令文を説明します。

まず第一に一番下のOntickFunctionを見てみましょう

void OnTick()はルールを記載していく領域です。もう、これは単語レベルで「この言葉はこういう意味なのね。そこはこういう領域なのね」と受け入れるしかありませんwロジックを考えるときは「なんでこうなるんだ、なんでこういう動きなんだ」と考える必要がありますが、ただただ暗記科目は暗記するしかありません。その代わり暗記系の用語は、ググれば答えが見つかります。儲かるロジックは皆さん教えてくれませんし、今後儲かる保証もないので、教えたとしても結果ダメになり詐欺師扱いされてもイヤですしねw

ほかの言い方をすると、void Ontickは最初の処理を開始する命令です。冒頭に書いた4つの処理が書かれていますので、全体から抜粋して説明しましょう🐱💦

説明文:は僕が解説した言葉です。正直文章じゃわかりづらいかもしれませんが許してください😹

//+------------------------------------------------------------------+
//| 5  OnTick function                                                  |
//+------------------------------------------------------------------+

void OnTick() ←これは、処理開始するよーっていう命令の合図
 {


//--- check for history and trading ←//がついてるのでコメントアウトといって、まあタイトル(メモ)のようなもの。これをプログラムで読んでもエラーが出るのでコメントアウトというシステムを用いて、メモとして処理します。


  if(Bars<100 || IsTradeAllowed()==false) ←if文があるので、これが命令。「もし」、「なになに」なら「なになに」してね。という事。
     return;

//説明文:Barsとはチャートのローソク足のこと。もし(ローソク足が100本未満。または(||は、またはの意味)、自動売買がオンになってないなら、false(偽、やらない)。という事が書かれてます。
//説明文:IsTradeAllowedとは自動売買のことで、MT4の自動売買がオフになってたら処理しない。ってことが書かれてます。



//--- calculate open orders by current symbol ←タイトル

  if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();

//説明文:もし、ポジションが保有中(CalculateCurrentOrders←は上部に書いてある自作関数で、ポジション保有かどうかの確認の関数)
//説明文:が、0なら、エントリー条件(CeckForOpen)のコードに行き、

  else                                    CheckForClose();

//説明文:それ以外(else、つまり保有中が1以上)なら、決済の条件(CheckForClose)に行く。という事が書かれてます。

//---
 }


今エントリーしてるところがあるかどうか確認する関数


//+------------------------------------------------------------------+
//| 1  Calculate open positions                                         |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)

//説明文:int(今からこういうの作るよって合図) Calculateなんちゃら保有中のポジションがあるかどうかってタイトルの関数だよ)
//説明文:Calcurateなんちゃら、は、ぶっちゃけ何でも良いです。
//説明文:自作関数は自由に名前を付けることができます。
//説明文:ただ、和訳すると「現在の注文を計算します」って意味なので、
//説明文:自分がわかりやすいように現在のポジションの数というタイトルを付けたんだと思います。
//説明文:例えば int PositionNow(string symbol)でも良いのです。


 {
 
  int buys=0,sells=0; 
//説明文: buys と sells という変数を指定して、これらに0を入れてね。っていうプログラム
//説明文:これらが0の場合は、さっき説明した、ポジションが0ってことですね。保有中のポジションが生まれたら1を足していく命令文が↓のプログラムです。


//---
  for(int i = 0 ; i < OrdersTotal(); i++ )
//説明文:for文という関数を使って、保有中と保留中のポジションをカウントしていく。
//説明文:保有中とはエントリー中。保留中とは指値注文中のポジションのこと。
//説明文:これらのトータルがOrdersTotalという関数です。

    {
     if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
//説明文:もし、エントリー中または保留中のポジションが、ない(false)なら、
//説明文:処理せず抜ける(break)。
    
     if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA)
//説明文:もし、現在注文中通貨が、今の通貨ペアと一致していて、且つ(&&)、
//説明文:マジックナンバー(上部の#define MAGICMA  20131111)が一致していたら、
//説明文:下記の処理をしてね。という命令文
//説明文:マジックナンバーとは、EAのナンバーで、EAを複数回す場合に、番号を指定しないとごっちゃになっちゃうため、この番号がある。

       {
        if(OrderType()==OP_BUY)  buys++;
        if(OrderType()==OP_SELL) sells++;
//説明文:三つ上のifの命令文が達成したら、買いエントリーならbuys(売りならsells)に1をプラスしてください。( ++ とは、条件達成するごとに1を足してって意味)
//説明文:という命令文
//補足文:つまり、一番上の方で指定したbuys = 0というのが、1ずつ増えていく処理。これにより、エントリー数をカウントしていく。

        
       }
    }

//--- return orders volume
  if(buys>0) return(buys);
//説明文:もし、買いの注文が1以上なら、buysの中身を処理。
  
  else       return(-sells);
//説明文:それ以外なら、sellに-を入れて、0以外に処理する。
//正直これはいまいち意味わかんないwただ、複数ポジション保有しないための処理とか、EAが動くための処理などをしてるって認識でとりあえずいいかなって感じ。
//わかる人教えてーーーw


 }

まあ、簡単に言うと、タイトル通りですが、保有中のポジションがあるかどうか確認してる関数。って感じですw


ロット数の指定している関数


//+------------------------------------------------------------------+
//| 2  Calculate optimal lot size                                       |
//+------------------------------------------------------------------+
double LotsOptimized()
 {
  double lot=Lots;
//説明文:doubleは、小数点以下を含むから覚悟してねって意味。lot という名前の箱に、Lotsという変数を入れてね。って意味。
//説明文:ちなみにLotsは、MAの中身の上の方に、0.1として指定されています。この数字を0.2とかにしてロット数を調整できます。
//説明文:つまり、lotという箱に0.1入れてね。って意味です。

  int    orders=HistoryTotal();     // history orders total
  int    losses=0;                  // number of losses orders without a break
//--- select lot size
  lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1);
//lot数を、余剰証拠金に0.02かけたりいろいろして、lotを余剰資金に応じて変更させる関数


//--- calcuulate number of losses orders without a break
  if(DecreaseFactor>0)
    {
     for(int i=orders-1;i>=0;i--)
       {
        if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false)
          {
           Print("Error in history!");
           break;
          }
        if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL)
           continue;
        //---
        if(OrderProfit()>0) break;
        if(OrderProfit()<0) losses++;
       }
     if(losses>1)
        lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
    }
//--- return lot size
  if(lot<0.1) lot=0.1;
  return(lot);
 }



あとでググったら、こんなわかりやすく書いてあるサイトがありました



これらを参考にするのも、いいと思いますwwまあ、なんのこっちゃって感じの言葉で書いてあるので、どこのサイトみても、わかんないものはわかんなかったりします。

とはいえ、僕みたいにわかんないままだと、中途半端な実力になるので、何とかしてわかるようにしたいので、

わかるかた、誰か教えてくださいッ😹逆にわかることはわかる範囲でお伝え出来たらと思います😹


エントリー条件に関する関数(さっきバックテストを取った条件w)

これが一番ワクワクするコードですw

この条件で、さっきバックテストを取ったんですよーー!


//+------------------------------------------------------------------+
//| 3  Check for open order conditions                                  |
//+------------------------------------------------------------------+
void CheckForOpen()
//説明文:CheckForOpen.「処理スタートの文」で出てきた関数です。
//補足文:ポジションが0の場合、エントリー条件に行ってね。っていうのが処理スタートの文でしたね。


 {
  double ma;
//説明文:maは0です。という意味。不親切な書き方がしてある。maは変数なのでなんでも良い。例えばkunでもtenchoでも良い(その場合 double kun = 0; という感じで書く)
//説明文:「double ma = 0;」のほうが親切。これは「小数点以下の数値がつく可能性がある、maという変数があります。それの値は今は0です」という意味。
  
  int    res;
//説明文:intは小数点がつかない変数。これは「整数の数値がつく可能性があるresという変数。今は0が入ってます。」という事。


//--- go trading only for first tiks of new bar
  if(Volume[0]>1) return;
//説明文:現在足のボリューム(ティックの動き)が1以上の場合はreturn(処理を返す)。
//補足文:ヴォリュームとは、ティックが動いた回数です。チャートのローソク足にカーソルを合わせると数値が出てきます。
//要は:現在の出来高が0じゃないと処理しないため、一個前の終値が確定した瞬間処理することになる。

//--- get Moving Average ←MAの関数をゲットしてね。ってタイトル
  ma = iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//説明文:変数maに、MAの数値を入れてねって意味です。
//補足文:maの中身、iMA(現在の通貨ペア、現在の時間軸、期間12、表示移動6、単純移動平均線、終値、現在足)を表しています。
//説明文:難しいですよね・・・😹まぁ、MAをチャート上に表示するときに期間とかシフトとかそういうの指定するのが、細かくコードで書かれてるだけです。
//説明文:まあなんにせよ、ma には、上記条件のMAが入っているってことです。チャートでMA出して、カーソル合わせると、指定条件の中の平均値が表示されます。





//--- sell conditions ←いよいよ売りエントリー条件です😹💕
  if(Open[1]>ma && Close[1]<ma)
//説明文:エントリー条件です。
//もし、1本前([1])の始値(Open)が、maの数値を超えていて 
//且つ(&&) 1本前([1])の終値(Close)が、maの数値未満の場合、↓の処理をする


    {
     res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);
     return;
//説明文:上記条件を満たした場合、
     Order(満たした通貨、売りエントリー、プログラムで指定のロット数、bid(売り値価格)、どの程度滑りを許容するか(この場合は3)、損切値(0)、利確値(0)、オーダーのコメント,マジックナンバ、有効期限(0)、赤印で表示)
    //補足文:利確値と損切値が0の場合は、例では利確損切は、0なので、設定はしてない。という事。  有効期限も0なので例えば30分後に強制決済などの設定も書いてない。  
    //補足文:じゃあどうやって利確損切すんの?ってのは、次の項目で説明します。
    }
    
 
//↓は買いのエントリー条件なので、売りのエントリー条件と逆のことが書いてある。
//--- buy conditions ←買いエントリー条件です😹💕
  if(Open[1]<ma && Close[1]>ma)
    {
     res=OrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,0,0,"",MAGICMA,0,Blue);
     return;
    }
//---
 }

エントリーの条件、
if( Open[1] > ma  &&  Close[1] < ma )
もし、1本前([1])の始値(Open)が、maの数値を超えていて 且つ(&&) 1本前([1])の終値(Close)が、maの数値未満の場合

ってどんな状況だよ。ってことですよね。

なので、バックテストの時に説明したビジュアルモードを使って表示してみました😹売りの場合は赤矢印、買いの場合は青矢印で表示されています。

画像6

このように、赤い四角(売りエントリー)を見てみると、赤い矢印のついてるローソク足よりも、1本前のローソク足の始値がMA(赤いぐにゃぐにゃした線)よりも上で、終値がMAよりも下にありますよね。こうなったら、エントリーしてね。って意味です。

この条件で、序盤にやったバックテストを取ったんです。こんな簡単なエントリーでは右肩下がりになっちゃいますよねw



bidとaskについては、

bid = 売る価格

ask = 買う価格

で、現在のチャートの価格とズレているため、別で価格を指定する必要があります。

チャートで気配値というのが見られるのですが、これを表示してもらうとbidとaskが見ることができます

画像7

上記の差額がスプレッドという事なりますね。🐱

証券会社によっても違うので、そのあたりはまあ適当に😹



決済(利確損切)条件の設定

前述したように、エントリー条件の際にも、pointの動きで直接設定することはできるのですが、決済の条件を別途指示することもできます。


//+------------------------------------------------------------------+
//| 4  Check for close order conditions                                 |
//+------------------------------------------------------------------+
void CheckForClose()
//説明文:クローズ(決済)の命令文を開始するよー

 {
 
  double ma;
//--- go trading only for first tiks of new bar
  if(Volume[0]>1) return;
//--- get Moving Average 
  ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//説明文:上記はエントリー条件の設定にも用いたが、新しい処理分になったので、その都度指定しないといけない。

  
//---
  for(int i=0;i<OrdersTotal();i++)
    {
     if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
//説明文:保有中のポジションがあるかどうかの確認  


     if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue;
//説明文:マジックナンバではない、または、通貨ペアではない場合、続ける。


     //--- check order type 
     if(OrderType()==OP_BUY)
//説明文:オーダーのタイプが買いなのか売りなのか。
//説明文:もし、オーダータイプが買いなら、下記処理をしてね。
       {
        if(Open[1]>ma && Close[1]<ma)//決済条件😹💕
  //説明文:もし、1本前の始値がMAより大きく、1本前の終値がMAより下なら、↓の処理
  
          {
           if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White))
 //説明文:逆のbid(売り決済)を滑り3まで許容し、白矢印で表示する。
              Print("OrderClose error ",GetLastError());
  //説明文:(!)もし上記処理に失敗したら、オーダークローズが正常に作動しなかった場合、エラーと表示してね。
           }
 //補足文:!を付けることにより、成功した場合の処理と、失敗した場合の処理を同時に指示できる。

        break;
//説明文:break(決済条件を満たしたにもかかわらず、このオーダークローズ文が通らなかったら抜けてね。って命令)


       }



//説明文:以降は、上記の反対の処理。売りの場合の決済は、買いで決済。それに失敗したらエラー表示をプリントしてね。って感じ
     if(OrderType()==OP_SELL)
       {
        if(Open[1]<ma && Close[1]>ma)
          {
           if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))
              Print("OrderClose error ",GetLastError());
          }
        break;
       }
    }
//---
 }


いやぁ、超ムズイですね😹

僕も書くのつかれたので今日はここまでですw


トレーリングストップとかの決済方法もまだまだあるんですよ・・・

これ、文章じゃわっかんないよなぁ・・・ていうか口頭でも難しいです😹



この記事が気に入ったらサポートをしてみませんか?