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

   2016/09/18

移動平均システム

ゼロから始める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 移動平均システム」でした。

  • このエントリーをはてなブックマークに追加
  • Pocket

コメント一覧

  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の開発に「答え」は存在しません。アイデアを色々と試してみてください。

      • 匿名希望 より:

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

この記事へのコメントはこちら

メールアドレスは公開されませんのでご安心ください。
また、* が付いている欄は必須項目となりますので、必ずご記入をお願いします。

内容に問題なければ、下記の「コメント送信」ボタンを押してください。