Net FrameworkとC#に基づくエキスパートアドバイザーと指標のためのグラフィカルインターフェイスの開発

Vasiliy Sokolov | 25 4月, 2019

はじめに

2018年10月以降、MQL5ではNet Frameworkライブラリとのネイティブ統合が利用できます。ネイティブサポートとは、呼び出し側の関数とそのパラメータを事前に宣言したり、2つの言語間で複雑に型変換したりすることなく、.NETライブラリにある型やメソッド、クラスがMQL5プログラムから直接アクセスできることを意味します。巨大な.NET FrameworkコードベースとC#言語の機能が、すべてのMQL5ユーザにそのまま利用できるようになったため、これは確実なブレークスルーと見なすことができます。

.NET Frameworkの機能は、ライブラリ自体によっては制限されません。開発プロセスは、統合されたフリーミアムのVisual Studio開発環境によって大いに単純化されます。たとえば、ドラッグアンドドロップモードで本格的なWindowsアプリケーションを開発し、そのすべての要素を他のWindowsグラフィカルアプリケーションと同じように動作させることができます。これがMQLが欠けていたものです。

MQL言語が生まれて以来、MQLアプリケーション内でのグラフィカルアプリケーション開発を非常に容易にするライブラリは複数作成されてきました。しかし、これらのライブラリがどれほど優れていたとしても、一連のコードの理解と、カスタムEAおよび指標コードと統合する機能が必要でした。言い換えれば、これらはプログラマ以外には手が届かない存在でした。.NET Frameworkライブラリとの統合がなければ、Visual Studioでのフォーム作成の単純さとMQLでのグラフィックライブラリ構成の複雑さとの間のギャップは未だ変わらなかったでしょう。 

本稿では、MQL5の取引エキスパートおよび指標用のカスタムグラフィカルインターフェイス(GUI)の開発について説明します。GUIは、各要素がEAの取引ロジックと密接に関連している標準的なグラフィカル要素のセットを含む標準のWindows Formsです。

グラフィカルアプリケーションを開発するには、MQLプログラムと.NETライブラリを統合する必要があります。本稿では、この作業についても詳しく説明しているため、MQLプログラム用のグラフィカルフォームを作成したい人だけでなく、サードパーティ製の.NETコードベースとの統合に興味がある人にとっても有用です。

重点は提案された方法の単純さに置かれています。主な課題は、C#コードとの対話をできるだけ簡単にすることです。対話自体は、C#コードがユーザの介入なしに作成されるように構成されています。これは、高度なC#言語ツールとVisual Studioの豊富な機能のおかげで可能になりました。

したがって、読者はC#の知識を必要としません。主なアイデアは、ビジュアルモードでボタンやテキストラベルなどのグラフィカルコントロールを配置してから、MQL言語を使用して各要素に適切なロジックを追加することです。パネルとMQLプログラムとの統合は、「舞台裏」で自動的に行われます。 


.NET GUIとの対話 - 一般原則

.NETは、人気のあるJavaプラットフォームに代わるものとして2002年にMicrosoftによって開発された共通言語プラットフォームの商標名です。このプラットフォームは、共通言語ランタイム(CLR)に基づいています。従来のプログラムがマシンコードに直接コンパイルされてコンピュータ上で直接起動されるのとは異なり、.NETアプリケーションはCLR仮想マシン上で実行されます。よって、.NETは高級言語を使用して開発されたプログラムがユーザのPC上で実行するために適用される一種の環境です。

C#は.NETの主要なプログラミング言語です。C#についての話題では.NETが意味され、その逆もまた同じです。.NETは明らかにC#と関連しています。簡単に言うと、.NETは主にC#で開発されたプログラムの実行環境です。本稿も例外ではありません。本稿で紹介されているコードはすべてC#で書かれています。

開発された.NETプラットフォーム用のプログラムは、CLR仮想マシンによって実行される中低水準言語CIL(Common Intermediate Language)の言語バイトコードにコンパイルされます。コード自体は、標準のWindowsプログラムエンティティ(exe 実行可能モジュールまたはdll動的ライブラリ)にパッケージ化されています。.NET仮想マシン用にコンパイルされたコードは高レベルの構造を持ち、そのプロパティは簡単に調べることができ、データの種類も確認できます。この優れた機能は、最新バージョンのMQLコンパイラで使用されています。コンパイル中に、コンパイラは動的ネットライブラリをダウンロードし、その中に定義されているパブリック静的メソッドを読み取ります。パブリック静的メソッドに加えて、MQLコンパイラはC#の基本データ型を理解します。これらのデータ型は次のとおりです。

  • すべての整数データ型: long/ulong、int/uint、byte、short/ushort
  • 浮動小数点数: float/double
  • 「char」文字データ型(charとucharがバイトデータ型であるMQLとは異なり、С#ではこの型はシンボルの定義に使用されます)
  • 「string」型
  • 上記の基本型をフィールドとして含む単純な構造体

これらの型に加えて、MQLコンパイラはC#配列を理解します。現在、MQLプログラムの「[]」インデクサによって配列要素への標準アクセスを取得することはできませんが、私は自信を持って型のサポートが将来的に拡張されると言えます。今日の機能は本格的な対話を手配するのに十分なものです。

このプロジェクトでは、Windows Formsテクノロジを使用してフォームを開発します。これは非常に単純なAPIのセットで、不慣れなユーザでも素早く簡単にGUIを描画できます。その特徴はイベント指向のアプローチです。つまり、ユーザがボタンをクリックしたり入力ウィンドウにテキストを入力したりすると、対応するイベントが生成されます。このようなイベントを処理した後、C#プログラムはフォームの特定のグラフィック要素がユーザによって変更されたと判断します。イベントの取り扱いは、C#に不慣れな人にとってはかなり複雑なプロセスです。フォームで発生したイベントを処理し、それらをMetaTrader 5ターミナルで実行されているMQLプログラムに渡すためには特別な中間コードが必要です。

したがって、このプロジェクトには互いに対話する3つの独立したオブジェクトが含まれます。

3つすべてのオブジェクトは、メッセージシステムを介して互いにやり取りします。メッセージシステムの1つはMQLアプリケーションとコントローラの間のやり取りに使用され、もう1つのメッセージシステムはコントローラとユーザウィンドウの間のやり取りに使用されます。



図1 MQLプログラムとC#グラフィカルアプリケーション間の対話 - 一般的な構造

この構造は最も一般的な形で提示されており、これまでのところ将来のグラフィカルアプリケーションでの記述された部分間の対話の詳細は明らかにされていません。しかし、提案された方式を考慮すると、このシステムは高度に分散されるだろうことが明らかになります。各モジュールは独立しており、他のモジュールが変更されてもコードへの介入を必要としません。以下のセクションでは、これらの部分間の対話とこの分離が実装される手段を詳しく見ていきます。


Visual Studioのインストールと構成

一般的な実装構造を準備したので、プロジェクトを進めていきます。これを行うには、Visual StudioをPCにインストールしておく必要があります。Visual Studioが既にインストールされている場合は、このセクションを飛ばしてください。今までにVisual Studioを使用したことがない初心者の方は、読み続けてください。 

Visual Studioは、さまざまなプログラミング作業のための専門的な開発環境です。このソフトウェアはいくつかのエディションで提供されています。ここではコミュニティエディションを使用します。これはフリーミアム版で、30日間使用した後は、無料で登録する必要があります。これを行うには、マイクロソフトのサービスのいずれかを使用して標準の確認手順を実行する必要があります。ここでは、プラットフォームのダウンロード、インストール、および登録の基本的な手順を説明します。これにより、初心者の方でもできるだけ短時間でその機能を使い始めることができるようになります。 

以下は、コンピュータにVisual Studioをインストールするためのステップバイステップガイドです。以下では英語版のインストーラのスクリーンショットを示します。特定の外観は、お使いのPCの地域の設定に応じて場合によって異なります。 

まず、公式のVisual Studioウェブサイトvisualstudio.microsoft.comにアクセスして、適切な配布キットを選択します。コミュニティ版を選択してください。

 

図2 VisualStudio配布キットの選択


その後、Visual Studioインストーラのダウンロードが開始されます。ウェブサイトから登録を求められた場合は、この手順をスキップしてください。登録は後でします。

インストーラを起動すると、インストーラを構成する必要があることを知らせるウィンドウが表示されます。[続行]をクリックします。

図3 [続行]をクリックしてインストールを続ける

次に必要なインストールファイルのダウンロードが始まります。帯域幅によっては、ダウンロードに時間がかかる場合があります。ダウンロードが完了すると、インストール設定ウィンドウが表示されます。提案されたコンポーネントから[.NETデスクトップ開発]オプションを選択します。 


図4 コンポーネントの選択

[インストール]をクリックすると、インストールプロセスが始まります。インストールには時間がかかることもあります。

図5 インストール

インストールが完了すると、Visual Studioが自動的に起動します。しない場合は、手動で起動してください。最初の起動時に、Visual Studioはアカウントにサインインするか新しいアカウントを作成するかを尋ねます。アカウントをお持ちでない場合は、[アカウントを作成]リンクをクリックして今すぐアカウントを作成してください。


図6 新しいアカウントの作成


新しいメールボックスの登録が始まります。このメールボックスは、すべてのマイクロソフトサービスに関連付けられます。提案されたアクションを一貫して実行して登録を完了します。登録プロセスは非常に標準的なので、詳細は説明しません。

登録したくない場合は、[後で]をクリックしてこの手順をスキップしてください。ただし、Visual Studioでは30日以内に登録が必要になります。東麓しないと、Visual Studioが作動しなくなります。


初めてのフォームの作成 - クイックスタート

アカウントを登録してログインすると、Visual Studioが起動します。初めてのビジュアルフォームを開発してMetaTraderに接続しましょう。このセクションでは、これがどんなに簡単にできるかを説明します。

まず、新しいプロジェクトを作成します。[ファイル]、[新規]、[プロジェクト]の順に選択します。プロジェクトタイプ選択ウィンドウがポップアップ表示されます。

図7 

[Windows フォームアプリケーション(.NET Framework)]を選択します。[プロジェクト名]フィールドにプロジェクト名を入力します。デフォルトの名前を変更して、プロジェクトをGuiMTと呼びましょう。[作成]をクリックします。Visual Studioは、ビジュアルデザイナーで自動的に作成されたフォームを表示します。

 

図8 Visual Studioウィンドウでのグラフィカルフォームの作成


ソリューションエクスプローラーウィンドウにはプロジェクト構造が含まれています。Form1.csに注意してください。これは、Form1.cs[Disign]グラフィカルエディタウィンドウに表示されるフォームのグラフィカル表現を作成するプログラムコードを含むファイルです。ファイル名を覚えておいてください。後で必要になります。

ビジュアルデザイナーでは、フォームサイズはマウスを使って変更できます。カスタム要素をフォームに配置することもできます。これらの機能は、初めての実験には十分です。サイドタブの[ツールボックス]タブを開き、メインウィンドウの左側にある[すべてのWindowsフォーム]セクションで[Button]要素を選択します。

図9 ボタンの選択

マウスを使って、Form1にドラッグします。

図10 初めてのフォーム

ボタンのサイズも変更できます。メインウィンドウのサイズとボタンの位置をいろいろと試すことができます。フォームにボタンが追加され、最初のアプリケーションの準備が整いました。コンパイルしましょう。これはさまざまな方法で実行できますが、今はデバッグモードで実行することにします。これには、[開始]をクリックします。

図11 デバッグモードでアプリケーションを実行するためのボタン  

ボタンをクリックすると、アプリケーションがコンパイルされて自動的に起動されます。アプリケーションが起動したら、ウィンドウを閉じるか、Visual Studioで停止アイコンをクリックしてデバッグを停止するだけでアプリケーションを停止できます。

図11 デバッグ停止アイコン

初めてのアプリケーションが完成しました。最後にやらなければならないことは、今作成したプログラムへの絶対パスを見つけることです。最も簡単な方法は、[プロパティ]ウィンドウの[プロジェクトフォルダ]フィールドのパスを確認することです。GuiMTプロジェクトは、ソリューションエクスプローラーウィンドウで強調表示されているはずです。

図12 [プロジェクトフォルダ]行のアプリケーションへの絶対パス

このウィンドウ内のパスはプロジェクト自体に関連しています。このプログラムの特定のアセンブリは、コンパイルモードに応じてサブディレクトリのいずれかに配置されます。この場合、パスは「.\bin\debug\<Custom_project_name.exe>」です。よって、アプリケーションの完全なパスは「C:\Users\<User_name>\source\repos\GuiMT\GuiMT\bin\debug\GuiMT.exe」となります。後でMQLコードに挿入する必要があるため、このパスをどこかに保存してください。


GuiController.dllの最新バージョン - GitHubでの作業

本稿に添付されているファイルには、GuiController.dllライブラリが含まれています。これを、\MQL5\Librariesディレクトリに配置してください。しかしながら、ライブラリは開発中であるので、更新され、添付されたアーカイブが最新でなくなることがしばしばあります。このような問題を避けるためにも、ユーザが新しいコードを自動的に利用できるようにするためにバージョン管理システムを使うことをお勧めします。このプロジェクトも例外ではありません。最新バージョンのGuiControllerを入手するために、 GitHub.comオープンソースコード保存サービスを使用しましょう。コントローラのソースコードは既にこのリポジトリに含まれているため、プロジェクトをダウンロードしてコントローラを動的ライブラリにコンパイルするだけです。このシステムを使用できない、または使用したくない場合は、このセクションを飛ばしてください。代わりに、GuiController.dllファイルをMQL5\Librariesディレクトリにコピーしてください。 

現在のソリューションがまだ開いている場合は、閉じます([ファイル]->[ソリューション])。[チームエクスプローラー]タブに移動して[クローン]リンクをクリックして、黄色のフィールドにプロジェクトのアドレスを入力します。

https://github.com/PublicMqlProjects/MtGuiController

次のフィールドは、ダウンロードしたプロジェクトの保存に使用されるローカルパスを指定します。パスはダウンロードしたプロジェクト名に従って自動的に選択されるので、手動では変更しません。以下のスクリーンショットには、チームエクスプローラに入力される値が示されています。

図13 リモートソースコードリポジトリへの接続

すべての準備が整ったので、[クローン]をクリックします。MtGuiControllerの最新バージョンを含むプロジェクトが、しばらくすると指定されたアドレスに表示されます。[ファイル]->[開く]->[プロジェクト/ソリューション]メニューのコマンドで開きます。プロジェクトをダウンロードして開いたら、コンパイルする必要があります。これを行うには、F6キーを押すか、メニューの[ビルド]->[ソリューションのビルド]を選択します。コンパイルされたMtGuiController.dllファイルをMtGuiController\bin\debugフォルダで見つけて、MetaTrader 5ライブラリのディレクトリ(MQL5\Libraries)にコピーします。

何らかの理由でgithubから最新版を入手できない場合は、以下に添付されているアーカイブからコントローラをコピーしてください。


最初のアプリケーションをMetaTrader 5と統合する

グラフィカルウィンドウシグナルをMetaTraderにブロードキャストする最初のアプリケーションとコントローラができたので、最後の部分に取り掛かります。つまり、コントローラを介してウィンドウからイベントを受け取るMQLプログラムをEAとして作成します。MetaEditorで、次の内容を持つGuiMtControllerという名前の新しいEAを開発しましょう。

//+------------------------------------------------------------------+
//|                                              GuiMtController.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#import  "MtGuiController.dll"
string assembly = "С:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- タイマーを作成する
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, "Form1");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- タイマーを破壊する
   GuiController::HideForm(assembly, "Form1");
   EventKillTimer();   
  }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                           |
//+------------------------------------------------------------------+
void OnTick()
{
//---
}
//+------------------------------------------------------------------+
//| タイマー関数                                                    |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == ClickOnElement)
         printf("Click on element " + el_name);
   }
  }
//+------------------------------------------------------------------+

既に説明したように、コードをコンパイルするにはMtGuiController.dllライブラリをMQL5\Librariesディレクトリに配置する必要があります。その上、行で指定された絶対パス:

string assembly = "С:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";

をウィンドウを使用したプログラムの実際の場所に置き換えます。
すべてが正しく行われば、EAがコンパイルされます。起動すると、MetaTraderメインウィンドウの背景にウィンドウが表示されます。

図14 C#の統合グラフィカルアプリケーションを使用したEA

button1をクリックすると、EAは[エキスパート]タブに、ボタンを押すイベントを受信したことを示す 「Click on element button1」メッセージを表示します。 


MQLプログラムとGuiControllerの対話 - イベントモデル

上に示したMQLコードを徹底的に分析して、開発したプログラムがどのように機能するのかを理解しましょう。

最初に確認できるのは、importディレクティブとassembly文字列です。

#import  "MtGuiController.dll"
string assembly = "C:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";

最初の文字列は、MtGuiController.dllにあるopen staticクラスメソッドの呼び出しが使用されることをコンパイラに通知します。このアセンブリでは、参照する正確なメソッドを指定する必要はありません。コンパイラが自動的にこれを行います。

2番目の文字列には、管理対象のフォームへのパスが含まれています。このアドレスは、フォームの実際の場所に対応している必要があります。

次に来るのは、EA初期化の標準的なOnInitのコードです。

//+------------------------------------------------------------------+
//| エキスパート初期化関数                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- タイマーを作成する
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, "Form1");
//---
   return(INIT_SUCCEEDED);
  }

ここでは、高頻度タイマーが設定され、カスタムクラスメソッドの1つが最初に呼び出されます。タイマー機能については後ほど説明します。それでは、ShowFormの呼び出しを見てみましょう。

GuiController::ShowForm(assembly, "Form1");

C#では、関数をクラスと別に存在させることはできません。したがって、各関数(メソッド)には、それが定義されている独自のクラスがあります。MtGuiController.dllには単一のGuiControllerクラスが定義されています。それにはウィンドウを管理するための静的メソッドを含みます。MtGuiController.dllには他のクラスはありません。つまり、管理全体がクラスを介して実行されます。これは、ユーザが単一の対話インターフェイスを操作し、一連の異なる定義内で必要な機能を検索しないため非常に便利です。

初期化ブロックで最初に実行されるのは、ShowFormメソッドを呼び出すことです。その名前が示すように、これによってフォームを表示するプロセスが開始されます。メソッドの最初のパラメータはフォームが定義されているファイルへの絶対パスを設定し、2番目のパラメータはフォーム自体の名前を設定します。1つのファイルには複数のフォームを定義できます。したがって、ファイル内で起動するフォームを正確に指定する必要があります。この場合、フォームの名前は、デフォルトでVisual Studioによってカスタムフォームに割り当てられたフォームクラスの名前になります。以前に作成したプロジェクトをVisual Studioで開き、Form1.Designer.csファイルをコードビューモードで開くと、必要なクラス名が表示されます。

partial class Form1
{
        /// <概要>
        /// 必要なデザイナー変数
        /// </概要>
        private System.ComponentModel.IContainer components = null;
        ...
}

さらに意味のあるクラス名を付ける必要があります。Visual Studioでは、これはクラスとそれへのすべての参照の名前を変更するだけで簡単に実行できます。この場合、ShowFormメソッドの2番目のパラメータの値も変更する必要があります。

次の関数はOnTimerです。これはタイマー設定によって1秒間に5回呼び出され、プロジェクト全体の中で最も興味深いコードが含まれています。関数本体には、イベントのシリアル番号を繰り返すforループが含まれています。

for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == ClickOnElement)
         printf("Click on element " + el_name);
   }

コントローラの観点から見ると、イベントはフォームに向けられた任意のユーザアクションです。たとえば、ユーザがボタンをクリックしたりテキストボックスにテキストを入力したりすると、コントローラは対応するイベントを受け取り、それをイベントリストに入れます。リスト内のイベント数は、MQLプログラムから呼び出すことができるGuiController::EventsTotal()静的メソッドによってブロードキャストされます。

Windows Formsにはたくさんのイベントがあります。フォーム、ボタン、テキストボックスなどの各要素には、多数のイベントが含まれています。すべてのイベントを処理できるわけではありませんが、それは必須ではありません。GuiControllerは最も重要なイベントのみを扱います。現在のバージョンでは、処理されるイベントは3つだけです。それらは以下のとおりです。

  • ボタンクリックイベント
  • テキスト入力完了イベント
  • 水平スクロールイベント

このリストは将来拡張される予定ですが、本稿の目的には現在の状態で十分です。

GuiControllerによってサポートされているイベントは、発生すると処理されてイベントリストに追加されます。イベントの処理は、MQLプログラムがイベントタイプとそのパラメータを比較的簡単に定義できるデータを受け取ることで行われます。そのため、各イベントのデータ形式は、OnChartEvent関数のイベントモデルと非常によく似た構造になっています。この類似性により、GuiControllerを使用しているユーザは、新しいイベントモデルの形式を習得する必要はありません。もちろん、提示されたアプローチにはそれ自身の問題があります。例えば、複雑なイベント(例: スクロール)を提案された型式に合わせるのは非常に難しいですが、これらの問題はC#言語ツールとその高度なオブジェクト指向プログラミングモデルを使って簡単に解決できます。それまでの間、提案されたモデルは我々のタスクを解決するのに十分です。

新しいイベントが到着するたびに、そのデータはGuiController::GetEvent静的メソッドを使用して参照型を介して受信できるようになります。このメソッドのプロトタイプは以下のとおりです。

public static void GetEvent(int event_n, ref string el_name, ref int id, ref long lparam, ref double dparam, ref string sparam)

パラメータを見ていきます。 

  • event-n — 受信するイベントのシリアル番号です。イベントのシリアル番号を指定できると、新しいイベントを制御するのが簡単になります。
  • el_name — このイベントを生成した要素の名前
  • id — イベントの種類
  • lparam — イベントの整数値
  • dparam — イベントの実数値
  • sparam — イベントの文字列値

ご覧のとおり、GuiControllerイベントモデルはOnChartEventモデルと非常によく似ています。GuiControllerのイベントには、常にシリアル番号とそれを生成したソース(要素名)があります。残りのパラメータはオプションです。ボタンクリックのようないくつかのイベントには追加のパラメータ(lparam、dparam、sparam)は全くありませんが、テキスト補完イベントではsparamパラメータにユーザによってフィールドに入力されたテキストを含みます。

以下は、現在使用できるイベントとそのパラメータを含む表です。

イベント名 ID  パラメータ
例外  0 sparam - 例外の原因となっているメッセージ
ClickOnElement  1 -
TextChange  2 sparam - ユーザによって入力された新しいテキスト 
ScrollChange  3

lparam - ひとつ前のスクロールレベル

dparam - 現在のスクロールレベル

GuiControllerでイベントモデルを処理したので、forループ内に表示されたコードを最後に理解できます。文字列

GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);

は、インデックス「i」によってイベントを取得します。イベントタイプがボタンクリックに対応している場合は、ボタン名とその押下に関するメッセージがターミナルコンソールに表示されます。

if(id == ClickOnElement)
   printf("Click on element " + el_name);

IDは、MQLプログラムコードのどこにも定義されていないClickOnElement定数と比較されることに注意してください。この定数は、C#のGuiController自体で定義されている列挙型の一部です。

/// <概要>
/// GUIイベントの種類
/// </概要>
public enum GuiEventType
{
    Exception,
    ClickOnElement,
    TextChange,
    ScrollChange
}

ご覧のとおり、コンパイラは.NETライブラリで定義されている外部列挙型を理解し、それを使用して動作します。 

メッセージの受信方法に再び注目しましょう。このプロセスにはタイマーが含まれますが、定期的に呼び出される他の関数(OnTickなど)を使用することもできます。しかし、周期性を制御することは非常に困難です。2つの連続したOnTick呼び出しの間にどれだけの時間が経過するかは定かではありません。

さらに、呼び出しの周期性を保証することは不可能です(OnTimerでさえ)。たとえば、ストラテジーテスターでは、OnTimer閾値値呼び出し頻度は、実際の作業でこの関数に設定できるものとは大きく異なります。これらの効果により、ユーザは2つの関数呼び出しの間に連続して複数のイベントを生成することができます。例えば、MQLプログラムが最初のクリックに反応できる前に、ユーザはボタンを2、3回クリックするかもしれません。

この問題はイベントキューによって解決されます。各イベントはリストに入り、MQLプログラムによってそのパラメータが取得されるのを待ちます。プログラムは、関数内で静的変数を定義することによってイベントの最後の番号を記憶します。次回の起動時には、新しく到着したイベントが受信されます。forループが非標準シグネチャを持つのはそのためです。

//--ループはイベントの最後のインデックスを記憶し、次に関数が起動されるときにそのインデックスから作業を開始する
for(static int i = 0; i < GuiController::EventsTotal(); i++)

イベントはGuiController::GetEventメソッドを使って受け取ることができます。GuiController::SendEventを使って送信することもできます。2番目のメソッドは、データをウィンドウに送信してその内容を変更する必要があるときに使用されます。プロトタイプはGetEventと同じです。唯一の違いは、イベントのシリアル番号はここでは意味がないため含まれていないことです。詳細には説明しませんが、本稿の最後の部分に例を示して説明します。

まだ調べていない最後のメソッドはGuiController::HideFormです。そのシグネチャはShowFormに似ていますが、アクションは全く反対です。このメソッドはウィンドウを非表示にします。そのためには、その場所と名前を指定する必要があります。

ご覧のとおり、フォームを表示し、着信イベントを分析するためのMQLコードは非常にコンパクトで簡単です。実際、コードは3つの簡単なステップを記述しています。

  1. プログラム起動時にウィンドウを表示します。
  2. ウィンドウから新しいデータを受け取ります。
  3. プログラムを終了するときにウィンドウを非表示にします。

ご覧のとおり、構造は非常に単純です。また、開発したフォームのコードにもご注意ください。Window Formsには同じコードが含まれていますが、C#は1行も記述していません。GuiControllerと同様に、Visual Studioの強化されたコード自動生成手段がすべての作業を補いました。これがネットテクノロジの力が発揮される方法です。強力な環境の最終的な目標は単純さです。


GuiControllerの舞台裏

C#に精通していない方は、この節を飛ばして構いません。この節は、GuiControllerがどのように機能するのか、そして個々の独立した.NETアプリケーションへのアクセスがどのように行われるのかを理解したい人にとっては興味深いことです。

GuiControllerは、静的部分とインスタンス部分の2つの部分からなる共有クラスです。クラスの静的部分には、MetaTraderとやり取りするためのオープンな静的メソッドが含まれています。クラスのこの部分では、MetaTrader 5とコントローラ自体の間のインターフェイスが実装されます。2番目の部分はインスタンス1です。つまり、この部分のデータとメソッドはインスタンスレベルでのみ存在します。その役割は、グラフィックウィンドウが配置されている独立したネットアセンブリと対話することです。Windows Formsのグラフィックウィンドウは、フォーム基本クラスから継承されたクラスです。したがって、各ユーザーウィンドウを使用して、より高度で抽象的なレベルのFormクラスで作業できます。

ネットアセンブリ(DLLやEXEなど)には、本質的にオープンなネットタイプが含まれています。それらへのアクセス、それらのプロパティそしてメソッドさえもがとても簡単です。これは.NETのリフレクションというメカニズムを使用して実行できます。このメカニズムのおかげで、.NETで作成されたDLLやEXEのようなすべてのファイルは、必要な要素の存在について調べることができます。これがGuiControllerクラスの動作です。.NETアセンブリへの絶対パスが渡されると、コントローラは特別なメカニズムを使用してこのアセンブリを読み込みます。その後、表示する必要があるグラフィックウィンドウを見つけます。作業を実行するGetGuiControllerメソッドのコードを提供しましょう。

/// <概要>
/// Windows Form用のGuiControllerを作成する
/// </概要>
/// <param name="assembly_path">アセンブリへのパス</param>
/// <param name="form_name">Windows Form名</param>
/// <returns></returns>
private static GuiController GetGuiController(string assembly_path, string form_name)
{
    //-- 指定されたアセンブリを読み込む
    Assembly assembly = Assembly.LoadFile(assembly_path);
    //-- その中で指定されたフォームを見つける
    Form form = FindForm(assembly, form_name);
    //-- 検出したフォームに管理コントローラを割り当てる
    GuiController controller = new GuiController(assembly, form, m_global_events);
    //-- 管理コントローラを呼び出しメソッドに返す
    return controller;
}

この手順は、いわゆるリソースグラバー(プログラムのバイナリコードからアイコンや画像などのメディアコンテンツを抽出できる特別なプログラム)の動作に似ています。

フォームの検索はリフレクションを使用して実行されます。FindFormメソッドは、渡されたアセンブリで定義されているすべての型を受け取ります。これらの型の中から、基本型がフォーム型と一致するものを検索します。検出された型の名前が必要な型の名前とも一致する場合は、この型のインスタンスが作成され、次の形式で返されます。

/// <概要>
/// 必要なフォームを見つける
/// </概要>
/// <param name="assembly">アセンブリ</param>
/// <returns></returns>
private static Form FindForm(Assembly assembly, string form_name)
{
    Type[] types = assembly.GetTypes();
    foreach (Type type in types)
    {
        //assembly.CreateInstance()
        if (type.BaseType == typeof(Form) && type.Name == form_name)
        {
            object obj_form = type.Assembly.CreateInstance(type.FullName);
            return (Form)obj_form;
        }
    }
    throw new Exception("Form with name " + form_name + " in assembly " + assembly.FullName + "  not find");
}

最もエキサイティングな瞬間は、アプリケーション自体の開発とその起動です。結局のところ、本当のプログラムは外部のバイナリデータセットから生まれ、独立したアプリケーションとして作動し始めます。

インスタンスを作成したら、コントローラーが割り当てられます。コントローラは、送信されたフォームを監視するGuiControllerクラスのインスタンスです。コントローラの目的は、イベントを追跡してフォームに渡すことです。

フォームはパラレルスレッドで起動および削除されます。これにより、現在の操作の完了を待っている間に現在のスレッドがブロックされるのを防ぎます。現在のスレッドでウィンドウを起動したとします。ウィンドウが機能すると、それを呼び出した外部プロセスはウィンドウが閉じるのを待ってハングします。この問題は別のスレッドでウィンドウを起動すれば解決します。

対応するコントローラメソッドは、ウィンドウの起動と削除を担当します。

/// <概要>
/// MetaTraderから呼び出されるカスタムフォームは、
/// インターフェースが即応性を保つために非同期的に実行されるべきである
/// </概要>
public static void ShowForm(string assembly_path, string form_name)
{
    try
    {
        GuiController controller = GetGuiController(assembly_path, form_name);
        string full_path = assembly_path + "/" + form_name;
        m_controllers.Add(full_path, controller);
        controller.RunForm();
    }
    catch(Exception e)
    {
        SendExceptionEvent(e);
    }
}
        
/// <概要>
/// EAがフォームの処理を終えた後、その実行は完了するはずである
/// </概要>
public static void HideForm(string assembly_path, string form_name)
{
    try
    {
        string full_path = assembly_path + "/" + form_name;
        if (!m_controllers.ContainsKey(full_path))
            return;
        GuiController controller = m_controllers[full_path];
        controller.DisposeForm();
    }
    catch(Exception ex)
    {
        SendExceptionEvent(ex);
    }
}

考慮すべき最後のコントローラ関連のことはイベントの取り扱い扱いです。リフレクションを使用して新しいフォームが作成されると、そのフォームはそのイベントをサブスクライブするメソッドに渡されるか、コントローラが処理できるイベントだけに渡されます。<要素 - イベントハンドラリスト>マッピングはこのために作成されます。このマッピングでは、イベントハンドラは必要なイベントにサブスクライブしています。 

/// <概要>
/// 利用できるイベントにサブスクライブする
/// </概要>
/// <param name="form">Windows Forms</param>
private void SubscribeOnElements(Form form)
{
    Dictionary<Type, List<HandlerControl>> types_and_events = new Dictionary<Type, List<HandlerControl>>();
    types_and_events.Add(typeof(VScrollBar), new List<HandlerControl>() { vscrol => ((VScrollBar)vscrol).Scroll += OnScroll });
    types_and_events.Add(typeof(Button), new List<HandlerControl>()  { button => ((Button)button).Click += OnClick });
    types_and_events.Add(typeof(Label), new List<HandlerControl>());
    types_and_events.Add(typeof(TextBox), new List<HandlerControl>() { text_box => text_box.LostFocus += OnLostFocus, text_box => text_box.KeyDown += OnKeyDown });
    foreach (Control control in form.Controls)
    {
        if (types_and_events.ContainsKey(control.GetType()))
        {
            types_and_events[control.GetType()].ForEach(el => el.Invoke(control));
            m_controls.Add(control.Name, control);
        }
    }
}

各フォームには、要素の未解決リストが含まれています。要素のリストを検索しながら、メソッドはコントローラがサポートできる要素を見つけ、それが必要とするイベントにサブスクライブします。コントローラでサポートされていないフォーム上の要素は、単に無視されます。それに関連付けられたイベントはMQLプログラムに配信されず、MQLプログラム自体はこの要素と対話できません。


GUIに基づいた取引パネル

システムのすべての部分を網羅したので、ここで本当に便利なものを作成しましょう。チャートの左上隅から標準の取引パネルを類推します。

図15 MetaTrader 5内蔵取引パネル

もちろん、このパネルはWindows OSウィンドウの標準的なグラフィック要素で構成されるので、機能は同一のままですが、デザインがよりシンプルになります。

そのようなパネルは一から開発することができますが、ビジュアルデザイナーの説明は、本稿のトピックの範囲を超えています。そのため、パネルを含むプロジェクトをVisual Studioにアップロードするだけにします。これには、アーカイブからプロジェクトをコピーしてVisual Studioで開くか、次のアドレスのリモートGitリポジトリからダウンロードするという2つの方法があります。 

https://github.com/PublicMqlProjects/TradePanelForm

この場合、gitを使った作業は該当する節で説明されているのと同じなので繰り返しません。

プロジェクトをダウンロードして開くと、次のフォームが表示されます。

図16 Visual StudioエディタのTradePanelウィンドウ

このプロジェクトには、取引パネルのレイアウトが含まれています。このような実際のプロジェクトでは、このフォームに配置された要素に絶えずアクセスし、それらにイベントを送信する必要があります。これらの目的のためには、各要素を名前で参照する必要があります。したがって、要素の名前は有意義で、覚えられるものでなければいけません。使用する要素がどのように呼ばれるのかを見てみましょう。各要素の名前を表示するには、最初に必要な要素を選択しながら、[プロパティ]ウィンドウで[名前]プロパティを探します。たとえば、[Buy]ボタンの名前は Buttonbuyです。

図17 プロパティウィンドウでの要素名

要素に表示されているテキストと要素自体の名前を区別する必要があります。これらは異なる値ですが、よく似た意味を持ちます。

これがこの取引パネルに含まれる要素のリストです。

要素は数個しかありませんが、それらの組み合わせによってかなり高度なインターフェイスが提供されます。前例同様、ここでのソリューションにはC#コードは1行も含まれていません。必要なすべての要素プロパティが[プロパティ]ウィンドウに表示され、要素の位置とサイズはドラッグアンドドロップ、つまりマウスで設定されます。


グラフィックウィンドウとEAコードの統合

ウィンドウの準備が整ったので、取引EAに統合する必要があります。MQLを使用して、インターフェイス要素と対話するための取引ロジックを作成します。完全なEAコードは以下の通りです。

//+------------------------------------------------------------------+
//|                                                   TradePanel.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#import  "MtGuiController.dll"
#include <Trade\Trade.mqh>
string assembly = "c:\\Users\\Bazil\\source\\repos\\TradePanel\\TradePanel\\bin\\Debug\\TradePanel.dll";
string FormName = "TradePanelForm";
double current_volume = 0.0;

//-- 注文を実行するための取引モジュール
CTrade Trade;  
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                            |
//+------------------------------------------------------------------+
int OnInit()
{
//--- タイマーを作成し、ウィンドウを表示して数量を設定する
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, FormName);
   current_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, DoubleToString(current_volume, 2));
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- フォームを廃棄する
   EventKillTimer();
   GuiController::HideForm(assembly, FormName);
//---
  }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                           |
//+------------------------------------------------------------------+
void OnTick()
{
//--- 気配値をリフレッシュする   
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   GuiController::SendEvent("AskLabel", TextChange, 0, 0.0, DoubleToString(ask, Digits()));
   GuiController::SendEvent("BidLabel", TextChange, 0, 0.0, DoubleToString(bid, Digits()));
//---
}

//+------------------------------------------------------------------+
//| タイマー関数                                                    |
//+------------------------------------------------------------------+
void OnTimer()
{
//--- タイマーで新しいイベントを取得する
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == TextChange && el_name == "CurrentVolume")
         TrySetNewVolume(sparam);
      else if(id == ScrollChange && el_name == "IncrementVol")
         OnIncrementVolume(lparam, dparam, sparam);
      else if(id == ClickOnElement)
         TryTradeOnClick(el_name);
   }
//---
}
//+------------------------------------------------------------------+
//| 数量を検証する                                                   |
//+------------------------------------------------------------------+
double ValidateVolume(double n_vol)
{
   double min_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   double max_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);
   //-- 下限を確認する 
   if(n_vol < min_vol)
      return min_vol;
   //-- 上限を確認する
   if(n_vol > max_vol)
      return max_vol;
   //-- 数量を正規化する
   double vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double steps = MathRound(n_vol / vol_step);
   double corr_vol = NormalizeDouble(vol_step * steps, 2);
   return corr_vol;
}
//+------------------------------------------------------------------+
//| 与えられたテキストからの新しい現在の数量を設定する                  |
//+------------------------------------------------------------------+
bool TrySetNewVolume(string nstr_vol)
{
   double n_vol = StringToDouble(nstr_vol);
   current_volume = ValidateVolume(n_vol);
   string corr_vol = DoubleToString(current_volume, 2);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, corr_vol);
   return true;
}
//+------------------------------------------------------------------+
//| 取引注文を実行する                                              |
//+------------------------------------------------------------------+
bool TryTradeOnClick(string el_name)
{
   if(el_name == "ButtonBuy")
      return Trade.Buy(current_volume);
   if(el_name == "ButtonSell")
      return Trade.Sell(current_volume);
   return false;
}
//+------------------------------------------------------------------+
//| 現在の数量を増減する                                        |
//+------------------------------------------------------------------+
void OnIncrementVolume(long lparam, double dparam, string sparam)
{
   double vol_step = 0.0;
   //-- インクリメントプレスを検出する
   if(dparam > lparam)
      vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- デクリメントプレスを検出する
   else if(dparam < lparam)
      vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- インクリメントプレスを再検出する
   else if(lparam == 0)
      vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- デクリメントプレスを再検出する
   else
      vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double n_vol = current_volume + vol_step;
   current_volume = ValidateVolume(n_vol);
   string nstr_vol = DoubleToString(current_volume, 2);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, nstr_vol);
}
//+------------------------------------------------------------------+

提示されたコードはここでのフォームの中核をなすものです。機能全体が標準のイベント処理関数内にMQL5で書かれていることは注目に値します。提供されたコードを詳細に分析しましょう。

OnInit関数が最初に行うことは、200ミリ秒の分解能でタイマーを設定することです。その後、ShowFormメソッドを使用してウィンドウが表示されます。

GuiController::ShowForm(assembly, FormName);

ここで「assembly」はウィンドウが配置されているアセンブリへのパスで、「FormName」はフォームクラスの名前です。

ウィンドウが起動した直後に、CurrentVolumeテキストボックスに最小数量を設定します。

GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, DoubleToString(current_volume, 2));

最小数量自体は、SymbolInfoDouble関数を使用して現在の取引環境に基づいて計算されます。

EAを閉じると、フォームウィンドウも閉じます。これは、GuiController::HideFormメソッドを使用してOnDeinit関数で行われます。 

OnTick関数は現在の売り/買呼値の変更に反応します。したがって、関数で現在の価格を受け取り、それをフォームの適切なテキストラベルに渡すと、パネルには現在の価格のすべての変更が即座に表示されます。

//-- 売呼値を取得する
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
//-- 買呼値を取得する
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
//-- AskLabelテキストラベルのテキストを現在の売呼値を文字列に変換したもので置き換える
GuiController::SendEvent("AskLabel", TextChange, 0, 0.0, DoubleToString(ask, Digits()));
//-- BidLabelテキストラベルのテキストを現在の買呼値を文字列に変換したもので置き換える
GuiController::SendEvent("BidLabel", TextChange, 0, 0.0, DoubleToString(bid, Digits()));

ユーザがフォームで実行できる3つのアクションはOnTimer関数で追跡されます。これらのアクションは次のとおりです。

  • CurrentVolumeテキストラベルに新しい数量を入力する
  • スクロールの形をした数量ステップの増減ボタンをクリックする
  • 取引要求を送信するためにBuyまたはSellボタンをクリックする

ユーザが実行したアクションに応じて、特定の命令セットが実行されます。現在の数量を最小許容ステップで増減するためにスクロールボタンをクリックするイベントはまだ分析されていないので、それについてもっと詳しく説明しましょう。

現在のイベントモデルのスクロールイベントは、lparamとdparamの2つのパラメータで構成されています。 1番目のパラメータは、ユーザがスクロールボタンをクリックする前のゼロレベルに対するキャリッジシフトを特徴付ける従来の値を含みます。2番目のパラメータは、クリックした後も同じ値になります。スクロール自体には、一定の操作範囲(例: 0~100)があります。したがって、lparamが30でdparamが50の場合、これは垂直スクロールが30から最大50%まで移動したことを意味します(垂直スクロールは同じ量だけ右に移動します)。パネル内のスクロール位置を定義する必要はありません。ユーザがどのボタンをクリックしたのかを知る必要があるだけです。これを行うには、以前の値と現在の値を分析する必要があります。このためにOnIncrementVolume関数が用意されています。スクロールクリックの種類を定義した後、SystemInfoDoubleシステム関数を使用して定義する最小音量ステップで現在の音量を増減します。

スクロール矢印は、新しい取引量を設定する唯一の方法ではありません。テキストラベルに直接入力することもできます。ユーザが新しい文字を入力すると、Windows Formsは対応するイベントを生成します。ただし、各文字を個別に分析するのではなく、最終的な文字列を分析することが重要です。したがって、GuiControllerは「Enter」キーの押下とテキストラベルのフォーカスの変更に反応します。これらのイベントはテキスト入力の終わりと見なされます。そのうちの1つが発生すると、生成されたテキストはEAによって順次読み取られるイベントキューに渡されます。ラベルイベントのテキスト変更に到達した後、MQLプログラムは新しい値を解析し、指定された値に従って新しいボリュームを設定します。分析はValidateVolume関数を使用して実行されます。入力された数量の以下のパラメータが制御されます。

  • 数量は、最小値と最大値の間になければならない
  • 数量値はそのステップの倍数でなければならない例えば、ステップが0.01ロットで、ユーザが1.0234の値を入力した場合、それは1.02に調整されます。

現在の取引環境の助けを借りてのみこれらのパラメータを制御することが可能であることに注意してください。したがって、ユーザが作成したフォームではなく、ユーザが入力した値の制御全体がMQLプログラム自体によって実行されます。 

チャートで取引パネルを起動し、それを使っていくつかの取引を実行してみましょう。


図18 リアルタイムでのパネル操作 

ご覧のとおり、取引パネルは割り当てられているすべての機能を正常に果たしています。


ストラテジーテスターでのGUI操作

MetaTrader 5ストラテジーテスターには、MQL GUI開発者が考慮すべきいくつかの特徴があります。主な特徴は、OnChartEventグラフィックイベント処理関数がまったく呼び出されないことです。グラフィック形式はリアルタイムでユーザーと作業することを含むため、これは論理的です。ただし、テスターに​​特定の種類のパネルを実装することは非常に興味深いでしょう。これらはいわゆる取引プレーヤーで、ユーザは自分のトレーディング戦略を手動でテストできます。たとえば、ユーザは[Buy]ボタンと[Sell]ボタンをクリックすると、ストラテジーテスターは現在の市場価格を早送りで生成し履歴の取引操作をシミュレートします。私たちが開発したTradePanelはまさにこのタイプのパネルです。その単純さにもかかわらず、それは最も必要な機能を備えた平易な取引プレーヤーであるかもしれません。 

しかし、MetaTrader 5ストラテジーテスターでパネルがどのように機能するかを考えてみましょう。TradePanelのグラフィカルウィンドウは、独立したネットアセンブリとして存在します。したがって、それは現在のMetaTrader 5環境あるいはターミナル自体にも依存しません。厳密に言えば、他のプログラムから実行することもできますが、ユーザー自身でもexeコンテナにあるアセンブリを起動できます。

したがって、私たちのプログラムはOnChartEventを呼び出す必要はありません。さらに、ストラテジーテスターで定期的に起動される任意のイベント処理機能で、ウィンドウ内のデータを更新し、ユーザーから新しい注文を受け取ることができます。OnTickとOnTimerはそのような関数です。パネルはそれらを通して動作します。したがって、リアルタイム操作用に設計されていますが、このパネルはストラテジーテスターでもうまく機能します。変更は必要ありません。テスターでパネルを起動し、いくつかの取引を実行して、これを確認しましょう。


図19 ストラテジーテスターのシミュレーションモードでのパネル操作

C#を使用したグラフィカルインターフェイスの開発は、ストラテジーテスターで作業する際に予想外のボーナスをもたらしています。Windows Formsアプリケーションの場合、ストラテジーテスターはいかなる制限も課しません。イベントモデルの操作機能は、パネルにも操作方法にも影響しませんストラテジーテスターで動作するようにプログラムを変更する必要もありません。 


終わりに

本稿では、カスタムのビジュアルフォームを素早く簡単に開発するためのアプローチを提案しました。このアプローチでは、グラフィカルアプリケーションは3つの独立した部分(MQLプログラム、GuiControllerアダプタ、ビジュアルパネル自体)に分割されます。アプリケーションのすべての部分は互いに独立しており、MQLプログラムはMetaTrader取引環境で動作し、GuiControllerを介してパネルから受信したパラメータに基づいて取引または分析機能を実行します。GuiController自体は、フォームやフォームの要素が変更されても変更する必要がない、独立したプログラムです。最後に、グラフィカルパネルは、Visual Studioの高度なビジュアルツールを使用してユーザ自身によって作成されます。このおかげで、かなり複雑なフォームを開発するとしてもC#プログラミング言語の知識は必要ではないでしょう。

カスタムフォーム自体は、起動するプログラムに依存せず、それがMetaTrader 5自体でもストラテジーテスターでも関係ありません。どちらの場合も、ウィンドウは組み込みロジックに従って動作します。その上、ウィンドウはそれを呼び出した関数に依存しません。このおかげで、グラフィカルインターフェイスはMetaTrader 5自体でもストラテジーテスターでも同様に機能します。そして、ウィンドウでEAが機能するか指標が機能するかは関係ありません。どの場合でも、ウィンドウの動作は同じです。

上記の特徴を考慮すると、確かに、提案されたアプローチにはファンができるでしょう。このアプローチは、取引エンジンやプレイヤー、データパネル、その他の標準的なGUI形式のビジュアルフォームなど、半自動フォームを開発したいと思っている人たちの間で最も人気があるかもしれません。このアプローチは、また、プログラミングに精通していない人にも魅力的です。カスタムフォームを開発するために必要なのはMQL5の一般的な知識だけです。 

他のテクノロジと同様に、提案されているアプローチには欠点があります。おもな欠点は、サードパーティのDLLの呼び出しが禁止されているために、市場での操作が不可能であることです。その上、なじみのないDLLまたはEXEは悪意のある機能を含む場合があるので、起動は安全でないかもしれません。しかし、プロジェクトのオープン性がこの問題を解決します。ユーザは自分が開発したプログラムは、自分が指定されたもの以外の要素を含んでいないことを知っています、そしてGuiControllerは公共のオープンソースプロジェクトです。もう1つの欠点は、アプリケーション間のやり取りがかなり複雑なプロセスであり、フリーズまたは予期せぬプログラムの終了を引き起こす可能性があるということです。これはインターフェイスの開発者に大きく依存します。純粋なMQL5で開発されたモノリスのものと比較して、そのようなシステムを停止することはより簡単です。

このプロジェクトは現在始まったばかりです。グラフィックウィンドウと対話する現在の機能はまだ非常に限られており、おそらく、ここには必要なコントロールがないでしょう。何にしても、本稿のWindows Formsの開発とそれらとの対話が見たよりも簡単であることを示すという主な役割は完了しました。本稿がMQLコミュニティにとって役立つようでしたら、引き続きこの分野の作業を基に開発を続けます。