決済のたびにCSVへ1行追記するログ機能を、

以前の記事で作りました。

ただしCSVには弱点があり、

「先月の合計損益は?」と集計したくなるたびに、

全行を自分でパースして合計する処理が必要になります。

この記事では、MQL5に標準で組み込まれているSQLiteデータベースを使い、

この集計の面倒さを解決する方法を解説します。

CSVログの限界、なぜSQLiteが必要か

まずCSV方式の何が面倒なのかを整理します。

CSVで集計しようとすると起きること

注意: CSVで「銘柄ごとの合計損益」を集計したい場合、

毎回全行を読み込んで自分でループ集計する必要があります。

行数が増えるほど、この処理は遅くなっていきます。

MQL5にSQLiteが標準搭載されている理由

MQL5には、軽量データベースエンジンSQLiteが標準で組み込まれており、

`DatabaseOpen`等の関数からSQL文で直接データを操作できます。

補足: SQL文で「合計する」「絞り込む」処理を書けるため、

集計のためのループ処理を自分で書く必要がなくなります。

MQL4にはこの機能自体が存在しません。

DatabaseOpenでデータベースを開き、テーブルを作る

まずデータベースファイルを開き、記録用のテーブルを作成します。

DatabaseOpenの引数とflags

int DatabaseOpen(string filename, uint flags);
フラグ 意味
DATABASE_OPEN_READWRITE 読み書き用に開く
DATABASE_OPEN_CREATE ファイルが無ければ新規作成する
DATABASE_OPEN_COMMON 全端末共通フォルダに配置する

戻り値はデータベースハンドル(int)で、

失敗すると`INVALID_HANDLE`が返ります。

DatabaseExecuteでテーブル作成・データ挿入

bool DatabaseExecute(int database, string sql);

`CREATE TABLE`・`INSERT`のような、

結果行を返さないSQL文の実行にはこの関数を使います。

クエリを実行して読み出す

集計結果を1行ずつ取り出す手順を確認します。

DatabasePrepare→DatabaseRead→DatabaseColumn系

int  DatabasePrepare(int database, string sql, ...);
bool DatabaseRead(int request);
bool DatabaseColumnText(int request, int column, string &value);
bool DatabaseColumnDouble(int request, int column, double &value);
void DatabaseFinalize(int request);

`SELECT`文は`DatabasePrepare`でリクエストハンドルを作り、

`DatabaseRead`を1回呼ぶごとに1行ずつ取得します。

DatabaseReadの終了判定(ERR_DATABASE_NO_MORE_DATA)

要点: `DatabaseRead`が`false`を返す原因には、

「行がもう無い(正常終了)」と「本当のエラー」の2種類があります。

`GetLastError()`が

`ERR_DATABASE_NO_MORE_DATA`(5126)なら正常終了です。

実際に動くコード全体(決済ログをSQLiteに記録・集計するEA)

決済のたびに`trade_log`テーブルへ1行記録し、

銘柄ごとの合計損益を集計する、

実際にMetaEditorでコンパイルできるEAです。

//+------------------------------------------------------------------+
//|                                  SqliteTradeLogEA.mq5             |
//|                     Copyright 2026, FX-EA System Project Creator |
//|                        https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link      "https://creator.fx-ea-system-project.com/"
#property version   "1.00"

int g_db = INVALID_HANDLE;

//+------------------------------------------------------------------+
//| Expert initialization function                                    |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_db = DatabaseOpen("TradeLog.sqlite", DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE);
   if(g_db == INVALID_HANDLE)
     {
      Print("DatabaseOpenに失敗しました。error=", GetLastError());
      return(INIT_FAILED);
     }

   if(!DatabaseExecute(g_db,
      "CREATE TABLE IF NOT EXISTS trade_log (symbol TEXT, profit REAL, close_time INTEGER)"))
     {
      Print("CREATE TABLEに失敗しました。error=", GetLastError());
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   PrintProfitBySymbol(); //終了時に集計結果を確認する(本来は任意のタイミングでよい)
   if(g_db != INVALID_HANDLE)
      DatabaseClose(g_db);
  }

//+------------------------------------------------------------------+
//| 決済(部分決済含む)のたびに1行記録する                              |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                         const MqlTradeRequest &request,
                         const MqlTradeResult &result)
  {
   if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
      return;
   if(!HistoryDealSelect(trans.deal))
      return;
   if(HistoryDealGetInteger(trans.deal, DEAL_ENTRY) != DEAL_ENTRY_OUT)
      return; //決済(手仕舞い)の約定だけを記録する

   string   symbol = HistoryDealGetString(trans.deal, DEAL_SYMBOL);
   double   profit  = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);
   datetime t       = (datetime)HistoryDealGetInteger(trans.deal, DEAL_TIME);

   string sql = StringFormat(
      "INSERT INTO trade_log(symbol, profit, close_time) VALUES('%s', %.2f, %d)",
      symbol, profit, (long)t);

   if(!DatabaseExecute(g_db, sql))
      Print("INSERTに失敗しました。error=", GetLastError());
  }

//+------------------------------------------------------------------+
//| 銘柄ごとの合計損益を集計してPrintする                              |
//+------------------------------------------------------------------+
void PrintProfitBySymbol()
  {
   int request = DatabasePrepare(g_db, "SELECT symbol, SUM(profit) FROM trade_log GROUP BY symbol");
   if(request == INVALID_HANDLE)
     {
      Print("DatabasePrepareに失敗しました。error=", GetLastError());
      return;
     }

   while(DatabaseRead(request))
     {
      string symbol;
      double totalProfit;
      DatabaseColumnText(request, 0, symbol);
      DatabaseColumnDouble(request, 1, totalProfit);
      Print(symbol, " の合計損益: ", DoubleToString(totalProfit, 2));
     }

   if(GetLastError() != ERR_DATABASE_NO_MORE_DATA)
      Print("DatabaseReadが異常終了しました。error=", GetLastError());

   DatabaseFinalize(request);
  }
//+------------------------------------------------------------------+

MetaEditorで実際に動かす手順

  1. MetaEditorで「ファイル」→「新規作成」→「エキスパートアドバイザー」で「SqliteTradeLogEA」を作成する
  2. ウィザードの中身を削除し、上のコードを丸ごと貼り付ける
  3. F7キーでコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
  4. デモ口座のチャートに適用し、何度か決済を行った後、EAを外して「エキスパート」タブに集計結果のログが出るか確認する

よくあるエラーと注意点

  • DatabaseOpenが失敗する: `DATABASE_OPEN_CREATE`の付け忘れを確認してください
  • INSERTした値が文字化けする: `StringFormat`で値をそのまま埋め込んでいるため、特殊文字は別途エスケープが必要です
  • 集計結果が空のまま: `OnTradeTransaction`は実際に決済した時だけ呼ばれます

CSVとSQLiteの使い分け

どちらか一方が常に優れているわけではなく、用途で使い分けます。

観点 CSV(FileWrite) SQLite(DatabaseOpen)
集計・絞り込み 自分でループを書く SQL文で完結する
他ソフトでの確認 Excelで開きやすい 専用ツールが必要
向いている用途 単純な記録・目視確認 集計・分析したいログ

よくある質問

DatabaseOpenの実装でよく検索されている疑問を、

3つにまとめて回答します。

MQL4でも使えますか?

いいえ、Database系の関数はMQL5独自の機能で、

MQL4には存在しません。

既存のCSVログをSQLiteに移行できますか?

`FileReadString`でCSVを1行ずつ読み、

`DatabaseExecute`でINSERTする処理を組み合わせれば移行できます。

複数のEAから同じデータベースを使えますか?

`DATABASE_OPEN_COMMON`フラグを付ければ、

全端末共通フォルダにファイルが置かれ、複数のEAで共有できます。

【MQL5】決済のたびにCSVへ追記するだけでは限界?組み込みSQLiteをDatabaseOpenで使う方法:まとめ

この記事では、CSVログの集計の面倒さを、

MQL5に組み込まれているSQLiteデータベースで解決する方法を解説しました。

`DatabaseExecute`でSQL文を直接実行できるため、

集計のループ処理を自分で書く必要がありません。

単純な記録はCSV、集計・分析したいログはSQLite、と使い分けてください。

関連記事