OnTesterInit関数について解説した記事内↓にて、
OnTesterInit関数を利用したサンプルコードも掲載しているのですが、同じ記事内で解説するには、ちょっと長くて複雑だったもので、別記事にして数回に分けて解説しています。
前回はClosePositionsByBars関数部分についての解説をおこないました↓
今回解説する記述は以下の箇所です。
//+------------------------------------------------------------------+
//| 成行注文を準備して送信する関数 |
//| 指定された注文内容で成行注文を送信し、成功でtrueを返す |
//+------------------------------------------------------------------+
bool MarketOrder(ENUM_ORDER_TYPE type, double volume, ulong slip, ulong magicnumber, ulong pos_ticket = 0)
{
MqlTradeRequest request = {}; // 取引リクエスト構造体の初期化
MqlTradeResult result = {}; // 取引結果構造体の初期化
double price = SymbolInfoDouble(Symbol(), SYMBOL_BID); // 成行注文の価格を取得
//--- 注文タイプが買いの場合は買い価格(ASK)を設定
if (type == ORDER_TYPE_BUY)
price = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
//--- リクエストパラメータの設定
request.action = TRADE_ACTION_DEAL; // 成行注文を指定
request.position = pos_ticket; // ポジションチケットを指定(決済用)
request.symbol = Symbol(); // 取引シンボルを設定
request.volume = volume; // 取引ボリュームを設定
request.type = type; // 注文タイプ(買いまたは売り)
request.price = price; // 取引価格
request.deviation = slip; // スリッページの許容値
request.magic = magicnumber; // マジックナンバーを設定
//--- 注文を送信し、成功すればtrueを返す
if (!OrderSend(request, result))
{
// 注文送信に失敗した場合、エラーメッセージを出力
PrintFormat("OrderSend %s %s %.2f at %.5f エラーコード: %d", request.symbol, EnumToString(type), volume, request.price, GetLastError());
return (false); // 注文が失敗した場合にfalseを返す
}
//--- 注文が成功した場合の結果を表示
PrintFormat("リクエストコード = %u、取引 = %I64u、注文番号 = %I64u", result.retcode, result.deal, result.order);
return (true); // 成功時にtrueを返す
}
コード解説1
//+------------------------------------------------------------------+
//| 成行注文を準備して送信する関数 |
//| 指定された注文内容で成行注文を送信し、成功でtrueを返す |
//+------------------------------------------------------------------+
bool MarketOrder(ENUM_ORDER_TYPE type, double volume, ulong slip, ulong magicnumber, ulong pos_ticket = 0)
{
MqlTradeRequest request = {}; // 取引リクエスト構造体の初期化
MqlTradeResult result = {}; // 取引結果構造体の初期化
double price = SymbolInfoDouble(Symbol(), SYMBOL_BID); // 成行注文の価格を取得
この部分では、指定された条件で成行注文を準備し、実際に注文を送信するためのMarketOrder関数です。取引リクエストの詳細を設定し、指定した注文内容で成行注文を送信します。注文が正常に実行されればtrue、失敗すればfalseを返します。
関数の引数について
- type(ENUM_ORDER_TYPE型)
注文の種類を指定する引数です。ORDER_TYPE_BUYは成行買い注文、ORDER_TYPE_SELLは成行売り注文を表します。この引数を使って、注文が買いか売りかを指定します。 - volume(double型)
注文の取引量を指定する引数で、ロット単位で表されます。たとえば、0.1を指定すると0.1ロットの成行注文が行われます。ロット数は取引量を決定し、取引の規模を設定します。 - slip(ulong型)
許容スリッページ(価格のずれ)を指定します。スリッページとは、発注時の価格と約定価格の差を指します。値が大きいほど、指定価格からの許容範囲が広がります。 - magicnumber(ulong型)
EAが出した注文を識別するためのマジックナンバーです。これを使用すると、複数のEAが動作している場合でも、それぞれのEAが出した注文を区別できます。 - pos_ticket(ulong型、デフォルト値0)
決済対象のポジションチケット番号です。既存のポジションを指定したボリュームで決済する際に使用します。デフォルト値は0で、新規注文時には指定する必要はありません。
変数と構造体の初期化
- request(MqlTradeRequest構造体)
MqlTradeRequest構造体を初期化し、取引のリクエスト内容を格納します。この構造体には注文の詳細(注文タイプ、取引量、価格など)が含まれており、取引サーバーに送信される内容を設定するために使用します。 - result(MqlTradeResult構造体型)
MqlTradeResult構造体を初期化し、取引の結果を格納します。注文が実行された後、この構造体に取引結果(注文が成功したかどうか、エラーコード、取引の詳細など)が返されます。
成行注文の価格取得
- price
成行注文を実行する価格を格納するための変数です。SymbolInfoDouble関数を使用して、現在のシンボルのBID価格を取得しています。成行注文の種類によっては、このBID価格が実際の注文価格として設定されます(通常、買い注文ではASK価格、売り注文ではBID価格が適用されます)。
この初期化と価格取得により、MarketOrder関数は成行注文を準備し、次のステップで取引内容をリクエストに設定して送信する準備が整います。
コード解説2
この部分では、成行注文のリクエスト内容を設定するために、MqlTradeRequest構造体の各フィールドに具体的な値を割り当てています。これにより、リクエストに必要な注文内容が揃い、サーバーに送信する準備が整います。
注文価格の設定
- 注文タイプが買いの場合に価格を設定
if文で、注文タイプが買いである場合に取引価格として買い価格(ASK)を設定します。typeがORDER_TYPE_BUY(買い注文)の場合、SymbolInfoDouble関数で現在のシンボルのASK価格を取得し、price変数に代入します。通常、買い注文はASK価格で実行され、売り注文はBID価格で実行されます。
リクエストパラメータの設定
- request.action
リクエストの取引操作タイプを指定します。ここではTRADE_ACTION_DEALが設定され、成行注文として即時に実行される取引リクエストを表します成行注文は、現在の市場価格で即時に実行されます。 - request.position
ポジションチケットを指定します。この値は、既存のポジションを決済する際に使用されます。新規の成行注文の場合にはデフォルト値の0が適用されますが、ポジションを特定して決済する際には指定されたチケット番号で対象ポジションを指定します。 - request.symbol
取引を行うシンボルを設定します。Symbol関数で現在のシンボル(通貨ペアや銘柄)を取得し、リクエスト内に設定することで、どのシンボルに対して注文を行うかが決まります。 - request.volume
取引量(ボリューム)を設定します。volume引数の値が設定され、取引のロット数を決定します。 - request.type
注文のタイプを設定します。type引数には、買い注文の場合ORDER_TYPE_BUY、売り注文の場合ORDER_TYPE_SELLが指定されます。これにより、リクエストが買い注文か売り注文かを明確にします。 - request.price
取引価格を設定します。ここで指定された価格は、通常は買い注文でASK価格、売り注文でBID価格を使用します。リクエストが送信される時点での適切な価格が設定されます。 - request.deviation
許容スリッページの範囲を設定します。スリッページとは、指定価格と実際の約定価格の差です。この範囲内であれば、価格がずれても注文が実行されます。 - request.magic
マジックナンバーを設定します。EAが出した注文を識別するための番号で、magicnumber引数の値が設定されます。これにより、他のEAや手動取引と区別でき、ポジション管理がしやすくなります。
この設定により、Request構造体にすべての注文情報が揃い、サーバーに送信される具体的な取引内容が整います。
コード解説3
//--- 注文を送信し、成功すればtrueを返す
if (!OrderSend(request, result))
{
// 注文送信に失敗した場合、エラーメッセージを出力
PrintFormat("OrderSend %s %s %.2f at %.5f エラーコード: %d", request.symbol, EnumToString(type), volume, request.price, GetLastError());
return (false); // 注文が失敗した場合にfalseを返す
}
//--- 注文が成功した場合の結果を表示
PrintFormat("リクエストコード = %u、取引 = %I64u、注文番号 = %I64u", result.retcode, result.deal, result.order);
return (true); // 成功時にtrueを返す
}
この部分のコードは、リクエストに基づいて注文を送信し、成功したかどうかに応じてtrueまたはfalseを返します。注文送信が成功した場合には取引結果の詳細を表示し、失敗した場合にはエラーメッセージを表示します。
注文送信の処理
- OrderSend関数
OrderSend関数は、MqlTradeRequest構造体で設定された注文内容をサーバーに送信し、その結果をMqlTradeResult構造体に格納します。関数がtrueを返した場合は注文が成功し、falseを返した場合は注文が失敗したことを意味します。
注文送信に失敗した場合の処理
- PrintFormat関数
PrintFormat関数を使い、エラーメッセージをフォーマットしてログに出力します。このメッセージには、シンボル名、注文のタイプ、取引量、価格、エラーコードが含まれており、どの注文がどのような理由で失敗したのかを確認できます。
PrintFormatの引数に使われている関数と値の解説
- request.symbol
現在の取引シンボル(通貨ペアや銘柄)を取得します。これにより、どのシンボルの注文でエラーが発生したかが確認できます。 - EnumToString(type)
注文タイプ(type)の値を文字列に変換します。EnumToString関数により、数値として保持されているenum列挙型の値が文字列として表示され、ここでは「ORDER_TYPE_BUY」または「ORDER_TYPE_SELL」と表示されます。 - volume
取引ボリュームを表示します。ロット数として表示されるため、注文の規模が確認できます。 - request.price
指定した価格での注文が失敗したことを示します。価格は成行注文が実行されるべき価格を示し、価格に問題がある場合などの確認に使えます。 - GetLastError()
エラーコードを取得します。注文が失敗した場合、GetLastError関数により発生したエラーコードを取得し、エラーメッセージに含めることで原因がわかります。
フォーマット指定子の解説
- %s
文字列を表示する指定子です。request.symbolやEnumToString(type)の値を表示します。 - %.2f
小数点以下2桁まで表示する指定子です。volumeの値が取引量として表示されます。 - %.5f
小数点以下5桁まで表示する指定子です。request.priceが注文価格として表示されます。 - %d
整数値を表示する指定子です。GetLastError関数の戻り値であるエラーコードが表示されます。
注文成功時の処理
- PrintFormat関数による結果表示
PrintFormat関数を使い、注文が成功した場合に取引結果をログに出力します。これにより、リクエストコード、取引ID、注文番号が表示され、注文が正しく実行されたことが確認できます。
フォーマット指定子の解説(注文成功時)
- %u
符号なし整数を表示する指定子です。リクエスト結果のコード(retcode)に対応し、取引が正常に実行されたかを示します。 - %I64u
64ビット符号なし整数を表示する指定子です。取引のID(deal)と注文番号(order)が表示されます。
このように、PrintFormat関数を使ってログを詳細に出力し、注文が成功したかどうかやエラーの原因を確認する手がかりを提供します。
サンプルコードの全体記述
OnTesterInit関数を利用したサンプルコードの全体記述は以下の通りです。
//--- EAの設定パラメータ
input double lots = 0.1; // 取引量を設定(ロット単位)
input double kATR = 3; // ATRのシグナル判断に使用する倍率
input int ATRperiod = 20; // ATRの計算期間(ローソク足の本数)
input int holdbars = 8; // ポジションを保持するバー数
input int slippage = 10; // 許容されるスリッページの値
input bool revers = false; // シグナルの反転設定
input ulong EXPERT_MAGIC = 0; // EAの識別用のマジックナンバー
//--- 変数宣言
int atr_handle; // ATR指標のハンドルを格納する変数
double last_atr, last_body; // 最後に取得したATR値とローソク足の実体サイズを格納
datetime lastbar_timeopen; // 最後のバーが開いた時間
double trade_lot; // 実行する取引のロット数を格納
datetime optimization_start; // 最適化開始時刻を記録
string report; // 結果を格納する文字列
//+------------------------------------------------------------------+
//| TesterInit関数 |
//| 最適化開始前に呼び出され、EAパラメータの初期化を行う |
//+------------------------------------------------------------------+
void OnTesterInit()
{
//--- 最適化パラメータの範囲を設定
ParameterSetRange("lots", false, 0.1, 0, 0, 0); // 取引量の最小値、最大値、初期値の設定
ParameterSetRange("kATR", true, 3.0, 1.0, 0.3, 7.0); // ATR倍率の設定範囲
ParameterSetRange("ATRperiod", true, 10, 15, 1, 30); // ATR計算期間の設定範囲
ParameterSetRange("holdbars", true, 5, 3, 1, 15); // 保持バー数の設定範囲
ParameterSetRange("slippage", false, 10, 0, 0, 0); // スリッページの設定
ParameterSetRange("revers", true, false, false, 1, true); // シグナル反転の設定
ParameterSetRange("EXPERT_MAGIC", false, 123456, 0, 0, 0); // EA識別用マジックナンバーの設定
//--- 初期化完了メッセージを出力
Print("初期化と最適化パラメータが設定されました");
//--- 最適化開始時刻を記録
optimization_start = TimeLocal();
//--- チャートおよびログに最適化開始のメッセージを表示
report = StringFormat("%s: 最適化は %s に開始されました", __FUNCTION__, TimeToString(TimeLocal(), TIME_MINUTES|TIME_SECONDS));
Print(report); // ログ出力
Comment(report); // チャート上に表示
}
//+------------------------------------------------------------------+
//| TesterDeinit関数 |
//| 最適化終了後に呼び出され、実行時間を出力する |
//+------------------------------------------------------------------+
void OnTesterDeinit()
{
//--- 実行時間を計算してログ出力
string log_message = StringFormat("%s: 最適化にかかった時間は %d 秒", __FUNCTION__, TimeLocal() - optimization_start);
PrintFormat(log_message); // 実行時間をログに出力
//--- 結果をレポートに追加し、画面表示を更新
report = report + "\r\n" + log_message;
Comment(report);
}
//+------------------------------------------------------------------+
//| EA初期化関数 |
//| EAの開始時に呼び出され、変数やATR指標の初期化を行う |
//+------------------------------------------------------------------+
int OnInit()
{
//--- 初期値設定
last_atr = 0; // ATR値の初期化
last_body = 0; // 最後のローソク足の実体サイズの初期化
//--- ロット数を設定(最小ロットと指定ロットを比較して設定)
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); // 最小ロットサイズを取得
trade_lot = lots > min_lot ? lots : min_lot; // 最小ロット以上の場合に設定
//--- ATR指標ハンドルを作成
atr_handle = iATR(_Symbol, _Period, ATRperiod); // ATRのハンドルを作成
if (atr_handle == INVALID_HANDLE) // ハンドル取得に失敗した場合
{
// ATRの作成に失敗した場合、エラーメッセージを出力
PrintFormat("%s: iATRの作成に失敗しました。エラーコード: %d", __FUNCTION__, GetLastError());
return (INIT_FAILED); // 初期化失敗を返す
}
//--- EAの初期化が成功したことを示す戻り値
return (INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 新しいティックが到着したときに呼び出される |
//| 各バーごとに取引シグナルを確認し、必要ならばポジションを取る |
//+------------------------------------------------------------------+
void OnTick()
{
//--- 取引シグナルを保持する変数(0はシグナルなし、1は買い、-1は売り)
static int signal = 0;
//--- 保持バー数に基づいて古いポジションを決済
ClosePositionsByBars(holdbars, slippage, EXPERT_MAGIC);
//--- 新しいバーが生成されたか確認
if (isNewBar())
{
signal = CheckSignal(); // シグナルを確認し、値を取得
}
//--- ネッティングモードでポジションが開かれている場合、シグナルを無効にして終了
if (signal != 0 && PositionsTotal() > 0 && (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_NETTING)
{
signal = 0; // シグナルを無効化
return; // 新しいティックイベントを終了
}
//--- ヘッジモードの場合、シグナルに基づいてポジションを取る
if (signal != 0)
{
//--- 買いシグナルの場合
if (signal > 0)
{
PrintFormat("%s: 買いシグナル!Revers = %s", __FUNCTION__, string(revers));
if (Buy(trade_lot, slippage, EXPERT_MAGIC)) signal = 0; // 買い注文が成功したらシグナルをリセット
}
//--- 売りシグナルの場合
else if (signal < 0)
{
PrintFormat("%s: 売りシグナル!Revers = %s", __FUNCTION__, string(revers));
if (Sell(trade_lot, slippage, EXPERT_MAGIC)) signal = 0; // 売り注文が成功したらシグナルをリセット
}
}
}
//+------------------------------------------------------------------+
//| 取引シグナルを生成する関数 |
//| ATRを使用してシグナルを確認し、シグナルがあれば1(買い)または-1(売り)を返す |
//+------------------------------------------------------------------+
int CheckSignal()
{
int res = 0; // 初期値(シグナルがない状態)
//--- ATR指標の値を取得
double atr_value[1]; // ATR値を格納する配列
if (CopyBuffer(atr_handle, 0, 2, 1, atr_value) != -1) // ATR指標から値を取得
{
last_atr = atr_value[0]; // 最新のATR値を変数に格納
//--- 最新のバーのデータを取得
MqlRates bar[1]; // ローソク足のデータを格納する変数
if (CopyRates(_Symbol, _Period, 1, 1, bar) != -1) // 最新のローソク足データを取得
{
last_body = bar[0].close - bar[0].open; // 実体サイズを計算(終値-始値)
//--- 実体サイズがATRの指定倍率を超えた場合、シグナルを設定
if (MathAbs(last_body) > kATR * last_atr) // 実体が一定以上の場合
res = last_body > 0 ? 1 : -1; // 上昇ローソクなら買いシグナル、下降なら売りシグナル
}
else // ローソク足データの取得に失敗した場合
{
PrintFormat("%s: 最新のバー取得に失敗!エラー: %d", __FUNCTION__, GetLastError());
}
}
else // ATR指標値の取得に失敗した場合
{
PrintFormat("%s: ATR指標値取得に失敗!エラー: %d", __FUNCTION__, GetLastError());
}
//--- シグナル反転の設定がされている場合、シグナルを反転
res = revers ? -res : res; // シグナルを反転
return (res); // シグナル値を返す
}
//+------------------------------------------------------------------+
//| 新しいバーが生成されたか確認する関数 |
//| 新しいバーが生成された場合にtrueを返し、そうでない場合はfalseを返す|
//+------------------------------------------------------------------+
bool isNewBar(const bool print_log = true)
{
static datetime bartime = 0; // 現在のバーの開始時刻を保持する変数
//--- 現在のバー(ゼロバー)の開始時刻を取得
datetime currbar_time = iTime(_Symbol, _Period, 0);
//--- 以前のバーと異なる時刻なら新しいバーが生成されたと判断
if (bartime != currbar_time)
{
bartime = currbar_time; // 現在のバー開始時刻を更新
lastbar_timeopen = bartime; // 最後のバー開始時刻を保存
//--- 新しいバーが生成されたことをログに表示(最適化またはテスト中でない場合)
if (print_log && !(MQLInfoInteger(MQL_OPTIMIZATION) || MQLInfoInteger(MQL_TESTER)))
{
PrintFormat("%s: %s %s に新しいバーが生成されました", __FUNCTION__, _Symbol, StringSubstr(EnumToString(_Period), 7));
//--- 最後のティックデータを取得
MqlTick last_tick;
if (!SymbolInfoTick(Symbol(), last_tick)) // ティック情報の取得に失敗した場合
Print("SymbolInfoTick() に失敗しました。エラーコード:", GetLastError());
//--- 最後のティックの時刻をミリ秒まで表示
PrintFormat("最終ティックの時刻: %s.%03d", TimeToString(last_tick.time, TIME_SECONDS), last_tick.time_msc % 1000);
}
return (true); // 新しいバーが生成された場合はtrueを返す
}
return (false); // 新しいバーが生成されていない場合はfalseを返す
}
//+------------------------------------------------------------------+
//| 成行価格で指定の量を買う関数 |
//| 指定されたボリュームで成行買い注文を実行し、成功でtrueを返す |
//+------------------------------------------------------------------+
bool Buy(double volume, ulong deviation = 10, ulong magicnumber = 0)
{
//--- 買い注文を行う
return (MarketOrder(ORDER_TYPE_BUY, volume, deviation, magicnumber));
}
//+------------------------------------------------------------------+
//| 成行価格で指定の量を売る関数 |
//| 指定されたボリュームで成行売り注文を実行し、成功でtrueを返す |
//+------------------------------------------------------------------+
bool Sell(double volume, ulong deviation = 10, ulong magicnumber = 0)
{
//--- 売り注文を行う
return (MarketOrder(ORDER_TYPE_SELL, volume, deviation, magicnumber));
}
//+------------------------------------------------------------------+
//| 保持バー数に基づき古いポジションを決済する関数 |
//| 指定されたバー数より前に開かれたポジションを確認し、条件が合えば決済|
//+------------------------------------------------------------------+
void ClosePositionsByBars(int holdtimebars, ulong deviation = 10, ulong magicnumber = 0)
{
int total = PositionsTotal(); // 現在のポジション数を取得
//--- ポジションリストを逆順に確認(最古のポジションを優先)
for (int i = total - 1; i >= 0; i--)
{
//--- ポジションの情報を取得
ulong position_ticket = PositionGetTicket(i); // ポジションチケット番号
string position_symbol = PositionGetString(POSITION_SYMBOL); // ポジションのシンボル
ulong magic = PositionGetInteger(POSITION_MAGIC); // マジックナンバー
datetime position_open = (datetime)PositionGetInteger(POSITION_TIME); // ポジションオープン時刻
//--- ポジションが開かれたバー数を計算
int bars = iBarShift(_Symbol, PERIOD_CURRENT, position_open) + 1;
//--- ポジションが指定バー数以上前に開かれている場合かつ条件一致時に決済
if (bars > holdtimebars && magic == magicnumber && position_symbol == _Symbol)
{
int digits = (int)SymbolInfoInteger(position_symbol, SYMBOL_DIGITS); // 小数点以下桁数
double volume = PositionGetDouble(POSITION_VOLUME); // ポジションのボリューム
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // ポジションタイプ
string str_type = StringSubstr(EnumToString(type), 14);
StringToLower(str_type); // 表示フォーマットのため小文字に変換
//--- ポジションの決済操作ログ
PrintFormat("ポジション #%I64u %s %s %.2f の決済を行います", position_ticket, position_symbol, str_type, volume);
//--- ポジションタイプに応じて売りまたは買い注文を行い決済
if (type == POSITION_TYPE_BUY)
MarketOrder(ORDER_TYPE_SELL, volume, deviation, magicnumber, position_ticket);
else
MarketOrder(ORDER_TYPE_BUY, volume, deviation, magicnumber, position_ticket);
}
}
}
//+------------------------------------------------------------------+
//| 成行注文を準備して送信する関数 |
//| 指定された注文内容で成行注文を送信し、成功でtrueを返す |
//+------------------------------------------------------------------+
bool MarketOrder(ENUM_ORDER_TYPE type, double volume, ulong slip, ulong magicnumber, ulong pos_ticket = 0)
{
MqlTradeRequest request = {}; // 取引リクエスト構造体の初期化
MqlTradeResult result = {}; // 取引結果構造体の初期化
double price = SymbolInfoDouble(Symbol(), SYMBOL_BID); // 成行注文の価格を取得
//--- 注文タイプが買いの場合は買い価格(ASK)を設定
if (type == ORDER_TYPE_BUY)
price = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
//--- リクエストパラメータの設定
request.action = TRADE_ACTION_DEAL; // 成行注文を指定
request.position = pos_ticket; // ポジションチケットを指定(決済用)
request.symbol = Symbol(); // 取引シンボルを設定
request.volume = volume; // 取引ボリュームを設定
request.type = type; // 注文タイプ(買いまたは売り)
request.price = price; // 取引価格
request.deviation = slip; // スリッページの許容値
request.magic = magicnumber; // マジックナンバーを設定
//--- 注文を送信し、成功すればtrueを返す
if (!OrderSend(request, result))
{
// 注文送信に失敗した場合、エラーメッセージを出力
PrintFormat("OrderSend %s %s %.2f at %.5f エラーコード: %d", request.symbol, EnumToString(type), volume, request.price, GetLastError());
return (false); // 注文が失敗した場合にfalseを返す
}
//--- 注文が成功した場合の結果を表示
PrintFormat("リクエストコード = %u、取引 = %I64u、注文番号 = %I64u", result.retcode, result.deal, result.order);
return (true); // 成功時にtrueを返す
}