複数ポジションを扱うプログラム

MQL4解説

はじめに

単一ポジションのみを保有するプログラムでは、OrdersTotal()関数を使って現在のポジション数を確認し、ポジションを保有している場合は「決済処理」、ポジションを保有していない場合は「エントリー処理」というように、比較的シンプルな条件分岐で処理を組み立てることができます。

しかし、複数ポジションを扱うプログラムになると、処理の考え方は少し複雑になります。
単純に「ポジションを持っているかどうか」だけで判断するのではなく、どのポジションがどの条件で保有されたものなのか、また どのポジションを決済対象とするのか を個別に判別する必要があるためです。

例えば、順張りの買いポジションと逆張りの売りポジションを同時に保有している場合、決済条件が異なることがほとんどだと思います。このようなケースでは、各ポジションを区別して管理する仕組みが欠かせません。

今回説明するプログラムでは、OrderComment()関数を活用し、各ポジションに識別用のコメントを付与することで、複数のポジションを適切に管理する方法について解説していきます。

プログラムについて

ご自身のメタエディターに貼り付けて、このページを参考にしながら、理解を進めていくことをおすすめします。(基本的なEAファイルの作成についてはこちらをご覧ください。)

また、決済条件やエントリー条件を書くところは「/*決済条件①*/」のように説明コメントになっていますので、ご自身で考えたロジックをここに書くことで、簡単に複数ポジションを扱うプログラムが作成可能です。

それではプログラムの内容説明に移っていきます。

EAの外観

EAの基本的な構造は以下のようになっています。

EAの一番上で変数定義をしています。取引ロット数を決める「Lots」の他に、「buy_trend」「buy_reversal」「sell_trend」「sell_reversal」の4つがありますが、これはそれぞれ

  • buy_trend → 順張り買いポジション
  • buy_reversal  → 逆張り買いポジション
  • sell_trend → 順張り売りポジション
  • sell_reversal → 逆張り売りポジション

のことを表します。このプログラムではこの4つの場合に分けてポジションを管理していきますが、実際は、自分がどのような売買ルールで運用したいかによって、管理するポジション用の変数は自由に増やしたり減らしたりすることができます

OnTick内のfor文の中では現在保有中のポジションを1つずつ順番に選択しています。そして、選択したポジションが上の4つのどのポジションなのかを判別し、それぞれに対応した決済条件を満たしているかの確認を行っています。

for文の下部では新規ポジションを建てるためのエントリー処理を記述しています。それぞれのエントリー条件に合致しているかを判定し、条件を満たした場合に新規ポジションがエントリーされます。

プログラムの内容説明

①変数定義

「Lots」は取引ロット数の設定するための変数です。0.1となっていますが、extern変数なので、MT4のプロパティ画面から値を変更することができます。
→extern変数についてはこちらをご覧ください。

「buy_trend」「buy_reversal」「sell_trend」「sell_reversal」の意味は上で説明した通りですが、初期値はすべて「0」に設定しており、これは「まだそのポジションを保有していない状態」を表します。ポジションがエントリーしたタイミングで「1」に変更することで、同じ種類のポジションを重複して建てないようにしています

<補足>
なお、bool型で記述する方法もありますが、個人的に0→無効、1→有効という数値によるフラグ管理の方が視覚的に分かりやすく、普段のプログラミングでも使い慣れているため、int型を使用しています。
また、bool型では「未保有/保有」の2つの状態しか表現できませんが、int型であれば利益が一定以上乗ったあとに利食い幅を拡大するフェーズを2とするなど、状態を柔軟に増やして管理していくことができます。

②決済処理

決済処理についてはポジションの「1.選択フェーズ」と「2.決済フェーズ」に分けて説明していきます。

1.選択フェーズ

選択フェーズはOnTick関数の初めの2行の処理が該当します。

for文は同じ処理を繰り返したいときに使う構文で、変数iを使い、繰り返し処理(ループ)を管理します。forの()内の基本形は以下の通りです。

<forの基本形>

for(初期値;条件式;増減)

実際のコードに当てはめてみると、それぞれの意味は次のようになります。

  • 初期値: int i = OrdersTotal() - 1

    これはOrdersTotal関数を使って、現在保有中のポジション数を取得し、-1することでポジション番号(インデックス番号)に変換しています。
    例えば、3つのポジションを持っているとき、ポジション番号は古い方から0, 1, 2と付けられるため、一番最後にエントリーされたポジションの番号は「2」となります。
    そして、一番最後(最新)のポジションを初期値とすることで、2→1→0とポジションが順番に選択されていくことになります。
  • 条件式:i >= 0

    この条件式はforの繰り返し処理を続ける条件を表しています。つまり、ポジションを2→1→0と選択していったとき、最後の繰り返し処理が終わるとiが-1になりますがこれは条件を満たしていないので、ここでforのループ処理が終了することになります。
    また、ポジションを保有していない場合、OrdersTotalが0になり、初めからiが-1になるので、forの処理は行われないことになります。
  • 増減:i–

    これは初期値のところで定義した変数iをforの処理1回ごとに1ずつ減らしていくことを表します。当たり前のように上で説明してしまいましたが、ポジション番号が2→1→0と減っていくのは、ここで「i–」としているからなのです。(逆に「i++」とするとiが1つずつ増えていきます。)

<補足>
なぜポジション選択は前からではなく後ろからなのか。前からポジションを選択をしていく場合、for(int i=0; i<OrdersTotal(); i++)のように書くこともできますが、仮にこの後の処理でそのポジションが決済されると、ポジション番号(インデックス番号)が前詰めされるため、次のポジションがあった場合、飛ばされることになります。

ループiの値ポジション状態
初期状態00, 1, 2
1回目00番を決済→1, 2が0, 1に詰まる。
2回目1元の2番が飛ばされ、元の3番が選択される。

次の条件式のif文ではfor文で取得したi番目のポジションをOrderSelect関数を使って、選択状態にし、そのポジションの情報を取得できるようにします。

OrderSelect関数のパラメータは以下の通りです。

  • i → i番目のポジション
  • SELECT_BY_POS → ポジション番号選択の方式。保有中のポジションに対して、先頭から順番に番号(0,1,2,…)を割り当てるインデックス方式であることを意味する。
  • MODE_TRADES → 現在保有中のポジションから選択。(過去の決済済みポジションではない。)

しかし、今回の場合OrderSelectの前に「!」が付いており、これは「否定(NOT)」を表しているので、if文の内容としては、ポジションが選択されない場合、continueということになります。
→continue はfor文内で使われると、「ここでループ処理を終了し、次のループへ進む」という意味を表します。

なぜこのような書き方にするのかというと、もしポジションの選択ができなかった場合に前回のループで選択していたポジション情報がそのまま残り、誤ったポジションを決済してしまう可能性があるためです。うまくポジション選択できなかった場合は、スキップ処理をすることでこのような決済エラーを防ぎます。

☞選択がうまくいった場合は、ifの条件がfalseとなるため、continueは実行されず、そのまま次の処理に進むことになります。

2.決済フェーズ

決済フェーズでは買いポジション(OP_BUY)と売りポジション(OP_SELL)に分けて記述しており、さらにその中で順張り(trend)と逆張り(reversal)のポジションごとに処理を分岐させています。これらの処理構造はすべて同じ形をしているため、本記事では「buy_trend」を代表例として解説を行います。

現在選択されているポジションが買いポジションの場合、OrderCommnet関数によって、”buy_trend”か”buy_reversal”のいずれかに分類されます。ここでは”buy_trend”に該当する場合の処理について説明していきます。
※なお、OrderCommentの文言は後ほど説明しますが、注文の際のOrderSend関数内で設定する項目があります。

OrderCommentでポジションを判別したら、次はそれぞれのポジションに応じた決済判定を行っていきます。このプログラムでは利食いと損切りの2つを想定し、2種類の決済条件を用意していますが、else ifを削除すれば、条件を1つにまとめるができますし、逆にelse ifを追加することで決済条件を3つ以上に増やすことも可能です。
どのような条件にするかはトレード手法によって異なるため、決済条件①・②の部分にはご自身のロジックに応じた内容を記述してください。
決済条件をコメント(/* */)のままにしておくと、条件が未記述の状態となりコンパイルエラーになります。動作させる際は必ず具体的な条件式を記述するようにしてください。

続いて、決済条件①または②のいずれかを満たすと、OrderClose関数によってポジションが決済されると同時に、buy_trendのフラグを0に戻します。このフラグは同じポジションを重複して建てないようにするためのフラグでエントリー時に1に設定されます。

今回解説したbuy_trend以外も基本的な構造は全て同じになっていますので、適宜編集してご自身のトレード手法に合わせたカスタマイズを行ってみてください。また、扱うポジションの種類を増やしたい場合は、OrderCommentによる分岐をelse ifで追加することで対応できます。その際は、①変数定義、これから説明する③エントリー処理での変数の追加・設定を忘れずに行ってください。

③エントリー処理

エントリー処理は決済処理ほど複雑なものではなく、それぞれのエントリー条件をif/else ifを使って記述していくのみとなります。基本的には単一ポジションしか持たないEAの記述方法と変わりませんが、ポジション管理のためのパラメータ設定が必要になります。

※「buy_trend」「buy_reversal」「sell_trend」「sell_reversal」の4種類がありますが、構造は全て同じであるため、「②決済処理」の場合と同じく、「buy_trend」を代表例として説明していきます。

まずはじめに「順張り買い(buy_trend)」のエントリー条件を満たしているかの判定を行います。満たしている場合、内部処理に進むことになりますが、満たしていない場合は、「逆張り買い(buy_reversal)」のエントリー条件の判定に進むことになります。

エントリー条件を満たし、内部処理へ進むと、次はポジションの保有状態を確認します。「buy_trend==0」は「順張り買いポジション」を保有していない状態を表しており、条件を満たす場合はOrderSend関数で買い注文を行います。このとき、OrderSend関数の第8引数を「”buy_trend”」にします。ここで設定した文字列はOrderComment関数から取得できるようになり、決済時にどの種類のポジションかを識別するために必要になります。OrderSendしたあとはポジション保有状態を表すために、「buy_trend」のフラグを1にします。

1つ前の分岐でポジションを保有している場合は、「buy_trend==1」となるため、エントリー条件を満たしたとしても新規エントリーは行わず、処理を終了(else return)させることで、ポジションの重複を防いでいます。

まとめ

いかがだったでしょうか。複数ポジションを扱う場合、for文の理解とポジションの識別というのが鍵になります。ポジションの識別についてはマジックナンバーやチケット番号で指定する方法が一般的ですが、番号管理が分かりにくい場合は、今回ご紹介したようにOrderCommentで識別する方法が有効だと思っています。単一ポジションのプログラムと比べるとやや複雑にはなりますが、やっていること自体はほとんど変わりません。条件に応じてエントリーを行い、ポジションを選択し決済する。基本的な流れは同じであり、複数ポジションではその処理をポジションごとに繰り返しているだけなのです。

サイトについて

当サイトではFXの中でも特に自動売買に特化した情報を主に初心者の方を対象に提供しております。自動売買ツールであるMT4のインストールから設定、プログラムの書き方などを説明しておりますので、ご自身のトレードにご活用下さい。

MQL4解説