Piyasa Analizi için Veritabanlarının Pratik Uygulaması
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!
# | 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
- Ücretsiz ticaret uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz