前回は、OriginalCTradeクラスに実装したBuy関数やSell関数を、第68回「簡単な仕組みのMT5用EAを作るーその1ー」~第69回「簡単な仕組みのMT5用EAを作るーその2ー」を通して作ったMT5用シンプルEAに導入する作業を行いました。↓
現時点では、記述を変えた部分は新規ポジション保有の箇所だけで、決済やストップロスとテイクプロフィットの設定についての処理には引き続き、OrderSend関数及びMqlTradeRequest構造体のメンバ変数を使った注文記述がなされています。
今後は、決済処理やストップロスとテイクプロフィットの設定処理も簡略化していく必要があります。
・今後は、インクルードファイルにメンバ関数や独立関数をさらに追加する
↓
・ EAにそれらを導入する
という流れを繰り返す事によって、 OrderSend関数及びMqlTradeRequest構造体をメインプログラムに記述する、いわゆる「プレーンな作り方」から、記述がどう変容していくかを見ていきます。
また第71回「トレード用のオリジナルクラスを作る」から作り始めているOriginalCTradeクラスですが、ヘッジングシステム用のものです。
※今後、派生クラスでネッティングシステム用のクラスファイルも作る予定です。 派生クラスについては↓の記事をご覧ください。
MQL5 EA講座 第51回「クラスについて4 -派生クラス-」
ネッティングシステムとヘッジングシステムについては↓の記事をご覧ください。
今回はOriginalCTradeクラスにさらに、関数を追加していく作業に戻ります。
今回はSLとTPを設定する関数を実装します。
SLとTPを設定する関数を実装のためのロードマップ
ストップロス(SL)とテイクプロフィット(TP)の設定をする関数に関しては、基本的に
第63回「約定したポジションにSLとTPを設定する」で書いた内容に沿ったものになります。
SLTPを設定する関数「SetSLTP」を宣言する
まずは、定石通りOriginalCTradeクラス内にSLTPを設定する関数を宣言します。
EAのメインプログラムで呼び出す事を目的としているので、アクセスレベルはpublicになります。
publicセクションに以下の記述を追加します。↓
//ストップロスとテイクプロフィットを設定する
bool SetSLTP(ulong parTicket, double parStop, double parProfit = 0);
parTicket=ポジション番号、
parStop=ストップロスを設定する値、
parProfit=テイクプロフィットを設定する値
をそれぞれ入力するように想定しています。
parProfitにだけ初期値0を設定して、省略可能としているのは、ストップロスとテイクプロフィットの利用頻度の問題からです。
色んな戦略のEAがあるので、あくまで個人的な感覚ですが、ストップロスは設定するけどテイクプロフィットは定めずに条件決済にするとか、動的ストップロスにする等の仕様にすることが多い気がします。(動的ストップロスを使った記述については、この先の内容になりますが第81回「動的ストップロスの一例」 を参考にしてもらえればと思います)
※関数の初期値については↓の記事をご覧ください。
SetSLTP関数の処理実装記述を行う
クラス内での宣言ができたので、処理の実装記述を行っていきます。
処理実装記述その1-インスタンスのリセット~各種メンバ変数への値代入-
前半部分はこんな感じです↓
//ストップロスとテイクプロフィットを設定する
bool OriginalCTrade::SetSLTP(ulong parTicket,double parStop,double parProfit=0.000000)
{
//インスタンスのリセット
ZeroMemory(request);
ZeroMemory(result);
//ポジション番号を取得し、変数に格納
bool select = PositionSelectByTicket(parTicket);
//銘柄を取得し、変数に格納
string symbol = PositionGetString(POSITION_SYMBOL);
//各種メンバ変数に値を代入
request.action = TRADE_ACTION_SLTP;//取引種別の設定
request.sl = parStop;//ストップロスの設定
request.tp = parProfit;//テイクプロフィットの設定
request.position = parTicket;//ポジション番号の設定
request.symbol = symbol;//銘柄情報の設定
request.type_filling = fillType;//フィルポリシーを設定
request.magic = magicNumber;////マジックナンバーを設定
インスタンスのリセット→ポジション番号と銘柄の取得→各種メンバ変数への値代入
という流れになっています。
講座記事第72回「ポジションオープン関数を実装する」内の「構造体インスタンスのリセットとメンバ変数への設定」の項目で書かれている記述に似通っていることがお分かりいただけるかと思います。
SLTP設定なので、取引種別を設定する.action にはTRADE_ACTION_SLTPが入り、request.slとrequest.tpにはストップロスとテイクプロフィットを設定する値が記述されます。
処理実装記述その2-リトライ回数とリターンコードを格納する変数を宣言する-
// リトライ回数とリターンコードを格納する変数を宣言
int retryCount = 0;//再試行回数を格納する
int checkCode = 0;//リターンコードを格納する
この部分も講座記事第72回と同じです。詳細は、→コチラをご覧ください。
処理実装記述その3-do-while文の中に発注回路を組み込んでいく-
do-while文中の構文も基本的にPositionOpen関数と同じです。
OrderSend関数でSLTP設定注文を出す
↓
ReturnCodeCheck関数で注文結果の次を行動を規定する。
↓
「約定が成功した場合」「再試行しても解決しないエラーの場合」「再試行すれば解決する可能性のあるエラーの場合」というの3つをif文 else文で条件分岐させ処理を切り分けていきます。
do
{ //SLTP設定注文を出す。
bool sent = OrderSend(request,result);
//注文結果を返す
checkCode = ReturnCodeCheck(result.retcode);
//約定成功の場合do-whileループを抜ける
if(checkCode == CHECK_RETCODE_OK) break;
//再試行しても解決しないエラーの場合
else if(checkCode == CHECK_RETCODE_ERROR)
{ //リターンコードを取得
string errDesc = TradeServerReturnCodeDescription(result.retcode);
//アラートを発出
Alert("SLTP設定エラー:エラー内容 ",result.retcode," - ",errDesc);
//ログの出力
TradeLog();
break;//ループを抜ける
}
//再試行すれば解決する可能性のあるエラーの場合
else
{ //ログ出力
Print("サーバーエラーを検知。 再試行中...");
//次の処理までの待機
Sleep(RETRY_MILLISECONDS);
//再試行回数をカウントする変数をインクリメントする
retryCount++;
}
}
while(retryCount < RETRY_LIMIT);
※TradeServerReturnCodeDescription関数については→コチラをご覧ください。
※ReturnCodeCheck関数の戻り値であるデータ型「ENUM_CHECK_RETCODE」については→コチラをご覧ください。
※do-while構文については、→の記事をご覧ください。第39回「do-while文について」
※インクリメントについては↓をご覧ください。
インクリメントとデクリメント(Increment,Decrement)
処理実装記述その4-再注文試行が上限回数に達した場合の記述-
この部分はPositionOpen関数と同じです。
if(retryCount >= RETRY_LIMIT)
{
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Alert("再注文上限回数に達しました: エラー内容 ",result.retcode," - ",errDesc);
}
if(retryCount >= RETRY_LIMIT)
という記述は↑再注文試行回数が#define命令で設定した定数RETRY_LIMITの数を上回っている事を意味します。
TradeServerReturnCodeDescription関数でリターンコードを獲得後、変数「errDesc」に格納→Alert関数で変数に格納された内容を発出します。
SLTP設定注文の結果をPrint関数でログ出力する
「再注文試行が上限回数に達した場合の記述」の下には、
注文が成功した場合であれ、失敗した場合であれ、共通してSLTP設定注文の結果をPrint関数でログ出力する記述をします。
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Print("SLTP設定ポジション番号 #",parTicket,
": ",result.retcode," - ",errDesc,
", SL: ",request.sl,", TP: ",request.tp,
", Bid: ",SymbolInfoDouble(symbol,SYMBOL_BID),
", Ask: ",SymbolInfoDouble(symbol,SYMBOL_ASK),
", ストップレベル: ",SymbolInfoInteger(symbol,SYMBOL_TRADE_STOPS_LEVEL));
これも、PositionOpen関数で同じことをやっているんですが、PositionOpen関数は新規でポジションを持つための関数なのに対し、SetSLTP関数はSLTP設定の役割を担う関数なので、当然Print関数で出力させる内容が違います。設定したストップロスとテイクプロフィットの価格がログ出力されるようになっています。
それと「ストップレベル」というこれまでの講座記事では出てこない単語が出てきていますが、ストップレベルについてはこの後、以下↓の講座記事で改めて取り上げる予定なのでもう少々お待ちください。
<その他参照>
戻り値を返して終了
//注文が成功した場合、trueを返す
if(checkCode == CHECK_RETCODE_OK)
{
Comment("ポジション番号 #",parTicket," SLTP設定した銘柄 ",symbol,", SL: ",request.sl,", TP: ",request.tp);
return(true);
}
//注文が失敗した場合、falseを返す
else return(false);
PositionOpen関数は処理の終わりに、戻り値としてポジション番号(ulong型)を返していましたが、SetSLTP関数の場合、戻り値はbool型です。SLTPの設定に成功した場合はtrue、失敗した場合はfalseを返すよう設計しています。
コード全体の記述内容
コード全体の記述内容は以下の通りです↓
//+------------------------------------------------------------------+
//| 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);
//ストップロスとテイクプロフィットを設定する
bool SetSLTP(ulong parTicket, double parStop, double parProfit = 0);
};//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;
}
//ストップロスとテイクプロフィットを設定する
bool OriginalCTrade::SetSLTP(ulong parTicket,double parStop,double parProfit=0.000000)
{
//インスタンスのリセット
ZeroMemory(request);
ZeroMemory(result);
//ポジション番号を取得し、変数に格納
bool select = PositionSelectByTicket(parTicket);
//銘柄を取得し、変数に格納
string symbol = PositionGetString(POSITION_SYMBOL);
request.action = TRADE_ACTION_SLTP;//取引種別の設定
request.sl = parStop;//ストップロスの設定
request.tp = parProfit;//テイクプロフィットの設定
request.position = parTicket;//ポジション番号の設定
request.symbol = symbol;//銘柄情報の設定
request.type_filling = fillType;//フィルポリシーを設定
request.magic = magicNumber;////マジックナンバーを設定
// リトライ回数とリターンコードを格納する変数を宣言
int retryCount = 0;//再試行回数を格納する
int checkCode = 0;//リターンコードを格納する
do
{ //SLTP設定注文を出す。
bool sent = OrderSend(request,result);
//注文結果を返す
checkCode = ReturnCodeCheck(result.retcode);
//約定成功の場合do-whileループを抜ける
if(checkCode == CHECK_RETCODE_OK) break;
//再試行しても解決しないエラーの場合
else if(checkCode == CHECK_RETCODE_ERROR)
{ //リターンコードを取得
string errDesc = TradeServerReturnCodeDescription(result.retcode);
//アラートを発出
Alert("SLTP設定エラー:エラー内容 ",result.retcode," - ",errDesc);
//ログの出力
TradeLog();
break;//ループを抜ける
}
//再試行すれば解決する可能性のあるエラーの場合
else
{ //ログ出力
Print("サーバーエラーを検知。 再試行中...");
//次の処理までの待機
Sleep(RETRY_MILLISECONDS);
//再試行回数をカウントする変数をインクリメントする
retryCount++;
}
}
while(retryCount < RETRY_LIMIT);
if(retryCount >= RETRY_LIMIT)
{
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Alert("再注文上限回数に達しました: エラー内容 ",result.retcode," - ",errDesc);
}
string errDesc = TradeServerReturnCodeDescription(result.retcode);
Print("SLTP設定ポジション番号 #",parTicket,
": ",result.retcode," - ",errDesc,
", SL: ",request.sl,", TP: ",request.tp,
", Bid: ",SymbolInfoDouble(symbol,SYMBOL_BID),
", Ask: ",SymbolInfoDouble(symbol,SYMBOL_ASK),
", ストップレベル: ",SymbolInfoInteger(symbol,SYMBOL_TRADE_STOPS_LEVEL));
//注文が成功した場合、trueを返す
if(checkCode == CHECK_RETCODE_OK)
{
Comment("ポジション番号 #",parTicket," SLTP設定した銘柄 ",symbol,", SL: ",request.sl,", TP: ",request.tp);
return(true);
}
//注文が失敗した場合、falseを返す
else return(false);
}
今回は以上になります。
最後までお読みいただきありがとうございました<m(__)m>
コメント