MQL4でSQLite3を使用する


MQL4で簡単にDBが使えるといいなと思い、情報を収集してみました。
最終的にたどり着いたのはSQLiteをMQL4での使用方法です。
※SQLiteは１つのファイルでDBの機能を果たすことができるため、MQL4で使用するのに最高です。
***

#####1.モジュールダウンロードサイト
***
```
1.1.sqlite3_wrapper.dllとsqlite3.mqhを下記のサイトから入手
https://github.com/Shmuma/sqlite3-mt4-wrapper

1.2.sqlite3の本家サイト(MQL4でSQLiteを使用する場合上記1.1だけで良い）
http://www.sqlite.org/download.html
```
#####2.ここでの情報は下記のサイトから入手したものにより、整理しました。
***
```
http://expertadviser-bighope.blogspot.jp/2011/03/mt4.html
https://github.com/Shmuma/sqlite3-mt4-wrapper
```
#####3.MQL4でSQLite3を使用する時のフォルダ構成は下記の通りです。
***
````
C:\Program Files\XM\MQL4\Include\sqlite3.mqh
▶︎上記１．１で入手したsqlite3.mqhをここに配置する
C:\Program Files\XM\MQL4\Libraries\sqlite3_wrapper.dll
▶︎上記１．１で入手したsqlite3_wrapper.dllをここに配置する
C:\Program Files\XM\MQL4\Files\SQLite\XXXXX.db
▶︎SQLiteのDBの保存箇所です。上記１．２のhttp://expertadviser-bighope.blogspot.jp/2011/03/mt4.htmlから入手したEventDate.dbをここに配置してみる
C:\Program Files\XM\MQL4\Experts\XXXX.mq4
▶︎EAの保存箇所
C:\Program Files\XM\MQL4\Scripts\XXXX.mq4
▶︎Scriptの保存箇所
```
#####4.サンプルソース
***
######4.1 EventHist.mq4
この部分のソースは上記の参考サイトから入手したものです。動かないところを修正したり、コメントを自分なりに入れたりをしました。

```c:EventHist.mq4
//+------------------------------------------------------------------+
//|                                                   sqliteTest.mq4 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <sqlite3.mqh>

#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

extern string contryname = "US";
extern int    Lank       =   5;

int init()
  {
    if (!sqlite_init()) {
        return INIT_FAILED;
    }

   string dates;
   string texts ="";
   string falname = "EventDate.db";
   string db = "";
   
   //絶対パスとデフォルトパスを使用する例
   if(false)
   {
      //1.絶対パスを使用する例
      db = "C:\\Program Files\\XM\\MQL4\\Files\\SQLite\\EventDate.db";
   }
   else
   {
      //2.デフォルトのパスを使用例、なおこれをしようするためにsqlite_init()をこの前に呼び出す必要がある

      db = sqlite_get_fname (falname);
      Print ("Dest DB path: " + db);
   }
   
   //使用sqlite_table_exists(db, tablename)的方法判断对应db中的表是否存在
   if (!sqlite_table_exists (db, "HistoricalDate"))
   {
        Print("files not found");
        Alert("sqlite_table_exists");
   }
   else
   {
         Alert("OK!");
   }
    
    //sqlite_query(db,sql,cols)でDBから条件に合うデータを取り出す
    int cols[1];
    int handle = sqlite_query (db, "select * from HistoricalDate where kuni = \'" + contryname + "\' and lank= \'" + Lank + "\'", cols);
    
    Comment("");
  
    //sqlite_next_row(handle) == 1 で次のレコードがあるかどうかを判断
    //次のレコードがある場合、sqlite_get_col(handle,N)で該当列のデータを取得する
    //Nは列番号である
    while (sqlite_next_row (handle) == 1)
    {
         dates = ""    + sqlite_get_col (handle, 1);
         dates = dates + sqlite_get_col (handle, 2);
         dates = dates + sqlite_get_col (handle, 3);
         dates = dates + sqlite_get_col (handle, 4);
         dates = dates + sqlite_get_col (handle, 5);
         dates = dates + sqlite_get_col (handle, 6);
         dates = dates + sqlite_get_col (handle, 7);
         dates = dates + sqlite_get_col (handle, 8);
         
         texts = texts + dates + "\n";
    }
    Comment(texts);

    //sqlite_query(db,sql,cols),sqlite_next_row(handle)実行後
    //sqlite_free_query(handel)を通してリソースを解放する？
    sqlite_free_query (handle);

    return(0);
  }

//OnDeinitでsqlite_finalize()を実行する;
void OnDeinit(const int reason)
{
   sqlite_finalize();
}
```

***
######4.2 sqlite_test.mq4

```
下記のsqlite_test.mq4では主に下記のメソッドの使用例が登場しています。
sqlite_init　　　　　//一番最初で呼び出す
sqlite_table_exists//テーブルの存在チェックに使用する
sqlite_exec        //テーブル作成、データ更新、データ削除、データ挿入で使用します。
sqlite_query       //テーブルから条件を満たすデータを取得する時に使う
sqlite_next_row    //次のレコードがあるかの判断に及びSELECT文の実行時に使う
sqlite_get_col     //レコードがある時に、列番号でデータを取得する(データの型により使用するメソッドが違う)
sqlite_free_query  //データ取得完了後リソースを解放？
sqlite_finalize    //一番最後で呼び出す（これもリソース解放？）
```

```c:sqlite_test.mq4

#property strict

#include <sqlite3.mqh>

bool do_check_table_exists (string db, string table)
{
    int res = sqlite_table_exists (db, table + "");

    if (res < 0) {
        PrintFormat ("Check for table existence failed with code %d", res);
        return (false);
    }

    return (res > 0);
}

void do_exec (string db, string exp)
{
    int res = sqlite_exec (db, exp + "");
    
    if (res != 0)
        PrintFormat ("Expression '%s' failed with code %d", exp, res);
}

int OnInit()
{
    if (!sqlite_init()) {
        return INIT_FAILED;
    }

    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
    sqlite_finalize();
}

void OnStart ()
{
    string db = "test.db";

    string path = sqlite_get_fname (db);
    Print ("Dest DB path: " + path);

    if (!do_check_table_exists (db, "test")) {
        Print ("DB not exists, create schema");
        do_exec (db, "create table test (name text)");
        do_exec (db, "insert into test (name) values ('test1')");
        do_exec (db, "insert into test (name) values ('test2')");
        do_exec (db, "insert into test (name) values ('test3')");
        do_exec (db, "insert into test (name) values ('test4')");
    }

    int cols[1];
    int handle = sqlite_query (db, "select * from test", cols);

    while (sqlite_next_row (handle) == 1) {
        for (int i = 0; i < cols[0]; i++)
            Print (sqlite_get_col (handle, i));
    }

    sqlite_free_query (handle);

    return;
}

```

***
######4.3 sqlite_test_binding.mq4
```
下記のsqlite_test_binding.mq4では主に下記のメソッドの使用例が登場しています。

sqlite_init　　　　　   //一番最初で呼び出す
sqlite_table_exists   //テーブルの存在チェックに使用する
sqlite_exec           //テーブル作成、データ更新、データ削除、データ挿入で使用します。

sqlite_query         //テーブルから条件を満たすデータを取得する時に使うだけではなく、データ挿入する時もできることをこの例で改めて分かりました。
**************************************************************************
string query = "insert into quotes (date, symbol, open, high, low, close) values (?, ?, ?, ?, ?, ?)";
string query = "select * from quotes where symbol = ? order by date";
**************************************************************************

sqlite_queryでハンドルを作っておいて、下記のメッソドの順番でデータをバインディングし、SQLを実行する、終わったらリソースを解放する
**************************************************************************
sqlite_reset          //バインディングをリセットする？
sqlite_bind_int64     //integer型の列にデータをバインディングする
sqlite_bind_text      //text型の列にデータをバインディングする
sqlite_bind_double    //real型の列にデータをバインディングする
sqlite_next_row       //Insetの時のバインディング及びも使うとは知らなかった、次のレコードを操作する？採番？
**************************************************************************

sqlite_get_col        //レコードがある時に、列番号でデータを取得する(string型の列)
sqlite_get_col_double //レコードがある時に、列番号でデータを取得する(double型の列)
sqlite_next_row    　　//データを取得する(SELECT)時に次のレコードがあるかの判断に使う
sqlite_free_query     //データ取得完了後リソースを解放？
sqlite_finalize       //一番最後で呼び出す（これもリソース解放？）
```

```c:sqlite_test_binding.mq4
#property strict

#include <sqlite3.mqh>

bool do_check_table_exists (string db, string table)
{
    int res = sqlite_table_exists (db, table + "");

    if (res < 0) {
        PrintFormat ("Check for table existence failed with code %d", res);
        return (false);
    }

    return (res > 0);
}

void do_exec (string db, string exp)
{
    int res = sqlite_exec (db, exp + "");

    if (res != 0)
        PrintFormat ("Expression '%s' failed with code %d", exp, res);
}

int OnInit()
{
    if (!sqlite_init()) {
        return INIT_FAILED;
    }

    return INIT_SUCCEEDED;
}

void OnStart ()
{
    string db = "test_binding.db";

    if (!do_check_table_exists (db, "quotes"))
        do_exec (db,
            "create table quotes (" +
            " date integer," +
            " symbol text," +
            " open real," +
            " high real," +
            " low real," +
            " close real)");

    int count = iBars (NULL, 0);
    PrintFormat ("Count = %d", count);

    string query = "insert into quotes (date, symbol, open, high, low, close) values (?, ?, ?, ?, ?, ?)";
    int cols[1];

    int handle = sqlite_query (db, query, cols);
    if (handle < 0) {
        Print ("Preparing query failed; query=", query, ", error=", -handle);
        return;
    }

    datetime start = TimeLocal ();
    for (int i = 0; i < count; i++) {
        sqlite_reset (handle);
        sqlite_bind_int64 (handle, 1, iTime (NULL, 0, i));
        sqlite_bind_text (handle, 2, Symbol ());
        sqlite_bind_double (handle, 3, NormalizeDouble (iOpen (NULL, 0, i), Digits));
        sqlite_bind_double (handle, 4, NormalizeDouble (iHigh (NULL, 0, i), Digits));
        sqlite_bind_double (handle, 5, NormalizeDouble (iLow (NULL, 0, i), Digits));
        sqlite_bind_double (handle, 6, NormalizeDouble (iClose (NULL, 0, i), Digits));
        sqlite_next_row (handle);
    }

    sqlite_free_query (handle);

    datetime end = TimeLocal ();
    datetime elapsed = end - start;
    PrintFormat ("insert %d rows in %u sec", IntegerToString(count), elapsed);
}

void OnDeinit (const int reason)
{

    string db = "test_binding.db";

    Print ("Fetching rows start");

    int cols[1];
    string query = "select * from quotes where symbol = ? order by date";
    int handle = sqlite_query (db, query, cols);
    if (handle < 0) {
        Print ("Preparing query failed; query=", query, ", error=", -handle);
        return;
    }

    sqlite_bind_text (handle, 1, Symbol ());

    int count = 0;
    
    // only print first 100 records
    while (sqlite_next_row (handle) == 1 && count < 100) {
        datetime date = (datetime) sqlite_get_col_int64 (handle, 0);
        string symbol = sqlite_get_col (handle, 1);
        double open = sqlite_get_col_double (handle, 2);
        double high = sqlite_get_col_double (handle, 3);
        double low = sqlite_get_col_double (handle, 4);
        double close = sqlite_get_col_double (handle, 5);

        PrintFormat ("date=%s, symbol=%s, open/high/low/close=%s/%s/%s/%s",
            TimeToString (date), Symbol (),
            DoubleToString (open, Digits),
            DoubleToString (high, Digits),
            DoubleToString (low, Digits),
            DoubleToString (close, Digits));

        count += 1;
    }
    
    Print ("fetching rows done");

    sqlite_free_query (handle);

    sqlite_finalize();
}

```


***
######4.4 sqlite_test_extra.mq4
```
下記のsqlite_test_extra.mq4では主に下記のメソッドの使用例が登場しています。

sqlite_init           //一番最初で呼び出す
sqlite_get_fname      //DBのフルパスの取得に使用する
sqlite_table_exists   //テーブルの存在チェックに使用する
sqlite_exec           //テーブル作成、データ更新、データ削除、データ挿入で使用します。
sqlite_query          //テーブルから条件を満たすデータを取得する時に使うだけではなく、データ挿入する時もできる
sqlite_get_col        //レコードがある時に、列番号でデータを取得する(string型の列)
sqlite_next_row       //データを取得する(SELECT)時に次のレコードがあるかの判断に使う
sqlite_free_query     //データ取得完了後リソースを解放？
sqlite_finalize       //一番最後で呼び出す（これもリソース解放？）
```

```c:sqlite_test_extra.mq4
#property strict
#include <sqlite3.mqh>

bool do_check_table_exists (string db, string table)
{
    int res = sqlite_table_exists (db, table + "");

    if (res < 0) {
        PrintFormat ("Check for table existence failed with code %d", res);
        return (false);
    }

    return (res > 0);
}

void do_exec (string db, string exp)
{
    int res = sqlite_exec (db, exp + "");
    
    if (res != 0)
        PrintFormat ("Expression '%s' failed with code %d", exp, res);
}

int OnInit()
{
    if (!sqlite_init()) {
        return INIT_FAILED;
    }

    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
    sqlite_finalize();
}

void OnStart ()
{
    string db = "test_extra.db";

    string path = sqlite_get_fname (db);
    Print ("Dest DB path: " + path);

    if (!do_check_table_exists (db, "test")) {
        Print ("DB not exists, create schema");
        do_exec (db, "create table test (name text)");
        do_exec (db, "insert into test (name) values ('test1')");
        do_exec (db, "insert into test (name) values ('test2')");
        do_exec (db, "insert into test (name) values ('test3')");
        do_exec (db, "insert into test (name) values ('test4')");
    }

    int cols[1];
    int handle = sqlite_query (db, "select cos(radians(45))", cols);

    PrintFormat ("Handle value: %d", handle);

    while (sqlite_next_row (handle) == 1) {
        for (int i = 0; i < cols[0]; i++)
            Print (sqlite_get_col (handle, i));
    }

    sqlite_free_query (handle);
}

```

***
######4.5 sqlite_test_insert_quotes.mq4
```
下記のsqlite_test_insert_quotes.mq4では主に下記のメソッドの使用例が登場しています。
sqlite_init           //一番最初で呼び出す
sqlite_table_exists   //テーブルの存在チェックに使用する
sqlite_exec           //テーブル作成、データ更新、データ削除、データ挿入などで使用します。
sqlite_finalize       //一番最後で呼び出す（これもリソース解放？）
```
```c:sqlite_test_insert_quotes.mq4
#property strict

#include <sqlite3.mqh>

int OnInit()
{
    if (!sqlite_init()) {
        return INIT_FAILED;
    }

    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
    sqlite_finalize();
}

void OnStart ()
{
    string db = "test_quotes.db";

    if (!sqlite_table_exists (db, "quotes"))
        sqlite_exec (db, "create table quotes (date, open, high, low, close)");

    int count = iBars (NULL, 0);
    PrintFormat ("Count = %d", count);

    datetime start = TimeLocal ();

    for (int i = 0; i < count; i++) {
        string query = "insert into quotes (date, open, high, low, close) values ('" + 
                     TimeToStr (iTime (NULL, 0, i)) + "'," + 
                     DoubleToString(iOpen (NULL, 0, i), Digits) + "," +
                     DoubleToString(iHigh (NULL, 0, i), Digits) + "," +
                     DoubleToString(iLow (NULL, 0, i), Digits) + "," +
                     DoubleToString(iClose (NULL, 0, i), Digits) + ");";

        sqlite_exec (db, query);
    }

    datetime end = TimeLocal ();
    datetime elapsed = end - start;
    PrintFormat ("inserted %d rows in %u sec", count, elapsed);
}

```

***
######4.5 sqlite_test_journal.mq4
```
下記のsqlite_test_journal.mq4では主に下記のメソッドの使用例が登場しています。
sqlite_init              //一番最初で呼び出す
sqlite_get_fname         //DBのフルパスの取得に使用する
sqlite_table_exists      //テーブルの存在チェックに使用する
sqlite_set_journal_mode  //??各モード
sqlite_exec              //テーブル作成、データ更新、データ削除、データ挿入などで使用します。
sqlite_finalize          //一番最後で呼び出す（これもリソース解放？）
```
```c:sqlite_test_journal.mq4
#property strict
#include <sqlite3.mqh>

bool do_check_table_exists (string db, string table)
{
    int res = sqlite_table_exists (db, table + "");

    if (res < 0) {
        PrintFormat ("Check for table existence failed with code %d", res);
        return (false);
    }

    return (res > 0);
}


void benchmark (string db, string mode)
{
    sqlite_set_journal_mode (mode);

    datetime start = TimeLocal ();

    sqlite_exec (db, "delete from bench;");

    for (int i = 0; i < 100000; i++)
        sqlite_exec (db, "insert into bench (" + IntegerToString (i) + ");");

    Alert ("Benchmark for mode " + mode + " took " + IntegerToString (TimeLocal() - start) + " seconds");
}

int OnInit()
{
    if (!sqlite_init()) {
        return INIT_FAILED;
    }

    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
    sqlite_finalize();
}

void OnStart ()
{
    string db = "test_journal.db", table = "test";

    string path = sqlite_get_fname (db);
    Print ("Dest DB path: " + path);

    if (!do_check_table_exists (db, table)) {
        Print ("DB not exists, create schema");
        sqlite_exec (db, "create table bench (id integer)");
    }

    Print ("Start benchmarks");

    benchmark (db, "DELETE");
    benchmark (db, "WAL");
    benchmark (db, "MEMORY");
    benchmark (db, "OFF");
}

```

###5.\MQL4\Include\sqlite3.mqhの中身
下記のソースは\MQL4\Include\sqlite3.mqhの内容となり、これでどれだけ関数使えるか及び引数と戻り値の型を確認することができます。

```c:1.関数一覧
/*
 * SQLite interface for MT4
 */

#import "sqlite3_wrapper.dll"
int sqlite_initialize (string terminal_data_path);
void sqlite_finalize ();

// Warning: These two routines are affected by MT4 (build 610) bug,
// which causes wrong argument order passed to DLL, when both arguments are from variables.
// The simplest workaround of this, is to add empty string to SECOND argument on call.
// See example sqlite_test.mq4.
int sqlite_exec (string db_fname, string sql);
int sqlite_table_exists (string db_fname, string table);

int sqlite_query (string db_fname, string sql, int& cols[]);
int sqlite_reset (int handle);
int sqlite_bind_int (int handle, int col, int bind_value);
int sqlite_bind_int64 (int handle, int col, long bind_value);
int sqlite_bind_double (int handle, int col, double bind_value);
int sqlite_bind_text (int handle, int col, string bind_value);
int sqlite_bind_null (int handle, int col);
int sqlite_next_row (int handle);
string sqlite_get_col (int handle, int col);
int sqlite_get_col_int (int handle, int col);
long sqlite_get_col_int64 (int handle, int col);
double sqlite_get_col_double (int handle, int col);
int sqlite_free_query (int handle);
string sqlite_get_fname (string db_fname);
void sqlite_set_busy_timeout (int ms);
void sqlite_set_journal_mode (string mode);
#import

bool sqlite_init()
{
    int error = sqlite_initialize(TerminalInfoString(TERMINAL_DATA_PATH));
    if (error == 0) {
        Print("sqlite initialization succeeded");
        return true;
    }
    else {
        Alert("ERROR: sqlite initialization failed, error=" + IntegerToString(error));
        return false;
    }
}

```

６.本当は上記のサンプルさえあれば、使えると思いますが。
今後上記の関数一覧の中の関数の使用例を１つ１つ追加していくことで使い倒すことを目指す

```c:2.関数の使用
//+------------------------------------------------------------------+
//sqlite_initとsqlite_finalizeの使用例
//概要：OnInitとOnDeinitで使用する
//注意：sqlite_get_fnameでDBの絶対パスを取得する前に、sqlite_initを呼び出す必要がある
//+------------------------------------------------------------------+
int OnInit(){
    if (!sqlite_init()) {
        return INIT_FAILED;
    }

    return INIT_SUCCEEDED;
}
void OnDeinit(const int reason){
    sqlite_finalize();
}


//+------------------------------------------------------------------+
//sqlite_get_fnameの使用例
//概要：dbの絶対パスを取得する(使用する前にsqlite_initを呼び出す必要がある）
//+------------------------------------------------------------------+
{
  extern bool absolutelyPath = false;
  extern string db = "C:\\Program Files\\XMMT4\\MQL4\\Files\\SQLite\\sqlite.db";
  string fname = "sqlite.db";
   
  //1.絶対パスを使用する
  if(absolutelyPath){
    db = db;
  }else{
    //2.sqlite_init()とDBの名前でDBを特定する
    db = sqlite_get_fname (fname);
    Print ("Dest DB path: " + db);
  }
}

//+------------------------------------------------------------------+
//sqlite_table_existsの使用例
//概要：テーブルの存在チェクをする
//+------------------------------------------------------------------+
bool do_check_table_exists (string db, string table)
{
    int res = sqlite_table_exists (db, table + "");

    if (res < 0) {
        PrintFormat ("Check for table existence failed with code %d", res);
        return (false);
    }

    return (res > 0);
}

```

補足：
下記のサイトも関連情報がありますので、ご確認してよいと思います。
https://www.mql5.com/ja/articles/862

https://github.com/Shmuma/sqlite3-mt4-wrapper