人気のトレーディングシステムを基にした Expert Advisor と売買ロボット最適化の錬金術

Nikolay Kositsin | 16 3月, 2016

はじめに

Forex 取引に関する書籍の大半は、学習用教材としてもっともシンプルなトレーディングシステムを提供するものです。ただ、現時点でそのようなシステムは既製の Expert Advisor に入っているトレーディング戦略の適切な実装なしでは一般的指導でしかありません。よってそのような例に実用的価値があるかどうか推測するのは不可能です。EA のプログラム専用の数多くのフォーラムを通じて見ると、EA の初心者プログラマーは、一番最初からもっともシンプルなトレーディングシステムを基にした初めての Expert Advisor を一から作成しなければならないのです。

私はそのようは作業の価値にはかなり疑いを持ち、そのようなトレーディングシステムを基にすべてを一から始める必要に迫られた場合、初心者トレーダーが皆、正しく EA を作成できるようにすることが必要であると考えます。本稿では問題解決の私独自のバリアントを提供したいと思います。EA 構築のためには、すでに記事 Effective Averaging Algorithms with Minimal Lag: Use in Indicators で使用したことのある私のライブラリのインディケータを利用します。



一般的 EA の構造スキーム

メインの記述を開始する前に、本稿に出る EA はすべて同一スキームに従って構築されているということにご注意いただきたいと思います。



変動方向の変化に基づくトレーディングシステム

作業細部をすべて説明し、既製の Expert Advisor 例で提供されている EA の構造スキームの詳細を説明してみようと思います。本システムでは買いのシグナルは下降から上昇への変動方向の移動です。

売りシグナルはここでは上昇から下降への変動方向の移動です。

トレンドシグナルの計算には、3番目、2番目、1番目のバーの変化値を使用します。ゼロバーにおける値変化は対象としません。すなわち、システムはクローズ済みのバーのみを処理するのです。移動としてカスタムインディケータ を利用しています。それは追加の JMA 平滑化を伴うシンプルなデジタル FATL フィルターです。FATL 方向変化のトレードに入ることで数ポイントの利益が約束される、というのをインターネットでよく見かけ、読者のみなさんは、現実でこの戦略がどれほど効率的かすぐに納得されることでしょう。以下は Expert Advisor の形式でのシステム実装バージョンです。



Expert Advisor コード

//+==================================================================+
//|                                                        Exp_1.mq4 |
//|                             Copyright © 2007,   Nikolay Kositsin | 
//|                              Khabarovsk,   farria@mail.redcom.ru | 
//+==================================================================+
#property copyright "Copyright © 2007, Nikolay Kositsin"
#property link "farria@mail.redcom.ru"
//---- EA INPUT PARAMETERS FOR BUY TRADES 
extern bool   Test_Up = true;//filter of trade calculations direction
extern int    Timeframe_Up = 240;
extern double Money_Management_Up = 0.1;
extern int    Length_Up = 4;  // smoothing depth 
extern int    Phase_Up = 100; // parameter changing in the range 
          //-100 ... +100, influences the quality of the transient process; 
extern int    IPC_Up = 0;/* Selecting prices, on which the indicator will 
be calculated (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 
6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 
11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 
14-Heiken Ashi Close.) */
extern int    STOPLOSS_Up = 50;  // stoploss
extern int    TAKEPROFIT_Up = 100; // takeprofit
extern bool   ClosePos_Up = true; // forced position closing allowed
//---- EA INOUT PARAMETERS FOR SELL TRADES 
extern bool   Test_Dn = true;//filter of trade calculations direction
extern int    Timeframe_Dn = 240;
extern double Money_Management_Dn = 0.1;
extern int    Length_Dn = 4;  // smoothing depth
extern int    Phase_Dn = 100; // parameter changing in the range
         // -100 ... +100, influences the quality of the transient process; 
extern int    IPC_Dn = 0;/* Selecting prices, on which the indicator will 
be calculated (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 
6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 
11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 
14-Heiken Ashi Close.) */
extern int   STOPLOSS_Dn = 50;  // stoploss
extern int   TAKEPROFIT_Dn = 100; // takeprofit
extern bool   ClosePos_Dn = true; // forced position closing allowed
//---- Integer variables for the minimum of counted bars
int MinBar_Up, MinBar_Dn;
//+==================================================================+
//| Custom Expert functions                                          |
//+==================================================================+
#include <Lite_EXPERT1.mqh>
//+==================================================================+
//| Custom Expert initialization function                            |
//+==================================================================+
int init()
  {
//---- Checking the correctness of Timeframe_Up variable value
   if (Timeframe_Up != 1)
    if (Timeframe_Up != 5)
     if (Timeframe_Up != 15)
      if (Timeframe_Up != 30)
       if (Timeframe_Up != 60)
        if (Timeframe_Up != 240)
         if (Timeframe_Up != 1440)
           Print(StringConcatenate("Timeframe_Up parameter cannot ",
                                  "be equal to ", Timeframe_Up, "!!!"));
//---- Checking the correctness of Timeframe_Dn variable value 
   if (Timeframe_Dn != 1)
    if (Timeframe_Dn != 5)
     if (Timeframe_Dn != 15)
      if (Timeframe_Dn != 30)
       if (Timeframe_Dn != 60)
        if (Timeframe_Dn != 240)
         if (Timeframe_Dn != 1440)
           Print(StringConcatenate("Timeframe_Dn parameter cannot ",
                                 "be equal to ", Timeframe_Dn, "!!!")); 
//---- Initialization of variables             
   MinBar_Up = 4 + 39 + 30;
   MinBar_Dn = 4 + 39 + 30;                                        
//---- end of initialization
   return(0);
  }
//+==================================================================+
//| Expert Advisor deinitialization function                         |
//+==================================================================+  
int deinit()
  {
//----+
   
    //---- End of EA deinitialization
    return(0);
//----+ 
  }
//+==================================================================+
//| Custom Expert iteration function                                 |
//+==================================================================+
int start()
  {
   //----+ Declaration of local variables
   int    bar;
   double Mov[3], dMov12, dMov23;
   //----+ Declaration of static variables
   static int LastBars_Up, LastBars_Dn;
   static bool BUY_Sign, BUY_Stop, SELL_Sign, SELL_Stop;
   
   //----++ CODE FOR LONG POSITIONS
   if (Test_Up)
    {
      int IBARS_Up = iBars(NULL, Timeframe_Up);
      
      if (IBARS_Up >= MinBar_Up)
       {
         if (LastBars_Up != IBARS_Up)
          {
           //----+ Initialization of variables 
           BUY_Sign = false;
           BUY_Stop = false;
           LastBars_Up = IBARS_Up;
           
           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM TO BUFFERS
           for(bar = 1; bar <= 3; bar++)
                     Mov[bar - 1]=
                         iCustom(NULL, Timeframe_Up,
                                "JFatl", Length_Up, Phase_Up,
                                                   0, IPC_Up, 0, bar);
           
           //----+ DEFINING SIGNALS FOR TRADES 
           dMov12 = Mov[0] - Mov[1];
           dMov23 = Mov[1] - Mov[2];
                                          
           if (dMov23 < 0)
              if (dMov12 > 0)
                        BUY_Sign = true;
                          
           if (dMov12 < 0)
                        BUY_Stop = true;
          }
          //----+ EXECUTION OF TRADES
          if (!OpenBuyOrder1(BUY_Sign, 1, Money_Management_Up,
                                          STOPLOSS_Up, TAKEPROFIT_Up))
                                                                 return(-1);
          if (ClosePos_Up)
                if (!CloseOrder1(BUY_Stop, 1))
                                        return(-1);
        }
     }
     
   //----++ CODE FOR SHORT POSITIONS
   if (Test_Dn)
    {
      int IBARS_Dn = iBars(NULL, Timeframe_Dn);
      
      if (IBARS_Dn >= MinBar_Dn)
       {
         if (LastBars_Dn != IBARS_Dn)
          {
           //----+ Initialization of variables
           SELL_Sign = false;
           SELL_Stop = false;
           LastBars_Dn = IBARS_Dn;
           
           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM TO BUFFERS        
           for(bar = 1; bar <= 3; bar++)
                     Mov[bar - 1]=
                         iCustom(NULL, Timeframe_Dn,
                                "JFatl", Length_Dn, Phase_Dn,
                                                   0, IPC_Dn, 0, bar);
           
           //----+ DEFINING SIGNALS FOR TRADES
           dMov12 = Mov[0] - Mov[1];
           dMov23 = Mov[1] - Mov[2];
                                           
           if (dMov23 > 0)
              if (dMov12 < 0)
                       SELL_Sign = true;
                          
           if (dMov12 > 0)
                       SELL_Stop = true;
          }
          //----+ EXECUTION OF TRADES
          if (!OpenSellOrder1(SELL_Sign, 2, Money_Management_Dn,
                                            STOPLOSS_Dn, TAKEPROFIT_Dn))
                                                                   return(-1);
          if (ClosePos_Dn)
                if (!CloseOrder1(SELL_Stop, 2))
                                        return(-1);
        }
     }
//----+ 
    
    return(0);
  }
//+------------------------------------------------------------------+

「売り」および「買い」のシグナルを取得するためには、2つのまったく独立したアルゴリズムが必要です。そのアルゴリズムはそれぞれ最適化のための外部パラメータを持ちます。私の経験ではこの EA プログラミングに対するこの類のアプローチは「買い」と「売り」のシグナルを検出するためのアルゴリズム1つのバリアントより収益性がずっと高い、と証明されています。ショートおよびロングのポジション両方に対する単一変化のバリアントにご興味がある方は、EXP_0.mq4 EA でこのアルゴリズムを学習することができます。ここでは2つの変化を持つ EA について説明を続けていきます。Expert Advisor は、取引対象ペアに対し、同時に「買い」方向でポジションを1つと、「売り」方向でポジションを1つオープンすることができます。EA は市場からのトレードを実行するのです。トレードはストップロスとテイクプロフィット注文により終了します。トレンドシグナルが EA で開いているポジションの逆に表れると、EA はトレードの強制終了を許可します。トレードを出るシグナルを受け取る方法は、トレードに入るシグナルを受け取る場合の類似体ですが、逆の性質を持ちます。



Lite_EXPERT1.mqh ファイルの内容

皆さんご自身の作業から最大限ムダが省かれるように、Expert Advisor を書くときは最大数のユニバーサルなユーザー定義関数を使用します。そしてのちに、新しい製品がすべて統一された標準明細、ブロック、モジュールを最大数持つ異なる機械の製造工場で行われるように、異なる部分からの EA コードを組み立てます。これが『トレード実行』ブロックのすべての EA で複数のユニバーサルなユーザー定義関数が使用される理由です。それらは#include <Lite_EXPERT1.mqh> 命令により EA コードにインクルードされます。

bool OpenBuyOrder1
( 
  bool BUY_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

bool OpenSellOrder1
( 
  bool SELL_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

CountLastTime
(
  int lastError
)

bool CloseOrder1
( 
  bool Stop_Signal, int MagicNumber
)

int StopCorrect
( 
  string symbol, int Stop
)

bool MarginCheck
(
  string symbol, int Cmd, double& Lot
)

double GetFreeMargin()

bool DeleteOrder1
(
  bool& CloseStop, int MagicNumber
)

bool OpenBuyLimitOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenBuyStopOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenSellLimitOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenSellStopOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenBuyOrder2
( 
  bool BUY_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

bool OpenSellOrder2
( 
  bool SELL_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

bool Make_TreilingStop
(
  int MagicNumber, int TRAILINGSTOP
)

OpenBuyOrder1() 関数は呼び出されると、外部変数 BUY_Signal の値が真で、オープンされたポジションがなければ、ロングポジションをオープンします。その識別(マジック)番号は MagicNumber 変数の値に等しくなっています。外部変数STOPLOSS および TAKEPROFIT の値はそれぞれポイントでストップロス値とテイクプロフィット値を定義します。Money_Management 変数の値はゼロから1まで変化します。この変数はトレード実行に対し有効なデポジットのどの部分を使うか示すものです。この変数の値がゼロより小さい場合は、OpenBuyOrder1() 関数がその値をロットサイズとして使用します。ショートポジションは類似の方法で OpenSellOrder1() 関数が呼び出されるときオープンします。どちらの関数もお互いに関連なくポジションをオープンしますが、トレード実行のコマンドはただ1つだけ 11 秒以内にサーバーに送信されます。トレード実行以外に、関数 OpenBuyOrder1() および OpenSellOrder1() はオープンされたトレードの情報をログファイルに記録します。

Stop_Signal 変数が真の値を取得する場合、CloseOrder1() 関数が参照されると、MagicNumber 変数の値に等しいマジックナンバーでポジションをクローズします。

StopCorrect() 関数は「停止」のパラメータとしてストップロス値またはテイクプロフィット値を受け取り、最低限受け取られた値と対応するか確認し、必要に応じて最低限の許容値に変更し、潜在的修正を考慮してその値を返します。

MarginCheck() 関数を割り当てることは、現行ロットサイズではフリーマージンが十分でない場合、フリーマージンがトレードをオープンするのに十分である最大サイズまでオープンされたトレードで使用されるロットサイズを減らすことを示します。


OpenBuyOrder1()、OpenSellOrder1()、CloseOrder1() は start() 関数内で使用されます。一方、関数 StopCorrect() および MarginCheck() はOpenBuyOrder1() と OpenSellOrder1() のコード内で使用されます。


OpenBuyOrder1()、OpenSellOrder1()、CloseOrder1() が正常に終了すると、そのどれもが「真」を返します。関数実行中にエラーが発生すると返される値は「偽」となります。3つの関数OpenBuyOrder1()、OpenSellOrder1()、CloseOrder1() はすべて、トレード実行時に外部変数 BUY_Signal、SELL_Signal、Stop_Signal の値を「偽」で返します。

GetFreeMargin() 関数は、ポジションオープンに使用される利益と損失を許容する現行アカウントのフリーマージンサイズを返します。この関数はロットサイズ計算に使用されます。

CountLastTime() 関数は、トレード実行中に発生したエラーに対するアカウントの LastTime 変数の初期化を行います。この関数はトレード実行後速やかに呼び出します。以下がその例です。

  //----+ Open Buy position    
  ticket = OrderSend(Symb, OP_BUY, Lot, ask, 3, 
            Stoploss, TakeProfit, NULL, MagicNumber, 0, Lime);
  
  //---- Calculating a pause between trade operations
  CountLastTime(GetLastError());

上で列挙された関数以外に、Lite_EXPERT.mqh ファイルには未決注文を出す関数があと4つ、未決注文を削除する関数が1つあります。それらは OpenBuyLimitOrder()、OpenBuyStopOrder1()、 OpenSellLimitOrder1()、OpenSellStopOrder1()、DeleteOrder1() です。これらの外部変数割り当ては完全に似ており、名前でわかるようになっています。新規の変数 LEVEL とExpiration は現行価格からの距離をポイント表記で関数に送信するのに必要です。その変数それぞれで、未決注文が出され、期限切れとなるのです。

これまで説明した関数以外にもファイルにはもう2つ関数があります。それはOpenBuylOrder2() と OpenSellOrder2() で、1点を除いてすべてOpenBuyOrder1() と OpenSellOrder1() の類似です。これらはまず、ストップロス注文もテイクプロフィット注文もなしでポジションをオープンし、その後すでにオープンしているポジションを修正してストップロスとテイクプロフィットを設定します。これらは、『銘柄』実行タイプであるため、市場でポジションをオープンする際、「クライアント」にストップロスとテイクプロフィットを出すことを許可しないブローカーで作成済み EA を使用する処理にとって必要な関数です。そのような仲介会社ではオープン済みポジションを修正するという方法でストップロス注文とテイクプロフィット注文が出されるのです。

そしてファイルに入っている最後の関数は Make_TreilingStop() です。この関数は標準的なトレーリングストップを行います。

関数以外にも Lite_EXPERT.mqh には変数 LastTime の完全版があります。それはオーダーをオープンする関数すべてで使用されるため、グローバルレベルで宣言される変数です。

私見では、この関数セットは EA のプログラミングを練習するにはひじょうに便利です。初心者の EA プログラマ―にとってはこのコードを書く必要がなくなり、時間の節約になることでしょう。例として提供されている関数セットからどんな関数も取り出すことができます。

//+==================================================================+
//| OpenBuyOrder1()                                                  |
//+==================================================================+
bool OpenBuyOrder1
        (bool& BUY_Signal, int MagicNumber, 
                double Money_Management, int STOPLOSS, int TAKEPROFIT)
{
//----+
  if (!BUY_Signal)
           return(true); 
  //---- Checking the expiration of minimal time interval 
                                    //between two trade operations         
  if (TimeCurrent() < LastTime)
                          return(true); 
  int total = OrdersTotal();
  //---- Checking the presence of an opened position 
          //with the magic number equal to Value of MagicNumber variable
  for(int ttt = total - 1; ttt >= 0; ttt--)     
      if (OrderSelect(ttt, SELECT_BY_POS, MODE_TRADES))
                      if (OrderMagicNumber() == MagicNumber)
                                                      return(true); 
  string OrderPrice, Symb = Symbol(); 
  int    ticket, StLOSS, TkPROFIT;
  double LOTSTEP, MINLOT, MAXLOT, MARGINREQUIRED;
  double FreeMargin, LotVel, Lot, ask, Stoploss, TakeProfit;                                                 
                                                      
  //----+ calculating lot size for position opening
  if (Money_Management > 0)
    {        
      MARGINREQUIRED = MarketInfo(Symb, MODE_MARGINREQUIRED);
      if (MARGINREQUIRED == 0.0)
                    return(false);
                    
      LotVel = GetFreeMargin()
               * Money_Management / MARGINREQUIRED;         
    }
  else 
    LotVel = MathAbs(Money_Management);
  //----  
  LOTSTEP = MarketInfo(Symb, MODE_LOTSTEP);
  if (LOTSTEP <= 0)
              return(false);  
  //---- fixing lot size for the nearest standard value
  Lot = LOTSTEP * MathFloor(LotVel / LOTSTEP);  
  
  //----+ checking lot for minimally accepted value
  MINLOT = MarketInfo(Symb, MODE_MINLOT);
  if (MINLOT < 0)
         return(false);
  if (Lot < MINLOT)
          return(true);
          
  //----+ checking lot for maximally accepted value
  MAXLOT = MarketInfo(Symb, MODE_MAXLOT);
  if (MAXLOT < 0)
         return(false);
  if (Lot > MAXLOT)
          Lot = MAXLOT;
          
  //----+ checking if free margin is enough for lot size 
  if (!MarginCheck(Symb, OP_BUY, Lot))
                               return(false);
  if (Lot < MINLOT)
          return(true);
  //----
  ask = NormalizeDouble(Ask, Digits);
  if (ask == 0.0)
          return(false);
  //----             
  StLOSS = StopCorrect(Symb, STOPLOSS);
  if (StLOSS < 0)
          return(false);   
  //----
  Stoploss = NormalizeDouble(ask - StLOSS * Point, Digits);
  if (Stoploss < 0)
         return(false);
  //----       
  TkPROFIT = StopCorrect(Symb, TAKEPROFIT);
  if (TkPROFIT < 0)
          return(false);  
  //----               
  TakeProfit = NormalizeDouble(ask + TkPROFIT * Point, Digits);
  if (TakeProfit < 0)
         return(false);
  
  Print(StringConcatenate
         ("Open for ", Symb,
            " a Buy position with the magic number ", MagicNumber));
            
  //----+ Open Buy position
  ticket = OrderSend(Symb, OP_BUY, Lot, ask, 3, 
            Stoploss, TakeProfit, NULL, MagicNumber, 0, Lime);
  
  //---- Calculating pause between trade operations
  CountLastTime(GetLastError());
  //----
  if(ticket > 0)
   {
     if (OrderSelect(ticket, SELECT_BY_TICKET))
       {
         BUY_Signal = false;
         OpderPrice = DoubleToStr(OrderOpenPrice(), Digits);  
         Print(StringConcatenate(Symb, " BUY order with the ticket No",
                ticket, " and magic number ", OrderMagicNumber(), 
                                         " opened with the price ",OpderPrice));
         return(true);
       }
     else
       {
         Print(StringConcatenate("Failed to open ", Symb, 
            " BUY order with the magic number ", MagicNumber, "!!!"));
         return(true);
       }
    }
  else
    {
      Print(StringConcatenate("Failed to open", Symb, 
           " BUY order with the magic number", MagicNumber, "!!!"));
      return(true);
    }
  //---- 
  return(true);
//----+
}

明らかなエラーもそうでないエラーもなくそのようなコードを書くことは、MQL4 の初心者ユーザーにとっては困難で時間のかかる作業です。そしてそのような既製のユニバーサルなコード(専門家が書いた)を自分の Expert Advisor で使用することはとても簡単です。

          //----+ EXECUTION OF TRADES
          if (!OpenBuyOrder1(BUY_Sign, 1, Money_Management_Up, 
                                          STOPLOSS_Up, TAKEPROFIT_Up))
                                                                 return(-1);
          if (ClosePos_Up)
                if (!CloseOrder1(BUY_Stop, 1))
                                        return(-1);

ユニバーサルなユーザー定義関数呼び出しを数行書くだけでコードは完成です!みなさんがすることは関数呼び出しがどのように書かれるか一度理解することだけです。もっと重要なことは、 EA のコードはきわめてシンプルで理解しやすくなり、このためみなさんはご自身の Expert Advisor にトレーディング戦略を実装することができる、ということです。EA Exp_1.mq4 では、未決注文をオープンする関数やトレーリングストップは使われないため、EA のコンパイル中にMetaEditor が EA からこれら関数を削除することについて警告を表示します。



EA コードに関する追加説明

残りの EA コードについて説明を始めます。EA は2件のほとんど同一のアルゴリズムで構成されています。よって詳細を適切に理解するためには、たとえばロングポジションをオープンする EA 部分を分析するだけで十分です。EA コードにはすでに個別のコードフラグメントの意味を説明するコメントが入っています。なので、コメントされていない部分を分析します。初期化ブロックでは MinBar_Up 変数が初期化されます。

//---- Initialization of variables             
   MinBar_Up = 4 + 39 + 30;

この変数は EA メモリにバーの最小数を格納するためのものです。それ以下のバー数では EA のロング方向の処理は不可能です。この値はカスタムインディケータ JFATL.mq4 のアルゴリズムから定義されます。FATL デジタルフィルタの唯一の値を計算するにはチャートバーが 39 本必要です。JMA アルゴリズムにより平滑化された JFATL を取得するには、最低 30 のFATL 値が必要で、トレード用シグナル削除のEA アルゴリズムは1つを除くチャートバー3本プラス4番目にゼロバーを使用します。以下がさらなる計算に対してバー本数が十分かどうかの確認です。

if (IBARS_Up >= MinBar_Up)

確認

if (LastBars_Up != IBARS_Up)

が、ティック毎に市場に入るための EA シグナル再計算を削除するのに必要です。EA はバー変更のときだけこれを行います。それがコンピュータのリソースと EA の最適化時間を十分節約します。LastBars_Up 変数が静的変数として宣言されるのはこのためです。int start() 関数の前回ティックにおけるバー本数を記憶するのです。市場に入るまたは出るための BUY_Sign および BUY_Stop の初期化はバー変更の際一度だけ行われます。それらはトレードが実行される、またはクローズされる、またはバー変更がもう一度起こるまで値を保持します。そのためこれら変数は静的に宣言されるのです。その他の EA 詳細はひじょうに明白でコードから理解できると思います。



Expert Advisor 内での変化の置換

ここから別の変化のための EA 変更の機能について詳しく説明したいと思います。例として、私のライブラリからカスタムインディケータ J2JMA.mq4 を使用します。以下ががその呼び出し記述です。

           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM INTO BUFFERS
           for(bar = 1; bar <= 3; bar++)
                     Mov[bar - 1]=                  
                         iCustom(NULL, Timeframe_Up, 
                                "J2JMA", Length1_Up, Length2_Up,
                                             Phase1_Up, Phase2_Up,  
                                                  0, IPC_Up, 0, bar);

このタスクは EA の外部パラメータブロックを少し変更することにあります(また例ではアルゴリズムを半分だけ記述しています)。

//---- EA INPUT PARAMETERS FOR BUY TRADES
extern bool   Test_Up = true;//filter of trade calculations direction
extern int    Timeframe_Up=240;
extern double Money_Management_Up = 0.1;
extern int    Length1_Up = 4;  // depth of the first smoothing
extern int    Phase1_Up = 100; // parameter of the first smoothing,
       //changing in the range -100 ... +100, influences the quality 
       //of the transient process of averaging;
extern int    Length2_Up = 4;  // depth of the second smoothing
extern int    Phase2_Up = 100; // parameter of the second smoothing,
       //changing in the range -100 ... +100, influences the quality
       //of the transient process of averaging;

初期化ブロックでは、変数値は変更されます。

//---- Initialization of variables
   MinBar_Up = 4 + 30 + 30;

連続した平滑化が 2 件あり、それぞれが最低 30 本のバーを必要とし、EA のアルゴリズム計算に 4 本のバーが必要です。その後、ソース値取得ブロックでは、カスタムインディケータ参照が段落の最初に置かれているものに変更されます。アルゴリズムの2番目部分ではすべて同じように行われます。このため、移動のみならずときとしてひじょうに便利なオシレータも使用します。J2JMA に基づく既製の EA コードは EXP_2.mqh ファイルにインクルードされています。



EA 最適化

所定の EA 例でこれら記事に含まれることになるすべての EA の最適化詳細をいくらかお話しします。前にも述べましたが、EA には2つ独立したアルゴリズムがあります。ロングポジションの処理とショートポジションの処理です。当然、1度に「買い」または「売り」に対してのみ EA を最適化する方が便利で迅速です。このためにの外部変数が2つあります。「買い」と「売り」それぞれに対応して、EA - Test_Up および Test_Dn です。

論理変数の一つに「偽」の値を割り当てることで、この方向における全計算を除外し、結果として最適化にかかる時間を短くします。一方向の EA パラメータの最適化後、変数Test_Upおよび Test_Dn の値を逆方向の値に変更し、逆方向で EA を最適化します。そしてその後初めて変数両方に「真」を割り当て、結果を検証します。最適化と検証の手順は記事 MetaTrader 4 クライアントターミナルにおける Expert Advisor の検証;概要 でひじょうに解りやすく説明しています。本 EA に対する最適化はそこに説明されている通りに行います。ストラテジーテスタを起動し、EA をアップロードしたら、トレードペア、ペアの期間、最適化方法、最適化期間を選択します。

その後、テスターで『エキスパート プロパティ』を選び、"Testing"に移動します。

ここでデポジットサイズを決め、最適化に対し一方向(ロングまたはショート)を選択したら、遺伝的最適化アルゴリズムを選びます。その後『インプット』タブに移ります。

ここでは、外部変数 Test_Up および Test_Dn の一つに「真」を割り当て、もう一方に「偽」を割り当てます。そして、変数Timeframe_Up および Timeframe_Dn variables にチャート期間の値を割り当てます。そこでは最適化が行われます。Money_Management_Up と Money_Management_Dn では「買い」と「売り」取引実行それぞれに対応して使用するデポジット部分を決定します。
残りの外部変数に対しては最適化中の変更限度を設定します。その後、最適化された外部変数にフラグを付けます。『OK』をクリックしてタブを閉じ、テスターで『開始する』をクリックして最適化を開始します。最適化が終わったら、

テスターの "Results" タブに移動します。

そして満足のいく最適化結果をストラテジーテスタにアップロードします。その後、逆方向についても同じ手順を行います。結果、アップロードされたパラメータを持つ EA を取得します。それは最適化の期間で利益を出すものです。MetaTrader 4 の EA プロパティタブは上で表示したものとは異なることに注意が必要です。

この形式でウィンドウを使用することはあまり便利ではありません。そのような場合には最大化された形式が好ましいと言えます。クライアントターミナルではウィンドウを最大化することはできませんので、最大化するために別のファイル(OpenExp.exe)を作成しました。このファイルは、名前が Exp_ c(大文字小文字の区別あり)で始まるファイルを持つ EA のプロパティウィンドウを最大化しました。このファイルを使うには、なんらかのディレクトリで開始する必要があります。それから、プログラム モジュールは EA プロパティウィンドウが表示されるのを待ち、そのサイズを変更します。そのとき、マウスを動かすことはお薦めできません。

検証を始めるEA プログラマーはだれしもさまざまな検証の詳細について疑問を持つことでしょう。まず、われわれの検証および最適化に対してはどんなチャート期間にするか決めます。特別な答えはないと思います。EA 動作のチャート期間が長いほど、トレーディングシステムにおける可視的規則はより安定します。それは本番の取引のようなものです。ただ、長期の取引で最適なのは、本格的な分析中、異なる検証と最適化バリアントの比較中にのみ取得可能な結果です。最適化に対して選択される銘柄も同様です。各銘柄には独特の性質があります。ここからはモデル化についてです。最初のバー変更時のトレード実行のためのシグナルを受け取る EA は最適化中に十分な質的、量的損失がないコントロール ポイントにおいてモデル化されるときひじょうにうまく最適化されることは、経験によりわかっています。当然、この意見は人それぞれの経験で確認すべきです。私は、全ティックのモデル化でそのようなを最適化することは無駄だとわかっています。それでは、もう一つ重要なパラメータ、最適化が行われる期間についてお話しします。

ここでは EA 最適化の目的に応じた異なる値が設定可能です。重要なことは、期間は履歴データの範囲を超えることはできないということです。そうでなければ、以下のようなことになります。

クオートアーカイブで有効な全履歴データの下限は以下のとおりです。



当然、最適化や検証に含まれる Expert Advisor やインディケータの操作に以下の類のエラーが発生する可能性があります。

こういったエラーは EA 自体には関係ありません。最適化期間は有効な範囲から選択されるのであって、好きな期間を入れるというわけではない、ということです。

また、Expert Advisor の『インプット』タブについて少し説明をしようと思います。ここでもその一部のみ分析します。ロングポジションについてです。すでに Test_Up については述べました。Timeframe_Up パラメータの意味は明らかです。Money_Management_Up も前に説明済みです。Length_Up パラメータについて分析します。このパラメータの意味は単純移動平均の「期間」パラメータの類似体です。このパラメータは1から無限の値を取ります。パラメータの上を150より大きく設定することには何の意味もありません。ほとんどの場合、より高いタイムフレームに移動するのはたやすいことです。このパラメータの最適化中には、パラメータが大きいほど、安定し、長期トレンドが JFATL インディケータによって検出されることに注意が必要です。よって、このトレーディングシステムには、パラメータ Length_Up と STOPLOSS_Up および TAKEPROFIT_Up 間に明らかな依存性はないのです。理論的には、ストップロスとテイクプロフィットは直接 Length_Up によって決定されます。もちろん、Length_Up に関係なく注文を出すことはできますが、そのような場合には、トレーディングシステムの収益性はシステムの特性ではなく、このトレーディングシステムでは決めることのできない現在の市場状況によって左右されるのです。この意見の意味するところは以下の例で理解できます。以下のパラメータを持つ EUR/USD についてこの EA を最適化する間、EA の検証からひじょうに良い結果を得ようとしているとします。

extern bool   Test_Up = true;
extern int    Timeframe_Up = 1;
extern double Money_Management_Up = 0.1;
extern int    Length_Up = 4;
extern int    Phase_Up = 100;
extern int    IPC_Up = 0;
extern int    STOPLOSS_Up = 100;
extern int    TAKEPROFIT_Up = 200;
extern bool   ClosePos_Up = false;

実は、Length_Up が 4 の JFATL はひじょうに速いトレンドのインディケータなのです。分足チャートと組み合わされ、そこでが動作するこれは、そのようなシステムに10から15ものポイントで変化する価格スケールを修正する可能性を提供します。そのような大きなストップロス値とテイクプロフィット値を持つ傑出した検証結果が、最適化中市場が強いトレンドを経験したという唯一の事実を示しているのです。これはシステム自体は検出しないことです。そのため、これらパラメータのアップロード後、EA がそのような良い結果を出すことはまずない、ということを理解する必要があります。ただし、なにか別のツールを使って市場に強いトレンドが存在することを検出できるなら、そのようなパラメータを使用するのが良いでしょう。

変数 Phase_Up の値範囲は -100~ +100 で変化します。この値が -100 のとき、JFATL 移動の一時プロセスは最小限の特性となりますが、私の経験では最良の最適化結果は値が +100 のとき得られました。IPC_Up 変数は JFATL アルゴリズムによってのちに処理するのにどの価格が使用されるかを決定します。変数 ClosePos_Up を使用することで、オープンしているポジションに逆らうトレンドが始まると、ポジションを強制的にクローズすることができます。テイクプロフィットとストップロスが市場からかけ離れて出されている場合、「移動」シグナルでポジションはクローズし、ClosePos_Up の値が「真」であれば、TP と SL はトレードに影響を与えないことに考慮が必要です。



おわりに

ここで私の説明は終わりです。本稿の末尾で語られた最適化トピックは話題として大きすぎます。なので、残りは次稿で説明しようと思います。第 1 稿の主な目的は、読者のみなさんに Expert Advisor を書く私独自の方法をお見せすることと、 EA を書く経験がほとんどなくても、EA コードを構築する簡単でユニバーサルな方法を提供することです。このタスクは本稿で達成されたと思います。次稿では、最適化結果を分析することの特徴の一部を説明し、トレーディングシステムのためにその一つを提供しようと思います。例として本稿に出した EA については、シンプルすぎて完全な値を持つ自動売買ではほとんで使い物にならないことに留意ください。ただ、トレーダーがクライアントターミナルを離れる場合の作業ツールとして、個別のトレード処理を自動化するのにはかなり便利なものです。