MQL5 Tarif Defteri: Çok Para Birimli Uzman Danışman - Basit, Sade ve Hızlı Yaklaşım

Anatoli Kazharski | 13 Ocak, 2022

Giriş

Bu makale, çok para birimli bir Uzman Danışman için uygun olan basit bir yaklaşımın uygulamasını açıklayacaktır. Bu, aynı koşullar altında, ancak her bir sembol için farklı parametreler ile test/alım satım için Uzman Danışmanı kurabileceğiniz anlamına gelir. Örnek olarak, gerekirse kodda küçük değişiklikler yaparak ek semboller eklenebilecek şekilde iki sembol için bir model oluşturacağız.

Çok para birimli bir model MQL5'te birçok şekilde uygulanabilir:


Uzman Danışman Geliştirme

"MQL5 Tarif Defteri: Uzman Danışmanlarda Alım Satım Koşullarını Belirlemek için Göstergeleri Kullanma" makalesinde içerilen Uzman Danışman bir şablon görevi görecektir. Bilgi paneliyle ilgili her şeyi zaten sildim ve ayrıca aşağıdaki başlığa sahip önceki makalede uygulanan pozisyon açma koşullarını basitleştirdim: "MQL5 Tarif Defteri: Üçlü Ekran Stratejisine Dayalı Bir Alım Satım Sistemi Çerçevesi Geliştirme". İki sembol için bir Uzman Danışman oluşturmayı amaçladığımızdan, bunların her birinin kendi harici parametre setine ihtiyacı olacaktır:

//--- 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

Harici parametreler, boyutları kullanılan sembollerin sayısına bağlı olacak dizilere yerleştirilecektir. Uzman Danışmanda kullanılan sembol sayısı, dosyanın başında oluşturmamız gereken NUMBER_OF_SYMBOLS sabitinin değeri ile belirlenecektir:

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

Harici parametreleri saklamak için gerekli olacak dizileri oluşturalım:

//--- 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

Dizi başlatma fonksiyonları InitArrays.mqh içerik dosyasına yerleştirilecektir. Symbols[] dizisini başlatmak için GetSymbol() fonksiyonunu oluşturacağız. Sembol adını harici parametrelerden alacak ve bu sembol sunucudaki sembol listesinde varsa Piyasa İzleme penceresinde seçilecektir. Veya gerekli sembol sunucuda bulunamazsa, fonksiyon boş bir dize döndürecek ve Uzman Danışmanlar Günlüğü buna göre güncellenecektir.

Aşağıda GetSymbol() fonksiyon kodu yer almaktadır:

//+------------------------------------------------------------------+
//| 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[] dizisi GetSymbols() fonksiyonunda başlatılacaktır:

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

Ek olarak, bunu, belirli bir sembolün harici parametrelerindeki boş bir değerin, ilgili bloğun test/alım satıma dahil olmayacağını belirtecek şekilde uygulayacağız. Bu, her bir sembol için parametreleri ayrı ayrı optimize ederken geri kalanı tamamen dışarıda tutmak için gereklidir.

Diğer tüm harici parametre dizileri aynı şekilde başlatılır. Diğer bir deyişle, her dizi için ayrı bir fonksiyon oluşturmamız gerekir. Tüm bu fonksiyonların kodları aşağıda verilmektedir:

//+------------------------------------------------------------------+
//| 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;
  }

Şimdi tüm harici parametre dizilerini tek seferde uygun şekilde başlatmamıza yardımcı olacak bir fonksiyon oluşturalım: InitializeInputParameters() fonksiyonu:

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

Harici parametre dizilerinin başlatılmasının ardından ana kısma geçebiliriz. Gösterge işleyicilerinin, bunların değerlerinin ve fiyat bilgilerinin elde edilmesi, yeni çubuğun kontrol edilmesi vb. gibi bazı prosedürler her bir sembol için döngüler halinde ardışık olarak gerçekleştirilecektir. Bu nedenle harici parametre değerleri dizilerde düzenlenmiştir. Böylece her şey döngülerde aşağıdaki gibi yapılacaktır:

//--- 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
     }
  }

Ancak mevcut fonksiyonları değiştirmeye ve yenilerini oluşturmaya başlamadan önce, bu modelde gerekli olacak dizileri de oluşturalım.

Gösterge işleyiciler için iki diziye ihtiyacımız olacak:

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

Bu iki dizi ilk olarak geçersiz değerlerle başlatılacaktır:

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

Fiyat verilerinin ve gösterge değerlerinin dizilerine yapılar kullanılarak erişilecektir:

//--- 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

Şimdi, listedeki ilk sembolün son tamamlanan çubuğundaki gösterge değerini elde etmemiz gerekiyorsa, şöyle bir şey yazmalısınız:

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

Daha önce CheckNewBar() fonksiyonunda kullanılan değişkenler yerine de diziler oluşturmamız gerekiyor:

//--- 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];

İşte dizileri düzenledik. Şimdi yukarıda yapılan değişikliklere göre birçok fonksiyonu değiştirmemiz gerekiyor. GetIndicatorHandles() fonksiyonu ile başlayalım:

//+------------------------------------------------------------------+
//| 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]+"!");
           }
        }
     }
  }

Şimdi, test/alım satımda kullanılan sembollerin sayısından bağımsız olarak, fonksiyon kodu aynı kalacaktır.

Benzer şekilde, diğer sembollerden tikleri aktaran gösterge aracılarının işleyicilerini elde etmek için bir başka fonksiyon oluşturacağız:GetSpyHandles(). Ancak bundan önce, Enums.mqh dosyasında bayraklar olarak düzenlenen ENUM_CHART_EVENT_SYMBOL sembolü ile tüm olayların bir başka numaralandırmasını ekleyeceğiz:

//+------------------------------------------------------------------+
//| 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)
  };

Bu numaralandırma, aşağıda kodu sağlanan GetSpyHandles() fonksiyonunda özel gösterge EventsSpy.mq5 (dosya makaleye eklenmiştir) ile çalışmak için gereklidir:

//+------------------------------------------------------------------+
//| 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]+"");
           }
        }
     }
  }

Lütfen iCustom() fonksiyonundaki son parametreye dikkat edin: bu durumda CHARTEVENT_TICK tanımlayıcısı tik olaylarını elde etmek için kullanılmıştır. Ancak gerekirse, yeni çubuk olaylarını alacak şekilde değiştirilebilir. Örneğin aşağıda gösterildiği gibi bir çizgi kullanırsanız Uzman Danışman dakikalık (M1) ve saatlik (H1) zaman aralıklarında yeni çubuk olaylarını alacaktır:

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

Tüm olayları (tüm zaman aralıklarında tik ve çubuk olayları) elde etmek için CHARTEVENT_ALL tanımlayıcısını belirtmelisiniz.

Tüm diziler, OnInit() fonksiyonunda başlatılır:

//+------------------------------------------------------------------+
//| 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();
  }

Makalenin daha başında bahsedildiği gibi gösterge aracılarından olaylar OnChartEvent() fonksiyonunda alınır. Aşağıda, bu fonksiyonda kullanılacak kod yer almaktadır:

//+------------------------------------------------------------------+
//| 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() fonksiyonunda (yukarıdaki kodda vurgulanmış çizgi), tüm sembollerin daha önce OnTick() fonksiyonu uygulandığı gibi değişimli olarak yeni çubuk olayı ve alım satım sinyalleri için kontrol edildiği bir döngümüz olacak:

//+------------------------------------------------------------------+
//| 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);
           }
        }
     }
  }

Sembol ve gösterge verilerinin yanı sıra harici parametreleri kullanan tüm fonksiyonların, yukarıdaki tüm değişikliklere göre değiştirilmesi gerekir. Bunun için, ilk parametre olarak sembol sayısını eklemeli ve fonksiyondaki tüm değişkenleri ve dizileri yukarıda açıklanan yeni diziler ile değiştirmeliyiz.

Gösterim amacıyla CheckNewBar(), TradingBlock() ve OpenPosition() fonksiyonlarının gözden geçirilmiş kodları aşağıda verilmiştir.

CheckNewBar() fonksiyon kodu:

//+------------------------------------------------------------------+
//| 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() fonksiyon kodu:

//+------------------------------------------------------------------+
//| 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() fonksiyon kodu:

//+------------------------------------------------------------------+
//| 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()));
     }
  }

Bu şekilde, her bir fonksiyon artık sembol numarasını (symbol_number) alır. Lütfen Yapı 803'te uygulanan değişikliğe dikkat edin:

Yapı 803 ile başlayarak Zarar Durdur ve Kâr Al, SYMBOL_TRADE_EXECUTION_MARKET modunda bir pozisyon açılması üzerine ayarlanabilir.

Diğer fonksiyonların gözden geçirilmiş kodları ekteki dosyalarda bulunabilir. Şimdi tek yapmamız gereken parametreleri optimize etmek ve test yapmak.


Parametreleri Optimize Etme ve Uzman Danışmanı Test Etme

İlk olarak birinci sembol ve ardından ikinci sembol için parametreleri optimize edeceğiz. EURUSD ile başlayalım.

Aşağıda Strateji Test Cihazının ayarları yer almaktadır:

Şekil 1. Strateji Test Cihazı ayarları

Şekil 1. Strateji Test Cihazı ayarları.

Uzman Danışmanın ayarlarının aşağıda gösterildiği gibi yapılması gerekmektedir (kolaylık olması açısından her bir sembol için ayarları içeren .set dosyaları makaleye eklenmiştir). Belirli bir sembolü optimizasyondan çıkarmak için sembol adı parametre alanını boş bırakmanız yeterlidir. Her sembol için ayrı ayrı gerçekleştirilen parametrelerin optimizasyonu optimizasyon sürecini de hızlandıracaktır.

Şekil 2. Parametre optimizasyonu için Uzman Danışman ayarları: EURUSD

Şekil 2. Parametre optimizasyonu için Uzman Danışman ayarları: EURUSD.

Optimizasyon, çift çekirdekli bir işlemcide yaklaşık bir saat sürecektir. Maksimum kurtarma faktörü test sonuçları aşağıda gösterildiği gibidir:

Şekil 3. EURUSD için maksimum kurtarma faktörü test sonuçları

Şekil 3. EURUSD için maksimum kurtarma faktörü test sonuçları.

Şimdi NZDUSD'yi ikinci sembol olarak ayarlayalım. Optimizasyon için, ilk parametre bloğunun sembol adını içeren satırı boş bırakın.

Alternatif olarak, sembol adının sonuna bir tire ekleyebilirsiniz. Uzman Danışman, bu ada sahip sembolü sembol listesinde bulamayacak ve dizi indisini boş bir dizeye başlatacaktır.

NZDUSD için sonuçlar aşağıdaki şekilde görünmektedir:

Şekil 4. NZDUSD için maksimum kurtarma faktörü test sonuçları

Şekil 4. NZDUSD için maksimum kurtarma faktörü test sonuçları.

Şimdi iki sembolü birlikte test edebiliriz. Strateji Test Cihazı ayarlarında, sonuçlar aynı olacağından, Uzman Danışmanın başlatıldığı herhangi bir sembolü ayarlayabilirsiniz. Hatta bu, alım satım/teste dahil olmayan bir sembol bile olabilir.

Aşağıda birlikte test edilen iki sembolün sonuçları verilmiştir:

Şekil 5. İki sembol için test sonuçları: EURUSD ve NZDUSD

Şekil 5. İki sembol için test sonuçları: EURUSD ve NZDUSD.


Sonuç

Neredeyse hepsi bu. Kaynak kodları aşağıya eklenmiştir ve yukarıdakilerin daha ayrıntılı bir incelemesi için indirilebilir. Alıştırma için, bir veya daha fazla sembol seçmeyi veya diğer göstergeleri kullanarak pozisyon açma koşullarını değiştirmeyi deneyin.

Dosyaları arşivden çıkardıktan sonra, MultiSymbolExpert klasörünü MetaTrader 5\MQL5\Experts dizisine yerleştirin. Ayrıca EventsSpy.mq5 göstergesi MetaTrader 5\MQL5\Indicators dizinine yerleştirilmelidir.