初心者からプロまでMQL5をマスターする(第4回):配列、関数、グローバルターミナル変数について
はじめに
この記事は初心者向け連載の続きです。前回の記事では、プログラム内部に保存されているデータを記述する方法について詳しく説明しました。この時点で、読者は次のことを理解しているはずです。
- データは変数または定数に保存できます。
- MQL5言語は厳密に型付けされた言語です。つまり、プログラム内の各データには独自の型があり、コンパイラはそれを使用してメモリを正しく割り当て、論理エラーを回避します。
- データ型には、単純なもの(基本型)と複雑なもの(ユーザー定義型)があります。
- MQL5プログラムでデータを使用するには、少なくとも1つの関数を宣言する必要があります。
- 任意のコードブロックを別のファイルに移動し、#includeプリプロセッサディレクティブを使用してこれらのファイルをプロジェクトに含めることができます。
この記事では、3つのグローバルなトピックを取り上げます。
- データ配列:プログラム内のデータに関する主要部分を完成させる
- グローバルターミナル変数:異なるMQL5プログラム間での簡単なデータの交換を可能にする
- 関数:機能の一部と変数との相互作用
配列に関する基本情報
配列は同じ型のデータのシーケンスを格納する変数です。
配列を記述するには、配列の型と変数名を記述し、角括弧を記述する必要があります。特定のシーケンス内の要素の数は、角括弧で指定できます。
int myArray[2]; // Describes an array of integers containing two elements
例1:静的配列の説明
MQL5プログラムでは、シーケンスを頻繁に記述します。これらには、過去の価格、ローソク足の開始時間、取引量などが含まれます。一般に、データセットがある場合は、配列が適切な選択肢となる可能性があります。
MQL5の配列内の要素の番号は常に0から始まります。したがって、配列の最後の要素の数は、常にその要素の数から1を引いた数と等しくなります(lastElement=size-1)。
配列要素にアクセスするには、その要素のインデックスを角括弧で指定するだけです。
// Fill the array with values: myArray[0] = 3; myArray[1] = 315; // Output the last element of this array to the log: Print(myArray[1]); // 315
例2:配列要素の使用
当然、配列は構造体と同じように、中括弧を使用して宣言時に初期化できます。
double anotherArray[2] = {4.0, 5.2}; // A two-element array is initialized with two values Print( DoubleToString(anotherArray[0],2) ); // Output 4.00
例3:説明中の配列の初期化
多次元配列
配列はその中に他の配列を格納できます。このようなネストされた配列は「多次元」と呼ばれます。
多次元配列の簡単な視覚的な例としては、本のページが挙げられます。文字は行に集められ、これが最初の次元となります。行は段落に集められ、2番目の次元を形成します。ページは段落の集合、つまり3番目の次元となります。

図1:文字は1行(1次元配列)に集められる

図2:行は段落(2次元配列)に集められる

図3:段落はページ(3次元配列)に集められる
MQL5でこのような配列を記述するには、新しい次元ごとに角括弧を追加するだけです。「外側」のコンテナの括弧は、「内側」のコンテナの括弧の左側に配置されます。たとえば、図1~3に示す配列は、次のように記述して使用できます。
char stringArray[21]; // One-dimensional array char paragraphArray[2][22]; // Two-dimensional array char pageArray[3][2][22]; // Three-dimensional array // Filling a two-dimensional array paragraphArray[0][0]='T'; paragraphArray[0][1]='h'; // … paragraphArray[1][20]='n'; paragraphArray[1][21]='.'; // Access to an arbitrary element of a two-dimensional array Print(CharToString(paragraphArray[1][3])); // Will print "a" (why?)
例4:図1~3の多次元配列の説明
配列内の次元の合計数は4を超えてはなりません。任意の次元の要素の最大数は2147483647です。
多次元配列の初期化は、1次元配列の初期化と同じくらい簡単です。中括弧は各配列の要素を単純にリストします。
int arrayToInitialize [2][5] = { {1,2,3,4,5}, {6,7,8,9,10} }
例5:多次元配列の初期化
動的配列
すべての配列について、要素数がすぐにわかるわけではありません。たとえば、ターミナル履歴や取引リストを含む配列は時間の経過とともに変化します。したがって、前のセクションで説明した静的配列に加えて、MQL5では、プログラムの動作中に要素の数を変更できる動的配列を作成できます。このような配列は静的配列とまったく同じ方法で記述されますが、要素の数だけが角括弧内に示されません。
int dinamicArray []; 例6:動的配列の説明
この方法で宣言された新しい配列には要素が含まれず、長さは0であるため、その要素にアクセスすることはできません。プログラムがこれを実行しようとすると、重大なエラーが発生し、プログラムは終了します。したがって、このような配列を操作する前に、特別な組み込み関数ArrayResizeを使用して配列のサイズを設定する必要があります。
ArrayResize(dinamicArray, 1); // The first parameter is the array and the second is the new size ArrayResize(dinamicArray,1, 100); // The third parameter is the reserved (excess) size
例7:動的配列のサイズ変更
言語のドキュメントでは、関数は最大3つのパラメータを取ることができることがわかりますが、3番目のパラメータにはデフォルト値があるため、例の最初の行で行ったように省略できます。
この関数の最初のパラメータは、必ず変更する配列になります。2番目は配列の新しいサイズです。これなら問題はないと思います。3番目のパラメータは「予約サイズ」です。
配列の最終的なサイズがわかっている場合は、予約されたサイズが使用されます。たとえば、問題の条件によれば、配列には100を超える値が存在することはできませんが、正確な数は不明です。次に、この関数でreserve_sizeパラメータを使用し、例7の2行目のように100に設定します。この場合、実際の配列サイズは2番目のパラメータで指定されたサイズ(1要素)のままになりますが、関数は100要素分の余分なメモリサイズを予約します。
なぜこのような複雑な問題が起こるのでしょうか。必要なときに一度に1つの要素を追加してみたらどうでしょうか。
簡単な答えは、プログラムの高速化です。
より詳細な回答を書くには長い時間がかかるでしょうが、簡単に言えば、ポイントは、3番目のパラメータなしでArrayResize関数を使用するたびに、プログラムがオペレーティングシステムに追加のメモリを要求するということです。このメモリ割り当ては、(プロセッサの観点からは)かなり長い操作であり、メモリが1つの要素のみに必要なのか、または一度に複数の要素に必要なのかは関係ありません。プログラムがこれを実行する頻度が少ないほど、良い結果が得られます。つまり、小さなスペースを割り当ててから拡張するよりも、一度に大量のスペースを予約してからそれを埋める方が適切です。ただし、ここでも、RAMは限られたリソースであることを考慮する必要があり、したがって、動作速度とデータサイズのバランスを常に見つける必要があります。
配列の制限がわかっている場合は、静的配列を宣言するか、ArrayResize関数の3番目のパラメータを使用してメモリを予約することによって、プログラムにこれを明示的に示すことをお勧めします。わからない場合、配列は間違いなく動的であり、ArrayResize関数の3番目のパラメータは必ずしも指定する必要はありませんが、配列の実際のサイズが予約されたサイズよりも大きい場合、MQL5は必要な実際のメモリを割り当てるだけなので、指定することもできます。
例7のように配列のサイズを変更したら、配列内のデータを変更できます。
dinamicArray[0] = 3; // Now our array contains exactly one element (see example 7), its index is 0
例8:修正された配列の使用
動的配列を扱う場合、ほとんどの場合、タスクはこの配列の末尾にデータを追加することであり、途中で何かを変更することではありません(ただし、このようなことも起こります)。プログラムは特定の時点で配列にいくつの要素が含まれているかを認識していないため、それを調べるには特別な関数が必要です。それはArraySizeと呼ばれます。この関数は1つのパラメータ(配列)を受け取り、この配列内の要素の数を表す整数値を返します。そして、動的配列(この関数が返す)の正確なサイズがわかれば、要素の追加は非常に簡単になります。
int size, // Number of elements in the array lastElemet; // Index of the last element char stringArray[]; // Our example dynamic array. // Immediately description its size is 0 (array cannot contain elements) // add an element to the end. size = ArraySize(stringArray); // Find the current size of the array size++; // Array size should increase by 1 ArrayResize(stringArray, size, 2); // Resize the array. In our example, the array will have no more than two elements. lastElemet = size — 1; // Numbering starts from 0, so the number of the last element is 1 less than the size of the array stringArray[lastElement] = `H`; // Write the value // Now add one more element. The sequence of actions is absolutely the same. size = ArraySize(stringArray); // Find the current size if the array size++; // Array size should increase by 1 ArrayResize(stringArray, size, 2); // Resize the array. In our example, the array will have no more than two elements. lastElemet = size — 1; // Numbering starts from 0, so the number of the last element is 1 less than the size of the array stringArray[lastElement] = `i`; // Write the value // Note that when adding the second element in this way, only on line changes: // the one that writes the actual value to a specific cell. // // This means that the solution can be written in a shorter form. For example, by creating a separate custom function for it.
例9:動的配列の末尾に要素を追加する
ArraySize関数とArrayResize関数は、動的配列を操作するときに頻繁に使用され、通常は例9のように組み合わせて使用されます。あまり一般的ではないですが、同様に便利なその他の関数については、ドキュメントを参照してください。
そして、このセクションの結論として、MQL5言語は多次元動的配列もサポートしていますが、その中で未定義にできるのは最初のインデックスのみであることに注意してください。
int a [][12]; // It's ok // int b [][]; // Compilation error: only the first index can be dynamic
例10:多次元動的配列
複数のインデックスを動的にする必要がある場合は、フィールドのみが動的配列となる構造体を作成し、そのような構造体の配列を作成できます。
struct DinArray // Structure containing a dynamic array { int a []; }; DinArray dinamicTwoDimensions []; // Dynamic array of structures ArrayResize( dinamicTwoDimensions, 1 ); // Set size of the outer dimension ArrayResize( dinamicTwoDimensions[0].a, 1 ); // Set size of the internal dimension dinamicTwoDimensions[0].a[0] = 12; // Use cell to write data
例11:2つの動的インデックスを持つ配列
この問題を解決する方法は他にもあります。たとえば、独自のクラスを作成することも、標準ライブラリに既に存在するクラスを使用することもできます。ただし、クラスの操作については、今後の記事で取り上げることにします。
時系列配列
MQL5では、始値、終値、高値、安値、ティックと実ボリューム、スプレッド、ローソク足の開始時間、各ローソク足のインジケーター値は、系列または時系列と呼ばれます。
MQL5プログラマーはこれらの時系列に直接アクセスすることはできませんが、この言語では、特別な定義済み関数のセット(表1にリスト)を使用して、このデータをプログラム内の任意の変数にコピーする機能が提供されています。
たとえば終値が必要な場合は、まずこれらの価格を格納する独自の配列を作成し、次にCopyClose関数を呼び出して、作成した配列を最後のパラメータとして渡す必要があります。この関数は標準時系列を変数にコピーし、その後、このデータを通常の方法(角括弧内のインデックスを使用する)で使用できるようになります。
ただし、時系列の操作は他の配列とは多少異なります。これは現在では伝統的に定着した形式です。
メモリ内では、時系列は他のすべてのデータと同じ方法、つまり古いものから新しいものの順に格納されます。しかし、表1の系列データ操作関数は、時系列内の要素を逆の順序、つまり右から左へ番号付けします。これらすべての機能において、ゼロローソク足は一番右、現在のローソク足、まだ完了していないローソク足になります。しかし、「通常の」配列はこれを認識しないため、このローソク足は最後のものになります。これはちょっと混乱しますね…
次の画像を使用して理解してみましょう。

図4:通常の配列(緑の矢印)と時系列(青の矢印)でのインデックスの方向

図5:系列を通常の配列にコピーする
図4は、時系列配列と通常配列のインデックス方向の違いを示しています。
図5は、CopyRatesまたは同様の関数を使用して、時系列データを通常の配列にコピーする方法を示しています(表1を参照)。 メモリ内の要素の物理的な順序は通常の配列と時系列で同じですが、インデックス付けは変更され、コピー後の時系列の最初の要素は通常の配列の最後の要素になります。
こうしたニュアンスを常に念頭に置きながらプログラミングするのは、不便な場合があります。この不便さに対処するには2つの方法があります。
- 標準のArraySetAsSeries関数を使用すると、任意の動的配列のインデックス方向を変更できます。配列自体と、それが時系列であるかどうかの指示(true/false)の2つのパラメーターを取ります。アルゴリズムに常に最後のローソク足から始まるデータのコピーが含まれる場合、ほとんどの場合、ターゲット配列をシリーズとして割り当てることができ、その後、標準関数とアルゴリズムは同じインデックスを使用して動作します。
- アルゴリズムにチャートの任意の場所から小さなデータ断片をコピーすることが含まれる場合、特にアルゴリズムの各ステップでその数が正確にわかっている場合(たとえば、3つのバーの終値を取得し、最初の終値はシリーズインデックス1で、それに続く2つはシリーズインデックス2と3である)、そのまま受け入れるのが最善です。インデックスが異なる方向になることを受け入れ、プログラミング中はより注意を払う方がよいでしょう。考えられる解決策の1つは、必要な値をチェックする別の関数を作成し、それを任意の式で使用することです。
次の例では、上記のすべてをコードで説明してみました。
datetime lastBarTime; // We'll try to write the last candle's time into this variable datetime lastTimeValues[]; // The array will store the time of the last two candles. // It's dynamic so that it can be made into a time series to test indices // Get the start time of the current candlestick using the iTime function lastBarTime = iTime ( Symbol(), // Use the current symbol PERIOD_CURRENT, // For the current timeframe 0 // Current candlestick ); Print("Start time of the 0 bar is ", lastBarTime); // Get the start time of the last two candlesticks using the CopyTime function CopyTime ( Symbol(), // Use the current symbol PERIOD_CURRENT, // For the current timeframe 0, // Start with position 0 2, // Take two values lastTimeValues // Write them to array lastTimeValues ("regular") array ); Print("No series"); ArrayPrint(lastTimeValues,_Digits,"; "); // Print the entire array to log. The separator between elements is a semicolon ArraySetAsSeries(lastTimeValues,true); // Convert the array into a time series Print("Series"); ArrayPrint(lastTimeValues,_Digits,"; "); // Print the entire array again. Note the order of the data /* Script output: 2024.08.01 09:43:27.000 PrintArraySeries (EURUSD,H4) Start time of the 0 bar is 2024.08.01 08:00:00 2024.08.01 09:43:27.051 PrintArraySeries (EURUSD,H4) No series 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) 2024.08.01 04:00:00; 2024.08.01 08:00:00 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) Series 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) 2024.08.01 08:00:00; 2024.08.01 04:00:00 */
例12:時系列を操作するための関数のテスト
表1:時系列にアクセスするための関数のリストこれらのすべての関数では、要素のインデックスは最後の(未完了の)キャンドルの右側から始まります。
| 関数 | アクション |
|---|---|
| CopyBuffer | 指定されたインジケータバッファのデータを配列に取得する |
| CopyRates | 指定された銘柄と時間枠の履歴データをMqlRates構造体の配列に取得する |
| CopySeries | 指定された銘柄/時間枠の複数の同期された時系列を指定された量で取得する(最後に、入力するすべての配列のリストが渡され、その順序はMqlRates構造体のフィールドに対応している必要がある) |
| CopyTime | 対応する銘柄と時間枠のバーの開始時刻の履歴データを配列に取得する |
| CopyOpen | 対応する銘柄と時間枠のバーの始値に関する履歴データを配列に取得する |
| CopyHigh | 対応する銘柄と時間枠のバーの高値の履歴データを配列に取得する |
| CopyLow | 対応する銘柄と時間枠のバーの安値の履歴データを配列に取得する |
| CopyClose | 対応する銘柄と時間枠のバーの終値の履歴データを配列に取得する |
| CopyTickVolume | 対応する銘柄と時間枠のティックボリュームの履歴データを配列に取得する |
| CopyRealVolume | 対応する銘柄と時間枠の取引量の履歴データを配列に取得する |
| CopySpread | 対応する銘柄と時間枠のスプレッドの履歴データを配列に取得する |
| CopyTicks | MqlTick形式のティックを配列で取得する |
| CopyTicksRange | 指定された日付範囲のティックの配列を取得する |
| iBarShift | 指定された時間を含む系列内のバーのインデックスを返す |
| iClose | 対応するチャート上のバー(shiftパラメータで示される)の終値を返す |
| iHigh | 対応するチャート上のバー(shiftパラメータで示される)の高値を返す |
| iHighest | 対応するチャートで見つかった最高値のインデックスを返す(現在のバーからのシフト) |
| iLow | 対応するチャート上のバー(shiftパラメータで示される)の安値を返す |
| iLowest | 対応するチャートで見つかった最小値のインデックスを返す(現在のバーからのシフト) |
| iOpen | 対応するチャート上のバー(shiftパラメータで示される)の始値を返す |
| iTime | 対応するチャート上のバー(shiftパラメータで示される)の開始時刻を返す |
| iTickVolume | 対応するチャート上のバー(shiftパラメータで示される)のティックボリュームを返す |
| iRealVolume | 対応するチャート上のバー(shiftパラメータで示される)の実ボリュームを返す |
| iSpread | 対応するチャートの「shift」パラメータで指定されたバーのスプレッド値を返します。 |
関数の作成(詳細)
MQL5プログラムのすべての関数は、本連載の最初の記事で簡単に説明した同じテンプレートを使用して作成されます。
ResultType Function_Name(TypeOfParameter1 nameOfParameter1, TypeOfParameter2 nameOfParameter2 …) { // Description of the result variable and other local variables ResultType result; // … //--- // Main actions are performed here //--- return resut; }
例13:関数の説明テンプレート
ResultType(およびTypeOfParameter)は、許可される任意のデータ型を表します。これは、int、double、クラス名、列挙名、またはその他既知の値になります。
パラメータがなかったり、関数操作の明示的な結果がなかったりすることもあります。次に、結果の型の代わりに、または括弧内のパラメータリストの代わりに、「void」という単語が挿入されます。これは空の結果を表します。
当然ですが、ResultTypeがvoidの場合はデータを返す必要がないため、中括弧内の最後の行(returnresult)を指定する必要はなく、結果変数を記述する必要もありません。
さらにいくつかの簡単なルールを紹介します。
- Function_Nameとパラメータ名は識別子の規則に準拠する必要があります。
- return演算子は1つの値のみを返します。2つ以上は戻せません。ただし、回避策はあります。これについては後ほど説明します。
- 関数は他の関数の内部で記述することはできず、すべての関数の外部でのみ記述できます。
- 同じ名前で、異なる数(または異なる型)のパラメータや異なる戻り値の型を持つ複数の関数を記述できます。特定のケースでどの関数を使用すべきかを正確に理解できることを確認してください。これらの違いを区別して、コードに詳しくない人にも説明できるのであれば、コンパイラでも同じことができます。
関数の記述方法を示す例をいくつか示します。
//+------------------------------------------------------------------+ //| Example 1 | //| Comments are often used to describe what a function does, | //| what date it needs and why. For example, like this: | | //| | //| | //| The function returns difference between two integers. | //| | //| Parameters: | //| int a is a minuend | //| int b is a subtrahend | //| Return value: | //| difference between a and b | //+------------------------------------------------------------------+ int diff(int a, int b) { // The action is very simple, we do not create a variable for the result. return (a-b); } //+------------------------------------------------------------------+ //| Example 1a | //| The function returns the difference between two real numbers. | //| | //| Function name is as in the previous example, but parameter type | //| differs | //| | //| Parameters: | //| double a is a minuendе | //| double b is a subtrahend | //| Return value: | //| difference between a and b | //+------------------------------------------------------------------+ double diff(double a, double b) { return (a-b); } //+------------------------------------------------------------------+ //| Example 2 | //| Illustrates the use of "void". | //| Calls (uses) the diff function | //+------------------------------------------------------------------+ void test() { // You can do whatever you want. // For example, use the function from Example 1. Print(diff(3,4)); // the result is -1 // Since when calling the diff function, integer parameters were // passed in parentheses, the result is also int. // Now let's try to call the same function with double precision parameters Print(diff(3.0,4.0)); // the result is -1.0 // Since the function is declared as "void", the "return" statement is not needed } //+------------------------------------------------------------------+ //| Example 3 | //| The function has no parameters to process. We could use | //| empty parentheses as in the previous example or explicitly use | //| the word "void" | //| Return value: | //| string nameForReturn is some name , always the same | | //+------------------------------------------------------------------+ string name(void) { string nameForReturn="Needed Name"; return nameForReturn; }
例14:テンプレートによる機能説明の例
関数の操作は、説明と使用という2つの段階で構成されていることを理解することが重要です。関数を記述した時点では、この関数はまだ何も実行しません。それは単なる正式なアクションアルゴリズムです。たとえば、例14のdiff関数は、次のように言葉で説明できます。
- 2つの整数(任意の整数、事前に不明)を取得します。
- アルゴリズム内では、そのうちの1つをa、もう1つをbとして表します。
- aからbを引きます。
- 計算結果(任意、事前に不明)を呼び出し元に渡します(呼び出しポイントに戻ります)。
「事前に不明な任意の」値について話す場合、この表現は「仮パラメータ」に置き換えることができます。関数は、任意のデータを使用して特定のアクションを正式に実行するために作成されます。
関数を記述するときに使用されるパラメータは仮パラメータと呼ばれます。
例14では、diff関数に2つの仮パラメータが含まれていますが、他の関数には仮パラメータがありません。一般に、関数には多くの仮パラメータ(最大63個)を設定できます。
しかし、特定の結果を得るためには、関数を呼び出す(つまり、使用する)必要があります。例14の関数testを参照してください。ここでは、Print関数とdiff関数が呼び出されています。ここで、関数は、呼び出しの時点で実際の非常に具体的な値、つまり変数または定数の内容、リテラル(私の例のように)、他の関数の結果を使用します。
呼び出し時に関数に渡すパラメータは「実際の」パラメータと呼ばれます。
関数を呼び出すには、その名前を指定し、実際のパラメータを括弧内にリストする必要があります。実際のパラメータは、型と数の点で仮パラメータに対応している必要があります。
例14では、test関数は正確に2つの整数または正確に2つの倍精度数を使用して、diff関数を呼び出します。間違って1つまたは3つのパラメータを記述しようとすると、コンパイルエラーが発生します。
変数のスコープ
変数を宣言するときは、どこで宣言するかを正確に考慮する必要があります。
-
変数が関数内で宣言されている場合(この関数の仮パラメータを含む)、他の関数はこの変数を参照できません(したがって、使用できません)。通常、このような変数は関数が呼び出された瞬間に「生成」され、関数が処理を終了したときに「消滅」します。このような変数はローカル変数と呼ばれます。
一般的に、変数のスコープはコード内の「エンティティ」全体によって決定されると言えます。たとえば、変数が中括弧内で宣言されている場合、その変数はブロックを形成する中括弧内でのみ表示され、ブロック外では表示されません。関数の形式パラメータは関数の「エンティティ」に属するため、その関数内でのみ表示されます。様々なシナリオでその選択肢を自動で決めさせることができます。このような変数の有効期間は、それが属する「エンティティ」の有効期間と同じです。たとえば、関数内で宣言された変数は、その関数が呼び出されたときに作成され、関数が終了すると破棄されます。
void OnStart() { //--- Local variable inside a function is visible to all blocks of that function, but not beyond it int myString = "This is local string"; // Curly braces describe a block inside a function { int k=4; // Block local variable - visible only inside curly braces Print(k); // It's ok Print (myString); } // Print(k); // Compilation error. The variable k does not exist outside the curly brace block. }
例15:中括弧ブロック内のローカル変数
- 変数が関数の記述外で宣言されている場合、その変数はアプリケーションのすべての関数で使用できます。この場合、そのような変数の有効期間はプログラムの有効期間と同じになります。このような変数はグローバルと呼ばれます。
int globalVariable = 345; void OnStart() { //--- Print (globalVariable); // It's ok }
例16:グローバル変数はプログラム内のすべての関数から参照できる
- ローカル変数はグローバル変数と同じ名前を持つことができますが、この場合、ローカル変数は指定された関数のグローバル変数を隠します。
int globalVariable=5; void OnStart() { int globalVariable=10; // The variable is described according to all the rules, including the type. // If the type were not declared, this expression would change the global variable //--- Print(globalVariable); // The result is 10 - that is, the value of the local variable Print(::globalVariable); // The result is 5. To print the value of a global variable, not the local one, // we use two colons before the name }
例17:ローカル変数とグローバル変数の名前を一致させ、ローカル変数がグローバル変数を隠す
静的変数
ローカル変数の記述には特別なケースがあります。
前述のように、関数が完了するとローカル変数の値は失われます。これは通常、まさに期待される動作です。ただし、関数の実行が完了した後でもローカル変数の値を保存する必要がある状況があります。
たとえば、関数の呼び出し回数のカウンタを保持する必要がある場合があります。トレーダーにとってより一般的なもう1つのタスクは、新しいローソク足の開始を確認するための関数を整理することです。これには、各ティックで現在の時間値を取得し、それを以前の既知の値と比較する必要があります。もちろん、これらのカウンタごとにグローバル変数を作成することもできます。しかし、これらのカウンターは1つの関数によって使用され、他の関数はそれらを変更したり参照したりしてはならないため、各グローバル変数によってエラーの可能性が高まります。
このような場合、ローカル変数がグローバル変数と同じ期間存続する必要がある場合は、static変数が使用されます。これらは通常のものと同じように記述されますが、記述の前にstaticという単語が追加されます。この使用法は、次の例のHowManyCalls関数に示されています。
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- HowManyCalls(); HowManyCalls(); HowManyCalls(); } //+------------------------------------------------------------------+ //| The function counts the number of requests | //+------------------------------------------------------------------+ void HowManyCalls() { //--- Variable description. The variable is local, but its lifetime is long. static int counter=0; // Since 'static' keyword is used, the variable is initialized only // before he first function call // (more precisely, before the OnInit function call) //--- Main actions counter++; // During program execution, the value will be stored till the end //--- Operation result Print( IntegerToString(counter)+" calls"); } // Script output: // 1 calls // 2 calls // 3 calls
例18:static変数の使用
この例には2つの関数が含まれています。HowManyCallsは、静的変数を使用して呼び出し回数をカウントし、その結果をログに出力します。また、OnStartは、HowManyCallsを3回続けて呼び出します。
関数パラメータを値渡しと参照渡しする
デフォルトでは、関数はパラメータとして渡されるデータのコピーのみを使用します(プログラマー用語では、データは値渡しだと言います)。したがって、関数内の変数パラメータに何かが書き込まれても、ソースデータには何も起こりません。
関数内でソースデータを変更する場合は、変更可能な仮パラメータを特別なアイコン&で指定する必要があります。パラメータを記述するこの方法は、参照渡しと呼ばれます。
関数が外部データを変更する方法を説明するために、次のコードを含む新しいスクリプトファイルを作成しましょう。
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart(void) { //--- Declare and initialize two local variables int first = 3; int second = 77; //--- Print their values BEFORE all changes Print("Before swap: first = " + first + " second = " + second); //--- Use the Swap function, which takes data by reference Swap(first,second); //--- See what happened Print("After swap: first = " + first + " second = " + second); //--- //--- Apply the CheckLocal function to the received data //--- This function takes parameters by value CheckLocal(first,second); //--- Print the result again Print("After CheckLocal: first = " + first + " second = " + second); } //+------------------------------------------------------------------+ //| Swaps the values of two integer variables | //| Data is passed by reference, so the originals will be modified | //+------------------------------------------------------------------+ void Swap(int &a, int& b) // It can be done in any way, both positions are correct { int temp; //--- temp = a; a = b; b = temp; } //+------------------------------------------------------------------+ //| Takes parameters by value, that is why changes happen | //| only locally | //+------------------------------------------------------------------+ void CheckLocal(int a, int b) { a = 5; b = 10; } // Script output: // Before swap: first = 3 second = 77 // After swap: first = 77 second = 3 // After CheckLocal: first = 77 second = 3
例19:パラメータの参照渡し
このコードは、OnStart、Swap、CheckLocalの3つの短い関数を定義します。
CheckLocalは値でデータを受け取るため、コピーで動作します。一方、Swapは参照で2つのパラメータを受け取るため、ソースデータで動作します。OnStart関数は2つのローカル変数を宣言し、それらの変数の値を出力し、Swap関数とCheckLocal関数を呼び出し、各対話の後にローカル変数の値をログに出力することで対話の結果を表示します。もう一度、Swap関数は渡されたデータを変更しましたが、CheckLocalはこれを実行できなかったという事実に注目してください。
複合型のすべての変数(列挙、構造体、オブジェクトなど、および配列)は常に参照渡しする必要があることに注意することが重要です。
このような変数を値渡ししようとすると、コンパイラはエラーを生成します。
ここでもう一度、変数と関数の相互作用に関する基本的なルールを簡単に挙げます。
- MQL5言語のグローバル変数は、値の変更を含め、任意の関数内から直接使用できます。
- ローカル変数は、宣言されているブロック内でのみアクセスできます。
- 仮パラメータがデータの「値渡し」を記述している場合、関数はパラメータ変数の値を内部的に変更しても元のデータを変更することはできません。ただし、「参照渡し」されたデータは元の場所で変更される可能性があります。
- グローバル変数とローカル変数の名前が同じ場合、ローカル変数が優先されます(つまり、ローカル変数がグローバル変数をオーバーライドします)。
- グローバル変数の有効期間はプログラムの有効期間と同じであり、ローカル変数はそれが記述されているブロックの有効期間と同じになります。
関数パラメータのデフォルト値
仮パラメータにはデフォルト値を割り当てることができます。
たとえば、ログ関数を作成する場合、関数のメッセージを他のすべてのターミナルメッセージと区別する必要がある場合があります。これをおこなう最も簡単な方法は、元のメッセージの先頭にプレフィックスを追加し、末尾にサフィックスを追加することです。文字列自体は常に指定する必要があります。そうしないと、関数の意味が失われます。「追加」は標準にすることも、変更することもできます。
この考え方を説明する最も簡単なコードを以下に示します。
//+------------------------------------------------------------------+ //| Add a prefix and suffix to a sting | //+------------------------------------------------------------------+ string MakeMessage( string mainString, string prefix="=== ", string suffix=" ===" ) { return (prefix + mainString + suffix); }
例20:デフォルトの仮パラメータを持つ関数の説明
この関数を呼び出すとき、デフォルト値を持つ1つまたは両方のパラメータを省略できます。これらのパラメータが明示的に指定されていない場合、関数は説明に指定された値を使用します。たとえば、以下のように設定することができます。
Print ( MakeMessage("My first string") ); // Default prefix and suffix Print ( MakeMessage("My second string", "~~ ") ); // Prefix changed, suffix remains unchanged Print ( MakeMessage("My third string", "~~ ", " ~~") ); // Both actual parameter have been changed // Script output: // === My first string === // ~~ My first string === // ~~ My first string ~~
例21:デフォルト値を持つ実際のパラメータは省略できる
デフォルト値を持つパラメータは連続して記述する必要があり、デフォルト値を持たない他のすべてのパラメータの後に記述する必要があります。
関数が複数の結果を返すようにする方法
前述のように、return演算子は1つの結果のみを返すことができます。さらに、この関数は配列を返すことができません。しかし、もっと多くの戻り値が必要な場合はどうすればよいでしょうか。たとえば、1つの関数を使用してローソク足の終値と価格の両方を計算したり、利用可能な銘柄のリストを取得したりする必要がある場合はどうでしょうか。まずはご自身で解決方法を見つけていただき、下記の内容と比較してみてください。
タイトルに記載されている問題を解決するには、次のいずれかの方法を使用できます。
- 複雑なデータ型(構造体など)を作成し、その型の変数を返します。
- 参照渡しパラメータを使用します。もちろん、これはreturn文を使用してこのデータを返すのには役立ちませんが、任意の値を書き留めて使用することはできるようになります。
- グローバル変数を使用します(非推奨)。この方法は前の方法と似ていますが、コードにとって潜在的に危険です。グローバル変数は、それなしでは絶対に実行できない場合にのみ、最小限に使用することをお勧めします。しかし、本当に必要な場合は、これを実行してみてください。
グローバル変数修飾子:inputとextern
グローバル変数を使用する場合、「特別なケース」もあります。これらには次のものが含まれます。
- input修飾子を使用してプログラムの入力パラメータを記述する
- extern修飾子の使用
入力パラメータ
MQL5で記述されたプログラムの各i入力パラメータは、グローバル変数(すべての関数の外)として記述され、記述の先頭に配置されるinputキーワードによって指定されます。
input string smart = "The smartest decision"; // The window will contain this description
例22:入力パラメータの説明
通常、プロパティウィンドウの左の列には変数名が表示されます。ただし、例22のように、この変数が記述されている同じ行にコメントが含まれている場合は、変数名の代わりにこのコメントが表示されます。
MQL5で書かれたプログラムでは、入力としてマークされた変数は読み取り専用としてアクセスできますが、そこに何も書き込むことはできません。これらの変数の値は、説明(コード内)またはプログラムのプロパティダイアログボックスからのみ設定できます。
エキスパートアドバイザー(EA)またはインジケーターを作成する場合、通常は戦略テスターを使用してそのような変数の値を最適化できます。ただし、いくつかのパラメータを最適化から除外したい場合は、単語入力の先頭に文字sまたは修飾子「static」を追加する必要があります。
input double price =1.0456; // optimize sinput int points =15; // NOT optimize static input int unoptimizedVariable =100; // NOT optimize
例23:sinput修飾子を使用して、テスターで変数を最適化から除外する
ユーザーが入力フィールドのリストから値を選択できるようにするには、各フィールドに列挙を追加する必要があります。列挙要素のインラインコメントも機能するため、POINT_PRICE_CLOSEなどの名前の代わりに、任意の人間の言語で「PointClosePrice」を表示できます。残念ながら、フィールド名(コメント)のテキスト言語を選択する簡単な方法はありません。使用する言語ごとに個別のファイルをコンパイルする必要があるため、経験豊富なプログラマーのほとんどは、ユニバーサル言語(英語)を使用することを好みます。
パラメータを視覚的にグループ化して使いやすくすることができます。グループ名を指定するには、特別な説明が使用されます。
input group "Group Name"
例24:パラメータグループヘッダー
これらすべての可能性を示す完全な例を以下に示します。
#property script_show_inputs // Enumeration. Allows you to create a list box. enum ENUM_DIRECTION { // All inline comments next to lines describing parameters, // will be displayed instead of the names in the parameters window DIRECTION_UP = 1, // Up DIRECTION_DN = -1, // Down DIRECTION_FL = 0 // Unknown }; input group "Will be optimized" input int onlyExampleName = 10; input ENUM_DIRECTION direction = DIRECTION_FL; // Possible directions list input group "Will not be optimized" sinput string something = "Something good"; static input double doNotOptimizedMagickVariable = 1.618; // Some magic //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- } //+------------------------------------------------------------------+
例25:入力パラメータを記述するためのさまざまなオプション

図6:パラメータダイアログ。グループ(入力グループ)は色で強調表示、緑色の矢印は、変数名の代わりに置き換えられたコメントの値
図6は、例25のコードから生成されたオプションダイアログを示しています。コンピュータによって多少異なる場合がありますが、いずれの場合もグループヘッダーが強調表示されます(図では青色で強調表示しています)。また、インラインコメントのないパラメーターでは変数名が使用されることもわかります。コメントがある場合、コンパイラは変数名の代わりにコメントを使用します。これは、緑の矢印で示されているセルで示されます。例25のコードと図を比較してください。すべてが理解しやすくなることを願っています。
そしてもう一つ。すべての初心者が各パラメータのデータ型の左側にあるアイコンに気づくわけではありません。例えば、図の「可能な方向リスト」というパラメータは列挙型のデータ型を持ち、そのアイコン(
)はリストであることを示します。このフィールドのデータは、限定された列挙からのみ選択できます。残りのアイコンも説明不要です。
パラメータの名前は63文字を超えてはなりません(実際の名前は通常それよりはるかに短いため、これはかなり長いです)。
文字列パラメータの長さは254文字を超えることはできません。また、パラメータの名前が長くなるほど、メモリ内に1つの連続した行として保存されるため、内容は短くなります。
特にプログラムにとって重要なWebページのアドレスを指定する場合は、この制限を覚えておくことが重要です。場合によってはアドレスが非常に長くなることがありますが、その場合は、アドレスをパラメータではなくグローバル変数としてハードコードするなど、別の方法で渡してみてください。もちろん、ファイルの使用や、複数のフラグメントからのアドレスの「接着」などのより良い解決策もありますが、パラメータ値の254文字の制限を覚えておいてください。
2番目の特殊なケースはextern変数です。
開発者が複数のファイルに分割された大規模なプログラムを作成する場合、グローバル変数が1つのファイルに記述され、プログラムが他のファイルからその変数にアクセスする必要がある場合があります。また、#includeディレクティブを使用してファイルをインクルードすることは望ましくありません。MetaEditorは各ファイルを個別に認識するため、この場合は役に立ちません。
ほとんどの場合、この状況は入力パラメータ(前のサブセクションで説明)を使用するときに発生します。
ここでexternキーワードを使用できます。
extern bool testComplete;
例26:外部変数の説明
このような変数はこのファイル内で初期化されない可能性があり、コンパイル時に、コンパイラが見つけることができれば、この変数のメモリアドレスは同じ名前の「実際の」グローバル変数に置き換えられる可能性が高くなります。ただし、関数はこの「正式な」データに自由にアクセスでき、変更も可能であり、IDEでは自動置換に問題はありません。
ターミナルグローバル変数
前のセクションで説明したローカル変数とグローバル変数はどちらも、現在のプログラムからのみアクセスできます。他のすべてのプログラムはこのデータを使用できません。しかし、プログラムが相互にデータを交換する必要がある場合や、ターミナルの電源をオフにした後でも変数の値が保存されるようにする必要がある場合もあります。
データ交換のケースの一例として、ポジションを開くために必要な資金の額を預金通貨で出力する必要がある非常に単純なインジケーターが挙げられます。すべてが単純なようです。ヘルプの目次を検索したところ、MQL5には必要な金額を計算する特別な関数OrderCalcMarginがあることがわかりました。それを適用しようすれば、がっかりしてしまいます。これは、インジケーターで取引機能を使用できないためです。これはコンパイラレベルでは物理的に禁止されています。OrderCalcMarginは取引関数です。
したがって、回避策を見つける必要があります。1つの選択肢は、必要な金額を計算し、その金額をターミナル変数に書き込むスクリプトまたはサービスを作成することです。そして、インジケーターはこのデータを計算するのではなく、読み取ります。このトリックが可能なのは、インジケーターとは異なり、スクリプトとサービスが取引を許可されているためです(本連載の最初の記事の表を参照)。
このようなデータ交換がどのように実装されるかを見てみましょう。まず、ウィザードを使用してスクリプトファイルを作成しましょう。このファイルに「CalculateMargin.mq5」という名前を付けます。
ターミナル変数にアクセスするための定義済み関数のセットがあり、その名前は接頭辞GlobalVariableで始まります。
これらとOrderCalcMargin関数を使用して、必要なデータをインジケーターで利用できるようにし、新しいスクリプトを作成します。
//+------------------------------------------------------------------+ //| CalculateMargin.mq5 | //| Oleg Fedorov (aka certain) | //| mailto:coder.fedorov@gmail.com | //+------------------------------------------------------------------+ #property copyright "Oleg Fedorov (aka certain)" #property link "mailto:coder.fedorov@gmail.com" #property version "1.00" #property script_show_inputs //--- Script input parameters input double requiredAmount = 1; // Number of lots //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Description of local variables string symbolName = Symbol(); // Name of the current symbol string terminalVariableName; // Terminal variable name double marginBuy, marginSell; // Margin values (buy and sell) double currentPrice = iClose(symbolName,PERIOD_CURRENT,0); // Current price to calculate margin bool okCalcBuy, okCalcSell; // Indication of success when calculating margin up or down //--- Main operations // Calculate Buy margin okCalcBuy = OrderCalcMargin( ORDER_TYPE_BUY, // Order type symbolName, // Symbol name requiredAmount, // Required volume in lots currentPrice, // Order open price marginBuy // Result (by reference) ); // Calculate Sell margin okCalcSell = OrderCalcMargin( ORDER_TYPE_SELL, // Sometimes different amounts are needed for opening up and down symbolName, requiredAmount, currentPrice, marginSell ); //--- Operation result // Create a terminal variable name for Buy details terminalVariableName = symbolName + "BuyAmount"; // Write the data. If the global terminal variable does not exist, it will be created. GlobalVariableSet ( terminalVariableName, // Where to write marginBuy // What to write ); // Now we create another name - for the Sell details terminalVariableName = symbolName + "SellAmount"; // Write data for Sell. If there was no variable with the name stored in terminalVariableName, // create one GlobalVariableSet(terminalVariableName,marginSell); } //+------------------------------------------------------------------+
例31:1ロットの売買に必要な預金通貨の資金を計算し、このデータをターミナルのグローバル変数に保存するスクリプト
ここでは、標準のGlobalVariableSet関数を使用して、ターミナル変数にデータを書き込みました。与えられた例では、これらの関数の使用は明らかだと思います。追加注意:グローバルターミナル変数の名前の長さは63文字を超えてはなりません。
このスクリプトを任意のチャートで実行しても、すぐには明らかな結果は表示されません。ただし、<F3>キーを使用するか、ターミナルメニューから[ツール]>[グローバル変数]を選択すると、何が起こったかを確認できます。

図7:ターミナル変数メニュー
このメニュー項目を選択すると、すべてのターミナル変数のリストを含むウィンドウが表示されます。

図8:グローバルターミナル変数のリストを表示するウィンドウ
図8では、スクリプトをEURUSDペアに対してのみ実行したため、表示される変数は2つだけであることがわかります。この場合は、購入額と売却額は同じです。
ここで、このデータを使用するインジケーターを作成し、同時に、標準関数が上記で説明した変数の操作の原則をどのように使用するかを確認します。
このファイルに「GlobalVars.mq5」という名前を付けます。このインジケーターの主な操作は、プログラムの起動直後に1回実行されるOnInit関数内で実行されます。また、チャートからインジケーターを削除するときにコメントを削除するOnDeinit関数も追加します。各インジケーターに必須で、ティックごとに実行されるOnCalculate関数もこのインジケーターに存在しますが、使用されません。
//+------------------------------------------------------------------+ //| GlobalVars.mq5 | //| Oleg Fedorov (aka certain) | //| mailto:coder.fedorov@gmail.com | //+------------------------------------------------------------------+ #property copyright "Oleg Fedorov (aka certain)" #property link "mailto:coder.fedorov@gmail.com" #property version "1.00" #property indicator_chart_window //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Description of local variables string symbolName = Symbol(); // Symbol name string terminalVariableName; // Name of global terminal value double buyMarginValue, sellMarginValue; // Buy and Sell value bool okCalcBuy; // Indication that everything is OK when calling one of the variants of the GlobalVariableGet function //--- Main operations // Create a terminal variable name for Buy details terminalVariableName = symbolName + "BuyAmount"; // Use the first method to get the value of a global variable. // To get the result, the parameter is passed by reference okCalcBuy = GlobalVariableGet(terminalVariableName, buyMarginValue); // Change the name of the terminal variable - for Sell details terminalVariableName = symbolName + "SellAmount"; // Second way to get the result: return value sellMarginValue = GlobalVariableGet(terminalVariableName); //--- Output the result as a comment on the chart Comment( "Buy margin is " + DoubleToString(buyMarginValue) // Buy margin value, the second parameter // of the DoubleToString function is omitted +"\n" // Line break +"Sell margin is " + DoubleToString(sellMarginValue,2) // Margin value for sale, indicated the number of // decimal places ); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| The function will be called when the program terminates. | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Очищаем комментарии Comment(""); } //+------------------------------------------------------------------+ //| Custom indicator iteration function (not used here) | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- //--- return value of prev_calculated for the next call return(rates_total); } //+------------------------------------------------------------------+
例32:インジケーターでグローバルターミナル変数を使用する
このデモインジケーターでは、ターミナル変数を一度読み取って操作を終了する必要があります。したがって、メインコードはOnInit関数内に配置されます。この例は大きくて怖いように見えるかもしれませんが、実際には、ほとんどがコメントなので、非常に読みやすいです。OnInit関数で何が起こるかをもう一度言葉で説明しましょう。
- この関数の最初のブロックでは、後で必要になるすべての変数を宣言します。
- 次に、グローバルターミナル変数の名前を作成します。
- 必要なグローバルターミナル変数の値を対応するローカル変数に読み込みます。
- 次に、2番目の変数の名前を生成し、次の行でその値を読み取ります。
- 最後のアクションは、左上隅にコメントの形式でユーザー向けのメッセージを出力します(図9を参照)。
GlobalVariableGet関数には、戻り値を使用するか参照によって渡されるパラメータを使用するかの2つの呼び出しオプションがあり、DoubleToString関数にはデフォルト値を持つパラメータがあることに注意してください。コードの機能を確認するために、クリップボード経由でコピーするのではなく、エディターにサンプルテキストを入力すると、MetaEditorによってこれらのニュアンスの入力を求められます。

図9:インジケーター操作の結果
出力を生成するためにDoubleToString関数を呼び出すさまざまな方法を使用したため、上部の行と下部の行のコメントが若干異なって見えます。一番上の行のメッセージをフォーマットするときに、DoubleToString関数の2番目のパラメータを省略しました。このパラメータは小数点以下の文字数を指定する必要があり、デフォルトでは8です。一番下の行では、この値を明示的に指定し、プログラムに2文字を出力するように指示しました。
インジケーターは、スクリプトが適用されたチャート上で、スクリプトの後にのみ起動する必要があります。これは、インジケーターの実行時にターミナルのグローバル変数が存在することを確認するためです。そうしないと、インジケーターの実行時にエラーが発生し、コメントが表示されません。
したがって、ターミナル変数を書き込むにはGlobalVariableSet関数を使用し、ターミナル変数を読み取るにはGlobalVariableGetを使用します。これらの関数はプログラマーが最も頻繁に使用しますが、他の関数も便利なので、少なくとも言語ドキュメント(セクションの冒頭のリンク)のリストを読むことをお勧めします。
結論
今日取り上げたトピックのリストをもう一度確認してみましょう。リスト内のいずれかの点が不明瞭な場合は、記事の対応する箇所に戻ってもう一度読んでください。この資料は、残りの作業の基礎となるためです(グローバルターミナル変数を除いて、なくても実行できる場合が多いですが、理解しても通常は問題はありません)。そこで、この記事では以下の点について説明しました。
- 配列
- 静的でも動的でも可能
- 1次元でも多次元でも可能
- 静的配列はリテラル(中括弧内)を使用して初期化できる
- 動的配列を操作するには、サイズを変更したり現在のサイズを確認したりするための標準関数を使用する必要がある
- 関数に関連する変数
- ローカル(static以外は短命)、static修飾子を追加できる
- グローバル(長寿命)、externおよびinput修飾子を追加できる
- 関数パラメータを渡す方法
- 参照渡し
- 値渡し
- グローバルターミナル変数:単なるグローバルプログラム変数とは異なり、異なるプログラム間でデータを交換するために使用できる。これらを使用するための特別な関数セットがある。
これらすべてを覚えていて、リストの項目に戸惑うことがなければ、もはや初心者とは言えません。すでにしっかりとした基礎が身についています。あとは、基本的な演算子の使い方を理解し、インジケーターやEAを作成する際に、MQL5言語には他の言語と比べてどのような特徴があるのかを把握するだけです。そうすれば、実用的なプログラムを書き始めることができます。しかし、「プロフェッショナル」レベルに到達するには、オブジェクト指向プログラミングを含むさらに十数のトピックを理解する必要があります。それでも、これらのトピックはすべて、何らかの形ですでに半分完成している基礎の上に成り立っています。
そして、言語のドキュメントが常にあなたの助けとなりますように…
本連載の以前の記事:
- 初心者からプロまでMQL5をマスターする(第1回):プログラミングを始める
- 初心者からプロまでMQL5をマスターする(第2回):基本的なデータ型と変数の使用
- 初心者からプロまでMQL5をマスターする(第3回):複雑なデータ型とインクルードファイル
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/15357
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
初級から中級へ:変数(III)
人工蜂の巣アルゴリズム(ABHA):理論と方法
取引におけるニューラルネットワーク:時系列予測のための軽量モデル
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
ありがとう。
喜んで...