OnTesterInit関数の働き・役割
OnTesterInit関数は、EAの最適化プロセスが開始される前に、ストラテジーテスターでの設定や初期化を行うために使用されます。この関数は、TesterInitイベントが発生したときに自動的に呼び出され、EAの初期化処理やパラメータの設定を行います。
この関数には、以下の2つの形式があります。
int OnTesterInit(void);
結果を返す形式:このバージョンは最適化初期化の成否を示すための整数型の戻り値を返します。例えば、正常に初期化が完了した場合はゼロ(INIT_SUCCEEDED)を返し、エラーが発生した場合にはエラーコードを返します。エラーが発生した場合には、最適化は中断されます。
void OnTesterInit(void);
結果を返さない形式:このバージョンは、主に互換性を保つために残されていますが、使用は推奨されません。
OnTesterInit関数を使用することで、EAの最適化プロセス前に最適化に必要なパラメータの初期設定や制限を行うことが可能になります。また、他のイベントハンドラと連携し、最適化の各段階で必要な処理を実行する際の重要な役割を担っています。
OnTesterInit関数を使う際の注意点
OnTesterInit関数は、ストラテジーテスターでの最適化が開始される前に実行されますが、ここで設定した初期化やパラメータは、最適化のすべてのパスに共通して適用されます。そのため、各パスごとに異なる設定が必要な場合は、別のイベントハンドラで対応する必要があります。
※ここでの「パス」とは、ストラテジーテスターでの最適化プロセスにおける「一つ一つのテストの組み合わせ」を意味します。MQL5では、最適化を行う際にEAのさまざまなパラメータ(例えばロットサイズ、ATR期間など)を複数の異なる値に設定して、それぞれの組み合わせをテストします。この各組み合わせが「パス」と呼ばれ、最適化プロセスでは数百から数千ものパスを一度にテストすることができます。
例えば、あるEAが「ロットサイズ」「ATR期間」「スリッページ」の3つのパラメータを持ち、それぞれ異なる値を取るように設定したとします。テスターはそれぞれのパラメータ組み合わせを順にテストし、どの設定が最も良い結果を出すかを調べます。この「1つの組み合わせでのテスト」が1つのパスです
OnTesterInit関数の処理時間には制限があり、長時間実行すると最適化がキャンセルされ、EAが強制的に終了されます。複雑な処理が必要な場合は、この関数ではなく他の関数で処理を行うように設計することが求められます。
OnTesterInit関数は、Init、Deinit、NewTickイベントなどの標準的なイベントを受け取りません。最適化中の初期化やデータ収集には、OnTesterInit、OnTesterDeinit、OnTesterPassといったイベントハンドラを用いる必要があります。
また、OnTesterInit関数は、テスターで指定されたシンボル(特定の通貨ペアや商品の識別子)や期間に基づいて動作するため、依存性がある設定の場合はそのシンボルや期間の範囲内で正しく機能するか確認してから使用してください。
OnTesterInit関数を使ってEAを作る際のアイディア
OnTesterInit関数は、最適化プロセスの開始前にEAの初期化やパラメータ設定を行うため、EAのパフォーマンス向上や精密なバックテストに役立つさまざまなアイディアを実現できます。以下に、OnTesterInit関数を使ったEA作成のアイディアをいくつか紹介します。
リスク管理と資金管理の最適化
OnTesterInit関数を利用して、リスクや資金管理パラメータの範囲を最適化することが可能です。たとえば、リスク許容範囲に応じたロット数、最大損失設定、トレーリングストップ設定などの幅広い条件を試行することで、最適なリスク管理戦略を発見できます。特に異なる資金量や取引スタイルに対応するEAを開発する場合、これにより汎用性の高いEAの構築が期待できます。
複数の時間軸を利用したトレンドフォロー戦略
OnTesterInit関数でATRやスリッページ設定などのパラメータを最適化することで、複数の時間軸を活用したトレンドフォロー型のEAを開発することができます。短期と長期のATR値の範囲を設定してトレンドの強さを測り、それに応じて取引方向や取引量を動的に変更することで、トレンドに応じた柔軟な取引が可能になります。
通貨ペアごとの最適化パラメータ設定
OnTesterInit関数を活用して、異なる通貨ペアごとに最適な取引パラメータを設定するEAを構築できます。たとえば、主要な通貨ペア(EUR/USDやUSD/JPYなど)ではリスク設定やATR期間の最適化を行い、通貨ペアの特徴に合わせた最適な設定を探ることが可能です。このように通貨ペアの特性に合わせたEAを構築することで、より正確な取引が期待できます。
ATRとローソク足の実体差による逆張り戦略
OnTesterInit関数で、ATRの期間や倍率を最適化し、ローソク足の実体がATR値を大きく上回る場合に逆張り取引を行うEAを作成できます。これは、価格の急激な変動時にトレンドが反転しやすいことを利用する戦略です。このようなEAは、ATR値とローソク足の実体差を基にシグナルを判定し、逆張りポイントを見極めます。
自動的な最適化レポート出力機能の追加
OnTesterInit関数とOnTesterDeinit関数を組み合わせて、EAが最適化終了時に自動的に最適化結果を整理・レポート出力する機能を追加することも可能です。たとえば、最適なパラメータセットをレポート形式でエキスパートログに記録したり、ファイルとして保存することで、最適化結果を分析しやすくなります。
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を返す
}
このコードは、エキスパートアドバイザー(EA)が最適化プロセスを行う際に、特定の初期化とパラメータ設定を実施し、ATR(Average True Range)指標を使って取引シグナルを判断します。EAが起動されると、最初にOnTesterInit関数が呼ばれて最適化のためのパラメータ範囲が設定されます。その後、OnInit関数でATR指標を初期化し、最適化実行時間などの初期データをセットします。
次に、新しいティックが到着するたびにOnTick関数が呼ばれ、ATR指標を基にして取引シグナルを生成します。ATRを用いることで、指定された条件を満たすローソク足の変動が検出されたときに売買シグナルを発生させます。例えば、一定のATR倍数を超えるローソク足の実体が形成された場合、そのローソク足の動きに応じて「買い」もしくは「売り」のシグナルを生成します。また、指定されたバー数(holdbars)を超えたポジションがあれば自動的に決済されます。
シグナルが生成され、現在のポジションが空の場合、取引が実行されます。取引実行の際には、設定されたスリッページやEA識別用のマジックナンバーが適用され、取引の正確な管理が行われます。最適化プロセスが終了すると、OnTesterDeinit関数で最適化にかかった時間が表示され、レポートが画面に表示されます。
詳細なコード解説をこの記事内でおこなうと、あまりにも長くなってしまうので、各ブロックごとのコード解説は別記事でおこないます。以下の記事群をご参照ください。
・OnTesterInit関数を使ったサンプルコードの解説その1-グローバル領域での定義-
・OnTesterInit関数を使ったサンプルコードの解説その2-OnTesterInit関数部分-
・OnTesterInit関数を使ったサンプルコードの解説その3-OnTesterDeinit関数部分-
・OnTesterInit関数を使ったサンプルコードの解説その4-OnInit関数部分-
・OnTesterInit関数を使ったサンプルコードの解説その5-CheckSignal関数部分-
・OnTesterInit関数を使ったサンプルコードの解説その6-isNewBar関数部分-
・OnTesterInit関数を使ったサンプルコードの解説その7-Buy関数とSell関数部分-