English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
Piyasa Analizi için Veritabanlarının Pratik Uygulaması

Piyasa Analizi için Veritabanlarının Pratik Uygulaması

MetaTrader 5Entegrasyon | 15 Aralık 2021, 09:49
80 0
Alexander
Alexander

Giriş

Verilerle çalışmak, hem bağımsız hem de ağ uygulamaları için modern yazılımların ana görevi haline geldi. Bu sorunu çözmek için özel bir yazılım oluşturuldu. Bunlar, bilgisayar depolama ve işlemeleri için verileri yapılandırabilen, sistematikleştirebilen ve düzenleyebilen Veritabanı Yönetim Sistemleridir (DBMS). Bu yazılımlar, üretimden finans ve telekomünikasyona kadar tüm sektörlerdeki bilgi faaliyetlerinin temelidir. 

Alım satım işlemine gelince, analistlerin çoğu çalışmalarında veritabanlarını kullanmaz. Ancak böyle bir çözümün işe yaraması gereken görevler vardır. 

Bu makale böyle bir görevi içerir: Veritabanından veri kaydeden ve yükleyen tick göstergesi.

BuySellVolume Algoritması 

BuySellVolume - Daha da basit bir algoritma ile göstergeye verdiğim bu basit ad: İki ardışık tick'in (tick1 ve tick2) zamanını (t) ve fiyatını (p) alın. Aralarındaki farkı hesaplayalım:

Δt = t2 - t1     (saniye)
Δp = p2 - p1    (nokta)

Hacim değeri şu formül kullanılarak hesaplanır:

v2 = Δp / Δt

Dolayısıyla hacmimiz, fiyatın hareket ettiği nokta sayısıyla doğru orantılı ve bunun için harcanan zamanla ters orantılıdır. Δt = 0 ise, bunun yerine 0,5 değeri alınır. Böylece, piyasadaki alıcı ve satıcılara ilişkin bir tür faaliyet değeri elde ederiz. 

1. Veritabanı kullanmadan gösterge uygulaması

İlk önce belirtilen işlevselliğe sahip, ancak veritabanıyla etkileşimi olmayan bir göstergeyi göz önünde bulundurmanın mantıklı olacağını düşünüyorum. Bence en iyi çözüm, uygun hesaplamaları yapacak bir temel sınıf ve veritabanı ile etkileşim gerçekleştirmek için türevlerini oluşturmaktır. Bunu uygulamak için AdoSuite kitaplığına ihtiyacımız olacak. Bu nedenle, bağlantıya tıklayın ve indirin.

Önce, BsvEngine.mqh dosyasını oluşturun ve AdoSuite veri sınıflarını bağlayın:

#include <Ado\Data.mqh>

Ardından, veritabanıyla çalışma dışında gerekli tüm işlevleri uygulayacak bir temel gösterge sınıfı oluşturun. Bu, aşağıdaki gibi görünür:

Listeleme 1.1

//+------------------------------------------------------------------+
// BuySellVolume indicator class (without storing to database)       |
//+------------------------------------------------------------------+
class CBsvEngine
  {
private:
   MqlTick           TickBuffer[];     // ticks buffer
   double            VolumeBuffer[];   // volume buffer
   int               TicksInBuffer;    // number of ticks in the buffer

   bool              DbAvailable;      // indicates, whether it's possible to work with the database

   long              FindIndexByTime(const datetime &time[],datetime barTime,long left,long right);

protected:

   virtual string DbConnectionString() { return NULL; }
   virtual bool DbCheckAvailable() { return false; }
   virtual CAdoTable *DbLoadData(const datetime startTime,const datetime endTime) { return NULL; }
   virtual void DbSaveData(CAdoTable *table) { return; }

public:
                     CBsvEngine();

   void              Init();
   void              ProcessTick(double &buyBuffer[],double &sellBuffer[]);
   void              LoadData(const datetime startTime,const datetime &time[],double &buyBuffer[],double &sellBuffer[]);
   void              SaveData();
  };

Çözümün verimliliğini artırmak için verilerin özel arabelleklere (TickBuffer ve VolumeBuffer) yerleştirildiğini ve belirli bir süre sonra veritabanına yüklendiğini belirtmek isterim. 

Sınıf uygulama sırasını ele alalım. Oluşturucu ile başlayalım...

Listeleme 1.2

//+------------------------------------------------------------------+
// Constructor                                                       |
//+------------------------------------------------------------------+
CBsvEngine::CBsvEngine(void)
  {
// Initially, can be placed up to 500 ticks in a buffer
   ArrayResize(TickBuffer,500);
   ArrayResize(VolumeBuffer,500);
   TicksInBuffer=0;
   DbAvailable=false;
  } 

Burada her şeyin net olması gerektiğini düşünüyorum: Değişkenler başlatılır ve arabelleklerin ilk boyutları ayarlanır.

Ardından Init() yönteminin uygulanması gelir:

 Listeleme 1.3

//+-------------------------------------------------------------------+
// Function, called in the OnInit event                               |
//+-------------------------------------------------------------------+
CBsvEngine::Init(void)
  {
   DbAvailable=DbCheckAvailable();
   if(!DbAvailable)
      Alert("Unable to work with database. Working offline.");
  }

Burada veritabanı ile çalışmanın mümkün olup olmadığını kontrol ediyoruz. Temel sınıfta DbCheckAvailable() her zaman false değerini döndürür; zira veritabanıyla çalışma yalnızca türetilmiş sınıflardan yapılacaktır. Sanırım, DbConnectionString(),, DbCheckAvailable(), DbLoadData(), DbSaveData() işlevlerinin henüz herhangi bir özel anlama sahip olmadığını fark etmiş olabilirsiniz. Bunlar, belirli bir veritabanına bağlanmak için alt öğelerde geçersiz kıldığımız işlevlerdir.

Liste 1.4'te, yeni tick'in ortaya çıkmasında çağrılan ProcessTick() işlevinin uygulaması gösterilmiştir, arabelleğe tick ekler ve göstergemiz için değerleri hesaplar. Bunu yapmak için işleve 2 gösterge arabelleği iletilir: Biri alıcıların faaliyetlerini, diğeri satıcıların faaliyetlerini depolamak için kullanılır. 

  Liste 1.4

//+------------------------------------------------------------------+
// Processing incoming tick and updating indicator data              |
//+------------------------------------------------------------------+
CBsvEngine::ProcessTick(double &buyBuffer[],double &sellBuffer[])
  {
// if it's not enough of allocated buffer for ticks, let's increase it
   int bufSize=ArraySize(TickBuffer);
   if(TicksInBuffer>=bufSize)
     {
      ArrayResize(TickBuffer,bufSize+500);
      ArrayResize(VolumeBuffer,bufSize+500);
     }

// getting the last tick and writing it to the buffer
   SymbolInfoTick(Symbol(),TickBuffer[TicksInBuffer]);

   if(TicksInBuffer>0)
     {
      // calculating the time difference
      int span=(int)(TickBuffer[TicksInBuffer].time-TickBuffer[TicksInBuffer-1].time);
      // calculating the price difference
      int diff=(int)MathRound((TickBuffer[TicksInBuffer].bid-TickBuffer[TicksInBuffer-1].bid)*MathPow(10,_Digits));

      // calculating the volume. If the tick came in the same second as the previous one, we consider the time equal to 0.5 seconds
      VolumeBuffer[TicksInBuffer]=span>0 ?(double)diff/(double)span :(double)diff/0.5;

      // filling the indicator buffers with data
      int index=ArraySize(buyBuffer)-1;
      if(diff>0) buyBuffer[index]+=VolumeBuffer[TicksInBuffer];
      else sellBuffer[index]+=VolumeBuffer[TicksInBuffer];
     }

   TicksInBuffer++;
  }

LoadData() işlevi, belirli bir süre için geçerli zaman dilimi boyunca veritabanından veri yükler. 

  Liste 1.5

//+------------------------------------------------------------------+
// Loading historical data from the database                         |
//+------------------------------------------------------------------+
CBsvEngine::LoadData(const datetime startTime,const datetime &time[],double &buyBuffer[],double &sellBuffer[])
  {
// if the database is inaccessible, then does not load the data
   if(!DbAvailable) return;

// getting data from the database
   CAdoTable *table=DbLoadData(startTime,TimeCurrent());

   if(CheckPointer(table)==POINTER_INVALID) return;

// filling buffers with received data
   for(int i=0; i<table.Records().Total(); i++)
     {
      // get the record with data
      CAdoRecord *row=table.Records().GetRecord(i);

      // getting the index of corresponding bar
      MqlDateTime mdt;
      mdt=row.GetValue(0).ToDatetime();
      long index=FindIndexByTime(time,StructToTime(mdt));

      // filling buffers with data
      if(index!=-1)
        {
         buyBuffer[index]+=row.GetValue(1).ToDouble();
         sellBuffer[index]+=row.GetValue(2).ToDouble();
        }
     }
   delete table;
  }

LoadData(), ardıllarda geçersiz kılınması ve üç sütunlu (çubuk zamanı, alıcıların arabellek değeri ve satıcıların arabellek değeri) bir tablo döndürmesi gereken DbLoadData() işlevini çağırır.

Burada başka bir işlev kullanılır - FindIndexByTime(). Bu makaleyi yazarken standart kitaplıkta zaman serileri için ikili arama işlevini bulamadım; bu nedenle bunu kendim yazdım.

Ve son olarak, verileri depolamak için SaveData() işlevi: 

Liste 1.6

//+---------------------------------------------------------------------+
// Saving data from the TickBuffer and VolumeBuffer buffers to database |
//+---------------------------------------------------------------------+
CBsvEngine::SaveData(void)
  {
   if(DbAvailable)
     {
      // creating a table for passing data to SaveDataToDb
      CAdoTable *table=new CAdoTable();
      table.Columns().AddColumn("Time", ADOTYPE_DATETIME);
      table.Columns().AddColumn("Price", ADOTYPE_DOUBLE);
      table.Columns().AddColumn("Volume", ADOTYPE_DOUBLE);

      // filling table with data from buffers
      for(int i=1; i<TicksInBuffer; i++)
        {
         CAdoRecord *row=table.CreateRecord();
         row.Values().GetValue(0).SetValue(TickBuffer[i].time);
         row.Values().GetValue(1).SetValue(TickBuffer[i].bid);
         row.Values().GetValue(2).SetValue(VolumeBuffer[i]);

         table.Records().Add(row);
        }

      // saving data to database
      DbSaveData(table);

      if(CheckPointer(table)!=POINTER_INVALID)
         delete table;
     }

// writing last tick to the beginning, to have something to compare
   TickBuffer[0] = TickBuffer[TicksInBuffer - 1];
   TicksInBuffer = 1;
  }

Gördüğümüz gibi yöntemde gösterge için gerekli bilgilerle bir tablo oluşturulur ve bu, veriyi veritabanına kaydeden DbSaveData() işlevine iletilir.Kayıttan sonra yalnızca arabelleği temizleriz.

Böylece, çerçevemiz hazır; şimdi Liste 1.7'ye yani BuySellVolume.mq5 göstergesinin nasıl göründüğüne bakalım: 

Liste 1.7

// including file with the indicator class
#include "BsvEngine.mqh"

//+------------------------------------------------------------------+
//| Indicator Properties                                             |
//+------------------------------------------------------------------+
#property indicator_separate_window

#property indicator_buffers 2
#property indicator_plots   2

#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  Red
#property indicator_width1  2

#property indicator_type2   DRAW_HISTOGRAM
#property indicator_color2  SteelBlue
#property indicator_width2  2

//+------------------------------------------------------------------+
//| Data Buffers                                                     |
//+------------------------------------------------------------------+
double ExtBuyBuffer[];
double ExtSellBuffer[];

//+------------------------------------------------------------------+
//| Variables                                                        |
//+------------------------------------------------------------------+
// declaring indicator class
CBsvEngine bsv;
//+------------------------------------------------------------------+
//| OnInit
//+------------------------------------------------------------------+
int OnInit()
  {
// setting indicator properties
   IndicatorSetString(INDICATOR_SHORTNAME,"BuySellVolume");
   IndicatorSetInteger(INDICATOR_DIGITS,2);
// buffer for 'buy'
   SetIndexBuffer(0,ExtBuyBuffer,INDICATOR_DATA);
   PlotIndexSetString(0,PLOT_LABEL,"Buy");
// buffer for 'sell'
   SetIndexBuffer(1,ExtSellBuffer,INDICATOR_DATA);
   PlotIndexSetString(1,PLOT_LABEL,"Sell");

// setting the timer to clear buffers with ticks
   EventSetTimer(60);

   return(0);
  }
//+------------------------------------------------------------------+
//| OnDeinit
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| OnCalculate
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
// processing incoming tick
   bsv.ProcessTick(ExtBuyBuffer,ExtSellBuffer);

   return(rates_total);
  }
//+------------------------------------------------------------------+
//| OnTimer
//+------------------------------------------------------------------+
void OnTimer()
  {
// saving data
   bsv.SaveData();
  }

Bence çok basit. Göstergede sınıfın yalnızca iki işlevi çağrılır: ProcessTick() ve SaveData(). ProcessTick() işlevi hesaplamalar için kullanılır ve verileri kaydetmese de, arabelleği ticklerle sıfırlamak için SaveData() işlevi gereklidir.

Derlemeye çalışalım, "Ve işte!" - Gösterge değerleri göstermeye başladı:

 

 Şekil 1. GBPUSD M1'deki veritabanına bağlantısı olmayan BuySellVolume göstergesi

Mükemmel! Tickler işaretliyor, gösterge hesaplıyor. Bu tip bir çözümün avantajı çalışması için yalnızca göstergenin kendisine (ex5) ihtiyacımız olması; başka bir şeye ihtiyacımız yok. Ancak, zaman dilimini veya enstrümanı değiştirirken veya terminali kapattığınızda, veriler geri döndürülemez bir şekilde kaybolur. Bunu önlemek için, göstergemize veri kaydetme ve yükleme özelliğini nasıl ekleyebileceğimizi görelim.

2. SQL Server 2008'e Bağlanma

Şu anda bilgisayarımda yüklü iki DBMSd var; SQL Server 2008 ve Db2 9.7. Birçok okuyucunun SQL Server'a Db2'den daha aşina olduğunu varsaydığım için SQL Server'ı seçtim.

Başlamak için, SQL Server 2008 için yeni bir BuySellVolume veritabanı (SQL Server Management Studio veya başka herhangi bir araçla) ve temel CBsvEngine sınıfını içeren dosyayı ekleyeceğimiz yeni bir BsvMsSql.mqh dosyası oluşturalım:

#include "BsvEngine.mqh"

SQL Server, OLE DB sürücüsü ile donatılmıştır; böylece AdoSuite kitaplığında bulunan OleDb sağlayıcısı aracılığıyla onunla çalışabiliriz. Bunu yapmak için gerekli sınıfları ekleyin:

#include <Ado\Providers\OleDb.mqh>

Ve aslında türetilmiş bir sınıf oluşturun:

Liste 2.1

//+------------------------------------------------------------------+
// Class of the BuySellVolume indicator, linked with MsSql database  |
//+------------------------------------------------------------------+
class CBsvSqlServer : public CBsvEngine
  {
protected:

   virtual string    DbConnectionString();
   virtual bool      DbCheckAvailable();
   virtual CAdoTable *DbLoadData(const datetime startTime,const datetime endTime);
   virtual void      DbSaveData(CAdoTable *table);

  };

İhtiyacımız olan tek şey doğrudan veritabanıyla çalışmaktan sorumlu olan dört işlevi geçersiz kılmak. Baştan başlayalım. DbConnectionString() yöntemi, veritabanına bağlanmak için bir dize döndürür.

Benim durumumda bu, şu şekilde görünür:

Liste 2.2

//+------------------------------------------------------------------+
// Returns the string for connection to database                     |
//+------------------------------------------------------------------+
string CBsvSqlServer::DbConnectionString(void)
  {
   return "Provider=SQLOLEDB;Server=.\SQLEXPRESS;Database=BuySellVolume;Trusted_Connection=yes;";
  }

Bağlantı dizesinden, yerel makinede bulunan SQLEXPRESS sunucusuyla MS SQL OLE-DB sürücüsü aracılığıyla çalıştığımızı görüyoruz.BuySellVolume veritabanına Windows kimlik doğrulamasını kullanarak bağlanıyoruz (diğer seçenek - açıkça kullanıcı adını ve parolayı girmektir).

Bir sonraki adım, DbCheckAvailable() işlevini uygulamaktır. Ama ilk olarak, bu işlevin gerçekten ne yapması gerektiğine bakalım.

Bunun, veritabanı ile çalışma olasılığını kontrol ettiği söylendi. Bu bir dereceye kadar doğrudur. Aslında, asıl amacı mevcut enstrüman için veri depolamak üzere bir tablo olup olmadığını kontrol etmek ve olmaması halinde söz konusu tabloyu oluşturmaktır.Bu işlemler hata ile sonuçlanacaksa false değerini döndürecektir; bu da tablodan gösterge verilerinin okunması ve yazılmasının yok sayılacağı ve göstergenin bizim halihazırda uyguladığımıza benzer şekilde çalışacağı anlamına gelir. (bkz. Liste 1.7).

Verilerle SQL Server'ın saklı yordamları (SP) aracılığıyla çalışmayı öneriyorum. Peki neden bunları kullanıyorum? Sadece bunu yapmak istedim.Bu elbette bir zevk meselesi, ancak SP'leri kullanmanın koda sorgu yazmaktan daha güzel bir çözüm olduğunu düşünüyorum (bu aynı zamanda derlemek için daha fazla zaman gerektirir, ancak dinamik sorgular kullanılacağı için bu durum için geçerli değildir :)

DbCheckAvailable() için saklı yordam aşağıdaki gibi görünür:

Liste 2.3

CREATE PROCEDURE [dbo].[CheckAvailable]
        @symbol NVARCHAR(30)
AS
BEGIN
        SET NOCOUNT ON;
        
        -- If there is no table for instrument, we create it
        IF OBJECT_ID(@symbol, N'U') IS NULL
        EXEC ('
                -- Creating table for the instrument
                CREATE TABLE ' + @symbol + ' (Time DATETIME NOT NULL,
                        Price REAL NOT NULL, 
                        Volume REAL NOT NULL);
                
                -- Creating index for the tick time
                CREATE INDEX Ind' + @symbol + '
                ON  ' + @symbol + '(Time);
        ');
END

İstenen tablonun veritabanında olmaması halinde bir tablo oluşturan dinamik sorgunun (dize olarak) oluşturulup yürütüldüğünü görüyoruz. Saklı yordam oluşturulduğunda, DbCheckAvailable() işleviyle işlem yapma zamanıdır: 

  Liste 2.4

//+------------------------------------------------------------------+
// Checks whether it's possible to connect to database               |
//+------------------------------------------------------------------+
bool CBsvSqlServer::DbCheckAvailable(void)
  {
// working with ms sql via Oledb provider
   COleDbConnection *conn=new COleDbConnection();
   conn.ConnectionString(DbConnectionString());

// using stored procedure to create a table
   COleDbCommand *cmd=new COleDbCommand();
   cmd.CommandText("CheckAvailable");
   cmd.CommandType(CMDTYPE_STOREDPROCEDURE);
   cmd.Connection(conn);

// passing parameters to stored procedure
   CAdoValue *vSymbol=new CAdoValue();
   vSymbol.SetValue(Symbol());
   cmd.Parameters().Add("@symbol",vSymbol);

   conn.Open();

// executing stored procedure
   cmd.ExecuteNonQuery();

   conn.Close();

   delete cmd;
   delete conn;

   if(CheckAdoError())
     {
      ResetAdoError();
      return false;
     }

   return true;
  }

Gördüğümüz gibi, sunucunun saklı yordamları ile çalışabiliyoruz; yalnızca CommandType özelliğini CMDTYPE_STOREDPROCEDURE olarak ayarlamamız, bunun ardından gerekli parametreleri iletmemiz ve yürütmemiz gerekiyor. Tasarlandığı gibi, bir hata durumunda DbCheckAvailable işlevi false değerini döndürür.

Şimdi, DbLoadData işlevi için bir saklı yordam yazalım. Veritabanında her tick için veri depolandığı için, gerekli dönemin her çubuğu için onlardan veri oluşturmamız gerekiyor.Ben aşağıdaki yordamı yaptım:

  Liste 2.5

CREATE PROCEDURE [dbo].[LoadData]
        @symbol NVARCHAR(30),   -- instrument
        @startTime DATETIME,    -- beginning of calculation
        @endTime DATETIME,      -- end of calculation
        @period INT             -- chart period (in minutes)
AS
BEGIN
        SET NOCOUNT ON;
        
        -- converting inputs to strings for passing to a dynamic query
        DECLARE @sTime NVARCHAR(20) = CONVERT(NVARCHAR, @startTime, 112) + ' ' + CONVERT(NVARCHAR, @startTime, 114),
                @eTime NVARCHAR(20) = CONVERT(NVARCHAR, @endTime, 112) + ' ' + CONVERT(NVARCHAR, @endTime, 114),
                @p NVARCHAR(10) = CONVERT(NVARCHAR, @period);
                
        EXEC('        
                SELECT DATEADD(minute, Bar * ' + @p + ', ''' + @sTime + ''') AS BarTime, 
                        SUM(CASE WHEN Volume > 0 THEN Volume ELSE 0 END) as Buy,
                        SUM(CASE WHEN Volume < 0 THEN Volume ELSE 0 END) as Sell 
                FROM 
                (
                        SELECT DATEDIFF(minute, ''' + @sTime + ''', TIME) / ' + @p + ' AS Bar,
                                Volume 
                        FROM ' + @symbol + '
                        WHERE Time >= ''' + @sTime + ''' AND Time <= ''' + @eTime + '''
                ) x 
                GROUP BY Bar 
                ORDER BY 1;
        ');
END 

Unutulmaması gereken tek şey ilk doldurulan çubuğun açılış zamanının @startTime olarak iletilmesi gerektiğidir; aksi takdirde ofseti alırız.

Aşağıdaki listeden DbLoadData() uygulamasını ele alalım:

Liste 2.6

//+------------------------------------------------------------------+
// Loading data from the database                                    |
//+------------------------------------------------------------------+
CAdoTable *CBsvSqlServer::DbLoadData(const datetime startTime,const datetime endTime)
  {
// working with ms sql via Oledb provider
   COleDbConnection *conn=new COleDbConnection();
   conn.ConnectionString(DbConnectionString());

// using stored procedure to calculate data
   COleDbCommand *cmd=new COleDbCommand();
   cmd.CommandText("LoadData");
   cmd.CommandType(CMDTYPE_STOREDPROCEDURE);
   cmd.Connection(conn);

// passing parameters to stored procedure
   CAdoValue *vSymbol=new CAdoValue();
   vSymbol.SetValue(Symbol());
   cmd.Parameters().Add("@symbol",vSymbol);

   CAdoValue *vStartTime=new CAdoValue();
   vStartTime.SetValue(startTime);
   cmd.Parameters().Add("@startTime",vStartTime);

   CAdoValue *vEndTime=new CAdoValue();
   vEndTime.SetValue(endTime);
   cmd.Parameters().Add("@endTime",vEndTime);

   CAdoValue *vPeriod=new CAdoValue();
   vPeriod.SetValue(PeriodSeconds()/60);
   cmd.Parameters().Add("@period",vPeriod);

   COleDbDataAdapter *adapter=new COleDbDataAdapter();
   adapter.SelectCommand(cmd);

// creating table and filling it with data, that were returned by stored procedure
   CAdoTable *table=new CAdoTable();
   adapter.Fill(table);

   delete adapter;
   delete conn;

   if(CheckAdoError())
     {
      delete table;
      ResetAdoError();
      return NULL;
     }

   return table;
  }

Burada saklı yordamı, iletim araçlarını, hesaplama başlangıç tarihini, hesaplama bitiş tarihini ve dakika cinsinden mevcut grafik dönemini çağırıyoruz. Ardından COleDbDataAdapter sınıfını kullanarak sonucu, göstergemizin arabelleklerinin doldurulacağı tabloya okuyoruz.

Ve son adım, DbSaveData() işlevini uygulamak olacaktır:

  Liste 2.7

CREATE PROCEDURE [dbo].[SaveData]
        @symbol NVARCHAR(30),
        @ticks NVARCHAR(MAX)
AS
BEGIN
        EXEC('
                DECLARE @xmlId INT,
                        @xmlTicks XML = ''' + @ticks + ''';

                EXEC sp_xml_preparedocument 
                        @xmlId OUTPUT, 
                        @xmlTicks;
                
                -- read data from xml to table
                INSERT INTO ' + @symbol + '
                SELECT *
                FROM OPENXML( @xmlId, N''*/*'', 0)
                WITH
                (
                        Time DATETIME N''Time'', 
                        Price REAL N''Price'',
                        Volume REAL N''Volume'' 
                );

                EXEC sp_xml_removedocument @xmlId;
        ');
END

Lütfen, depolanan tick verileriyle xml'in yordama @ticks parametresi olarak iletilmesi gerektiğini unutmayın.Bu karar performans nedenlerinden dolayı alınmıştır; yordamı bir kez çağırmak ve oraya 20 tick göndermek, onu 20 kez çağırarak oraya bir tick iletmekten daha kolaydır. Aşağıdaki listede xml dizesinin nasıl oluşturulması gerektiğini görelim: 

Liste 2.8

//+------------------------------------------------------------------+
// Saving data to database                                           |
//+------------------------------------------------------------------+
CBsvSqlServer::DbSaveData(CAdoTable *table)
  {
// if there is nothing to write, then return
   if(table.Records().Total()==0) return;

// forming the xml with data to pass into the stored procedure
   string xml;
   StringAdd(xml,"<Ticks>");

   for(int i=0; i<table.Records().Total(); i++)
     {
      CAdoRecord *row=table.Records().GetRecord(i);

      StringAdd(xml,"<Tick>");

      StringAdd(xml,"<Time>");
      MqlDateTime mdt;
      mdt=row.GetValue(0).ToDatetime();
      StringAdd(xml,StringFormat("%04u%02u%02u %02u:%02u:%02u",mdt.year,mdt.mon,mdt.day,mdt.hour,mdt.min,mdt.sec));
      StringAdd(xml,"</Time>");

      StringAdd(xml,"<Price>");
      StringAdd(xml,DoubleToString(row.GetValue(1).ToDouble()));
      StringAdd(xml,"</Price>");

      StringAdd(xml,"<Volume>");
      StringAdd(xml,DoubleToString(row.GetValue(2).ToDouble()));
      StringAdd(xml,"</Volume>");

      StringAdd(xml,"</Tick>");
     }

   StringAdd(xml,"</Ticks>");

// working with ms sql via Oledb provider
   COleDbConnection *conn=new COleDbConnection();
   conn.ConnectionString(DbConnectionString());

// using stored procedure to write data
   COleDbCommand *cmd=new COleDbCommand();
   cmd.CommandText("SaveData");
   cmd.CommandType(CMDTYPE_STOREDPROCEDURE);
   cmd.Connection(conn);

   CAdoValue *vSymbol=new CAdoValue();
   vSymbol.SetValue(Symbol());
   cmd.Parameters().Add("@symbol",vSymbol);

   CAdoValue *vTicks=new CAdoValue();
   vTicks.SetValue(xml);
   cmd.Parameters().Add("@ticks",vTicks);

   conn.Open();

// executing stored procedure
   cmd.ExecuteNonQuery();

   conn.Close();

   delete cmd;
   delete conn;

   ResetAdoError();
  }

Bu işlevin iyi yarısı, bu dizenin oluşumunu xml ile alır. Ayrıca, bu dize saklı yordama iletilir ve orada ayrıştırılır.

Şimdilik SQL Server 2008 ile etkileşimin uygulanması tamamlandı; BuySellVolume SqlServer.mq5 göstergesini uygulayabiliriz.

Göreceğiniz gibi, bu sürümün uygulanması, daha sonra tartışılacak bazı değişiklikler dışında, sonuncunun uygulanmasına benzerdir.

  Liste 2.9

// including file with the indicator class
#include "BsvSqlServer.mqh"

//+------------------------------------------------------------------+
//| Indicator Properties                                             |
//+------------------------------------------------------------------+
#property indicator_separate_window

#property indicator_buffers 2
#property indicator_plots   2

#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  Red
#property indicator_width1  2

#property indicator_type2   DRAW_HISTOGRAM
#property indicator_color2  SteelBlue
#property indicator_width2  2

//+------------------------------------------------------------------+
//| Input parameters of indicator                                    |
//+------------------------------------------------------------------+
input datetime StartTime=D'2010.04.04'; // start calculations from this date

//+------------------------------------------------------------------+
//| Data Buffers                                                     |
//+------------------------------------------------------------------+
double ExtBuyBuffer[];
double ExtSellBuffer[];

//+------------------------------------------------------------------+
//| Variables                                                        |
//+------------------------------------------------------------------+
// declaring indicator class
CBsvSqlServer bsv;
//+------------------------------------------------------------------+
//| OnInit
//+------------------------------------------------------------------+
int OnInit()
  {
// setting indicator properties
   IndicatorSetString(INDICATOR_SHORTNAME,"BuySellVolume");
   IndicatorSetInteger(INDICATOR_DIGITS,2);
// buffer for 'buy'
   SetIndexBuffer(0,ExtBuyBuffer,INDICATOR_DATA);
   PlotIndexSetString(0,PLOT_LABEL,"Buy");
// buffer for 'sell'
   SetIndexBuffer(1,ExtSellBuffer,INDICATOR_DATA);
   PlotIndexSetString(1,PLOT_LABEL,"Sell");

// calling the Init function of indicator class
   bsv.Init();

// setting the timer to load ticks into database
   EventSetTimer(60);

   return(0);
  }
//+------------------------------------------------------------------+
//| OnDeinit
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
// if there are unsaved data left, then save them
   bsv.SaveData();
  }
//+------------------------------------------------------------------+
//| OnCalculate
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   if(prev_calculated==0)
     {
      // calculating the time of the nearest bar
      datetime st[];
      CopyTime(Symbol(),Period(),StartTime,1,st);
      // loading data
      bsv.LoadData(st[0],time,ExtBuyBuffer,ExtSellBuffer);
     }

// processing incoming tick
   bsv.ProcessTick(ExtBuyBuffer,ExtSellBuffer);

   return(rates_total);
  }
//+------------------------------------------------------------------+
//| OnTimer
//+------------------------------------------------------------------+
void OnTimer()
  {
// saving data
   bsv.SaveData();
  }

Göze çarpan ilk fark, StartTime giriş parametresinin varlığıdır. Bu parametre, gösterge için veri yükleme aralığını sınırlayacak şekilde tasarlanmıştır. Gerçek şu ki, aslında geçersiz veriler bizi ilgilendirmese de, çok miktarda verinin hesaplanması uzun sürebilir.

İkinci fark, bsv değişkeninin türünün bir başkasıyla değiştirilmesidir.

Üçüncü fark, gösterge verilerinin ilk hesaplamasına veri yüklenmesi ve OnInit() içindeki Init() işlevi ve OnDeinit() içindeki SaveData() işlevinin eklenmesidir.

Şimdi göstergeyi derlemeye çalışalım ve sonucu görelim: 

 

Şekil 2. EURUSD M15 üzerinde SQL Server 2008 veritabanına bağlı BuySellVolume göstergesi

Tamamlandı! Artık verilerimiz kaydedildi; zaman dilimleri arasında serbestçe geçiş yapabiliriz.

3. SQLite 3.6'ya Bağlanma

"Gereğinden çok uğraşmak" - Sanırım ne demek istediğimi anladınız. Bu görev için SQL Server'ı dağıtmak oldukça saçma. Tabii ki bu DBMS zaten yüklüyse ve aktif olarak kullanıyorsanız bu, tercih edilen seçenek olabilir. Ama ya tüm bu teknolojilerden uzak ve çözümün işe yaraması için minimum çaba sarf etmek isteyen birine bir gösterge vermek isterseniz ne olacak?

İşte, öncekilerden farklı olarak, dosya-sunucusu mimarisine sahip bir veritabanıyla çalışan üçüncü gösterge sürümü.Bu yaklaşımda, çoğu durumda veritabanı çekirdeği ile yalnızca birkaç DLL'ye ihtiyacınız olacak.

Daha önce SQLite ile hiç çalışmamış olmama rağmen, sadeliği, hızı ve basitliği nedeniyle onu seçtim. Başlangıçta, yalnızca C++ ve TCL'de yazılmış programlardan çalışmak için API'miz vardı, ancak üçüncü taraf geliştiricilerin ODBC sürücüsünü ve ADO.NET sağlayıcısını da buldum.AdoSuite, ODBC aracılığıyla veri kaynaklarıyla çalışmaya izin verdiği için ODBC sürücüsünü indirip yüklemek daha iyi olacak gibi görünüyor. Ama anladığım kadarıyla desteği bir yıldan uzun bir süre önce sonlandırıldı ve ayrıca ADO.NET'in teorik olarak daha hızlı çalışması gerekiyor.

O halde göstergemizden ADO.NET sağlayıcısı aracılığıyla SQLite ile çalışabilmemiz için neler yapılması gerektiğine bakalım.

İki işlem bizi hedefimize ulaştıracaktır:

  • İlk olarak, sağlayıcıyı indirmeli ve yüklemelisiniz. İndirme bağlantısının bulunduğu resmi web sitesi şu şekildedir: http://sqlite.phxsoftware.com/. Tüm bu dosyalardan System.Data.SQLite.dll. derlemesiyle ilgileniyoruz. Bu, SQLite çekirdeğinin kendisini ve ADO.NET sağlayıcısını içerir. Kolaylık sağlaması için bu kitaplığı makaleye ekledim. İndirdikten sonra, Windows Gezgini'nde (!) Windows\assembly klasörünü açın. Şekil 3'te gösterildiği gibi bir derleme listesi görmeniz gerekir:

 

Şekil 3. Gezgin, GAC'yi (genel derleme önbelleği) bir liste olarak görüntüleyebilir


Şimdi (!) System.Data.SQLite.dll öğesini bu klasöre sürükleyin ve bırakın.

Sonuç olarak, derleme genel derleme önbelleğine (GAC) yerleştirilir; bu durumda onunla çalışabiliriz:


Şekil 4. GAC'de yüklü System.Data.SQLite.dll

Şimdilik sağlayıcı kurulumu tamamlandı.

  • Yapmamız gereken ikinci hazırlık, SQLite ile çalışmak için AdoSuite sağlayıcısı yazmaktır. Bu, hızlı ve kolay yazılmıştır (yaklaşık 15 dakikamı aldı). Makalenin daha uzun olmaması için kodunu buraya yazmayacağım. Kodu, bu makaleye eklenmiş dosyalarda bulabilirsiniz.

Şimdi her şey tamamlandığına göre bir gösterge yazmaya başlayabilirsiniz. SQLite veritabanı için MQL5\Files klasöründe yeni bir boş dosya oluşturalım. SQLite dosya uzantısı konusunda seçici değil; bu nedenle onu basitçe BuySellVolume.sqlite olarak adlandıralım.

Aslında, dosyayı oluşturmak gerekli değildir: Bağlantı dizesinde belirtilen veritabanını ilk sorguladığınızda bu, otomatik olarak oluşturulur (bkz. Liste 3.2). Burada yalnızca nereden geldiğini netleştirmek için onu açık bir şekilde oluşturuyoruz.

BsvSqlite.mqh adlı yeni bir dosya oluşturun, SQLite için temel sınıfımızı ve sağlayıcımızı ekleyin: 

#include "BsvEngine.mqh"
#include <Ado\Providers\SQLite.mqh>

 Türetilmiş sınıf, adı dışında bir öncekiyle aynı forma sahiptir:

   Liste 3.1

//+------------------------------------------------------------------+
// Class of the BuySellVolume indicator, linked with SQLite database |
//+------------------------------------------------------------------+
class CBsvSqlite : public CBsvEngine
  {
protected:

   virtual string    DbConnectionString();
   virtual bool      DbCheckAvailable();
   virtual CAdoTable *DbLoadData(const datetime startTime,const datetime endTime);
   virtual void      DbSaveData(CAdoTable *table);

  };

Şimdi yöntemlerin uygulanmasına devam edelim.

DbConnectionString() aşağıdaki gibi görünür:

    Liste 3.2

//+------------------------------------------------------------------+
// Returns the string for connection to database                     |
//+------------------------------------------------------------------+
string CBsvSqlite::DbConnectionString(void)
  {
   return "Data Source=MQL5\Files\BuySellVolume.sqlite";
  }

Gördüğünüz gibi, bağlantı dizesi çok daha basit görünüyor ve yalnızca temelimizin konumunu gösteriyor.

Burada göreli yol belirtilir, ancak mutlak yola da izin verilir: "Data Source = c:\Program Files\Metatrader 5\MQL 5\Files\BuySellVolume.sqlite".

Liste 3.3'te, DbCheckAvailable() kodu gösterilmiştir. SQLite bize saklı yordamlara benzer bir şey sunmadığı için artık tüm sorgular doğrudan koda yazılır:

   Liste 3.3

//+------------------------------------------------------------------+
// Checks whether it's possible to connect to database               |
//+------------------------------------------------------------------+
bool CBsvSqlite::DbCheckAvailable(void)
  {
// working with SQLite via written SQLite provider
   CSQLiteConnection *conn=new CSQLiteConnection();
   conn.ConnectionString(DbConnectionString());

// command, that checks the availability of table for the instrument
   CSQLiteCommand *cmdCheck=new CSQLiteCommand();
   cmdCheck.Connection(conn);
   cmdCheck.CommandText(StringFormat("SELECT EXISTS(SELECT name FROM sqlite_master WHERE name = '%s')", Symbol()));

// command, that creates a table for the instrument
   CSQLiteCommand *cmdTable=new CSQLiteCommand();
   cmdTable.Connection(conn);
   cmdTable.CommandText(StringFormat("CREATE TABLE %s(Time DATETIME NOT NULL, " +
                        "Price DOUBLE NOT NULL, "+
                        "Volume DOUBLE NOT NULL)",Symbol()));

// command, that creates an index for the time
   CSQLiteCommand *cmdIndex=new CSQLiteCommand();
   cmdIndex.Connection(conn);
   cmdIndex.CommandText(StringFormat("CREATE INDEX Ind%s ON %s(Time)", Symbol(), Symbol()));

   conn.Open();

   if(CheckAdoError())
     {
      ResetAdoError();
      delete cmdCheck;
      delete cmdTable;
      delete cmdIndex;
      delete conn;
      return false;
     }

   CSQLiteTransaction *tran=conn.BeginTransaction();

   CAdoValue *vExists=cmdCheck.ExecuteScalar();

// if there is no table, we create it
   if(vExists.ToLong()==0)
     {
      cmdTable.ExecuteNonQuery();
      cmdIndex.ExecuteNonQuery();
     }

   if(!CheckAdoError()) tran.Commit();
   else tran.Rollback();

   conn.Close();

   delete vExists;
   delete cmdCheck;
   delete cmdTable;
   delete cmdIndex;
   delete tran;
   delete conn;

   if(CheckAdoError())
     {
      ResetAdoError();
      return false;
     }

   return true;
  }

Bu işlevin sonucu, SQL Server eşdeğeriyle aynıdır. Belirtmek istediğim bir husus var; o da tablo için alan türleri. İşin ilginç yanı, alan türlerinin SQLite için çok az anlamı vardır. Ayrıca, orada DOUBLE ve DATETIME veri türleri yoktur (en azından standart olanlara dahil değillerdir). Tüm değerler dize biçiminde depolanır ve ardından dinamik olarak gerekli türe yöneltilir.

Öyleyse, sütunları DOUBLE ve DATETIME olarak bildirmenin anlamı ne? İşlemin inceliklerini bilmiyorum, ancak sorguda ADO.NET bunları otomatik olarak DOUBLE ve DATETIME türlerine dönüştürür. Ancak bazı temel unsurlar olduğu için bu, her zaman doğru değildir; bunlardan biri aşağıdaki listede ortaya çıkacaktır.

O halde, aşağıdaki DbLoadData() işlevinin listesini ele alalım:

   Liste 3.4

//+------------------------------------------------------------------+
// Loading data from the database                                    |
//+------------------------------------------------------------------+
CAdoTable *CBsvSqlite::DbLoadData(const datetime startTime,const datetime endTime)
  {
// working with SQLite via written SQLite provider
   CSQLiteConnection *conn=new CSQLiteConnection();
   conn.ConnectionString(DbConnectionString());

   CSQLiteCommand *cmd=new CSQLiteCommand();
   cmd.Connection(conn);
   cmd.CommandText(StringFormat(
                   "SELECT DATETIME(@startTime, '+' || CAST(Bar*@period AS TEXT) || ' minutes') AS BarTime, "+
                   "  SUM(CASE WHEN Volume > 0 THEN Volume ELSE 0 END) as Buy, "+
                   "  SUM(CASE WHEN Volume < 0 THEN Volume ELSE 0 END) as Sell "+
                   "FROM "+
                   "("+
                   "  SELECT CAST(strftime('%%s', julianday(Time)) - strftime('%%s', julianday(@startTime)) AS INTEGER)/ (60*@period) AS Bar, "+
                   "     Volume "+
                   "  FROM %s "+
                   "  WHERE Time >= @startTime AND Time <= @endTime "+
                   ") x "+
                   "GROUP BY Bar "+
                   "ORDER BY 1",Symbol()));

// substituting parameters
   CAdoValue *vStartTime=new CAdoValue();
   vStartTime.SetValue(startTime);
   cmd.Parameters().Add("@startTime",vStartTime);

   CAdoValue *vEndTime=new CAdoValue();
   vEndTime.SetValue(endTime);
   cmd.Parameters().Add("@endTime",vEndTime);

   CAdoValue *vPeriod=new CAdoValue();
   vPeriod.SetValue(PeriodSeconds()/60);
   cmd.Parameters().Add("@period",vPeriod);

   CSQLiteDataAdapter *adapter=new CSQLiteDataAdapter();
   adapter.SelectCommand(cmd);

// creating table and filling it with data
   CAdoTable *table=new CAdoTable();
   adapter.Fill(table);

   delete adapter;
   delete conn;

   if(CheckAdoError())
     {
      delete table;
      ResetAdoError();
      return NULL;
     }

// as we get the string with date, but not the date itself, it is necessary to convert it
   for(int i=0; i<table.Records().Total(); i++)
     {
      CAdoRecord* row= table.Records().GetRecord(i);
      string strDate = row.GetValue(0).AnyToString();
      StringSetCharacter(strDate,4,'.');
      StringSetCharacter(strDate,7,'.');
      row.GetValue(0).SetValue(StringToTime(strDate));
     }

   return table;
  }

Bu işlev, MS SQL için uygulanmasıyla aynı şekilde çalışır. Ama neden işlevin sonunda bir döngü var? Evet, bu sihirli sorguda DATETIME türünü döndürme girişimlerimin tümü başarısız oldu. SQLite'ta DATETIME türünün yokluğu belirgindir; tarih yerine YYYY-AA-GG ss:dd:ss biçimindeki dize döndürülür. Ancak, StringToTime işlevi için anlaşılabilir olan bir biçime kolayca dönüştürülebilir; biz de bu avantajı kullandık.

Ve son olarak, DbSaveData() işlevi:

  Liste 3.5

//+------------------------------------------------------------------+
// Saving data to database                                           |
//+------------------------------------------------------------------+
CBsvSqlite::DbSaveData(CAdoTable *table)
  {
// if there is nothing to write, then return
   if(table.Records().Total()==0) return;

// working with SQLite via SQLite provider
   CSQLiteConnection *conn=new CSQLiteConnection();
   conn.ConnectionString(DbConnectionString());

// using stored procedure to write data
   CSQLiteCommand *cmd=new CSQLiteCommand();
   cmd.CommandText(StringFormat("INSERT INTO %s VALUES(@time, @price, @volume)", Symbol()));
   cmd.Connection(conn);

// adding parameters
   CSQLiteParameter *pTime=new CSQLiteParameter();
   pTime.ParameterName("@time");
   cmd.Parameters().Add(pTime);

   CSQLiteParameter *pPrice=new CSQLiteParameter();
   pPrice.ParameterName("@price");
   cmd.Parameters().Add(pPrice);

   CSQLiteParameter *pVolume=new CSQLiteParameter();
   pVolume.ParameterName("@volume");
   cmd.Parameters().Add(pVolume);

   conn.Open();

   if(CheckAdoError())
     {
      ResetAdoError();
      delete cmd;
      delete conn;
      return;
     }

// ! explicitly starting transaction
   CSQLiteTransaction *tran=conn.BeginTransaction();

   for(int i=0; i<table.Records().Total(); i++)
     {
      CAdoRecord *row=table.Records().GetRecord(i);

      // filling parameters with values
      CAdoValue *vTime=new CAdoValue();
      MqlDateTime mdt;
      mdt=row.GetValue(0).ToDatetime();
      vTime.SetValue(mdt);
      pTime.Value(vTime);

      CAdoValue *vPrice=new CAdoValue();
      vPrice.SetValue(row.GetValue(1).ToDouble());
      pPrice.Value(vPrice);

      CAdoValue *vVolume=new CAdoValue();
      vVolume.SetValue(row.GetValue(2).ToDouble());
      pVolume.Value(vVolume);

      // adding record
      cmd.ExecuteNonQuery();
     }

// completing transaction
   if(!CheckAdoError())
      tran.Commit();
   else tran.Rollback();

   conn.Close();

   delete tran;
   delete cmd;
   delete conn;

   ResetAdoError();
  }

Bu işlev uygulamasının ayrıntılarını ele almak istiyorum.

İlk olarak, mantıklı olmasına rağmen işlemde her şey yapılır. Ancak bu, veri güvenliği nedenleriyle yapılmadı; performans nedenlerinden dolayı yapıldı: Açık işlem olmadan bir giriş eklenirse sunucu örtük olarak bir işlem oluşturur, tabloya bir kayıt ekler ve bir işlemi kaldırır. Ve bu, her tick için yapılır! Ayrıca, giriş kaydedilirken tüm veritabanı kilitlenir! Komutların mutlaka işlem gerektirmediğini belirtmekte fayda var. Diğer yandan, bunun neden olduğunu tam olarak anlamadım. Bunun birden fazla işlemin olmamasından kaynaklandığını düşünüyorum.

İkinci olarak, bir kez bir komut oluşturuyoruz ve ardından bir döngüde parametreler atayarak yürütüyoruz. Bu, komut bir kez derlendiği için (optimize edildiği için) ve daha sonra derlenmiş bir sürümle iş yapıldığı için, bu, yine verimlilik sorunudur. 

Sadede gelelim. BuySellVolume SQLite.mq5 göstergesinin kendisine bakalım:

  Liste 3.6

// including file with the indicator class
#include "BsvSqlite.mqh"

//+------------------------------------------------------------------+
//| Indicator Properties                                             |
//+------------------------------------------------------------------+
#property indicator_separate_window

#property indicator_buffers 2
#property indicator_plots   2

#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  Red
#property indicator_width1  2

#property indicator_type2   DRAW_HISTOGRAM
#property indicator_color2  SteelBlue
#property indicator_width2  2

//+------------------------------------------------------------------+
//| Input parameters of indicator                                    |
//+------------------------------------------------------------------+
input datetime StartTime=D'2010.04.04';   // start calculations from this date

//+------------------------------------------------------------------+
//| Data Buffers
//+------------------------------------------------------------------+
double ExtBuyBuffer[];
double ExtSellBuffer[];

//+------------------------------------------------------------------+
//| Variables
//+------------------------------------------------------------------+
// declaring indicator class
CBsvSqlite bsv;
//+------------------------------------------------------------------+
//| OnInit
//+------------------------------------------------------------------+
int OnInit()
  {
// setting indicator properties
   IndicatorSetString(INDICATOR_SHORTNAME,"BuySellVolume");
   IndicatorSetInteger(INDICATOR_DIGITS,2);
// buffer for 'buy'
   SetIndexBuffer(0,ExtBuyBuffer,INDICATOR_DATA);
   PlotIndexSetString(0,PLOT_LABEL,"Buy");
// buffer for 'sell'
   SetIndexBuffer(1,ExtSellBuffer,INDICATOR_DATA);
   PlotIndexSetString(1,PLOT_LABEL,"Sell");

// calling the Init function of indicator class
   bsv.Init();

// setting the timer to load ticks into database
   EventSetTimer(60);

   return(0);
  }
//+------------------------------------------------------------------+
//| OnDeinit
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
// if there are unsaved data left, then save them
   bsv.SaveData();
  }
//+------------------------------------------------------------------+
//| OnCalculate
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   if(prev_calculated==0)
     {
      // calculating the time of the nearest bar
      datetime st[];
      CopyTime(Symbol(),Period(),StartTime,1,st);
      // loading data
      bsv.LoadData(st[0],time,ExtBuyBuffer,ExtSellBuffer);
     }

// processing incoming tick
   bsv.ProcessTick(ExtBuyBuffer,ExtSellBuffer);

   return(rates_total);
  }
//+------------------------------------------------------------------+
//| OnTimer
//+------------------------------------------------------------------+
void OnTimer()
  {
// saving data
   bsv.SaveData();
  }

Yalnızca işlev sınıfı değişti, kodun geri kalanı değişmeden kaldı.

Şu an için göstergenin üçüncü sürümünün uygulanması tamamlandı; sonucu görüntüleyebilirsiniz.

 

Şekil 5. EURUSD M5 üzerinde SQLite 3.6 veritabanına bağlı BuySellVolume göstergesi

Bu arada, SQLite'taki Sql Server Management Studio'nun aksine, veritabanlarıyla çalışmak için standart bir yardımcı program yoktur. Bu nedenle, "kara kutu" ile çalışmamak için uygun yardımcı programı üçüncü taraf geliştiricilerden indirebilirsiniz. Şahsen, SQLiteMan'i seviyorum; kullanımı kolay ve aynı zamanda gerekli tüm işlevselliğe sahip. Buradan indirebilirsiniz: http://sourceforge.net/projects/sqliteman/.

Sonuç

Bu satırları okuyorsanız, makalenin sona erdiğini anlamışsınızdır). İtiraf etmeliyim ki, bu makalenin bu kadar büyük olmasını beklemiyordum. Bu nedenle mutlaka yanıtlayacağım sorular olması kaçınılmazdır.

Gördüğümüz gibi, her çözümün avantajları ve dezavantajları var. İlk varyant bağımsızlığı, ikincisi performansı ve üçüncüsü taşınabilirliği ile farklılık gösteriyor. Hangisini seçeceğiniz size kalmış.

Uygulanan gösterge yararlı mı? Aynı şekilde.. Bu konuda karar verecek olan kişi yine sizsiniz. Bence bu, çok ilginç bir örnek.

Veda etme zamanım geldi. Sonra görüşürüz!

Arşiv içeriğinin açıklaması:

 # Filename Açıklama
1
 Sources_en.zip
 Tüm göstergelerin kaynak kodlarını ve AdoSuite kitaplığını içerir. Paketi terminalinizin uygun klasörüne açılmalıdır. Göstergelerin amacı: Veritabanı (BuySellVolume.mq5) kullanmadan SQL Server 2008 veritabanı (BuySellVolume SqlServer.mq5) ile ve SQLite veritabanı ile çalışma (BuySellVolume SQLite.mq5).
2
 BuySellVolume-DB-SqlServer.zip
 SQL Server 2008 veritabanı arşivi*
3
 BuySellVolume-DB-SQLite.zip
 SQLite veritabanı arşivi*
4
 System.Data.SQLite.zip
 SQLite veritabanı ile çalışmak için gerekli System.Data.SQLite.dll arşivi
  5  Databases_MQL5_doc_en.zip  Kaynak kodları, göstergeler ve AdoSuite kitaplığı belge arşivi

* Her iki veritabanı da aşağıdaki enstrümanlar için 5 ila 9 Nisan tarihleri arasında tick göstergesi verilerini içermektedir: AUDNZD, EURUSD, GBPUSD, USDCAD, USDCHF, USDJPY.

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

Örnek olarak MQL5'te OOP: Uyarı ve Hata Kodlarını İşleme Örnek olarak MQL5'te OOP: Uyarı ve Hata Kodlarını İşleme
Makalede, alım satım sunucusu dönüş kodlarıyla çalışmak için bir sınıf oluşturma örneği ve MQL programının çalıştırılması sırasında meydana gelen tüm hatalar açıklanmaktadır. Makaleyi okuduktan sonra MQL5'te sınıflar ve nesnelerle nasıl çalışacağınızı öğreneceksiniz. Aynı zamanda bu, hataları işlemek için uygun bir araçtır ve bu aracı özel ihtiyaçlarınıza göre daha da değiştirebilirsiniz.
Göstergeleri MQL4'ten MQL5'e Aktarma Göstergeleri MQL4'ten MQL5'e Aktarma
Bu makale, MQL4'te yazılan fiyat yapılarının MQL5'e aktarılmasına ilişkin özelliklere ayrılmıştır. Gösterge hesaplamalarını MQL4'ten MQL5'e aktarma işlemini kolaylaştırmak için mql4_2_mql5.mqh işlevler kitaplığı önerilir. Kullanımı MACD, Stokastik ve RSI göstergelerinin aktarımı temelinde açıklanmıştır.
MetaTrader 5: Bloglarda, sosyal ağlarda ve özel web sitelerinde e-posta yoluyla alım satım tahminleri ve canlı alım satım beyanları yayınlamak MetaTrader 5: Bloglarda, sosyal ağlarda ve özel web sitelerinde e-posta yoluyla alım satım tahminleri ve canlı alım satım beyanları yayınlamak
Bu makale, MetaTrader 5 kullanarak tahmin yayınlamak için hazır çözümler sunulmasına yöneliktir. Bu, MetaTrader beyanlarını yayınlamak için özel web siteleri kullanmaktan, kişinin neredeyse hiç web programlama deneyimi gerektirmeden kendi web sitesini kurmasına ve son olarak birçok okuyucunun katılmasına ve tahminleri takip etmesine izin veren bir sosyal ağ mikroblog hizmeti ile entegrasyona kadar bir dizi fikri içerir. Burada sunulan tüm çözümler %100 ücretsizdir ve temel e-posta ve ftp hizmetleri bilgisi olan herkes tarafından kurulabilir. Profesyonel barındırma ve ticari alım satım tahmini hizmetleri için aynı tekniklerin kullanılması konusunda hiçbir engel yoktur.
MQL5'te "Yılan" Oyunu Oluşturma MQL5'te "Yılan" Oyunu Oluşturma
Bu makalede, bir "Yılan" oyun programlama örneği açıklanmaktadır. MQL5'te oyun programlama, öncelikle olay işleme özellikleri nedeniyle mümkün hale geldi. Nesne yönelimli programlama bu süreci büyük ölçüde basitleştirir. Bu makalede olay işleme özelliklerini, Standart MQL5 Kitaplık sınıflarının kullanım örneklerini ve periyodik işlev çağrılarının detaylarını öğreneceksiniz.