MQL4のEA開発において、OrderSelect関数は様々な場面で頻繁に使用される関数であると共に、一番奥が深い関数でもあります。頻繁に使用される関数であるからこそ、OrderSelect関数を正しく理解しなくてはいけません。適切でないOrderSelect関数の扱いはバグの原因となり、ロジック通りの取引ができなくなる危険性があります。
OrderSelect関数とは
まずはOrderSelect関数の概要について説明します。
bool OrderSelect(
int index, // index or order ticket
int select, // flag
int pool=MODE_TRADES // mode
);
OrderSelect関数では3つの引数を指定して、該当のオーダーを選択します。オーダーの選択に成功した場合はtrue、失敗した場合はfalseが返り値となります。OrderSelect関数でオーダーを選択すると、OrderMagicNumberやOrderOpenPriceなどの関数を使用してオーダーの詳細情報を取得できるようになります。
OrderSelect関数は大きく分けて3通りの使い方があります。
- インデックス番号を指定して、トレーディングプールからオーダーを選択
- インデックス番号を指定して、ヒストリープールからオーダーを選択
- チケット番号を指定してオーダーを選択
1と2のインデックス番号を使用してオーダーを選択する場合には、第二引数にはSELECT_BY_POSを指定します。3のチケット番号を使用してオーダーを選択する場合には、SELECT_BY_TICKETを指定します。
トレーディングプールからオーダーを選択する場合には、第三引数にMODE_TRADESを指定します。第三引数はデフォルトがMODE_TRADESなので、トレーディングプールから選択する場合には省略することができます。ヒストリープールから選択する場合には、第三引数にMODE_HISTORYを指定します。
第二引数にSELECT_BY_POSを指定した場合、第一引数にはオーダープールのインデックス番号を指定します。第二引数にSELECT_BY_TICKETを指定した場合、第一引数にはチケット番号を指定します。
第二引数にSELECT_BY_TICKETを指定すると、第三引数は無視されます。チケット番号でOrderSelectすると、どちらか存在する方から選択されます。どちらのプールから選択されたか知りたい場合は、OrderCloseTime関数などを使って決済済みか判断します。
幾つか用語が出てきたため補足します。
トレーディングプール
トレーディングプール(trading pool)とは未決済の保有中ポジションと未約定の待機注文が集まって管理されている場所のことです。ターミナルの取引タブのような場所だと考えると、イメージしやすいかと思います。
上の図では3つの保有中ポジションと、1つの待機注文があります。この4つのオーダーはいずれも、トレーディングプールによって管理されています。OrderSelect関数を使ってこれらのオーダーを選択するには、第三引数にMODE_TRADESを指定して、トレーディングプールを使用しましょう。
ヒストリープール
ヒストリープール(history pool)とは、決済済みのオーダーとキャンセルされた待機注文が集まって管理されている場所のことです。こちらはターミナルの口座履歴のような場所と考えて下さい。
決済済みオーダーと、キャンセルされたオーダー以外にも、入出金やOANDA Japanのスワップなどのようにブローカーによって挿入された履歴もヒストリープールによって管理されます。これらのオーダーを選択するには、第二引数にSELECT_BY_POSを、第三引数にMODE_HISTORYを指定しましょう。
オーダープール
オーダープール(order pool)とは、トレーディングプールとヒストリープールをまとめた呼称です。
インデックス
インデックス(index)とは、オーダープールに集まっているオーダーに割り振られた管理番号のことです。このインデックスの番号は、オーダープールに来た順番に、0, 1, 2, ... と割り振られていきます。この番号は必ず0から始まる連番となります。
OrderSelect関数を使う上では、これらの用語が重要となります。中でもインデックスの動きが特に重要となります。
オーダープールの基礎
まずはオーダープールの基本的な動きについて理解しましょう。
新しいオープンポジション、もしくは待機注文が作られたタイミングで、トレーディングプールにオーダーが追加されます。
決済または待機注文のキャンセルがされると、トレーディングプールのオーダーが減り、ヒストリープールに新たなインデックスのオーダーが追加されます。これ以外に、入出金をおこなった場合などにも、ヒストリープールにオーダーが追加されます。
オーダーを決済すると、トレーディングプールのインデックスに欠番が発生することがあります。その場合にインデックスは、0からの連番となるように再計算がおこなわれます。
上の図ではトレーディングプール内のindex 2のオーダーが決済されたことで、index 2の情報はヒストリープール内にindex 4として新たに追加されています。index 2が決済され欠番となったため、トレーディングプール内ではインデックスの再計算が起こります。インデックスはオーダープールに来た順に0から割り振られます。この場合ですと、index 0と1はそのままで、index 3が2へ、index 4が3となることで、再び0からの連番となります。
これがオーダープールとインデックスの基本的な流れになっています。続いて、オーダープールにオーダーが追加されるタイミングについて、もう少し詳しく見ていきましょう。
トレーディンプールにオーダーが追加されるタイミング
トレーディングプールにオーダーが追加されるタイミングは4つあります。
- 新規エントリー時(成り行き買い・成り行き売り)
- 待機注文時(指値買い・指値売り・逆指値買い・逆指値売り)
- 部分決済時
- 待機注文からエントリー時
1と2に関しては特に説明する必要は無いかと思います。新しい取引がされたため、トレーディングプールにもオーダーが追加されます。
部分決済は少々特殊な処理となっております。部分決済が行われた場合、一度そのオーダーは決済され、新しいチケット番号で残りのロット数のオーダーが作られます。ターミナルの取引タブではオーダーの総数に変化は無いように見えますが、トレーディングプールではオーダーが削除されてから、オーダーの追加が行われています。インデックスはオーダープールに追加された順に割り振られるため、部分決済の際にインデックス番号が変わることがあります。例えば4ポジションを保有していた際に、index 1のオーダーを部分決済したとします。この時index 1は一旦トレーディングプールから削除されるため、index 2が1へ繰り上がり、index 3が2へ繰り上がります。そして部分決済後の旧index 1のオーダーが、index 3として新たに追加されます。部分決済をおこなった場合は、ロット数だけでなくチケット番号とインデックスも変わるということを覚えておいてください。
待機注文が約定し、エントリーされた場合にもトレーディングプールにオーダーの追加がおこなわれます。これも先ほどの部分決済の場合と同様で、一旦待機注文だったオーダーが削除されます。その後、新しいチケット番号が割り振られ、トレーディングプールにオーダーが追加されています。下の2つの画像は、待機注文のエントリー前とエントリー後のチケット番号です。エントリー前は146890316でしたが、エントリー後は146890590に変わっています。
部分決済時と待機注文のエントリー時にチケット番号が変わるというのは、非常に重要な要素です。OrderSend関数の返り値として受け取ったチケット番号を利用して決済するEAの場合、部分決済後にチケット番号が変わるため、無効なチケット番号となってしまいます。部分決済後のオーダーを決済するには、チケット番号の再取得が必要となります。
待機注文でエントリーするEAにおいても、待機状態のチケット番号とエントリー後のチケット番号が異なりますので、エントリー後にオーダーを決済するには、チケット番号を再取得しなくてはいけません。
EA側で部分決済することが無くとも、EAが保有したポジションを利用者が裁量で部分決済してしまうこともあります。「EAのポジションを手動で切るな!」と思うかもしれませんが、本当に様々な使い方する人がいます。
このようなケース時に不具合を起こさないためにも、チケット番号は外部変数に保持して使いまわすのではなく、必要なタイミング(決済前、注文変更前など)でOrderSelect関数を使い、再取得して利用するのが望ましいです。
トレーディンプールからオーダーが削除されるタイミング
トレーディングプールからオーダーが削除されるタイミングも合計4つあります。
- オープンポジション決済時
- 待機注文キャンセル時
- 部分決済時
- 待機注文のエントリー時
1と2は説明せずともイメージできるかと思います。決済・キャンセルされたことでトレーディングプールからオーダーが消えます。補足するのであれば、待機注文がキャンセルされる方法には、手動でキャンセル、EAによるOrderDelete関数からのキャンセル、設定した注文の有効期限切れによるキャンセルの3種類があります。
3と4については、トレーディングプールへの追加の際に説明した通りです。一旦オーダーが削除され、新たにオーダーが追加され直すという流れで処理が行われいます。
ヒストリープールにオーダーが追加されるタイミング
ヒストリープールにオーダーが追加されるタイミングは合計7通りに分類できます。
- オープンポジションが決済された時
- 待機注文がキャンセルされた時
- 部分決済がされた時
- 待機注文がエントリーした時
- 入金・出金を口座に反映された時
- ブローカーによる口座残高の調整がされた時
- 口座履歴の期間が変更された時
1と2については特に説明は不要かと思われます。
部分決済された場合は、決済された分の取引数量の決済オーダーが作られ、ヒストリープールに追加されます。トレーディングプールに残ったオーダーについても、決済される毎にヒストリープールに追加されます。そのため部分決済を使用している場合、1回のエントリーであっても、ヒストリープールには複数のオーダーが作られることとなります。
4について、待機注文がエントリーすると、それまでの待機注文のオーダーはヒストリープールに追加されます。トレーディングプールには新たにオープンポジションのとしてのオーダーが追加されます。このポジションが決済されると、その情報もヒストリープールに追加されるため、待機注文からエントリーすると、取引としては1回ですが、ヒストリープールには2つの取引情報が作られることとなります。
上の2つの画像はトレーディングプールにオーダーが追加されるタイミングの説明でも使用した画像です。待機注文のエントリー前後でチケット番号が変わっています。下の画像はこのポジションが決済された場合の口座履歴の画像です。上の2つの画像と、下の画像のチケット番号を見比べてみて下さい。チケット番号が一致していますね。このように待機注文からエントリーして、決済された場合には、ヒストリープールにはオーダーが2つ作られます。
入金額や出金額に関する情報もヒストリープールに追加されるため、入金や出金の手続きをおこなうことでヒストリープールにオーダーが追加されます。
ブローカーによって口座残高の調整がおこなわれ、オーダーが挿入されることがあります。これはOANDA Japanで取引をおこなったことのある方なら経験があるのではないでしょうか?OANDA Japanの口座でポジションを保有しても、オープンポジションにはスワップポイントが付きません。代わりに日毎にスワップポイント分だけ口座残高の増減がおこなわれ、口座残高が調整がされます。ちなみにOANDA Japanのベーシック口座やスタンダード口座では、スワップポイントの計算が秒単位でおこなわれるため、日を跨がずにポジションを決済してもスワップポイント分の調整がおこなわれることがあります。
ヒストリープールからオーダーが削除されるタイミング
ヒストリープールに存在するオーダーの数は、口座履歴の件数と同じです。そのため、口座履歴の期間を変更した際にヒストリープールのオーダーの数が増減します。下の図では、口座履歴の期間を全期間に設定しています。
口座履歴に全期間の情報を表示した状態で、下記のコードを実行してヒストリープールのオーダーの件数を取得してみます。
int OnInit()
{
Print("OrdersHistoryTotal = ", OrdersHistoryTotal());
return(INIT_SUCCEEDED);
}
このコードを試しに私の口座で実行した結果、76件のオーダーがありました。
続いて、意図的に口座履歴の期間を絞り、件数を減らしてみました。下の図はその結果です。
かなり期間を絞ったので、口座履歴には1件もオーダーが表示されていません。この状態で、先ほどのコードを実行してみます。
この場合の実行結果は0件となりました。このように、口座履歴に表示する期間を変えることで、ヒストリープールの件数は変化します。
口座履歴の期間が変わるのは、手動で変更したタイミングだけではありません。MT4の再起動時に、知らぬ間に変わってしまう場合もあります。
口座履歴に全期間を指定しても、口座開設時からの全履歴が取得できるわけではありません。一部のブローカーでは、一定期間以上経過した古い取引履歴については配信されなります。どの程度の期間で配信されなくなるかは、ブローカーによって変わります。
このように、口座履歴の件数が変化するタイミングで、ヒストリープールの件数も増減します。
総括
長くなりましたが、以上がMQL4でOrderSelect関数を使うための前提知識です。
上記の振る舞いを理解した上で、他プログラムや手動で取引が発生しても問題の無い処理を考える必要があります。
実際にどんなコードを書くべきかは、そのうち別の記事で書きたいと思います。