自動売買システムを作ろう!ゼロから始めるEA開発Part7 移動平均システム

移動平均システムの構築 FX関連情報

移動平均システム

ゼロから始めるEA開発Part7 移動平均システム

前回はインジケーター・移動平均の使い方を解説しました。

今回はその移動平均を使ってシステムを作ってみたいと思います。

まず、前回作ったものの問題点である「移動平均付近を上下に行ったり来たりする値動きになると、ダマシが頻発する」という問題を解決します。

「大きな値動きが発生した」という売買条件を追加してみましょう。

「大きな値動き」を検知するロジック

ロジックは色々考えられると思いますが、今回は単純に「ローソク足の実体」が「価格のX%だったら」という条件にしました。

なお、このロジックで作ると「Openした足でStopLossとなった場合、再度Openしてしまう」という問題があるため、次の足になるまでOpenしないようにしておきます。

パラメーターと変数を以下のように定義しました。

input double HeightPercent = 0.5;
datetime OpenBarTime = 0;

「高さ」がパラメーターHeightPercentを超えた場合にOpenとします。Openした場合、変数OpenBarTimeに時間を保存します。

double barHeight = MathAbs(Close[0] - Open[0]);
double percentage = (barHeight / Ask) * 100;

Close価格からOpen価格を引いて実体の高さ(価格差)を計算します。

※Close[0]は現在の足のClose価格を取得できます。Close[1]とすると1本前のClose価格を取得します。

そのままだと、陰線(Closeの方が小さい)場合に負の数になってしまいますのでMathAbsという関数で「絶対値」を取得しました。

もし、ローソク足の実体でなく、ヒゲも含める場合には単純にHigh[0]-Low[0]とすればOKです。(負の数は出ないはずですので、MathAbsは不要になります)

取得した実体の高さ(価格差)を現在価格(Askの方を使っていますがBidでも良いです)で除算し、100を掛けて「価格に対する実体の割合」を算出します。

if(percentage > HeightPercent && Time[0] != OpenBarTime)

あとはif文で「計算した実体の割合」と「パラメーターHeightPercent」と比較して、実体が大きければOpenします。

Time[0]は現在のバーの時刻です。1時間足であれば、1時間毎の時刻が入っています。(Time[0]が9:00であれば、Time[1]は8:00、Time[2]は7:00 …)

15分の場合は15分毎です。

OrderSendを実行した後に

OpenBarTime = Time[0];

のように保存しておいて、この時刻を比較すれば「OrderSendした足かどうか」判断できます。

これで完成です。以下、全文。

//+------------------------------------------------------------------+
//|                                              ZeroCreatePart2.mq4 |
//|                                                                  |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright ""
#property link      ""
#property version   "1.00"
#property strict

input int Magic = 123465;
input double Lots = 0.1;
input int Slippage = 2;
input string OrdersComment = "ZeroCreate";
input int StopLoss = 800;
input int TakeProfit = 6500;
input int EMAPeriod = 20;
input double HeightPercent = 0.5;

datetime OpenBarTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//---
   
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   double ema = iMA(Symbol(),0,EMAPeriod,0,MODE_EMA,PRICE_CLOSE,0);

   if(OrdersTotal() == 0)
   {
      double barHeight = MathAbs(Close[0] - Open[0]);
      double percentage = (barHeight / Ask) * 100;
      
      double stopLoss = 0;
      double takeProfit = 0;
      if(percentage > HeightPercent && Time[0] != OpenBarTime)
      {
         if(Ask > ema)
         {
            stopLoss = Ask - StopLoss * Point;
            takeProfit = Bid + TakeProfit * Point;
            OrderSend(Symbol(),OP_BUY,Lots,Ask,Slippage,stopLoss,takeProfit,OrdersComment,Magic,0,clrBlue);
         }
         if(ema > Bid)
         {
            stopLoss = Bid + StopLoss * Point;
            takeProfit = Ask - TakeProfit * Point;
            OrderSend(Symbol(),OP_SELL,Lots,Bid,Slippage,stopLoss,takeProfit,OrdersComment,Magic,0,clrRed);
         }
         OpenBarTime = Time[0];
      }
   }
   
   if(OrdersTotal() > 0)
   {
      for(int i=0; i<=OrdersTotal()-1; i++)
      {
          if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES) == false) break;
          if(OrderType() == OP_BUY && ema > Ask)
          {
            OrderClose(OrderTicket(),OrderLots(),Bid,Slippage,clrBlue);
          }
          else if(OrderType() == OP_SELL && Bid > ema)
          {
            OrderClose(OrderTicket(),OrderLots(),Ask,Slippage,clrRed);
          } 
      }
   }
}
//+------------------------------------------------------------------+

バックテスト結果

今回は回数が少なくなるため、2005年1月から2016年7月でバックテストしました。

※バックテストデータ(ヒストリカルデータ)の作り方は以下のサイトを参考にしてください。

2005年

前半微妙な動きですが2010年以降はいい感じです。優位性があるのでは…!?

※パラメーターは「プログラム全文」の規定値です。StopLossやTakeProfitを大きくしてHeightPercentは0.5としています。

試しに2010年からバックテストしてみました。

2010年からバックテスト

PF1.32。「このまま使う!」という人もいるかもしれませんね!(笑)

レンジ相場の時期を裁量で停止すれば、それなりに勝てるのでないかと思います。

これに色々とアイデアを加えれば使えるEAになりそうな予感ですが、本連載の内容から外れてきますので…これ以上の修正はしません。(これをベースに良い物ができたら、「ソースコード付きで販売する」ということはあるかもしれません!(笑))

まとめ

ようやく「自動売買システムらしいバックテスト結果」になりました。

作り始めた当初は「トントンより多少良い成績がでれば良い」くらいに思っていたのですが、思ったよりもずっと良い物ができましたね!

100行にも満たないソースコードでこれだけやれるとは…。

どこかで時間取って改良してみたいです(笑)

次回は今まで放置してきた「エラー処理」について書いていく予定です。(これでコンパイル時の警告が消えます!)

余談

さっくり作ったような雰囲気で書いていますが、実は少し手間取っています。

最初はATRを使って「値幅」を見ようとしたのです。

iATRという関数を呼び出して、ATRの2倍だったら「大きな値動き発生」というロジックで作ったのですが…値動きの無い時はATRが非常に小さくなって「少し大きな値動きが発生しただけで検知する」ため、ダマシが非常に多くなりました…。

double atr = iATR(Symbol(),0,ATRPeriod,0);
double atrNow = iATR(Symbol(),0,1,0);
if(atrNow > atr * 2)

こんな感じですかね。(今、思い出して書いただけで…コンパイルも通してませんが大丈夫だと思います。)

現在のATR(期間に1を指定)がATR(規定値は14)の2倍になっていたら~…というロジックです。

ATRはボラティリティが大きくなったことを検知する際に使いやすいインジケーターで、トレンドフォローでも逆張りでも使える便利なインジです。

 

移動平均の値を変えたり、ストップロスやテイクプロフィットを変えたりしても、どうも良くならなかったので「値幅そのもの」を「価格との割合」にしてみたところ、0.5%だと非常に良い結果になった次第です。(0.5%以上にするともっと良いのですがトレード回数が極端に減ります。)

以上、初心者向けEA開発講座「自動売買システムを作ろう!ゼロから始めるEA開発Part7 移動平均システム」でした。

→ ゼロから始めるEA開発Part8 エラー時の処理

コメント

  1. 匿名希望 より:

    いつも拝見させていただいております。
    今回のコードの内容で質問がありコメントいたしました。
    以下の部分なのですが

    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    void OnTick()
    {
    中略

    double barHeight = MathAbs(Close[0] – Open[0]);

    <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    OnTick関数内にあるClose[0]なのですがPrintで確認したとことTickが動くたびに変動している動きになっていました。
    この場合、AskやBidではまずいのでしょうか?
    それとも時間足が更新される最後の瞬間の価格を取得するためでしょうか?その場合Close[1]を使用するとなにか不具合が生じるのでしょうか?
    Close[0]を使用したことがなかったので疑問に思っていました。(いつもはClose[1]などの時間足の更新が完了した終値を取得するものだと思っておりました)

    以上です。
    お忙しいところ申し訳ありませんがよろしくお願いします。

    • emija より:

      コメントありがとうございます。
      どの足を使うかはシステムによります。
      「足が完成した」ことを待っていては「遅い」と判断して「現在の足」を使用しています。
      (大きく動いた時には早く仕掛けたかったので0を使用しただけです…)

      AskやBidでも良いですが、Bid-Open[0] では「実体の長さを測っている」ことがわかりづらくなります。(1本後ろの足に変更しようとした場合の修正量も僅かですが増えます)
      Close[1]-Open[1]でも良いです。私は試していないで、そのほうが結果が良くなる可能性もあります。
      EAの開発に「答え」は存在しません。アイデアを色々と試してみてください。

      • 匿名希望 より:

        早い回答ありがとうございます。
        このコードは色々ベースになりそうなので試してみたいと思います。
        以上、宜しくお願いします。

  2. keita より:

    昔の記事にコメントをさせて頂き申し訳ございません。こちらの記事で使用している通貨ペアを教えて頂きたいです。何卒よろしくお願いいたします。

    • emija より:

      すみません‥。なにぶん、昔の記事のため記憶に無いです‥。
      マイナーな通貨ペアを使うとは思えないので、EURUSDかUSDJPYではないか‥とは思うのですが‥。

  3. keita より:

    早速のご回答ありがとうございます、承知致しました。
    どのPartもとても参考になります、今後ともよろしくお願いいたします。

    • emija より:

      気になったので試してみました。EURUSDのH1だと思います。
      Alpariのデータ、スプレッド10でバックテストするとバックテスト結果が似たような形になりました。
      ただし、記事を書いた頃(2018年)がピークで、その後は下がっていました。優位性はもう無いかもしれません‥。

  4. keita より:

    すいません、先ほど返信に気が付きました。
    いえいえ、同様の結果を再現したかったので助かりました、検証ありがとうございます。

タイトルとURLをコピーしました