MQL 4 で信頼性ある安全な売買ロボットを作成する方法

Shashev Sergei | 17 2月, 2016


はじめに

本格的なプログラムを作成する過程ではいつも、開発者はそのプログラムにはあらゆる可能なまた不可能なエラーが出現する状況に直面しうるものです。開発段階でエラーは多大な問題を引き起こし、ソリューションの信頼性をなくすことにつながり、それが売買ロボットであれば、資金を失いかねません。もっとも一般的なエラー、その原因、エラーの検出手法とプログラム処理を分析します。クライアントターミナル MetaTrader 4 向け Expert Advisor の開発と使用の過程において、次のようなエラーが発生する可能性があります。

  1. 構文 – コンパイル段階で発見され、プログラマーが簡単に修正することができます。
  2. 理論 – コンパイラでは検出できません。例は次のようなものです。:変数名の混乱、誤った関数呼び出し、さまざまなタイプのデータ処理、等。
  3. アルゴリズム関連 – 括弧が正しく配置されていない場合に発生します。分岐表記の混乱など。
  4. クリティカル – ありそうにないエラーです。それらを発生させるにはいくばくかの労力が必要です。にもかかわらず dll の作業時に頻発します。
  5. トレーディング – 注文を処理する際に発生するエラーです。このようなエラーは売買ロボットにとっての弱点です。
まず、実行エラーに関してはドキュメンテーションを詳しく見ることをお薦めします。一度これを行うと、後の大幅時間節約となります。トレード処理により発生するエラーについてはこちらに説明があります。

構文エラー

このタイプのエラーは処理、変数、異なる関数呼び出しの誤字によって起こります。コンパイル時、プログラムコードがチェックされ、構文エラーはすべての『ツール』ウィンドウに表示されます。実際、ほとんどすべてのエラーが検出され、プログラマーによって修正可能です。

例外は、括弧の混乱です。コンパイル時誤った箇所に入れられた開く/閉じるカッコが検出されますが、エラーの位置は誤った方法で表示されます。そしてエラーを視覚的に見つけることができるようにコードをダブルチェックする必要がありますが、それは残念ながらうまくいかない可能性があります。2番目の方法はコメントを使用してコードのブロックを連続してシャットダウンすることです。この場合、新しいブロックにコメントを入れた後エラーが消えると、それは明らかにこのコメント付ブロックに入れられます。それにより検索領域は大幅に狭まり、括弧の誤った配置を素早くみつけるのに役立ちます。


ロジカル、アルゴリズム、クリティカルなエラー

このタイプでもっとも一般的なエラーは変数名と変数タイプの混乱で、Expert Advisor の分岐におけるアルゴリズムエラーです。たとえば、以下のコードを詳しく見ましょう。

bool Some = false;
 
void check()
  {
    // Much code
    Some = true;
  }
// Very much code
int start()
  {
    bool Some = false;
    //
    if(Some)   
      {
        //sending the order
      }
   return(0);
  }
 

ここで判ることは何でしょうか?理論変数 "Some" は、プログラム全体に共通しており、ポジションオープンに重要なインディケータですが、これが誤って低く設定されています。それがオーダーオープンに誤りを生じ、そのため損失を招くことになるのです。変数には多くの名前を付けることができます。ただ、なんらかの理由で大きなプログラムではこういった名前が誤って繰り返され、そのために上述のような問題につながるのです。

この種のエラーは、変数が混同されているとき、またはあるタイプの式が別のタイプの式に割り当てられているときに発生します。たとえば、以下の行です。

int profit = NormalizeDouble(SomeValue*point*2 / 3, digit);

"int" タイプの変数に "double" タイプの値式を割り当てようとしており、それはゼロ値となります。そしてテイクプロフィットレベルを計算しています!この類のエラーは誤ったトレーディングにつながります。


Expert Advisor の分岐におけるアルゴリズムエラーは括弧がアルゴリズムに従って配置されていない、または "else" 演算子によって 'if" 演算子の範囲が誤ることを意味します。結果、Expert Advisor を取得しますが、それは技術的要件に従ったものではありません。

エラーの中には解りにくいものがあり、見つけるには「コード上で瞑想にふけり」何時間も費やす可能性があります。残念ながら、C++ 族の言語環境とは異なり、MetaEditor では変数値を追跡する機能はありません。そのため、Print() 関数によってメッセージの出力を通じてエラーを追跡するしかないのです。

関数は GetLastError() エラーコードを返します。最終値は、プログラムの脆弱な可能性のある箇所を一つずつあたった後に確認することをお薦めします。エラーコードによって、ドキュメンテーション内にその説明を簡単に見つけることができ、エラーによっては対処方法すらわかるものもあります。

前述のエラーはおそらくデモアカウント使用前、検証段階で検出されるため、そういったエラーによる損失はありそうにありません。

クリティカルエラーの主な特徴は、そういったエラーが発生するとき、プログラムの実行が即停止することです。それでもエラーコードは定義済み変数 "last_error" 内で変わらないままです。そのため、関数 GetLastError() を呼んでエラーコードを調べることができます。


トレーディングエラー

このエラーにより損失を招いたり、デモアカウント、ひいては実アカウントで Expert Advisor が操作できなくなることがよくあります。こういったエラーはオーダーを送信したり変更する際、すなわち、トレードサーバーとの交信時に発生します。
以下のようなシンプルな処理

ticket = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3,
         Bid + StopLoss*Point, Bid - TakeProfit*Point, 0, MAGICMA, 
         0, Red);
if(ticket > 0) 
  {
    err = GetLastError();
    Print("While opening the order the error #  occured", err);
  }

は役立ちません。オーダーがサーバーに送信されていないことを確認し、エラーコードを調べました。それでどのようなことなのでしょうか?マーケットへの重要なエントランスを逃しました。もちろん、収益性ある Expert Advisor を持つなら。

次のような無限ループを持つバリアント


while (true)
  {
    ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, slippage,
             Bid + StopLoss*Point, Bid - TakeProfit*Point, 0, 
             MAGICMA, 0, Red);
    if(ticket > 0) 
      {
        err = GetLastError();
        Print("While opening the order the error #  occured", err);
        break;      
      }
    Sleep(1000);
    RefleshRates();
  }

はやや役に立ちます。オーダーがサーバーに到達する可能性は増えるでしょう。しかし、いくらかの問題に出会います。

  1. ブローカーが頻繁な依頼を好まない。
  2. このエラーは致命的で、この場合、依頼はいずれにしてもサーバーには届きません。
  3. 長時間 Expert Advisor が応答しない。
  4. サーバーはトレード依頼をまったく受け付けていない可能性があります。週末、祝日、メンテナンス作業等でしょう。

ほとんどどのエラーも特殊で、個別対応を必要とします。それでは、Switch オペレータによるバリアントについてお話し、エラーをそれぞれ多かれ少なかれ個別にときほぐします 。標準的なエラー No.146 - 「トレードフローが混みなっています」は TradeContext.mqh ライブラリ内のセマフォを用いて処理されます。本稿内にライブラリおよびその詳細説明があります。

//The library for differentiation of work with the trading flow
//written by komposter
#include <TradeContext.mqh>
 
//parameters for the signals
extern double MACDOpenLevel=3;
extern double MACDCloseLevel=2;
extern double MATrendPeriod=26;
 
// maximum acceptable slippage
int       slippage = 3;
//total number of transactions
int deals = 0;
//time for the pause after transaction
int TimeForSleep = 10;
//period of request
int time_for_action = 1;
//number of tries of opening/closing the position
int count = 5;
//indicator of operability of the EA
bool Trade = true;
//indicator of availability of funds for opening the position
bool NoOpen = false;
//+------------------------------------------------------------------+
//| Do not ask the server for quotes on weekends                    |
//+------------------------------------------------------------------+
bool ServerWork()
  {
   if(DayOfWeek() == 0 || DayOfWeek() == 6)
       return(false);
   return(true);
  }
//+------------------------------------------------------------------+
//| Generation of magik                                                  |
//+------------------------------------------------------------------+ 
int GenericMagik()
  {
   return(deals);
  }
//+------------------------------------------------------------------+   
//| Closing of transactions                                                  |
//+------------------------------------------------------------------+
bool CloseOrder(int magik)
  {
   int ticket,i;
   double Price_close;
   int err;
   int N;
//Function tries to shut the server at count attempts, if it fails,
//it gives an error message to the logfile
   while(N < count)
     {
       for(i = OrdersTotal() - 1; i >= 0; i--)
         {
           if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
               if(OrderSymbol() == Symbol())
                   if(OrderMagicNumber() == magik)
                     {
                       if(OrderType() == OP_BUY)
                           Price_close = NormalizeDouble(Bid, Digits);
                       if(OrderType() == OP_SELL)
                           Price_close = NormalizeDouble(Ask, Digits);
                       if(OrderClose(OrderTicket(), OrderLots(),
                          Price_close,slippage))
                         {
                           //reduce the number of transactions for the EA
                           deals--;
                           //the piece of the margin became available - you can open again
                           NoOpen = false;
                           return(true);
                         }
                         //we have reached this place, it means that the order has not been sent
                       N++;
                       //processing of possible errors
                       err = ErrorBlock();
                       //if the error is seriuos
                       if(err > 1)
                         {
                           Print("Manual closing of the order #  needed",
                                 OrderTicket());
                           return(false);
                         }
                     }
         }
        // taking a pause of 5 seconds and trying to close the transaction again
       Sleep(5000);
       RefreshRates();
     }
    //if we have reached this place, the transaction was not closed at count attempts 
   Print("Manual closing of the order #  needed",OrderTicket());
   return(false);
  }
//+------------------------------------------------------------------+
//|Tranaction for act 1-buy, 2-sell, the second parameter - the number of lots      |
//+------------------------------------------------------------------+ 
int Deal(int act, double Lot)
  {
   int N = 0;
   int ticket;
   int err;
   double Price_open;
   double Lots;
   int cmd;
   int magik;
   magik = GenericMagik();
   Lots = NormalizeDouble(Lot,1);
   if(act == 1)
     {
       Price_open = NormalizeDouble(Ask, Digits);
       cmd = OP_BUY;
     }
   if(act == 2)
     {
       Price_open = NormalizeDouble(Bid, Digits);
       cmd = OP_SELL;
     }
   //checking the margin for opening the position
   AccountFreeMarginCheck(Symbol(), cmd,Lots);
   err = GetLastError();
   if(err>0)
     {
       Print("No money for new position");
       NoOpen = true;
       return(0);
     }      
//Sending the order                  
   ticket = OrderSend(Symbol(), cmd, Lots, Price_open, slippage,
                      0, 0, 0, magik);
   if(ticket > 0)
     {
       deals++;
       return(ticket);
     }
//If the order has not been sent, we will try to open it 5 times again      
   else
     {
       while(N < count)
         {
           N++;
           err = ErrorBlock();
           if(err == 1)
             {
               Sleep(5000);
               RefreshRates();
               if(act == 1)
                   Price_open = NormalizeDouble(Ask, Digits);
               if(act == 2)
                   Price_open = NormalizeDouble(Bid, Digits);
               ticket = OrderSend(Symbol(), cmd, Lots, Price_open,
                                  slippage, 0, 0, 0, magik);
               if(ticket > 0)
                 {
                   deals++;
                   return(ticket);
                 }
             }
           // we have got a serious error  
           if(err > 1)
               return(0);
         }
     }
   return(0);
  }
//+------------------------------------------------------------------+
//| // 0-no error, 1-need to wait and refresh, 2-transaction rejected,   |
//|    3-fatal error                                            |
//+------------------------------------------------------------------+
//Block of the error control 
int ErrorBlock()
  {
   int err = GetLastError();
   switch(err)
     {
       case 0: return(0);
       case 2:
         {
           Print("System failure. Reboot the computer/check the server");
           Trade = false;
           return(3);
         }
       case 3:
         {
           Print("Error of the logic of the EA");
           Trade = false;
           return(3);
         }
       case 4:
         {
           Print("Trading server is busy. Wait for 2 minutes.");
           Sleep(120000);
           return(2);
         }
       case 6:
         {
           bool connect = false;
           int iteration = 0;
           Print("Disconnect ");
           while((!connect) || (iteration > 60))
             {
               Sleep(10000);
               Print("Connection not restored", iteration*10,
                     "  seconds passed");
               connect = IsConnected();
               if(connect)
                 {
                   Print("Connection restored");
                   return(2);
                 }
               iteration++;
             }
           Trade = false;
           Print("Connection problems");
           return(3);
         }
       case 8:
         {
           Print("Frequent requests");
           Trade = false;
           return(3);
         }
       case 64:
         {
           Print("Account is blocked!");
           Trade = false;
           return(3);
         }
       case 65:
         {
           Print("Wrong account number???");
           Trade = false;
           return(3);
         }
       case 128:
         {
           Print("Waiting of transaction timed out");
           return(2);
         }
       case 129:
         {
           Print("Wrong price");
           return(1);
         }
       case 130:
         {
           Print("Wrong stop");
           return(1);
         }
       case 131:
         {
           Print("Wrong calculation of trade volume");
           Trade = false;
           return(3);
         }
       case 132:
         {
           Print("Market closed");
           Trade = false;
           return(2);
         }
       case 134:
         {
           Print("Lack of margin for performing operation");
           Trade = false;
           return(2);
         }
       case 135:
         {
           Print("Prices changed");
           return (1);
         }
       case 136:
         {
           Print("No price!");
           return(2);
         }
       case 138:
         {
           Print("Requote again!");
           return(1);
         }
       case 139:
         {
           Print("The order is in process. Program glitch");
           return(2);
         }
       case 141:
         {
           Print("Too many requests");
           Trade = false;
           return(2);
         }
       case 148:
         {
           Print("Transaction volume too large");
           Trade = false;
           return(2);
         }
     }
   return (0);
  }
//+------------------------------------------------------------------+
//| generation of signals for opening/closing position on Macd       |
//+------------------------------------------------------------------+
int GetAction(int &action, double &lot, int &magik)
   {
   double MacdCurrent, MacdPrevious, SignalCurrent;
   double SignalPrevious, MaCurrent, MaPrevious;
   int cnt,total;
   
   MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
   MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
   SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
   SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
   MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
   MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
   
  if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
         MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious)
      {
         action=1;
         lot=1;
         return (0);
      }
  if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
         MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious)
      {
         action=2;
         lot=1;
         return (0);
      }
   total=OrdersTotal();
   for(cnt=0;cnt<total;cnt++)
     {
      OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if(OrderType()<=OP_SELL &&   // check for opened position 
         OrderSymbol()==Symbol())  // check for symbol
        {
         if(OrderType()==OP_BUY)   // long position is opened
           {
            // should it be closed?
            if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
               MacdCurrent>(MACDCloseLevel*Point))
                {
                 action=3;
                 magik=OrderMagicNumber();
                 return(0); // exit
                }
           }
         else // go to short position
           {
            // should it be closed?
            if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&
               MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
              {
               action=3;
               magik=OrderMagicNumber();
               return(0);
              }
           }
        }
     }
   }
//+------------------------------------------------------------------+
//| The EA initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
    if(!IsTradeAllowed())
      {
        Print("Trade not allowed!");
        return(0);
      }
  }
//+------------------------------------------------------------------+
//| The EA deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//Closing all orders
   for(int k = OrdersTotal() - 1; k >= 0 ; k--)
       if(OrderSymbol() == Symbol())
         {
           if(OrderType() == OP_BUY)
              OrderClose(OrderTicket(), OrderLots(),
                         NormalizeDouble(Bid,Digits), 10);
           if(OrderType() == OP_SELL)
               OrderClose(OrderTicket(), OrderLots(),
                          NormalizeDouble(Ask, Digits),10);
         }
  } 
//+------------------------------------------------------------------+
//| The EA start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   int action =0;
   double lot = 1;
   int magik = 0;
   while(Trade)
     {
       Sleep(time_for_action*1000);
       RefreshRates();
       /*Logic of the EA where the we calculate the action, position limit and magik for closing the order
       action 1-buy, 2-sell, 3-close
       for example, take the EA on Macd*/
       GetAction(action,lot,magik);
       if(ServerWork())
         {
           if(((action == 1) || (action == 2)) && (!NoOpen))
             {
               if(TradeIsBusy() < 0)
                   return(-1);
               Deal(action, lot);
               Sleep(TimeForSleep*1000);
               TradeIsNotBusy();
             }
           if(action == 3)
             {
               if(TradeIsBusy() < 0)
                 {
                   return(-1);
                   if(!CloseOrder(magik))
                       Print("MANUAL CLOSE OF TRANSATION NEEDED");
                   Sleep(TimeForSleep*1000);
                   TradeIsNotBusy();
                 }
             }
         }
       else
         {
            Print("Weekends");
            if(TradeIsBusy() < 0)
                  return(-1);
            Sleep(1000*3600*48);
            TradeIsNotBusy();
         }
       action = 0;
       lot = 0;
       magik = 0;
     }
   Print("Critical error occured and the work of the EA terminated");
   return(0);
  }
//+------------------------------------------------------------------+


売買ロボットの本バージョンは無限ループ内で動作します。作成済みのマルチ通貨対応 Expert Advisor をスキャルピングする際、需要が発生します。EA を操作するアルゴリズムは以下です。

1. 解析ブロック GetAction() からシグナルを取得します。
2. 関数 Deal() および CloseOrder() 内で必要なトランザクションを行います。
3. 深刻な障害がなければ、短いポーズ time_for_action の後、ポイント 1 に戻ります。

解析ブロックからシグナル(売り、買い、クローズ)を取得したら、Expert Advisor はトレードフローを遮断し(記事を読みます)、トランザクションを行おうとします。その後、数秒間のポーズを取って、トレードフローを別のEA に解放します。Expert Advisor は "count" 回数を越えないオーダーを送信しようとします。再クオートを取得する非定常市場ではオーダーを渡すにはそれで十分です。オーダー送信中に深刻なエラーが発生すれば、Expert Advisor は機能を停止します。なんらかの問題が発生した場合、"Expert Advisors" フォルダにエラーメッセージが表示されます。エラーが重大でない場合、Expert Advisor は動作を続けます。

エラーは次のスキームに従って ErrorBlock() プロシージャ内で処理されます。:プロシージャがエラーコードを取得し、それを処理する短いアルゴリズムを提供します。エラーのほとんどは、ログ内メッセージです。エラーが重大であれば、トレードインディケータ Trade およびNoOpen が変わります。接続障害の場合、状況処理はやや複雑です。ロボットは定義済み定期的シーケンスによって 60 回サーバーに到達しようとします。サーバーに到達しなければ、おそらく重大な問題があるため、しばらくトレードをストップする必要があります。トレードに与えるエラーの影響によって、処理アルゴリズムは異なる意味を返します。

0 - エラーなし
1 - エラーは市場の非安定性に関連しています。もう一度オーダーを送信してみることが可能です。
2 - オーダー送信中に重大なエラーが発生しました。しばらくポジションオープンを控えます。
3 - の重大な障害、接続障害。- 状況が判明するまでトレードを控えます。

おわりに

構文、アルゴリズム、ロジカルエラーはアルゴリズムのコード化にあまり注意を払っていないときに起こります。こういったエラーは確認、ログ内変数値検証で修正します。これらはコンパイル、Expert Advisor 検証段階で検出されるものです。その類のエラーは長期間存在することはなく、デモアカウント使用前には修正されます。

トレーディングエラーはサーバーへのオーダー送信中に発生します。それらは再クオート、スリッページがあり、ディーラーがスキャルピングや機器の故障に奮闘する実トレードに関連しています。そういったエラーは予測がつきませんが、対応可能です。それらには、Expert Advisor のロジック、トランザクション頻度、オーダー変更に応じて、毎週個別に対処する必要があります。

Expert Advisor の操作中に発生する誤りは処理が必要です。それは簡単な作業ではありません。それは EA の複雑さと機能によります。本稿では、このタスクを行う Expert Advisor の典型的パターンを確認することができます。より安心・安全なトレーディングシステムを作成するには長い時間がかかります。が、問題のない自動売買システムの開発に費やす時間は、資金の安全と安眠という何倍もの恩恵をもたらすことになるのです。