MQL4の配列を理解してFX自動売買の足と添字を確かめる方法

MQL4の配列で最初に確かめたいのは、要素の数、添字の方向、そこに入れた足の時刻です。通常配列の0番目と、価格の時系列配列の0番目は同じ意味とは限りません。ArraySetAsSeriesを付けても足が確定したり、新しい価格が自動で補充されたりするわけではないため、この三つを分けて考えます。
FX自動売買のEAでは、配列の読み違いが「前の足を比較したつもりで現在足を見ていた」「データが足りないのに最後の要素を読んだ」といった不具合につながります。この記事ではMT4のMQL4を対象に、価格データと一時計算用の配列を扱うときの確認方法を、発注しない小さな例で整理します。
- 固定サイズと可変サイズの配列を用途で選ぶ
- 通常の添字と足の新旧を混同せず読み取る
- 取得件数と有効範囲を確認してから参照する
- 価格と足の開始時刻を組み合わせて照合する
MQL4の配列とFX自動売買の添字

価格と一時計算値の入れ物を選ぶ
配列は、同じ型の値を一つの名前で扱い、角括弧の中の番号で要素を指定する入れ物です。終値や値幅ならdouble型、件数ならint型、足の開始時刻ならdatetime型が使えます。価格をclose1、close2、close3と別々の変数へ入れる代わりに、一つの配列へまとめれば同じ計算を繰り返せます。
要素数を宣言時に決める例はdouble work[3];です。この配列の有効な添字は0、1、2で、角括弧内の3は最後の番号ではなく要素数を表します。一方、double work[];のように最初の次元の大きさを省いたものが動的配列です。使う領域を確保する前に、work[0]へ代入できるとは考えないでください。
静的配列はサイズをあらかじめ決める配列で、動的配列は必要に応じてサイズを変えるための配列です。ここでいう「静的」は大きさの分類です。変数宣言に付けるstatic修飾子と、同じ意味の言葉として読み替えないようにしましょう。宣言の区別はMQL4公式の動的配列と静的配列の説明で確認できます。
常に三つの計算結果しか使わないなら、固定サイズでも役割を明確にできます。取得本数が変わる価格の保存や、足と同じ方向の添字で読む設計なら、一次元の動的配列が候補です。動的であれば必ず速いという意味ではありません。必要な件数と、その後どう読むかで選ぶ方が整理しやすくなります。
また、EAが自分で作った配列には、代入やコピーをした値しか入りません。名前をCloseやPriceに似せただけでチャートの価格へ接続されることはありません。取得元を読む配列、変換後の値を置く配列、今回だけの集計結果を置く配列を、変数名や短いコメントで区別しておくと確認が進みます。
通常配列と価格配列の0を分ける

一般的な配列は通常方向の添字でアクセスし、0が先頭、1が次の要素です。ただし、先頭の値が古い足なのか新しい足なのかは、何をどの順番で格納したかで決まります。自分でwork[0] = Close[1];と代入したなら、work[0]には代入した時点の一つ前の足の終値が入ります。番号が0だから現在足、という規則は通常配列にはありません。
MT4が用意しているClose[]は、現在のチャートの各足の終値を格納する時系列配列です。こちらは0が現在足、1が一つ前の足、2が二つ前の足です。Time[]も同じ方向で、各足の開始時刻を表します。現在足は進行中なので、Close[0]を確定済みの終値として扱うと、確定足だけを見る条件とは別の判定になります。
| 参照するもの | 0番目の意味 | 1番目の意味 |
|---|---|---|
| 通常配列work[] | 自分が先頭へ入れた値 | 自分が次へ入れた値 |
| 定義済みClose[] | 現在足の終値として更新される値 | 一つ前の足の終値 |
| 定義済みTime[] | 現在足の開始時刻 | 一つ前の足の開始時刻 |
たとえば、15分足のチャートで「直近の確定足二本を比較する」なら、通常はClose[1]とClose[2]が比較対象です。Close[0]とClose[1]では、進行中の足が含まれます。どちらが正しいかは売買ルール次第ですが、確定足のルールを作ったのに現在足を混ぜることは避けたいところです。
この対応は公式のClose[]と公式のTime[]で定義されています。Close[]は現在チャートの配列なので、別の銘柄や別の時間足の価格として、そのまま使うこともできません。配列の方向が合っていても、データの対象が違えば同じ足の比較にはなりません。
なお、一つ前の足は、時計で一定分数だけ引いた時刻と常に一致するとは限りません。取引がない時間や履歴の欠けがあるためです。ここでは添字が示す足をTimeで確認し、複数時間足の対応付けなどの設計とは切り分けて考えましょう。
ArraySetAsSeriesの役割と制約

動的配列を時系列と同じ向きで読みたい場合は、ArraySetAsSeries(array, true)を使います。成功すると、配列の末尾側の要素から0、1、2とアクセスする方向になります。戻り値はboolなので、設定できた前提で処理を続けず、trueかどうかを確認してください。
大切なのは、この関数が変えるのはアクセス方法であり、メモリ内の値の並びを物理的に逆転させる操作ではないことです。価格を大小順に並べ替える機能でも、古いデータを再取得する機能でもありません。内容が同じでも、どの値をarray[0]として読むかが変わるため、設定前後で同じ添字を同じ足と思わないようにします。
また、AS_SERIESフラグは静的配列と多次元配列には設定できません。double work[10];はサイズをコンパイル時に決める静的配列なので対象外です。double work[][2];のように最初の次元を変えられても、多次元であるため対象外になります。「動的なら何でも使える」とまとめると、この条件を見落とします。
仕様は公式のArraySetAsSeriesと、物理配置を含む配列・バッファ・時系列の添字方向を組み合わせて確認できます。EAの一時計算用配列まで、すべて時系列方向へ変える必要はありません。先頭から順に集計する処理なら、通常方向の方が追いやすい場合もあります。
現在の方向はArrayGetAsSeriesで確認できます。似た名前のArrayIsSeriesは、その配列が時系列に属するかを調べる関数で、添字方向そのものを調べたいときとは目的が違います。自作配列にAS_SERIESを付けたことと、MT4が提供する価格配列であることを同一視しないようにしましょう。違いはArrayGetAsSeriesの公式説明にあります。
カスタム指標側のコードを読む場合には、SetIndexBufferで結び付けた後の方向にも注意します。公式仕様では結び付け後は通常方向になるため、時系列方向にしたいならその後に設定します。EA側の普通の作業配列と指標バッファを混同しないことが、サイズや方向の問題を切り分ける近道です。
指標のOnCalculateに渡される価格配列も、名前がcloseやtimeだから必ず時系列方向だと推測しないでください。公式のArrayGetAsSeriesの説明では、渡された価格配列の方向が時系列と同じとは限らないとされています。別のコードから関数を抜き出すときは、受け取る配列の方向を確認する処理まで含めて読みます。EAの定義済みClose[]と、関数の引数として受け取る配列を区別すると、移した処理だけ足が逆になる原因を探しやすくなります。
Copy系の取得範囲と格納順を読む

CopyCloseやCopyRatesでは、取得元の開始位置と、受け取り先の添字を分けて読みます。たとえばCopyClose(Symbol(), PERIOD_CURRENT, 1, 3, values)は、現在の銘柄・時間足について、一つ前の足から過去方向へ三本分を要求する呼び出しです。この1は取得元の足の位置で、受け取り先のvalues[1]へ書き始めるという意味ではありません。
コピー先の物理メモリでは、要求した範囲のうち最も古い要素が先頭側へ置かれます。これはコピー先のAS_SERIESがtrueでもfalseでも同じです。したがって通常方向の三要素配列ならvalues[0]が三つ前、values[1]が二つ前、values[2]が一つ前の足に対応します。時系列方向なら、そのアクセス順が逆になります。
| 一つ前から三本を取得 | values[0] | values[1] | values[2] |
|---|---|---|---|
| 通常方向で読む | 三つ前の足 | 二つ前の足 | 一つ前の足 |
| 時系列方向で読む | 一つ前の足 | 二つ前の足 | 三つ前の足 |
この表は、三本すべてを取得でき、受け取り先も三要素として扱う場合の対応です。要求した本数を取得できたか確認する前に、表どおりだと決め付けないでください。また、開始位置を0へ変えれば現在足が含まれ、開始位置が2なら直近の確定足も含まれません。コピー先の0番目がどの足かは、方向だけでなく取得範囲との組み合わせで決まります。
物理格納順と開始位置の規則は、CopyCloseの公式リファレンスに明記されています。終値と足の時刻を一組で確かめたいときは、後半で使うCopyRatesでMqlRates配列に取得すると、同じ要素のtimeとcloseを一緒に読めます。
一度取得した自作配列は、その時点のコピーです。新しいティックや新しい足が来ても、変数に入れた値が定義済みClose[]と同じように自動更新されるわけではありません。再取得したタイミングも含めて考えないと、添字は合っていても前回取得した価格を評価し続ける処理になってしまいます。
MQL4の配列をサイズと時刻で確かめる

サイズ変更と初期化を別に確認する
通常の動的配列の大きさを変える関数がArrayResizeです。変更するのは最初の次元で、成功すると変更後の配列の総要素数、失敗すると-1を返します。一次元配列で三要素が必要なら、戻り値が3になったことを確認してから0〜2を使います。宣言とサイズ変更は別の工程です。
double work[];
if(ArrayResize(work, 3) != 3)
return;
ArrayInitialize(work, 0.0);
work[0] = 1.5;
これは関数内へ置く説明用の断片で、これだけで動くEAではありません。大きさを確保したあとに数値配列を0で初期化し、その後に値を代入する順番を示しています。記事内のコードはMetaEditorでのコンパイル・実行を確認していません。実際のEAへ組み込む前に、対象の環境で確認してください。
ArrayResizeの対象は動的配列ですが、SetIndexBufferで指標バッファへ割り当てた配列のサイズは、自分で変更できません。指標バッファの大きさはMT4の実行システムが管理します。宣言が空の角括弧だからといって、EAの作業用配列と同じようにリサイズできるわけではない点は、公式のArrayResizeとSetIndexBufferの注意書きで確認できます。
第三引数のreserve_sizeは、頻繁な拡張でメモリを確保し直す回数を減らすための予約領域です。予約した分まで、今すぐ有効な添字として読み書きできるわけではありません。たとえば有効サイズを3、予約を10にしても、有効範囲は0〜2です。予約容量とArraySizeが示すサイズは分けて扱います。
初期化にも同じ区別が必要です。ArrayInitializeで既存要素を0にしても、予約領域から後で増えた要素まで0が保証されるわけではありません。公式のArrayInitializeも、拡張後の追加要素の値は未定義になることを説明しています。増やした要素へ必要な値を代入してから計算へ回す設計にしましょう。
ArraySizeと有効添字を照合する

一次元配列のサイズがnなら、有効な添字は0以上n未満です。nが3なら最後は2であり、array[3]は範囲外です。ループではi < nとi <= nの違いを確認します。最後の一回だけ範囲外になる処理は、価格取得の不具合と誤解しやすい箇所です。
空の配列ではnが0なので、0番目も存在しません。最後の要素を読む式としてarray[n - 1]を使うなら、その前にnが正か確認します。前後の要素を比較する場合も、iが範囲内なだけでは不十分です。i+1やi-1を含め、実際に参照するすべての添字が入る条件を作ってください。
たとえば、通常配列で隣り合う二要素の差を計算するなら、少なくとも二要素が必要です。iとi+1を使う処理では、i+1がn未満になる範囲で繰り返します。時系列方向でも有効な番号の範囲自体は0〜n-1で変わりません。変わるのは番号と足の新旧の対応であり、範囲外の参照が許されるようになるわけではありません。
ArraySizeは配列全体の要素数を返し、一次元ではArrayRange(array, 0)と同じです。多次元では総要素数を各次元の上限として使わないでください。たとえば三行二列の配列は総要素数が6でも、行の添字は0〜2、列の添字は0〜1です。個々の次元の大きさはArrayRangeで確認します。根拠はArraySizeとArrayRangeの公式仕様です。
サイズが足りなかったときに、とりあえず0を返して売買条件へ進ませるのも避けましょう。「計算結果が0」と「計算できなかった」を区別できなくなります。処理できないときは今回は判定しない状態を戻すなど、計算の成否と数値を別々に扱うと、不足したデータで条件が成立する誤りを防ぎやすくなります。
発注せず取得件数と足を表示する

添字の対応を確かめるときは、先に価格と時刻だけを表示する小さな処理へ切り出すと追いやすくなります。次の例は、現在のチャートから直近の確定足三本をMqlRates配列へ取得し、添字、開始時刻、終値を表示するスクリプトの例です。OrderSendなどの発注処理は含みません。
#property strict
void OnStart()
{
MqlRates rates[];
if(!ArraySetAsSeries(rates, true))
return;
ResetLastError();
int copied = CopyRates(Symbol(), PERIOD_CURRENT, 1, 3, rates);
if(copied != 3)
{
Print("skip copied=", copied, " error=", GetLastError());
return;
}
int size = ArraySize(rates);
if(size < copied)
return;
for(int i = 0; i < copied; i++)
Print("index=", i,
" time=", TimeToString(rates[i].time, TIME_DATE|TIME_MINUTES),
" close=", DoubleToString(rates[i].close, Digits));
}
この例も、コンパイル・実行は未確認です。使用するMetaEditorでエラーと警告を確認し、検証用環境で動作を確かめてください。MT4でコンパイル結果と実行ファイルを確認する手順を使えば、編集したソースと読み込むファイルの取り違えも点検できます。
CopyRatesの開始位置は1なので、取得範囲に現在足を含めていません。AS_SERIESをtrueにした受け取り先では、三本の取得に成功した場合、rates[0]が直近の確定足、rates[1]がその一つ前、rates[2]がさらに前に対応します。rates[0]だからチャートの0番足という読み方はせず、開始位置との組み合わせで理解しましょう。
CopyRatesの戻り値は取得した要素数で、エラー時は-1です。必要な履歴がまだ構築されていない、取得範囲が利用可能なデータの外にある、といった場合もあります。この例では三本そろわなければ表示処理へ進みません。詳しい取得条件は公式のCopyRatesで確認できます。
配列を用意できたことと、必要な価格を取得できたことは別です。ArraySizeが十分でも、直前のコピーに失敗していれば、以前の値を今回の取得結果と誤認する可能性があります。再利用する配列では特に、コピーの戻り値を先に確認し、成功した今回の範囲だけを使う順番にします。エラー番号だけを見て成功と決めず、戻り値を入口にしてください。
時刻と値で同じ足かを見直す

表示できたら、検証用チャートの同じ足へカーソルを合わせ、データウィンドウの開始時刻と終値に対応するかを確認します。まず一つの銘柄・一つの時間足で、三つの要素が新しい足から古い足へ並んでいるかを見ます。ログの数字がもっともらしいことより、どの足の数字なのか説明できることを重視しましょう。
照合するときは、銘柄、時間足、取得元の開始位置、AS_SERIESの状態、取得件数、先頭と末尾の時刻を一組として扱うと原因を絞れます。時刻の表示には、チャートと同じ基準を使います。MT4が示す足の時刻を、日本の時計の時刻だと決め付けて照合すると、配列が正しくてもずれて見えることがあります。
さらに、新しい足ができる前後で確認します。定義済みのTime[1]が指す足は、新しい足ができると一つ進みます。しかし、前にコピーしたrates[0]のtimeは、再取得しない限り前のままです。番号は足を永久に識別するIDではありません。後から同じ足か確かめたいときは、保存した開始時刻を手掛かりにします。
数値が合わない場合は、最初に足の時刻を照合し、次に銘柄・時間足、最後に取得値や計算を確認する順番が実用的です。違う足同士を比べたまま小数点の丸めだけを直しても、原因は解消しません。価格が同じ二本の足もあり得るので、価格の一致だけを同じ足の証拠にしないことも大切です。
カスタム指標の値をEAの配列へ保存する場合も、配列の番号と、取得時に指定した指標のshiftを別に管理します。iCustomの呼び出し自体はiCustomで指標の値を同じ足と照合する手順で確認し、この記事では受け取った値をどの添字へ入れたか、再取得したかを点検してください。
MQL4の配列は件数と足から確認する
MQL4の配列をEAで扱うときは、宣言した型と大きさ、添字の方向、取得した足の範囲を順に確認します。静的か動的かはサイズの扱い、AS_SERIESは読み方の方向、Copy系の開始位置は取得元の足を表します。それぞれの役割を分ければ、「0番目に何が入っているか」を一つずつ説明できます。
修正後は、必要本数に満たないとき、ちょうどそろったとき、新しい足へ切り替わったときの三つを少なくとも確認しましょう。足りない場合には参照せず、そろった場合には先頭と末尾の時刻が意図どおりで、切り替わった場合にはコピー済みデータを使い回していないかを見る流れです。配列の全要素を大量に表示するより、境界になる箇所を絞ると追いやすくなります。
コンパイルが通ることは、狙った足を参照できていることの証明にはなりません。発注しない処理で時刻と値を照合したあとにEAへ組み込み、同じ条件でテストします。環境と実行条件の確認にはMT4のEA設定とバックテストの手順も使えます。
今回の確認は、配列の扱いとEAの動作を点検するためのものです。足の参照が正しいことと、売買戦略が将来も利益を出せることは別の検証になります。まずは一つの配列について「何本あり、どちら向きで、何時の足か」を確認できる状態から進めてください。
