Çok Para Birimli, Çok Sistemli bir Uzman Danışman Oluşturma

Maxim Khrolenko | 13 Ocak, 2022

Giriş

Birden fazla alım satım sembolü ile alım satım yapan ve birçok strateji kullanan oldukça fazla yatırımcı olduğuna inanıyorum. Bu yaklaşım, potansiyel olarak kârınızı artırmanıza olanak sağlamakla kalmaz, aynı zamanda etkili para yönetimi ile önemli düşüş riskini de minimum düzeye indirir. Bir Uzman Danışman oluştururken, program stratejisinin etkinliğini kontrol etmede ilk doğal adım, en iyi giriş parametrelerini belirlemek için optimizasyondur.

Parametre değerleri tanımlandığında, Uzman Danışmanlar teknik olarak alım satıma hazır olmaktadır. Ancak, yanıtlanmamış önemli bir soru kalmaktadır. Bir yatırımcı tek bir Uzman Danışmanda tüm stratejilerini bir araya getirebilseydi test sonuçları nasıl olurdu? Birkaç semboldeki veya stratejideki düşüşün bir noktada çakışabileceği ve korkunç bir toplam düşüş ve hatta teminat çağrısı ile sonuçlanabileceğinin bilincinde olunması bazen kötü bir sürpriz olabilir.

Bu makale, bu önemli soruya bir yanıt bulmamıza olanak sağlayacak çok para birimli, çok sistemli bir Uzman Danışman oluşturma kavramını tanıtmaktadır.


1. Uzman Danışmanın yapısı

Genel itibarıyla Uzman Danışman yapısı aşağıdaki gibidir:

Şekil 1. Çok para birimli, çok sistemli Uzman Danışman yapısı

Şekil 1. Çok para birimli, çok sistemli Uzman Danışman yapısı

Gördüğünüz gibi program, bir for döngüsüne dayanmaktadır. Her bir strateji, her bir yinelemenin ayrı olarak her bir sembolün alınıp satılmasından sorumlu olduğu bir döngüde düzenlenir. Burada, döngülerde sınırsız sayıda strateji düzenleyebilirsiniz. Böyle bir programı "işlemek" için bilgisayarınızda yeterli kaynakların olması önemlidir.

MetaTrader 5'te alım satımı yapılan her bir sembol için yalnızca bir pozisyon olabileceğini unutmamalısınız. Bu pozisyon, daha önce yürütülen Alışlar ve Satışlar lotlarının toplamını temsil eder. Dolayısıyla, bir sembol için çok stratejili test sonucu, aynı sembol için aynı stratejilerin ayrı test sonuçlarının toplamı ile aynı olmayacaktır.

Uzman Danışmanın yapısının daha yakından değerlendirilmesi için, her biri iki sembol için alım satım yapan 2 strateji ele alacağız:

Strateji A:

Strateji B:

Uzman danışmanın üzerinde test edileceği veya alım satım yapacağı bir sembol için yeni tiklerden bağımsız olarak, çok para birimli modda alım satım için OnTimer() fonksiyonunun kullanılması tavsiye edilir.

Bu amaçla, Uzman Danışmanı başlatırken, EventSetTimer() fonksiyonunu kullanarak bir program hesaplaması olayı oluşturma frekansını belirtiriz ve sonlandırma üzerine, terminale olay oluşturmayı durdurmasını söylemek için EventKillTimer() fonksiyonunu kullanırız:

// Include standard libraries
// Create external parameters
// Create arrays, variables, indicator handles, etc.

//--- Initialization of the Expert Advisor
int OnInit()
  {
   //--- Set event generation frequency
   EventSetTimer(1); // 1 second
   // ...
   return(0);
  }
void OnTimer()
  {
   // ...
  }
//--- Deinitialization of the Expert Advisor
void OnDeinit(const int reason)
  {
   //--- Stop event generation
   EventKillTimer();
   // ...
  }

EventSetTimer() yerine, frekansın milisaniye değerine ayarlandığı EventSetMillisecondTimer() fonksiyonunu kullanabilirsiniz, ancak çok sık program hesaplama çağrısı ile bunu aşırı kullanmamalısınız.

Hesaba, pozisyon ve sembol ayarlarına ve alım satım fonksiyonlarına erişim için, sırasıyla CAccountInfo, CPositionInfo, CSymbolInfo ve CTrade sınıflarını kullanacağız. Bunları Uzman Danışmana ekleyelim:

//--- Include standard libraries
#include <Trade\AccountInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\Trade.mqh>

Uzman Danışman for döngüleri tabanlı olduğu için, bunun harici parametreleri için diziler oluşturmamız gerekecektir. İlk olarak, her bir strateji için sembol sayısına eşit sabitler oluşturalım:

//--- Number of traded symbols for each strategy
#define Strategy_A 2
#define Strategy_B 2

Daha sonra harici parametreleri bildiririz: Sabitleri kullanarak bunların kopyalanacağı dizilerin boyutlarını belirleriz. Ayrıca gösterge işleyicileri ve diğer global değişkenleri oluştururuz.

Strateji A'nın bir sembolünün bir örneği aşağıda verilmiştir:

//------------------- External parameters of strategy A
input string          Data_for_Strategy_A="Strategy A -----------------------";
//--- Symbol 0
input string          Symbol_A0      = "EURUSD";   // Symbol
input bool            IsTrade_A0     = true;       // Permission for trading
//--- Bollinger Bands (BB) parameters
input ENUM_TIMEFRAMES Period_A0      = PERIOD_H1;  // ВВ period
input uint            BBPeriod_A0    = 20;         // Period for calculation of the moving average of BB
input int             BBShift_A0     = 0;          // Horizontal shift of ВВ
input double          BBDeviation_A0 = 2.0;        // Number of standard deviations of BB
//...
//--- General parameters of strategy A
input double          DealOfFreeMargin_A = 1.0;    // Percent of free margin for a deal
input uint            MagicNumber_A      = 555;    // Magic number
input uint            Slippage_A         = 100;    // Permissible slippage for a deal
//...
//------------- Set variables of strategy A -----
//--- Arrays for external parameters
string          Symbol_A[Strategy_A];
bool            IsTrade_A[Strategy_A];
ENUM_TIMEFRAMES Period_A[Strategy_A];
int             BBPeriod_A[Strategy_A];
int             BBShift_A[Strategy_A];
double          BBDeviation_A[Strategy_A];
//--- Arrays for global variables
double          MinLot_A[Strategy_A],MaxLot_A[Strategy_A];
double          Point_A[Strategy_A],ContractSize_A[Strategy_A];
uint            DealNumber_A[Strategy_A];
datetime        Locked_bar_time_A[Strategy_A],time_arr_A[];
//--- Indicator handles
int             BB_handle_high_A[Strategy_A];
int             BB_handle_low_A[Strategy_A];
//--- Arrays for indicator values
double          BB_upper_band_high[],BB_lower_band_high[];
double          BB_upper_band_low[],BB_lower_band_low[];
//--- Class
CTrade          Trade_A;
//...
//--- Set global variables for all strategies
long            Leverage;
//--- Classes
CAccountInfo    AccountInfo;
CPositionInfo   PositionInfo;
CSymbolInfo     SymbolInfo;

Belirli bir sembol için alım satımı devre dışı bırakma imkanına sahip olmak için, for döngülerinin en başına yerleştirilecek bir IsTrade_A0 Boolean değişkeni oluşturduk.


2. Uzman Danışmanı Başlatma

İlk olarak, örneğin kaldıraç olmak üzere tüm stratejiler için gerekli değerleri elde edelim. Kaldıraç alım satım hesabına uygulanmadığından ve bir strateji veya bir sembol ile hiçbir alakası olmadığında, bunun değerini dizilere kopyalamaya gerek yoktur.

//--- Get the leverage for the account
   Leverage=AccountInfo.Leverage();

Daha sonra harici parametreleri dizilere kopyalarız.

//--- Copy external variables to arrays
   Symbol_A[0]     =Symbol_A0;
   IsTrade_A[0]    =IsTrade_A0;
   Period_A[0]     =Period_A0;
   BBPeriod_A[0]   =(int)BBPeriod_A0;
   BBShift_A[0]    =BBShift_A0;
   BBDeviation_A[0]=BBDeviation_A0;

Herhangi bir harici parametre bir diğer parametreye dönüşüm gerektirecek tür ile tanımlanıyorsa, bu, dizilere kopyalama yapılırken daha uygun bir şekilde gerçekleştirilebilir.

Bu durumda, BBPeriod_A0 öğesinin, kullanıcının bir negatif değer ayarlanmasını engellemek için uint olarak oluşturulduğunu görebiliriz. Burada, bunu int öğesine dönüştürürüz ve bunu int olarak oluşturulan diziye kopyalarız. Diğer şekilde derleyici, gösterge işleyiciye uint türünde parametre eklemeye çalışıp çalışmadığınıza dair bir uyarı verecektir.

Ayrıca, alım satımı yapılan sembolün Piyasa İzlemede mevcut olup olmadığını ve bunun bir strateji içinde birkaç kez kullanılıp kullanılmadığına bakalım:

//--- Check for the symbol in the Market Watch
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      if(IsSymbolInMarketWatch(Symbol_A[i])==false)
        {
         Print(Symbol_A[i]," could not be found on the server!");
         ExpertRemove();
        }
     }

//--- Check whether the symbol is used more than once
   if(Strategy_A>1)
     {
      for(int i=0; i<Strategy_A-1; i++)
        {
         if(IsTrade_A[i]==false) continue;
         for(int j=i+1; j<Strategy_A; j++)
           {
            if(IsTrade_A[j]==false) continue;
            if(Symbol_A[i]==Symbol_A[j])
              {
               Print(Symbol_A[i]," is used more than once!");
               ExpertRemove();
              }
           }
        }
     }
//--- The IsSymbolInMarketWatch() function
bool IsSymbolInMarketWatch(string f_Symbol)
  {
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      if(f_Symbol==SymbolName(s,false))
         return(true);
     }
   return(false);
  }

Semboller doğru bir şekilde seçildiyse, bunlardan her biri için giriş parametrelerinde hataları kontrol edin, gösterge işleyiciler oluşturun, lot hesaplaması için gerekli verileri elde edin ve gerekiyorsa belirli strateji ile tanımlanan diğer şeyleri yapın.

Yukarıda bahsedilen eylemleri bir for döngüsüne uygulayacağız.

//--- General actions
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      //--- Check for errors in input parameters
      //...
      //--- Set indicator handles
      BB_handle_high_A[i]=iBands(Symbol_A[i],Period_A[i],BBPeriod_A[i],BBShift_A[i],BBDeviation_A[i],
                                 PRICE_HIGH);
      if(BB_handle_high_A[i]<0)
        {
         Print("Failed to create a handle for Bollinger Bands based on High prices for ",Symbol_A[i]," . Handle=",INVALID_HANDLE,
               "\n Error=",GetLastError());
         ExpertRemove();
        }
      //...
      //--- Calculate data for the Lot
      //--- set the name of the symbol for which the information will be obtained
      SymbolInfo.Name(Symbol_A[i]);
      //--- minimum and maximum volume size in trading operations
      MinLot_A[i]=SymbolInfo.LotsMin();
      MaxLot_A[i]=SymbolInfo.LotsMax();
      //--- point value
      Point_A[i]=SymbolInfo.Point();
      //--- contract size
      ContractSize_A[i]=SymbolInfo.ContractSize();

      //--- Set some additional parameters
     }

Daha sonra, CTrade sınıfının Trade_A nesnesini kullanarak strateji A'nın alım sayım işlemlerine yönelik parametreleri ayarlayacağız.

//--- Set parameters for trading operations
//--- set the magic number
   Trade_A.SetExpertMagicNumber(MagicNumber_A);
//--- set the permissible slippage in points upon deal execution
   Trade_A.SetDeviationInPoints(Slippage_A);
//--- order filling mode, use the mode that is allowed by the server
   Trade_A.SetTypeFilling(ORDER_FILLING_RETURN);
//--- logging mode, it is advisable not to call this method as the class will set the optimal mode by itself
   Trade_A.LogLevel(1);
//--- the function to be used for trading: true - OrderSendAsync(), false - OrderSend().
   Trade_A.SetAsyncMode(true);

Aynı prosedür her bir strateji için tekrarlanır, yani:

  1. Harici parametreleri dizilere kopyala;
  2. Sembollerin doğru şekilde seçilip seçilmediğini kontrol et;
  3. Hataları kontrol et, gösterge işleyiciler oluştur, lot için ve belirli bir strateji için gereken her şey için verileri hesapla;
  4. Alım satım işlemleri için parametreleri ayarla.

Son olarak, birkaç stratejide bir ve aynı sembolün kullanılıp kullanılmadığını kontrol etmek iyi olacak (iki strateji için bir örnek aşağıda verilmiştir):

//--- Check whether one and the same symbol is used in several strategies
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      for(int j=0; j<Strategy_B; j++)
        {
         if(IsTrade_B[j]==false) continue;
         if(Symbol_A[i]==Symbol_B[j])
           {
            Print(Symbol_A[i]," is used in several strategies!");
            ExpertRemove();
           }
        }
     }

3. "For" Döngüleri ile Alım Satım

OnTimer() fonksiyonu içinde for döngülerinin çerçevesi aşağıdaki gibidir:

void OnTimer()
  {
//--- Check if the terminal is connected to the trade server
   if(TerminalInfoInteger(TERMINAL_CONNECTED)==false) return;

//--- Section A: Main loop of the FOR operator for strategy A -----------
   for(int A=0; A<Strategy_A; A++)
     {
      //--- A.1: Check whether the symbol is allowed to be traded
      if(IsTrade_A[A]==false)
         continue; // terminate the current FOR iteration

     }

//--- Section В: Main loop of the FOR operator for strategy В -----------
   for(int B=0; B<Strategy_B; B++)
     {
      //--- B.1: Check whether the symbol is allowed to be traded
      if(IsTrade_B[B]==false)
         continue; // terminate the current FOR iteration

     }
  }

Tek bir stratejiye dayalı tek sembollü bir Uzman Danışman, sonraki tüm işlemlerin durdurulması gerektiği koşuluna sahipse, geri dön (return) işlecini kullanırız. Bizim durumumuzda, sadece mevcut yinelemeyi durdurmamız ve sonraki sembol yinelemelerine ilerlememiz gerekiyor. Bunun için en iyisi devam et (continue) işlecini kullanmaktır.

Sonraki tüm hesaplamaların durdurulması koşulunu içeren bir for döngüsü ile bir strateji ekleyerek çok stratejili Uzman Danışmanınızı geliştirmek isterseniz, aşağıdaki modeli kullanabilirsiniz:

//--- Section N: Main loop of the FOR operator for strategy N -----------
for(int N=0; N<Strategy_N; N++)
  {

   //...
   bool IsInterrupt=false;
   for(int i=0; i<Number; i++)
     {
      if(...) // terminate all calculations
        {
         IsInterrupt=true;
         break;
        }
     }
   if(IsInterrupt=true)
      continue; // terminate the current FOR iteration
   //...

  }

for döngülerinin çerçevesini oluşturduktan sonra, bunu diğer EA'ların kodlarına ekliyoruz ve ardından bazı değişkenleri dizi öğeleri ile değiştiriyoruz.

Örneğin önceden tanımlanmış _Symbol değişkenini Symbol_A[i] ile veya _Point değişkenini Point_A[i] ile değiştiriyoruz. Bu değişkenlerin değerleri, belirli sembol için tipiktir ve bu yüzden başlatma üzerine dizilere kopyalanmıştır.

Örneğin gösterge değerini bulalım:

 //--- A.3: Lower band of BB calculated based on High prices
 if(CopyBuffer(BB_handle_high_A[A],LOWER_BAND,BBShift_A[A],1,BB_lower_band_high)<=0)
    continue; // terminate the current FOR iteration
 ArraySetAsSeries(BB_lower_band_high,true);

Bir alış pozisyonunun kapatılmasını uygulamak için aşağıdaki kodu yazacağız:

 //--- A.7.1: Calculate the current Ask and Bid prices
 SymbolInfo.Name(Symbol_A[A]);
 SymbolInfo.RefreshRates();
 double Ask_price=SymbolInfo.Ask();
 double Bid_price=SymbolInfo.Bid();

 if(PositionSelect(Symbol_A[A]))
   {
    //--- A.7.2: Closing a BUY position
    if(PositionInfo.PositionType()==POSITION_TYPE_BUY)
      {
       if(Bid_price>=BB_lower_band_high[0] || DealNumber_A[A]==0)
         {
          if(!Trade_A.PositionClose(Symbol_A[A]))
            {
             Print("Failed to close the Buy ",Symbol_A[A]," position. Code=",Trade_A.ResultRetcode(),
                   " (",Trade_A.ResultRetcodeDescription(),")");
             continue; // terminate the current FOR iteration
            }
          else
            {
             Print("The Buy ",Symbol_A[A]," position closed successfully. Code=",Trade_A.ResultRetcode(),
                   " (",Trade_A.ResultRetcodeDescription(),")");
             continue; // terminate the current FOR iteration
            }
         }
      }

    //...
   }

Bir Alış pozisyonu açma:

 //--- A.9.1: for a Buy
 if(Ask_price<=BB_lower_band_low[0])
   {
    //...

    //--- A.9.1.3: Execute a deal
    if(!Trade_A.Buy(OrderLot,Symbol_A[A]))
      {
       Print("The Buy ",Symbol_A[A]," has been unsuccessful. Code=",Trade_A.ResultRetcode(),
             " (",Trade_A.ResultRetcodeDescription(),")");
       continue; // terminate the current FOR iteration
      }
    else
      {
       Print("The Buy ",Symbol_A[A]," has been successful. Code=",Trade_A.ResultRetcode(),
             " (",Trade_A.ResultRetcodeDescription(),")");
       continue; // terminate the current FOR iteration
      }
   }

Zamanlayıcı olayı oluşturmayı durdurmayı ve sonlandırmada gösterge işleyicilerini silmeyi unutmayın.


4. Test Sonuçları

Uzman Danışman hazır olduğunda, her bir stratejiyi ve her bir sembolü ayrı olarak test ederiz ve test sonuçlarını tüm stratejilerin ve sembollerin eş zamanlı olarak alım satım yaptığındaki test modunda elde edilenler ile karşılaştırırız.

Kullanıcının halihazırda giriş parametreleri için optimum değerleri tanımladığı varsayılır.


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

Şekil 2. Strateji Test Cihazı ayarları

Şekil 2. Strateji Test Cihazı ayarları

Strateji A, EURUSD için sonuçlar:

Şekil 3. Strateji A, EURUSD için test sonuçları

Şekil 3. Strateji A, EURUSD için test sonuçları

Strateji A, GBPUSD için sonuçlar

Şekil 4. Strateji A, GBPUSD için test sonuçları

Şekil 4. Strateji A, GBPUSD için test sonuçları

Strateji B, AUDUSD için sonuçlar

Şekil 5. Strateji B, AUDUSD için test sonuçları

Şekil 5. Strateji B, AUDUSD için test sonuçları

Strateji B, EURJPY için sonuçlar:

Şekil 6. Strateji B, EURJPY için test sonuçları

Şekil 6. Strateji B, EURJPY için test sonuçları

Tüm stratejiler ve semboller için test sonuçları:

Şekil 7. Tüm stratejiler ve semboller için test sonuçları

Şekil 7. Tüm stratejiler ve semboller için test sonuçları


Sonuç

Bunun sonucunda, stratejilerinizden herhangi birini ekleyebileceğiniz çok para birimli, çok sistemli Uzman Danışmanın uygun ve basit bir yapısını elde ettik.

Böyle bir Uzman Danışman, tüm stratejilerinizi kullanarak alım satım verimliliğinizi daha iyi değerlendirmenize olanak sağlar. Belirli bir hesapta yalnızca bir Uzman Danışmanın çalışmasına izin verildiği durumda da faydalı olduğu gösterilebilir. Uzman Danışmanın kaynak kodu, yukarıdaki bilgilerin çalışılmasını kolaylaştırmak için makaleye eklenmiştir.