—
前回は、動的ストップロスを設定する一例として、直近高安値を取得して、それをストップロスとするやり方についての解説を行いました。
今回の記事から、しばらくはポジション情報を取得し、管理する記述について解説する内容が続きます。最終的には、ポジション管理を効率化できる関数をいくつか作り、それらを集約したポジション管理クラスを作ることを目的としています。
ポジション情報の取得とポジション管理について
MT5でEAを開発していくにあたって、ポジション情報を把握する事は重要になってきます。
- 保有しているポジションは買い(Buy)なのか、売り(Sell)なのか?
- ポジションの現在の損益はどうなっているのか?
- ポジションのロット数(Volume)はどれくらいなのか?
- 現在のストップロスやテイクプロフィットの値はいくつになっているのか?
・・・等のポジション情報を把握し、その情報をもとにして、EAの動作を決める事は多くあります。
従って、ポジション情報へのアクセスを容易にするための工夫を凝らすこと=ポジション情報に関する関数やクラスを作る事は、後々のEA開発を楽にしてくれるはずです。
ポジション情報取得の基本
ポジション情報の取得方法について、まずは基本を押さえましょう。
主流を占めるヘッジングシステムを前提に説明すると、 PositionSelectByTicket関数で、ポジションを選択し、PositionGet~関数で必要な情報を取得する という流れが基本となります。
例えば保有しているポジションは買い(Buy)なのか、売り(Sell)なのかを知りたい場合は↓
PositionSelectByTicket(ポジション番号);//ポジション番号でポジション選択
PositionGetInteger(POSITION_TYPE) ;//ポジションタイプを確認
といったような記述をすることになります。
ただ問題なのは、このやり方だと、取得したい情報に対して、どの関数を使い(PositionGetIntegerなのか、PositionGetDoubleなのか、PositionGetStringなのか・・・)、さらにはどの定数値を使えばいいのかをある程度覚えていなくてはいけないという点です。
把握できていない場合は、その都度公式リファレンスにアクセスして調べなくてはならず、それでは少々面倒です。
その面倒くささを解消するべく、どの情報を取得すれば良いのかすぐに把握できるように関数・クラス化していこうという訳です。
ポジション情報を管理するクラス作成へのロードマップ
それでは早速ポジションを管理するクラスを作っていきましょう。
※クラスについての概要は以下のリンクから確認・復習をお願いします。
- MQL5 EA講座 第48回「クラスについて1-クラスとは?-」
- MQL5 EA講座 第49回「クラスについて2 -クラスの使い方-」
- MQL5 EA講座 第50回「クラスについて3 -アクセス指定子-」
- MQL5 EA講座 第51回「クラスについて4 -派生クラス-」
- MQL5 EA講座 第52回「クラスについて5 -コンストラクタ-」
- MQL5 EA講座 第53回「クラスについて6 -仮想関数-」
ポジション管理クラス「OriginalCPositions」を宣言する
なにはともあれ、まずはポジション管理クラスを宣言します。
宣言は、OriginalCTradeクラスなどを作っているOriginalTrade.mqhファイルに行います。
//ポジション情報を管理するクラス------------------------------
class OriginalCPositions
{
};
クラス名を「OriginalCPositions」としました。現時点では空のクラスですが、これからメンバ変数・メンバ関数をどんどん追加していきます。
アクセスレベルprotectedのメンバを追加する
まずはアクセスレベルがprotectedのメンバをいくつか追加します
//ポジション情報を管理するクラス------------------------------
class OriginalCPositions
{
protected:
ulong BuyPosNum[];
ulong SellPosNum[];
ulong PosNum[];
int BuyPosCount;
int SellPosCount;
int TotalPosCount;
void GetPosInfo(ulong parMagicNum = 0);
int ChangeArraySize(ulong &parArray[]);
};
ulong型の配列 BuyPosNum[]、SellPosNum[]、PosNum[]は、
BuyPosNumは買いポジションのポジション番号を、
SellPosNumは売りポジションのポジション番号を、
PosNumは買いと売り両方のポジション番号を、それぞれ格納する予定です。
その下に続く、int型の3つの変数 BuyPosCount、SellPosCount、TotalPosCountについてですが、
BuyPosCount=買いポジションの数を、
SellPosCount=売りポジションの数を、
TotalPosCount=買いと売りの合計ポジション数を、それぞれ格納する予定になっています。
どうのような記述でポジション数を格納していくかはこの後説明します。
戻り値がvoid型のGetPosInfo関数ですが、役割としては後々作るpublic関数がアクセスして、必要なポジション情報を受け渡す処理を担う内部関数となります(今はなにをいっているかわからなくても大丈夫です。後で詳しく実装処理記述を見ていきます)。
戻り値がint型のChangeArraySize関数ですが、役割としては、ポジション情報が格納された配列の、配列サイズ変更を担う内部関数となります。
ヘッジングシステムは複数ポジションを持つことを許可されているシステムですから、ポジション数が増えれば配列サイズも変更しなくてはいけません。
引数には、BuyPosNum[]やSellPosNum[]などのポジション番号情報を格納した配列が渡される想定になっています。
<復習用リンク>
アクセスレベルpublicのメンバを追加する
続いては、アクセスレベルがpublicのメンバを追加していきます。
public:
int GetBuyPosCount(ulong parMagicNum);
int GetSellPosCount(ulong parMagicNum);
int GetTotalPosCount(ulong parMagicNum);
void GetBuyPosNum(ulong parMagicNum,ulong &parTickets[]);
void GetSellPosNum(ulong parMagicNum,ulong &parTickets[]);
void GetTotalPosNum(ulong parMagicNum,ulong &parTickets[]);
ポジション数を取得するpublic関数を宣言する
ポジション数を取得する為のpublic関数を3つ宣言します。ポジション数を取得ということなので、戻り値はint型になります。
GetBuyPosCount関数は、買いポジションの数を取得する為の関数です。
GetSellPosCount関数は、売りポジションの数を取得する為の関数です。
GetTotalPosCount関数は、買いと売りの合計ポジション数を取得する為の関数です。
ポジション番号を取得するpublic関数を宣言する
続いてポジション番号を取得するpublic関数を3つ宣言します。戻り値はvoid型とします。
GetBuyPosNum関数は、買いポジションのポジション番号を取得する関数です。
GetSellPosNum関数は、売りポジションのポジション番号を取得する関数です。
GetTotalPosNum関数は、買いと売り両方のポジション番号を取得する関数です。
これまで勉強してきた方であれば、ひょっとしたら「ポジション番号を取得する、という事はint型とかlong型じゃないの?なんでvoid型なの?」と思われた方もいるかもしれません。
それぞれの関数の詳しい内容は後程解説しますが、ポジション番号を取得するにあたっては、アクセスレベル=protectedセクションで定義したBuyPosNum[]やSellPosNum[]などの配列を利用します。
配列をreturn演算子によって、戻り値として返すことは出来ない為、各関数の第2引数に参照渡しで配列を渡すことによって情報を取得します。
※「そもそも、参照渡しって何だっけ?」という方は↓の記事をご覧ください。
配列を値渡しでなく、参照渡しにする必要性については第44回の中の「配列を参照渡しで渡す」セクションをご覧ください。
この後は、クラス内に宣言した関数について、処理記述を施していき、それを解説します。
GetPosInfo関数について
まず最初は、アクセスレベル=protectedのGetPosInfo関数についてです。
GetPosInfo関数の引数構成
GetPosInfo関数の引数構成は以下の通りです。
void GetPosInfo(ulong parMagicNum = 0)
仮引数のparMagicNumにはマジックナンバーが記述される想定です。初期値0を設けて省略可能にしてあります。
GetPosInfo関数の処理実装記述
GetPosInfo関数の処理実装記述は以下の通りです。
// ポジション情報を取得・振り分けする関数
void OriginalCPositions::GetPosInfo(ulong parMagicNumber = 0)
{
//ポジション数を格納する変数の初期化
BuyPosCount = 0;
SellPosCount = 0;
TotalPosCount = 0;
//ポジション番号格納配列の初期化とリサイズ
ArrayResize(BuyPosNum, 1);
ArrayInitialize(BuyPosNum, 0);
ArrayResize(SellPosNum, 1);
ArrayInitialize(SellPosNum, 0);
ArrayResize(PosNum, 1);
ArrayInitialize(PosNum, 0);
for(int i = 0; i < PositionsTotal(); i++)//未決済ポジション内を一番古いポジからチェック
{
ulong posNumber = PositionGetTicket(i);//ポジションプール内からインデックス順にポジション番号を取得
PositionSelectByTicket(posNumber);//ポジション選択
if(PositionGetInteger(POSITION_MAGIC) != parMagicNumber && parMagicNumber > 0) continue;
//当該EAからの注文でなければスキップ
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{ //買いポジションの数を1増加させる
BuyPosCount++;
int arrayIndex = ChangeArraySize(BuyPosNum);//配列をリサイズし、インデックスを戻り値として返す
BuyPosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//Buy ポジション番号情報を配列に格納
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{ ////売りポジションの数を1増価させる
SellPosCount++;
int arrayIndex = ChangeArraySizeSellPosNum);//配列をリサイズし、インデックスを戻り値として返す
SellPosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//Sell ポジション番号情報を配列に格納
}
//全体のポジション数を1増価させる
TotalPosCount++;
int arrayIndex = ChangeArraySize(PosNum);//配列をリサイズし、インデックスを戻り値として返す
PosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//全てのポジションの ポジション番号情報を配列に格納
}// for(int i = 0; i < PositionsTotal(); i++)の終端
//各種ポジカウント変数とポジション番号格納配列に必要な情報を振り分けて作業を終える
}
一つ一つ順を追って見ていきましょう
ポジション数を格納する変数の初期化
//ポジション数を格納する変数の初期化
BuyPosCount = 0;
SellPosCount = 0;
TotalPosCount = 0;
BuyPosCount、SellPosCount、TotalPosCountは
「アクセスレベルprotectedのメンバを追加する」のセクションで解説しました。ポジション数を格納する為の、アクセスレベルがprotectedのメンバ変数です。3つの変数の初期値を0とします。
ポジション番号を格納する配列の初期化とリサイズ
//ポジション番号格納配列の初期化とリサイズ
ArrayResize(BuyPosNum, 1);
ArrayInitialize(BuyPosNum, 0);
ArrayResize(SellPosNum, 1);
ArrayInitialize(SellPosNum, 0);
ArrayResize(PosNum, 1);
ArrayInitialize(PosNum, 0);
続いて、ポジション番号を格納する配列の初期化とリサイズを行います。
ArrayResize関数とArrayInitialize関数は、ともに第1引数に処理を施したい配列を指定して使う関数です。
一緒に使う事が多いですが、ArrayResize関数が配列のサイズ(いわば箱の数)を規定するものであるのに対し、ArrayInitialize関数は各配列の値(いわば箱の中身)を規定するものである点に注意が必要です。
ArrayResize(BuyPosNum, 1);
↑の表記は配列「BuyPosNum」の配列数を1にする、という決まりを作ったことを意味します。
ArrayInitialize(BuyPosNum, 0);
↑の表記は
各配列の値を0にする、という決まりを作ったことを意味します。
↑の例は、買いポジションのポジション番号を格納している配列BuyPosNumに対しての処理ですが、これをSellPosNum、PosNumに対しても行っているという訳です。
for文で未決済ポジション内を一番古いポジションからチェック
for(int i = 0; i < PositionsTotal(); i++)//未決済ポジション内を一番古いポジからチェック
{
ulong posNumber = PositionGetTicket(i);//ポジションプール内からインデックス順にポジション番号を取得
PositionSelectByTicket(posNumber);//ポジション選択
if(PositionGetInteger(POSITION_MAGIC) != parMagicNumber && parMagicNumber > 0) continue;
//当該EAからの注文でなければスキップ
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{ //買いポジションの数を1増価させる
BuyPosCount++;
int arrayIndex = ChangeArraySize(BuyPosNum);//配列をリサイズし、インデックスを戻り値として返す
BuyPosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//Buy ポジション番号情報を配列に格納
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{ ////売りポジションの数を1増価させる
SellPosCount++;
int arrayIndex = ChangeArraySize(SellPosNum);//配列をリサイズし、インデックスを戻り値として返す
SellPosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//Sell ポジション番号情報を配列に格納
}
値の取得に必要な変数や配列の初期化・リサイズが終わったので、次はfor文によるポジションチェックです。ポジションチェックにはPositionsTotal関数を使います。for文とPositionsTotal関数を使ったポジションチェックについての詳細は→コチラをご覧ください。
for文の内部記述1:ポジションの選択
for文中でポジション選択の記述を行います。
ポジション選択の記述には、PositionGetTicket関数とPositionSelectByTicket関数を使います。
PositionGetTicket関数はポジション番号を取得し、戻り値として返します。
ulong型の変数「posNumber」にfor文で取得したポジションインデックスを引数としたPositionGetTicket関数の戻り値を格納します。
ulong posNumber = PositionGetTicket(i);
//ポジションプール内からインデックス順にポジション番号を取得
晴れてポジション番号が取得出来たので、PositionSelectByTicket関数を使ってポジションを選択します。
PositionSelectByTicket関数の引数に変数「posNumber」を記述することによって、PositionGetInteger、PositionGetDouble,PositionGetStringどをつかって各ポジションに関する詳細情報が獲得できるようになります。
PositionSelectByTicket(posNumber);//ポジション選択
<復習用リンク>
for文、if文、else文、インデックス、PositionsTotal関数、インクリメントとデクリメント
PositionGetTicket関数、PositionSelectByTicket関数、ArrayResize関数、ArrayInitialize関数
PositionGetInteger関数、PositionGetDouble関数,PositionGetString関数
for文の内部記述2:ポジションの選択
if(PositionGetInteger(POSITION_MAGIC) != parMagicNumber && parMagicNumber > 0) continue;
//当該EAからの注文でなければスキップ
PositionGetInteger関数の引数に定数値「POSITION_MAGIC」を記述することによって、ポジションのマジックナンバーが取得できます。取得出来た値と、今作っているこのGetPosInfo関数の仮引数「parMagicNumber」に格納されている値が、一致していればチェック中のポジションは、運用中のEAから注文されたという事を意味します。
もし一致していなければ、そのポジションは運用中のEAから注文されたものではないので、continueキーワードにより処理をスキップして次のポジションチェックに移ります。
<復習用リンク>
for文の内部記述3:チェック中のポジションが「買い」だった場合の処理
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{ //買いポジションの数を1増加させる
BuyPosCount++;
int arrayIndex = ChangeArraySize(BuyPosNum);//配列をリサイズし、インデックスを戻り値として返す
BuyPosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//Buy ポジション番号情報を配列に格納
}
ポジションが運用中のEAから注文されたものだと確定したら、次はチェック中のポジションが「買い」だった場合の処理記述です。
PositionGetInteger関数の引数に定数「POSITION_TYPE」を記述して、ポジションタイプを取得します。
戻り値がPOSITION_TYPE_BUYだった場合、買いポジションということになるので、以下の処理を行います。
- 買いポジションの数を格納する変数「BuyPosCount」の値を1増加させる(インクリメント)
- ChangeArraySize関数によって、配列をリサイズし、配列のインデックスを戻り値として返す。
- ChangeArraySize関数を使って取得したインデックスを配列BuyPosNum[]内に記述し、そこにPositionGetInteger関数の定数値「POSITION_TICKET」を使って取得したポジション番号を代入していく。
以上の処理によって、買いポジションの数と、買いポジションの各ポジション番号が、変数「BuyPosCount」と配列「BuyPosNum」にそれぞれ格納することができました。
これと同じようなことを売りポジションの場合も行います
※ChangeArraySize関数は「アクセスレベルprotectedのメンバを追加する」のところでも書いたように、ポジション情報格納配列の、配列サイズ変更を行うアクセスレベル=protectedの内部関数となります。
・・・ただし、ChangeArraySize関数はまだ処理実装記述が済んでいないので、このままコンパイルしてもエラーになってしまいます。
次回の講座記事で、ChangeArraySize関数の処理実装記述は解説予定です。
今はポジション情報が格納された配列の、配列サイズ変更処理を担う内部関数として作られる予定である、ということだけ押さえておいてください。
for文の内部記述4:チェック中のポジションが「売り」だった場合の処理
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{ ////売りポジションの数を1増価させる
SellPosCount++;
int arrayIndex = ChangeArraySize(SellPosNum);//配列をリサイズし、インデックスを戻り値として返す
SellPosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//Sell ポジション番号情報を配列に格納
}
PositionGetInteger関数の引数に定数「POSITION_TYPE」を記述します。
戻り値がPOSITION_TYPE_SELLだった場合、売りポジションということになるので、以下の処理を行います。
- 売りポジションの数を格納する変数「SellPosCount」の値を1増加させる(インクリメント)
- ChangeArraySize関数よって、配列をリサイズし、配列のインデックスを戻り値として返す。
- ChangeArraySize関数を使って取得したインデックスを配列SellPosNum[]内に記述し、そこにPositionGetInteger関数の定数値「POSITION_TICKET」を使って取得したポジション番号を代入していく。
以上の処理によって、売りポジションの数と、売りポジションの各ポジション番号が変数「SellPosCount」と配列「SellPosNum」に格納することができました。
※ChangeArraySize関数の処理実装記述が済んでいないので、ChangeArraySize関数がまだ機能せず、コンパイルしてもエラーになってしまうのは買いポジションの時と事情は同じです。
for文の内部記述5:「買い」と「売り」両方を合計したポジション数とポジション番号を格納
//全体のポジション数を1増価させる
TotalPosCount++;
int arrayIndex = ChangeArraySize(PosNum);//配列をリサイズし、インデックスを戻り値として返す
PosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//全てのポジションの ticket情報を配列に格納
}// for(int i = 0; i < PositionsTotal(); i++)の終端
//各種ポジカウント変数とポジション番号格納配列に必要な情報を振り分けて作業を終える
}
最後に「買い」と「売り」両方を合計したポジション数とポジション番号を格納するための記述を行います。主な記述処理は以下の通りです。
- 全体のポジション数を格納する変数「TotalPosCount」の値を1増加させる(インクリメント)
- ChangeArraySize関数によって、配列をリサイズし、配列のインデックスを戻り値として返す。
- ChangeArraySize関数を使って取得したインデックスを配列PosNum[]内に記述し、そこにPositionGetInteger関数の定数値「POSITION_TICKET」を使って取得したポジション番号を代入していく。
これで「買い」と「売り」両方を合計したポジション数とポジション番号を格納するための記述は終了です。
同時に、for文の{}も終了し、GetPosInfo関数全体の{}も閉じられGetPosInfo関数の処理実装記述は完成です。
※くどいようですが、ChangeArraySize関数はまだ機能していません。
現時点での「OriginalCPositions」クラスの全体記述
今回はポジションを管理する為のクラス作成第1回目として、まずは「OriginalCPositions」を宣言し、必要なprotectedのメンバ、 publicのメンバも宣言しました。そしてそのうちのGetPosInfo関数について処理実装記述を紹介し、追加しました。現時点での「OriginalCPositions」クラスの全体記述は以下の通りです。
//ポジション情報を管理するクラス------------------------------
class OriginalCPositions
{
protected:
ulong BuyPosNum[];
ulong SellPosNum[];
ulong PosNum[];
int BuyPosCount;
int SellPosCount;
int TotalPosCount;
void GetPosInfo(ulong parMagicNum = 0);
int ChangeArraySize(ulong &parArray[]);
public:
int GetBuyPosCount(ulong parMagicNum);
int GetSellPosCount(ulong parMagicNum);
int GetTotalPosCount(ulong parMagicNum);
void GetBuyPosNum(ulong parMagicNum,ulong &parTickets[]);
void GetSellPosNum(ulong parMagicNum,ulong &parTickets[]);
void GetTotalPosNum(ulong parMagicNum,ulong &parTickets[]);
};
// ポジション情報を取得・振り分けする関数
void OriginalCPositions::GetPosInfo(ulong parMagicNumber = 0)
{
//ポジション数を格納する変数の初期化
BuyPosCount = 0;
SellPosCount = 0;
TotalPosCount = 0;
//ポジション番号格納配列の初期化とリサイズ
ArrayResize(BuyPosNum, 1);
ArrayInitialize(BuyPosNum, 0);
ArrayResize(SellPosNum, 1);
ArrayInitialize(SellPosNum, 0);
ArrayResize(PosNum, 1);
ArrayInitialize(PosNum, 0);
for(int i = 0; i < PositionsTotal(); i++)//未決済ポジション内を一番古いポジからチェック
{
ulong posNumber = PositionGetTicket(i);//ポジションプール内からインデックス順にポジション番号を取得
PositionSelectByTicket(posNumber);//ポジション選択
if(PositionGetInteger(POSITION_MAGIC) != parMagicNumber && parMagicNumber > 0) continue;
//当該EAからの注文でなければスキップ
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{ //買いポジションの数を1増加させる
BuyPosCount++;
int arrayIndex = ChangeArraySize(BuyPosNum);//配列をリサイズし、インデックスを戻り値として返す
BuyPosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//Buy ポジション番号情報を配列に格納
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{ ////売りポジションの数を1増加させる
SellPosCount++;
int arrayIndex = ChangeArraySize(SellPosNum);//配列をリサイズし、インデックスを戻り値として返す
SellPosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//Sell ポジション番号情報を配列に格納
}
//全体のポジション数を1増加させる
TotalPosCount++;
int arrayIndex = ChangeArraySize(PosNum);//配列をリサイズし、インデックスを戻り値として返す
PosNum[arrayIndex] = PositionGetInteger(POSITION_TICKET);//全てのポジションの ポジション番号情報を配列に格納
}// for(int i = 0; i < PositionsTotal(); i++)の終端
//各種ポジカウント変数とポジション番号格納配列に必要な情報を振り分けて作業を終える
}
※「OriginalCPositions」クラスもこれまでと同様に、OriginalTrade.mqhファイルに記述していきますが、記述が煩雑になるので、今回の記事では「OriginalCPositions」とそのメンバに関する記述のみにしてあります。
<復習用リンク>
これまでのOriginalTrade.mqhファイル全体の記述内容
↑OriginalCTradeクラスのメンバや、これまでに追加してきた独立関数の記述が確認できます。
今回は一旦以上とさせていただきます。次回以降、OriginalCPositionsに追加する関数を紹介・解説していきたいと思います。
最後までお読みいただきありがとうございました。
コメント