English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
preview
SQLite: MQL5'te SQL veritabanlarıyla yerel olarak çalışma

SQLite: MQL5'te SQL veritabanlarıyla yerel olarak çalışma

MetaTrader 5Sınayıcı | 17 Ekim 2022, 09:55
342 0
MetaQuotes
MetaQuotes

İçindekiler


MetaTrader 5'te modern algoritmik ticaret

MQL5, hem sözdizimi hem de hesaplama hızı açısından C++'a olabildiğince yakın olduğundan dolayı algoritmik ticaret için mükemmel bir çözümdür. MetaTrader 5 platformu, kullanıcılarının basit ticaret görevlerinin ötesine geçmelerini ve her türlü karmaşıklıkta analiz sistemleri oluşturmalarını mümkün kılan, böylece ticaret robotları ve özel göstergeler geliştirmelerine olanak sağlayan modern ve özelleşmiş bir dil sunmaktadır.

Asenkron ticaret fonksiyonlarına ve matematik kütüphanelerine ek olarak, yatırımcılar ayrıca ağ fonksiyonlarına, verileri Python'a aktarmaya, OpenCL'de paralel hesaplamaya, "akıllı" fonksiyon aktarmayla .NET kütüphaneleri için yerel desteğe, MS Visual Studio ile entegrasyona ve DirectX kullanarak veri görselleştirmeye erişebilir. Modern algoritmik ticaretin cephaneliğindeki bu vazgeçilmez araçlar, günümüzde kullanıcıların MetaTrader 5 işlem platformundan ayrılmadan çok çeşitli görevleri çözmelerine olanak sağlamaktadır.


Veritabanlarıyla çalışmak için fonksiyonlar

Ticaret stratejilerinin geliştirilmesi, büyük miktarda verinin işlenmesiyle ilişkilidir. Bugün artık sadece hızlıca bir MQL5 programı şeklinde bir ticaret algoritması yazmak yeterli değildir. Güvenilir sonuçlar elde etmek için, yatırımcıların ayrıca çeşitli ticaret enstrümanları üzerinde çok sayıda test ve optimizasyon gerçekleştirmesi, sonuçları kaydetmesi, işlemesi ve analiz yapması ve devamında bir sonraki adıma karar vermesi gerekmektedir.

Artık basit ve popüler SQLite motorunu doğrudan MQL5'te kullanarak veritabanlarıyla çalışabilirsiniz. Geliştiricilerin web sitesindeki testlerin sonuçları, SQL sorgularının yüksek hızda yürütüldüğünü göstermektedir. Çoğu görevde PostgreSQL ve MySQL'den daha iyi performans göstermektedir. Biz de bu testlerin MQL5 ve LLVM 9.0.0 üzerinde uygulanma hızlarını karşılaştırdık ve sonuçları tablo halinde düzenledik. Sonuçlar milisaniye cinsinden verilmiştir - ne kadar düşükse o kadar iyidir.

Ad
Açıklama
 LLVM  
MQL5
Test 1
 1000 INSERTs
11572
8488
Test 2
 25000 INSERTs in a transaction
59
60
Test 3
 25000 INSERTs into an indexed table
102
105
Test 4
 100 SELECTs without an index
142
150
Test 5
 100 SELECTs on a string comparison
391
390
Test 6
 Creating an index
43
33
Test 7
 5000 SELECTs with an index
385
307
Test 8
 1000 UPDATEs without an index
58
54
Test 9
 25000 UPDATEs with an index
161
165
Test 10
 25000 text UPDATEs with an index
124
120
Test 11  INSERTs from a SELECT
84
84
Test 12
 DELETE without an index
25
74
Test 13
 DELETE with an index
70
72
Test 14  A big INSERT after a big DELETE
62
66
Test 15  A big DELETE followed by many small INSERTs
33
33
Test 16  DROP TABLE: finished
42
40

Test ayrıntılarını ekteki SqLiteTest.zip dosyasında bulabilirsiniz. Ölçümlerin yapıldığı bilgisayarın teknik özellikleri: Windows 10 x64, Intel Xeon E5-2690 v3 @ 2.60GHz. 

Yukarıdaki sonuçlar, MQL5'te veritabanlarıyla çalışırken maksimum performanstan emin olabileceğimizi göstermektedir. SQL ile daha önce hiç karşılaşmamış olanlar, yapılandırılmış sorgu dilinin (Structured Query Language, SQL), karmaşık döngülere ve örneklemelere ihtiyaç duymadan birçok görevi hızlı ve zarif bir şekilde çözmelerine olanak tanıdığını görecektir.


Basit sorgu örneği

Veritabanları bilgileri tablolar şeklinde depolar, yeni veriler alma/değiştirme ve ekleme işlemleri ise SQL dilinde sorgular kullanılarak yapılır. Şimdi basit bir veritabanının nasıl oluşturulacağını ve ondan nasıl veri alınacağını görmek adına bir örneğe göz atalım.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   string filename="company.sqlite";
//--- create or open the database in the common terminal folder
   int db=DatabaseOpen(filename, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE |DATABASE_OPEN_COMMON);
   if(db==INVALID_HANDLE)
     {
      Print("DB: ", filename, " open failed with code ", GetLastError());
      return;
     }
... working with the database


//--- close the database
   DatabaseClose(db);
  }

Veritabanı oluşturma ve kapatma, dosyalarla çalışmaya benzerdir. Önce veritabanı için bir tanıtıcı oluşturuyoruz, sonra kontrol ediyoruz ve son olarak kapatıyoruz.

Ardından, veritabanında tablonun var olup olmadığını kontrol ediyoruz. Tablo halihazırda mevcutsa, örneğimizde olduğu gibi aynı verileri ekleme girişimi bir hatayla sonuçlanacaktır.

//--- if the COMPANY table exists, delete it
   if(DatabaseTableExists(db, "COMPANY"))
     {
      //--- delete the table
      if(!DatabaseExecute(db, "DROP TABLE COMPANY"))
        {
         Print("Failed to drop table COMPANY with code ", GetLastError());
         DatabaseClose(db);
         return;
        }
     }
//--- create the COMPANY table 
   if(!DatabaseExecute(db, "CREATE TABLE COMPANY("
                       "ID INT PRIMARY KEY     NOT NULL,"
                       "NAME           TEXT    NOT NULL,"
                       "AGE            INT     NOT NULL,"
                       "ADDRESS        CHAR(50),"
                       "SALARY         REAL );"))
     {
      Print("DB: ", filename, " create table failed with code ", GetLastError());
      DatabaseClose(db);
      return;
     }

Tablo, sorgular kullanılarak oluşturulur ve silinir. Yürütme sonucu her zaman kontrol edilmelidir. COMPANY tablosunda yalnızca beş alan bulunmaktadır: ID, NAME, AGE, ADDRESS ve SALARY. ID alanı bir anahtardır, yani benzersiz bir indekstir. İndeksler, her girdinin güvenilir bir şekilde tanımlanmasına olanak sağlar ve onları birbirine bağlamak için farklı tablolarda kullanılabilir. Bu, bir pozisyon kimliğinin o pozisyona ait tüm işlemleri ve emirleri nasıl birbirine bağladığına benzerdir.

Şimdi tabloyu verilerle doldurmamız gerekiyor. Bu, INSERT sorgusu kullanılarak yapılır:

//--- enter data to the table 
   if(!DatabaseExecute(db, "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (1,'Paul',32,'California',25000.00); "
                       "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (2,'Allen',25,'Texas',15000.00); "
                       "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (3,'Teddy',23,'Norway',20000.00);"
                       "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (4,'Mark',25,'Rich-Mond',65000.00);"))
     {
      Print("DB: ", filename, " insert failed with code ", GetLastError());
      DatabaseClose(db);
      return;
     }

Gördüğümüz gibi, COMPANY tablosuna dört girdi eklendi. Her girdi için alanların sırası ve bu alanlara eklenecek değerler belirtilir. Her girdi, tek bir sorgu halinde ayrı "INSERT..." sorguları tarafından eklenir. Başka bir deyişle, her girdiyi ayrı bir DatabaseExecute() çağrısı ile tabloya ekleyebiliriz.

Komut dosyasının sonunda veritabanı company.sqlite dosyasına kaydedileceğinden, bir dahaki sefere çalıştırıldığında, aynı verileri aynı ID’lerle COMPANY tablosuna yazmaya çalışacağız. Bu, hataya neden olacaktır. Dolayısıyla, komut dosyası her çalıştırıldığında sıfırdan başlamak adına önce tabloyu siliyoruz.

Şimdi COMPANY tablosundan SALARY alanı > 15000 olan tüm girdileri alalım. Bu, sorgu metnini derleyen ve daha sonra DatabaseRead() veya DatabaseReadBind()’da kullanılmak üzere ona bir tanıtıcı geri döndüren DatabasePrepare() fonksiyonu kullanılarak yapılır.

//--- create a query and get a handle for it
   int request=DatabasePrepare(db, "SELECT * FROM COMPANY WHERE SALARY>15000");
   if(request==INVALID_HANDLE)
     {
      Print("DB: ", filename, " request failed with code ", GetLastError());
      DatabaseClose(db);
      return;
     }

Sorgu başarıyla oluşturulduktan sonra yürütme sonuçlarını almamız gerekiyor. Bunu, ilk çağrı sırasında sorguyu yürüten ve sonuçlardaki ilk girdiye hareket eden DatabaseRead()’i kullanarak yapacağız. Sonraki her çağrıda, sona ulaşana kadar bir sonraki girdiyi okur. Bizim durumumuzda false geri döndürecektir, bu, başka girdi yok anlamına gelir.

//--- print all entries with the salary greater than 15000
   int    id, age;
   string name, address;
   double salary;
   Print("Persons with salary > 15000:");
   for(int i=0; DatabaseRead(request); i++)
     {
      //--- read the values of each field from the obtained entry
      if(DatabaseColumnInteger(request, 0, id) && DatabaseColumnText(request, 1, name) &&
         DatabaseColumnInteger(request, 2, age) && DatabaseColumnText(request, 3, address) && DatabaseColumnDouble(request, 4, salary))
         Print(i, ":  ", id, " ", name, " ", age, " ", address, " ", salary);
      else
        {
         Print(i, ": DatabaseRead() failed with code ", GetLastError());
         DatabaseFinalize(request);
         DatabaseClose(db);
         return;
        }
     }
//--- remove the query after use
   DatabaseFinalize(request);

Yürütme sonucu aşağıdaki gibi olacaktır:

Persons with salary > 15000:
0:  1 Paul 32 California 25000.0
1:  3 Teddy 23 Norway 20000.0
2:  4 Mark 25 Rich-Mond  65000.0

Örneğin tam kodu DatabaseRead.mq5 dosyasındadır.

MetaEditor'da SQL sorgularında hata ayıklama

Veritabanıyla çalışmaya yönelik tüm fonksiyonlar, başarısız bir çağrı durumunda bir hata kodu geri döndürür. Şu dört basit kuralı izlerseniz, onlarla çalışırken herhangi bir sorun oluşmasını önlersiniz:

  1. DatabaseFinalize() tarafından kullanıldıktan sonra tüm sorgu tanıtıcıları yok edilmelidir;
  2. Veritabanı, tamamlanmadan önce DatabaseClose() ile kapatılmalıdır;
  3. Sorgu yürütme sonuçları kontrol edilmelidir;
  4. Bir hata durumunda, önce sorgu yok edilmeli, ardından veritabanı kapatılmalıdır.

En zor şey, sorgu oluşturulmamışsa hatanın ne olduğunu anlamaktır. MetaEditor *.sqlite dosyalarının açılmasına ve SQL sorguları kullanılarak onlarla çalışılmasına olanak tanır. Oluşturulan company.sqlite örnek dosyasını kullanarak bunun nasıl yapıldığını görelim:

1. Ortak terminal klasöründen company.sqlite dosyasını açıyoruz.

2. Veritabanını açtıktan sonra Kılavuzda COMPANY tablosunu görebiliriz. Üzerine çift tıklayın.

3. SELECT * FROM COMPANY sorgusu, durum çubuğunda otomatik olarak oluşturulur.

4. Sorgu otomatik olarak yürütülür. Ayrıca F9'a basılarak veya Yürüt düğmesine tıklanarak da yürütülebilir.

5. Sorgunun yürütme sonucuna bakıyoruz.

6. Bir şeyler ters giderse, hatalar düzenleyicinin günlüğünde görüntülenir.


SQL sorgularını kullanarak örneğin toplam ve ortalama gibi tablo alanları hakkında istatistikler elde edebiliriz. Sorguları yapalım ve çalışıp çalışmadıklarını kontrol edelim.

MetaEditor'da sorgularla çalışma

Şimdi bu sorguları MQL5 kodunda uygulayabiliriz:

   Print("Some statistics:");
//--- prepare a new query about the sum of salaries
   request=DatabasePrepare(db, "SELECT SUM(SALARY) FROM COMPANY");
   if(request==INVALID_HANDLE)
     {
      Print("DB: ", filename, " request failed with code ", GetLastError());
      DatabaseClose(db);
      return;
     }
   while(DatabaseRead(request))
     {
      double total_salary;
      DatabaseColumnDouble(request, 0, total_salary);
      Print("Total salary=", total_salary);
     }
//--- remove the query after use
   DatabaseFinalize(request);
 
//--- prepare a new query about the average salary
   request=DatabasePrepare(db, "SELECT AVG(SALARY) FROM COMPANY");
   if(request==INVALID_HANDLE)
     {
      Print("DB: ", filename, " request failed with code ", GetLastError());
      ResetLastError();
      DatabaseClose(db);
      return;
     }
   while(DatabaseRead(request))
     {
      double aver_salary;
      DatabaseColumnDouble(request, 0, aver_salary);
      Print("Average salary=", aver_salary);
     }
//--- remove the query after use
   DatabaseFinalize(request);

Yürütme sonuçlarını karşılaştırabiliriz:

Some statistics:
Total salary=125000.0
Average salary=31250.0


DatabaseReadBind() kullanılarak sorgu sonuçlarının yapıya otomatik olarak okunması

DatabaseRead() fonksiyonu, sorgu sonucundaki tüm girdilerin üzerinden geçilmesine ve böylece sonuç tablosundaki her bir sütun hakkında tam veri elde edilmesine olanak tanır:

Bu fonksiyonlar, herhangi bir sorgunun sonuçlarıyla toplu bir şekilde çalışılmasına olanak sağlar. Ancak bunun bedeli ise aşırı koddur. Sorgu sonuçlarının yapısı önceden biliniyorsa, tüm girdinin yapıya hemen okunmasını sağlayan DatabaseReadBind() fonksiyonunu kullanmak daha iyidir. Önceki örneği şu şekilde yeniden yazabiliriz - ilk olarak Person yapısını bildiriyoruz:

struct Person
  {
   int               id;
   string            name;
   int               age;
   string            address;
   double            salary;
  };

Ardından, DatabaseReadBind(request, person) ile sorgu sonuçlarından her girdiyi okuyacağız:

//--- display obtained query results
   Person person;
   Print("Persons with salary > 15000:");
   for(int i=0; DatabaseReadBind(request, person); i++)
      Print(i, ":  ", person.id, " ", person.name, " ", person.age, " ", person.address, " ", person.salary);
//--- remove the query after use
   DatabaseFinalize(request);

Böylece mevcut girdiden tüm alanların değerlerini, ayrı ayrı okumaya gerek kalmadan, anında alıyoruz.


İşlemleri DatabaseTransactionBegin()/DatabaseTransactionCommit() içerisine sararak hızlandırma 

Bir tabloyla çalışırken, INSERT, UPDATE veya DELETE komutlarını toplu olarak kullanmak gerekebilir. Bunu yapmanın en iyi yolu işlemleri kullanmaktır. İşlemler yapılırken önce veritabanı kilitlenir (DatabaseTransactionBegin). Devamında toplu değişiklik komutları gerçekleştirilir ve kaydedilir (DatabaseTransactionCommit), bir hata varlığında ise iptal edilir (DatabaseTransactionRollback).

DatabasePrepare fonksiyonunun açıklaması, işlemlerin kullanımına ilişkin bir örnek içermektedir:

//--- auxiliary variables
   ulong    deal_ticket;         // deal ticket
   long     order_ticket;        // a ticket of an order a deal was executed by
   long     position_ticket;     // ID of a position a deal belongs to
   datetime time;                // deal execution time
   long     type ;               // deal type
   long     entry ;              // deal direction
   string   symbol;              // a symbol a deal was executed for
   double   volume;              // operation volume
   double   price;               // price
   double   profit;              // financial result
   double   swap;                // swap
   double   commission;          // commission
   long     magic;               // Magic number (Expert Advisor ID)
   long     reason;              // deal execution reason or source
//--- go through all deals and add them to the database
   bool failed=false;
   int deals=HistoryDealsTotal();
// --- lock the database before executing transactions
   DatabaseTransactionBegin(database);
   for(int i=0; i<deals; i++)
     {
      deal_ticket=    HistoryDealGetTicket(i);
      order_ticket=   HistoryDealGetInteger(deal_ticket, DEAL_ORDER);
      position_ticket=HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);
      time= (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME);
      type=           HistoryDealGetInteger(deal_ticket, DEAL_TYPE);
      entry=          HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);
      symbol=         HistoryDealGetString(deal_ticket, DEAL_SYMBOL);
      volume=         HistoryDealGetDouble(deal_ticket, DEAL_VOLUME);
      price=          HistoryDealGetDouble(deal_ticket, DEAL_PRICE);
      profit=         HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);
      swap=           HistoryDealGetDouble(deal_ticket, DEAL_SWAP);
      commission=     HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION);
      magic=          HistoryDealGetInteger(deal_ticket, DEAL_MAGIC);
      reason=         HistoryDealGetInteger(deal_ticket, DEAL_REASON);
      //--- add each deal to the table using the following query
      string request_text=StringFormat("INSERT INTO DEALS (ID,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON)"
                                       "VALUES (%d, %d, %d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %d, %d)",
                                       deal_ticket, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason);
      if(!DatabaseExecute(database, request_text))
        {
         PrintFormat("%s: failed to insert deal #%d with code %d", __FUNCTION__, deal_ticket, GetLastError());
         PrintFormat("i=%d: deal #%d  %s", i, deal_ticket, symbol);
         failed=true;
         break;
        }
     }
//--- check for transaction execution errors
   if(failed)
     {
      //--- roll back all transactions and unlock the database
      DatabaseTransactionRollback(database);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
      return(false);
     }
//--- all transactions have been performed successfully - record changes and unlock the database
   DatabaseTransactionCommit(database);

İşlemler, DatabaseTransactionBegin örneğinde gösterildiği gibi toplu tablo işlemlerinin yüzlerce kat hızlandırılmasına olanak sağlar:

Result:
   Deals in the trading history: 2737 
   Transations WITH    DatabaseTransactionBegin/DatabaseTransactionCommit: time=48.5 milliseconds
   Transations WITHOUT DatabaseTransactionBegin/DatabaseTransactionCommit: time=25818.9 milliseconds
   Use of DatabaseTransactionBegin/DatabaseTransactionCommit provided acceleration by 532.8 times


İşlem geçmişindeki işlemlerle çalışma

SQL sorgularının gücü, kod yazmak zorunda kalmadan kaynak verileri kolayca sıralayabilmemiz, seçebilmemiz ve değiştirebilmeniz gerçeğinde yatar. DatabasePrepare fonksiyonunun açıklamasından örneği analiz etmeye devam edelim - tek bir sorgu aracılığıyla ticaret işlemlerinin nasıl alınacağını göreceğiz. Bir ticaret işlemi, giriş ve çıkış tarihleri, giriş ve çıkış fiyatları, sembol, yön ve hacim hakkında bilgiler içerir. Ticaret işleminin yapısına bakarsak, giriş/çıkış işlemlerinin ortak bir pozisyon kimliği ile bağlı olduğunu görebiliriz. Dolayısıyla, bir hedging hesapta basit bir ticaret sistemimiz varsa, alış ve satış işlemlerini tek bir ticaret işlemi halinde kolayca birleştirebiliriz. Bu, aşağıdaki sorgu kullanılarak yapılır:

//--- fill in the TRADES table using an SQL query based on DEALS table data
   ulong start=GetMicrosecondCount();
   if(DatabaseTableExists(db, "DEALS"))
     {
      //--- fill in the TRADES table
      if(!DatabaseExecute(db, "INSERT INTO TRADES(TIME_IN,TICKET,TYPE,VOLUME,SYMBOL,PRICE_IN,TIME_OUT,PRICE_OUT,COMMISSION,SWAP,PROFIT) "
                          "SELECT "
                          "   d1.time as time_in,"
                          "   d1.position_id as ticket,"
                          "   d1.type as type,"
                          "   d1.volume as volume,"
                          "   d1.symbol as symbol,"
                          "   d1.price as price_in,"
                          "   d2.time as time_out,"
                          "   d2.price as price_out,"
                          "   d1.commission+d2.commission as commission,"
                          "   d2.swap as swap,"
                          "   d2.profit as profit "
                          "FROM DEALS d1 "
                          "INNER JOIN DEALS d2 ON d1.position_id=d2.position_id "
                          "WHERE d1.entry=0 AND d2.entry=1"))
        {
         Print("DB: fillng the TRADES table failed with code ", GetLastError());
         return;
        }
     }
   ulong transaction_time=GetMicrosecondCount()-start;

Burada mevcut DEALS tablosu kullanılır. Girdiler, INNER JOIN aracılığıyla aynı DEAL_POSITION_ID'ye sahip işlemlerden oluşturulur. İlgili DatabasePrepare komut dosyasını bir işlem hesabında çalıştıralım:

Result:
   Deals in the trading history: 2741 
   The first 10 deals:
       [ticket] [order_ticket] [position_ticket]              [time] [type] [entry] [symbol] [volume]   [price]   [profit] [swap] [commission] [magic] [reason]
   [0] 34429573              0                 0 2019.09.05 22:39:59      2       0 ""        0.00000   0.00000 2000.00000 0.0000      0.00000       0        0
   [1] 34432127       51447238          51447238 2019.09.06 06:00:03      0       0 "USDCAD"  0.10000   1.32320    0.00000 0.0000     -0.16000     500        3
   [2] 34432128       51447239          51447239 2019.09.06 06:00:03      1       0 "USDCHF"  0.10000   0.98697    0.00000 0.0000     -0.16000     500        3
   [3] 34432450       51447565          51447565 2019.09.06 07:00:00      0       0 "EURUSD"  0.10000   1.10348    0.00000 0.0000     -0.18000     400        3
   [4] 34432456       51447571          51447571 2019.09.06 07:00:00      1       0 "AUDUSD"  0.10000   0.68203    0.00000 0.0000     -0.11000     400        3
   [5] 34432879       51448053          51448053 2019.09.06 08:00:00      1       0 "USDCHF"  0.10000   0.98701    0.00000 0.0000     -0.16000     600        3
   [6] 34432888       51448064          51448064 2019.09.06 08:00:00      0       0 "USDJPY"  0.10000 106.96200    0.00000 0.0000     -0.16000     600        3
   [7] 34435147       51450470          51450470 2019.09.06 10:30:00      1       0 "EURUSD"  0.10000   1.10399    0.00000 0.0000     -0.18000     100        3
   [8] 34435152       51450476          51450476 2019.09.06 10:30:00      0       0 "GBPUSD"  0.10000   1.23038    0.00000 0.0000     -0.20000     100        3
   [9] 34435154       51450479          51450479 2019.09.06 10:30:00      1       0 "EURJPY"  0.10000 118.12000    0.00000 0.0000     -0.18000     200        3
 
   The first 10 trades:
                 [time_in] [ticket] [type] [volume] [symbol] [price_in]          [time_out] [price_out] [commission]   [swap]  [profit]
   [0] 2019.09.06 06:00:03 51447238      0  0.10000 "USDCAD"    1.32320 2019.09.06 18:00:00     1.31761     -0.32000  0.00000 -42.43000
   [1] 2019.09.06 06:00:03 51447239      1  0.10000 "USDCHF"    0.98697 2019.09.06 18:00:00     0.98641     -0.32000  0.00000   5.68000
   [2] 2019.09.06 07:00:00 51447565      0  0.10000 "EURUSD"    1.10348 2019.09.09 03:30:00     1.10217     -0.36000 -1.31000 -13.10000
   [3] 2019.09.06 07:00:00 51447571      1  0.10000 "AUDUSD"    0.68203 2019.09.09 03:30:00     0.68419     -0.22000  0.03000 -21.60000
   [4] 2019.09.06 08:00:00 51448053      1  0.10000 "USDCHF"    0.98701 2019.09.06 18:00:01     0.98640     -0.32000  0.00000   6.18000
   [5] 2019.09.06 08:00:00 51448064      0  0.10000 "USDJPY"  106.96200 2019.09.06 18:00:01   106.77000     -0.32000  0.00000 -17.98000
   [6] 2019.09.06 10:30:00 51450470      1  0.10000 "EURUSD"    1.10399 2019.09.06 14:30:00     1.10242     -0.36000  0.00000  15.70000
   [7] 2019.09.06 10:30:00 51450476      0  0.10000 "GBPUSD"    1.23038 2019.09.06 14:30:00     1.23040     -0.40000  0.00000   0.20000
   [8] 2019.09.06 10:30:00 51450479      1  0.10000 "EURJPY"  118.12000 2019.09.06 14:30:00   117.94100     -0.36000  0.00000  16.73000
   [9] 2019.09.06 10:30:00 51450480      0  0.10000 "GBPJPY"  131.65300 2019.09.06 14:30:01   131.62500     -0.40000  0.00000  -2.62000
   Filling the TRADES table took 12.51 milliseconds

Bu komut dosyasını siz de hedging hesabınızda çalıştırın ve sonuçları işlem geçmişindeki pozisyonlarla karşılaştırın. Böyle bir sonucu elde etmek için döngüler kodlama konusunda yeterli bilgiye veya zamana sahip olmayabilirsiniz. Dolayısıyla artık bunu tek bir SQL sorgusu ile yapabilirsiniz. Komut dosyasının sonucunu MetaEditor'da görüntüleyebilirsiniz - bunun için ekteki trades.sqlite dosyasını açın.


Stratejilere göre portföy analizi

Yukarıda gösterilen DatabasePrepare komut dosyasının sonuçları, ticaretin birden fazla döviz çiftinde gerçekleştirildiğini göstermektedir. Ek olarak, [magic] sütununda da 100 ile 600 arasında değerler görmekteyiz. Bu, işlem hesabının, ticaret işlemlerini tanımlamak için her birinin kendi sihirli sayısına sahip olduğu birkaç strateji tarafından yönetildiği anlamına gelir.

Bir SQL sorgusu yardımıyla ticareti sihirli sayılar açısından analiz edebiliriz:

//--- get trading statistics for Expert Advisors by Magic Number
   request=DatabasePrepare(db, "SELECT r.*,"
                           "   (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff,"
                           "   (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent,"
                           "   (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent,"
                           "   r.gross_profit/r.win_trades as average_profit,"
                           "   r.gross_loss/r.loss_trades as average_loss,"
                           "   (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor "
                           "FROM "
                           "   ("
                           "   SELECT MAGIC,"
                           "   sum(case when entry =1 then 1 else 0 end) as trades,"
                           "   sum(case when profit > 0 then profit else 0 end) as gross_profit,"
                           "   sum(case when profit < 0 then profit else 0 end) as gross_loss,"
                           "   sum(swap) as total_swap,"
                           "   sum(commission) as total_commission,"
                           "   sum(profit) as total_profit,"
                           "   sum(profit+swap+commission) as net_profit,"
                           "   sum(case when profit > 0 then 1 else 0 end) as win_trades,"
                           "   sum(case when profit < 0 then 1 else 0 end) as loss_trades "
                           "   FROM DEALS "
                           "   WHERE SYMBOL <> '' and SYMBOL is not NULL "
                           "   GROUP BY MAGIC"
                           "   ) as r");

Sonuç:

Trade statistics by Magic Number
    [magic] [trades] [gross_profit] [gross_loss] [total_commission] [total_swap] [total_profit] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor]
[0]     100      242     2584.80000  -2110.00000          -33.36000    -93.53000      474.80000    347.91000          143            99           1.96198      59.09091       40.90909         18.07552      -21.31313         1.22502
[1]     200      254     3021.92000  -2834.50000          -29.45000    -98.22000      187.42000     59.75000          140           114           0.73787      55.11811       44.88189         21.58514      -24.86404         1.06612
[2]     300      250     2489.08000  -2381.57000          -34.37000    -96.58000      107.51000    -23.44000          134           116           0.43004      53.60000       46.40000         18.57522      -20.53078         1.04514
[3]     400      224     1272.50000  -1283.00000          -24.43000    -64.80000      -10.50000    -99.73000          131            93          -0.04687      58.48214       41.51786          9.71374      -13.79570         0.99182
[4]     500      198     1141.23000  -1051.91000          -27.66000    -63.36000       89.32000     -1.70000          116            82           0.45111      58.58586       41.41414          9.83819      -12.82817         1.08491
[5]     600      214     1317.10000  -1396.03000          -34.12000    -68.48000      -78.93000   -181.53000          116            98          -0.36883      54.20561       45.79439         11.35431      -14.24520         0.94346

6 stratejiden 4'ünün karlı olduğunu görüyoruz. Her strateji için birçok istatistiksel değer de elde ettik:

  • trades - stratejinin gerçekleştirdiği işlem sayısı,
  • gross_profit — stratejinin toplam kârı (tüm pozitif kâr değerlerinin toplamı),
  • gross_loss — stratejinin toplam zararı (tüm negatif kâr değerlerinin toplamı),
  • total_commission — stratejinin işlemlerine ilişkin tüm komisyonlarının toplamı,
  • total_swap - stratejinin işlemlerine ilişkin tüm swapların toplamı,
  • total_profit — gross_profit+gross_loss,
  • net_profit — gross_profit+gross_loss+total_commission+total_swap,
  • win_trades — kâr>0 olduğu işlem sayısı,
  • loss_trades — kâr<0 olduğu işlem sayısı,
  • expected_payoff — swaplar ve komisyonlar hariç işlem başına beklenen getiri = net_profit/trades,
  • win_percent — kârla kapanan işlemlerin yüzdesi,
  • loss_percent — zararla kapanan işlemlerin yüzdesi,
  • average_profit — ortalama kâr = gross_profit/win_trades,
  • average_loss — ortalama zarar = gross_loss/loss_trades,
  • profit_factor — kâr faktörü = gross_profit/gross_loss.

Kâr ve zararın hesaplanmasına yönelik bu istatistikler, pozisyondan alınan swapları ve komisyonları dikkate almaz. Bu, net maliyetlerin görülmesine olanak sağlar. Bir stratejinin az miktarda kâr oluşturduğu, swaplar ve komisyonlar da dahil olunca genellikle kârsız olduğu ortaya çıkabilir.


İşlemlerin sembollere göre analizi

Ticareti sembollerle analiz edebiliriz. Bunu yapmak için aşağıdaki sorguyu gerçekleştireceğiz:

//--- get trading statistics per symbols
   int request=DatabasePrepare(db, "SELECT r.*,"
                               "   (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff,"
                               "   (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent,"
                               "   (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent,"
                               "   r.gross_profit/r.win_trades as average_profit,"
                               "   r.gross_loss/r.loss_trades as average_loss,"
                               "   (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor "
                               "FROM "
                               "   ("
                               "   SELECT SYMBOL,"
                               "   sum(case when entry =1 then 1 else 0 end) as trades,"
                               "   sum(case when profit > 0 then profit else 0 end) as gross_profit,"
                               "   sum(case when profit < 0 then profit else 0 end) as gross_loss,"
                               "   sum(swap) as total_swap,"
                               "   sum(commission) as total_commission,"
                               "   sum(profit) as total_profit,"
                               "   sum(profit+swap+commission) as net_profit,"
                               "   sum(case when profit > 0 then 1 else 0 end) as win_trades,"
                               "   sum(case when profit < 0 then 1 else 0 end) as loss_trades "
                               "   FROM DEALS "
                               "   WHERE SYMBOL <> '' and SYMBOL is not NULL "
                               "   GROUP BY SYMBOL"
                               "   ) as r");

Sonuç:

Trade statistics by Symbol
      [name] [trades] [gross_profit] [gross_loss] [total_commission] [total_swap] [total_profit] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor]
[0] "AUDUSD"      112      503.20000   -568.00000           -8.83000    -24.64000      -64.80000    -98.27000           70            42          -0.57857      62.50000       37.50000          7.18857      -13.52381         0.88592
[1] "EURCHF"      125      607.71000   -956.85000          -11.77000    -45.02000     -349.14000   -405.93000           54            71          -2.79312      43.20000       56.80000         11.25389      -13.47676         0.63512
[2] "EURJPY"      127     1078.49000  -1057.83000          -10.61000    -45.76000       20.66000    -35.71000           64            63           0.16268      50.39370       49.60630         16.85141      -16.79095         1.01953
[3] "EURUSD"      233     1685.60000  -1386.80000          -41.00000    -83.76000      298.80000    174.04000          127           106           1.28240      54.50644       45.49356         13.27244      -13.08302         1.21546
[4] "GBPCHF"      125     1881.37000  -1424.72000          -22.60000    -51.56000      456.65000    382.49000           80            45           3.65320      64.00000       36.00000         23.51712      -31.66044         1.32052
[5] "GBPJPY"      127     1943.43000  -1776.67000          -18.84000    -52.46000      166.76000     95.46000           76            51           1.31307      59.84252       40.15748         25.57145      -34.83667         1.09386
[6] "GBPUSD"      121     1668.50000  -1438.20000           -7.96000    -49.93000      230.30000    172.41000           77            44           1.90331      63.63636       36.36364         21.66883      -32.68636         1.16013
[7] "USDCAD"       99      405.28000   -475.47000           -8.68000    -31.68000      -70.19000   -110.55000           51            48          -0.70899      51.51515       48.48485          7.94667       -9.90563         0.85238
[8] "USDCHF"      206     1588.32000  -1241.83000          -17.98000    -65.92000      346.49000    262.59000          131            75           1.68199      63.59223       36.40777         12.12458      -16.55773         1.27902
[9] "USDJPY"      107      464.73000   -730.64000          -35.12000    -34.24000     -265.91000   -335.27000           50            57          -2.48514      46.72897       53.27103          9.29460      -12.81825         0.63606

İstatistikler, 10 sembolden 5'inde net kârın elde edildiğini (net_profit>0), kâr faktörünün ise 10 sembolden 6'sında pozitif olduğunu (profit_factor>1) göstermektedir. Swaplar ve komisyonlar sonucunda EURJPY’nin kârsız hale geldiği görülmektedir.


İşlemlerin giriş saatlerine göre analizi

Ticaret tek bir sembol üzerinde tek bir strateji ile yapılsa bile, işlemleri piyasaya giriş saatlerine göre analiz etmek faydalı olabilir. Bu, aşağıdaki SQL sorgusu tarafından yapılır:

//--- get trading statistics by market entry hours
   request=DatabasePrepare(db, "SELECT r.*,"
                           "   (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff,"
                           "   (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent,"
                           "   (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent,"
                           "   r.gross_profit/r.win_trades as average_profit,"
                           "   r.gross_loss/r.loss_trades as average_loss,"
                           "   (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor "
                           "FROM "
                           "   ("
                           "   SELECT HOUR_IN,"
                           "   count() as trades,"
                           "   sum(volume) as volume,"
                           "   sum(case when profit > 0 then profit else 0 end) as gross_profit,"
                           "   sum(case when profit < 0 then profit else 0 end) as gross_loss,"
                           "   sum(profit) as net_profit,"
                           "   sum(case when profit > 0 then 1 else 0 end) as win_trades,"
                           "   sum(case when profit < 0 then 1 else 0 end) as loss_trades "
                           "   FROM TRADES "
                           "   WHERE SYMBOL <> '' and SYMBOL is not NULL "
                           "   GROUP BY HOUR_IN"
                           "   ) as r");

Sonuç:

Trade statistics by entry hour
     [hour_in] [trades] [volume] [gross_profit] [gross_loss] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor]
[ 0]         0       50  5.00000      336.51000   -747.47000   -410.96000           21            29          -8.21920      42.00000       58.00000         16.02429      -25.77483         0.45020
[ 1]         1       20  2.00000      102.56000    -57.20000     45.36000           12             8           2.26800      60.00000       40.00000          8.54667       -7.15000         1.79301
[ 2]         2        6  0.60000       38.55000    -14.60000     23.95000            5             1           3.99167      83.33333       16.66667          7.71000      -14.60000         2.64041
[ 3]         3       38  3.80000      173.84000   -200.15000    -26.31000           22            16          -0.69237      57.89474       42.10526          7.90182      -12.50938         0.86855
[ 4]         4       60  6.00000      361.44000   -389.40000    -27.96000           27            33          -0.46600      45.00000       55.00000         13.38667      -11.80000         0.92820
[ 5]         5       32  3.20000      157.43000   -179.89000    -22.46000           20            12          -0.70187      62.50000       37.50000          7.87150      -14.99083         0.87515
[ 6]         6       18  1.80000       95.59000   -162.33000    -66.74000           11             7          -3.70778      61.11111       38.88889          8.69000      -23.19000         0.58886
[ 7]         7       14  1.40000       38.48000   -134.30000    -95.82000            9             5          -6.84429      64.28571       35.71429          4.27556      -26.86000         0.28652
[ 8]         8       42  4.20000      368.48000   -322.30000     46.18000           24            18           1.09952      57.14286       42.85714         15.35333      -17.90556         1.14328
[ 9]         9      118 11.80000     1121.62000   -875.21000    246.41000           72            46           2.08822      61.01695       38.98305         15.57806      -19.02630         1.28154
[10]        10      206 20.60000     2280.59000  -2021.80000    258.79000          115            91           1.25626      55.82524       44.17476         19.83122      -22.21758         1.12800
[11]        11      138 13.80000     1377.02000   -994.18000    382.84000           84            54           2.77420      60.86957       39.13043         16.39310      -18.41074         1.38508
[12]        12      152 15.20000     1247.56000  -1463.80000   -216.24000           84            68          -1.42263      55.26316       44.73684         14.85190      -21.52647         0.85227
[13]        13       64  6.40000      778.27000   -516.22000    262.05000           36            28           4.09453      56.25000       43.75000         21.61861      -18.43643         1.50763
[14]        14       62  6.20000      536.93000   -427.47000    109.46000           38            24           1.76548      61.29032       38.70968         14.12974      -17.81125         1.25606
[15]        15       50  5.00000      699.92000   -413.00000    286.92000           28            22           5.73840      56.00000       44.00000         24.99714      -18.77273         1.69472
[16]        16       88  8.80000      778.55000   -514.00000    264.55000           51            37           3.00625      57.95455       42.04545         15.26569      -13.89189         1.51469
[17]        17       76  7.60000      533.92000  -1019.46000   -485.54000           44            32          -6.38868      57.89474       42.10526         12.13455      -31.85813         0.52373
[18]        18       52  5.20000      237.17000   -246.78000     -9.61000           24            28          -0.18481      46.15385       53.84615          9.88208       -8.81357         0.96106
[19]        19       52  5.20000      407.67000   -150.36000    257.31000           30            22           4.94827      57.69231       42.30769         13.58900       -6.83455         2.71129
[20]        20       18  1.80000       65.92000    -89.09000    -23.17000            9             9          -1.28722      50.00000       50.00000          7.32444       -9.89889         0.73993
[21]        21       10  1.00000       41.86000    -32.38000      9.48000            7             3           0.94800      70.00000       30.00000          5.98000      -10.79333         1.29277
[22]        22       14  1.40000       45.55000    -83.72000    -38.17000            6             8          -2.72643      42.85714       57.14286          7.59167      -10.46500         0.54408
[23]        23        2  0.20000        1.20000     -1.90000     -0.70000            1             1          -0.35000      50.00000       50.00000          1.20000       -1.90000         0.63158

En fazla sayıda işlemin 9 ile 16 saatleri (dahil) arasında gerçekleştirildiği açıkça görülmektedir. Diğer saatlerdeki ticaret daha az işlem sağlamaktadır ve çoğunlukla kârsızdır. DatabaseExecute() fonksiyonuna ilişkin örnekten bu üç sorgu türünü içeren kaynak kodunun tamamını bulabilirsiniz.


DatabasePrint() ile Uzman Danışman günlüğüne zahmetsiz veri çıkışı

Önceki örneklerde, sorgu sonuçlarını görüntülemek için her girdiyi yapıya okumamız ve onları teker teker yazdırmamız gerekiyordu. Yalnızca tabloyu veya sorgu sonuçlarını görüntülemek için bir yapı oluşturmak çoğu zaman uğraştırıcı olabilir. Bu gibi durumlar için DatabasePrint() fonksiyonu eklenmiştir:

long  DatabasePrint(
   int     database,          // database handle received in DatabaseOpen
   string  table_or_sql,      // a table or an SQL query
   uint    flags              // combination of flags
   );

Sadece mevcut tablonun değil, aynı zamanda tablo olarak görüntülenebilen sorgu sonuçlarının da kolayca yazdırılmasına olanak sağlar. Örneğin, aşağıdaki sorguyu kullanarak DEALS tablosunu görüntüleyelim:

   DatabasePrint(db,"SELECT * from DEALS",0);

Sonuç (tablonun ilk 10 satırı görüntüleniyor):

  #|       ID ORDER_ID POSITION_ID       TIME TYPE ENTRY SYMBOL VOLUME   PRICE  PROFIT  SWAP COMMISSION MAGIC REASON
---+----------------------------------------------------------------------------------------------------------------
  1| 34429573        0           0 1567723199    2     0           0.0     0.0  2000.0   0.0        0.0     0      0 
  2| 34432127 51447238    51447238 1567749603    0     0 USDCAD    0.1  1.3232     0.0   0.0      -0.16   500      3 
  3| 34432128 51447239    51447239 1567749603    1     0 USDCHF    0.1 0.98697     0.0   0.0      -0.16   500      3 
  4| 34432450 51447565    51447565 1567753200    0     0 EURUSD    0.1 1.10348     0.0   0.0      -0.18   400      3 
  5| 34432456 51447571    51447571 1567753200    1     0 AUDUSD    0.1 0.68203     0.0   0.0      -0.11   400      3 
  6| 34432879 51448053    51448053 1567756800    1     0 USDCHF    0.1 0.98701     0.0   0.0      -0.16   600      3 
  7| 34432888 51448064    51448064 1567756800    0     0 USDJPY    0.1 106.962     0.0   0.0      -0.16   600      3 
  8| 34435147 51450470    51450470 1567765800    1     0 EURUSD    0.1 1.10399     0.0   0.0      -0.18   100      3 
  9| 34435152 51450476    51450476 1567765800    0     0 GBPUSD    0.1 1.23038     0.0   0.0       -0.2   100      3 
 10| 34435154 51450479    51450479 1567765800    1     0 EURJPY    0.1  118.12     0.0   0.0      -0.18   200      3 


Verileri içe/dışa aktarma

Ayrıca, verileri içe/dışa aktarmayı kolaylaştırmak için de DatabaseImport() ve DatabaseExport() fonksiyonları eklenmiştir. Bu fonksiyonlar, CSV dosyaları ve ZIP arşivlerindeki verilerle çalışılmasına olanak sağlar.

DatabaseImport(), verileri belirtilen tabloya aktarır. Belirtilen ada sahip bir tablo yoksa, otomatik olarak oluşturulur. Oluşturulan tablodaki alanların adları ve türleri, dosyada bulunan verilere göre otomatik olarak tanımlanır. 

DatabaseExport() ise tablonun veya sorgu sonuçlarının bir dosyaya kaydedilmesine olanak tanır. Sorgu sonuçları dışa aktarılırsa, SQL sorgusu "SELECT" veya "select" ile başlamalıdır. Başka bir deyişle, SQL sorgusu veritabanının durumunu değiştiremez, aksi takdirde DatabaseExport() başarısız olur.

MQL5 Dokümantasyonundan fonksiyonların tam açıklamasına bakabilirsiniz.


Optimizasyon sonuçlarını veritabanına kaydetme

Veritabanlarıyla çalışmak için fonksiyonlar, optimizasyon sonuçlarını işlemek için de kullanılabilir. Şimdi platformda standart olarak bulunan MACD Sample Uzman Danışmanını kullanarak framelerle test sonuçlarının nasıl alınacağını ve tüm optimizasyon kriterlerinin değerlerinin tek bir dosyaya nasıl kaydedileceğini göreceğiz. Bunu yapmak için, ticaret istatistiklerini göndermek adına OnTester() metodunu tanımladığımız CDatabaseFrames sınıfını oluşturalım:

//+------------------------------------------------------------------+
//| Tester function - sends trading statistics in a frame            |
//+------------------------------------------------------------------+
void               CDatabaseFrames::OnTester(const double OnTesterValue)
  {
//--- stats[] array to send data to a frame
   double stats[16];
//--- allocate separate variables for trade statistics to achieve more clarity
   int    trades=(int)TesterStatistics(STAT_TRADES);
   double win_trades_percent=0;
   if(trades>0)
      win_trades_percent=TesterStatistics(STAT_PROFIT_TRADES)*100./trades;
//--- fill in the array with test results
   stats[0]=trades;                                       // number of trades
   stats[1]=win_trades_percent;                           // percentage of profitable trades
   stats[2]=TesterStatistics(STAT_PROFIT);                // net profit
   stats[3]=TesterStatistics(STAT_GROSS_PROFIT);          // gross profit
   stats[4]=TesterStatistics(STAT_GROSS_LOSS);            // gross loss
   stats[5]=TesterStatistics(STAT_SHARPE_RATIO);          // Sharpe Ratio
   stats[6]=TesterStatistics(STAT_PROFIT_FACTOR);         // profit factor
   stats[7]=TesterStatistics(STAT_RECOVERY_FACTOR);       // recovery factor
   stats[8]=TesterStatistics(STAT_EXPECTED_PAYOFF);       // trade mathematical expectation
   stats[9]=OnTesterValue;                                // custom optimization criterion
//--- calculate built-in standard optimization criteria
   double balance=AccountInfoDouble(ACCOUNT_BALANCE);
   double balance_plus_profitfactor=0;
   if(TesterStatistics(STAT_GROSS_LOSS)!=0)
      balance_plus_profitfactor=balance*TesterStatistics(STAT_PROFIT_FACTOR);
   double balance_plus_expectedpayoff=balance*TesterStatistics(STAT_EXPECTED_PAYOFF);
   double balance_plus_dd=balance/TesterStatistics(STAT_EQUITYDD_PERCENT);
   double balance_plus_recoveryfactor=balance*TesterStatistics(STAT_RECOVERY_FACTOR);
   double balance_plus_sharpe=balance*TesterStatistics(STAT_SHARPE_RATIO);
//--- add the values of built-in optimization criteria
   stats[10]=balance;                                     // Balance
   stats[11]=balance_plus_profitfactor;                   // Balance+ProfitFactor
   stats[12]=balance_plus_expectedpayoff;                 // Balance+ExpectedPayoff
   stats[13]=balance_plus_dd;                             // Balance+EquityDrawdown
   stats[14]=balance_plus_recoveryfactor;                 // Balance+RecoveryFactor
   stats[15]=balance_plus_sharpe;                         // Balance+Sharpe
//--- create a data frame and send it to the terminal
   if(!FrameAdd(MQLInfoString(MQL_PROGRAM_NAME)+"_stats", STATS_FRAME, trades, stats))
      Print("Frame add error: ", GetLastError());
   else
      Print("Frame added, Ok");
  }

Bu sınıfın ikinci önemli metodu OnTesterDeinit()'dir. Optimizasyondan sonra alınan tüm frameleri okur ve istatistikleri veritabanına kaydeder:

//+------------------------------------------------------------------+
//| TesterDeinit function - read data from frames                    |
//+------------------------------------------------------------------+
void               CDatabaseFrames::OnTesterDeinit(void)
  {
//--- take the EA name and optimization end time
   string filename=MQLInfoString(MQL_PROGRAM_NAME)+" "+TimeToString(TimeCurrent())+".sqlite";
   StringReplace(filename, ":", "."); // ":" character is not allowed in file names
//--- open/create the database in the common terminal folder
   int db=DatabaseOpen(filename, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE | DATABASE_OPEN_COMMON);
   if(db==INVALID_HANDLE)
     {
      Print("DB: ", filename, " open failed with code ", GetLastError());
      return;
     }
   else
      Print("DB: ", filename, " opened successful");
//--- create the PASSES table
   if(!DatabaseExecute(db, "CREATE TABLE PASSES("
                       "PASS               INT PRIMARY KEY NOT NULL,"
                       "TRADES             INT,"
                       "WIN_TRADES         INT,"
                       "PROFIT             REAL,"
                       "GROSS_PROFIT       REAL,"
                       "GROSS_LOSS         REAL,"
                       "SHARPE_RATIO       REAL,"
                       "PROFIT_FACTOR      REAL,"
                       "RECOVERY_FACTOR    REAL,"
                       "EXPECTED_PAYOFF    REAL,"
                       "ON_TESTER          REAL,"
                       "BL_BALANCE         REAL,"
                       "BL_PROFITFACTOR    REAL,"
                       "BL_EXPECTEDPAYOFF  REAL,"
                       "BL_DD              REAL,"
                       "BL_RECOVERYFACTOR  REAL,"
                       "BL_SHARPE          REAL );"))
     {
      Print("DB: ", filename, " create table failed with code ", GetLastError());
      DatabaseClose(db);
      return;
     }
//--- variables for reading frames
   string        name;
   ulong         pass;
   long          id;
   double        value;
   double        stats[];
//--- move the frame pointer to the beginning
   FrameFirst();
   FrameFilter("", STATS_FRAME); // select frames with trading statistics for further work
//--- variables to get statistics from the frame
   int trades;
   double win_trades_percent;
   double profit, gross_profit, gross_loss;
   double sharpe_ratio, profit_factor, recovery_factor, expected_payoff;
   double ontester_value;                              // custom optimization criterion
   double balance;                                     // Balance
   double balance_plus_profitfactor;                   // Balance+ProfitFactor
   double balance_plus_expectedpayoff;                 // Balance+ExpectedPayoff
   double balance_plus_dd;                             // Balance+EquityDrawdown
   double balance_plus_recoveryfactor;                 // Balance+RecoveryFactor
   double balance_plus_sharpe;                         // Balance+Sharpe
//--- block the database for the period of bulk transactions
   DatabaseTransactionBegin(db);
//--- go through frames and read data from them
   bool failed=false;
   while(FrameNext(pass, name, id, value, stats))
     {
      Print("Got pass #", pass);
      trades=(int)stats[0];
      win_trades_percent=stats[1];
      profit=stats[2];
      gross_profit=stats[3];
      gross_loss=stats[4];
      sharpe_ratio=stats[5];
      profit_factor=stats[6];
      recovery_factor=stats[7];
      expected_payoff=stats[8];
      stats[9];
      balance=stats[10];
      balance_plus_profitfactor=stats[11];
      balance_plus_expectedpayoff=stats[12];
      balance_plus_dd=stats[13];
      balance_plus_recoveryfactor=stats[14];
      balance_plus_sharpe=stats[15];
      PrintFormat("VALUES (%d,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%G,%.2f,%.2f,%2.f,%.2f,%.2f,%.2f,%.2f)",
                  pass, trades, win_trades_percent, profit, gross_profit, gross_loss, sharpe_ratio,
                  profit_factor, recovery_factor, expected_payoff, ontester_value, balance,
                  balance_plus_profitfactor, balance_plus_expectedpayoff, balance_plus_dd, balance_plus_recoveryfactor,
                  balance_plus_sharpe);
      //--- write data to the table
      string request=StringFormat("INSERT INTO PASSES (PASS,TRADES,WIN_TRADES, PROFIT,GROSS_PROFIT,GROSS_LOSS,"
                                  "SHARPE_RATIO,PROFIT_FACTOR,RECOVERY_FACTOR,EXPECTED_PAYOFF,ON_TESTER,"
                                  "BL_BALANCE,BL_PROFITFACTOR,BL_EXPECTEDPAYOFF,BL_DD,BL_RECOVERYFACTOR,BL_SHARPE) "
                                  "VALUES (%d, %d, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %G, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f)",
                                  pass, trades, win_trades_percent, profit, gross_profit, gross_loss, sharpe_ratio,
                                  profit_factor, recovery_factor, expected_payoff, ontester_value, balance,
                                  balance_plus_profitfactor, balance_plus_expectedpayoff, balance_plus_dd, balance_plus_recoveryfactor,
                                  balance_plus_sharpe);

      //--- execute a query to add a pass to the PASSES table
      if(!DatabaseExecute(db, request))
        {
         PrintFormat("Failed to insert pass %d with code %d", pass, GetLastError());
         failed=true;
         break;
        }
     }
//--- if an error occurred during a transaction, inform of that and complete the work
   if(failed)
     {
      Print("Transaction failed, error code=", GetLastError());
      DatabaseTransactionRollback(db);
      DatabaseClose(db);
      return;
     }
   else
     {
      DatabaseTransactionCommit(db);
      Print("Transaction done successful");
     }
//--- close the database
   if(db!=INVALID_HANDLE)
     {
      Print("Close database with handle=", db);
      DatabaseClose(db);
     }

Ardından, MACD Sample Uzman Danışmanında DatabaseFrames.mqh dosyasını dahil edelim ve CDatabaseFrames sınıf değişkenini bildirelim:

#define MACD_MAGIC 1234502
//---
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include "DatabaseFrames.mqh"
...
CDatabaseFrames DB_Frames;

Sonrasında da Uzman Danışmanın sonuna yalnızca optimizasyon sırasında çağrılacak üç fonksiyon ekliyoruz:

//+------------------------------------------------------------------+
//| TesterInit function                                              |
//+------------------------------------------------------------------+
int OnTesterInit()
  {
   return(DB_Frames.OnTesterInit());
  }
//+------------------------------------------------------------------+
//| TesterDeinit function                                            |
//+------------------------------------------------------------------+
void OnTesterDeinit()
  {
   DB_Frames.OnTesterDeinit();
  }
//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
   double ret=0;
   //--- create a custom optimization criterion as the ratio of a net profit to a relative balance drawdown
   if(TesterStatistics(STAT_BALANCE_DDREL_PERCENT)!=0)
      ret=TesterStatistics(STAT_PROFIT)/TesterStatistics(STAT_BALANCE_DDREL_PERCENT);
   DB_Frames.OnTester(ret);
   return(ret);
  }
//+------------------------------------------------------------------+

Optimizasyonu başlatıyoruz ve ortak terminal klasöründe ticaret istatistiklerini içerecek bir veritabanı dosyası oluşturuyoruz:

CDatabaseFrames::OnTesterInit: optimization launched at 15:53:27
DB: MACD Sample Database 2020.01.20 15.53.sqlite opened successful
Transaction done successful
Close database with handle=65537
Database stored in file 'MACD Sample Database 2020.01.20 15.53.sqlite'

Oluşturulan veritabanı dosyası ileri çalışma için MetaEditor'da veya başka bir MQL5 uygulamasında açılabilir.

MetaEditor'da veritabanıyla çalışma

Böylece, herhangi bir veriyi istediğiniz biçimde hazırlayarak ileri analiz veya diğer yatırımcılarla paylaşmak amacıyla kullanabilirsiniz. Kaynak kodunu, optimizasyon parametrelerini içeren ini dosyasını ve yürütme sonucunu aşağıda ekli MACD.zip arşivinde bulabilirsiniz.


İndeksleri kullanarak sorgu yürütmeyi optimize etme

SQL kullanmanın güzelliği (sadece SQLite’ta değil, tüm uygulamalarında), yordamsal bir dil değil, bildirimsel bir dil olmasıdır. SQL'de programlama yaparken, sisteme NASIL hesaplaması gerektiğini değil NE hesaplamak istediğinizi söylersiniz. “Nasıl” sorusunu çözme görevi, SQL veritabanı motorundaki sorgu planlayıcı alt sistemine devredilir.

Belirli bir SQL sorgusunu yürütmek için yüzlerce veya binlerce farklı algoritma olabilir. Bazıları diğerlerinden daha hızlı çalışsa da, bu algoritmaların tümü doğru cevabı verecektir. SQLite'taki sorgu planlayıcı, her SQL ifadesi için en hızlı ve en verimli algoritmayı seçmeye çalışır.

SQLite'taki sorgu planlayıcı çoğu zaman doğru algoritmayı seçme konusunda iyi bir iş çıkarır. Ancak, sorgu planlayıcının elinden gelenin en iyisini yapması için indekslere ihtiyacı vardır. Bu indeksler genellikle programcılar tarafından eklenmelidir. Ancak, bazen sorgu planlayıcı, optimal olmayan bir algoritma seçecektir. Bu durumlarda, programcılar, sorgu planlayıcının daha iyi bir iş çıkarmasına yardımcı olmak adına ek ipuçları sağlamak isteyebilir.

İndeksler olmadan arama

Belirtilen 14 alanı içeren DEALS tablosuna sahip olduğumuzu varsayalım. Aşağıda bu tablonun ilk 10 girdisi bulunmaktadır.

rowid
ID ORDER_ID POSITION_ID TIME TYPE ENTRY SYMBOL VOLUME PRICE PROFIT SWAP COMMISSION MAGIC REASON
1 34429573 0 0 1567723199 2 0 0 0 2000 0 0 0 0
2 34432127 51447238 51447238 1567749603 0 0 USDCAD 0.1 1.3232 0 0 0.16 500 3
3 34432128 51447239 51447239 1567749603 1 0 USDCHF 0.1 0.98697 0 0 0.16 500 3
4 34432450 51447565 51447565 1567753200 0 0 EURUSD 0.1 1.10348 0 0 0.18 400 3
5 34432456 51447571 51447571 1567753200 1 0 AUDUSD 0.1 0.68203 0 0 0.11 400 3
6 34432879 51448053 51448053 1567756800 1 0 USDCHF 0.1 0.98701 0 0 0.16 600 3
7 34432888 51448064 51448064 1567756800 0 0 USDJPY 0.1 106.962 0 0 0.16 600 3
8 34435147 51450470 51450470 1567765800 1 0 EURUSD 0.1 1.10399 0 0 0.18 100 3
9 34435152 51450476 51450476 1567765800 0 0 GBPUSD 0.1 1.23038 0 0 0.2 100 3
10 34435154 51450479 51450479 1567765800 1 0 EURJPY 0.1 118.12 0 0 0.18 200 3

Tablo işlem geçmişinin analizi için işlem özellikleri bölümünden (DEAL_TIME_MSC, DEAL_COMMENT ve DEAL_EXTERNAL_ID hariç) gerekli verileri içermektedir. Her tablo her zaman ilk olarak int tipi rowid anahtarını ve devamında girdi alanlarını içerir. rowid anahtarları otomatik olarak oluşturulurlar, tablo içerisinde benzersizdirler ve yeni girdiler eklendikçe artarlar. Girdilerin silinmesi numaralandırma boşluklarına neden olabilir, ancak tablo satırları her zaman artan rowid düzeninde depolanır.

Belirli bir pozisyonla ilgili işlemleri bulmamız gerekirse, ID=51447571 için, aşağıdaki sorguyu yazmalıyız:

SELECT * FROM deals WHERE position_id=51447571

Bu durumda, tam bir tablo taraması gerçekleştirilir - tüm satırlar taranır ve POSITION_ID alanının her satırda 51447571 değerine eşit olup olmadığı kontrol edilir. Bu koşulu sağlayan satırlar, sorgu sonuçlarında görüntülenecektir. Tablo milyonlarca girdi içeriyorsa, arama önemli miktarda uzun sürebilir. Aramayı position_id=51447571 yerine rowid=5 koşuluna göre yapsaydık, arama süresi (tablonun boyutuna bağlı olarak) binlerce hatta milyonlarca kez azalabilirdi.

SELECT * FROM deals WHERE rowid=5

rowid=5 olan satır position_id=51447571 olduğundan, sorguyu yürütmenin sonucu aynı olacaktır. rowid değerlerinin artan şekilde sıralanması ve sonucu almak için ikili arama kullanılması nedeniyle hızlanma sağlanır. Ancak ne yazık ki rowid değeriyle arama yapmak bizim için uygun değildir, çünkü biz istediğimiz position_id değerine sahip girdilerle ilgileniyoruz.

İndekse göre arama

Sorgunun yürütülmesini daha verimli hale getirmek için, aşağıdaki sorguyu kullanarak POSITION_ID alanı için bir indeks eklememiz gerekir:

 CREATE INDEX Idx1 ON deals(position_id)

Bu durumda, iki sütunlu ayrı bir tablo oluşturulur. İlk sütun artan şekilde sıralanmış POSITION_ID değerlerinden, ikinci sütun ise rowid değerlerinden oluşur.

POSITION_ID rowid
0 1
51447238 2
51447239 3
51447565 4
51447571 5
51448053 6
51448064 7
51450470 8
51450476 9
51450479 10

rowid’nin sırası, örneğimizde korunmuş olmasına rağmen, sizin durumunuzda halihazırda ihlal edilmiş olabilir. Çünkü zamanla pozisyonlar açıldıkça POSITION_ID de artacaktır.

Artık POSITION_ID alanı için indekse sahip olduğumuza göre, sorgumuz

SELECT * FROM deals WHERE position_id=51447571

farklı çalışacaktır. İlk olarak, POSITION_ID sütunundaki Idx1 indeksinde ikili arama yapılır ve koşulla eşleşen tüm rowid’ler bulunur. Ardından, orijinal DEALS tablosunda ikinci ikili arama yapılır. Bu arama da bilinen rowid değerlerine göre tüm girdileri arar. Böylece, büyük tablonun tek tam taraması iki ardışık arama ile değiştirilmiş olur - önce indekse, ardından da tablo satır numaralarına göre. Bu, tabloda çok sayıda satır olması durumunda, bu tür sorguların yürütme süresinin binlerce kez azaltılmasına olanak tanır.

Genel kural: Tablo alanlarından bazıları genellikle arama/karşılaştırma/sıralama için kullanılıyorsa, bu alanlar için indeksler oluşturulması önerilir.

DEALS tablosu ayrıca SYMBOL, MAGIC ve ENTRY alanlarını da içermektedir. Bu alanlar üzerinde seçim yapılması gerekiyorsa, onlar için uygun indeksler oluşturulması mantıklıdır. Örneğin:

CREATE INDEX Idx2 ON deals(symbol)
CREATE INDEX Idx3 ON deals(magic)
CREATE INDEX Idx4 ON deals(entry)

İndeksler oluşturmanın ek bellek gerektirdiği ve her girdi ekleme/silme ile yeniden indeksleme gerçekleştirildiği akılda tutulmalıdır. Ayrıca, birden çok alana dayalı çoklu indeksler de oluşturulabilir. Örneğin, USDCAD sembolünde MAGIC=500 olan Uzman Danışman tarafından gerçekleştirilen tüm işlemleri seçmek istiyorsak aşağıdaki sorguyu oluşturabiliriz:

SELECT * FROM deals WHERE magic=500 AND symbol='USDCAD'

Bu durumda MAGIC ve SYMBOL alanları için çoklu indeks oluşturabiliriz:

CREATE INDEX Idx5 ON deals(magic, symbol)

Böylece, aşağıdaki indeks tablosu oluşturulur (şematik olarak ilk 10 satır gösterilmiştir):

MAGIC SYMBOL rowid
100 EURUSD 4
100 EURUSD 10
100 EURUSD 20
100 GBPUSD 5
100 GBPUSD 11
200 EURJPY 6
200 EURJPY 12
200 EURJPY 22
200 GBPJPY 7
200 GBPJPY 13

Oluşturulan çoklu indekste girdiler önce MAGIC alanına göre, ardından da SYMBOL alanına göre bloklar halinde sıralanır. Dolayısıyla, AND sorguları varlığında, indekste arama ilk olarak MAGIC sütununda gerçekleştirilecek, sonrasında ise SYMBOL sütunu kontrol edilecektir. Her iki koşul da karşılanırsa, rowid, orijinal tablo aramasında kullanılacak sonuç kümesine eklenir. Genel olarak konuşursak, böyle bir çoklu indeks, önce SYMBOL alanının, ardından MAGIC alanının kontrol edildiği sorgular için uygun değildir:

SELECT * FROM deals WHERE  symbol='USDCAD' AND magic=500 

Her ne kadar sorgu planlayıcı bu gibi durumlarda nasıl doğru hareket edeceğini anlayıp aramayı doğru sırada yapsa da, tablo ve sorgu tasarımındaki hataları her zaman otomatik olarak düzelteceğini ummak akıllıca olmaz.

OR sorguları

Çoklu indeksler yalnızca AND sorguları için uygundur. Örneğin, MAGIC=100 olan Uzman Danışman tarafından veya EURUSD üzerinde gerçekleştirilen tüm işlemleri bulmak istediğimizi varsayalım:

SELECT * FROM deals WHERE magic=100 OR symbol='EURUSD'

Bu durumda, iki ayrı arama yapılır. Ardından bulunan rowid’ler, orijinal tablodaki satır numaralarına göre son arama için ortak bir seçimde birleştirilir.

SELECT * FROM deals WHERE magic=100 
SELECT * FROM deals WHERE symbol='EURUSD'

Burada da OR sorgusunun her iki alanının da indekse sahip olması gerekir, aksi takdirde arama tam tablo taramasına neden olur.

Sıralama

Sıralamayı hızlandırmak adına, sorgu sonuçlarının sıralanacağı alanlar için de indekslerin var olması akıllıcadır. Örneğin, tüm EURUSD işlemlerini işlem zamanına göre sıralanmış olarak seçmemiz gerektiğini varsayalım:

SELECT * FROM deals symbol='EURUSD' ORDER BY time

Bu durumda, TIME alanı için bir indeks oluşturmalıyız. İndeks ihtiyacı tablonun boyutuna bağlıdır. Tabloda girdi sayısı azsa, indeksleme gözle görülür bir zaman kazandırmayacaktır.

Burada sorgu optimizasyonunun yalnızca temellerini ele aldık. Daha iyi anlamak adına SQLite geliştiricilerinin web sitesindeki Query Planning bölümünden başlayarak bu konuyu incelemenizi öneririz.


Veritabanlarıyla çalışmanın MetaEditor'a entegrasyonu

MetaTrader 5 platformu sürekli geliştirilmektedir. MQL5 diline SQL sorguları için yerel destek ekledik. Veritabanı oluşturma, veriler ekleme ve silme ve toplu işlemler gerçekleştirme dahil olmak üzere veritabanlarıyla çalışmak için MetaEditor'a yeni işlevsellikler entegre ettik. Veritabanı oluşturma, MQL5 Sihirbazı kullanılarak standart bir şekilde gerçekleştirilir. Dosya ve tablo adını belirtmeniz ve gerekli tüm alanları türleriyle birlikte eklemeniz yeterlidir.

MQL Sihirbazında bir veritabanı oluşturma

Ardından, tabloyu verilerle doldurabilir, arama ve seçim yapabilir, SQL sorgularını tanıtabilirsiniz... Böylece veritabanlarıyla yalnızca MQL5 programlarından değil manuel olarak da çalışabilirsiniz. Bunun için üçüncü taraf uygulamalara başvurmanıza gerek yoktur.

SQLite'ın MetaTrader'a girişi, yatırımcılara büyük miktarda veriyi hem programlı hem de manuel olarak işlemek için yeni fırsatlar sunmaktadır. Ayrıca, bu işlevselliklerin kullanımının mümkün olduğu kadar pratik hale gelmesine ve hız açısından diğer çözümlerden daha düşük olmamasını sağlamak adına elimizden gelenin en iyisini yaptık. SQL sorgu dilini öğrenin ve onu çalışmalarınızda uygulayın.

MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/7463

Ekli dosyalar |
SqLiteTest.zip (2708.45 KB)
trades.sqlite (340 KB)
MACD.zip (8.27 KB)
DatabaseRead.mq5 (10.11 KB)
DatabasePrepare.mq5 (35.02 KB)
DatabaseExecute.mq5 (64.83 KB)
Görselleştirin! R dilinin plot fonksiyonuna benzer MQL5 grafik kütüphanesi Görselleştirin! R dilinin plot fonksiyonuna benzer MQL5 grafik kütüphanesi
Ticaret modellerini incelerken grafikler şeklinde görselleştirme büyük önem taşımaktadır. R ve Python gibi bilim topluluğu arasında popüler olan programlama dilleri görselleştirme için özel plot fonksiyonuna sahiptir. Bu fonksiyon, ticaret modellerinin çizgiler, nokta dağılımları ve histogramlar şeklinde görselleştirilmesine olanak sağlar. MQL5’te de aynısı CGraphics sınıfı kullanılarak yapılabilir.
Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 09): Kavramsal sıçrama (II) Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 09): Kavramsal sıçrama (II)
Bu makalede ticaret arayüzümüzü kayan pencereye yerleştireceğiz. Önceki makalede, şablonların kayan pencerede kullanılmasına olanak sağlayan temel bir sistem oluşturmuştuk.
MetaTrader 5'te DirectX’i kullanarak 3D grafikler nasıl oluşturulur? MetaTrader 5'te DirectX’i kullanarak 3D grafikler nasıl oluşturulur?
3D grafikler, gizli modellerin görselleştirilmesine olanak sağladıkları için büyük miktarda veriyi analiz etmek adına çok uygundur. Doğrudan MQL5 diliyle DireсtX fonksiyonları kullanılarak üç boyutlu nesneler oluşturulabilir. Böylece, MetaTrader 5 için herhangi bir karmaşıklıkta programlar, hatta 3D oyunlar oluşturmak bile mümkün hale gelir. Basit üç boyutlu şekiller çizerek 3D grafikleri öğrenmeye başlayın.
Parabolic SAR göstergesine dayalı bir ticaret sistemi nasıl geliştirilir? Parabolic SAR göstergesine dayalı bir ticaret sistemi nasıl geliştirilir?
Bu makalede de en popüler göstergeleri kullanarak ticaret sistemleri oluşturma konulu serimize devam ediyoruz. Bu sefer Parabolic SAR göstergesinden bahsedeceğiz. Ticarette nasıl yararlı olabileceğini anlamak adına bu göstergeyi ayrıntılı olarak inceleyeceğiz ve basit stratejilerle MetaTrader 5 işlem platformu için ona dayalı bir ticaret sistemi geliştireceğiz.