English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
Expert Advisorの限界と検証

Expert Advisorの限界と検証

MetaTrader 5 | 13 8月 2015, 10:40
1 439 0
MetaQuotes
MetaQuotes

はじめに

自動トレーディングのアルゴリズムを作成する時、トレードシグナルを作るために価格を処理するだけでなく、Expert Advisorの操作の限界について補助情報をたくさん入手できるべきです。本記事でその方法を紹介します。

  • トレーディングセッション情報の入手;
  • ポジションをオープンするのに十分な資産があるか確認;
  • シンボルによる総トレーディングボリュームに限界をもうける;
  • 総オーダー数をに限界をもうける;
  • エントリー価格とストップロスの間の潜在的ロスを計算;
  • 新しいバーがあるか確認。


トレーディングセッションと気配値セッション

トレーディング セッションについて情報を得るには、SymbolInfoSessionTrade()関数、気配値セッションでは対応する SymbolInfoSessionQuote() 関数を使うべきです。両方の関数は同じように働きます。指定の曜日の指定インデックスによるセッションがある場合 (セッションのインデックスはゼロからスタート)、関数は trueに戻ります。セッション開始と終了時間はリンクによって渡される4番目と5番目パラメータに書かれます。

//--- check if there is a quotation session with the number session_index
bool session_exist=SymbolInfoSessionQuote(symbol,day,session_index,start,finish);
指定日のすべてのセッションを見つけるには、この関数がfalseに戻るまでループで呼び出します。
//+------------------------------------------------------------------+
//|  Display information about quotation sessions                    |
//+------------------------------------------------------------------+
void PrintInfoForQuoteSessions(string symbol,ENUM_DAY_OF_WEEK day)
  {
//--- start and end of session
   datetime start,finish;
   uint session_index=0;
   bool session_exist=true;

//--- go over all sessions of this day
   while(session_exist)
     {
      //--- check if there is a quotation session with the number session_index
      session_exist=SymbolInfoSessionQuote(symbol,day,session_index,start,finish);

      //--- if there is such session
      if(session_exist)
        {
         //--- display the day of week, the session number and the time of start and end
         Print(DayToString(day),": session index=",session_index,"  start=",
               TimeToString(start,TIME_MINUTES),"    finish=",TimeToString(finish-1,TIME_MINUTES|TIME_SECONDS));
        }
      //--- increase the counter of sessions
      session_index++;
     }
  }

ENUM_DAY_OF_WEEK列挙値をパラメータとして受け取るカスタム関数 DayToString()を使って曜日が文字列形式で表示されています。

//+------------------------------------------------------------------+
//| Receive the string representation of a day of week               |
//+------------------------------------------------------------------+
string DayToString(ENUM_DAY_OF_WEEK day)
  {
   switch(day)
     {
      case SUNDAY:    return "Sunday";
      case MONDAY:    return "Monday";
      case TUESDAY:   return "Tuesday";
      case WEDNESDAY: return "Wednesday";
      case THURSDAY:  return "Thursday";
      case FRIDAY:    return "Friday";
      case SATURDAY:  return "Saturday";
      default:        return "Unknown day of week";
     }
   return "";
  }

SymbolInfoSession.mq5 スクリプトの最終コードは本記事の下に添付されています。ここでは主要部分だけ表示しましょう。

void OnStart()
  {
//--- the array where the days of week are stored
   ENUM_DAY_OF_WEEK days[]={SUNDAY,MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY};
   int size=ArraySize(days);

//---
   Print("Quotation sessions");
//--- go over all the days of week
   for(int d=0;d<size;d++)
     {
      PrintInfoForQuoteSessions(Symbol(),days[d]);
     }

//---
   Print("Trading sessions");
//--- go over all the days of week
   for(int d=0;d<size;d++)
     {
      PrintInfoForTradeSessions(Symbol(),days[d]);
     }
  }


マージンの確認

ポジションのオープンまたは増加に必要なマージン量を確認するには、OrderCalcMargin() 関数を使うことができます。それに渡される最初のパラメータは ENUM_ORDER_TYPE 列挙からの値です。買いでは ORDER_TYPE_BUY パラメータ、売りには ORDER_TYPE_SELL パラメータを使って呼び出すべきです。この関数は、ロットの数と始値に応じてマージン量を戻します。

void OnStart()
  {
//--- the variable to receive the value of margin
   double margin;
//--- to receive information about the last tick
   MqlTick last_tick;
//--- try to receive the value from the last tick
   if(SymbolInfoTick(Symbol(),last_tick))
     {
      //--- reset the last error code
      ResetLastError();
      //--- calculate margin value
      bool check=OrderCalcMargin(type,Symbol(),lots,last_tick.ask,margin);
      if(check)
        {
         PrintFormat("For the operation %s  %s %.2f lot at %G required margin is %.2f %s",OrderTypeToString(type),
                     Symbol(),lots,last_tick.ask,margin,AccountInfoString(ACCOUNT_CURRENCY));
        }
     }
   else
     {
      Print("Unsuccessful executio the SymbolInfoTick() function, error ",GetLastError());
     }
  }

OrderCalcMargin() 関数はマーケットオーダーだけでなくペンディングオーダのマージン値も計算できることに注意します。Check_Money.mq5 スクリプトを使って、全てのオーダータイプにおいて戻された値を確認できます。

トレーディングシステムによってはペンディングオーダーにもマネーバックが求められるかもしれないのでOrderCalcMargin() 関数はペンディングオーダーのマージンサイズを計算することを目的としています。 通常、 ペンディングオーダーのマージンサイズは長期・短期ポジションのマージンサイズの係数を通して計算されます。

識別子

説明

プロパティのタイプ

SYMBOL_MARGIN_LONG

長期ポジションにかかるマージンレート

ダブル

SYMBOL_MARGIN_SHORT

短期ポジションにかかるマージンレート

ダブル

SYMBOL_MARGIN_LIMIT

リミットオーダーにかかるマージンレート

ダブル

SYMBOL_MARGIN_STOP

ストップオーダーで変わるマージンレート

ダブル

SYMBOL_MARGIN_STOPLIMIT

ストップリミットオーダーにかかるマージンレート

ダブル

これらの係数値はシンプルなコードを使って得られます。

//--- Calculate the rates of margin charging for different types of orders
   PrintFormat("Rate of margin charging on long ポジション is equal to %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_LONG));
   PrintFormat("Rate of margin charging on short ポジション is equal to %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_SHORT));
   PrintFormat("Rate of margin charging on Limit orders is equal to %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_LIMIT));
   PrintFormat("Rate of margin charging on Stop orders is equal to %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_STOP));
   PrintFormat("Rate of margin charging on Stop Limit orders is equal to %G",SymbolInfoDouble(Symbol(),SYMBOL_MARGIN_STOPLIMIT));

Forexシンボルでは、 ペンディングオーダーにかかるマージンレートは0に等しいです。つまり、それらにはマージン要求がありません。

Check_Money.mq5スクリプト実行の結果

Check_Money.mq5スクリプト実行の結果

マージンのかかり方に応じ、マネー管理システムが変わるかもしれません。また、もしペンディングオーダーにマージンが要求されている場合、トレーディングシステム自身がある限界を経験するかもしれません。このためこれらのパラメータがExpert Advisor操作の自然の制限であるかもしれません。


潜在的利益と損失の管理

保護ストップレベルを置くとき、その起動に対して準備ができているべきです。マネーの観点から潜在的損失のリスクを考慮に入れるべきです。そしてOrderCalcProfit()はこれを目的としています。すでに考察したOrderCalcMargin() 関数にとても似ていますが、それは計算に始値と終値の両方を必要とします。

ENUM_ORDER_TYPE列挙の2つの値の内1つ値を最初のパラメータ ORDER_TYPE_BUYまたはORDER_TYPE_SELLとして指定します。 他のタイプのオーダーはエラーになります。最後のパラメータでは、レファレンスを使って、正常な実行の場合、OrderCalcProfit() 関数が利益・損失値を書くところへ変数を渡すべきです。

入るときと抜け るときの指定レベル付きの長期ポジションをクローズする時における、利益または損失を計算するCalculateProfitOneLot() 関数の使用例:

//+------------------------------------------------------------------+
//| Calculate potential profit/loss for buying 1 lot                 |
//+------------------------------------------------------------------+
double CalculateProfitOneLot(double entry_price,double exit_price)
  {
//--- receive the value of profit to this variable
   double profit=0;
   if(!OrderCalcProfit(ORDER_TYPE_BUY,Symbol(),1.0,entry_price,exit_price,profit))
     {
      Print(__FUNCTION__,"  Failed to calculate OrderCalcProfit(). Error ",GetLastError());
     }
//---
   return(profit);
  }

この関数の計算結果は図に表されています。

OrderCalcProfit() 関数を使った潜在的損失の計算とチャート表示例

全体のコードは添付Expert Advisor CalculateProfit_EA.mq5にあります。


新しいバーがあるか確認

多くのトレーディングシステムの開発は、新しいバー が現れた時トレード シグナルが計算され、全てのトレードアクションが一度だけ実行されることを想定しています。 MetaTrader 5 クライアントターミナルのストラテジーテスターの「Only open prices」 モードはこのような自動トレーディング システムを確認するの良いです。

「Open prices only」モードでは、全てのインディケーター計算とExpert Advisor のOnTick() 関数の呼び出しはテスト中各バーにおいて一度だけ実行されます。これは速めのトレーディングモードで、 概して、僅少価格振動に対して最も耐障害性あるトレーディング システム作成方法です。 同時に、もちろん, Expert Advisorで使用されているインディケーターは正しく書かれ、新しいバーが来た時、値をゆがめるべきではありません。

ストラテジーテスター の「Open prices only」モードは、Expert Advisorが各バーにつき一度だけ起動するか確認せずに済むようになっており、それはとても便利です。しかし、デモまたは正式アカウントにおいてリアルタイムモードで作業する間、トレーダーはそれらExpert Advisorの活動をコントロールし、トレード操作 が1つのシグナル受信毎に一回だけ実行するようにするべきです。その一番な簡単な方法は現在の未形成バーのオープニングを追跡することです。

最後のバーのオープニング時間を手に入れるには、シンボルの指定の名前・タイムフレーム・ SERIES_LASTBAR_DATE プロパティ付きでSeriesInfoInteger() 関数を使うべきです。一つのバーが変数に保存された状態で現在のバーのオープニング時間を常に比較していると、新しいバーが現れた瞬間を簡単に検知することができます。そのことで以下のように見えるカスタム関数 isNewBar() を作成することができます。

//+------------------------------------------------------------------+
//| Return true if a new bar appears for the symbol/period pair      |
//+------------------------------------------------------------------+
bool isNewBar()
  {
//--- remember the time of opening of the last bar in the static variable
   static datetime last_time=0;
//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set time and exit
      last_time=lastbar_time;
      return(false);
     }

//--- if the time is different
   if(last_time!=lastbar_time)
     {
      //--- memorize time and return true
      last_time=lastbar_time;
      return(true);
     }
//--- if we pass to this line then the bar is not new, return false
   return(false);
  }

この関数の使用例は添付Expert Advisor CheckLastBar.mq5にあります。

M1タイムフレームにおける新しいバー登場についてのCheckLastBar Expert Advisorのメッセージ

M1タイムフレームにおける新しいバー登場についてのCheckLastBar Expert Advisorのメッセージ


ペンディングオーダー数の制限

アカウントに同時にプレースすることができるアクティブペンディングオーダーの数を制限する必要がある場合、 自分のカスタム関数を書くことができます。別のペンディングオーダーをプレースできるか確認する IsNewOrderAllowed()を作りましょう。Automated Trading Championshipのルールに準拠して書きましょう。

//+------------------------------------------------------------------+
//| Checks if it is allowed to place another order                   |
//+------------------------------------------------------------------+
bool IsNewOrderAllowed()
  {
//--- get the allowed number of pending orders on an account
   int max_allowed_orders=(int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);

//--- if there is no limitations, return true; you can send an order
   if(max_allowed_orders==0) return(true);

//--- if we pass to this line, then there are limitations; detect how many orders are already active
   int orders=OrdersTotal();

//--- return the result of comparing
   return(orders<max_allowed_orders);
  }

この関数はシンプルです。max_allowed_orders 変数に許容オーダー数を入手します。もし値がゼロに等しくない場合、 現在の オーダー数と比較します。しかし、 この関数は別の潜在的限界である指定シンボルによるオープンポジションとペンディングオーダーの総許容ボリュームにおける限界を考えません。


指定シンボルによるロット数の制限

指定シンボルによるオープンポジションのサイズを得るには、まずPositionSelect() 関数を使ってポジションを選択する必要があります。その後、やっとオープンポジションのボリュームをPositionGetDouble()を使って要求できます。 ダブルタイプの様々な選択ポジションのプロパティを戻します。シンボルによってポジションボリュームを得るためのPostionVolume() 関数を書きましょう。

//+------------------------------------------------------------------+
//| Returns the size of position by a specific symbol                |
//+------------------------------------------------------------------+
double PositionVolume(string symbol)
  {
//--- try to select a positions by a symbol
   bool selected=PositionSelect(symbol);
//--- the position exists
   if(selected)
      //--- return the position volume
      return(PositionGetDouble(POSITION_VOLUME));
   else
     {
      //--- report about the unsuccessful attempt to select the position
      Print(__FUNCTION__," Failed to execute PositionSelect() for the symbol ",
            symbol," Error ",GetLastError());
      return(-1);
     }
  }

シンボルでペンディング オーダーをプレースするトレード要求を作成する前、SYMBOL_VOLUME_LIMITのシンボルによるにオープンポジションとペンディングオーダーの総ボリュームの限界を確認するべきです。 もし制限がない場合、ペンディングオーダーのボリュームは、 SymbolInfoDouble() ボリュームを使って受け取られる最大許容ボリュームを超えることができません。

double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT);
if(max_volume==0) volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);

しかし、このアプローチはシンボル規定による現在のペンディングオーダーのボリュームを考えません。この値を計算する関数を書きましょう。

//+------------------------------------------------------------------+
//| Returns the size of position by a specified symbol               |
//+------------------------------------------------------------------+
double PositionVolume(string symbol)
  {
//--- try to select a position by a symbol
   bool selected=PositionSelect(symbol);
//--- the position exist
   if(selected)
      //--- return the position volume
      return(PositionGetDouble(POSITION_VOLUME));
   else
     {
      //--- return zero if there is no position
      return(0);
     }
  }

オープンポジションのボリュームとペンディングオーダーのボリュームを考慮し、最終確認は以下のように見えます。

//+------------------------------------------------------------------+
//|  Returns maximum allowed volume for an order by a symbol         |
//+------------------------------------------------------------------+
double NewOrderAllowedVolume(string symbol)
  {
   double allowed_volume=0;
//--- get the limitation on the maximum volume of an order
   double symbol_max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
//--- get the limitation of volume by a symbol
   double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT);

//--- get the volume of open position by a symbol
   double opened_volume=PositionVolume(symbol);
   if(opened_volume>=0)
     {
      //--- if we already used available volume
      if(max_volume-opened_volume<=0)
         return(0);

      //--- volume of the open position doen't exceed max_volume
      double orders_volume_on_symbol=PendingsVolume(symbol);
      allowed_volume=max_volume-opened_volume-orders_volume_on_symbol;
      if(allowed_volume>symbol_max_volume) allowed_volume=symbol_max_volume;
     }
   return(allowed_volume);
  }

このセクションで言及した関数を含むCheck_Order_And_Volume_Limits.mq5 Expert Advisorのコード全体は本記事に添付されています。

自動トレーディングチャンピオンシップ 2010参加者のアカウントにおけるCheck_Order_And_Volume_Limits Expert Advisorを使った確認例。

自動トレーディングチャンピオンシップ 2010参加者のアカウントにおけるCheck_Order_And_Volume_Limits Expert Advisorを使った確認例。


ボリューム正確性の確認

トレーディングロボットの重要な点はトレード操作を実行するための正しいボリュームを選択できる事です。ここではマネー管理やリスクマネジメントのシステムではなく、対応するシンボルのプロパティに応じた正しいボリュームについて話していきます。

識別子

説明

プロパティのタイプ

SYMBOL_VOLUME_MIN

ディールの最小ボリューム

ダブル

SYMBOL_VOLUME_MAX

ディールの最大ボリューム

ダブル

SYMBOL_VOLUME_STEP

ディール実行の最小ボリューム変更ステップ

ダブル

 

こうような検証を行うには、カスタム 関数 CheckVolumeValue()を書くことができます。

//+------------------------------------------------------------------+
//|  Check the correctness of volume of an order                     |
//+------------------------------------------------------------------+
bool CheckVolumeValue(double volume,string &description)
  {
//--- Minimum allowed volume for trade operations
   double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   if(volume<min_volume)
     {
      description=StringFormat("Volume is less than the minimum allowed SYMBOL_VOLUME_MIN=%.2f",min_volume);
      return(false);
     }

//--- Maximum allowed volume for trade opertations
   double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
   if(volume>max_volume)
     {
      description=StringFormat("Volume is greater than the maximum allowed SYMBOL_VOLUME_MAX=%.2f",max_volume);
      return(false);
     }

//--- get the minimal volume change step
   double volume_step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP);

   int ratio=(int)MathRound(volume/volume_step);
   if(MathAbs(ratio*volume_step-volume)>0.0000001)
     {
      description=StringFormat("Volume is not a multiple of minimal step SYMBOL_VOLUME_STEP=%.2f, the closest correct volume is %.2f",
                               volume_step,ratio*volume_step);
      return(false);
     }
   description="Correct value of volume ";
   return(true);
  }

この関数の作動は、本記事に添付されたCheckVolumeValue.mq5 スクリプトを使って確認できます。

ボリュームの正しさをチェックするCheckVolumeValue.mq5 のメッセージ

ボリュームの正しさをチェックするCheckVolumeValue.mq5 のメッセージ


結論

本記事は自分自身の自動トレーディング システムを書く際に発生するかもしれないExpert Advisor動作の潜在的限界における基本的検証を説明しました。これらの例はトレードアカウントでExpert Advisor操作時に確認するべき潜在的条件をすべて網羅していません。しかしこれらの例が初めての方にとってMQL5 言語で最もよく使われる検証をどのように実行するか理解する手助けになればいいと願っています。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/22

MQL5でのオブジェクト作成と削除の順番 MQL5でのオブジェクト作成と削除の順番
MQL5プログラムでは、すべてのオブジェクトはカスタムオブジェクトだろうと、動的配列またはオブジェクト配列は特定の方法で作成され削除されます。 しばしば オブジェクトは他のオブジェクトの一部で、非初期化のオブジェクト削除の順は特に重要になります。本記事ではオブジェクトを使用するメカニズムの例を紹介します。
インディケーターを別のインディケーターに適用 インディケーターを別のインディケーターに適用
OnCalculate() 関数呼び出しショートフォームのインディケーターを書くとき、インディケーターが価格データだけでなく、他のインディケーターデータによっても計算できる事実を見逃すかもしれません。 (内蔵またはカスタムのインディケーターにかかわらず)。他のインディケーターデータに正しく適用するためにインディケーターを改善したいですか?本記事ではそのような修正に必要なステップを復習します。
初心者のためのMQL5: Expert Advisorでのテクニカルインディケーター使用ガイド 初心者のためのMQL5: Expert Advisorでのテクニカルインディケーター使用ガイド
Expert Advisorで内蔵またはカスタムインディケーターの値を入手するには、まずそのハンドラーを対応する関数を使って作成しなければなりません。本記事の例は、自分のプログラム作成時にどのようにテクニカルインディケーターを使うか説明します。本記事はMQL5 言語の内蔵のインディケーターを説明します。これはトレーディングストラテジー策定の経験が浅い人向けで、関数のライブラリを使ったシンプルで分りやすいインディケーターの使用法を紹介します。
MQL5: 自分のインディケーターの作成 MQL5: 自分のインディケーターの作成
インディケーターとは何でしょう?スクリーン上に便利な方法で表示したい計算値の一式です。値の一式は配列としてプログラムに表されます。そのため、インディケーターの作成は配列 (価格配列) を取り扱い、他の配列 (インディケーター 値)に結果を記録するアルゴリズムを書くことを意味します。True Strength Indexの作成について説明することで、MQL5でどのようにインディケーターを書くか説明します。