OnTesterInit関数を使ったサンプルコードの解説その2-OnTesterInit関数部分-

MQL5リファレンス

OnTesterInit関数について解説した記事内↓にて、

OnTesterInit関数を利用したサンプルコードも掲載しているのですが、同じ記事内で解説するには、ちょっと長くて複雑だったもので、別記事にして数回に分けて解説しています。

前回はグローバル領域での定義部分について解説しました↓

今回解説する記述は以下の箇所です。

//+------------------------------------------------------------------+
//| 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); // チャート上に表示
}

このコードは、ストラテジーテスターEAの最適化が開始される前に実行されるOnTesterInit関数を定義しています。この関数では、最適化で使用するパラメータの範囲や初期値を設定し、EAの最適化開始時刻やメッセージの表示を行います。

コードの詳細解説

  • ParameterSetRange関数を用いて、最適化する各パラメータの設定範囲と初期値を指定しています。
  • lotsでは取引量を設定します。最適化対象から除外され、初期値として0.1が指定されています。
  • kATRではATR指標を利用したシグナル生成の倍率を設定します。最適化対象であり、初期値3.0、最小1.0、最大7.0、ステップ0.3で設定されています。
  • ATRperiodではATRの計算に使用する期間(ローソク足の数)を指定します。最適化対象で、初期値10、範囲は15から30、ステップ1で設定されています。
  • holdbarsはポジションを保持するバー数を設定します。最適化対象で、初期値5、範囲は3から15、ステップ1です。
  • slippageは許容スリッページを設定し、取引実行時の価格のずれを防止する基準を定めています。このパラメータは最適化対象外で、値は10です。
  • reversはシグナル反転のフラグです。最適化対象で、初期値と最小値はfalse、最大値true、ステップ1で設定されています。
  • EXPERT_MAGICはEAマジックナンバーです。EAが開いたポジションを識別するために使用され、最適化対象外で値は123456です。
  • Print関数を使い、EAの初期化と最適化パラメータの設定完了をログに出力しています。
  • TimeLocal関数を利用し、最適化の開始時刻をoptimization_start変数に記録します。最適化プロセスが完了した際にこの開始時刻を参照し、所要時間を計算するのに利用します。
  • StringFormat関数を使用し、最適化開始メッセージを作成してreport変数に格納しています。このメッセージは、ログ(Print関数)とチャート上(Comment関数)に表示され、EAの最適化が開始されたことを視覚的に確認するために役立ちます。

このように、OnTesterInit関数では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を返す
}
タイトルとURLをコピーしました