MetaTrader 5とMATLABの連携
はじめに
著者の最初の稿MetaTrader4とMATLABエンジン(バーチャルMATLABマシン)間の連携はMQLコミュニティに知られていました。読者の中には、(1Q2W3E4R5T)このプロジェクトをBorlandからVS2008移動することが可能な方もいらっしゃいました。しかし、時は絶え間なく前に向かって流れ、(悲しいかな現実は)ポインタとダイナミックメモリを導入したMQL5搭載の後継機、MetaTrader5に道を譲ってMetaTrader4は姿を消しつつあります。
革新のおかげで、MATLABエンジンバーチャルマシンとの互換性あるユニバーサルライブラリについて書く機会、またMATLABによって生成されたライブラリをMetaTrader5と直接リンクする機会を得ました。 本稿はそういう機能性について述べていきます。本稿は理論的に前稿を踏まえ、またMetaTrader 5と MATLABの間の連携における問題により力を入れて取り組んでいきます。
まだなじみのない読者が本稿で述べる内容をより理解しやすいように、3つのパートに分割しました。理論、参照、実践の3つです。理論部分はMQL5と MATLABで使われるデータタイプとその両者間での変換について述べています。参照部分では、DLL作成に必要な言語構成と関数の言い回しを学習します。実践部分ではこの連携の陥りやすい危険を分析します。
すでに経験ある読者の方は理論部分と参照部分はとばして実践部分から始めてください。その他の方は必ず理論部分と参照部分を読んでその後実践に進んでください。また、『文献』で言及している書籍を読むのもよいでしょう。
1. 理論
1.1MQL5と MATLABのデータ タイプ
1.1.1シンプルなデータ タイプ
では進めていきましょう。
まず必要なことはMQL5とMATLABの内部世界を知ることです。変数タイプを調べたら、形だけはそれらが瓜二つであることが判ります。
MQL5 | サイズはバイト表示 | 最小値 | 最大値 | MATLAB |
---|---|---|---|---|
char | 1 | -128 | 127 | int8/char |
uchar | 1 | 0 | 255 | int8/char |
bool | 1 | 0(false) | 1(true) | Array logical |
short | 2 | -32768 | 32767 | Array int16 |
ushort | 2 | 0 | 65535 | Array int16 |
int | 4 | -2147483648 | 2147483647 | Array int32 |
uint | 4 | 0 | 4294967295 | Array int32 |
long | 8 | -9223372036854775808 | 9223372036854775807 | Array int64 |
ulong | 8 | 0 | 18446744073709551615 | Array int64 |
float | 4 | 1.175494351e-38 | 3.402823466e+38 | Array single |
double | 8 | 2.225073858507201e-308 | 1.7976931348623158e+308 | Array double |
Table 1. MQL5と MATLABのデータ タイプ
一つ大きな違いがあります。それは、MQL5の変数はシンプルでありえるし複合的(複雑)でもありえます。 MATLABではすべての変数はマルチディメンション(複雑)、すなわち行列です。この違いについては常に頭に入れておかなければなりません!
1.1.2複雑なデータタイプ
MQL5のデータには、4種類のコンプレックスタイプがあります。配列、ストリング、ストラクチャ、そしてクラスです。コンプレックスデータタイプはシンプルデータタイプがいくつかセットになったもので、一定長のメモリブロックに組み込まれています。そのようなデータを扱う際に知っておくべきことは、それぞれのメモリブロックのバイトサイズ、またはエレメント(クラス以外)数です。ここで話題にするのは、配列とストリングだけです。なぜならMATLABにクラスとMQL5ストラクチャをサブミットすることには意味がないからです。
タイプの配列を渡すとき知っておくべきことは、ArraySize()関数を使うタイプ(ディメンション)とエレメント数です。特に注意が必要なのは、MetaTrader 5のインデックスです。通常それは逆向きです。最初のエレメントには次のものよりも最近のデータが含まれている、ということです。 ArrayIsSeries()関数を使ってこのことを確認します。And MATLABは次のようなインデックスです。最初のエレメントには次のものよりも古いデータが含まれています。よって、 フラグがAS_SERIES = TRUEであれば、MATLABに送信する前に配列を逆にする必要があります。上記を踏まえ、以下を確認していきます。
- MQL5プログラムに対して、配列を『見えないように』『リバース』します。charタイプと二次元配列は例外です。両者には変更を加えずそのままにしておきます。
- MATLABからの配列はすべて『見えないように』リバースしAS_SERIESフラッグを TRUEに割り当てます。ただし、charタイプと二次元配列は例外です。両者には変更を加えずそのままにしておきます。
- 『逆向き』インデックスに従い作成されたMQL5プログラムの配列では、AS_SERIESフラッグはTRUEとする必要があります。ただし、charタイプ配列と二次元配列は例外です。両者には変更を加えずそのままにしておきます。
しかしこれだけが配列で作業をする場合の制約ではありません。マルチディメンション配列または数列で作業をするとき、正確性を求めるならば、特にMATLABからのものに対しては、二次元配列以上は使用しないという制約を設けます。その場合、AS_SERIESフラグはTRUEではなく、またそのためそういった配列は『リバース』ではありません。
MQL5内のストリングはcharタイプエレメントの配列ではないことを忘れないでください。よって、ストリングの受け渡しが少々問題となります。Unicodeを使ってエンコードされたMQL5ストリングでは、MATLABはANSIエンコードを使用します。ゆえに、ストリングを渡す前、そのストリングはStringToCharArray()関数によってANSI特性の配列に変換する必要があります。逆に、MATLABからANSI特性の配列を受け取る場合、CharArrayToString()関数によって変換します。(表2を参照してください。)混乱を避けるため、MQL5プログラム内の全ストリングを保存します。charタイプ配列は保存しません。
1.2MQL5とMATLABのデータタイプ比較
関数の数を減らし、ライブラリアルゴリズムを単純化するために、自動変換によりタイプ数を減らします。この手法はデータの統一性に影響を与えません。以下のテーブルはMQL5からMATLABへデータタイプを変換するルールを表記しています。
MQL5 | MatLab同等 |
---|---|
char uchar |
Array char |
bool | Array logical |
short ushort int uint |
Array int32 |
long ulong |
Array int64* |
float double |
Array double |
string | StringToCharArray()を使うchar配列 <=> CharArrayToString()関数 |
* ただし、このタイプの変換は正確性を欠きます。ここでは使用しませんが、ご自身のプログラムではこういった変換方法を使用することも可能です。
表2 MQL5とMATLABのデータタイプ比較
もうMQL5とMATLABで使用されるデータ タイプには慣れましたね。『落とし穴』がデータ受け渡しで何を待つか、また有効にバイパスする方法はおわかりでしょう。しかしまだMATLABエンジンAPIを知る必要があり、MATLABコンパイラ4をよく知る必要もあります。
2. MATLABエンジンAPI 参照、MATLABコンパイラ4参照およびC++ インプット/アウトプットライブラリ参照
本項では、MATLABエンジンAPI の最重要関数、MATLABコンパイラ4の機能、そしてC++ 標準インプット/アウトプットライブラリの便利な機能についてお伝えします。では始めましょう。
2.1MATLABエンジンAPIおよびMCR関数
MATLABエンジンは、他のプログラムがMATLABデスクトップを使えるようにする外部インターフェースです。それにより、なんの制約もなく全MATLABパッケージをフル活用できます。
ドキュメンテーションでは述べられていませんが、システムプログラマの観点から言うと、それはPHP、MySQL、その他のようなただのバーチャルマシンで、MetaTrader 4/5とMATLAB間のデータ交換を簡単に比較的速く行うようサポートするものです。
MATLABパッケージに外部プログラムを接続するというこの手法は開発者によって推奨されています。 このインターフェースは6つの関数で構成されています。
Engine *pEng = engOpen(NULL) —この関数はMATLABデスクトップを呼び、パラメータは常にNULLです。ポインタをデスクトップのデスクリプタに返します。
int exitCode = engClose(Engine *pEng) — この関数はデスクトップを終了します。以下にあるMATLABデスクトップの残存ユーザー数を返します。
- Engine *pEng — デスクトップ デスクリプタへのポインタ
mxArray *mxVector = mxCreateDoubleMatrix(int m, int n, int ComplexFlag) — この関数はMATLABデスクトップの変数(行例)を作成し、以下にある変数(行列)にポインタを返します。 ここで
- mxArray *mxVector — 行列変数へのポインタ
- int m —行数
- int n — 列数
- ComplexFlag — MetaTrader 4/5 mxREALへのコンプレックス数タイプ
void = mxDestroyArray(mxArray *mxVector) — この関数はMATLAB数列を破壊します。以下でメモリを消去するのに必要とされます。ここで
- mxArray *mxVector — 行列変数へのポインタ
int = engPutVariable(Engine *pEng, char *Name, mxArray *mxVector) — この関数はデスクトップに変数を送信します。mxArrayタイプの変数を作成するだけではなく、以下において作成した変数を MATLABへ送信する必要があります。ここで
- Engine *pEng — デスクトップ デスクリプタへのポインタ
- char *Name — MATLABデスクトップにおけるcharタイプの変数名
- mxArray *mxVector — 行列変数へのポインタ
mxArray *mxVector = engGetVariable(Engine *pEng, char *Name) — この関数はデスクトップから変数を取得します。前の関数の逆を行います。以下ではmxArrayタイプの変数のみ受けつけます。ここで
- mxArray *mxVector — 行列変数へのポインタ
- Engine *pEng — デスクトップ デスクリプタへのポインタ
- char *Name — MATLABデスクトップにおけるcharタイプの変数名
double *p = mxGetPr(mxArray *mxVector) — この関数は値配列にポインタを取得します。以下でmemcpy() (see 2.3 C++ Standard Input/Output Library)に従いデータをコピーするのに使用されます。ここで
- double *p — ダブルタイプ配列へのポインタ
- mxArray *mxVector — 行列変数へのポインタ
int = engEvalString(Engine *pEng, char *Command) — この関数は以下MATLABデスクトップにコマンドを送信します。ここで
- Engine *pEng — デスクトップ デスクリプタへのポインタ
- char *Command — MATLABに対するコマンド。charタイプのストリング
おそらく、MATLABエンジンAPIによりダブルタイプにしかmxArrayストラクチャの作成が可能でないことはご存じでしょう。この制約があるからといって、性能に影響するものではありませんが、ライブラリのアルゴリズムには影響があります。
MCR (MCR instance) — はMATLABパッケージの特殊なライブラリで、どんなコンピュータでもMATLAB環境によって生成される独立アプリケーション/パブリックライブラリの実行を可能にします。完全なMATLABパッケージがあったとしても、<MATLAB>\Toolbox\compiler\deploy\win32フォルダにあるMCRInstaller.exeファイルを実行してMCRライブラリをインストールしなければならない点には注意が必要です。そのため、MATLAB環境により作成されたパブリックライブラリ関数を呼ぶ前にMCR初期化関数を呼ぶ必要があります。
bool = mclInitializeApplication(const char **option, int count) – 以下でMCRが順調に開始されるとTRUEを返します。それ以外にはFALSEを返します。ここで
- const char **option — mcc - Rにあるようなオプションのストリング。通常値はNULL
- int count — サイズオプションのストリング。通常値は0
パブリックライブラリを終了するには以下を呼びます。
bool = mclInitializeApplication(const char **option, int count) – MCRが順調に終了されるとTRUEを返します。
2.2MATLABコンパイラ4
MATLABコンパイラによりM関数から以下を作成することができます。
- MATLABがインストールされていなくても動作する独立アプリケーション
- MATLABがエンドユーザのシステムになくても使用可能なC/C++共通ライブラリ
コンパイラはMATLABのほとんどのコマンドやパッケージをサポートしますが、すべてをサポートするものではありません。制約を網羅したリストはMATLABのウェブサイトで参照できます。このメソッドによりMetaTrader 5とMATLABの『ソフトウェア独立型バンドル』の作成が可能ですがMATLAB エンジンと異なり、そのためには経験豊富なプログラマでなければならず、コンパイルの深い知識が要求されます。
MATLABには少なくとも以下のC/C++コンパイラの一つが必須です。
- Lcc C (通常MATLABに搭載されています。)ただのCコンパイラです。
- Borland C++バージョン5.3、5.4、5.5、5.6
- マイクロソフト ビジュアルC/C++ バージョン6.0、7.0、7.1
MATLABコンパイラ4は前機と異なり、インターフェースコード(ラッパー)のみを生成します。すなわち、MATLABコンパイラ4は m-関数をバイナリやC/C++コードに変換しません。しかし、コンポーネントテクノロジファイル(CTF)を基にした特殊なファイルを作成します。そこにはm-関数をサポートするのに必要な様々なパッケージの統合物が含まれています。MATLABコンパイラはまた独自の(unrepeated) 1024ビットキーを用いてこのファイルを暗号化します。
それでは、MATLABコンパイラ4 動作のアルゴリズムについて考察していきます。それ抜きでは、コンパイルするときになってつまらない誤りをたくさん犯すことになるからです。
- 依存性分析 — この段階ですべての関数を決めます。MEXファイル、Pファイルですが、それらはコンパイルされたm関数が依存するものです。
- アーカイブ作成 - CTFが作成されます。それは暗号化され圧縮されます。
- ラッパーのオブジェクトコードを生成します。 – この段階で全ソースコードが作成され、コンポーネントに必要となります。
- コマンドライン(NameFile_main.c)で指定される関数に必要なC/C++インターフェースコード
- m-コード(CTFファイルに保管されている暗号化キーとパスを含む)実行に必要な全情報を含むコンポーネントファイル
- C/C++ 翻訳 ここでC/C++ ソースコード ファイルはオブジェクト ファイルにコンパイルされます。
- リンクプロジェクト構築の最終段階
MATLABコンパイラアルゴリズムのふるまいに慣れたところで、 コンパイラ(mcc)使用の際の動作を細かく計画するためのキーについてもうすこし詳しく学習する必要があります。
キー | 目的 |
---|---|
ファイル名 | アーカイブに<filename>ファイルを追加し、CTFアーカイブに追加するファイルを判断します。 |
l | 関数ライブラリを生成するマクロ |
N | 最小限必要なディレクトリセット以外のパスをすべて消去します。 |
p <directory> | 手順に従い翻訳パスを追加します。Nキーを要求します。 |
R -nojvm | MCRオプション (MATLABコンポーネントランタイム。MATLABヘルプを参照)をキャンセルします。 |
W | 関数ラッパーの作成を管理します。 |
lib | 初期化生成と関数完成 |
メイン | main()関数のPOSIXシェルを作成します。 |
T | アウトプットステージを指定します。 |
codegen | 独立型アプリケーションのラッパーコードを作成します。 |
compile:exe | codegenに同じ |
compile:lib | パブリックDLLのラッパーコードを作成します。 |
link:exe | コンパイルに同じ:exe plus linking |
link:lib | コンパイルに同じ:exe plus linking |
表3 Matlab mccコンパイラ(バージョン4)のキー
表3はよくある問題を解決する基本のポイントを含んでいます。 より詳しいヘルプにはMATLABコマンドhelp mccまたはdoc mccを使います。
MATLABリンカーを知る必要があります。以下は主なキー(mbuild)です。
キー | 目的 |
---|---|
-設定 | 連携モードでは、コンパイラオプションファイルの定義mbuildの呼び出しの初期設定で使用されるとなっています。 |
-g | デバッグ情報を使ってプログラムを作成します。DEBUGFLAGSをファイルの最後につけたします。 |
-O | オブジェクトコードの最適化 |
表4 Matlab mbuildリンカー(バージョン4)のキー
表4に主なキーを挙げます。さらなる情報には help mbuildまたはdoc mbuildコマンドを使います。
2.3C++ 標準インプット/アウトプットライブラリ
標準インプット/アウトプットライブラリの使用によりデータを正確にコピーすることができます。それを使用することで、プログラム設計段階での『つまらない』エラーを起こさないでおくことができます。(たとえば初心者プラグラマーの多くがメモリブロック全体をコピーするところでポインタだけをメモリブロックにコピーしてしまう)インプット/アウトプットライブラリ全体からただ一つだけ注意を向ける関数があります。
void *pIn = memcpy(void *pIn, void *pOut, int nSizeByte) – この関数は、nSizeByteバイトサイズで変数や配列をpOutからpInにコピー(クローン)します。ここで
- void *pIn — 配列のポインタ。コピー先
- void *pOut — 配列のポインタ。コピー元
- int nSizeByte — コピーされたデータサイズ。pIn配列サイズを超えてはいけません。 配列サイズを超えてしまうと、メモリアクセスエラーを起こします。
3. 実践
理論部分は終了しましたので、MetaTrader 5-MATLAB連携の実用部分に進みます。
おそらく予想されているかと思いますが、連携の方法は2とおりありますMATLABエンジンのバーチャルマシン使用とMATLABコンパイラによって生成されるライブラリの使用です。まず、シンプルで用途の広い方法を考察していきます。それはMATLABエンジンを使用する方法です。
この部分は最初から最後まで読んでください。両者は連携手法に違いがありそうなのに、考え方は同じでなじみある言語構造を持ちます。簡単な例を用いる方が新しいことは学びやすいものです。
3.1MetaTrader 5-MATLABエンジン間連携のユニバーサルライブラリ開発
この連携方法は美しく速いとは言い難いものですが、信頼性が高くMATLABパッケージ全体に適用可能なものです。もちろん最終モデル開発ではスピードについても言及します。開発の要は、MetaTrader 4/5-7MATLABエンジン間連携にユニバーサルラッパーを書くことです。その後、MetaTrader 4/5スクリプト/インディケータ/エクスパートはMATLABバーチャルデスクトップを管理できるようになります。そして全体の数学アルゴリズムMQLプログラムにストリングとして保存され、知財を保護するのに使うことができます。(詳細について『開発者よ、自らを守れ!』の稿を参照ください。)また、<MetaTrader 5>\MQL5\Libraries フォルダの個別ファイル、m-関数やP-関数に保存される可能性もあります。
連携のアプリケーションの可能性ある領域
- 複雑なプログラム(知財保護はMATLABパッケージとしてP-関数を用い、MQL-プログラムにarrangeされます。)を書かず『数学的モデル/考え』をテスト、表示
- MATLAB機能を利用した複雑な数学的モデルの書き出し
- 自身のスクリプト/インディケータ/エクスパートを配布しない方々へ
では進めていきましょう。みなさんは1.1 MATLABとMQL5のデータタイプ、1.2 MQL5とMATLABのデータタイプ比較, 2.1 MATLABエンジンAPI とMCR関数そして2.3 C++ 標準インプット/アウトプットライブラリの項を読まれたと思います。よって、これ以上はそういったトピックのために時間をかけ、分析することはしません。以下のブロック図を注意深く読んでください。これから扱うライブラリのアルゴリズムについて示しています。
図1 ライブラリ アルゴリズムのブロック図
図1に見られるように、ライブラリのアルゴリズムは3つのブロックで構成されています。ではブロックの目的について考察します。
- MQL5ブロックはデータ設定/受け取りの前持った準備です。
- 逆配列
- タイプ変換
- 変換をエンコードするストリング
- C/C++ ブロック
- 配列をmxArray構造体に変換します。
- MATLABエンジンコマンドを渡します。
- MATLABエンジンブロック — 計算システム
では、アルゴリズムに取り組みましょう。MQL5ブロックから始めます。注意して読んでいる読者の方はすでにMATLABとMQL5のデータタイプ項で述べられた内容の実装に着目していくということがお分かりかと思います。もしその項をお読みでなければ、ここで述べるすべてがなぜ必要か理解するのが難しいでしょう。
mlInput <variable_type> 関数のアルゴリズムはほとんど瓜二つです。MATLABバーチャルマシンにダブルタイプの変数インプットを提供するmlInputDouble() 関数についてみていきます。
プロトタイプはこちらです。
bool mlInputDouble(double &array[],int sizeArray, string NameArray)。ここで、
- array — 変数の参照、または ダブル タイプ配列です。
- sizeArray — 配列サイズ(エレメント数です。バイト表示ではありません。)
- NameArray — ストリング。MATLABバーチャルマシンの独自の変数名を含みます。 (名前はMATLAB要件に対応している必要があります。)
アルゴリズム
- StringToCharArray()関数を用い、NameArrayストリングをchar配列に変換します。
- ArrayIsSeries()関数を用いてインデックスタイプをチェックします。インデックスタイプがノーマルであれば — 値を mlxInputDouble()関数に渡します。時系列配列のELSEインデックス:配列を『リバース』し値をmlxInputDouble()関数に渡します。
- 最後の関数は返された値をmlxInputDouble() 関数に渡します。
mlGet <variable_type>関数のアルゴリズムもほとんど瓜二つです。MATLABバーチャルマシンからダブルタイプの変数を返すmlGetDouble()関数についてみていきます。
プロトタイプ
int mlGetDouble(double &array[],int sizeArray, string NameArray)。ここで
- array — 変数の参照、または <b1><i2>ダブル タイプ配列です。
- sizeArray — 配列サイズ(エレメント数です。バイト表示ではありません。)
- NameArray — ストリング。MATLABバーチャルマシンの独自の変数名を含みます。
アルゴリズム
- StringToCharArray()関数を用い、NameArrayストリングをchar配列に変換します。
- mlxGetSizeOfName()関数を用いて配列サイズを見つけます。
- IFサイズがMORE THAN ZEROの場合、ArrayResize()関数を使って求められるサイズの受取配列を割り当て、mlxGetDouble()のデータを取得し、配列サイズを返します。
- IFサイズがゼロの場合、エラーを返します。すなわち、null値です。
以上です!mlGetInt()関数とmlGetLogical()関数はdouble ->; int/boolタイプの『シャドウ』変換を行います。これを行うため、これら関数は関数の本文に一時記憶バッファを作成します。これは強制的な手法です。なぜなら残念なことにMATLAB APIはダブルタイプのデータ以外には mxArray構造体を作成することができないからです。ただし、これはMATLABがダブルタイプ以外には動作しないということではありません。
C/C++ ブロックはもっと簡単です。 - ダブルタイプから mxArray構造体にデータ翻訳を行います。それはmxCreateDoubleMatrix()関数、 mxGetPr()関数、 memcpy() 関数を用いて行われます。そのため、engPutVariable()関数の使用でデータはMATLABバーチャルマシンに渡され、データ抽出にengGetVariable()関数を使用します。ふたたび、プレフィクスを伴なう関数IntとLogicalに着目しましょう。— ブロック図で見たように、これらは直接MATLABとは連携しませんがmlxInputDouble/mlxGetDouble関数およびmlxInputChar()関数を利用します。アルゴリズムはは単純です。: mlxInputDouble/mlxGetDouble関数呼び出し — インプット/アウトプット値はdouble(!)で、『シャドウ』 MATLABコマンドを送りmlxInputChar() 関数によってデータタイプを変換します。
MATLABエンジンブロックはもっと簡単です。数学的関数を提供するだけです。ふるまいはみなさんのコマンドおよびm/p関数に依存します。
さて、プロジェクトの全『詳細』が明らかになりました。ここからはプロジェクト構築に入ります。
そのような構築はすべてメインライブラリの生成から始まります。ここでは C/C++ ブロックの生成です。そのために、ANSI-テクストエディタ(ノートパッド、ブレッドなど)DEF拡張子を伴なくファイルを作成します。ファイル名はスペースや点を含まないラテン文字であることが望ましく、それ以外だとコンパイラから気を引く『言葉』をたくさん『聞く』ことになります。このファイルより関数の不変性が提供されます。このファイルが空の場合、C/C++ コンパイラは関数をエクスポートするため、独自の『変わった名前』を付けてしまいます。
このファイルは以下を含みます。: LIBRARY — 言葉をコントロールします、LibMlEngine — ライブラリ名、EXPORTS — 第二のことばをコントロールする関数です。それから関数名がきます。ご存じかとは思いますが、関数エクスポート名はスペースや点を含んみません。MATLABEngine.zipアーカイブからのDllUnit.defファイルのテキストです。
LIBRARY LibMlEngine
EXPORTS
mlxClose
mlxInputChar
mlxInputDouble
mlxInputInt
mlxInputLogical
mlxGetDouble
mlxGetInt
mlxGetLogical
mlxGetSizeOfName
mlxOpen
これでプロジェクトの最初のファイルができました。それでは、Windows Explorer を開いて、'<MATLAB>\Extern\include' フォルダに移動しましょう。engine.hファイルをコピーします。( MATLABバーチャルマシンのヘッダファイルです。)そこでプロジェクトは構築されます。(そうしなければ、コンパイル段階でマニュアルでパスを指定することになります。)
ではC/C++ ブロックを作成します。本稿ではプログラムのソースコードを網羅することはしません。というのも、このファイルはDllUnit.cppとしてMATLABEngine.zipで参照することができ、そこで詳しく述べられているからです。__stdcall convention を使って関数作成する方がよいということに留意してください。— パラメータはスタックを通して渡され、関数はスタックを消去するからです。これはWin32/64 APIにもともとある標準です。
関数宣言の仕方をみていきます。
extern "C" __declspec(dllexport) <variable_type> __stdcall Function(<type> <name>)
- extern "C" __declspec(dllexport) — C++ コンパイラにその関数が外部関数であることを告げます。
- <variable_type> — 返される変数タイプで、void, bool, int, doubleの可能性があります。複合タイプ(Dllに知られるだけではなく、プログラムを呼ぶにも知られています。)とポインタ。
- __stdcall — 関数にパラメータを渡す、戻す宣言。Win32/64 APIの標準です。.
- Funcion — ご自身の関数名
- <type> <name> — インプット変数のタイプと名前。変数の最大数は64。
本トピックはデータ変換の方法: 10分でわかるMQL5のためのDLL稿で詳しく述べられています。
C/C++ ブロック構築:このためには標準のインプット/アウトプットライブラリを取り入れる必要があります。そして、プロジェクトに以下のファイルを追加します。(ご自身のコンパイラで:プロジェクト->プロジェクト追加)
- DllUnit.def
- <MATLAB>\Extern\lib\<win32/64>\<compiler>\ folde、ここで
<MATLAB> — MATLAB メインフォルダ
<win32/64> — 32-ビットOS向けwin32フォルダまたは64-ビットOS向けwin64フォルダ
<compiler> — Borland C/C++ バージョン向け『ボーランド』フォルダ 5-6、Microsoft Visual C++用『マイクロソフト』フォルダ- libeng.lib
- libmx.lib
次のような疑問が出るかもしれません。「私は別バージョンのコンパイラを持っていて、それはリストにありません!(ごくまれにそのようなリストは存在しません)」では、パブリックライブラリをマニュアル作成する方法をみてみます。ビジュアルC++ と ボーランド C++による方法を考えます。
- FAR open <MATLAB>\Bin\<win32/64> フォルダにおいて、ここで
<MATLAB> — MATLAB メインフォルダ
<win32/64> — 32-ビットOS用win32フォルダまたは64-ビットOS用win64フォルダ - ボーランド C++には 次を入力します:implib libeng.lib libeng.dll. libmx.dllにも同じです。
- ビジュアルC++には次を入力します:lib libeng.dll. libmx.dllにも同じです。
- その他のコンパイラの場合:どんな言語を使用するどんなコンパイラにもこのユーティリティは備わっています。 - ライブラリマネージャは通常コンソールプログラムです。<compiler _folder>\bin\*lib*.exe.
ところで、注意するのを忘れていました。- 32-ビットコンプライアに64-ビットLIBを作成しようとしないでください。まず、64-ビットアドレスサポートがコンプライアヘルプにあるか確認します。なければ32-ビットMATLAB DLLを探すか、別のC/C++ コンプライアを選択します。コンパイルを行います。その後、terminal_folder\MQL5\Librariesフォルダにあるはずのライブラリを取得します。
それではMQLを始めましょう。MetaEditorを実行し、下図にあるように『新規』をクリックします。
図2 MQL5ウィザード:ライブラリの作成
図3 MQL5ウィザード:ライブラリの一般的プロパティ
MQL5ウィザードがテンプレートを作成したら、編集に進みます。
1. 関数インポートを記載します。
//+------------------------------------------------------------------+ //| DECLARATION OF IMPORTED FUNCTIONS | //+------------------------------------------------------------------+ #import "LibMlEngine.dll" void mlxClose(void); //void – means: don't pass any parameters! bool mlxOpen(void); //void – means: don't pass and don't receive any parameters! bool mlxInputChar(char &CharArray[]); //char& CharArray[] – means: pass a reference! bool mlxInputDouble(double &dArray[], int sizeArray, char &CharNameArray[]); bool mlxInputInt(double &dArray[], int sizeArray, char &CharNameArray[]); bool mlxInputLogical(double &dArray[], int sizeArray, char &CharNameArray[]); int mlxGetDouble(double &dArray[], int sizeArray, char &CharNameArray[]); int mlxGetInt(double &dArray[], int sizeArray, char &CharNameArray[]); int mlxGetLogical(double &dArray[], int sizeArray, char &CharNameArray[]); int mlxGetSizeOfName(char &CharNameArray[]); #import
MQL 5では、『ポインタ』は2とおりの方法で渡すことができることに注意してください。
- void NameArray[] ; // 配列から渡すこの方法は読み取りデータ限定です。ただし、この参照を「コンテンツを編集する」に使用する場合、メモリアクセスエラーが発生します。(うまくすると、MetaTrader 5はSEH-フレームで静かにエラーを処理しますが、われわれは SEH-フレームを書いていないので、エラー理由も見落とす可能性があります。)
- void& NameArray[] ; // この手法では配列コンテンツの読み取りおよび編集が可能ですが、配列サイズは保持する必要があります。
関数がパラメータを受け付けない、または渡さない場合、常にvoidタイプを指定します。
2. MQLブロックの関数を網羅することはしません。MatlabEngine.mq5のソースコードはMATLABEngine.zipで参照できます。
よって、MQL5における外部関数の宣言と定義をみていきます。
bool mlInputChar(string array)export { //... body of function }
例にあるように、宣言と定義は組み合わさっています。ここでは、mlInputChar()という関数を外部 (export)関数として宣言します。それはブール タイプの値を返し、パラメータとして配列 ストリングを受け取ります。では、コンパイルです。
ライブラリの最終ブロックは完成しました。それをコンパイルしました。今度はそれを現実の条件下で検証します。
このためには、簡単な検証用スクリプトを書きます。(またはTestMLEngine.mq5ファイルのMATLABEngine.zipから入手します。)
スクリプトコードは簡単でよく書かれています。
#property copyright "2010, MetaQuotes Software Corp." #property link "https://www.mql5.com/ru" #property version "1.00" #import "MatlabEngine.ex5" bool mlOpen(void); void mlClose(void); bool mlInputChar(string array); bool mlInputDouble(double &array[], int sizeArray, string NameArray); bool mlInputInt(int &array[], int sizeArray, string NameArray); int mlGetDouble(double &array[], string NameArray); int mlGetInt(int &array[], string NameArray); bool mlInputLogical(bool &array[], int sizeArray, string NameArray); int mlGetLogical(bool &array[], string NameArray); int mlGetSizeOfName(string strName); #import void OnStart() { // Dynamic buffers for MATLAB output double dTestOut[]; int nTestOut[]; bool bTestOut[]; // Variables for MATLAB input double dTestIn[] = { 1, 2, 3, 4}; int nTestIn[] = { 9, 10, 11, 12}; bool bTestIn[] = {true, false, true, false}; int nSize=0; // Variables names and command line string strComm="clc; clear all;"; // command line - clear screen and variables string strA = "A"; // variable A string strB = "B"; // variable B string strC = "C"; // variable C /* ** 1. RUNNING DLL */ if(mlOpen()==true) { printf("MATLAB has been loaded"); } else { printf("Matlab ERROR! Load error."); mlClose(); return; } /* ** 2. PASSING THE COMMAND LINE */ if(mlInputChar(strComm)==true) { printf("Command line has been passed into MATLAB"); } else printf("ERROR! Passing the command line error"); /* ** 3. PASSING VARIABLE OF THE DOUBLE TYPE */ if(mlInputDouble(dTestIn,ArraySize(dTestIn),strA)==true) { printf("Variable of the double type has been passed into MATLAB"); } else printf("ERROR! When passing string of the double type"); /* ** 4. GETTING VARIABLE OF THE DOUBLE TYPE */ if((nSize=mlGetDouble(dTestOut,strA))>0) { int ind=0; printf("Variable A of the double type has been got into MATLAB, with size = %i",nSize); for(ind=0; ind<nSize; ind++) { printf("A = %g",dTestOut[ind]); } } else printf("ERROR! Variable of the double type double hasn't ben got"); /* ** 5. PASSING VARIABLE OF THE INT TYPE */ if(mlInputInt(nTestIn,ArraySize(nTestIn),strB)==true) { printf("Variable of the int type has been passed into MATLAB"); } else printf("ERROR! When passing string of the int type"); /* ** 6. GETTING VARIABLE OF THE INT TYPE */ if((nSize=mlGetInt(nTestOut,strB))>0) { int ind=0; printf("Variable B of the int type has been got into MATLAB, with size = %i",nSize); for(ind=0; ind<nSize; ind++) { printf("B = %i",nTestOut[ind]); } } else printf("ERROR! Variable of the int type double hasn't ben got"); /* ** 7. PASSING VARIABLE OF THE BOOL TYPE */ if(mlInputLogical(bTestIn,ArraySize(bTestIn),strC)==true) { printf("Variable of the bool type has been passed into MATLAB"); } else printf("ERROR! When passing string of the bool type"); /* ** 8. GETTING VARIABLE OF THE BOOL TYPE */ if((nSize=mlGetLogical(bTestOut,strC))>0) { int ind=0; printf("Variable C of the bool type has been got into MATLAB, with size = %i",nSize); for(ind=0; ind<nSize; ind++) { printf("C = %i",bTestOut[ind]); } } else printf("ERROR! Variable of the bool type double hasn't ben got"); /* ** 9. ENDING WORK */ mlClose(); }
スクリプトにあるように、値を入力し、そして値を取得するのです。ここがMetaTrader 4と異なる点です。MetaTrader 4では設計段階でバッファサイズを知る必要がありましたが、MetaTrader 5では、動的バッファを使用しているため、バッファサイズを知る必要がありません。
やっとMATLABバーチャルマシンを理解しました。これでMATLAB環境でDLL構築を始めることができます。
3.2MATLAB コンパイラ4で生成されたDLL を構築/使用するための技術的ガイドライン
前項では、MATLABを使用して、ユニバーサル連携のためのライブラリ作成方法を学習しました。ただ、この方法にはひとつ難点があります。 - エンドユーザからMATLABパッケージが要求されることです。 この制約は完成したソフトウェアを配布するのに数々の困難をきたします。MATLAB数学的パッケージが内蔵式コンパイラであるのはこの理由です。 そうなっていることで、MATLABパッケージから独立した『自立型アプリケーション』の作成が可能となります。ではそれを見ていきましょう。
たとえば、単純なインディケータを考えてみます。 - 移動平均 (SMA)です。 中立的ネットワークフィルタ (GRNN)を追加してややアップグレードしてみます。そうすると『ホワイトノイズ』(ランダムバースト)が滑らかになります。新規インディケータをNeoSMAと名付け、フィルタをRNNFilterとします。
これで2つのm-関数を得ました。それでMetaTrader 5から呼ぶことのできるDLLが作成可能です。
MetaTrader 5はDLLで以下のフォルダを検索することを思い出します。
- <terminal_dir>\MQL5\Libraries
- <terminal_dir>
- 現在のフォルダ
- システムフォルダ <windows_dir>\SYSTEM32;
- <windows_dir>
- システム環境変数PATHに挙げられたディレクトリ
よってこれらディレクトリのひとつを2つのm-関数(NeoSMA.mとGRNNFilter.m)に配置します。そこにDLLを構築します。この配置の事実に注目していただきたいと思います。なぜならこれはたまたまできたものではないからです。注意深い読者の方はすでにMATLABコンパイル機能についてご存じでしょう - それはコンパイル中にパスを保持します。(『2.2 MATLABコンパイラ4』を参照ください。)
プロジェクトのコンパイル前にコンパイラをコンフィギュアする必要があります。それには次の手順に従います。
- MATLABコマンドラインにmbuild -setupを入力します。
- システムにC/C++互換コンパイラがインストールされているのを確認し 'y' を押します。
- 標準Lcc-win32 C コンパイラを選択します。
- 選択コンパイラの確認 'y' を押します。
図4 プロジェクトのコンパイル
m-関数コンパイル手順に移る準備ができました。
この入力のために
mcc -N -W lib:NeoSMA -T link:lib NeoSMA.m GRNNFilter.m
キーの説明
- -N — 不要なパスをとばします。
- -W lib:NeoSMA — NeoSMAがライブラリ名となっているコンパイラを知らせます。
- -T link:lib — リンクでパブリックライブラリを作成するコンパイラを知らせます。
- NeoSMA.m and GRNNFilter.m — m-関数名
コンパイラ結果を見てみます。
- mccExcludedFiles.log — コンパイラの動作情報を含むログファイル
- NeoSMA.c — ライブラリのC変換(ラッパーのС-コードを含みます)
- NeoSMA.ctf — CTFファイル(2.2 MATLABコンパイラ4参照) 領域
- NeoSMA.h — ヘッダファイル(ライブラリ、関数、コンスタントの宣言含む)
- NeoSMA.obj — オブジェクトファイル(マシンとみせかけのコードを含むソースファイル)
- NeoSMA.exports — エクスポートされた関数の名前
- NeoSMA.dll — 今後のリンクのためのDll
- NeoSMA.lib — C/C++ プロジェクトで使用するDll
- NeoSMA_mcc_component_data.c — コンポーネントのCバージョン (used for compliance with CTF-ファイルとの整合性に使用され、パスを含むなど)
- NeoSMA_mcc_component_data.obj — コンポーネントのオブジェクトバージョン(マシンとみせかけのコードを含むソースファイル)
DLLを操作してみます。正確にはその内部ストラクチャです。それは以下(基本関数のみ)から構成されています。
- DLLの主な関数 - BOOL WINAPI DllMain()は(マイクロソフトの仕様によると)DLL内で発生するイベントを扱います。イベント:プロセスのアドレス空間へDLLをロードする、新規ストリームを作成する、ストリームを削除し、メモリからDllをアンロードする。
- DLL初期化/初期化取消のサービス関数: BOOL <NameLib>Initialize(void)/void <NameLib>Terminate(void) — はライブラリ関数を使用する前および使用終了時にMath Work環境を開始/アンロードするのに必要です。
- エクスポートされたm-関数 – void mlf<NameMfile>(int <number_of_return_values>, mxArray **<return_values>, mxArray *<input_values>, ...)。ここで
- <number_of_return_values> — 返された変数の数(配列サイズなどと混同しないでください)
- mxArray **<return_values> — mxArrayストラクチャのアドレスで そこにm-関数動作結果が返されます。
- mxArray *<input_values> — pointer to m-関数インプット変数のmxArrayストラクチャへのポインタ
ご覧のように、エクスポートされたm-関数はmxArrayストラクチャに対するアドレスとポインタを持ちます。これら関数をMetaTrader 5から直接呼ぶことができます。それはこのタイプのデータを理解しないからです。MetaTrader 5のmxArrayストラクチャについては述べません。MATLAB開発者は後にそれを変更しないとは約束していないからです。同じバージョンですらです。よって単純なDLLアダプタを書く必要があります。
そのブロック図を下記に図示します。
図5 DLL-アダプタのブロック図
MATLAB向けDLLの右側とひじょうに似通っています。そのアルゴリズムの解析はせず、コードに直接進みます。このため、C/C++ コンプライアに小さなファイルを2つ作成します。
nSMA.cpp (DllMatlab.zipより):
#include <stdio.h> #include <windows.h> /* Include MCR header file and library header file */ #include "mclmcr.h" #include "NEOSMA.h" /*--------------------------------------------------------------------------- ** DLL Global Functions (external) */ extern "C" __declspec(dllexport) bool __stdcall IsStartSMA(void); extern "C" __declspec(dllexport) bool __stdcall nSMA(double *pY, int nSizeY, double *pIn, int nSizeIn, double dN, double dAd); /*--------------------------------------------------------------------------- ** Global Variables */ mxArray *TempY; mxArray *TempIn; mxArray *TempN; mxArray *TempAd; bool bIsNeoStart; //--------------------------------------------------------------------------- int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { switch(reason) { case DLL_PROCESS_ATTACH: bIsNeoStart = false; TempY = 0; //Nullify pointers to buffers TempN = 0; TempIn = 0; TempAd = 0; break; case DLL_PROCESS_DETACH: NEOSMATerminate(); //Delete old data before exiting from Dll if(TempY != NULL) mxDestroyArray(TempY); if(TempN != NULL) mxDestroyArray(TempN); if(TempIn != NULL) mxDestroyArray(TempIn); if(TempAd != NULL) mxDestroyArray(TempAd); mclTerminateApplication(); } return 1; } //--------------------------------------------------------------------------- bool __stdcall IsStartSMA(void) { if(bIsNeoStart == false) { if(!mclInitializeApplication(NULL,0) ) { MessageBoxA(NULL, (LPSTR)"Can't start MATLAB MCR!", (LPSTR) "MATLAB DLL: ERROR!", MB_OK|MB_ICONSTOP); return false; }else { bIsNeoStart = NEOSMAInitialize(); }; }; return bIsNeoStart; } //--------------------------------------------------------------------------- bool __stdcall nSMA(double *pY, int nSizeY, double *pIn, int nSizeIn, double dN, double dAd) { /* ** Create buffers */ if(TempN == NULL){ TempN = mxCreateDoubleMatrix(1, 1, mxREAL);} else { mxDestroyArray(TempN); TempN= mxCreateDoubleMatrix(1, 1, mxREAL); }; if(TempIn == NULL){ TempIn = mxCreateDoubleMatrix(1, nSizeIn, mxREAL);} else { mxDestroyArray(TempIn); TempIn= mxCreateDoubleMatrix(1, nSizeIn, mxREAL); }; if(TempAd == NULL){ TempAd = mxCreateDoubleMatrix(1, 1, mxREAL);} else { mxDestroyArray(TempAd); TempAd= mxCreateDoubleMatrix(1, 1, mxREAL); }; /* ** Creating data for processing */ memcpy((char *)mxGetPr(TempIn), (char *) pIn, (nSizeIn)*8); memcpy((char *)mxGetPr(TempN), (char *) &dN, 8); memcpy((char *)mxGetPr(TempAd), (char *) &dAd, 8); /* ** Send and receive a response from the m-function */ if(mlfNeoSMA(1, (mxArray **)TempY, (mxArray *)TempIn, (mxArray *)TempN , (mxArray *)TempAd) == false) return false; /* ** Return calculated vector from the m-function and clear buffers */ memcpy((char *) pY, (char *)mxGetPr(TempY), (nSizeY)*8); mxDestroyArray((mxArray *)TempY); TempY = 0; mxDestroyArray((mxArray *)TempN); TempN = 0; mxDestroyArray((mxArray *)TempIn); TempIn = 0; mxDestroyArray((mxArray *)TempAd); TempAd = 0; return true; }
nSMA.def (from DllMatlab.zip):
LIBRARY nnSMA
EXPORTS
IsStartSMA
nSMA
C/C++ コンプライアにプロジェクトを構築します。:このためは標準インプット/アウトプットライブラリを含め、以下のファイルをプロジェクトに追加する必要があります(みなさんのコンパイラでは:プロジェクト->プロジェクト追加)
- nSMA.def
- <MATLAB>\Extern\lib\<win32/64>\<compiler>\ folder、ここで
- <MATLAB> — MATLAB メインフォルダ
- <win32/64> — 32-ビットOS向けwin32フォルダまたは64-ビットOS向けwin64フォルダ
- <compiler> — Borland C/C++ バージョン向け『ボーランド』フォルダ 5-6、マイクロソフトビジュアルC++向け『マイクロソフト』フォルダ(私はバージョン6のフォルダを持っています)
- libmx.lib
- mclmcr.lib
- NeoSMA.lib — マニュアル作成(3.1 MetaTrader 5-MATLABエンジン間連携のユニバーサルライブラリ開発を参照)
最後に、プロジェクトをMATLABがインストールされていない別のコンピュータに移動させるとき必要なファイルに関してお伝えしたいと思います。
対象マシンのファイルとパスのリストです。
- MCRInstaller.exe - どのフォルダでも (MCRインストーラ)
- extractCTF.exe - どのフォルダでも (MCRインストーラ向け)
- MCRRegCOMComponent.exe - どのフォルダでも (MCRインストーラ向け)
- unzip.exe - どのフォルダでも (MCRインストーラ向け)
- NeoSMA.dll - <terminal_dir>\MQL5\Libraries
- NeoSMA.ctf - <terminal_dir>\MQL5\Libraries
- nnSMA.dll - <terminal_dir>\MQL5\Libraries
経験豊かなプログラマの方々はすでに推測されていることでしょうが、インストーラプログラム (SETUP)を使うこともおすすめします。インターネット検索で無料のものも含め多く見つかります。
MetaTrader 5で今回のDLLを検証する必要があります。検証のために簡単なスクリプトを書きます。(DllMatlab.zipから入手できるTestDllMatlab.mq5 です。)
#property copyright "2010, MetaQuotes Software Corp." #property link "nav_soft@mail.ru" #property version "1.00" #import "nnSMA.dll" bool IsStartSMA(void); bool nSMA(double &pY[], int nSizeY, double &pIn[], int nSizeIn, double dN, double dAd); #import datetime Time[]; // dynamic array of time coordinates double Price[]; // dynamic array of price double dNeoSma[]; // dynamic array of price void OnStart() { int ind=0; // run Dll if(IsStartSMA()==true) { //--- create and fill arrays CopyTime(Symbol(),0,0,301,Time); // time array + 1 ArraySetAsSeries(Time,true); // get the time chart CopyOpen(Symbol(),0,0,300,Price); // price array ArraySetAsSeries(Price,true); // get the open prices ArrayResize(dNeoSma,300,0); // reserve space for function response // get data if(nSMA(dNeoSma,300,Price,300,1,2)==false) return; // specify array orientation ArraySetAsSeries(dNeoSma,true); // plot data on chart for(ind=0; ind<ArraySize(dNeoSma);ind++) { DrawPoint(IntegerToString(ind,5,'-'),Time[ind],dNeoSma[ind]); } } } //+------------------------------------------------------------------+ void DrawPoint(string NamePoint,datetime x,double y) { // 100% ready. Plot data on chart. Drawing using arrows. // Main properties of chart object ObjectCreate(0,NamePoint,OBJ_ARROW,0,0,0); ObjectSetInteger(0, NamePoint, OBJPROP_TIME, x); // time coordinate x ObjectSetDouble(0, NamePoint, OBJPROP_PRICE, y); // price coordinate y // Additional properties of chart object ObjectSetInteger(0, NamePoint, OBJPROP_WIDTH, 0); // line width ObjectSetInteger(0, NamePoint, OBJPROP_ARROWCODE, 173); // arrow type ObjectSetInteger(0, NamePoint, OBJPROP_COLOR, Red); // arrow color } //+------------------------------------------------------------------+
おわりに
MetaTrader 5-MATLAB連携のためのユニバーサルライブラリ作成方法とMATLAB環境でDLLビルドを接続する方法を学びました。MetaTrader 5-MATLAB連携インターフェースについて書くべきことはまだありますが、今回の趣旨からは外れています。本稿で扱う内容は詳述しました。特殊な『アダプタ』を使用することなく、最も効果的な連携方法を選びました。 .NETテクノロジーの WCFサービスを利用してExport QuotesをMetaTrader 5から .NET アプリケーションにエクスポートする方法など、『他の方法』も選ぶことはできるでしょう。
どの手法を選ぶのがよいのだろう?との疑問を持たれる読者も多いでしょう。答えは簡単です。どちらも、です。数学的モデルの設計/デバッグ時はスピードは必要ないからです。プログラミングに『特別な生産コスト』をかけずにMATLABの力を最大限発揮することは必要でしょう。もちろんMATLABエンジンはここで手助けしてくれます。ただし、数学的モデルがデバッグし使用準備が整ったところで、スピード、マルチタスク(インディケータの作業と/または複数価格チャートでのトレードシステム)が必要となります。ここで間違いなくDLL、内蔵MATLABが必要となるのです。
これらすべてを追い求める必要はありません。基本的にプロジェクト規模(インディケータ数や/またはトレードシステムユーザー数)に対する『プログラムコスト』のバランスにより、みなさんはご自身でこの疑問に対する答えを見つけるでしょう。1~2人のユーザーのためにDllIMATLAB環境で作成することに意味はありません。(MATLABを2台のコンピュータにインストールする方が簡単です。)
MATLABに慣れておられる多くの読者の方は、たぶん「なぜこれですべてなんだ?」と疑問をお持ちでしょう。MQL5にはすでに数学的関数が備わっているのです。「MATLABを使うことで努力せずみなさんの数学的考えを導入することができます。」 というのが答えです。ここに挙げたのは可能性のリストの一分に過ぎません。
- インディケータおよび/または機械トレードシステムにおけるあいまいな理論の動的アルゴリズム
- 機械トレードシステム(動的戦略テスタ)における動的遺伝アルゴリズム
- インディケータおよび/または機械トレードシステムにおける動的中立根とワークアルゴリズム
- 三次元的インディケータ
- ノンリニアは管理システムのシミュレーション
すべてはみなさんの手の中です。そして忘れないでください。「数学は常に科学の女王である」ことを。そしてMATLABパッケージはみなさんの科学的計算機なのです。
文献
- MATLAB内蔵ヘルプ
- MQL5内蔵ヘルプ
- Jeffrey Richter. Programming Applications for Microsoft Windows.
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/44
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索