English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
MQL5 クックブック: マルチ通貨 Expert Advisor - シンプル、かしこい、迅速なアプローチ

MQL5 クックブック: マルチ通貨 Expert Advisor - シンプル、かしこい、迅速なアプローチ

MetaTrader 5 | 26 11月 2015, 07:46
1 088 0
Anatoli Kazharski
Anatoli Kazharski

はじめに

本稿ではマルチ通貨 Expert Advisorに適切なシンプルなアプローチの実装について述べます。これは理想的な条件下でありながら各シンボルに対して異なるパラメータでExpert Advisor を検証/トレーディングする設定を可能にするということです。例として2個のシンボルに対するパターンを作成しますが、コードに少し変更を加えるだけで必要に応じてそれ以外のシンボルも追加できるようにしておきます。

MQL5 にマルチ通貨パターンを実装する方法は数多くあります。

  • Expert Advisor が時間によって導かれるパターンを利用することができます。このパターンでは OnTimer() 関数で指定される時間間隔によってより正確な確認ができます。

  • またシリーズの先行記事で取り入れられているすべての Expert Advisor 同様、動作してい対象の現在シンボルに対するティックに依存する場合、確認はOnTick()関数で行われます。別のシンボルに完了したバーがあれば、一方現シンボルに対するティックがまだなくても Expert Advisor は現シンボルに対する新規ティックがあるか一度だけ確認を行います。

  • 著者 Konstantin Gruzdev (Lizar)によって提案される興味深いオプションがまだもう一つあります。それは次のようなイベントモデルを採用します。OnChartEvent() 関数を用いて Expert Advisor は検証/トレードに関わるシンボルチャート上にあるインディケータエージェントによって再生されるイベントを取得します。インディケータエージェントは新規バーとアタッチされるシンボルのティックイベントを再生します。この種のインディケータ(EventsSpy.mq5)は本稿末尾でダウンロードすることができます。それは Expert Advisorの処理に必要となります。


Expert Advisor の作成

記事 "MQL5 Cookbook: Using Indicators to Set Trading Conditions in Expert Advisors" で取り上げられている Expert Advisor はテンプレートの役目を果たします。私はすでにそれから情報パネルに関するものをすべて削除しました。また先行記事 "MQL5 Cookbook: Developing a Framework for a Trading System Based on the Triple Screen Strategy"で実装されているポジションオープンの条件を簡素化しました。2個のシンボルについて Expert Advisor を作成したいので、それぞれの外部パラメータセットが必要となります。

//--- External parameters of the Expert Advisor
sinput long   MagicNumber           = 777;      // Magic number
sinput int    Deviation             = 10;       // Slippage
//---
sinput string delimeter_00=""; // --------------------------------
sinput string Symbol_01             = "EURUSD"; // Symbol 1
input  int    IndicatorPeriod_01    = 5;        // |     Indicator period
input  double TakeProfit_01         = 100;      // |     Take Profit
input  double StopLoss_01           = 50;       // |     Stop Loss
input  double TrailingStop_01       = 10;       // |     Trailing Stop
input  bool   Reverse_01            = true;     // |     Position reversal
input  double Lot_01                = 0.1;      // |     Lot
input  double VolumeIncrease_01     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_01 = 10;       // |     Volume increase step
//---
sinput string delimeter_01=""; // --------------------------------
sinput string Symbol_02             = "NZDUSD"; // Symbol 2
input  int    IndicatorPeriod_02    = 5;        // |     Indicator period
input  double TakeProfit_02         = 100;      // |     Take Profit
input  double StopLoss_02           = 50;       // |     Stop Loss
input  double TrailingStop_02       = 10;       // |     Trailing Stop
input  bool   Reverse_02            = true;     // |     Position reversal
input  double Lot_02                = 0.1;      // |     Lot
input  double VolumeIncrease_02     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_02 = 10;       // |     Volume increase step

外部パラメータはサイズが使用されているシンボル数に依存する配列内に入れられます。Expert Advisor で使用されるシンボル数はファイルの冒頭で作成する必要のなる定数 NUMBER_OF_SYMBOLS の値によって決められます。

//--- Number of traded symbols
#define NUMBER_OF_SYMBOLS 2
//--- Name of the Expert Advisor
#define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME)

外部パラメータ格納に必要な配列を作成します。

//--- Arrays for storing external parameters
string Symbols[NUMBER_OF_SYMBOLS];            // Symbol
int    IndicatorPeriod[NUMBER_OF_SYMBOLS];    // Indicator period
double TakeProfit[NUMBER_OF_SYMBOLS];         // Take Profit
double StopLoss[NUMBER_OF_SYMBOLS];           // Stop Loss
double TrailingStop[NUMBER_OF_SYMBOLS];       // Trailing Stop
bool   Reverse[NUMBER_OF_SYMBOLS];            // Position reversal
double Lot[NUMBER_OF_SYMBOLS];                // Lot
double VolumeIncrease[NUMBER_OF_SYMBOLS];     // Position volume increase
double VolumeIncreaseStep[NUMBER_OF_SYMBOLS]; // Volume increase step

配列初期化関数はインクルードファイル InitArrays.mqh に入れます。Symbols[] 配列を初期化するために GetSymbol()関数を作成します。それは外部パラメータからシンボル名を取得し、そのシンボルがサーバーのシンボルリストで使用可能であれば銘柄ウィンドウで選択されます。それ以外、必要なシンボルがサーバー上に見つからなければこの関数は空の文字列を返し、Expert Advisors の「ジャーナル」が適切に更新されます。

以下は GetSymbol() 関数のコードです。

//+------------------------------------------------------------------+
//| Adding the specified symbol to the Market Watch window           |
//+------------------------------------------------------------------+
string GetSymbolByName(string symbol)
  {
   string symbol_name="";   // Symbol name on the server
//--- If an empty string is passed, return the empty string
   if(symbol=="")
      return("");
//--- Iterate over the list of all symbols on the server
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      //--- Get the symbol name
      symbol_name=SymbolName(s,false);
      //--- If the required symbol is available on the server
      if(symbol==symbol_name)
        {
         //--- Select it in the Market Watch window
         SymbolSelect(symbol,true);
         //--- Return the symbol name
         return(symbol);
        }
     }
//--- If the required symbol cannot be found, return the empty string
   Print("The "+symbol+" symbol could not be found on the server!");
   return("");
  }

Symbols[] 配列は GetSymbols() 関数内で初期化されます。

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void GetSymbols()
  {
   Symbols[0]=GetSymbolByName(Symbol_01);
   Symbols[1]=GetSymbolByName(Symbol_02);
  }

また、特定シンボルの外部パラメータの空の値が対応するブロックが検証/トレードの対象ではないことを示すように実装します。これは各シンボルに対し個別にパラメータを最適化し、同時に残りのシンボルを完全に除外するために必要です。

外部パラメータのその他配列はすべて同じ方法で初期化されます。すなわち、各配列に対して個別の関数を作成する必要があるのです。以下はこれらすべての関数のコードです。

//+------------------------------------------------------------------+
//| Filling the indicator period array                               |
//+------------------------------------------------------------------+
void GetIndicatorPeriod()
  {
   IndicatorPeriod[0]=IndicatorPeriod_01;
   IndicatorPeriod[1]=IndicatorPeriod_02;
  }
//+------------------------------------------------------------------+
//| Filling the Take Profit array                                    |
//+------------------------------------------------------------------+
void GetTakeProfit()
  {
   TakeProfit[0]=TakeProfit_01;
   TakeProfit[1]=TakeProfit_02;
  }
//+------------------------------------------------------------------+
//| Filling the Stop Loss array                                      |
//+------------------------------------------------------------------+
void GetStopLoss()
  {
   StopLoss[0]=StopLoss_01;
   StopLoss[1]=StopLoss_02;
  }
//+------------------------------------------------------------------+
//| Filling the Trailing Stop array                                  |
//+------------------------------------------------------------------+
void GetTrailingStop()
  {
   TrailingStop[0]=TrailingStop_01;
   TrailingStop[1]=TrailingStop_02;
  }
//+------------------------------------------------------------------+
//| Filling the Reverse array                                        |
//+------------------------------------------------------------------+
void GetReverse()
  {
   Reverse[0]=Reverse_01;
   Reverse[1]=Reverse_02;
  }
//+------------------------------------------------------------------+
//| Filling the Lot array                                            |
//+------------------------------------------------------------------+
void GetLot()
  {
   Lot[0]=Lot_01;
   Lot[1]=Lot_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncrease array                                 |
//+------------------------------------------------------------------+
void GetVolumeIncrease()
  {
   VolumeIncrease[0]=VolumeIncrease_01;
   VolumeIncrease[1]=VolumeIncrease_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncreaseStep array                             |
//+------------------------------------------------------------------+
void GetVolumeIncreaseStep()
  {
   VolumeIncreaseStep[0]=VolumeIncreaseStep_01;
   VolumeIncreaseStep[1]=VolumeIncreaseStep_02;
  }

それではすべての外部パラメータを同時に都合よく初期化するのに役立つ関数 InitializeInputParameters() を作成しましょう。

//+------------------------------------------------------------------+
//| Initializing external parameter arrays                           |
//+------------------------------------------------------------------+
void InitializeInputParameters()
  {
   GetSymbols();
   GetIndicatorPeriod();
   GetTakeProfit();
   GetStopLoss();
   GetTrailingStop();
   GetReverse();
   GetLot();
   GetVolumeIncrease();
   GetVolumeIncreaseStep();
  }

外部パラメータ配列の初期化に続き、主要部分に進んでいくことができます。インディケータハンドル、その値、価格情報を取得するなどいくつかの手順は、新規バーを確認するなどのようにシンボルごとに連続してループで実行します。これは外部パラメータ値が配列で作成されているためです。よってすべては次のようにループで行われます。

//--- Iterate over all symbols
for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
  {
//--- If trading for this symbol is allowed
   if(Symbols[s]!="")
     {
      //--- The rest of the code
     }
  }

ただ既存の関数を変更して新しい関数を作成する前に、そのパターンで必要になる配列も作成します。

インディケータハンドルに対しては配列が2つ必要です。

//--- Array of indicator agent handles
int spy_indicator_handles[NUMBER_OF_SYMBOLS];
//--- Array of signal indicator handles
int signal_indicator_handles[NUMBER_OF_SYMBOLS];

これら2つの配列は最初に無効な値に対して初期化されます。

//+------------------------------------------------------------------+
//| Initializing arrays of indicator handles                         |
//+------------------------------------------------------------------+
void InitializeArrayHandles()
  {
   ArrayInitialize(spy_indicator_handles,INVALID_HANDLE);
   ArrayInitialize(signal_indicator_handles,INVALID_HANDLE);
  }

価格データとインディケータ値の配列はストラクチャを用いてアクセスされます。

//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[NUMBER_OF_SYMBOLS];      // Opening price of the bar
PriceData high[NUMBER_OF_SYMBOLS];      // High price of the bar
PriceData low[NUMBER_OF_SYMBOLS];       // Low price of the bar
PriceData close[NUMBER_OF_SYMBOLS];     // Closing price of the bar
PriceData indicator[NUMBER_OF_SYMBOLS]; // Array of indicator values

リストの最初のシンボルの最終完了のインディケータ値を取得する必要があれば、以下のようなものを書きます。

double indicator_value=indicator[0].value[1];

またCheckNewBar() 関数で前に使用された変数の代わりに配列を作成する必要があります。

//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[NUMBER_OF_SYMBOLS];
//--- Array for checking the new bar for each symbol
datetime new_bar[NUMBER_OF_SYMBOLS];

配列を整理します。上記変更に従って多くの関数を変更する必要があります。GetIndicatorHandles() 関数から始めます。

//+------------------------------------------------------------------+
//| Getting indicator handles                                        |
//+------------------------------------------------------------------+
void GetIndicatorHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(signal_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            signal_indicator_handles[s]=iMA(Symbols[s],_Period,IndicatorPeriod[s],0,MODE_SMA,PRICE_CLOSE);
            //--- If the indicator handle could not be obtained
            if(signal_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to get the indicator handle for the symbol "+Symbols[s]+"!");
           }
        }
     }
  }

検証/トレードに使用されるシンボル数に関わらず、これで関数のコードは変わりません。

同様にべつの関数 GetSpyHandles()を作成します。これはティックと他のシンボルからは真するインディケータエージェントのハンドルを取得します。その前にシンボルごとに Enums.mqhファイルにフラグとして整理されるすべてのイベントの列挙 ENUM_CHART_EVENT_SYMBOLをもう一つ追加します。

//+------------------------------------------------------------------+
//| New bar and tick events from all symbols and time frames         |
//+------------------------------------------------------------------+
enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_NO         = 0,          // Events are disabled - 0
   CHARTEVENT_INIT       = 0,          // Initialization event - 0
   //---
   CHARTEVENT_NEWBAR_M1  = 0x00000001, // New bar event on a minute chart (1)
   CHARTEVENT_NEWBAR_M2  = 0x00000002, // New bar event on a 2-minute chart (2)
   CHARTEVENT_NEWBAR_M3  = 0x00000004, // New bar event on a 3-minute chart (4)
   CHARTEVENT_NEWBAR_M4  = 0x00000008, // New bar event on a 4-minute chart (8)
   //---
   CHARTEVENT_NEWBAR_M5  = 0x00000010, // New bar event on a 5-minute chart (16)
   CHARTEVENT_NEWBAR_M6  = 0x00000020, // New bar event on a 6-minute chart (32)
   CHARTEVENT_NEWBAR_M10 = 0x00000040, // New bar event on a 10-minute chart (64)
   CHARTEVENT_NEWBAR_M12 = 0x00000080, // New bar event on a 12-minute chart (128)
   //---
   CHARTEVENT_NEWBAR_M15 = 0x00000100, // New bar event on a 15-minute chart (256)
   CHARTEVENT_NEWBAR_M20 = 0x00000200, // New bar event on a 20-minute chart (512)
   CHARTEVENT_NEWBAR_M30 = 0x00000400, // New bar event on a 30-minute chart (1024)
   CHARTEVENT_NEWBAR_H1  = 0x00000800, // New bar event on an hour chart (2048)
   //---
   CHARTEVENT_NEWBAR_H2  = 0x00001000, // New bar event on a 2-hour chart (4096)
   CHARTEVENT_NEWBAR_H3  = 0x00002000, // New bar event on a 3-hour chart (8192)
   CHARTEVENT_NEWBAR_H4  = 0x00004000, // New bar event on a 4-hour chart (16384)
   CHARTEVENT_NEWBAR_H6  = 0x00008000, // New bar event on a 6-hour chart (32768)
   //---
   CHARTEVENT_NEWBAR_H8  = 0x00010000, // New bar event on a 8-hour chart (65536)
   CHARTEVENT_NEWBAR_H12 = 0x00020000, // New bar event on a 12-hour chart (131072)
   CHARTEVENT_NEWBAR_D1  = 0x00040000, // New bar event on a daily chart (262144)
   CHARTEVENT_NEWBAR_W1  = 0x00080000, // New bar event on a weekly chart (524288)
   //---
   CHARTEVENT_NEWBAR_MN1 = 0x00100000, // New bar event on a monthly chart (1048576)
   CHARTEVENT_TICK       = 0x00200000, // New tick event (2097152)
   //---
   CHARTEVENT_ALL        = 0xFFFFFFFF  // All events are enabled (-1)
  };

この列挙はカスタムインディケータEventsSpy.mq5 (ファイルは本稿に添付があります)と GetSpyHandles() 関数内で連携するために必要です。この関数のコードは以下に記載しています。

//+------------------------------------------------------------------+
//| Getting agent handles by the specified symbols                   |
//+------------------------------------------------------------------+
void GetSpyHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(spy_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            spy_indicator_handles[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_TICK);
            //--- If the indicator handle could not be obtained
            if(spy_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to install the agent on "+Symbols[s]+"");
           }
        }
     }
  }

iCustom() 関数の最後のパラメータに注意が必要です。この場合、ティックイベント取得に識別子 CHARTEVENT_TICK が使用sれています。ただ必要に応じ、それは新規バーイベント取得のために変更可能です。たとえば下記のような行にすれば、 Expert Advisor は分足(M1)および時間足(H1)のタイムフレームで新規バーイベントを取得します。

handle_event_indicator[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_H1);

イベントをすべて(全タイムフレームでのティックイベントとバーイベント)取得するには識別 CHARTEVENT_ALL を指定する必要があります。

すべての配列は OnInit() 関数で初期化されます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialization of arrays of external parameters
   InitializeInputParameters();
//--- Initialization of arrays of indicator handles
   InitializeArrayHandles();
//--- Get agent handles
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeArrayNewBar();
  }

本稿冒頭ですでに述べたように、インディケータエージェントからのイベントは OnChartEvent() 関数で受け取られます。以下はこの関数で使用されるコードです。

//+------------------------------------------------------------------+
//| Chart events handler                                             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // Event identifier
                  const long &lparam,   // Long type event parameter
                  const double &dparam, // Double type event parameter
                  const string &sparam) // String type event parameter
  {
//--- If this is a custom event
   if(id>=CHARTEVENT_CUSTOM)
     {
      //--- Exit if trading is not allowed
      if(CheckTradingPermission()>0)
         return;
      //--- If there was a tick event
      if(lparam==CHARTEVENT_TICK)
        {
         //--- Check signals and trade on them
         CheckSignalsAndTrade();
         return;
        }
     }
  }

CheckSignalAndTrade() 関数(上記コードで強調表示されている行)では、すべてのシンボルが交代でOnTick() 関数で前に実装されている新規バーイベントとトレードシグナルについて確認されるループを持ちます。

//+------------------------------------------------------------------+
//| Checking signals and trading based on the new bar event          |
//+------------------------------------------------------------------+
void CheckSignalsAndTrade()
  {
//--- Iterate over all specified symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the bar is not new, proceed to the next symbol
         if(!CheckNewBar(s))
            continue;
         //--- If there is a new bar
         else
           {
            //--- Get indicator data. If there is no data, proceed to the next symbol
            if(!GetIndicatorsData(s))
               continue;
            //--- Get bar data               
            GetBarsData(s);
            //--- Check the conditions and trade
            TradingBlock(s);
            //--- Trailing Stop
            ModifyTrailingStop(s);
           }
        }
     }
  }

外部パラメータ、またシンボルとインディケータデータを使用する関数はすべて上記の全変更に従い変更される必要があります。このために最初のパラメータとしてシンボル番号を追加し、関数内のすべての変数と配列を上に述べられている新しい配列と置き換えます。

完全な解説、関数 CheckNewBar()TradingBlock()OpenPosition()の改定版コードは以下に記載します。

CheckNewBar() 関数コード

//+------------------------------------------------------------------+
//| Checking for the new bar                                         |
//+------------------------------------------------------------------+
bool CheckNewBar(int number_symbol)
  {
//--- Get the opening time of the current bar
//    If an error occurred when getting the time, print the relevant message
   if(CopyTime(Symbols[number_symbol],Period(),0,1,lastbar_time[number_symbol].time)==-1)
      Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError()));
//--- If this is a first function call
   if(new_bar[number_symbol]==NULL)
     {
      //--- Set the time
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      Print(__FUNCTION__,": Initialization ["+Symbols[number_symbol]+"][TF: "+TimeframeToString(Period())+"]["
            +TimeToString(lastbar_time[number_symbol].time[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]");
      return(false);
     }
//--- If the time is different
   if(new_bar[number_symbol]!=lastbar_time[number_symbol].time[0])
     {
      //--- Set the time and exit
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      return(true);
     }
//--- If we have reached this line, then the bar is not new, so return false
   return(false);
  }

TradingBlock() 関数コード

//+------------------------------------------------------------------+
//| Trading block                                                    |
//+------------------------------------------------------------------+
void TradingBlock(int symbol_number)
  {
   ENUM_ORDER_TYPE      signal=WRONG_VALUE;                 // Variable for getting a signal
   string               comment="hello :)";                 // Position comment
   double               tp=0.0;                             // Take Profit
   double               sl=0.0;                             // Stop Loss
   double               lot=0.0;                            // Volume for position calculation in case of position reversal
   double               position_open_price=0.0;            // Position opening price
   ENUM_ORDER_TYPE      order_type=WRONG_VALUE;             // Order type for opening a position
   ENUM_POSITION_TYPE   opposite_position_type=WRONG_VALUE; // Opposite position type
//--- Find out if there is a position
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Get the signal
   signal=GetTradingSignal(symbol_number);
//--- If there is no signal, exit
   if(signal==WRONG_VALUE)
      return;
//--- Get symbol properties
   GetSymbolProperties(symbol_number,S_ALL);
//--- Determine values for trade variables
   switch(signal)
     {
      //--- Assign values to variables for a BUY
      case ORDER_TYPE_BUY  :
         position_open_price=symb.ask;
         order_type=ORDER_TYPE_BUY;
         opposite_position_type=POSITION_TYPE_SELL;
         break;
         //--- Assign values to variables for a SELL
      case ORDER_TYPE_SELL :
         position_open_price=symb.bid;
         order_type=ORDER_TYPE_SELL;
         opposite_position_type=POSITION_TYPE_BUY;
         break;
     }
//--- Get the Take Profit and Stop Loss levels
   sl=CalculateStopLoss(symbol_number,order_type);
   tp=CalculateTakeProfit(symbol_number,order_type);
//--- If there is no position
   if(!pos.exists)
     {
      //--- Adjust the volume
      lot=CalculateLot(symbol_number,Lot[symbol_number]);
      //--- Open a position
      OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
     }
//--- If the position exists
   else
     {
      //--- Get the position type
      GetPositionProperties(symbol_number,P_TYPE);
      //--- If the position is opposite to the signal and the position reversal is enabled
      if(pos.type==opposite_position_type && Reverse[symbol_number])
        {
         //--- Get the position volume
         GetPositionProperties(symbol_number,P_VOLUME);
         //--- Adjust the volume
         lot=pos.volume+CalculateLot(symbol_number,Lot[symbol_number]);
         //--- Reverse the position
         OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
         return;
        }
      //--- If the signal is in the direction of the position and the volume increase is enabled, increase the position volume
      if(!(pos.type==opposite_position_type) && VolumeIncrease[symbol_number]>0)
        {
         //--- Get the Stop Loss of the current position
         GetPositionProperties(symbol_number,P_SL);
         //--- Get the Take Profit of the current position
         GetPositionProperties(symbol_number,P_TP);
         //--- Adjust the volume
         lot=CalculateLot(symbol_number,VolumeIncrease[symbol_number]);
         //--- Increase the position volume
         OpenPosition(symbol_number,lot,order_type,position_open_price,pos.sl,pos.tp,comment);
         return;
        }
     }
  }

OpenPosition() 関数コード

//+------------------------------------------------------------------+
//| Opening a position                                               |
//+------------------------------------------------------------------+
void OpenPosition(int symbol_number,
                  double lot,
                  ENUM_ORDER_TYPE order_type,
                  double price,
                  double sl,
                  double tp,
                  string comment)
  {
//--- Set the magic number in the trading structure
   trade.SetExpertMagicNumber(MagicNumber);
//--- Set the slippage in points
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- Instant Execution and Market Execution mode
//    *** Starting with build 803, Stop Loss and Take Profit ***
//    *** can be set upon opening a position in the SYMBOL_TRADE_EXECUTION_MARKET mode ***
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT ||
      symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      //--- If the position failed to open, print the relevant message
      if(!trade.PositionOpen(Symbols[symbol_number],order_type,lot,price,sl,tp,comment))
         Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
     }
  }

これで各関数はシンボル番号を得ます(symbol_number)。ビルド 803で行われた変更にも注意を払ってください。

ビルド 803から始めると、ストップロステイクプロフィットSYMBOL_TRADE_EXECUTION_MARKET モードでのポジションオープン時に設定することができます。

その他関数の改定済みコードは添付ファイルにあります。ここで必要なのはパラメータの最適化と検証を行うことだけです。


パラメータの最適化と Expert Advisorの検証

まず最初のシンボルに対してパラメータを最適化し、それから2番目のシンボルに対して最適化を行います。EURUSDから始めます。

以下がストラレジーテスタの設定です。

図1 ストラレジーテスタ設定

図1 ストラレジーテスタ設定

Expert Advisor の設定は以下に示されるように行う必要があります(便宜上各シンボルに対する設定を持つ .set ファイルを本稿に添付しています)。最適化から特定シンボルを除外するにはそのシンボル名パラメータフィールドを空のままにしておくだけです。それぞれのシンボルに対して行われるパラメータの最適化は最適化プロセスをスピードアップします。

図2 パラメータ最適化のためのExpert Advisor 設定: EURUSD

図2 パラメータ最適化のためのExpert Advisor 設定: EURUSD

デュアルコアプロセッサの場合、最適化は約1時間かかります。最大リカバリーファクター検証結果を以下に表示します。

図3 EURUSDに対する最大リカバリーファクター検証結果

図3 EURUSDに対する最大リカバリーファクター検証結果

そして2番目のシンボルをして NZDUSD を設定します。最適化のためには最初のパラメータのシンボル名を持つラインブロック空のままにしておきます。

また、シンボル名の最後にハイフンを追加するだけでも結構です。Expert Advisor はシンボルリスト上にそのような名前のシンボルを見つず、空の文字列に対して配列インデックスを初期化します。

NZDUSD に対する結果は以下のようなものです。

図4 NZDUSDに対する最大リカバリーファクター検証結果

図4 NZDUSDに対する最大リカバリーファクター検証結果

ここで2つのシンボルを一緒に検証することができます。ストラテジーテスタ設定では、Expert Advisor を起動する対象シンボルを任意に設定することができます。なぜなら結果は同様だからです。トレード/検証対称でないシンボルでもありえます。

以下が一緒に検証された2つのシンボルに対する結果です。

図5 2つのシンボルに対する結果:EURUSD および NZDUSD

図5 2つのシンボルに対する結果:EURUSD および NZDUSD


おわりに

これでおしまいです。以下にソースコードを添付しております。上記について詳しく調査するのにダウンロードしていただけます。練習のためにシンボルを1個以上選択するか他のインディケータを用いてポジションをオープンする条件を変更してみてください。

このファイルをアーカイブから抽出したら、MetaTrader 5\MQL5\ExpertsディレクトリのMultiSymbolExpert フォルダに入れます。その後、EventsSpy.mq5 インディケータは MetaTrader 5\MQL5\Indicators ディレクトリに入れる必要があります。

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

MQL5 クックブック:トリプルスクリーン戦略に基づくトレーディングシステムに対するフレームワーク作成 MQL5 クックブック:トリプルスクリーン戦略に基づくトレーディングシステムに対するフレームワーク作成
本稿では MQL5で「リプルスクリーン」戦略に基づくトレーディングシステムに対するフレームワークを作成します。Expert Advisor を一から作成することはしません。代わりに、実質上すでにわれわれの目的に役だっている先行記事 "MQL5 Cookbook: Using Indicators to Set Trading Conditions in Expert Advisors" のプログラムを変更するだけとします。よって本稿は既製プログラムのパターンを簡単に変更する方法もお伝えします。
MQL5 クックブック:パラメータ数無制限での複数通貨対応 EXPERT 作成 MQL5 クックブック:パラメータ数無制限での複数通貨対応 EXPERT 作成
本稿ではトレーディングシステムの最適化に対して一組のパラメータを使うパターンを作成していきます。同時にパラメータ数は無制限に認めます。シンボルリストは標準的なテキストファイル (*.txt)内に作成します。各シンボルに対する入力パラメータもファイルに格納されます。このように Expert Advisorの入力パラメータ数に関してターミナルの制約を回避することができます。
Expert Advisor パラメータのユーザーパネルからの『オンザフライ』の変更 Expert Advisor パラメータのユーザーパネルからの『オンザフライ』の変更
本稿ではパラメータをユーザーパネルから管理できる Expert Advisor の実装を示すちょっとした例を提供します。『オンザフライ』でパラメータを変更するとき、Expert Advisor はのちにファイルから読み込みパネル上で適切に表示するために情報パネルからファイルに取得した値を書きます。本稿はマニュアルでトレードを行う方または半自動モードでトレードを行う方に適しているでしょう。
MQL5 クックブック:ディールヒストリーのファイルへの書き込みと シンボルごとの残高チャートの Excel形式での作成 MQL5 クックブック:ディールヒストリーのファイルへの書き込みと シンボルごとの残高チャートの Excel形式での作成
さまざまなフォーラムのコミュニケーションの際、Microsoft Excel チャート形式のスクリーンショットとして表示される検証結果の例を多く使いました。そしてそのようなチャートの作成方法を教えてほしいと頻繁に質問を受けました。ついに本稿でそれを説明する時間を得ました。