前回の講座記事は トレード用のクラスを作っていく回 の1回目 でした。
- MQL5ウィザードを立ち上げて、「OriginalTrade」という名前のインクルードファイルを生成する。
- 「OriginalCTrade」という名前のクラスを宣言し、{}内に、「request」というMqlTradeRequest構造体のインスタンス、「result」というMqlTradeResult構造体のインスタンスをそれぞれ生成する。
- 「request」のアクセスレベルはprotected、「result」のアクセスレベルはpublicとする。
以上の作業を行いました。
現時点では↓のような感じになります。
//トレード用のクラス「OriginalCTrade」を定義する
class OriginalCTrade
{
protected:
MqlTradeRequest request;
public:
MqlTradeResult result;
}//class OriginalCTrade
今回は、この「OriginalCTrade」クラスに、新規にポジションを保有する注文を出す関数を実装していきたいと思います。
ポジションオープン関数を宣言する
※クラスの定義に関する詳しい手順については↓の記事をご覧ください。
protected:
MqlTradeRequest request;
//ポジションオープンクラスの実装
ulong PositionOpen(string parSymbol,
ENUM_ORDER_TYPE parType,
double parVolume,
double parStop = 0,
double parProfit = 0,
string parComment = NULL);
関数の名前は、「PositionOpen」とし、アクセスレベルは protected とします。(この関数のアクセスレベルがprotected である理由は後程説明します)
戻り値はulong型にしてあります。これは最終的に戻り値としてオーダー番号を返すように関数を作るためです。
※「ulong型とはなにか?」については↓の記事をご覧ください。
続けて()内にデータ型と仮引数を設定します。仮引数の名前にはパラメーターであることを示す[par-]という接頭辞をつけてみました。仮引数の名前から、それぞれの役割は概ね想像つく人もいるかもしれませんが、一応コメントアウトした部分に説明コメントを付与すると↓のようになります。
//ポジションオープンクラスの実装
ulong PositionOpen
(string parSymbol,//銘柄情報を設定する
ENUM_ORDER_TYPE parType,//オーダー種類を設定する
double parVolume,//ロット数を設定する
double parStop = 0,//ストップロスを設定する
double parProfit = 0,//テイクプロフィットを設定する
string parComment = NULL//コメントを設定する
);
引数は6個用意し、そのうちストップロスを設定するparStop、テイクプロフィットを設定するparProfit、コメントを設定するparCommentには初期値が割り振られています。
※初期値を割り振っている引数は、順番としては最後にまとめて書く必要があります。詳しくは↓
第46回「関数の初期値について」 をご覧ください。
引数は6個、初期値を割り振られている引数は、省略可能なので、値の入力が必須なのは、parSymbol、parType、parVolumeという最初の3つだけです。
アクセスレベルprotectedに変数をいくつか追加する
アクセスレベル protectedに変数をいくつか追加します。これは、後程、PositionOpen関数内で使うための変数です。
magicNumberにはマジックナンバーを格納させます。
deviationにはスリッページを格納させます。
fillTypeにはフィルポリシーを格納させます。
ulong magicNumber;//マジックナンバーを格納する
ulong deviation;//スリッページを格納する
ENUM_ORDER_TYPE_FILLING fillType;//フィルポリシーを格納する
↑の変数をどうやって使っていくかは、
第74回「マジックナンバー・スリッページ・フィルポリシーを返す関数を実装」
で解説します。
ポジションオープン関数の具体的な処理を記述していく
クラスのメンバ関数の定義記述のやり方に従って、ポジションオープン関数の具体的な処理を記述していきます。
//PositionOpen関数の処理内容を実装
ulong OriginalCTrade::ulong PositionOpen(string parSymbol, ENUM_ORDER_TYPE parType, double parVolume, double parStop = 0, double parProfit = 0, string parComment = NULL)
{
}
クラス名のOriginalCTradeに続けてコロンを2つ(::)記述することによって、次に書かれる関数名がOriginalCTrade所属のメンバー関数である、という命令になります。
構造体インスタンスのリセットとメンバ変数への設定
ZeroMemory関数を使って、インスタンス「request」や「result」の初期値をまとめてゼロにリセットします。
メンバ変数 への設定記述については、第62回「実際に注文を出すコードを書いてみる」 に従って行います。
ulong OriginalCTrade::PositionOpen(string parSymbol, ENUM_ORDER_TYPE parType, double parVolume, double parStop = 0, double parProfit = 0, string parComment = NULL)
{
//構造体インスタンスのリセット
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;//取引の種類を設定する
request.symbol = parSymbol;//取引銘柄を設定する
request.type = parType;//注文タイプを設定する
request.sl = parStop;//ストップロスを設定する
request.tp = parProfit;//テイクプロフィットを設定する
request.comment = parComment;//コメントを設定する
request.volume = parVolume;//ロット数を設定する
request.deviation = deviation;//スリッページを設定する
request.type_filling = fillType;//フィルポリシーを設定する
request.magic = magicNumber;////マジックナンバーを設定する
requestインスタンスに値を設定していく↑の記述に関しては大分見慣れてきたかと思います。
今回注目してほしいのは、.action と .type の部分です。
新規にポジションを保有する為の関数なので、メンバ変数 .actionに設定する定数値はTRADE_ACTION_DEALになります。
その一方で、メンバ変数 .type に代入する値は仮引数parTypeになっています。
これは、ひと口に「新規にポジションを保有する」と言っても、例えばORDER_TYPE_BUY(成り行き買い)であったり、ORDER_TYPE_SELL_LIMIT(売り指値注文)であったりと複数の注文タイプがあるため、注文タイプについてはアクセスレベル publicの関数を別途作り、その関数から設定させる為です。
注文タイプを設定するアクセスレベル publicの注文関数はこれから作っていきます。
PositionOpen関数のアクセスレベル がprotectedなのは、これから作るアクセスレベル publicの各種注文関数と連結させるからです。MT5用EAのメインプログラムに記述するのは、アクセスレベル publicの各種注文関数になるので、PositionOpen関数はメインプログラムで呼び出す必要がない設計にします。
今は何を言っているのかピンとこないかもしれません。それで大丈夫です。
一緒に手を動かして実際に作っていく事でわかるようになります。
<復習用リンク>
リトライ回数とリターンコードを格納する変数を宣言する
// リトライ回数とリターンコードを格納する変数を宣言
int retryCount = 0;
int checkCode = 0;
変数「retryCount」は、注文を出して注文エラーが発生した時、エラー内容次第では再注文すれば約定する可能性があるので、指定回数はエラーが出たときに再注文を出す回路を組み込む為に使います。
変数「checkCode」は、注文後、インスタンス「result」が受け取るリターンコードを格納するためのものです。注文が成功したのか?、失敗したのならばその理由は何かを確認する為に使います。
do-while文の中に発注回路を組み込んでいく。
リトライ回数とリターンコードを格納する変数を宣言する の項で書いたようにPositionOpen関数は注文エラーが発生した時に、再注文を出す回路を組み込みます。そのため、do-while文の中に発注回路を記述していきます。
define命令で、再注文の上限回数を設定する。
まずは、注文エラーが発生した際、最大何回まで再注文を繰り返すか?という上限を定数にして設定します。定数を設定するにはグローバル領域で、define命令を使って行います。
#property copyright "MQL5ssei"
#property link "https://mqlinvestmentlab.com/"
#define RETRY_LIMIT 3//エラー時に再注文を出す上限回数
RETRY_LIMITという定数を用意しました。便宜的に値を3としています。仮に注文エラーが発生した際、3回までは再注文を行う、ということになります。
そして、そして用意した定数RETRY_LIMITをdo-while文における、繰り返し条件を規定するwhile()に記述します。
//発注回路の記述
do
{
}
while(retryCount < RETRY_LIMIT);
while(retryCount < RETRY_LIMIT);←この記述により、retryCountに格納される値が、RETRY_LIMITで定めた数に達していない間は、再注文処理を繰り返すことになります。
※do-while構文については、↓の記事をご覧ください。
ErrorDescription.mqhファイルをインクルードする
TradeServerReturnCodeDescription関数について
メタエディターのincludeフォルダには、ErrorDescription.mqhファイルというものが標準で実装されています。↓
このファイル内にあるTradeServerReturnCodeDescription関数を利用する事によって、リターンコードの視認が用意になります。
グローバル領域、property命令やdefine命令の下にErrorDescription.mqhファイルを利用する命令記述を行いましょう。インクルードファイルを取り込む記述になるので、↓のようになります。
#property copyright "MQL5ssei"
#property link "https://mqlinvestmentlab.com/"
#define RETRY_LIMIT 3//エラー時に再注文を出す上限回数
#include <errordescription.mqh>
※インクルードファイルを取り込むinclude命令については↓の記事をご覧ください
第56回「#include命令(#include directive)」
TradeServerReturnCodeDescription関数の使い方については、実際の記述の際に解説します。
do-while文の中の具体的な記述1
.Price以外の「request」インスタンス のメンバ変数 はもう設定済みなのでif文で買いか、売りか、を切り分けてSymbolInfoDouble関数から取得したAsk Bid値を.Priceに代入します。
メンバ変数 の設定が終わったので、OrderSend関数で注文を出します。
//買いの場合の注文価格
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);
<復習用リンク>
リターンコードによって、次の行動を決めるためのenum列挙型を作る
OrderSend関数によって注文を出した後、プログラムが行う判断は、
- 注文は成功したので、もう再注文はしない。
- 再注文しても解消されないエラーなので、エラー内容を確認する。
- 注文エラーだが再注文すれば解消される可能性があるので再注文に移る。
というように3つに分かれます。その判断の切り分けをする為、グローバル領域に自作のenum列挙型を作ります↓ enum列挙型の名前はENUM_CHECK_RETCODEとしました。
// リターンコードによって、次の行動を決めるためのenum列挙型
enum ENUM_CHECK_RETCODE
{
CHECK_RETCODE_OK,
CHECK_RETCODE_ERROR,
CHECK_RETCODE_RETRY
};
- CHECK_RETCODE_OK → 注文は成功したので、もう再注文はしない。
- CHECK_RETCODE_ERROR → 再注文しても解消されないエラーなので、エラー内容を確認する。
- CHECK_RETCODE_RETRY → 注文エラーだが再注文すれば解消される可能性があるので再注文に移る。
という処理を組み込む為にこのenum列挙型を作っています。
※enum列挙型については、→の記事をご覧ください。第21回「Enum列挙型」
リターンコードのチェックをする関数を作成する
次の行動を決めるためのenum列挙型であるENUM_CHECK_RETCODEができたら、リターンコードのチェックをし、ENUM_CHECK_RETCODEの定数値を戻り値として返す関数をグローバル領域に作ります。
リターンコードのチェック関数にはswitch文を用います。↓
// リターンコードのチェックをする関数
int ReturnCodeCheck(uint parRetCode)
{
int status;
switch(parRetCode)
{
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_PARTIAL:
case TRADE_RETCODE_PLACED:
case TRADE_RETCODE_NO_CHANGES:
status = CHECK_RETCODE_OK;
break;
default: status = CHECK_RETCODE_ERROR;
}
return(status);
再注文すれば解決する可能性がある注文エラーについてはCHECK_RETCODE_RETRYを変数「status」
注文成功と見なせるリターンコードを受け取った時はCHECK_RETCODE_OKを変数「status」
それ以外のリターンコードであれば、再注文で解決しない類の注文エラーなので、defaultキーワードをつけたうえで、CHECK_RETCODE_ERRORを変数「status」に代入して、switch文をbreak離脱。
という流れになっています。
※switch文については、↓の記事をご覧ください。
変数「checkCode」にReturnCodeCheck関数の戻り値を格納する
変数「checkCode」に、ReturnCodeCheck関数の戻り値を格納します。
//注文を出す
bool sent = OrderSend(request,result);
//リターンコードを変数checkCode に格納する
checkCode = ReturnCodeCheck(result.retcode);
ReturnCodeCheck関数の戻り値は、自作したENUM_CHECK_RETCODEの、
CHECK_RETCODE_OK,
CHECK_RETCODE_ERROR,
CHECK_RETCODE_RETRY
のうちどれかになります。変数「checkCode」に格納された値により、条件を分岐させます。
checkCodeの値により、条件分岐させる
//リターンコードを変数checkCode に格納する
checkCode = ReturnCodeCheck(result.retcode);
//注文が成立したので、ループを抜ける
if(checkCode == CHECK_RETCODE_OK) break;
//再注文で解決しないエラーの場合
else if(checkCode == CHECK_RETCODE_ERROR)
{//エラー内容を変数に格納
string errDesc = TradeServerReturnCodeDescription(result.retcode);
//エラーアラート発出
Alert("注文エラー: エラー内容 ",result.retcode," - ",errDesc);
TradeLog();
break;
}
else
{
Print("サーバーエラーを検知。 再試行中...");
Sleep(RETRY_MILLISECONDS);
retryCount++;
}//else
CHECK_RETCODE_OKの場合
checkCode == CHECK_RETCODE_OKの場合、注文が成功しているので、再注文試行をする必要がありません。break演算子によって、do-while文の{}を抜けます。
//注文が成立したので、ループを抜ける
if(checkCode == CHECK_RETCODE_OK) break;
CHECK_RETCODE_ERRORの場合
checkCode == CHECK_RETCODE_ERRORの場合、再注文をしても解決しない類の注文エラーが発生したことを意味しているので、注文と注文結果をエキスパートログに残す関数を呼び出した上で、break演算子によって、do-while文の{}を抜けます。
string型の変数「errDesc」 に TradeServerReturnCodeDescription関数の内容を代入します。引数にはresult.retcodeを代入します。TradeServerReturnCodeDescription関数は、リターンコードの定数値を文字化して戻り値に返す仕組みになっているので、この変数「errDesc」に格納された情報を使い、Alert関数でアラートを発します。
※Alert関数
Alert関数は引数に記述した情報をアラートウィンドウに表示する関数です。引数はコンマで区切って、64まで設定する事が可能です)↓
注文内容と注文結果をエキスパートログに出力する関数を作る
注文内容と注文結果をエキスパートログに出力するクラスのメンバ関数をグローバル領域に作ります。
関数名は「TradeLog」とします。アクセスレベルはPositionOpen関数と同じくprotectedとします。
関数の処理はMqlTradeRequest構造体とMqlTradeResult構造体の各メンバ変数 に格納されている値をPrint関数でログ出力するだけなので、戻り値はvoid型です。↓
//注文内容と結果をエキスパートログに残す
void OriginalCTrade::TradeLog()
{
Print("MqlTradeRequest - 取引の種類:",request.action,
", コメント:",request.comment,
",スリッページ:",request.deviation,
", 注文有効期限:",request.expiration,
", マジックナンバー:",request.magic,
", オーダー番号:",request.order,
", ポジション番号:",request.position,
", 反対ポジションの番号:",request.position_by,
", ポジション価格:",request.price,
", ストップロス:",request.sl,
", テイクプロフィット:",request.tp,
", ストップリミット:",request.stoplimit,
", 取引銘柄:",request.symbol,
", オーダー種類:",request.type,
", フィルポリシー:",request.type_filling,
", 有効期限の種類:",request.type_time,
", ロット数:",request.volume);
Print("MqlTradeResult - ask:",result.ask,
", bid:",result.bid,
", コメント:",result.comment,
", 約定番号:",result.deal,
", オーダー番号:",result.order,
", ポジション価格:",result.price,
", リクエストID:",result.request_id,
", リターンコード:",result.retcode,
", リターンコード(外部):",result.retcode_external,
", ロット数:",result.volume);
}//void OriginalCTrade::TradeLog()
出来上がったTradeLog関数を
else if(checkCode == CHECK_RETCODE_ERROR)の{}内で呼び出します。
//再注文で解決しないエラーの場合
else if(checkCode == CHECK_RETCODE_ERROR)
{//エラー内容を変数に格納
string errDesc = TradeServerReturnCodeDescription(result.retcode);
//エラーアラート発出
Alert("注文エラー: エラー内容 ",result.retcode," - ",errDesc);
TradeLog();
break;
}
これで、再注文で解決しないエラーが発生した時に、アラートを出す回路とエキスパートログに出力する回路が出来上がりました。
※breakについては↓の記事をご覧ください
MQL5 EA講座 第41回「breakとcontinue」
※インクリメントについては↓をご覧ください。
インクリメントとデクリメント(Increment,Decrement)
CHECK_RETCODE_RETRYの場合
最後の条件分岐であるelse文の{}内に checkCode ==CHECK_RETCODE_RETRYだった場合の処理を記述していきます。
以上↑が処理内容になります。
※Sleep関数
Sleep関数は、引数に指定した時間EA、またはスクリプトの実行を一時停止させる関数です(カスタムインジケ-タでは使用できません)
引数はミリ秒(milliseconds)単位で指定します。ミリ秒というのは1/1000秒です。
再注文処理に移るまでの待機時間をdefine命令で設定
引数に指定するミリ秒については、#define命令でグローバル領域に定数設定します。定数名は「RETRY_MILLISECONDS」とし、初期値は3000(3秒)とします。
#property copyright "MQL5ssei"
#property link "https://mqlinvestmentlab.com/"
#define RETRY_LIMIT 3//エラー時に再注文を出す上限回数
#define RETRY_MILLISECONDS 3000//再注文処理に移るまでの待機時間
CHECK_RETCODE_RETRYの場合のelse文{}内記述の全体は↓の通りです。
else//CHECK_RETCODE_RETRYの場合
{
Print("サーバーエラーを検知。 再試行中...");
Sleep(RETRY_MILLISECONDS);
retryCount++;
}//else
}//do
while(retryCount < RETRY_LIMIT);
↑これで、checkCode ==CHECK_RETCODE_RETRYだった場合、再度注文をする為に、do-while文の冒頭にプログラムが戻るわけです(再注文上限回数内の間)。
do-while文の{}も同時にここで閉じられます。
do-while文が終わるので、その下の記述は
CHECK_RETCODE_OK、CHECK_RETCODE_ERROR、CHECK_RETCODE_RETRY共通の処理記述になります。
再注文試行が上限回数に達した場合の記述
続いては、再注文をしても約定しないまま、再注文試行回数が上限に達した場合の記述になります。
//再注文上限回数に達した時
if(retryCount >= RETRY_LIMIT)
{
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Alert("再注文上限回数に達しました: エラー内容 ",result.retcode," - ",errDesc);
}
if(retryCount >= RETRY_LIMIT)は再注文試行回数が#define命令で設定した定数RETRY_LIMITの数を上回っている事を意味します。
まずはTradeServerReturnCodeDescription関数でリターンコードを獲得し、変数「errDesc」に格納します。
変数「errDesc」に格納された値とresult.retcodeに格納されたリターンコードをAlert関数でアラート出力させます。
注文種類を確認する関数を宣言・定義する
注文種類を確認する関数を宣言・定義します。処理の内容は↓の通りです。
//注文種類を確認する関数
string ConfirmOrderType(ENUM_ORDER_TYPE parType)
{
string orderType;
if(parType == ORDER_TYPE_BUY) orderType = "buy";
else if(parType == ORDER_TYPE_SELL) orderType = "sell";
else if(parType == ORDER_TYPE_BUY_STOP) orderType = "buy stop";
else if(parType == ORDER_TYPE_BUY_LIMIT) orderType = "buy limit";
else if(parType == ORDER_TYPE_SELL_STOP) orderType = "sell stop";
else if(parType == ORDER_TYPE_SELL_LIMIT) orderType = "sell limit";
else if(parType == ORDER_TYPE_BUY_STOP_LIMIT) orderType = "buy stop limit";
else if(parType == ORDER_TYPE_SELL_STOP_LIMIT) orderType = "sell stop limit";
else orderType = "不適切な注文種類です。";
return(orderType);
}
if文とelse-if文を使って注文種類を選別し、注文種類に応じた文字列を戻り値として返します。
メインプログラム内でこのConfirmOrderType関数を呼び出し、受け取った戻り値をPrint関数でログ出力します。
Print("注文種類",orderType,
"オーダー番号 #",result.order,
" リターンコード: ",result.retcode,
" - ",errDesc,
", ロット数: ",result.volume,
", 約定価格: ",result.price);
戻り値を返して終了
PositionOpen関数の戻り値はulong型です。注文が成功している場合(checkCode == CHECK_RETCODE_OKの場合)は、returnキーワードでresult.orderに格納されているオーダー番号を戻り値として返します。(Comment関数で一部注文情報も表示させます)
注文が失敗した場合(else)は0を返します。
以上で、PositionOpen関数の処理は終了です。
//戻り値を返す
if(checkCode == CHECK_RETCODE_OK)
{
Comment("注文種類: ",orderType," オーダー番号 #",result.order," 約定価格 ",result.price);
return(result.order);
}
else return(0);
※returnキーワードについては↓の記事をご覧ください。
<その他復習用リンク>
コード全体の記述内容
コード全体の記述内容は以下の通りです。
//+------------------------------------------------------------------+
//| OriginalTrade.mqh |
//| MQL5ssei |
//| https://mqlinvestmentlab.com/ |
//+------------------------------------------------------------------+
#property copyright "MQL5ssei"
#property link "https://mqlinvestmentlab.com/"
#define RETRY_LIMIT 3//エラー時に再注文を出す上限回数
#define RETRY_MILLISECONDS 3000//再注文処理に移るまでの待機時間
#include <errordescription.mqh>
//トレード用のクラス「OriginalCTrade」を定義する
class OriginalCTrade
{
protected:
MqlTradeRequest request;
ulong magicNumber;//マジックナンバーを格納する
ulong deviation;//スリッページを格納する
ENUM_ORDER_TYPE_FILLING fillType;//フィルポリシーを格納する
void TradeLog();//注文内容と結果をエキスパートログに残す
//ポジションオープンクラスの実装
ulong PositionOpen(string parSymbol, ENUM_ORDER_TYPE parType, double parVolume, double parStop = 0, double parProfit = 0, string parComment = NULL);
public:
MqlTradeResult result;
//買い注文を出す関数
ulong Buy(string parSymbol, double parVolume, double parStop = 0, double parProfit = 0, string parComment = NULL);
//売り注文を出す関数
ulong Sell(string parSymbol, double parVolume, double parStop = 0, double parProfit = 0, string parComment = NULL);
//マジックナンバーを設定する関数
void OriginalCTrade::SetMagicNumber(ulong parMagic);
// スリッページを設定する
void SetDeviation(ulong parDeviation);
// フィルポリシーを設定する
void SetFillType(ENUM_ORDER_TYPE_FILLING parFill);
};//class OriginalCTrade
//PositionOpen関数の処理内容を実装
ulong OriginalCTrade::PositionOpen(string parSymbol, ENUM_ORDER_TYPE parType, double parVolume, double parStop = 0, double parProfit = 0, string parComment = NULL)
{
//構造体インスタンスのリセット
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;//取引の種類を設定する
request.symbol = parSymbol;//取引銘柄を設定する
request.type = parType;//注文タイプを設定する
request.sl = parStop;//ストップロスを設定する
request.tp = parProfit;//テイクプロフィットを設定する
request.comment = parComment;//コメントを設定する
request.volume = parVolume;//ロット数を設定する
request.deviation = deviation;//スリッページを設定する
request.type_filling = fillType;//フィルポリシーを設定する
request.magic = magicNumber;////マジックナンバーを設定する
// リトライ回数とリターンコードを格納する変数を宣言
int retryCount = 0;
int checkCode = 0;
//発注回路の記述
do
{
//買いの場合の注文価格
if(parType == ORDER_TYPE_BUY)
request.price = SymbolInfoDouble(parSymbol,SYMBOL_ASK);
//売りの場合の注文価格
else
if(parType == ORDER_TYPE_SELL)
request.price = SymbolInfoDouble(parSymbol,SYMBOL_BID);
//注文を出す
bool sent = OrderSend(request,result);
//リターンコードを変数checkCode に格納する
checkCode = ReturnCodeCheck(result.retcode);
if(checkCode == CHECK_RETCODE_OK) break;
else if(checkCode == CHECK_RETCODE_ERROR)
{
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Alert("注文エラー: エラー内容 ",result.retcode," - ",errDesc);
TradeLog();
break;
}
else//CHECK_RETCODE_RETRYの場合
{
Print("サーバーエラーを検知。 再試行中...");
Sleep(RETRY_MILLISECONDS);
retryCount++;
}//else
}//do
while(retryCount < RETRY_LIMIT);
//再注文上限回数に達した時
if(retryCount >= RETRY_LIMIT)
{
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Alert("再注文上限回数に達しました: エラー内容 ",result.retcode," - ",errDesc);
}
//注文種類を確認する関数を呼び出し戻り値を変数に格納
string orderType = ConfirmOrderType(parType);
string errDesc = TradeServerReturnCodeDescription(result.retcode);
//ログ出力
Print("注文種類",orderType,
"オーダー番号 #",result.order,
" リターンコード: ",result.retcode,
" - ",errDesc,
", ロット数: ",result.volume,
", 約定価格: ",result.price);
//戻り値を返す
if(checkCode == CHECK_RETCODE_OK)
{
Comment("注文種類: ",orderType," オーダー番号 #",result.order," 約定価格 ",result.price);
return(result.order);
}
else return(0);
}//OriginalCTrade::PositionOpen
//+------------------------------------------------------------------+
// リターンコードによって、次の行動を決めるための列挙型
enum ENUM_CHECK_RETCODE
{
CHECK_RETCODE_OK,
CHECK_RETCODE_ERROR,
CHECK_RETCODE_RETRY
};
// リターンコードのチェックをする関数
int ReturnCodeCheck(uint parRetCode)
{
int status;
switch(parRetCode)
{
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_PARTIAL:
case TRADE_RETCODE_PLACED:
case TRADE_RETCODE_NO_CHANGES:
status = CHECK_RETCODE_OK;
break;
default: status = CHECK_RETCODE_ERROR;
}
return(status);
}
//注文内容と結果をエキスパートログに残す
void OriginalCTrade::TradeLog()
{
Print("MqlTradeRequest - 取引の種類:",request.action,
", コメント:",request.comment,
",スリッページ:",request.deviation,
", 注文有効期限:",request.expiration,
", マジックナンバー:",request.magic,
", オーダー番号:",request.order,
", ポジション番号:",request.position,
", 反対ポジションの番号:",request.position_by,
", ポジション価格:",request.price,
", ストップロス:",request.sl,
", テイクプロフィット:",request.tp,
", ストップリミット:",request.stoplimit,
", 取引銘柄:",request.symbol,
", オーダー種類:",request.type,
", フィルポリシー:",request.type_filling,
", 有効期限の種類:",request.type_time,
", ロット数:",request.volume);
Print("MqlTradeResult - ask:",result.ask,
", bid:",result.bid,
", コメント:",result.comment,
", 約定番号:",result.deal,
", オーダー番号:",result.order,
", ポジション価格:",result.price,
", リクエストID:",result.request_id,
", リターンコード:",result.retcode,
", リターンコード(外部):",result.retcode_external,
", ロット数:",result.volume);
}//void OriginalCTrade::TradeLog()
//注文種類を確認する関数
string ConfirmOrderType(ENUM_ORDER_TYPE parType)
{
string orderType;
if(parType == ORDER_TYPE_BUY) orderType = "buy";
else if(parType == ORDER_TYPE_SELL) orderType = "sell";
else if(parType == ORDER_TYPE_BUY_STOP) orderType = "buy stop";
else if(parType == ORDER_TYPE_BUY_LIMIT) orderType = "buy limit";
else if(parType == ORDER_TYPE_SELL_STOP) orderType = "sell stop";
else if(parType == ORDER_TYPE_SELL_LIMIT) orderType = "sell limit";
else if(parType == ORDER_TYPE_BUY_STOP_LIMIT) orderType = "buy stop limit";
else if(parType == ORDER_TYPE_SELL_STOP_LIMIT) orderType = "sell stop limit";
else orderType = "不適切な注文種類です。";
return(orderType);
}
// 買い注文を出す
ulong OriginalCTrade::Buy(string parSymbol, double parVolume, double parStop = 0, double parProfit = 0, string parComment = NULL)
{
ulong ticket = PositionOpen(parSymbol,ORDER_TYPE_BUY,parVolume,parStop,parProfit,parComment);
return(ticket);
}
// 売り注文を出す
ulong OriginalCTrade::Sell(string parSymbol, double parVolume, double parStop = 0, double parProfit = 0, string parComment = NULL)
{
ulong ticket = PositionOpen(parSymbol,ORDER_TYPE_SELL,parVolume,parStop,parProfit,parComment);
return(ticket);
}
// マジックナンバーを設定する
void OriginalCTrade::SetMagicNumber(ulong parMagic)
{
magicNumber = parMagic;
}
// スリッページを設定する
void OriginalCTrade::SetDeviation(ulong parDeviation)
{
deviation = parDeviation;
}
// フィルポリシーを設定する
void OriginalCTrade::SetFillType(ENUM_ORDER_TYPE_FILLING parFill)
{
fillType = parFill;
}
//+------------------------------------------------------------------+
//フィルポリシーを返す関数
ENUM_ORDER_TYPE_FILLING FillPolicy()
{
long fillType = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
if(fillType==SYMBOL_FILLING_IOC)return ORDER_FILLING_IOC;
else if(fillType==SYMBOL_FILLING_FOK)return ORDER_FILLING_FOK;
else return ORDER_FILLING_RETURN;
}
コメント