OnTester関数を使ったサンプルコードの解説その5:OnInit関数とOnTick関数の記述

MQL5リファレンス

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

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

「口座タイプを考慮してポジションを選択する 関数」であるSelectPosition関数についての解説をおこないました。

今回はイベントハンドラーであるOnInit関数OnTick関数内の記述について解説していきます。

解説する箇所は以下の部分です。

//+------------------------------------------------------------------+
//| エキスパート初期化関数                                           |
//+------------------------------------------------------------------+
int OnInit(void)
{
    //--- 取引タイプ(ネッティングまたはヘッジ)を設定する
    IsHedging = ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);

    //--- 正しいポジション制御のために取引操作クラスのオブジェクトを初期化する
    trade.SetExpertMagicNumber(EA_MAGIC); // マジックナンバーを設定
    trade.SetMarginMode();                // 証拠金モードを設定
    trade.SetTypeFillingBySymbol(Symbol());// シンボルに基づいた執行タイプを設定
    trade.SetDeviationInPoints(Slippage); // 許容スリッページを設定

    //--- 移動平均指標を作成する
    IndicatorHandle = iMA(_Symbol, _Period, MovingPeriod, MovingShift, MODE_SMA, PRICE_CLOSE);
    if (IndicatorHandle == INVALID_HANDLE)
    {
        printf("Error creating iMA indicator");
        return (INIT_FAILED);
    }

    //--- 初期化成功を示す
    return (INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| エキスパートティック関数                                         |
//+------------------------------------------------------------------+
void OnTick(void)
{
    //--- ポジションが既に開かれている場合は、決済条件を確認する
    if (SelectPosition())
        CheckForClose();

    //--- ポジションを開く条件を確認する
    CheckForOpen();
}
スポンサーリンク
スポンサーリンク

OnInit関数の解説1

このセクションでは、エキスパートアドバイザEA)の初期化を行うOnInit関数について解説します。OnInit関数は、EAの設定や初期化処理を行い、取引の準備を整えるために重要な役割を果たします。

取引タイプの設定

int OnInit(void)
{
    //--- 取引タイプ(ネッティングまたはヘッジ)を設定する
    IsHedging = ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);

まず、取引口座がヘッジング口座かネッティング口座かを設定します。
AccountInfoInteger関数を使用して口座の証拠金モードを取得し、それがACCOUNT_MARGIN_MODE_RETAIL_HEDGINGである場合にIsHedgingをtrueに設定します。
ここでは、ENUM_ACCOUNT_MARGIN_MODEへのタイプキャストを行って、AccountInfoInteger関数から取得した値を適切に比較しています。タイプキャストとは、あるデータ型を別のデータ型に変換する処理のことです。これにより、口座のタイプに応じた取引管理が可能になります。

取引操作クラスのオブジェクトを初期化する

    //--- 正しいポジション制御のために取引操作クラスのオブジェクトを初期化する
    trade.SetExpertMagicNumber(EA_MAGIC); // マジックナンバーを設定
    trade.SetMarginMode();                // 証拠金モードを設定
    trade.SetTypeFillingBySymbol(Symbol());// シンボルに基づいた執行タイプを設定
    trade.SetDeviationInPoints(Slippage); // 許容スリッページを設定

次に、取引操作クラス(CTradeクラス)のインスタンスtradeを初期化します。

  1. SetExpertMagicNumberメソッドを使用して、EAマジックナンバーを設定します。このマジックナンバーは、EAが発行する注文を識別するために使用されます。
  2. SetMarginModeメソッドを使用して、証拠金モードを設定します。
    証拠金モードとは、取引口座がヘッジング可能かどうかを示す設定です。これにより、口座のタイプに応じた取引管理が可能になります。
  3. SetTypeFillingBySymbolメソッドを使用して、現在のシンボルに基づいた執行タイプを設定します。執行モードとは、注文がどのように処理されるか(例えば、即時執行か成行執行か)を示す設定です。
    ※詳細はMQL5 EA講座  第58回「成行注文と執行方式について」をご覧ください。
  4. SetDeviationInPointsメソッドを使用して、許容スリッページ(価格変動幅)を設定します。

移動平均指標を作成する

    //--- 移動平均指標を作成する
    IndicatorHandle = iMA(_Symbol, _Period, MovingPeriod, MovingShift, MODE_SMA, PRICE_CLOSE);
    if (IndicatorHandle == INVALID_HANDLE)
    {
        printf("Error creating iMA indicator");
        return (INIT_FAILED);
    }

次に、移動平均を作成します。iMA関数を使用して、指定されたシンボル、期間、移動平均の種類(ここでは単純移動平均、MODE_SMA)、価格(終値、PRICE_CLOSE)に基づいて移動平均指標を作成し、そのハンドルをIndicatorHandleに格納します。
もし、指標の作成に失敗した場合は、エラーメッセージを出力し、INIT_FAILEDを返して初期化を中止します。

初期化成功を示す

    //--- 初期化成功を示す
    return (INIT_SUCCEEDED);
}

最後に、初期化が成功したことを示すためにINIT_SUCCEEDEDを返します。

このようにして、OnInit関数は、EAの動作に必要な初期設定を行い、取引の準備を整えます。各設定や初期化処理が適切に行われることで、EAは正しく機能するようになります。

OnInit関数の解説2

このセクションでは、OnInit関数の中で移動平均指標を作成し、初期化の成否を確認する部分について解説します。

移動平均指標を作成する

//--- 移動平均指標を作成する
IndicatorHandle = iMA(_Symbol, _Period, MovingPeriod, MovingShift, MODE_SMA, PRICE_CLOSE);
if (IndicatorHandle == INVALID_HANDLE)
{
    printf("Error creating iMA indicator");
    return (INIT_FAILED);
}

ここでは、iMA関数を使用して移動平均指標を作成します。iMA関数の働きは、指定されたパラメータに基づいて移動平均を計算し、そのハンドル識別子)を返すことです。具体的には以下のパラメータを使用します:

  • シンボル: 現在の通貨ペアのシンボルを指定します。
  • 時間枠: 現在の時間枠を指定します。
  • 移動平均期間: 移動平均を計算する期間を指定します。
  • 移動平均シフト: 移動平均をシフトする値を指定します。
  • 単純移動平均: 単純移動平均(SMA)を指定します。
  • 終値: 終値を使用することを指定します。

この関数が成功すると、移動平均指標のハンドル変数IndicatorHandleに格納されます。

次に、IndicatorHandleがINVALID_HANDLEと等しいかどうかをチェックします。INVALID_HANDLEは、指標の作成に失敗したことを示します。

もし、iMA関数INVALID_HANDLEを返した場合、printf関数を使用してエラーメッセージ「Error creating iMA indicator」を出力し、INIT_FAILEDを返して初期化を中止します。

初期化成功を示す

//--- 初期化成功を示す
return (INIT_SUCCEEDED);

最後に、初期化が成功したことを示すためにINIT_SUCCEEDEDを返します。これにより、EAの初期化が正常に完了し、取引の準備が整ったことを示します。

このようにして、OnInit関数は、移動平均指標の作成と初期化の成否を確認する重要な役割を果たします。各設定や初期化処理が適切に行われることで、EAは正しく機能するようになります。

OnTick関数の解説

このセクションでは、エキスパートアドバイザEA)の主要な関数の一つであるOnTick関数について解説します。OnTick関数は、新しいティック(価格変動)が発生するたびに呼び出され、取引ロジックを実行します。

関数の概要

OnTick関数は、新しいティックが発生するたびに以下の処理を行います。

ポジションが既に開かれている場合は、決済条件を確認する

//--- ポジションが既に開かれている場合は、決済条件を確認する
if (SelectPosition())
    CheckForClose();

まず、SelectPosition関数を呼び出して、既存のポジションがあるかどうかを確認します。SelectPosition関数は、口座のタイプ(ヘッジング口座かネッティング口座か)に基づいてポジションを選択します。ポジションが既に開かれている場合、CheckForClose関数を呼び出して、ポジションを閉じる条件を確認します。

ポジションを開く条件を確認する

//--- ポジションを開く条件を確認する
CheckForOpen();

次に、CheckForOpen関数を呼び出して、ポジションを開く条件を確認します。この関数は、市場条件に基づいて新しいポジションを開くべきかどうかを判断します。

まとめ

このコードは、ネッティング口座でもヘッジング口座でも、実質的に同時に1つのポジションしか保持できない設計になっています。

ネッティング口座の場合

ネッティング口座では、同じシンボルでのポジションは1つしか保持できません。既存のポジションがある場合、新しいポジションは既存のポジションと相殺されます。
コードの中で、SelectPosition関数が呼び出され、既存のポジションを確認し、ある場合はCheckForClose関数で決済条件を確認するため、基本的に1つのポジションのみを管理する設計になっています。

ヘッジング口座の場合

ヘッジング口座では、同じシンボルで買いと売りのポジションを同時に持つことができます。しかし、このコードでは、SelectPosition関数でポジションを確認し、既存のポジションがある場合はCheckForClose関数を呼び出して決済条件を確認しています。これにより、新しいポジションを開く前に既存のポジションを確認・決済するため、実質的に1つのポジションしか保持しないように設計されています。

サンプルコードの全体記述

OnTester関数を利用したサンプルコードの全体記述は以下の通りです。

//-- 取引操作クラスをインクルードする
#include <Trade\Trade.mqh>

//--- EA入力パラメータ
input double Lots = 0.1;        // ロット数(取引量)
input int Slippage = 10;        // 許容されるスリッページ(価格変動幅)
input int MovingPeriod = 80;    // 移動平均の期間
input int MovingShift = 6;      // 移動平均のシフト値

//--- グローバル変数
int IndicatorHandle = 0;    // インジケータのハンドル(識別子)
bool IsHedging = false;     // ヘッジング口座フラグ
CTrade trade;               // 取引操作クラスのインスタンス

//--- マジックナンバーの定義(識別子)
#define EA_MAGIC 18052018

//+------------------------------------------------------------------+
//| ポジションを開く条件を確認する                                   |
//+------------------------------------------------------------------+
void CheckForOpen(void)
{
    MqlRates rt[2]; // 過去2つのローソク足データを格納するための配列

    //--- 新しいバーの始めのみで取引する
    if (CopyRates(_Symbol, _Period, 0, 2, rt) != 2)
    {
        Print("CopyRates of ", _Symbol, " failed, no history");
        return;
    }

    //--- ティックボリュームを確認する
    if (rt[1].tick_volume > 1)
        return;

    //--- 移動平均値を取得する
    double ma[1]; // 移動平均値を格納する配列
    if (CopyBuffer(IndicatorHandle, 0, 1, 1, ma) != 1)
    {
        Print("CopyBuffer from iMA failed, no data");
        return;
    }

    //--- シグナルの存在を確認する
    ENUM_ORDER_TYPE signal = WRONG_VALUE; // シグナルの種類

    //--- ローソク足が移動平均より高く開き、低く閉じた場合のシグナル
    if (rt[0].open > ma[0] && rt[0].close < ma[0])
        signal = ORDER_TYPE_BUY;   // 買いシグナル
    else if (rt[0].open < ma[0] && rt[0].close > ma[0]) // ローソク足が移動平均より低く開き、高く閉じた場合のシグナル
        signal = ORDER_TYPE_SELL;  // 売りシグナル

    //--- 追加の確認を行う
    if (signal != WRONG_VALUE)
    {
        if (TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol, _Period) > 100)
        {
            double price = SymbolInfoDouble(_Symbol, signal == ORDER_TYPE_SELL ? SYMBOL_BID : SYMBOL_ASK); // 取引価格を取得
            trade.PositionOpen(_Symbol, signal, Lots, price, 0, 0); // ポジションを開く
        }
    }
}

//+------------------------------------------------------------------+
//| ポジションを閉じる条件を確認する                                 |
//+------------------------------------------------------------------+
void CheckForClose(void)
{
    MqlRates rt[2]; // 過去2つのローソク足データを格納するための配列

    //--- 新しいバーの始めのみで取引する
    if (CopyRates(_Symbol, _Period, 0, 2, rt) != 2)
    {
        Print("CopyRates of ", _Symbol, " failed, no history");
        return;
    }

    if (rt[1].tick_volume > 1)
        return;

    //--- 移動平均値を取得する
    double ma[1]; // 移動平均値を格納する配列
    if (CopyBuffer(IndicatorHandle, 0, 1, 1, ma) != 1)
    {
        Print("CopyBuffer from iMA failed, no data");
        return;
    }

    //--- ポジションがすでに選択されているか確認する
    bool signal = false;
    long type = PositionGetInteger(POSITION_TYPE); // ポジションの種類を取得

    //--- ショートポジションを決済する条件
    if (type == (long)POSITION_TYPE_SELL && rt[0].open > ma[0] && rt[0].close < ma[0])
        signal = true;

    //--- ロングポジションを決済する条件
    if (type == (long)POSITION_TYPE_BUY && rt[0].open < ma[0] && rt[0].close > ma[0])
        signal = true;

    //--- 追加の確認を行う
    if (signal)
    {
        if (TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol, _Period) > 100)
            trade.PositionClose(_Symbol, Slippage); // ポジションを閉じる
    }
}

//+------------------------------------------------------------------+
//| 口座タイプ(ネッティングまたはヘッジ)を考慮してポジションを選択する    |
//+------------------------------------------------------------------+
bool SelectPosition()
{
    bool res = false;

    //--- ヘッジ口座のポジションを選択する
    if (IsHedging)
    {
        uint total = PositionsTotal(); // 全ポジションの数を取得
        for (uint i = 0; i < total; i++)
        {
            string position_symbol = PositionGetSymbol(i); // ポジションのシンボルを取得
            if (_Symbol == position_symbol && EA_MAGIC == PositionGetInteger(POSITION_MAGIC))
            {
                res = true;
                break;
            }
        }
    }
    //--- ネッティング口座のポジションを選択する
    else
    {
        if (!PositionSelect(_Symbol))
            return (false);
        else
            return (PositionGetInteger(POSITION_MAGIC) == EA_MAGIC); // マジックナンバーを確認
    }

    //--- 実行結果を返す
    return (res);
}

//+------------------------------------------------------------------+
//| エキスパート初期化関数                                           |
//+------------------------------------------------------------------+
int OnInit(void)
{
    //--- 取引タイプ(ネッティングまたはヘッジ)を設定する
    IsHedging = ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);

    //--- 正しいポジション制御のために取引操作クラスのオブジェクトを初期化する
    trade.SetExpertMagicNumber(EA_MAGIC); // マジックナンバーを設定
    trade.SetMarginMode();                // 証拠金モードを設定
    trade.SetTypeFillingBySymbol(Symbol());// シンボルに基づいた執行タイプを設定
    trade.SetDeviationInPoints(Slippage); // 許容スリッページを設定

    //--- 移動平均指標を作成する
    IndicatorHandle = iMA(_Symbol, _Period, MovingPeriod, MovingShift, MODE_SMA, PRICE_CLOSE);
    if (IndicatorHandle == INVALID_HANDLE)
    {
        printf("Error creating iMA indicator");
        return (INIT_FAILED);
    }

    //--- 初期化成功を示す
    return (INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| エキスパートティック関数                                         |
//+------------------------------------------------------------------+
void OnTick(void)
{
    //--- ポジションが既に開かれている場合は、決済条件を確認する
    if (SelectPosition())
        CheckForClose();

    //--- ポジションを開く条件を確認する
    CheckForOpen();
}

//+------------------------------------------------------------------+
//| テスタ関数                                                       |
//+------------------------------------------------------------------+
double OnTester()
{
    //--- カスタム条件最適化の値(高いほど良い)
    double ret = 0.0;

    //--- 取引結果を配列に入れる
    double array[];
    double trades_volume;
    GetTradeResultsToArray(array, trades_volume);
    int trades = ArraySize(array);

    //--- 10取引未満の場合、肯定的結果がないことをテストする
    if (trades < 10)
        return (0);

    //--- 取引あたりの平均結果
    double average_pl = 0;
    for (int i = 0; i < ArraySize(array); i++)
        average_pl += array[i];
    average_pl /= trades;

    //--- 単一テストモード用のメッセージを表示する
    if (MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_OPTIMIZATION))
        PrintFormat("%s: Trades=%d, Average profit=%.2f", __FUNCTION__, trades, average_pl);

    //--- 利益グラフの線形回帰を計算する
    double a, b, std_error;
    double chart[];
    if (!CalculateLinearRegression(array, chart, a, b))
        return (0);

    //--- 回帰直線からグラフの偏差の誤差を計算する
    if (!CalculateStdError(chart, a, b, std_error))
        return (0);

    //--- 傾向偏差の標準偏差を計算する
    ret = (std_error == 0.0) ? a * trades : a * trades / std_error;

    //--- カスタム条件最適化値を返す
    return (ret);
}

//+------------------------------------------------------------------+
//| 取引の利益/損失の配列を得る                                      |
//+------------------------------------------------------------------+
bool GetTradeResultsToArray(double &pl_results[], double &volume)
{
    //--- 完全な取引履歴をリクエストする
    if (!HistorySelect(0, TimeCurrent()))
        return (false);

    uint total_deals = HistoryDealsTotal(); // 全取引の数を取得
    volume = 0;

    //--- 証拠金を持つ配列の初期サイズを、履歴の取引数で設定する
    ArrayResize(pl_results, total_deals);

    //--- 取引結果を修正する取引のカウンター - 利益または損失
    int counter = 0;
    ulong ticket_history_deal = 0;

    //--- 全ての取引を見る
    for (uint i = 0; i < total_deals; i++)
    {
        //--- 取引を選択する
        if ((ticket_history_deal = HistoryDealGetTicket(i)) > 0)
        {
            ENUM_DEAL_ENTRY deal_entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_history_deal, DEAL_ENTRY); // 取引のエントリタイプを取得
            long deal_type = HistoryDealGetInteger(ticket_history_deal, DEAL_TYPE); // 取引のタイプを取得
            double deal_profit = HistoryDealGetDouble(ticket_history_deal, DEAL_PROFIT); // 取引の利益を取得
            double deal_volume = HistoryDealGetDouble(ticket_history_deal, DEAL_VOLUME); // 取引の量を取得

            //--- 興味があるのは取引操作のみである
            if ((deal_type != DEAL_TYPE_BUY) && (deal_type != DEAL_TYPE_SELL))
                continue;

            //--- 損益を固定する取引のみ
            if (deal_entry != DEAL_ENTRY_IN)
            {
                //--- 取引結果を配列に書き込み、取引のカウンターを増やす
                pl_results[counter] = deal_profit;
                volume += deal_volume;
                counter++;
            }
        }
    }

    //--- 配列の最終サイズを設定する
    ArrayResize(pl_results, counter);
    return (true);
}

//+------------------------------------------------------------------+
//| 線形回帰を計算する y=a*x+b                                      |
//+------------------------------------------------------------------+
bool CalculateLinearRegression(double &change[], double &chartline[], double &a_coef, double &b_coef)
{
    //--- データが十分か確認する
    if (ArraySize(change) < 3)
        return (false);

    //--- 蓄積されたチャート配列を作成する
    int N = ArraySize(change);
    ArrayResize(chartline, N);
    chartline[0] = change[0];
    for (int i = 1; i < N; i++)
        chartline[i] = chartline[i - 1] + change[i];

    //--- 線形回帰を計算する
    double x = 0, y = 0, x2 = 0, xy = 0;
    for (int i = 0; i < N; i++)
    {
        x = x + i;
        y = y + chartline[i];
        xy = xy + i * chartline[i];
        x2 = x2 + i * i;
    }

    a_coef = (N * xy - x * y) / (N * x2 - x * x); // 傾きaを計算する
    b_coef = (y - a_coef * x) / N;                // 切片bを計算する

    //--- 成功を示す
    return (true);
}

//+------------------------------------------------------------------+
//| 指定されたaとbの平均二乗偏差誤差を計算する                       |
//+------------------------------------------------------------------+
bool CalculateStdError(double &data[], double a_coef, double b_coef, double &std_err)
{
    //--- 誤差の平方和
    double error = 0;
    int N = ArraySize(data);
    if (N <= 2)
        return (false);

    for (int i = 0; i < N; i++)
        error += MathPow(a_coef * i + b_coef - data[i], 2);

    std_err = MathSqrt(error / (N - 2)); // 標準誤差を計算する

    //--- 成功を示す
    return (true);
}
タイトルとURLをコピーしました