発注クラスの作成 P97
OrderSend()関数を使用して、すべてのタイプの注文を発注、変更、クローズする方法を学びました。ここで、注文、変更、およびクローズするための再利用可能なクラスを作成します。注文発注クラスは、MqlTradeRequest変数の記入、注文の発注、MqlTradeResult変数のチェックを行って注文結果を確認します。クラスに基づいてオブジェクトを宣言し、関連するパラメータをオブジェクトの発注関数に渡すだけです。
まず、発注クラスを保持するためのインクルードファイルを作成する必要があります。インクルードファイルはデフォルトで\MQL5\Includeディレクトリに保存されます。インクルードファイルはMq15Bookという名前のサブフォルダに保存します。http://www.expertadvisorbook.comからソースコードをダウンロードすると、この本で使用されているインクルードファイルは\MQL5\Include\Mql5Bookフォルダにあります。
プログラムファイルの#includeディレクトリはMqlBookパスを参照します。最初に作成するインクルードファイルの名前はTrade.mqhです。したがって、このファイルをEAプログラムに含めるための#includeディレクティブは次のようになります。
#include <Mql5Book\Trade.mqh>
\MQL5\Include\Mq15BookフォルダにTrade.mqhという名前の空のインクルードファイルがあると仮定して、トレードクラスを作成しましょう。この本で使用するクラス名の規則は大文字の「C」の後に大文字のクラス名が続きます。トレードクラスの名前はCTradeになります。
一部のクラス名はMQL5標準ライブラリのクラス名と同じになることに注意してください。この本ではMQL5標準ライブラリを使用しないため、MQL5リファレンスにある一同のクラス名は、この本にあるクラスではなく、標準ライブラリを参照していることに注意してください。
以下で作成しているCTradeクラスはネッティングポジションアカウントに成行注文を出すために使用されることに注意してください。この章の後半で、ヘッジ口座に成行注文を出すために使用される派生クラスを作成します。指値注文を出すプロセスは、アカウントの種類に関係なく同じです。第13章で指値注文クラスを作成します。
CTradeクラス P97
トレードクラスのクラス宣言を作成することから始めましょう。ほとんどのクラス関数で使用されるためMqlTradeRequestおよびMqlTradeResultオブジェクトを宣言に追加します。
class CTrade
{
protected:
MqlTradeRequest;
public:
MqlTradeResult result;
};
プログラマーがクラス外のリクエストオブジェクトメンバーにアクセスできる理由がないため、リクエストオブジェクトを保護しました。ただし、任意の派生クラスで要求オブジェクトを継承できるようにする必要があります。これは、後でヘッジ注文を出すサブクラスを作成するときに行います。結果オブジェクトはパブリックです。これは、プログラマーがクラス外の結果オブジェクトメンバーにアクセスして、最後の取引の結果を判断する場合があるためです。
OpenPosition()関数 P98
当初のクラス関数は成行注文を出します。未決注文ではなく市場ポジションを開くため、関数にOpenPosition()という名前を付けます。OpenPosition()関数をpublicではなくprotectedにします。この章の後半で、特定の種類の注文を出すパブリックヘルパー関数を作成します。
クラス宣言内のOpenPosition()関数宣言は次のとおりです。
class CTrade
{
protected:
MqlTradeRequest request;
bool OpenPosition(string pSymbol, ENUM_ORDER_TYPE pType, double pVolume,
double pStop = 0, double pProfit = 0, string pComment = NULL);
public:
MqlTradeResult result;
};
OpenPosition()関数の戻り値の型はboolです。注文が正常に行われた場合、関数はtrueを返し、そうでない場合はfalseを返します。この関数には6つのパラメータがあります。これらは、前の章のリクエストパラメータとして認識されます。すべての関数パラメータ変数は、小文字の“p”(“パラメータ”の略)で始まります。これは、変数が関数に対してローカルな関数パラメータであることを示します。
OpenPosition()関数のパラメータの説明は次のとおりです。
pSymbol - 成行注文を開始する金融証券のシンボル。
pType - 注文タイプ。ENUM_ORDER_TYPE列挙の値を受け入れます。
pVolume - 成行注文のロット数。
pStop - ストップロス価格。オプション。
pProfit - テイクプロフィット価格。オプション。
pComment - 注文コメント。オプション。
必要なパラメータは、シンボル、注文タイプ、および取引量のみです。ストップロスとテイクプロフィットのパラメータが提供されていますが、注文後にストップロスとテイクプロフィットをポジションに追加します。これについては、次の章で説明します。
次に、関数自体を定義します。これは、クラス宣言の下で行われます。
bool CTrade:OpenPoshition(string pSymbol, ENUM_ORDER_TYPE pType, double pVolume,
double pStop = 0, double pProfit = 0, string pComment = NULL)
{
}
OpenPosition()の前のCTrade::に注意してください。二重コロン(::)はスコープ解決演算子であり、CTradeクラスの一部としてOpenPosition()関数を識別します。クラス名とスコープ解決演算子は、クラス関数識別子の前に存在する必要があります。
空の関数にコードを追加してみましょう。まず、リクエストオブジェクト変数を入力します。
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;
request.symbol = pSymbol;
request.type = pType;
request.sl = pStop;
request.tp = pProfit;
request.comment = pComment;
request.deviation = deviation;
request.type_filling = fillType;
request.magic = magicNumber;
これは前の章でおなじみのはずです。TRADE_ACTION_DEALは、取引操作を成行注文として定義します。関数パラメータは、それぞれのリクエスト変数に割り当てられます。偏差、type_fillingおよびマジック変数については、この章の後半で説明します。
次に取引量を決定する必要があります。この関数の目的は、指定されたタイプ(買いまたは売り)とボリュームのネットポジションを開くことです。現在反対方向に開いているポジションがある場合は、注文を開くときにそれを閉じる必要があります。例えば、1ロットの買いポジションをオープンしていて、現在オープンしている1ロットの売りポジションがある場合1ロットのネット買いポジションになるには、2ロットの買いポジションを開く必要があります。
これを行うには、選択したシンボルで現在開いているポジションがあるかどうか、およびどの方向にあるかを判断する必要があります。位置が存在する場合は、PositionSeLect()関数を使用して位置を選択します。ポジションが存在する場合、現在のポジションの注文タイプとボリュームを取得します。
double positionVol = 0;
long poshitionType = WRONG_VALUE;
if(PositionSelect(pSymbol) == true)
{
positionVol = PositionGetDouble(POSHITION_VOLUME);
positionType = PositionGetInteger(POSITION_TYPE);
}
まず、現在のポジションのボリュームを保持するためにpositionVoという名前のdouble変数を宣言します。positionTypeという名前のlong変数を宣言して、位置の型を保持します。これは定数WRONG_VALUEに初期化されます。WRONG_VALUE定数の整数値は‐1です。これにより、変数がニュートラルな状態に設定されます。PositionTypeを0に初期化しない理由は0が有効な位置タイプの値であるためです。
PositionSelect()関数はpSymbol変数に含まれる銘柄にオープンポジションがあるかどうかを照会します。ポジションが開いている場合、PositionSelect()関数はtrueの値を返し、処理のために現在のポジションを選択します。PositionSelect()がtrueを返す場合、括弧内の式を実行します。
POSITION_VOLUMEパラメータを指定したPositionGetDouble()関数は現在の位置ボリュームを返し、それをpositionVol変数に割り当てます。POSITION_TYPEパラメータを指定したPositionGetInteger()関数は位置タイプを返し、それをPositionType変数に割り当てます。
ポジションタイプはPOSITION_TYPE_BUYまたはPOSITION_TYPE_SELLのいずれかのENUM_POSITION_TYPE列挙の値です。POSITION_TYPE_BUYの整数値は0であるため、上記のpositionType変数をWRONG_VALUEまたは‐1に初期化しました。
現在のポジションに関する情報が得られたので、適切な取引量を計算する必要があります。現在のポジションが買いポジションで、pType変数で識別される成り行き注文タイプが売り注文の場合(または、その逆)、現在のポジションのボリュームをpVolume変数の値に加算して合計トレードボリュームを取得する必要があります。
取引量:
if((pType == ORDER_TYPE_BUY && positionType == POSITION_TYPE_SELL)
|| (pType == ORDER_TYPE_SELL && positionType == POSITION_TYPE_BUY)
{
request.volume = pVolume + positionVol;
}
else request.volume = pVolume;
if演算子内には、OR演算(||)で区切られた2つのブールAND演算(&&)があります。成行注文タイプ(pType変数)が買い注文(ORDER_TYPE_BUY)で、ポジションタイプ(positionType変数)が売りポジション(POSITION_TYPE_SELL)の場合、pVolumeパラメータの値をpositionVol変数の値に加算して合計取引量を取得します。これはrequest.volume変数に割り当てられます。成行注文タイプが売り注文で、ポジションタイプが買いポジションの場合も同様です。
いずれの場合も、既存のポジションは決済され、結果はpType変数によって示されるタイプのネットポジションになります。開いているポジションが無い場合、または現在開いているポジションが成行注文と同じタイプの場合、pVolumeの値をrequest.volume変数に割り当てるだけです。
注文を出す前に最後に行う必要があるのは、現在の市場価格をrequest.price変数に割り当てることです。これは、注文タイプが買いか売りかによって異なります。買い注文の場合、注文の始値は現在の売値になります。売り注文の場合、注文の始値は現在の買い値になります。
if(pType == ORDER_TYPE_BUY) request.price = SymbolInfoDouble(pSymbol, SYMBOL_ASK);
else if(pType == ORDER_TYPE_SELL) request.price = SymbolInfoDouble(pSymbol, SYMBOL_BID);
pType変数が買い注文を示す場合、SymbolInfoDouble()関数はpSymbol変数によって示される銘柄の現在の売値を取得します。pTypeが売り注文を示す場合、SymbolInfoDouble()は現在の売値を取得します。
あとは、OderSend()関数を呼び出して成行注文を出すだけです。
OrderSend(request, result);
j
エラー処理 P101
取引エラーが発生した場合、ユーザーが適切な措置をとれるように、ユーザーに通知する必要があります。また、トラブルシューティングに役立つように、関連する取引情報も記録します。一部のエラー状態(リクオート、タイムアウト、接続エラーなど)は、発注を再試行するだけで自動的に修正されます。
最初に行う必要があるのは、サーバーからの戻りコードを確認することです。覚えていると思いますが、OrderSend()関数に渡すMqlTradeResultオブジェクトには、サーバーからの戻りコードがrecode変数に含まれています。
リターンコード10008(TRADE_RETCODE_PLACED)および10009(TRADE_RETCODE_DONE)は、取引が正常に行われたことを示します。サーバーからその他のコードが返された場合は、エラー状態である可能性が高いことを示しています。リターンコードをチェックする関数を作成し、リターンコードがエラー状態または正常な注文配置を示しているかどうかを評価します。
最初に、注文が正常に行われたかどうかを判断するための戻り値として使用される列挙を作成します。この列挙は、Trade.mqhファイルで定義されます。
Enum ENUM_CHECK_RETCODE
{
CHECK_RETCODE_OK,
CHECK_RETCODE_ERROR,
};
定数CHECK_RETCODE_OKは取引操作が成功したことを示し、定数CHECK_RETCODE_ERRORは取引操作が失敗したことを示します。
次に、リターンコードをチェックする関数を作成します。CheckReturnCode()関数は、switch-case演算子を使用して、リターンコード定数のリストを評価します。関数に渡された戻りコードに応じて、switch演算子は上記のENUM_CHECK_RETCODE列挙から定数の1つを返します。
これがCheckReturnCode()関数です。これはTrade.mqhファイルに配置されます。
int CheckReturnCode(uint pRetCode)
{
int status;
switch(pRetCode)
{
case TRADE_RETCODE_DONE:
case TRADE_RETCODE_DONE_PARTIAL:
case TRADE_RETCODE_PLACED:
case TRADE_RETCODE_NO_CHANGES:
status = CHECK_RETCODE_OK;
break;
default: status = CHECK_RETCODE_ERROR;
}
return(status);
}
CheckReturnCode()関数は、1つのパラメータ(pRetCodeという名前のuint変数)を受け入れます。これには、MqlTradeResultオブジェクトからの戻りコードが含まれています。
ENUM_CHECK_RETCODE列挙からの値を保持する。statusという名前の整数変数を宣言します。switch演算子は、pRetCodeの値をcase演算子によって示される戻りコード定数のリストと比較します。pRetCodeの値がいずれかのケース演算子と一致する場合、break演算子に到達するか、switch演算子が終了するまで、プログラムの実行が続行されます。
まず、注文が正常に行われたかどうかを確認します。注文が正常に行われたか、エラーではない状態であることを示すいくつかのリターンコードを追加しました。pRetCode値がこのリストのいずれかの戻りコードと一致する場合、status変数にはCHECK_RETCODE_ERROR定数が割り当てられます。statusの値がプログラムに返され、関数が終了します。
成行注文関数に戻り、CheckReturnCode()関数を追加しましょう。
int checkCode = 0;
OrderSend(request, result);
checkCode = CheckReturnCode(result, retcode);
checkCodeという整数変数を宣言します。これは、CheckReturnCode()関数の戻り値を保持します。OrderSdend()関数を呼び出して注文を出します。次に、result.retcode変数をCheckReturnCode()関数に渡し、戻り値をcheckCodeに格納します。
次に、checkCode変数の値をチェックして、リターンコードが取引の成功またはエラー状態を示しているかどうかを判断します。エラーが発生した場合、アラートがユーザーに表示されます。
if(checkCode = CHECK_RETCODE_ERROR)
{
string errDesc = TradeServerReturnCodeDescription(result, retcode);
Alert(“Open market order: Error ”, result.retcode, ” - ”, errDesc);
LogTradeRequset();
break;
}
checkCodeにCHECK_RETCODE_ERRORの値が含まれている場合、TradeServerReturnCodeDescription()関数からのエラーの説明を取得します。この関数は、一部のバージョンのMetaTrader5とともにインストールされるerrordescription.mqhファイルに含まれていますが、便宜上、ソースコードのダウンロードに含まれています。このファイルをプロジェクトに追加するには、この#includeディレクティブをTrade.mqhファイルの先頭に配置します。
#include <errordescription.mqh>
TradeServerReturnDescription()関数は、エラーコードの説明を含む文字列を返します。これはerrDesc変数に格納されます。Alert()関数は、リターンコードと説明を含むアラートポップアップウィンドウを表示します。
図10.1-OpenPosition()関数からのエラーメッセージを表示するAlertダイアログ。
LogTradeRequest()関数は、トラブルシューティングの目的で、リクエストオブジェクトの結果オブジェクトの内容をログに出力するだけです。独自の取引システムを開発しているときに取引エラーが発生した場合、この関数の出力を詳しく調べると、コーディングエラーを特定するのに役立ちます。
void Ctrade : : LogTradeRequest()
{
Print(“MqlTradeRequest – action:”, request.action,”, comment:”,request.comment,
“,deviation:”,request.deviation,”,expiration:”,request.expiration,
“,magic:”,request.magic,”,order:”,request.order,”,position:”,request.position,
“,position_by:”,request.position_by,”,price:”,request.price,”,ls:”,request.sl,
“,stoplimit:”,request.stoplimit,”,symbol:”,request.symbol,”,tp:”,request.tp,
“,type:”,request.type,”,type_filling:”,request.type_filling,
“,type_time:”,request.type_time,”,volume:”,request.volume);
Print(“MqlTradeResult – ask:”,result.ask,”,bid:”,result.bid,
“,comment:”,result.comment,”,deal:”,result.deal,”,order:”,result.order,
“,price:”,result.price,”,request_id:”,result.request_id,
“,retcode:”,result.retcode,”,retcode_external:”,result.retcode_external,
“,volume:”,result.volume);
}
次に、トラブルシューティングのために注文情報をログに出力します。エラーが発生した場合、この情報が役立つ場合があります。注文タイプ、チケット番号、リターンコードと説明、および注文価格と現在の買値と売値を出力します。
注文タイプの定数をチェックし、注文を説明する説明的な文字列を返す関数を作成しました。
タイプ:
string CheckOrderType(ENUM_ORDER_TYPE pType)
{
string orderType;
if(pType == ORDER_TYPE_BUY) orderType = “buy”;
else if(pType == ORDER_TYPE_SELL) orderType = “sell”;
else if(pType == ORDER_TYPE_BUY_STOP) orderType = “buy stop”;
else if(pType == ORDER_TYPE_BUY_LIMIT) orderType = “buy limit”;
else if(pType == ORDER_TYPE_SELL_STOP) orderType = “sell stop”;
else if(pType == ORDER_TYPE_SELL_LIMIT) orderType = “sell limit”;
else if(pType == ORDER_TYPE_BUY_STOP_LIMIT) orderType = “buy stop limit”;
else if(pType == ORDER_TYPE_SELL_STOP_LIMIT) orderType = “sell stop limit”;
else if(pType == “invalid order Type”;
return(orderType);
}
この関数はTrade.mqhファイルで定義されています。これは、プログラムのどこでも使用できるパブリック関数です。pTypeパラメータには、ENUM_ORDER_TYPE列挙の値が含まれます。if-else演算子を使用してpTypeの値を評価し、適切な文字列値をorderType文字列変数に割り当てます。その後、文字列がプログラムに返されます。ここでswitch-case演算子を使用することもできましたが、この関数を単純にしたかったので、CheckReturn()関数で必要だったswitch演算子の高度な機能は必要ないことに注意してください。
CheckOrderType()およびTreadServerReturnCodeDescription()関数を使用して、注文タイプとリターンコードを説明する文字列を取得します。次に、Print()関数を呼び出して、取引情報をログに出力します。
string orderType = CheckOrderType(pType);
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Print(“Open”, orderType,”order #”,result.order,”: “,result.retcode,” – “,errDesc,
“,volume:”,result.volume,”,price:”,result.price,”,Bid:”,result.bid,
“,Ask:”,result.ask);
エキスパートログを調べると、成功した買い成り行き注文のPrint()関数の出力は次のようになります。
Open Buy order #105624: 10009 – Request is completed, Volume: 0.1, price: 1.31095,
Bid: 1.31076, Ask: 1.31095
最後に、戻りコードを評価して、真または偽の値をプログラムに返すかどうかを決定します。注文が正常に行われた場合、チャートにコメントが出力されます。
if(checkCode == CHECK_RETCODE_OK)
{
Comment(orderType,” position opened at “,
result.price,” on “,pSymbol);
return(true);
}
else return(false);
図10.2-Comment()関数によって配置されたチャートコメント
checkCode変数にCHECK_RETCODE_OKの値が含まれている場合、チャートにコメントを出力し、取引操作が成功したことを示すtrueの値を返します。それ以外の場合は、falseの値を返します。図10.2は、正常な注文完了に配置されたチャートコメントを示しています。
エラー時は再試行 P106
リターンコードに応じて取引操作を再試行することで、EAの信頼性と堅牢性を高めることができます。リクオート、オフクオート、サーバー接続の問題などの一部のエラー状態は、取引操作を再試行するだけで自動修正される可能性があります。
取引操作を再試行するかどうかを決定するために、CheckReturnCode()関数を変更して3番目の値を返します。ENUM_CHECK_RETCODE列挙でCHECK_RETCODE_RETRYという名前の3番目の定数を定義します。これは、リターンコードによって定義されたエラー状態が潜在的に回復可能であることを示します。
enum ENUM_CHECK_RETCODE
{
CHECK_RETCODE_OK,
CHECK_RETCODE_ERROR,
CHECK_RETCODE_RETRY
};
この定数をCheckReturnCode()関数に追加しましょう。
int CheckReturnCode(uint pRetCode)
{
int status;
switch(pRetCode)
{
case TRADE_RETCODE_ REQUOTE:
case TRADE_RETCODE_ CONNECTION:
case TRADE_RETCODE_ PRICE_CHANGED:
case TRADE_RETCODE_ TIMEOUT:
case TRADE_RETCODE_ PRICE_OFF:
case TRADE_RETCODE_ REJECT:
case TRADE_RETCODE_ ERROR:
status = CHECK_RETCODE_RETRY;
break;
case TRADE_RETCODE_DONE:
case TRADE_RETCODE_DONE_RARTIAL:
case TRADE_RETCODE_PLACED:
case TRADE_RETCODE_NO_CHANGES:
status = CHECK_RETCODE_OK;
break;
default: status = CHECK_RETCODE_ERROR;
}
return(status);
}
太字で強調表示されたケース演算子に割り当てられた7つの戻りコード定数があります。これらのエラーは、自動修正される可能性があると判断したエラーです。pRetCodeパラメータがこれらの定数のいずれかに一致する場合、CHECK_RETCODE_RETRY定数がステータス変数に割り当てられます。
成行注文機能を変更して、注文再試行ロジックを追加する必要があります。do-whileループを使用して、注文の再試行機能を処理します。更新されたコードは太字で強調表示されています。
int retryCount = 0;
int checkCode = 0;
do
{
if(pType == ORDER=TYPE_BUY)request.price = SymbolInfoDouble(pSymbol,SYMBOL_ASK);
else if(pType == ORDER_TYPE_SELL)request.price = SymbolInfoDouble(pSymbol,SYMBOL_BID);
OrderSend(request,result);
checkCode = CheckreturnCode(result.retcode);
if(checkCode == CHECK_RETCODE_OK) break;
else if(checkCode == CHECK_RETCODE_ERROR)
{
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Alert(“Open market order: Error”, result.retcode,” – ”,errDesc);
LogTradeRequest();
Break;
}
else
{
Print(“Server error detected, retrying…”);
Sleep(RETRY_DELAY);
retryCount++;
}
}
while(retryCount < MAX_RETRIES);
まず、retryCountという整数変数を宣言します。これは、発注を再試行した回数を追跡するカウンターになります。前のセクションの注文配置とエラー処理コード全体がdo-whileループ内に配置されます。
doループ内で最初に行うことは、現在の買値または売値を取得することです。これらの価格は取引操作間で変化する可能性があるため、OrderSend()関数を呼び出す前に常に最新の価格を取得します。
注文して、CheckReturnCode()関数を呼び出した後、checkCode変数を調べて、実行するアクションを決定します。取引が成功し、checkCodeにCHECK_RETCODE_OK定数が含まれている場合、doループから抜け出します。それ以外の場合、checkCodeにCHECK_RETCODE_ERROR定数が含まれている場合は、アラートを表示してループから抜け出します。
CheckCode変数にCHECK_RETCODE_RETRY定数が含まれている場合、else演算子のコードが実行されます。エラーが検出されたことを示すメッセージをログに出力し、プログラムは取引操作を再試行します。Sleep()関数は、RETRY_DELAY定数によって示されるミリ秒単位の値を使用して、プログラムの実行を一時的に停止します。次に、retryCount変数が1増加します。
ループの最後のwhile演算子は、retryCount変数をチェックし、その値をMAX_RETRIESという名前の定数と比較します。MAX_RETRIES定数は、発注を再試行する最大回数です。retryCountがMAX_RETRIES未満の場合、do-whileループが再度実行されます。それ以外の場合、ループは終了します。
RETRY_DELAYおよびMAX_RETRIES定数は、Trade.mqhインクルードファイルの先頭で定義されています。これらを妥当なデフォルト値に設定します。必要に応じてこれらを変更するか、ユーザーが変更できる入力変数で置き換えることができます。
#define MAX_RETRIES 5
#define RETRY_DELAY 3000
MAX_RETRIES定数は、取引操作を5回まで再試行するように設定されています。RETRY_DELAYは3000ミリ秒、つまり3秒です。必要に応じてこれらを変更し、このファイルを使用するEAを再コンパイルできます。
何度か再試行しても取引操作が成功しなかった場合は、ユーザーに警告する必要があります。このコードは、do-whileループの直後、注文情報をログに出力する前に実行されます。
if(retryCount >= MAX_RETRIES)
{
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Alert(“Max retries exceeded: Error “, result.retcode,” – “, errDesc);
}
retryCountがMAX_RETRIES以上の場合、ユーザーの画面にアラートを送信し、それを示します。発注が失敗し、最後の既知のエラーコードと説明が表示されます。
これで、CTradeクラスのOpenPosition()関数が完成しました。\MQL5\Include\Mq15Book\Trade.mqhファイルで関数の完全なコードを表示できます。
偏差値・マジックナンバーそしてフィルタイプ P109
ブローカーが即時実行タイプを使用している場合、偏差をポイントで指定できます。偏差は、発注時に許容される最大スリッページです。現在の市場価格が要求された価格から指定されたポイント数を超えて離れている場合、注文は行われません。
ヘッジ口座で取引している場合、またはネッティング口座取引システムを使用して複数のシンボルを取引する場合で、異なるEAによる取引を区別する方法が必要な場合はマジックナンバー。マジックナンバーはユーザーが設定した任意の番号であり、EAによって出されたすべての注文に割り当てられます。これは、ヘッジ口座の注文を管理する際に重要になります。
偏差とマジックナンバーの値を設定するために使用されるいくつかのクラス変数と関数をCTradeクラスに追加しましょう。プログラマーが変更したい場合に備えて、塗りつぶしタイプを設定する関数も追加します。
class CTrade
{
protected:
ulong magicNumber;
ulong deviation;
ENUM_ORDER_TYPE_FILLING fillType;
public:
void MagicNumber(ulong pMagic);
void Deviation(ulong pDeviation);
void FillType(ENUM_ORDER_TYPE_FILLING pFill);
};
CTradeクラスの外でこれらの値にアクセスできるようにする必要がないため、magicNumber、deviation、およびfillType変数は保護されたものとして設定されます。MagicNumber()、Deviation()、およびFillType()関数は、ユーザーがプログラムの初期化時にこれらの値を設定できるパブリック関数です。
ユーザーが偏差をポイントで設定できる機能から始めましょう。CTrade :: Deviation()関数は、発注関数で使用する偏差クラス変数の値を設定します。
void CTrade :: Deviation(ulong pDeviation)
{
deviation = pDeviation;
}
CTrade :: MagicNumber()関数は、発注関数で使用するためのmagicNumberクラス変数の値を設定します。
void CTrade :: MagicNumber(ulong pMagic)
{
magicNumber = pMagic;
}
サーバーのデフォルトとは異なる塗りつぶしタイプを使用する場合は、塗りつぶしタイプを設定することもできます。
void CTrade :: FillType(ENUM_ORDER_TYPE_FILLING pFill)
{
fillType = pFill;
}
塗りつぶしの種類はENUM_ORDER_TYPE_FILLING列挙の定数を使用して指定する必要があります。EAで上記の関数を使用して、偏差、マジックナンバー、およびフィルタイプを設定する方法は次のとおりです。値を設定する必要があるのは一度だけなので、OnInit()イベントハンドラは値を設定するのに理想的な場所です。
#include <Mql5Book\Trade.mqh>
CTrade Trade;
//入力変数
input ulong Deviation = 300;
input ulong MagicNumber = 12345;
//OnInitイベントハンドラ
int OnInit()
{
Trade.Deviation(Deviation);
Trade.MagicNumber(MagicNumber);
return(0);
}
ユーザーが偏差とマジックナンバーを設定するための2つの入力変数があります。OnInit()イベントハンドラ内で、TradeMagicNumber()関数を呼び出し、入力変数DeviationとMagicNumberによって設定された値を渡します。上記の例では、注文タイプをReturnに設定しています。
OpenPosition()関数の使用 P111
買いポジションと売りポジションを配置するヘルパー関数をいくつか作成して、注文プロセスを簡素化しましょう。
class CTrade
{
public:
bool Buy(string pSymbol, double pVolume, double pStop = 0,
double pProfit = 0, string pComment = NULL);
bool Sell(string pSymbol, double pVolume, double pStop = 0,
double pProfit = 0, string pComment = NULL);
};
CTradeクラスで、Buy()とSell()という2つのパブリック関数を宣言しました。これらのクラスを使用して、保護されたOpenPosition()関数を呼び出します。このように、注文タイプの列挙定数を覚えておく必要はありません。関数の定義は次のとおりです。
bool CTrade::Buy(string pSymbol, double pVolume, double pStop=0, double pProfit=0,
string pComment=NULL)
{
bool success=OpenPosition(pSymbol, ORDER_TYPE_BUY, pVolume, pStop, pProfit, pComment);
return(success);
}
bool CTrade::Sell(string pSymbol, double pVolume, double pStop = 0, double pProfit = 0,
string pComment = NULL)
{
bool success=OpenPosition(pSymbol, ORDER_TYPE_SELL, pVolume, pStop, pProfit,pComment);
return(success);
}
どちらの関数も、適切な注文タイプの定数を指定してOpenPosition()関数を呼び出します。Buy()関数はOpenPosition()関数の2番目のパラメータにORDER_TYPE_BUY定数を使用しますが、Sell()はORDER_TYPE_SELLを使用します。入力パラメータの残りは、適切なOpenPosition()関数パラメータに割り当てられます。
#include <Mql5Book\Trade.mqh>
CTrade Trade;
//入力変数
input double TradeVolume=0.1;
input int StopLoss=1000;
input int TakeProfit=1000;
input int MAPeriod=10;
//グローバル変数
bool glBuyPlaced, glSellPlaced;
//OnTick()イベントハンドラ
void OnTick()
{
//取引構造
MqltradeRequest request;
MqltradeResult result;
ZeroMemory(request);
//移動平均線
double ma[ ];
ArraySetAsAsries(ma, true);
int maHandle=iMA(_Symbol, 0, MAPeriod, MODE_LWMA, 0, PRICE_CLOSE);
CopyBuffer(maHandle, 0, 0, 1, ma);
//終値
double close[];
ArraySetAsSerise(close, true);
CopyClose(_Symbol, 0, 0, 1, close);
//現在位置情報
bool openPosition = PositionSelect(_Symbol);
long positionType = PositionGetInteger(POSHITION_TYPE);
//成行買い注文
if(close[0] > ma[0] && glBuyPlaced == false
&& (positionType != POSITION_TYPE_BUY || openPosition == false))
{
glBuyPlaced = Trade.Buy(_Symbol, TradeVolume);
//SL/TPを変更する
if(glBuyPlaced == true)
{
request.action = TRADE_ACTION_SLTP;
do Sleep(100); while(PositionSelect(_Symbol) == false);
double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
if(StopLoss > 0) request.sl = positionOpenPrice – (StopLoss * _Point);
if(TakeProfit > 0) request.tp = positionOpenPrice +(TakeProfit * _Point);
if(request.sl > 0 && request.tp > 0) OrderSend(request,result);
glSellPlaced = false;
}
}
//成り行き売り注文
else if(close[0] < ma[0] && glSellPlaced == false && positionType != POSITION_TPE_SELL)
{
glSellPlaced = Trade.Sell(_Symbol, TradeVolume);
//SL/TPを変更する
if(glSellPlaced = true)
{
request.action = TRADE_ACTION_SLTP;
do Sleep(100); while(PositionSelect(_Symbol) == false);
double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
if(StopLoss > 0) request.sl = positionOpenPrice + (StopLoss * _Point);
if(TakeProfit > 0) request.tp = positionOpenPrice – (TakeProfit * _Point);
if(request.sl > 0 && request.tp > 0) OrderSend(request,result);
glBuyPlaced = false;
}
}
変更点は太字で強調表示されています。プログラムの先頭に#includeディレクティブを追加して、トレードクラスを含むTrade.mqhファイルをインクルードします。次に、CTradeクラスに基づいてオブジェクトを作成します。このオブジェクトをTradeと名付けます。
発注コードに移ると、リクエスト変数とOrderSend()関数をTrade.Buy()およびTrade.Sell()クラス関数に置き換えました。glBuyPlacedおよびglSellPlacedグローバルブール変数には、取引操作の結果が含まれます。取引が成功した場合、これらの変数の値はtrueになります。
注文が確定したら、glBuyPlacedまたはglSellPlaced変数の値を確認します。値がtrueの場合、ストップロスとテイクプロフィットの価格を計算し、それをポジションに追加します。最後に反対側のglBuyPlacedまたはglSellPlaced変数をfalseに設定します。
位置変更コードは、プログラム以前に定義された要求オブジェクトと結果オブジェクトを引き続き使用することに注意してください。トレードを使用するようになったので、注文を出すときにこれらのオブジェクトを使用する必要はありません。独自のリクエストオブジェクトと結果オブジェクトを含むTrade.Buy()およびTrade.Sell()関数を使用するようになったため、注文を出すときにこれらのオブジェクトを使用する必要はありません。次の章では、位置を変更するプロセスを簡素化し、これらのオブジェクトを完全に宣言する必要をなくす関数を作成します。
これらの値は、ファイル\MQL5\Experts\Mql5Book\SimpleExpertAdvisor with Function.mq5にあります。この本では、このプログラムにあと数回変更を加える予定です。
CTrad Hedgeクラス P114
この章で前述したように、ヘッジ口座の成り行き注文を処理するには、別のクラスを作成する必要があります。このクラスは、作成中のCTradeクラスから特定のメソッドを継承し、そのクラスのほかのメソッドをオーバーロードします。両方のクラスの使用方法は似ています。唯一の違いは、これらのメソッドの戻り値の型です。CTradeクラスのメソッド(ネッティングアカウントに使用)はブール値を返し、取引が成功したどうかを示します。一方、CTradeHedgeクラスのメソッドは、置かれたばかりの取引の注文チケット番号を含むulongを返します。後で取引を変更するために、このチケット番号を保存できます。
CTradeHedgeクラスには、以前に作成したTrade.mqhクラスが含まれます。これは、そのクラスで宣言したMqlTradeResultおよびMqlTradeResultオブジェクト、および後で宣言するその他の関数が必要になるためです。クラス宣言から始めましょう。
#include <Mql5Book\Trade.mqh>
class CTradeHedge : public CTrade
{
protected:
ulong OpenPosition(string pSymbol, ENUM_ORDER_TYPE pType, double pVolume,
double pStop = 0, double pProfit = 0, string pComment = NULL);
};
コロン(:)の後に表示されるpublic CTradeは、CTradeHedgeクラスがCTradeクラスの関数、オブジェクト、および変数を継承することを示します。前に作成したCTradeクラスでは、上記のOpenPosition()メソッドと同じパラメータを使用して、OpenPosition()という名前のメソッドも作成しました。違いは、このメソッドがboolではなくulongを返すことです。このクラスのOpenPosition()メソッドは、CTradeクラスのOpenPosition()関数を効果的に隠します。これは、プログラマーがヘッジ注文を出すためだけに別のメソッドを呼び出す必要が無いように、便宜上行われます。
CTradeHedge :: OpenPosition()関数の詳細に入りましょう。機能の多くは、この章の前半で宣言したOpenPosition()関数と非常によく似ています。
ulong CTradeHedge::OpenPosition(string pSymbol, ENUM_ORDER_TYPE pType, double pVolume,
double pStop = 0, double pProfit = 0, string pComment = NULL)
{
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;
request.symbol = pSymbol;
request.type = pType;
request.sl = pStop;
request.tp = pProfit;
request.comment = pComment;
request.volume = pVolume;
request.deviation = deviation;
request.type_filling = fillType;
request.magic = magicNumber;
これは、先にCTrade::OpenPosition()関数で入力したリクエストフィールドに似ていますが、request.volume変数を直接設定している点が異なります。現在の位置を反転することを心配する必要はありません。
//Order loop
int retryCount = 0;
int checkCode = 0;
do
{
if(pType == ORDER_TYPE_BUY) request.price = SymbolInfoDouble(pSymbol, SYMBOL_ASK);
else if(pType == ORDER_TYPE_SELL)
request.price = SymbolInfoDouble(pSymbol, SYMBOL_BID);
bool sent = OrderSend(request, result);
checkCode = CheckReturnCode(result, retcode);
if(checkCode == CHECK_RETCODE_OK) break;
else if(checkCode == CHECK_RETCODE_ERROR)
{
string errDesc = TradeServerReturnCodeDescription(result, retcode);
Alert(“Open market order : Error”, result.retcode,” – “, errDesc);
LogTradeRequest();
break;
}
else
{
Print(“Server error detected, retrying…”);
Sleep(RETRY_DELAY);
retryCount++;
}
}
while(retryCount < MAX_RETRIES);
現在のポジションの状態を気にする必要が無いので、すぐに発注ループに入ります。このコードは、以前の発注コードと同じです。注文タイプに応じて、現在のbidまたはask価格のいずれかを取得します。注文して結果を確認し、エラーが発生した場合はログに記録するか、回復可能なエラーであると判断した場合は注文を再試行します。
if(retryCount >= MAX_RETRIES)
{
string errDesc = TradeServeReturnCodeDescription(result. retcode);
Alert(“Max retries exceeded: Error “, result.retcode,” – “, errDesc);
}
string orderType = CheckOrderType(pType);
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Print(“Open “, orderType,“ order #”,result.order,”: “,result.retcode,” - ”,errDesc,”,
Volume: “,result.volume,”,Price: “,result.price,”, Bid: “,result.bid,”,
Ask: “,result.ask);
if(checkCode == CHECK_RETCODE_OK)
{
Comment(orderType,”position #”,result.order,”result.order,”opened at”,result.price,
“ on “,pSymbol);
return(result,order);
}
else return(0);
}
繰り返しますが、これは以前のエラーログと戻り値のコードに似ています。唯一の違いは、取引が成功した場合にresult.orderの値を返すことです。result.order変数には、配置したポジションの注文チケット番号が含まれています。注文が正常に行われなかった場合、ゼロの値を返します。
前と同じように、2つのパブリックヘルパー関数、Buy()とSell()を宣言します。これらは、保護されたOpenPosiiton()関数を使用して成行注文を発注するために使用します。
class CTradeHedge : public CTrade
{
protected:
ulong OpenPosition(string pSymbol, ENUM_ORDER_TYPE pType, double pVolume,
double pStop = 0, double pProfit = 0, string pComment =NULL);
public :
ulong Buy(string pSymbol, double pVolume, double pStop = 0, double pProfit= 0,
string pComment = NELL);
ulong Sell(string pSymbol, double pVolume, double pStop = 0, double pProfit= 0,
string pComment = NELL);
};
CTradeHedge Buy()およびSell()関数の関数定義は次のとおりです。
ulong CTradeHedge::Buy(string pSymbol, double pVolume, double pStop = 0.000000,
double pProfit=0.000000, string pComment=NULL)
{
ulong ticket = OpenPosition(pSymbol, ORDER_TYPE_BUY, pVolume, pStop, pProfit, pComment);
return(ticket);
}
ulong CTradeHedge::Sell(string pSymbol, double pVolume, double pStop = 0.000000,
double pProfit=0.000000, string pComment=NULL)
{
ulong ticket= OpenPosition(pSymbol, ORDER_TYPE_SELL, pVolume, pStop, pProfit, pComment);
return(ticket);
}
これらの関数は、以前にCTradeクラスで宣言したBuy()およびSell()関数と同じように機能しますが、これらの関数は、新しく配置された買いまたは売り成行注文のチケット番号(または注文が配置されていない場合はゼロ)を返すという例外があります。上記のOpenPosition()関数と同様に、これらのメソッドは、CTradeクラスの同様の名前の関数をオーバーロードします。
CTreadHedgeクラスの使用 P117
前の章で作成した単純なEAのヘッジバージョンを変更しましょう。まず、TradeHedge.mqhへのinclude参照を追加する必要があります。
#include <Mql5Book\TradeHedge.mqh>
CTradeHedge Trade;
Tradeという名前のTradeHedgeオブジェクトを作成しました。これにはCTradeHedgeのすべてのクラス関数が含まれ、CTradeクラスのパブリックにアクセス可能な関数やオブジェクトも含まれます。OnTick()イベントハンドラの買い注文コードにスキップして、変更を確認しましょう。
//成り行き買い注文
if(close[0] > ma[0] && glBuyPlace == false)
{
//売り注文を閉じる
if(sellTicket > 0)
{
PositionSelectByTicket(sellTicket);
request.action = TRADE_ACTION_DEAL;
request.type = ORDER_TYPE_BUY;
request.symbol = _Symbol;
request.position = sellTicket;
request.volume = PositionGetDouble(POSITION_VOLUME);
request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
request.deviation = 50;
bool sent = OrderSend(request, result);
}
//買い注文を開く
buyTicket = Trade.Buy(_Symbol, TradeVolume);
//SL/TPを変更
If(buyTicket > 0)
{
request.action = TRADE_ACTION_SLTP;
request.position = buyTicket;
PositionSelectByTicket(buyTicket);
double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
if(StopLoss > 0) request.sl = positionOpenPrice – (StopLoss * _Point);
if(TakeProfit > 0) request.tp = positionOpenPrice + (TakeProfit * _Point);
if(request.sl > 0 && request.tp > 0) sent = OrderSend(request.result);
glSellPlaced = false;
}
}
Trade.Buy()メソッドは正常に実行されたかどうかをチェックするようになったのでbuyTicketの戻り値を使用して、注文が行われたかどうかを判断できます。buyTicketが0より大きい場合、注文のストップロスとテイクプロフィット価格の変更に進みます。ストップロスとテイクプロフィットの価格を変更する場合、request.positionをbuyTicketの注文チケットの値に設定し、PositionSelectByTicket()を使用してbuyTicketの値で注文を選択し、注文の始値を取得します。
次の章では、注文情報、変更、および終了コードを同様の関数に置き換えます。このプログラムへの変更は\MQL5\Experts\Mql5Book\Simple Expert Advisor with Functions(Hedging).mq5で確認できます。