データベースは簡単(第1回):SQLiteを用いたMQL5向け軽量ORMフレームワーク
目次
はじめに:データベース操作の簡素化
アルゴリズム取引の世界では、堅牢なデータ管理が非常に重要です。MQL5はSQLite用の低レベルなデータベースAPIを提供しており強力ではありますが、手動でSQLを扱う必要があります。C#のADO.NETやEntity FrameworkのようなORMソリューションとは異なり、MQL5には再利用可能で実用的なORMフレームワークが現時点では存在していません。本記事では、MetaTrader 5向けに設計された軽量でポータブルかつプロフェッショナルなORM (Object-Relational Mapping)システムを紹介します。このシステムはSQLiteを対象としており、SQLクエリ操作をより扱いやすくするために設計されています。
従来のSQLベースの方法とは異なり、このシステムはフルエントインターフェースを提供し、データベース操作を直感的かつ保守しやすくします。 最初は面倒で難しく感じるかもしれませんが、データベース管理システムを構築することで作業は大幅に簡素化され、長く煩雑なコマンドから解放されます。
この考え方に基づき、ORMスタイルのデータベース管理システムを段階的に実装していきます。これにより、データベース操作が容易になります。DatabaseOpen、 DatabaseClose、 DatabasePrepare、 DatabaseFinalizeなどのMQL5の基本SQL関数を毎回コードにコピー&ペーストする必要がなくなります。その代わりに、データベースと変数間でデータをスムーズに受け渡すための有用なメソッドを集約したフレームワークが必要になります。実装セクションでは、ORMクラスオブジェクトを通してデータベースファイルと変数の間でデータを対応付ける独自のモデルを作成します。その後、各セクションで実装したクラスをテストし、期待通りの結果が得られるかを確認します。記事の最後には、任意のプロジェクトに統合できるフレームワークモジュールとテストファイルが用意されています。執筆時点では、MQL5の記事セクションに同様のORMフレームワークは存在しておらず、本記事が初の試みとなります。このフレームワークの実装は、以下のステップで進めていきます。
MQL5でORMを採用する理由は?
典型的なMQL5のデータベースコードは以下の特徴があります。
- SQL中心で記述量が多い
- エラー処理が弱い
- 再利用できない
- 大規模EAにスケールしにくい
- ビジネスロジックからSQLを排除する
- 強力な型付けを提供する
- コードファースト開発を可能にする
- 安全性と保守性を向上させる
BaseModel → ORMField → DatabaseORM → SQLite API ↑ MacroModel主要コンポーネント
- ORMField: 列のメタデータおよびバインディング
- BaseModel: エンティティ基底クラス
- DatabaseORM: SQLの生成と実行(ADO.NETスタイルのインターフェース)
- MacroModel: コードファースト構文
MQL5におけるORMフレームワークの段階的構築
1. 辞書クラス型:キーとオブジェクトの対応付け
最初のステップでは、辞書型としてオブジェクトを保存するクラスが必要です。このクラスを使うことで、オブジェクトに付けた変数名をキーとして、必要なときにそのオブジェクトへアクセスできるようになります。このクラスを実装するためには、MQL5に標準で用意されているCArrayObjクラスを継承し、シンプルな辞書構造を実装する必要があります。
template<typename T> class CDictObj : public CArrayObj { public : CDictObj(void) {}; ~CDictObj(void) {Clear();}; T* operator[](const int index) const { return(At(index)); } T* operator[](const string param) const { for(int index=0; index<Total(); index++) { T* obj = At(index); if(obj.Name() == param) return obj; } return NULL; } };
このクラスは汎用として実装する必要があります。これにより、任意のクラスをこの仕組みにバインドできるようになり、そのクラスのメソッドから必要なオブジェクトを取得できるようになります。
2.フィールドプロパティの保存:各列のフィールド情報を格納するクラスの定義
次に必要となるのは、データベーステーブルに追加するフィールド情報を列マッピングとして保持するための別のクラスです。このクラスは、SQLフィールド形式の入力情報として、各フィールドの情報を保持します。具体的には、フィールド名、型(Text、Integer、Realのいずれか)、主キーであるかどうか、自動インクリメントかどうか、NOT NULL制約の有無、そしてデフォルト値といった情報が含まれます。これらの情報は各列の重要な属性としてこのクラス内に保存されます。また、これらのフィールドは、先ほど実装したDictionaryクラスに対してフィールド名をキーとして格納される仕組みになっています。これにより、各フィールドの情報を利用してデータベースとの間でデータをスムーズに受け渡すことが可能になります。
// Field Definition //+------------------------------------------------------------------+ class CORMField : public CObject { ... ... ... string Name() const { return m_name; } ENUM_FIELD_TYPE FieldType() const { return m_fieldType; } uint Flags() const { return m_flags; } // -------- Attributes -------- void PrimaryKey(bool v=true) { m_isPrimaryKey=v; } void AutoIncrement(bool v=true) { m_isAutoIncrement=v; } void Nullable(bool v=true) { m_isNullable=v; } template<typename T> void SetValue(T v) { if(m_value!=string(v)) m_value=string(v); } string GetValue() const { return m_value; } bool IsPrimaryKey() const { return m_isPrimaryKey; } bool IsAutoIncrement() const { return m_isAutoIncrement; } bool IsNullable() const { return m_isNullable; } ... ... ... }; //+------------------------------------------------------------------+
このクラスは、目的のフィールドの値を取得および設定するための2つの重要なメソッドを提供します。これにより、特定のフィールドの値を取得したり、設定したり、あるいは更新したりすることが可能になります。
3. フィールドとデータベースの通信をおこなうベースモデルクラスの作成:データとSQLモデルの制御
CBaseModelは、すべてのデータベースエンティティの基盤となるクラスであり、自動的なフィールド管理機能を提供します。このクラスは強い型付けをサポートし、自動マッピング機能を備えているほか、派生クラスに対して再利用可能なロジックを提供します。
CBaseModelクラスは、各列のフィールドを含むテーブル構造を保持し、SQL文とともに生データを管理します。実際には、データベースとユーザーデータの間にはSQL構造を介した関係が存在しており、SQL文を繰り返しコピーする必要はありません。このモデルがそのSQL文の実行を代行し、必要な処理を自動的に提供してくれます。
// Base Model Class class CBaseModel : public CObject { ... ... ... virtual void DefineFields(); void AddField(); string GetCreateTableQuery(); virtual string GetInsertQuery(); virtual string GetUpdateQuery(); virtual void LoadFromStatement(); string GetTableName(); int GetTableCount(); string GetPrimaryKeyName(); // --- Reflection-like API --- string Get(string field); void Set(string field,string value); // --------- Binding Interface (IMPORTANT) --------- virtual string OnGet(string field) { return ""; }; virtual void OnSet(string field,string value) {}; void PullFromModel(); void PushToModel(); ... ... ... };
以下は、このクラスが提供する重要なメソッドの一覧です。
| メソッド | 詳細 |
|---|---|
| DefineFields | 子クラス内で使用され、複数のフィールドをまとめて自動的に追加するためのものです。これにより、コードの他の場所でフィールドを個別に追加する必要がなくなります。 |
| AddField | テーブルに列フィールドを追加します。 |
| GetCreateTableQuery | テーブル作成メソッドはここで作成されます。 |
| GetInsertQuery | データベースに行を挿入するためのコマンドであり、仮想メソッドとして定義されているため、子クラスでオーバーライドすることが可能です。 |
| GetUpdateQuery | 既存のレコードを更新するためのSQLコマンドであり、こちらも仮想メソッドとして子クラスでオーバーライドできます。 |
| LoadFromStatement | クエリ結果を変数へバインドする処理をおこなう部分であり、これも仮想メソッドとして子クラスでオーバーライド可能です。 |
| GetTableName | テーブル名を返します。 |
| GetTableCount | 選択されているフィールドの数を返します。 |
| GetPrimaryKeyName | 主キーとして設定されているフィールド名を返します。 |
| OnGet | バインディングインターフェースから変数へ値を取得するための抽象メソッドであり、子クラスで必ず実装する必要があります。 |
| OnSet | 変数からバインディングインターフェースへ値を設定するための抽象メソッドであり、こちらも子クラスで実装されます。 |
| PullFromModel | 変数からの値を自動的に取得します。 |
| PushToModel | 変数からの値を自動的に設定します。 |
このクラスは、データベースへのデータ転送処理そのものを抽象化して表現しています。開発者は必要なフィールドを追加するだけで、それらの値を特別な追加処理なしにデータベースへ転送できるようになります。ただし、このクラスを継承して利用する場合には、いくつかの重要なメソッドを自分のモデルに合わせて実装する必要があります。このモデルクラスからの継承を用いることで、独自のゲッター・セッターモデルを定義することができます。特に3つの重要なメソッドをオーバーライドすることで、変数とデータベースの接続方法を自由に定義することが可能になります。例のセクションでは、このモデルクラスを継承することで、変数とデータベースの間の接続をどのように簡単に確立できるかを確認することができます。
4. SQLite API:SQLite関数に直接接続するクラス
もう一つのモジュールとして、SQLite関数と直接連携し、接続後にSQL文をデータベースに実行する役割を持つクラスがあります。CDatabaseクラスは、SQLiteの機能をラップしたシンプルなモジュールであり、データベース操作に必要な基本的かつ便利なコマンドを提供します。具体的には、Open、Close、Read、Deleteといった機能を扱います。
// Database sqlite API class CDatabase { ... ... ... // ---------- Open / Close ---------- bool Open(string file, uint flags); void Close(); bool IsOpen(); int Prepare(string sql); void Finalize(int stmt); bool Execute(string sql); bool TransactionBegin(); bool TransactionCommit(); bool TransactionRollback(string sql); bool Read(int stmt); ... ... ... };
このクラスオブジェクトは、SQLiteデータベースへ直接接続するための必要な関数をまとめて提供しており、コードの他の場所でMQL5の基本的なデータベース関数を繰り返し記述する必要をなくします。そのため、開発者はこのクラスのオブジェクトを一つ初期化するだけで、そのインスタンスを通じて必要なタイミングでデータベースファイルに接続し、操作できるようになります。
5. コアアーキテクチャ:データベースORMの基礎(ORMクラスモデルの作成)
CDatabaseORM クラスは基盤となる層として機能し、接続管理、トランザクション処理、そして基本的なCRUD (Create/Read/Update/Delete)操作を提供します。このクラスは最も基本的であると同時に中核的なデータベースアーキテクチャです。このクラスは、モデルオブジェクトを受け取り、そのモデルはデータベースのフィールドを保持しており、入力として使用されます。そして、このクラスは適切なSQLクエリに基づいて、要求されたタスクを実行します。実際、このクラスはCREATE/INSERT/UPDATE/DELETEなどのデータベース関連のタスクを実行します。
// ORM core model class CDatabaseORM { ... ... ... bool Connect(); void Disconnect(); bool CreateTable(); bool Insert(); bool Update(); bool Delete(); bool Select(); bool SelectAll(); ... ... ... };
| メソッド | 詳細 |
|---|---|
| Connect | データベースに接続します。 |
| Disconnect | データベースから切断します。 |
| CreateTable | 必要に応じてテーブルを作成します。 |
| Insert | テーブルにデータを挿入します。 |
| Update | テーブル内のデータを更新します。 |
| Delete | テーブルからデータを削除します。 |
| Select | WHERE条件に基づいて、最初に見つかった項目を検索します。 |
| SelectAll | WHERE条件に基づいて、検索対象となるすべてのアイテムを検索します。 |
主な特徴は以下の通りです。
- 自動接続管理:データベース接続をシームレスに処理します。
- トランザクションサポート:接続および切断に関する完全な対応をおこないます。
- SQLプロンプトの適用:Create / Insert / Update / Delete / Selectを完全にサポートします。
- 接続のプーリング:効率的なリソース管理をおこないます。
- エラー処理:包括的なエラー報告と復旧を提供します。
このクラスはデータベースへの接続処理を代行してくれますが、ここではデータの転送処理、つまり、データ自体を収集したオブジェクトからCDatabaseORMクラスに接続してデータベース内のデータを更新または挿入する処理を代行してくれるものが必要です。そのオブジェクトは、CBaseModelクラス(またはこのCBaseModelクラスから継承されたモデル)から入力としてデータを受け取り、このオブジェクトを使って目的のコマンドを転送します。そして、CDatabaseORMがそれらのコマンドを実行します。
それでは、これまで実装してきたクラスの各部分について、具体的な例を見ていきましょう。
実務実装
1. カスタム取引レポートモデルの定義
バックテスト結果を保存するための包括的なモデルを作成しましょう。
class CTradeReportModel : public CBaseModel { private: long m_id; string m_strategy_name; datetime m_report_date; double m_total_net_profit; double m_profit_factor; double m_max_drawdown_relative; int m_total_trades; string m_parameters; public: CTradeReportModel(string table_name="trade_report") : CBaseModel(table_name) { m_id = 0; m_total_net_profit = 0; m_profit_factor = 0; m_max_drawdown_relative = 0; m_total_trades = 0; } CTradeReportModel(const CTradeReportModel& model) : CBaseModel(model) { } void DefineFields() override { AddField("id", FIELD_TYPE_INT, true, true, true); AddField("strategy_name", FIELD_TYPE_STRING, false, false, true); AddField("report_date", FIELD_TYPE_DATETIME, false, false, true); AddField("total_net_profit", FIELD_TYPE_DOUBLE); AddField("profit_factor", FIELD_TYPE_DOUBLE); AddField("max_drawdown_relative", FIELD_TYPE_DOUBLE); AddField("total_trades", FIELD_TYPE_INT); AddField("parameters", FIELD_TYPE_STRING); } string GetInsertQuery() override { string query = StringFormat("INSERT INTO %s (strategy_name, report_date, total_net_profit, profit_factor, " + "max_drawdown_relative, total_trades, parameters) " + "VALUES ('%s','%s', %d, %.2f, %.2f, %d, '%s')", GetTableName(), m_strategy_name, TimeToString(m_report_date), m_total_trades, m_profit_factor, m_max_drawdown_relative, m_total_trades, m_parameters); return query; } void LoadFromStatement(int statement) override { m_id = DatabaseColumnInt(statement, 0); m_strategy_name = DatabaseColumnText(statement, 1); m_report_date = (datetime)DatabaseColumnInt(statement, 2); m_total_net_profit = DatabaseColumnDouble(statement, 3); m_profit_factor = DatabaseColumnDouble(statement, 4); m_max_drawdown_relative = DatabaseColumnDouble(statement, 5); m_total_trades = DatabaseColumnInt(statement, 6); m_parameters = DatabaseColumnText(statement, 7); } // Setters void SetStrategyName(string name) { m_strategy_name = name; m_fields["strategy_name"].SetValue(name); } void SetTotalNetProfit(double profit) { m_total_net_profit = profit; m_fields["total_net_profit"].SetValue(profit);} void SetProfitFactor(double factor) { m_profit_factor = factor; m_fields["profit_factor"].SetValue(factor);} void SetMaxDrawdown(double max_dd) { m_max_drawdown_relative = max_dd; m_fields["max_drawdown_relative"].SetValue(max_dd);} void SetReportDate(datetime date) { m_report_date = date; m_fields["report_date"].SetValue(date);} void SetTotalTrades(int total) { m_total_trades = total; m_fields["total_trades"].SetValue(total);} void SetParameters(string param) { m_parameters = param; m_fields["parameters"].SetValue(param);} // Getters long GetId() const { return m_id; } string GetStrategyName() const { return m_strategy_name; } double GetTotalNetProfit() const { return m_total_net_profit; } double GetProfitFactor() const { return m_profit_factor; } double GetMaxDrawdown() const { return m_max_drawdown_relative; } datetime GetReportDate() const { return m_report_date; } int GetTotalTrades() const { return m_total_trades; } string GetParameters() const { return m_parameters; } };
これはCBaseModelを継承するカスタム子クラスであり、独自のゲッター/セッターメソッドを提供します。LoadFromStatementメソッドでは、実際にこのメソッド内で変数をデータベースにバインドします。データベースから値を読み込みたいとき、このメソッドによってバインドされ、ゲッターを呼び出して目的の変数から値を取得します。
2.反射バインディングインターフェースを使用する
別の例として、以下のように変数のメインメソッドをOnGet/OnSetとしてオーバーライドすることで、リフレクションインターフェースを使用してデータベースから値を取得/設定するように変数をバインドできます。
// binding model class TradeReportModel : public CBaseModel { public: int Id; string Symbol; double Lots; datetime OpenTime; TradeReportModel() { Table("TradeReport"); AddField(new CORMField("Id",FIELD_TYPE_INT,PRIMARY_KEY|AUTO_INCREMENT)); AddField(new CORMField("Symbol",FIELD_TYPE_STRING,REQUIRED)); AddField(new CORMField("Lots",FIELD_TYPE_DOUBLE)); AddField(new CORMField("OpenTime",FIELD_TYPE_DATETIME)); } // ---------- Binding ---------- string OnGet(string field) override { if(field=="Id") return (string)Id; if(field=="Symbol") return Symbol; if(field=="Lots") return DoubleToString(Lots); if(field=="OpenTime") return (string)OpenTime; return ""; } void OnSet(string field,string value) override { if(field=="Id") Id=(int)value; if(field=="Symbol") Symbol=value; if(field=="Lots") Lots=StringToDouble(value); if(field=="OpenTime") OpenTime=(datetime)value; } };
このタイプのバインディングでは、バインドされた変数を反映するオーバーライドされた関数を使用して、データとフィールド間の処理を委譲しました。
3. マクロの力を活用する:コードファーストのマクロモデル
マクロの力を活用することで、独自のカスタムクラスを最短時間で実装する別の方法があります。複数の変数とそれに対応するゲッター/セッターメソッドを持つカスタムクラスを作成し、さらにその変数をデータベースにバインドします。
//--------DB class start //-- class start DB_CLASS_BEGIN(CMyDBClassModel) //-- defining the getter/setter members DB_DEFINE_FIELD_INT(id) DB_DEFINE_FIELD_STRING(strategy_name) DB_DEFINE_FIELD_DATETIME(report_date) DB_DEFINE_FIELD_DOUBLE(total_net_profit) DB_DEFINE_FIELD_DOUBLE(profit_factor) DB_DEFINE_FIELD_DOUBLE(max_drawdown_relative) DB_DEFINE_FIELD_INT(total_trades) DB_DEFINE_FIELD_STRING(parameters) //-- adding the members DB_ADD_BEGIN DB_ADD_FIELD_INT(id,PRIMARY_KEY | AUTO_INCREMENT | REQUIRED) DB_ADD_FIELD_STRING(strategy_name,REQUIRED) DB_ADD_FIELD_DATETIME(report_date,REQUIRED) DB_ADD_FIELD_DOUBLE(total_net_profit,FIELD_NONE) DB_ADD_FIELD_DOUBLE(profit_factor,FIELD_NONE) DB_ADD_FIELD_DOUBLE(max_drawdown_relative,FIELD_NONE) DB_ADD_FIELD_INT(total_trades,FIELD_NONE) DB_ADD_FIELD_STRING(parameters,FIELD_NONE) DB_ADD_END //-- binding the members DB_BIND_BEGIN DB_BIND_FIELD_INT(id,0) DB_BIND_FIELD_STRING(strategy_name,1) DB_BIND_FIELD_DATETIME(report_date,2) DB_BIND_FIELD_DOUBLE(total_net_profit,3) DB_BIND_FIELD_DOUBLE(profit_factor,4) DB_BIND_FIELD_DOUBLE(max_drawdown_relative,5) DB_BIND_FIELD_INT(total_trades,6) DB_BIND_FIELD_STRING(parameters,7) DB_BIND_END //-- class end DB_CLASS_END
ここでは、Integer/Double/String/Datetime/Boolean型のフィールドが定義およびバインドされ、カスタムクラスCMyDBClassModelが作成されます。このクラスのオブジェクトを定義することで、CBaseModelから継承された独自のカスタムモデルを作成できます。これにより、テーブルのフィールド構造をCDatabaseORMデータベースオブジェクトに転送し、目的のコマンドをこのオブジェクトに適用できるようになります。次のセクションでは、テスト例に進み、実装したこれらのクラスを使用して、データベースの操作がいかに簡単かを確認します。
完全なサンプル
クラスの簡単な使用例、そしてバックテスト結果でデータベースを埋めることによるモデルの使用とORMオブジェクトクラスの呼び出し、ここではデータベースを作成し、サンプルバックテストデータでそれを埋める包括的な例を示します。 (TestDatabase.mq5ファイル)
1. CBaseModelオブジェクトを使用することで:
bool InitializeDatabase1() { Print("=== Backtest Data Generator by Native Base ==="); Print("Initializing database..."); if(!m_database.Connect()) { Print("Failed to connect to database"); return false; } // Create tables report_model.AddField("id", FIELD_TYPE_INT, true, true, true); report_model.AddField("strategy_name", FIELD_TYPE_STRING, false, false, true); report_model.AddField("report_date", FIELD_TYPE_DATETIME, false, false, true); report_model.AddField("total_net_profit", FIELD_TYPE_DOUBLE); report_model.AddField("profit_factor", FIELD_TYPE_DOUBLE); report_model.AddField("max_drawdown_relative", FIELD_TYPE_DOUBLE); report_model.AddField("total_trades", FIELD_TYPE_INT); report_model.AddField("parameters", FIELD_TYPE_STRING); if(!m_database.CreateTable(report_model)) { Print("Failed to create table"); return false; } Print("Database initialized successfully"); return true; }
ここでは、データベースに接続した後、AddFieldメソッドを使用して、CBaseModelクラスのオブジェクトから目的のモデルを初期化します。そして、同じモデルオブジェクトをデータベースに提示することでテーブルを作成します。その後、サンプル値を設定して、データベースに転送します。
// Sample strategy configurations string strategies[] = { "MA_Crossover_10_20", "RSI_Strategy_14_30_70", "Bollinger_Bands_20_2", "MACD_Strategy_12_26_9", "Stochastic_14_3_3", "Parabolic_SAR_002_02", "Ichimoku_Cloud", "ADX_Strategy_14", "Price_Action_Breakout", "Volume_Weighted_MA" }; string symbols[] = {"EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "XAUUSD"}; string timeframes[] = {"H1", "H4", "D1", "W1"}; int totalRecords = 0; for(int i = 0; i < ArraySize(strategies); i++) { for(int j = 0; j < ArraySize(symbols); j++) { for(int k = 0; k < ArraySize(timeframes); k++) { // Generate realistic backtest results report_model["strategy_name"].SetValue(strategies[i] + "_" + symbols[j] + "_" + timeframes[k]); report_model["report_date"].SetValue(TimeCurrent() - (MathRand() % 2592000)); // Random date in last month report_model["total_net_profit"].SetValue(GenerateRealisticProfit()); report_model["profit_factor"].SetValue(1.0 + (MathRand() % 200) / 100.0); // 1.0 to 3.0 report_model["max_drawdown_relative"].SetValue(5.0 + (MathRand() % 250) / 10.0); // 5% to 30% report_model["total_trades"].SetValue(50 + (MathRand() % 450)); // 50 to 500 trades string parameters = StringFormat("Symbol=%s, Timeframe=%s, Strategy=%s", symbols[j], timeframes[k], strategies[i]); report_model["parameters"].SetValue(parameters); if(m_database.Insert(report_model)) { totalRecords++; } // Add some variation Sleep(10); } } }
ここでは、サンプルの値を作成し、それらの値を辞書方式を用いてモデルに渡しています。その後、同じモデルを使用して値をデータベースに渡し、データベースモデルオブジェクトのInsertメソッドによってINSERT文を実行しています。下の画像では、MetaEditorでデータベースファイルを開いた際に、これらのデータがそれぞれデータベースに保存されている様子を確認できます。
出力

以下は、サンプルコードを使用してデータベースを初期化した際のレポートメッセージです。各モデルごとにデータベースを作成するためのサンプルコードが3つあり、それぞれ以下のレポートメッセージに表示されています。
出力

次に、このコードを使用することで、必要な行を簡単に選択できます。
... ... ... // Select example if(db.Select(report_model)) { PrintFormat("Found record : %s", report_model["strategy_name"].GetValue()); } // SelectAll example CArrayObj found_array; int items = db.SelectAll(found_array,report_model,"profit_factor > 1.0"); Print("items : ", items); if(items>0) { for(int i=0; i<found_array.Total(); i++) { CBaseModel* _temp = found_array.At(i); if(_temp!=NULL) PrintFormat("Found record : %s", _temp["strategy_name"].GetValue()); } } ... ... ...
ご覧のとおり、データベースから最初の値を取得するために、CDataBaseORMオブジェクトのSQLコマンドからWHERE条件なしでSELECTを提供するSelectメソッドを呼び出しました。そして次の部分では、複数の値を検索して返すようにしました。 そこで、私たちはWHERE条件「profit_factor > 1.0」を指定してSelectAllメソッドを呼び出しました。ご覧のとおり、このコマンドは正しく実行され、望んでいた値が転送されました。
出力

2.カスタムCTradeReportModelオブジェクトを使用する
ここでは、独自に実装したカスタムクラスであるCTradeReportModelを使用する方法について説明します。
... ... ... bool InitializeDatabase2() { Print("=== Backtest Data Generator by Inherited Base ==="); Print("Initializing database..."); if(!m_database.Connect()) { Print("Failed to connect to database"); return false; } // Create tables trade_model.DefineFields(); if(!m_database.CreateTable(trade_model)) { Print("Failed to create table"); return false; } Print("Database initialized successfully"); return true; } ... ... ...
ここでは、目的のデータベースに接続した後、DefineFieldsメソッドを呼び出します。前の例と同様に、このメソッド内で指定したAddFieldsメソッドが自動的に呼び出されます。そして、データベースORMオブジェクトとカスタムモデルのオブジェクトを使用してテーブルを作成します。
... ... ... // Generate realistic backtest results trade_model.SetStrategyName(strategies[i] + "_" + symbols[j] + "_" + timeframes[k]); trade_model.SetReportDate(TimeCurrent() - (MathRand() % 2592000)); // Random date in last month trade_model.SetTotalNetProfit(GenerateRealisticProfit()); trade_model.SetProfitFactor(1.0 + (MathRand() % 200) / 100.0); // 1.0 to 3.0 trade_model.SetMaxDrawdown(5.0 + (MathRand() % 250) / 10.0); // 5% to 30% trade_model.SetTotalTrades(50 + (MathRand() % 450)); // 50 to 500 trades string parameters = StringFormat("Symbol=%s, Timeframe=%s, Strategy=%s", symbols[j], timeframes[k], strategies[i]); trade_model.SetParameters(parameters); if(m_database.Insert(trade_model)) ... ... ...
ここでは前の例と同じように動作しますが、少し違いがあります。モデルの辞書状態を使用できるだけでなく、実装したカスタムメソッドのゲッター/セッター状態も使用できます。次に、前の例と同様に、以下のように WHERE句を使用して、データベースから必要な値を取得することができます。
... ... ... // Select example CTradeReportModel found_by_select; if(db.Select(found_by_select)) { PrintFormat("Found record : %s", found_by_select.GetStrategyName()); } // SelectAll example CTradeReportModel found_by_selectAll; CArrayObj found_array; if(db.SelectAll(found_array,found_by_selectAll,"profit_factor > 1.0")>0) { for(int i=0; i<found_array.Total(); i++) { CTradeReportModel* _temp = found_array.At(i); PrintFormat("Found record : %s", _temp.GetStrategyName()); } } ... ... ...
ここでは、CArrayObj クラスはカスタムモデルクラスであるCTradeReportModelクラスのオブジェクトを返し、定義したゲッターメソッドを呼び出して値を取得します。ご覧のとおり、SelectAll メソッドによって返されたオブジェクトを出力することで、値も正しく返されます。
出力

3. マクロオブジェクトの力を活用する
この部分では、先ほど示した例とまったく同じことを簡単に実行できますが、マクロで作成したカスタムクラスを使用します。
... ... ... bool InitializeDatabase3() { Print("=== Backtest Data Generator by Macro Base ==="); Print("Initializing database..."); if(!m_database.Connect()) { Print("Failed to connect to database"); return false; } // Create tables macro_model.DefineFields(); if(!m_database.CreateTable(macro_model)) { Print("Failed to create table"); return false; } Print("Database initialized successfully"); return true; } ... ... ...
ここでは、先日カスタムマクロクラスを使っておこなったのと同じことをして、データベースにサンプル値を転送します。
... ... ... // Generate realistic backtest results macro_model.Set_strategy_name(strategies[i] + "_" + symbols[j] + "_" + timeframes[k]); macro_model.Set_report_date(TimeCurrent() - (MathRand() % 2592000)); // Random date in last month macro_model.Set_total_net_profit(GenerateRealisticProfit()); macro_model.Set_profit_factor(1.0 + (MathRand() % 200) / 100.0); // 1.0 to 3.0 macro_model.Set_max_drawdown_relative(5.0 + (MathRand() % 250) / 10.0); // 5% to 30% macro_model.Set_total_trades(50 + (MathRand() % 450)); // 50 to 500 trades string parameters = StringFormat("Symbol=%s, Timeframe=%s, Strategy=%s", symbols[j], timeframes[k], strategies[i]); macro_model.Set_parameters(parameters); if(m_database.Insert(macro_model)) ... ... ...
ご覧のとおり、自動ゲッター/セッターメソッドはマクロ定義によって実装されています。そして、前の例と同じように、WHERE条件によって目的の値を取得するには 、データベースからSelectAllメソッドを呼び出します。
... ... ... // Select example CMyDBClassModel found_by_select; if(db.Select(found_by_select)) { PrintFormat("Found record : %s", found_by_select.Get_strategy_name()); } // SelectAll example CMyDBClassModel found_by_selectAll; CArrayObj found_array; if(db.SelectAll(found_array,found_by_selectAll,"profit_factor > 1.0")>0) { for(int i=0; i<found_array.Total(); i++) { CMyDBClassModel* _temp = found_array.At(i); PrintFormat("Found record : %s", _temp.Get_strategy_name()); } } ... ... ...
ここでは、CMyDBClassModel クラスのオブジェクトがモデルクラスに返され、SelectAll メソッドによって見つかったゲッターメソッドを使用して値を出力します。
出力

4. 反射バインディングインターフェース
最近、オーバーライドした OnGet/OnSet メソッドとして実装した、リフレクションを使用して変数をバインドする別の例として、以下のように記述します。
... ... ... // creating fields CORMField *id=new CORMField("id",FIELD_TYPE_INT); id.PrimaryKey(true); id.AutoIncrement(true); CORMField *name=new CORMField("name",FIELD_TYPE_STRING); name.SetValue("Alice"); // adding fields to model model.AddField(id); model.AddField(name); // spending the model fields to ORM database orm.CreateTable(model); orm.Insert(model); ... ... ...
まず列フィールドを作成し、次にそれらをCBaseModelクラスから作成したモデルに追加します。その後、ORMデータベースに渡して、目的のデータベースにデータが格納されます。別の方法として、リフレクションAPIも実行するCBaseModelエンティティから継承したモデルを使用しました。
... ... ... // creating ORM DB CDatabaseORM db; db.Connect("trade.db", db_flags); // definging the inherited model TradeReportModel report; report.Symbol = "EURUSD"; report.Lots = 0.10; report.OpenTime = TimeCurrent(); // spending the custom model to ORM DB db.CreateTable(report); db.Insert(report); // Retrieving data from DB by custom model TradeReportModel loaded; if(db.Select(loaded,"Symbol='EURUSD'")) { Print("Loaded: ",loaded.Symbol," ",loaded.Lots); } // Updating data loaded.Lots = 0.20; db.Update(loaded); // Soft delete db.Delete(loaded); ... ... ...
これら4つの簡単な方法を使えば、長くて面倒なSQLコマンドを使わずに、簡単にデータベースに接続できます。
メリット、ユースケース、主な利点
- 型安全性:コンパイル時のフィールド名と型のチェック
- パフォーマンス:効率的なSQL生成と実行
- 柔軟性:複雑なクエリ、トランザクションのサポート
結論
この強力なORMフレームワークシステムは、データベース開発を簡素化し、現代的なC#のORMコンセプトによるデータベース管理機能をMQL5にもたらします。そして、取引戦略データの保存および分析のための堅牢な基盤を提供します。包括的なバックテストシステムの構築、リアルタイム取引パフォーマンスの追跡、あるいは戦略研究の実施など、どのような用途においても、このORMはMetaTrader 5における効果的なデータ管理のためのツールを提供します。また、データベースへの接続、データの送信および取得を容易におこなう方法についても学びました。完全なソースコードは付属のヘッダファイルとして提供されており、プロフェッショナルかつ大規模なプロジェクトにも適しており、MQL5プロジェクトへすぐに統合できるようになっています。次の表では、この記事に付随するすべてのソースコードファイルについて説明します。
| ファイル名 | 詳細 |
|---|---|
| BaseModel v1.0.mqh BaseModel v2.0.mqh | データとデータベース間の通信に使用する基底クラスモデルを含むファイル |
| DatabaseORM v1.0.mqh DatabaseORM v2.0.mqh | SQLクエリコマンドを実行するための基本モデルオブジェクトを取得するORMフレームワークを含むファイル |
| Database.mqh | カプセル化されたMQL5 SQL基本関数を含むファイル |
| MacroModel.mqh | カスタム基底クラスを即座に作成するためのマクロ定義を含むファイル |
| MyDBClassModel v1.0.mqh | マクロ定義によって作成されたカスタム基底クラスのサンプルコードを含むファイル |
| TradeReportModel v1.0.mqh TradeReportModel v2.0.mqh | CBaseModelから継承したクラスのサンプルコードを含むファイル |
| ORMError.mqh | データベース/SQLエラー定義を含むファイル |
| ORMField.mqh | データベース列フィールドコレクションを含むファイル |
| TestDatabase.mq5 TestDatabase v2.mq5 | CBaseモデル、継承モデル、マクロモデル、リフレクションバインディングモデルなど、各モデルの使用例を含むファイル |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20654
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MetaTrader 5用シグマスコアインジケーター:単純な統計的異常検出器
ラリー・ウィリアムズの『市場の秘密』(第4回):MQL5における短期的スイングハイとスイングローの自動化
ラリー・ウィリアムズの『市場の秘密』(第5回):MQL5におけるボラティリティブレイクアウト戦略の自動化
Python-MetaTrader 5ストラテジーテスター(第2回):シミュレーターにおけるバー、ティック、組み込み関数のオーバーロード処理
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索