価格データをはじめとした、「時間」によって変動するデータはMQLでは配列の形で扱われます。
この配列はMQLでは時系列配列と呼ばれています。
この時系列配列データ、少しながらクセのある扱いになっているので、十分に理解しておきましょう。
MQLでの時系列配列の考え方
MQLにおける時系列配列では、デフォルトで直近のデータが配列のインデックス0に格納されます。
MQL以外のプログラミング言語を齧ってからだと、ものすごい違和感を覚えてしまいますがそういう仕様なのです。
この時系列配列をCopyBuffer関数を使って普通の配列に複製して、複製先の配列を参照する際、インデックスの付番が逆転します。インデックス0には複製したうちのもっとも古いデータが格納されることに。
なんともややこしい仕様に思えますが、直近のデータがもっとも重要になるというFXの性質から見れば必要な仕様なのやもしれません。
可変のインデックスでなく、毎回インデックス0として値を参照できたほうが、プログラミングとしては簡素に記述できます
とはいえ、新たに直近のデータが生成されるたびに時系列配列内の値を再配置していたらメモリ領域の確保に掛かるコストがバカになりません。
そこらへんはうまくできているようで、物理的に値を格納しているメモリ上の領域は再配置しないよう実装されているようです。
OnCalculate関数の引数について
OnCalculate関数のインデックスの向き
カスタムインジケーターで必須の関数であるOnCalculate関数。
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[])
これらの引数は高値や始値といった各種価格データを保持する時系列配列です。
しかしこの引数、時系列配列ではあるもののインデックス0には直近の値でなく、期間内のもっとも古い値が格納されています。
実は時系列配列のインデックスの向き(0を最新とするか、0を最古とするか)は、AS_SERIESフラグを設定することで変更することができます。
OnCalculate関数の引数について、次のように確認してみます。
string strTemp = "";
StringAdd(strTemp, "time : 時系列/"+ArrayIsSeries(time)+", 方向/"+ArrayGetAsSeries(time)+"\n");
StringAdd(strTemp, "open : 時系列/"+ArrayIsSeries(open)+", 方向/"+ArrayGetAsSeries(open)+"\n");
StringAdd(strTemp, "high : 時系列/"+ArrayIsSeries(high)+", 方向/"+ArrayGetAsSeries(high)+"\n");
StringAdd(strTemp, "low : 時系列/"+ArrayIsSeries(low)+", 方向/"+ArrayGetAsSeries(low)+"\n");
StringAdd(strTemp, "close : 時系列/"+ArrayIsSeries(close)+", 方向/"+ArrayGetAsSeries(close)+"\n");
StringAdd(strTemp, "tick_volume : 時系列/"+ArrayIsSeries(tick_volume)+", 方向/"+ArrayGetAsSeries(tick_volume)+"\n");
StringAdd(strTemp, "volume : 時系列/"+ArrayIsSeries(volume)+", 方向/"+ArrayGetAsSeries(volume)+"\n");
StringAdd(strTemp, "spread : 時系列/"+ArrayIsSeries(spread)+", 方向/"+ArrayGetAsSeries(spread)+"\n");
配列が時系列配列なのかどうかをチェックする関数。
引数で指定した配列が時系列の場合はtrueを、それ以外の場合はfalseを返します。
bool ArrayIsSeries(
const void& array[] // チェック対象の配列
);
インデックスの向きをチェックする関数。
インデックス0に最新のデータが格納されている場合はtrue、それ以外の場合はfalseを返します。
bool ArrayGetAsSeries(
const void& array[] // チェック対象の配列
);
OnCalculate関数の引数に対してチェックした結果は次のとおり。
時系列配列であるものの、インデックスの向きはfalse、つまりインデックス0にはもっとも古いデータが格納されています。
time : 時系列/true, 方向/false
open : 時系列/true, 方向/false
high : 時系列/true, 方向/false
low : 時系列/true, 方向/false
close : 時系列/true, 方向/false
tick_volume : 時系列/true, 方向/false
volume : 時系列/true, 方向/false
spread : 時系列/true, 方向/false
最新の価格データの取得方法
OnCalculate関数の引数はインデックス0にはもっとも古いデータが格納されています。
最新のデータを抽出するには、配列の最後尾のインデックスを参照する必要があるため、OnCalculate関数の引数であるrates_totalを使用します。
timeやopen、highなどOnCalculate関数に渡される価格データの「時間枠」のサイズ。
各配列の要素数を示す。
rates_totalは各配列の要素数を示すため、配列の有効なインデックスは0〜rates_total-1。
配列の最後尾を参照するには、time[rates_total-1]というような形で指定します。
Comment(TimeToString(time[rates_total-1], TIME_SECONDS));
上のソースを実行すると画面が更新されるたびに次のように最新のデータが表示できます。
00:00:01
↓
00:00:02
↓
00:00:03
↓
00:00:04
↓
・・・
配列のサイズを取得するArraySize関数を使っても実装できますが、直接値を使用できるrates_totalを使用すべきです。
TimeToString(time[rates_total-1], TIME_SECONDS);
TimeToString(time[ArraySize(time)-1], TIME_SECONDS);
まとめ
MQLにおける時系列配列と、OnCalculate関数の価格データについてまとめてみました。
MQLの仕様なので仕方ないところではありますが、解釈するのに少し分かりにくく、一歩間違えると重大なバグになってしまうところでもあります。
少しプログラムのライン数が多くなってしまいますが、ArrayGetAsSeries関数などでチェックする処理を入れるなどすると理解も進みますし、想定外のバグが発生する可能性も減らすことができます。
各種チェック処理をうまく活用して、適切に利用していきましょう