【MQL5】DatabaseColumnType関数の使い方と自動売買実装コード

1. DatabaseColumnType関数の概要と実務での活用法

MQL5におけるDatabaseColumnTypeは、実行したSQLクエリの結果セット(リクエストハンドル)から、指定した列のデータ型を取得するための中間的な関数です。

実務レベルのシステムトレード開発において、データベース(SQLite)はバックテスト結果の保存や、複数のEA間での変数共有、あるいは複雑な取引履歴の解析に不可欠です。しかし、開発が進むにつれて「テーブル設計を変更したのに、EA側のデータ読み取り処理を修正し忘れた」というミスが頻発します。

DatabaseColumnTypeを活用すれば、プログラム実行時に動的にデータの型を判定できるため、以下のような実務的なメリットがあります。
データ不整合の回避: 期待する型(例:浮動小数点数)と実際の型(例:文字列)が異なる場合にエラーハンドリングを行い、計算ミスやクラッシュを防ぐ。
汎用的なデータロガーの作成: テーブル構造が事前にわからなくても、自動で型を判別してCSV出力やログ出力を行うツールが構築できる。

特に、浮動小数点数(価格データ)を扱うFXにおいては、型を誤って整数として処理してしまうと致命的な計算誤差を招くため、この関数による型チェックは堅牢なシステム作りの第一歩となります。


2. 構文と戻り値

DatabaseColumnType関数の基本的な構文は以下の通りです。

bool DatabaseColumnType(
   int                          request,      // DatabasePrepareで作成されたリクエストハンドル
   int                          column,       // 列のインデックス(0から始まる)
   ENUM_DATABASE_FIELD_TYPE&    type          // 型を受け取るための変数(参照渡し)
);

パラメーター

  1. request: DatabasePrepare()関数によって返された有効なクエリハンドルを指定します。
  2. column: 型を調べたい列の番号を指定します。最初の列は0です。
  3. type: ENUM_DATABASE_FIELD_TYPE型の変数を渡します。関数実行後、この変数に実際の型が格納されます。

戻り値

成功した場合は true、失敗した場合は false を返します。エラーの詳細は GetLastError() で確認できます。

ENUM_DATABASE_FIELD_TYPE の主な値

  • DATABASE_FIELD_TYPE_INTEGER: 整数型(Magic Numberや注文Ticketなど)
  • DATABASE_FIELD_TYPE_FLOAT: 浮動小数点型(価格、ロット数、利益など)
  • DATABASE_FIELD_TYPE_TEXT: 文字列型(通貨ペア名、コメントなど)
  • DATABASE_FIELD_TYPE_BLOB: バイナリデータ型
  • DATABASE_FIELD_TYPE_NULL: データなし

3. 具体的な使い方・実践サンプルコード

以下のサンプルは、取引履歴を保存したデータベースから動的に列の型を判定し、適切な形式でエキスパートアドバイザーの操作ログに出力する実装例です。

void OnStart()
{
   string db_file = "MyTradeHistory.sqlite";

   // 1. データベースを開く
   int db_handle = DatabaseOpen(db_file, DATABASE_OPEN_READONLY);
   if(db_handle == INVALID_HANDLE) {
      Print("DBオープン失敗: ", GetLastError());
      return;
   }

   // 2. SQLクエリの準備(例:最新1件の履歴を取得)
   string sql = "SELECT * FROM Trades LIMIT 1";
   int res_handle = DatabasePrepare(db_handle, sql);

   if(res_handle != INVALID_HANDLE) {
      // 3. 列数を取得
      int columns = DatabaseColumnsCount(res_handle);

      // クエリを1行進める
      if(DatabaseRead(res_handle)) {
         for(int i = 0; i < columns; i++) {
            ENUM_DATABASE_FIELD_TYPE col_type;
            string col_name;

            // 列名の取得
            DatabaseColumnName(res_handle, i, col_name);

            // 今回のメイン:型の判定
            if(DatabaseColumnType(res_handle, i, col_type)) {
               PrintFormat("列 %d [%s] の型: %s", i, col_name, EnumToString(col_type));

               // 型に応じた値の取得例
               if(col_type == DATABASE_FIELD_TYPE_FLOAT) {
                  double val;
                  DatabaseColumnDouble(res_handle, i, val);
                  Print("値 (double): ", val);
               }
               else if(col_type == DATABASE_FIELD_TYPE_TEXT) {
                  string val;
                  DatabaseColumnText(res_handle, i, val);
                  Print("値 (string): ", val);
               }
            }
         }
      }
      // リソース解放
      DatabaseFinalize(res_handle);
   } else {
      Print("クエリ準備失敗: ", GetLastError());
   }

   // DBを閉じる
   DatabaseClose(db_handle);
}

4. 使用上の注意点とよくあるエラー

列インデックスの範囲外指定

column引数に、実際の列数(DatabaseColumnsCount()で取得可能)以上の数値を指定するとエラーになります。必ずループ処理の際には列数未満であることを確認してください。

SQLiteの動的型付け(Dynamic Typing)の罠

SQLiteは「柔軟な型付け」を採用しており、同じ列でも行によって異なるデータ型が保存されている可能性があります。DatabaseColumnType現在読み込んでいる行のデータ型を返します。テーブル定義上の型ではなく、実データの型を返すという点に注意してください。

DatabaseRead()の実行タイミング

DatabaseColumnTypeを呼び出す前に、DatabaseRead()を実行してカーソルを有効な行に移動させておく必要があります。データが1件もヒットしていない状態で型を取得しようとすると、期待通りの結果が得られない場合があります。


5. 【重要】自動売買における約定スピードと環境の罠

クオンツエンジニアとして断言しますが、どんなに優れたアルゴリズムをデータベースで管理し、緻密なロジックを組んだとしても、自宅PCや一般的な光回線での運用は「負け」を前提としたスタートになります。FXの自動売買において、ミリ秒(1000分の1秒)単位の遅延は、バックテストの結果とリアルトレードの結果を乖離させる最大の要因です。

自宅PCの場合、OSのバックグラウンド更新による一時的なフリーズや、プロバイダ経由のネットワーク遅延、そして取引サーバーとの物理的な距離が壁となります。これを放置すれば、スリッページによる損失が累積し、期待値を大幅に削り取られることになります。プロレベルの約定スピードと24時間365日の安定稼働を確保するには、取引サーバーに物理的に近いデータセンター内に設置された専用のVPS(仮想専用サーバー)の利用が必須条件です。インフラへの投資を惜しむことは、シストレにおいて利益を捨てることと同義であると理解してください。

💡 この記事の内容を実運用で活かすには?

この記事の内容を実運用で活かすには、正しい環境が必要です。
特にVPSを使わないと、このロジックは再現できません。

コメント

タイトルとURLをコピーしました