市場分析のための実践的なデーターベースの活用
はじめに
データを扱うことは、現代のソフトウェアのメインの業務となっています。これは、スタンドアロン系、ネットワーク系のアプリ双方において言えることです。この問題を解決するため、特別なソフトウェアが作成されました。それは、データベース管理システム(DBMS)です。コンピューター内ストレージや、その処理においてデータを整理し、構築します。これらのソフトウェアは、製造から金融、通信などすべての業界の情報活動の土台となっています。
トレーディングにおいて、多くの分析はデータベースを使用しません。しかし、ソリューションがより便利になる必要のある業務があります。
この記事は、データベースからデータをロードし、保存するティックインジケーターについて紹介します。
BuySellVolume アルゴリズム
BuySellVolume - 付けられている単純な名前以上に、簡単なアルゴリズムを持つこのインジケーターは、(ティック1、ティック2)連続する二つのティックの時間(t)、金額(p)、を取得します。それでは、二つの差を計算してみましょう:
Δt = t2 - t1 (seconds)
Δp = p2 - p1 (points)
額の値は、以下の公式を用い計算されます:
v2 = Δp / Δt
従って、額は、値段が移動したポイントの数に直接比例しており、それに費やされた時間に反比例しています。もし Δt = 0 なら、代わりに0.5の値が取得されます。従って、市場の売り手と買い手の活性値を知ることができます。
1. データベースを使用しないインジケーターの実行
データベースとの関わりのない、特定の機能を持つインジケーターをまず考察することが論理的だと思います。私の意見として、最良のソリューションは、適切な計算を行うベースクラスを作成することであり、データベースとのインタラクションの実現は、派生的なものであると思います。これを実行するために、AdoSuite ライブラリが必要となります。リンクをクリックして、ダウンロードを行ってください。
まず、BsvEngine.mqhファイルを作成し、AdoSuiteデータクラスを接続させてください。
#include <Ado\Data.mqh>
それでは、データベースとの連携以外の必要なすべての機能を実行するベースインジケータークラスを作成します。以下のようになります:
リスト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(); };
ソリューションの生産性を向上させるために、データは特別なバッファー(TickBufferとVolumeBuffer)に格納され、一定の時間後、データベース上にアップロードされるようにすることを記したいと思います。
クラスの実行順序を考察しましょう。まずコンストラクタから始めます。
リスト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; }
変数は初期化され、バッファーの初期サイズが設定され、すべてが明確にされていると思います。
次にInit()メソッドの実行となります。
リスト1.3
//+-------------------------------------------------------------------+ // Function, called in the OnInit event | //+-------------------------------------------------------------------+ CBsvEngine::Init(void) { DbAvailable=DbCheckAvailable(); if(!DbAvailable) Alert("Unable to work with database. Working offline."); }
ここでは、データベースを扱うことができるかをチェックします。 ベースクラスでは、 DbCheckAvailable()は、派生クラスのみからデータベースの操作が行われるため、常にFalseを返します。 お気づきかもしれませんが、 DbConnectionString()、DbCheckAvailable()、DbLoadData()、DbSaveData() は未だ特別な意味はありません。 これらは、特定のデータベースへ接続させるために再定義する関数であるためです。
リスト1.4では、ProcessTick()関数の実行を紹介しており、それは、新しいチークの出現時に呼ばれ、バッファーにチークを格納後、インジケーターの値を計算するというものです。これを行うためには、二つのインジケーターバッファーが関数に渡されます。一つは、買い手の活動を保管し、一方は、売り手の活動を保管します。
リスト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()関数は、特定の時間枠での現時点のタイムフレームにおけるデータをデータベースからロードします。
リスト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()関数は、後に再定義され、バーの時間、買い手のバッファー値、売り手のバッファー値の三つのカラムを持つ図表データを返します。
別の関数が、ここで使われています。それは、FindIndexByTime()関数です。 この記事の執筆時、標準ライブラリ内に時系列のバイナリ検索用関数がなかったので、私の方で作成しました。
最後に、データを保管するSaveData()関数です:
リスト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; }
メソッド内に記載されているように、図表はインジケーターに必要な情報を持ち作成され、DbSaveData関数に渡されます。記録後、バッファーを除去します。
それでは、フレームワークが完成しました。リスト1.7を参照し、BuySellVolume.mq5インジケーターがどのような形か見てみましょう:
リスト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(); }
とても簡単だと思います。インジケータ内では、クラスの二つの関数のみ呼ばれます:ProcessTick()とSaveData()関数です。ProcessTick関数は、計算に用いられ、SaveData関数は、データを保存しませんが、ティックを持つバッファーをリセットする上で必要です。
コンパイルし、「終了」しましょう - インジケータが値を表示し始めます:
図1. GBPUSD M1のデータベースへの接続のないBuySellVolumeインジケーター
素晴らしい!ティックは作動し、インジケータが計算を行っています。そのようなソリューションの利点は、作業においてインジケータのみを必要とし、それ以上のものは必要ないということです。しかしながら、タイムフレームや金融商品を変更する際、もしくは、ターミナルを終了する際、データが失われてしまいます。これを避けるため、インジケータ内にデータを保管し、ロードする方法を見てみましょう。
2. SQLサーバー2008への接続
現在私のコンピューターには、SQL Server 2008と、Db2 9.7の二つのDBMSdがインストールされています。 多くの読者がDb2よりもSQL Serverに馴染みがあると想定していますので、SQL Serverを使用しました。
始めるために、(SQL Server Management Studioか、その他の手段を用いて)SQL Server 2008にBuySellVolumeデータベースを新規作成しましょう。そして、新しいファイルBsvMsSql.mqhも作成します。これに、基礎のCBsvEngineクラスをインクルードする予定です:
#include "BsvEngine.mqh"
SQL Serverは、OLE DBドライバーが付属しており、AdoSuiteライブラリに組み込まれるOleDbプロバイダを通して扱うことができます。このために、必要なクラスをインクルードしてください:
#include <Ado\Providers\OleDb.mqh>
そして、派生クラスを作成しましょう:
リスト 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); };
必要なことは、データベースを直接扱う4つの関数の再定義することのみです。始めからスタートしていきます。DbConnectionString()メソッドは、データベースに接続する文字列型を返します。
私の場合だと以下のようになります:
リスト2.2
//+------------------------------------------------------------------+ // Returns the string for connection to database | //+------------------------------------------------------------------+ string CBsvSqlServer::DbConnectionString(void) { return "Provider=SQLOLEDB;Server=.\SQLEXPRESS;Database=BuySellVolume;Trusted_Connection=yes;"; }
接続文から、ロ−カルマシン上にあるMS SQL OLE-DBドライバーを通して、SQLEXPRESSサーバーを扱うことがわかります。(外部からのログインIDとパスワードを入力するという他のオプションか)Windowsの認証を使用し、BuyEsllVolumeデータベースに接続します。
次のステップは、DbCheckAvalibale()関数の実行です。 しかし、まずは、この関数が何を行うかを見ていきましょう。
データベースの使用可能性をチェックすると言われていました。 これはある程度正しいです。 ただ、実際は、現在の金融商品におけるデータを格納するテーブルがあるのかをチェックし、なければ作成するということが主要な目的としてあります。もしこれがエラーにて終了した場合、Falseを返します。これは、インジケータのデータのテーブルからの読み取り、書き込みが無視され、インジケータはすでに実行した内容(リスト1.7をみてください)に近い処理を行うことを意味しています。
SQL Serverの保管されているプロセッサ(SP)を経由しデータを扱ってください。 なぜそれらを使用するか、ですが、 ただ私がそうしたいからです。これは、嗜好的な問題ではあるが、SPを使用すると、動的クエリ文が使用されるため、今回は当てはまりませんが、コードでクエリ文を書くよりも高貴なソリューションになると思っています。
DbCheckAvailable() SPは以下のようになります:
リスト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
もしデータベース内に望まれるテーブルがない場合、テーブルを作成する(String型の)動的クエリ文が作成され、実行されます。SPが作成されれば、DbCheckAvailable関数を扱っていきます。
リスト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; }
ご覧の通り、サーバーのSPを扱うことができています。必要なパラメータを渡し、実行するために、CommandTypeプロパティをSCMDTYPE_STOREDPROCEDUREに設定する必要があります。考慮されていた通り、エラーの場合、DbCheckAvaliable関数はFalseを返します。
次に、DbLoadData関数のためのSPを記述していきます。 各ティックごとにデータを保存していくので、以下の処理を作成しました:
リスト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
注意を一つだけ - 始めに記述されるバーの開始時刻は@starTimeとして渡される必要があり、さもなくば、差し引き計算された値が取得されます。
DbLoadData()関数の実行を以下のリストから考察しましょう:
リスト 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; }
ここでは、SPや、値渡し用ツール、計算開始時刻、計算終了時刻、分単位の現在のチャートの時間などが呼び出されます。それから、COleDbDataAdapterクラスを使用し、インジケーターのバッファーが記載されるテーブルに挿入される結果を読み取ります。
そして、最後に、DbSaveData()関数を実行します。
リスト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
保存されたティックデータを持つXMLは、処理内に@ticksパラメータで渡される必要があることに注意してください。こ決定は、パフォーマンスの理由からなされています。それは、一回で処理を呼び出し、20ティックを送ることが、20回も呼び出し、一つのティックを渡すよりも簡単であるためです。 XML String型データが以下のリストで作成されています:
リスト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(); }
この機能の半分がXMLとSringの形成を行います。さらに、このStringは、SPに渡され、パースされます。
今のところ、SQL Server 2008との連携の実行は終了し、BuySellVolume SqlServer.mq5 インジケーターを実行します。
このバージョンの実行は、今後紹介する変更点のみは、先ほどの実行処理と類似しています。
リスト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(); }
目に止まる最初の違いは、StartTime入力パラメータの存在です。このパラメータは、インジケーターのデータのロードのインタバールを制御することを意図されています。実際は、大量のデータは、必要のないデータは関係ないですが長い計算時間を必要とします。
二つ目の違いは、bsv変数の種類が他のものに変化されているという点です。
三つ目の違いは、インジケーターデータの最初の計算におけるデータのロードが追加されており、OnInit()関数のInit()関数や、OnDeinit()関数のSaceData()関数も同様です。
それでは、インジケーターをコンパイルし、結果を見てみましょう:
図2. SQL Server 2008に接続されている、EURUSD M15のBuySellVolumeインジケーター
終了です!データが保存され、自由にタイムフレームを移行することができます。
3. SQLite 3.6への接続
「ささいなことに骨を折る」 - どう意味か理解されたかと思います。 この作業において、SQL Serverのデプロイは、むしろバカバカしいです。 もし、すでにDBMSをインストールし、使用しているなら、よりよい選択肢になるかもしれません。 しかし、もし最小限の努力で全ての技術を持たない人にインジケーターを渡したい場合どうしますか?
ここに、以前のバージョンとは異なり、ファイルサーバー間のアーキテクチャーを持つデータベースを扱うインジケーター第三版があります。この手法で、多くの場合、データベースカーネルをほんの少ししか必要としません。
SQLiteを以前扱ったことはありませんが、単純さや、スピード、軽量な点などからこれを選びました。 まず、C++やTCLで記述されているプログラムから作業するためのAPIしかありませんが、ODBCドライパやADO.NETプロバイダなどのサードパーティ開発製品を発見しています。 AdoSuite によりデータソースをODBC経由で扱うことができるので、ODBCドライバをダウンロードし、インストールすることがよい方法だと思います。しかし、その製品のサポートは、1年以上前に終了しており、加えて、ADO.NETは理論的により早く動きます。
それでは、インジケータからADO.NETを経由して、SQLiteを使用するために、なにがなされるべきかについて知りましょう。
ゴールへの二つのアクション
- まず、プロバイダーをダウンロードし、取得しなければなりません。こちらは公式のウェブサイトです。http://sqlite.phxsoftware.com/ ここでは、ダウンロード用リンクが記載されています。これら全てのファイルの中で、 System.Data.SQLite.dll. アセンブリが必要です。SQLiteカーネルと、ADO.NETを含みます。都合のため、ライブラリをこの記事に添付しています。ダウンロード後、Windows\assemblyフォルダをWindows Exploerにて開いてください。アセンブリのリストを以下の図3からご覧ください:
図 3. エクスプローラーは、 GAC (グローバルアセンブリキャッシュ)をリストで表示します。
System.Data.SQLite.dll をこのフォルダにドラッグ&ドロップしてください。
結果、アセンブリは、グローバルアセンブリキャッシュ(GAC)下におかれます。そして、使用することができます:
図4. GACにインストールされたSystem.Data.SQLite.dll
プロバイダセットアップが完了しました。
- 二番目に行うべきこととして、SQLiteを扱うAdoSuiteプロバイダを記述します。簡単に素早く記述されています。(私には15分ほどかかります)記事が膨大にならないよう、ここにはコードを貼りません。この記事に貼り付けられたファイルのコードを見ることができます。
全てが終了すれば、インジケーターの記述を始めることができます。MQL5\Files フォルダ内に新規の空ファイルをSQLiteデータベースのために作成しましょう。SQLiteは、ファイルの拡張子にうるさくはありませんので、単純にBuySellVolume.sqliteを読んでみましょう
実際、ファイルを作成する必要はありません:自動的にコネクションストリングに特化したデータベースの問い合わせをすることで作成されます。(リスト3.2をみてください) 明確にするためだけに、ここでは外部にて作成します。
BsvSqlite.mqhと呼ばれる新しいファイルを作成し、SQLiteのベースクラスとプロバイダをインクルードしてください。
#include "BsvEngine.mqh" #include <Ado\Providers\SQLite.mqh>
派生クラスは、名前以外は以前と同じものを持っています。
リスト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); };
それでは、メソッドの実行に移りましょう
DbConnectionString()は以下です。
リスト 3.2
//+------------------------------------------------------------------+ // Returns the string for connection to database | //+------------------------------------------------------------------+ string CBsvSqlite::DbConnectionString(void) { return "Data Source=MQL5\Files\BuySellVolume.sqlite"; }
コネクションストリングは、よりシンプルに見え、ベースの位置のみ示しています。
相対パスが示されています。しかし、絶対パスも可能です:「Data Source = c:\Program Files\Metatrader 5\MQL 5\Files\BuySellVolume.sqlite」.
リスト3.3は、DbCheckAvailable()コードを示しています。SQLiteSPのようなものを提供していません、全てのクエリはコードに直接書かれます。:
リスト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; }
この関数の結果は、SQL Serverと同様のものです。一つ注意してほしい点は、テーブルのフィールドの種類です。おかしい点は、フィールドの種類は、SQLiteでは大した意味を持ちません。さらに、DOUBLEやDATETIMEデータ型がありません(少なくとも、スタンドアロン型には含まれていません。)全ての値は、String型で保存されています。そして、動的に必要なタイプにキャストされます。
なので、絡むをDOUBLE、DATETIMEと宣言する重要な点は何か?オペレーションの複雑性は知る必要はないが、クエリではADO.NETがそれらをDOUBLE、DATETIME型に自動的に変換します。しかし、これはいつでも正しというわけではなく、以下のリストで出現する場合もあります。
それでは、以下DbLoadData()関数のリストを見てみましょう。
リスト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; }
この関数は、MS SQLの実行と同様に動作します。しかし、関数の最後にループがあるのでしょうか?はい、このマジックのようなクエリは、DATETIMEを返す試みは失敗していたのです。DATETIME型がSQLiteでないことは明確です- 代わりに、YYYY-MM-DD hh:mm:ssでのフォーマットにて返されます。しかし、フォームに簡単にキャストされ、StringToTImeに簡単に理解されます。この利点を使用しました。
そして、最後にDbSaveData()関数です:
リスト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(); }
この関数の実行の詳細を紹介したいと思います。
まず、論理的ですが、全てがそのトランザクションにて行われます。しかし、これは、データの安全のためではなく、パフォーマンスのためになされました。もし、エントリーが外部のトランザクションなしで追加されていたら、サーバー内部にてトランザクションを作成し、レコードをテーブルに挿入し、トランザクションを除去します。そして、これは全てのティックごとに行われます!さらに、データベース中がエントリが記録されている際、ロックされます。コマンドが必ずしもトランザクションを必要としないことは注目すべきです。また、完全にはなぜこれが起こるのか理解していません。おそらく、これは、複数のトランザクションがないことに由来します。
次に、コマンドを一旦作成し、ループ内にてパラメータを割り当て、実行します。これは、コマンドがコンパイルされる際の効率性の問題であり、それからの作業はコンパイルされたバージョンにてなされます。
それでは取り掛かりましょう。BuySellVolume SQLite.mq5インジケータを見てみましょう。
リスト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(); }
関数が変化し、その他コードの残りは、変化しません。
今のところ、インジケータの第3バージョンの実行は、終了です。結果を見ることができます。
図5. SQLite3.6に接続されたEURUSED M5のBuySellVolumeインジケーター
ところで、SQLiteのSql Server Management Studioと異なり、データベースを扱う標準のユーティリティがあります。それゆえ、「ブラックボックス」を扱わないために、サードパーティの開発者から適切なユーティリティをダウンロードできます。個人的には、SQLiteManが好きです。使いやすく、同時に必要な機能を全て持っています。ここからダウンロードできます:http://sourceforge.net/projects/sqliteman/.
結論
これらの行を読めば、全てが終了です。この記事がそこまで大きくなってしまうことは予想していませんでした。それゆえ、答えなければならない質問は、不可避です。
ご覧の通り、全てのソリューションが利点と欠点を持っています。異形は独立性で変化します。二つ目は、パフォーマンス、そして三つ目はポータビリティです。どれを選ぶかはあなた次第です。
実行されたインジケータは便利でしょうか?決定するのもあなた次第です。私に関してはとても面白い内容だと思います。
それではみなさん最後の挨拶になります。さよなら!
アーカイブのコンテンツの詳細:
# | Filename | 概要 |
---|---|---|
1 | Sources_en.zip | 全てのインジケーターのソースコードとAdoSuiteライブラリを含んでいます。ターミナルの適切なフォルダーに展開されます。インジケーターの目的:データベースの使用なしでの(BuySellVolume.mq5)、SQL Server 2008 database (BuySellVolume SqlServer.mq5)や、SQLite database (BuySellVolume SQLite.mq5)の使用 |
2 | BuySellVolume-DB-SqlServer.zip | SQL Server 2008 データベースアーカイブ* |
3 | BuySellVolume-DB-SQLite.zip | SQLiteデータベースアーカイブ |
4 | System.Data.SQLite.zip | SQLiteデータベースを扱うのに必要なSystem.Data.SQLite.dll アーカイブ |
5 | Databases_MQL5_doc_en.zip | ソースコード、インジケーター、AdoSuiteライブラリのドキュメントアーカイブ |
* 両方のデータベースは以下の通貨における4月5日から9日までのティックのインジケーターデータを含んでいます: AUDNZD, EURUSD, GBPUSD, USDCAD, USDCHF, USDJPY.
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/69
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索