決済のたびに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で実際に動かす手順
- MetaEditorで「ファイル」→「新規作成」→「エキスパートアドバイザー」で「SqliteTradeLogEA」を作成する
- ウィザードの中身を削除し、上のコードを丸ごと貼り付ける
- F7キーでコンパイルし、「0 error(s), 0 warning(s)」になることを確認する
- デモ口座のチャートに適用し、何度か決済を行った後、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、と使い分けてください。
コメント