多通貨エキスパートアドバイザーの開発(第28回):決済マネージャの追加
はじめに
第12回では、多通貨EAに、日次および全体のドローダウンを制限するためのリスクマネージャモジュールを追加しました。このモジュールは利益を増やすものではありませんが、不利な相場環境において資金を保護するために重要な役割を果たします。このモジュールは、プロップトレーディングのルールに基づいており、通貨単位、残高に対する割合、または当日の開始時点を基準としたドローダウンを柔軟に設定できます。
このモジュールはCVirtualRiskManagerクラスとして実装されており、残高や利益を追跡し、各種制限をチェックするためのメソッドを備えています。また、利益を確保して取引を停止する機能も提供しており、目標利益に到達すると、すべての未決済ポジションを決済して取引を停止します。
一般的な取引口座では、目標利益に到達した後、自動的に取引を再開できるほうが望ましいでしょう。しかし、現在はこの操作を手動でおこなう必要があります。そろそろこの部分も自動化すべきです。
目標利益に達した際に取引戦略を再開するにあたり、私は2つの選択肢を検討しました。
- 現在のリスクマネージャを拡張する
- 別のモジュールを作成する
今回は後者を採用しました。現在のリスクマネージャは戦略から独立して動作しており、実際のポジションのみを決済し、仮想ポジションには影響を与えません。このロジックを変更するとアーキテクチャが複雑になり、モジュール間の独立性も損なわれます。
また、リスクマネージャはテスト時の負荷も増加させます。そのため、新しい機能は別モジュールとして実装するほうが適切です。このモジュールは、リスクマネージャを使用していない環境でも利用できます。
新たな目標は、利益・損失・時刻などの指定した条件を満たした際に、取引履歴に依存することなく、手動操作も不要で、すべての戦略を再開できるモジュールを作成することです。この新しいモジュールを決済マネージャと呼ぶことにします。これは独立したオプションモジュールですが、追加によって結果が改善する可能性があります。また、実際のポジションと仮想ポジションを含む、すべての未決済ポジションを完全に決済する処理を管理します。
初期要件
決済マネージャの役割と設定項目を、もう少し明確に整理します。
決済マネージャには、次の機能を持たせます。
- 利益確定:指定した利益に到達したら、すべての仮想ポジションを決済します。このとき、実際の未決済ポジションも自動的に決済されます。これを実現するため、以下の3つのパラメータを用意します。
- 基準残高:利益または損失を計算する際の基準となる取引口座の残高です。
- 利益計算方法:基準残高に対する割合や、口座通貨での固定金額など、利益の計算方法を選択します。
- 利益値:選択した計算方法に基づいて利益目標を算出するための数値です。
- 損失制限:指定した損失に達したら、すべての仮想ポジションを決済します。このときも、実際のポジションは自動的に決済されます。この機能には以下の3つのパラメータが必要です。利益確定と共通のパラメータを使用しても構いません。
- 基準残高:利益または損失を計算する際の基準となる取引口座の残高です。
- 損失計算方法:利益計算と同様に、割合または固定金額などの計算方法を選択します。
- 損失額:選択した計算方法に基づいて損失上限を算出するための数値です。
- 利益トレーリングの有効化:指定した利益に達しても直ちに仮想ポジションを決済するのではなく、より低い利益水準を決済ラインとして保存します。その後、利益がさらに増加した場合は、この決済ラインも追従して引き上げます。決済ラインの引き上げは、連続的におこなうことも、一定のステップ幅ごとにおこなうこともできます。この機能には、次のようなパラメータを設けることができます。
- トレーリングの有効/無効(はい/いいえ)
- レベル設定方法トレーリング開始レベルの設定方法を選択します。固定利益に対する割合や、口座通貨での絶対額などを指定できます。
- トレーリング開始レベル:選択した方法でトレーリング開始レベルを計算するための数値です。
- ステップ幅: トレーリングラインを更新する間隔です。トレーリング開始レベルと同じ計算方法を利用できます。
- ブレークイーブンの有効化:利益が指定した水準に到達した時点で、一定の小さな正の利益水準を決済ラインとして保存します。その後、利益がさらに増加しても、この決済ラインはトレーリングとは異なり更新されません。この機能を制御するパラメータは次のとおりです。
- ブレークイーブンの有効/無効(はい/いいえ)
- レベル設定方法:トレーリングと同様に、割合または絶対額で指定できます。
- ブレークイーブン有効化レベル: 選択した方法でブレークイーブンレベルを計算するための数値です。
プロジェクト側リポジトリ
第25回では、新しい戦略を追加し、選択した戦略を自動的に最適化するとともに、異なるパラメータを持つ複数の戦略インスタンスを組み込んだ最終的なEAを作成するためのプロジェクトの構築方法について説明しました。コード全体は、ライブラリ部とプロジェクト部の2つに分割しました。ライブラリについては、第26回でMQL5 Algo Forge上のAdwizard公開コードリポジトリをすでに公開しています。一方、プロジェクト部については、まだ対応していませんでした。
そこで、これを機に新たにSimpleCandlesリポジトリを作成します。このリポジトリには、同名の戦略を使用して最終的なEAを作成するためのプロジェクト部を格納します。mainブランチに加えて、developという開発ブランチも作成します。このプロジェクトが複数の記事に対応する場合、それぞれの記事に関連する変更はdevelopブランチから派生した個別のブランチで管理します。そして、作業が完了したら、それらをdevelopブランチおよびmainブランチへマージします。
まず、プロジェクトフォルダを格納するためのローカルフォルダを作成します。たとえば、MQL5/Experts/Articles/17608.です。このフォルダにSimpleCandlesリポジトリをクローンし、その中にIncludeフォルダを作成します。このIncludeフォルダには、このプロジェクトが依存するライブラリ部のリポジトリを配置します。つまり、AdwizardライブラリリポジトリをIncludeフォルダへクローンします。
最終的に、ターミナルフォルダ内のフォルダ構成は、おおよそ次のようになります。

図1:プロジェクト部およびライブラリ部をクローンした後の、プロジェクトリポジトリのフォルダ構成
クローンしたAdwizardリポジトリでは、developブランチへ切り替えます。このブランチは複数の記事間で共通して使用されるものです。ただし、このプロジェクトの開発を進める中でAdwizardライブラリにも変更を加えるため、このリポジトリではdevelopブランチから新しい作業ブランチを作成します。
その後、SimpleCandlesプロジェクトリポジトリでも、本記事用の作業ブランチを作成し、開発を開始します。
ライブラリコードの準備
決済マネージャの実装に向けて、まずはライブラリ側の準備を進めます。まず注意すべき点として、最新のMetaTraderビルドでは変数型のチェックがより厳格になりました。そのため、以前は問題なくコンパイルできていたコードで、現在では次のようなエラーが発生するようになっています。
parameter convertion type 'short[260]' to 'ushort[] &' is not allowed MTTester.mqh int user32::GetClassNameW(long,ushort&[],int) winuser.mqh
幸い、この問題は使用しているコード内では1か所だけで発生しており、配列の型を変更することで修正できました。
static string GetClassName( const HANDLE Handle ) { string Str = NULL; ushort Buffer[MAX_PATH] = {0}; if (user32::GetClassNameW(Handle, Buffer, ::ArraySize(Buffer))) Str = ::ShortArrayToString(Buffer); return(Str); }
ただし、その後のターミナルアップデートで、このファイルはMultiTesterライブラリの最新版に完全に置き換えられました。これは、別の原因による不具合を修正するためです。
次の変更は、決済マネージャがすべての未決済ポジションの決済を開始できるようにするためのものです。そのため、CVirtualAdvisorクラスに、すべての未決済ポジションを決済するための専用メソッドを追加し、必要に応じて決済マネージャから呼び出せるようにします。
このメソッドの実装に必要なものは、すでに揃っています。CVirtualStrategyを継承した各戦略には、すべての仮想ポジションを決済するメソッドが用意されています。そのため、EAクラスでは各戦略に対してこのメソッドを呼び出すだけです。
//+------------------------------------------------------------------+ //| Close positions of all strategies | //+------------------------------------------------------------------+ void CVirtualAdvisor::Close(void) { // For all strategies, we call the method for closing virtual positions FOREACH(m_strategies) ((CVirtualStrategy *)m_strategies[i]).Close(); }
第27回では、EAを配置したチャート全体を覆うウィンドウに複数行のテキストを表示するコンポーネントを作成しました。これは別のプロジェクトの一部として実装したものですが、このプロジェクトでも役立ちます。そこで、CConsoleDialogクラスを含むファイルをAdwizard/Utilsフォルダへ移動し、Adwizardライブラリの一部とします。これを使用するため、EAクラスを定義しているAdwizard/Experts/Expert.mqhファイルで、このクラスのオブジェクトを生成するようにします。
CConsoleDialog *dialog; // Dialog for displaying text with results //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // ... // Create and launch a dialog to display the results dialog = new CConsoleDialog(); dialog.Create(__NAME__ + ":" + (string) magic_); dialog.Run(); // Successful initialization return(INIT_SUCCEEDED); }
同じファイル内の新しいティックを処理する関数では、このオブジェクトに表示するテキストを更新する処理を追加します。表示するテキストは、後ほど実装するCVirtualAdvisorクラスのText()メソッドから取得します。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { expert.Tick(); // ... // Display text with information about the EA operation if (IsNewBar(Symbol(), PERIOD_M1)) { dialog.Text(expert.Text()); } }
また、仮想ポジションを表すラインがテキスト表示と重ならないようにするため、一時的にその表示を無効にします。そのため、CVirtualChartOrder::Show()メソッドの中身を空にします。
//+------------------------------------------------------------------+ //| Show virtual position (order) | //+------------------------------------------------------------------+ void CVirtualChartOrder::Show() { return; // ... }
CFactorableのすべての派生クラスに対するIsActiveプロパティ
暗号資産を含む銘柄を対象として最適化をおこない、その結果作成した最終EAを暗号資産をサポートしていないブローカーで実行すると、エラーが発生する場合があります。これは、気配値表示に含まれていない銘柄の取引履歴や各種プロパティを取得しようとすることが原因です。このような場合でも、最終EAに気配値表示で利用可能な銘柄を対象とする多数の戦略インスタンスが含まれていれば、気配値表示に存在しない銘柄の戦略だけを無効化すれば十分です。
現在、すべての取引戦略はCFactorableクラスの派生クラスとなっており、初期化文字列から各戦略のオブジェクトを生成できます。このクラスでは、初期化文字列が完全に正しくない場合も考慮されています。その場合、このオブジェクトと、共通の初期化文字列からそれ以前に生成されたすべてのオブジェクトは無効とみなされます。その結果、EAは初期化できず、動作を開始できません。
ここで実現したいのは、初期化文字列内の特定の種類の「エラー」であれば、その部分だけを無視し、初期化文字列全体からEAオブジェクトを生成できるようにすることです。そのために、CFactorableクラスへm_isActiveという新しいプロパティと、その値を取得するIsActive()メソッドを追加します。
//+------------------------------------------------------------------+ //| Base class of objects created from a string | //+------------------------------------------------------------------+ class CFactorable { private: // ... protected: // ... bool m_isActive; // Is the object active? // ... public: // ... bool IsActive(); // Is the object active? // ... };
このプロパティは、CVirtualRiskManagerリスクマネージャクラスなど、一部のクラスにはすでに存在していました。そのため、これらのクラスではプロパティの宣言を削除し、基底クラスであるCFactorableに一本化します。今後実装する決済マネージャクラスでも、このプロパティを使用して有効かどうかを判定します。
これとあわせて、リスクマネージャと決済マネージャを初期化文字列で省略できるようにしました。そのため、CVirtualAdvisorクラスのコンストラクタでは、EAの起動時にこれらが存在するかどうかを確認するように変更します。
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CVirtualAdvisor::CVirtualAdvisor(string p_params) { // Save the initialization string m_params = p_params; // Read the initialization string of the strategy group object string groupParams = ReadObject(p_params); // Read the initialization string of the risk manager object string riskManagerParams = NULL; if(IsObjectOf(p_params, "CVirtualRiskManager")) { riskManagerParams = ReadObject(p_params); } // Read the initialization string of the closing manager object string closeManagerParams = NULL; if(IsObjectOf(p_params, "CVirtualCloseManager")) { closeManagerParams = ReadObject(p_params); } // Read the magic number ulong p_magic = ReadLong(p_params); // Read the EA name string p_name = ReadString(p_params); // Read the work flag only at the bar opening m_useOnlyNewBar = (bool) ReadLong(p_params); // If there are no read errors, if(IsValid()) { // Create a strategy group CREATE(CVirtualStrategyGroup, p_group, groupParams); // Initialize the symbol monitor with a static symbol monitor m_symbols = CSymbolsMonitor::Instance(); // Initialize the receiver with the static receiver m_receiver = CVirtualReceiver::Instance(p_magic); // Initialize the interface with the static interface m_interface = CVirtualInterface::Instance(p_magic); // Form the name of the EA database file for saving the state from the EA name and parameters m_fileName = FileName(p_name, p_magic); // Save the work (test) start time m_fromDate = TimeCurrent(); // Reset the last save time m_lastSaveTime = 0; // Add the contents of the group to the EA Add(p_group); // Remove the group object delete p_group; // Create the risk manager object if(riskManagerParams != NULL) { m_riskManager = NEW(riskManagerParams); } // Create the closing manager object if(closeManagerParams != NULL) { m_closeManager = NEW(closeManagerParams); m_closeManager.Expert(&this); } } }
これらの変更が完了したら、いよいよ本題である決済マネージャの実装に進みます。
決済マネージャの作成
まずは、決済マネージャが取り得る状態を整理します。通常の状態では、目標利益にも最大損失にも達していません。この状態では、決済マネージャは、後続のいずれかの状態へ遷移するのを待つだけです。指定した利益または損失に到達すると、それぞれに対応する状態へ遷移します。これらの状態では、決済マネージャはすべての未決済ポジションを決済し、指定された利益および損失の新しい基準値を記録した後、通常状態へ戻ります。
利益トレーリングが有効な場合は、指定した利益水準に到達すると、決済マネージャは別の状態へ遷移します。この状態から通常状態へ戻るには、より複雑な処理が必要になるため、その詳細についてはここでは扱いません。
これらの状態は、すべてENUM_CM_STATE列挙型として実装します。
また、目標利益および目標損失の計算方法を定義するために、ENUM_CM_CALC_LOSSおよびENUM_CM_CALC_PROFITという2つの列挙型も作成します。ここでは、固定金額と、基準残高に対する割合の2つの方法を用意します。
// Possible states of the closing manager enum ENUM_CM_STATE { CM_STATE_OK, // Limits are not exceeded CM_STATE_LOSS, // Overall limit exceeded CM_STATE_PROFIT, // Total profit reached CM_STATE_TRAIL_PROFIT // Profit trailing }; // Possible methods for calculating total loss enum ENUM_CM_CALC_LOSS { CM_CALC_LOSS_MONEY_BB, // [$] Fixed Money CM_CALC_LOSS_PERCENT_BB, // [%] of Base Balance }; // Possible methods for calculating total profit enum ENUM_CM_CALC_PROFIT { CM_CALC_PROFIT_MONEY_BB, // [$] Fixed Money CM_CALC_PROFIT_PERCENT_BB, // [%] of Base Balance };
決済マネージャクラス自体は、初期化文字列からオブジェクトを生成できるようにするため、CFactorable基底クラスから派生させます。これにより、決済マネージャの有効/無効を簡単に切り替えられるm_isActiveプロパティも継承されます。
この機能を実現するため、決済マネージャは、利益または損失の計算基準となる基準残高を保持する必要があります。利益確定または損失確定がおこなわれた後は、この基準残高も、すべての未決済ポジションを決済した時点の現在の口座残高へ更新されます。この点が、リスクマネージャにおける同名のパラメータとの違いです。リスクマネージャでは、基準残高は常に変更されません。
次のプロパティ群は、目標利益および目標損失の計算方法と、その計算に使用されます。これらは、金額ベースの値を返すLossMoney()およびProfitMoney()メソッドで使用されます。
未決済ポジションを決済するために、決済マネージャはEAオブジェクトへ決済要求を送信できる必要があります。そのため、EAオブジェクトへのポインタと、それを設定するためのメソッドを追加します。
さらに、決済マネージャの現在の状態を保持するためのプロパティも追加します。
CFactorableを継承してオブジェクトを生成できるようにするには、第24回で説明したように、コンストラクタをprivate領域へ配置し、2つの専用マクロを追加する必要があります。
その結果、決済マネージャクラスは、おおよそ次のようになります。
//+------------------------------------------------------------------+ //| Closing manager class (profit and loss taking) | //+------------------------------------------------------------------+ class CVirtualCloseManager : public CFactorable { protected: // Main constructor parameters double m_baseBalance; // Base balance ENUM_CM_CALC_LOSS m_calcLossLimit; // Method of calculating the maximum overall loss double m_maxLossLimit; // Parameter of calculating the maximum total loss ENUM_CM_CALC_PROFIT m_calcProfitLimit; // Method for calculating maximum overall profit double m_maxProfitLimit; // Parameter for calculating the maximum overall profit CVirtualAdvisor* m_expert; // Pointer to the EA object // Current state ENUM_CM_STATE m_state; // State // Updated values double m_balance; // Current balance double m_equity; // Current equity double m_profit; // Current floating profit double m_overallProfit; // Current total profit relative to base balance // Protected methods double LossMoney(); // Maximum total loss double ProfitMoney(); // Maximum profit void UpdateProfit(); // Update current profit values void CheckLimits(); // Check whether acceptable profit/loss levels have been achieved CVirtualCloseManager(string p_params); // Private constructor public: STATIC_CONSTRUCTOR(CVirtualCloseManager); // Static object creation method virtual void Tick(); // Handle tick in the closing manager virtual string Text(); // Information about the current state // Bind the EA to the closing manager void Expert(CVirtualAdvisor* p_expert); virtual bool Save(); // Save status virtual bool Load(); // Load status virtual string operator~() override; // Convert object to string }; REGISTER_FACTORABLE_CLASS(CVirtualCloseManager); // Register a new CFactorable child
続いて、このクラスの主要な2つのメソッドであるコンストラクタとティック処理メソッドを見ていきます。
コンストラクタでは、これまでと同様に、初期化文字列から各パラメータを順番に読み取り、対応するプロパティへ代入します。その後、現在の状態を通常状態に設定し、現在の利益情報を更新します。基準残高が指定されていない場合は、現在の口座残高を基準残高として設定します。
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CVirtualCloseManager::CVirtualCloseManager(string p_params) { // Save the initialization string m_params = p_params; // Read the initialization string and set the property values m_isActive = (bool) ReadLong(p_params); m_baseBalance = ReadDouble(p_params); m_calcLossLimit = (ENUM_CM_CALC_LOSS) ReadLong(p_params); m_maxLossLimit = ReadDouble(p_params); m_calcProfitLimit = (ENUM_CM_CALC_PROFIT) ReadLong(p_params); m_maxProfitLimit = ReadDouble(p_params); // Set the state: Limits are not exceeded m_state = CM_STATE_OK; // Update the current profit values UpdateProfit(); // Adjust the base balance if it is not set if(m_baseBalance == 0) { m_baseBalance = m_balance; } }
基本的なティック処理メソッドでは、現在の状態を判定し、それに応じて処理を実行します。通常状態であれば、目標利益または目標損失に達したかどうかを確認します。一方、利益確定または損失確定状態であれば、すべての未決済ポジションの決済を開始し、その後、通常状態へ戻ります。
//+------------------------------------------------------------------+ //| Tick processing in the risk manager | //+------------------------------------------------------------------+ void CVirtualCloseManager::Tick() { // If the risk manager is inactive, exit if(!m_isActive) { return; } // Update the current profit values UpdateProfit(); // If the manager is in the trailing state, if(m_state == CM_STATE_TRAIL_PROFIT) { // immediately take the profit // switching the manager to the corresponding state if(true) { m_state = CM_STATE_PROFIT; } } // If the manager is in normal condition, if(m_state == CM_STATE_OK) { // Check for exceeding loss and profit limits CheckLimits(); } // If the manager is in a state of achieved loss or profit, if(m_state == CM_STATE_LOSS || m_state == CM_STATE_PROFIT) { // Close all positions m_expert.Close(); // If all positions are closed, if(PositionsTotal() == 0) { // Switch to normal state m_state = CM_STATE_OK; // Update the base balance value m_baseBalance = m_balance; } else { // Wait for all positions to close } // Save the EA state m_expert.Save(); } }
まずは、この機能までを決済マネージャの基本機能とすることにしました。そのため、現時点では利益トレーリング状態はまだ使用していません。
入力パラメータの受け渡し
決済マネージャクラスを作成したら、次はEAへ組み込みます。そのためには、決済マネージャの初期化文字列を生成するための入力パラメータを追加する必要があります。これらは、Adwizard/Experts/Expert.mqhファイルに追加します。
// ... //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "::: Use a strategy group" sinput int groupId_ = 0; // - ID of the group from the new library (0 - last) sinput bool useAutoUpdate_ = true; // - Use auto update? input group "::: Money management" sinput double expectedDrawdown_ = 10; // - Maximum risk (%) sinput double fixedBalance_ = 10000; // - Used deposit (0 - use all) in the account currency input double scale_ = 1.00; // - Group scaling multiplier input group "::: Closing manager" input bool cmIsActive_ = true; // - Active? input double cmStartBaseBalance_ = 0; // - Basic balance input ENUM_CM_CALC_LOSS cmCalcLossLimit_ = CM_CALC_LOSS_MONEY_BB; // - Loss calculation method input double cmLossLimit_ = 100; // - Threshold loss value input ENUM_CM_CALC_PROFIT cmCalcProfitLimit_ = CM_CALC_PROFIT_MONEY_BB; // - Method for calculating total profit input double cmProfitLimit_ = 1000000; // - Profit target // ...
このファイルは最終EAのコンパイル時に読み込まれるため、追加した入力パラメータは最終EAでも利用できるようになります。デフォルトでは、利益確定および損失確定の値は、口座通貨による金額で指定するようにしています。実際に使用する値については今後設定するため、現時点ではデフォルト値そのものに特別な意味はありません。
初期テスト
作成したものを確認してみましょう。まずは利益額そのものを評価対象にせず、ポジション決済の仕組みが正しく動作するかを確認します。すべてが正常に動作することを確認できれば、次の段階として、得られる利益の最適化を開始できます。
第25回で作成した最終EAのデータベースを使用して、テスター上で最終EAを起動します。その後、1年間の複数の期間を対象として高速最適化を実行し、strategy_groupsテーブルに保存された12個の戦略グループを取得しました。

データベースファイルの名前はSimpleCandles-27183.test.db.sqliteです。 最終EAがこのデータベースを使用できるようにするには、このファイルをMetaTrader5ターミナルの共有データフォルダ内にあるFilesサブフォルダへ配置する必要があります。 また、最終EAの名前はSimpleCandles.ex5とし、入力パラメータのマジックナンバーの値は27183のままにする必要があります。
まず、決済マネージャを使用せず、EAデータベース内の最初の戦略グループ(id_group=20)を使用してEAを起動します。そのため、入力パラメータには次の値を設定します。

テスト期間には、自動最適化を実行した期間と同じものを使用します。つまり、2022年の1年間です。その結果、以下の結果が得られました。


図2:2022年に決済マネージャを使用せずid_group=20の最終EAを実行した結果
ご覧のように、最適化によって、異なるシンプルなトレード戦略インスタンスに対して、指定したドローダウン10%以内に収まりながら、対象期間で大きな利益を確保できる、非常に良いパラメータの組み合わせが見つかりました。
次に、決済マネージャを有効にし、利益確定の目標値として、たとえば10USDという小さな値を設定します。

EAをビジュアルテストモードで実行します。最終EAには稼働状況の表示機能を追加しているため、このモードではEAデータベース内のID20のグループで使用されている銘柄と戦略数、決済マネージャの基準残高、さらに決済を実行する利益および損失の目標レベルを確認できます。使用されている銘柄は3つで、GBPUSD、EURUSD、EURGBP、そして48個の戦略が含まれています。

図3:決済マネージャを有効にしたEAのビジュアルテスト開始時の状態
図3から分かるように、決済マネージャの基準残高はすでに10009.89USDに達しています。これは、10USDの利益目標に到達した時点で、すべての未決済ポジションが決済されたことを意味します。
ログには次の行が表示されます。
2022.01.03 02:31:00 CVirtualCloseManager::CheckLimits | CLOSE PROFIT Profit = 12.94 | OverallProfit = 10.54 (10.00)
決済マネージャは、初期基準残高10000USDに対する総利益(OverallProfit=10.54)が10USDを超えた時点で動作しました。テストモードでは1分足のバー開始時のみ処理を実行していたため、すべての未決済ポジションの決済処理が隣接する2本の1分足にまたがって実行されました。そのため、記録された新しい基準残高は、わずかに10010USD未満になっています。「すべてのティック」モードを有効にした場合、このような差異は発生しなくなります。
次に、損失制限を行う決済マネージャをテストします。損失許容値を例えば20USDという小さな値に設定し、利益確定値は大きく設定します。これにより、利益確定よりも損失による決済が発生する可能性を高くします。

その他のパラメータでは、バー開始時のみ動作する設定を無効にします。これにより、ティックごとのシミュレーションモードでは、EAが1分足の開始時だけではなく、すべてのティックで必要な処理を実行します。

テストは短期間、つまり2022年1月3日の1日間で実行します。ログメッセージをフィルタリングし、指定した20USDの損失に到達した時点で表示された行だけを確認します。
2022.01.03 17:11:33 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -33.13 | OverallProfit = -20.06 (-20.00)
2022.01.03 17:30:39 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -20.51 | OverallProfit = -20.51 (-20.00)
2022.01.03 19:13:31 CVirtualCloseManager::CheckLimits | CLOSE LOSS Profit = -21.20 | OverallProfit = -20.11 (-20.00)
テスト日の間に、この処理が3回発生していることが分かります。ティックごとのモードでは、指定した損失に到達した際にポジション決済を実行する基準となる総利益(Overall Profit)は、入力パラメータで指定した値に非常に近くなっています。
ここで、上記ログの最初の行に含まれる次の部分に注目してください。
Profit = -33.13
これは、現在未決済しているポジションの現在利益を示しています(負の利益は損失を意味します)。この場合、この値は-20USDとは異なっています。これは、最初にいくつかのポジションが約13USDの利益で決済されていたためです。その結果、初期基準残高に対して20USDの損失に到達した時点では、未決済ポジションの含み損はこの値になっていました。
以上のように、初期テストによって、今回開発した決済マネージャが、基本的な役割をすでに実行できることを確認できました。
結論
ここでは一度短い休止を入れ、次回以降の記事で決済マネージャの開発をさらに進めていきます。今後の主な機能拡張としては、未決済ポジションに対する利益トレーリング機能の追加、およびブレークイーブンレベルを設定する機能の追加を予定しています。
しかし、改善点はこれだけではありません。たとえば、現在の決済マネージャは、すべてのポジションが決済されたかどうかを、単純に未決済ポジション数が0になるまで待つことで確認しています。しかし、仮想ポジションが残っている場合でも、この条件が成立する可能性があります。そのため、ここでは、より信頼性の高い確認方法を使用する必要があるかどうか検討します。また、リスクマネージャと決済マネージャの連携も必要になる可能性があります。決済処理がおこなわれた場合にはリスクマネージャの状態を更新し、逆の場合も同様に連携する必要があるでしょう。
それでも、最初のバージョンは完成しました。次の開発は、ゼロから始めるのではなく、この基盤の上に進めることができます。
お読みいただきありがとうございました。またすぐにお会いしましょう。
重要な注意事項
この記事および連載のこれまでのすべての記事で提示された結果は、過去のテストデータのみに基づいており、将来の利益を保証するものではありません。このプロジェクトでの作業は研究的な性質のものであり、公開された結果はすべて、自己責任で使用されるべきです。
アーカイブ内容
| # | 名前 | バージョン | 詳細 | 最近の変更 |
|---|---|---|---|---|
| SimpleCandles | プロジェクトの作業フォルダ(MQL5/Experts) | |||
| 1 | SimpleCandles.mq5 | 1.01 | 複数のモデル戦略グループを並列運用するための最終的なEA。パラメータは組み込みのグループライブラリから取得されます。 | 第25回 |
| └ Optimization | プロジェクト最適化EAフォルダ | |||
| 2 | CreateProject.mq5 | 1.02 | ステージ、ジョブ、最適化タスクを含むプロジェクトを作成するためのEAスクリプト | 第25回 |
| 3 | Optimization.mq5 | 1.00 | プロジェクト自動最適化用EA | |
| 4 | Stage1.mq5 | 1.02 | 取引戦略単一インスタンス最適化EA(第1ステージ) | 第25回 |
| 5 | Stage2.mq5 | 1.01 | 取引戦略インスタンスグループ最適化EA(段階2) | 第25回 |
| 6 | Stage3.mq5 | 1.01 | 生成された標準化された戦略グループを、指定された名前のEAデータベースに保存するEA | 第25回 |
| └ Strategies | プロジェクト戦略フォルダ | 第25回 | ||
| 7 | SimpleCandlesStrategy.mqh | 1.01 | SimpleCandles取引戦略クラス | 第25回 |
| └ Include/Adwizard | Adwizardライブラリフォルダ | |||
| └ Base | 他のプロジェクトクラスが継承する基本クラス | |||
| 8 | Advisor.mqh | 1.04 | EA基本クラス | 第10回 |
| 9 | Factorable.mqh | 1.06 | 文字列から作成されたオブジェクトの基本クラス | 第28回 |
| 10 | FactorableCreator.mqh | 1.00 | 名前とCFactorable派生クラスの静的コンストラクタを関連付けるクリエイタークラス | 第24回 |
| 11 | Interface.mqh | 1.01 | さまざまなオブジェクトを視覚化するための基本クラス | 第4回 |
| 12 | Receiver.mqh | 1.04 | オープンボリュームを市場ポジションに変換するための基本クラス | 第12回 |
| 13 | Strategy.mqh | 1.04 | 取引戦略基本クラス | 第10回 |
| └ Database | プロジェクトEAで使用されるすべての種類のデータベースを扱うファイル | |||
| 14 | Database.mqh | 1.12 | データベースを扱うクラス | 第25回 |
| 15 | db.adv.schema.sql | 1.00 | 最終EAのデータベース構造 | 第22回 |
| 16 | db.cut.schema.sql | 1.00 | 簡略化された最適化データベースの構造 | 第22回 |
| 17 | db.opt.schema.sql | 1.05 | 最適化データベース構造 | 第22回 |
| 18 | Storage.mqh | 1.01 | EAデータベース内の最終EAのキー値ストレージを扱うクラス | 第23回 |
| └ Experts | 異なるタイプのEAで使用される共通部分のファイル | |||
| 19 | Expert.mqh | 1.24 | 最終EAのライブラリファイル(グループパラメータはEAデータベースから取得) | 第28回 |
| 20 | Optimization.mqh | 1.04 | 最適化タスクの起動を管理するEAのライブラリファイル | 第23回 |
| 21 | Stage1.mqh | 1.19 | 単一インスタンス取引戦略最適化EAのライブラリファイル(第1ステージ) | 第23回 |
| 22 | Stage2.mqh | 1.04 | 取引戦略インスタンスのグループを最適化するEAのライブラリファイル(第2ステージ) | 第23回 |
| 23 | Stage3.mqh | 1.04 | 生成された標準化された戦略グループを、指定された名前のEAデータベースに保存するEAのライブラリファイル | 第23回 |
| └ Optimization | 自動最適化を担当するクラス | |||
| 24 | OptimizationJob.mqh | 1.00 | 最適化プロジェクトステージジョブクラス | 第25回 |
| 25 | OptimizationProject.mqh | 1.00 | 最適化プロジェクトクラス | 第25回 |
| 26 | OptimizationStage.mqh | 1.00 | 最適化プロジェクトステージクラス | 第25回 |
| 27 | OptimizationTask.mqh | 1.00 | 最適化タスククラス(作成) | 第25回 |
| 28 | Optimizer.mqh | 1.03 | プロジェクト自動最適化マネージャのクラス | 第22回 |
| 29 | OptimizerTask.mqh | 1.03 | 最適化タスククラス(コンベア) | 第22回 |
| └ Strategies | プロジェクトの動作を示すために使用される取引戦略の例 | |||
| 24 | HistoryStrategy.mqh | 1.00 | 取引履歴を再生するための取引戦略のクラス | 第16回 |
| 25 | SimpleVolumesStrategy.mqh | 1.11 | ティックボリュームを使用した取引戦略のクラス | 第22回 |
| └ Utils | 補助ユーティリティ、コード削減用マクロ | |||
| 26 | ConsoleDialog.mqh | 1.01 | チャート上にテキストデータを表示するためのクラス | 第28回 |
| 26 | ExpertHistory.mqh | 1.00 | 取引履歴をファイルにエクスポートするクラス | 第16回 |
| 27 | Macros.mqh | 1.07 | 配列操作に便利なマクロ | 第26回 |
| 28 | MTTester.mqh | — | MultiTesterライブラリのストラテジーテスターを操作するためのファイル | 第28回 |
| 29 | NewBarEvent.mqh | 1.00 | 特定の銘柄の新しいバーを定義するクラス | 第8回 |
| 30 | SymbolsMonitor.mqh | 1.01 | 取引商品(銘柄)に関する情報を取得するためのクラス | 第28回 |
| └ Virtual | 仮想の取引注文やポジションのシステムを用いた各種オブジェクト作成用クラス | |||
| 31 | Money.mqh | 1.01 | 基本的なお金の管理クラス | 第12回 |
| 32 | TesterHandler.mqh | 1.07 | 最適化イベント処理クラス | 第23回 |
| 33 | VirtualAdvisor.mqh | 1.12 | 仮想ポジション(注文)を扱うEAのクラス | 第28回 |
| 34 | VirtualChartOrder.mqh | 1.02 | グラフィカル仮想ポジションクラス | 第28回 |
| 35 | VirtualCloseManager.mqh | 1.00 | 決済マネージャクラス | 第28回 |
| 36 | VirtualHistoryAdvisor.mqh | 1.00 | トレード履歴再生EAクラス | 第16回 |
| 37 | VirtualInterface.mqh | 1.00 | EAGUIクラス | 第4回 |
| 38 | VirtualOrder.mqh | 1.09 | 仮想注文とポジションのクラス | 第22回 |
| 39 | VirtualReceiver.mqh | 1.04 | オープンボリュームを市場ポジションに変換するクラス(レシーバー) | 第23回 |
| 40 | VirtualRiskManager.mqh | 1.06 | リスクマネジメントクラス(リスクマネージャ) | 第28回 |
| 41 | VirtualStrategy.mqh | 1.09 | 仮想ポジションを使った取引戦略のクラス | 第23回 |
| 42 | VirtualStrategyGroup.mqh | 1.04 | 取引戦略グループのクラス | 第28回 |
| 43 | VirtualSymbolReceiver.mqh | 1.00 | 銘柄レシーバークラス | 第3回 |
| Common/Files | MetaTrader 5ターミナルのデータ共有フォルダ | |||
| 44 | SimpleCandles-27183.test.db.sqlite | — | 最終EAデータベース | 第25回 |
ソースコードは、SimpleCandlesおよびAdwizardでも入手可能です。
オープンソースリポジトリの使い方
新しいAlgo Forgeストレージへの移行を徐々に進めていますが、現在も最も適切で便利な利用方法について検討を続けています。MetaEditorでは、MQL5ルートフォルダに対応する単一のリポジトリしか使用できないという制限があります。この制限は、あまり便利とは言えません。その他のリポジトリは、Shared Projectsフォルダのサブフォルダとしてのみ利用可能になります。そのため、管理は多少容易になりますが、この方法をすぐに採用するほど十分な利便性があるとは言えません。
さらに、移行期間中、唯一のメインリポジトリがsuperadmin名義で2回作成されました。そして2回目の作成時には、理由は不明ですが、それ以前にユーザーによって作成されていた追加のリポジトリが破壊されました。幸い、リポジトリのローカルコピーがあれば、サーバーへ再アップロードできます。しかし、このような強制的な対応は、あまり望ましいものではありません。そのため、現時点では、MetaEditorのリポジトリ管理機能がさらに発展し、リポジトリを直接使用せずに扱えるようになるのを待つことにします。
この作業自体は難しいものではありません。今後もMetaEditorで開発を続けながら、当面の間、ストレージ関連の操作は外部アプリケーションへ移します。
たとえば、以下のスクリプトをコンソールで実行することで、この記事のコードを含むすべてのファイルのコピーをローカルコンピュータへ取得できます。事前に、対象のMetaTraderターミナルのMQL5フォルダ内にある任意のフォルダを現在の作業フォルダとして設定しておきます。
# プロジェクトフォルダを作成する
mkdir SimpleCandles
# プロジェクトフォルダへ移動する
cd SimpleCandles
# プロジェクトリポジトリを現在のフォルダにクローンする
git clone https://forge.mql5.io/antekov/SimpleCandles.git .
# リポジトリを目的のブランチに切り替える(本記事の場合はarticle-17608-close-manager )
git checkout article-17608-close-manager
# ブランチに切り替えたことを確認する
git status
# ライブラリ部分用のフォルダを作成する
mkdir Include
# 移動する
cd Include
# Adwizardリポジトリをライブラリフォルダにクローンする
git clone https://forge.mql5.io/antekov/Adwizard.git
# 作成したフォルダへ移動する
cd Adwizard
# リポジトリを目的のブランチに切り替える(本記事の場合は「article-17608-close-manager」)
git checkout article-17608-close-manager
# ブランチに切り替えたことを確認する
git status
# 2階層上の元のプロジェクトフォルダに移動する
cd ./../..
これらのリポジトリで不足している唯一のものは、最終EAのデータベースファイルです。これは、リポジトリ内のコードを使用して自動最適化を実行することで生成できます。必要であれば、このファイルを記事のアーカイブから取得し、共有ターミナルフォルダ内のFilesフォルダへ配置できます。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/17608
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
市場取引アルゴリズム(EMA)
初級から中級まで:オブジェクトイベント(I)
因果推論とアルゴリズム取引における回帰モデルの活用
市場シミュレーション:MQL5におけるSQL入門(IV)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索