OnTester関数の働き・役割
OnTester関数は、エキスパートアドバイザー(EA)のテストが終了した後に呼び出される関数です。この関数は、テスト結果を評価するためのカスタム条件の最適化値を計算するために使用されます。主に、遺伝的最適化の際に入力パラメータのカスタム最大基準として使用されます。
遺伝的最適化とは
遺伝的最適化は、自動売買においてトレーディング戦略の最適なパラメータを見つけるために使用されます。自動売買では、多くの異なるパラメータ設定を試す必要がありますが、手動でこれを行うのは非常に時間がかかります。遺伝的最適化を使用することで、以下のような利点があります。
- 効率的な探索:
遺伝的最適化は、ランダムに生成したパラメータセットから始め、良い結果をもたらすパラメータを次の世代に引き継ぎます。これにより、全てのパラメータ組み合わせを試すことなく、効率的に最適なパラメータを見つけることができます。 - 多様な解の探索:
パラメータの組み合わせが多すぎる場合でも、遺伝的最適化は広範囲にわたって探索し、局所最適解に陥るリスクを減少させます。突然変異により、多様な解を生成し続けることで、より良いパラメータセットを見つける可能性が高まります。 - 自動化による時間短縮:
自動売買の設定を手動で調整するのは非常に手間がかかりますが、遺伝的最適化を使用することで、自動的に最適なパラメータセットを見つけることができます。これにより、トレーダーはより重要な意思決定や戦略の開発に集中することができます。
このように、遺伝的最適化は、自動売買システムの効率と効果を大幅に向上させるため、MQL5/MT5のようなプラットフォームで広く使用されています。
OnTester関数の引数について
OnTester関数は引数を持ちません。
double OnTester();
この関数は、EAのテストが終了した後に自動的に呼び出され、カスタムの最適化基準を計算するために使用されます。
OnTester関数の戻り値について
OnTester関数の戻り値は double型で、テスト結果を評価するためのカスタム最適化値を返します。この値は、遺伝的最適化の際に使用され、より良いトレードパラメータを見つけるための基準となります。具体的には、戻り値が高いほどテスト結果が良好であるとみなされ、遺伝的最適化プロセスで優れたパラメータセットが次世代に引き継がれます。
また、この値を用いて独自のテスト結果レポートを作成し、保存することもできます。これにより、最適化プロセスを通じてトレーディング戦略の最良のパラメータを見つけることが可能になります。
OnTester関数を使う際の注意点
- テスト時のみ使用可能:
OnTester関数はエキスパートアドバイザー(EA)のテスト時にのみ使用できます。リアルタイムのトレードやデモトレードでは呼び出されません。 - 戻り値の重要性:
遺伝的最適化では、OnTester関数の戻り値が最適化プロセスの基準として使用されます。このため、戻り値は戦略のパフォーマンスを正確に反映するものである必要があります。適切な基準を選定し、テスト結果を正確に評価することが重要です。 - データの一貫性:
OnTester関数内で使用されるデータは、一貫して正確である必要があります。特に、テスト結果を評価するためのデータ収集や計算に誤りがないように注意してください。 - 計算の効率性:
OnTester関数は、テストが終了するたびに呼び出されます。計算が複雑すぎると、最適化のスピードが遅くなる可能性があります。効率的なアルゴリズムを使用して計算を行うことが推奨されます。 - エラーハンドリング:
OnTester関数内で発生する可能性のあるエラーに対する適切なエラーハンドリングを行ってください。特に、データの取得や計算中に予期せぬエラーが発生した場合に備えて、エラーチェックを行うことが重要です。 - 適切なデバッグ:
OnTester関数を開発およびテストする際には、適切なデバッグ手法を使用してください。Print関数やログファイルを使用して、関数の動作を詳細に確認し、予期せぬ動作やエラーを早期に発見することが重要です。
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);
}
このコードは、MetaTrader5 (MT5) 上で動作するエキスパートアドバイザ (EA) です。具体的には、以下の操作を行います。
- 取引パラメータの設定:
- ポジションの管理:
- 取引シグナルの生成:
- 移動平均とローソク足の位置関係から買いシグナルや売りシグナルを生成します。
- ポジションの開閉:
- 条件が整った場合にポジションを開き、また条件が整った場合に既存のポジションを閉じます。
- バックテストと最適化のサポート:
- 過去の取引結果を集計し、カスタム条件最適化のための値を計算します。
このEAは、基本的なトレンドフォローモデルに基づいており、移動平均を利用して売買シグナルを生成し、それに基づいて自動的に取引を行います。
本来であれば、この記事内でコード解説を行うのですが、同じ記事でコード解説を行ってしまうとあまりにも長くなってしまうので、コード解説は別記事でおこないます。以下の記事群をご参照ください。
・OnTester関数を使ったサンプルコードの解説その1-グローバル領域での定義-
・OnTester関数を使ったサンプルコードの解説その2-ポジションを開く条件を設定する関数
・OnTester関数を使ったサンプルコードの解説その3:ポジションを閉じる条件を設定する関数
・OnTester関数を使ったサンプルコードの解説その4:口座タイプを考慮してポジションを選択する 関数
・OnTester関数を使ったサンプルコードの解説その5:OnInit関数とOnTick関数の記述
・OnTester関数を使ったサンプルコードの解説その6:OnTester関数の記述
・OnTester関数を使ったサンプルコードの解説その7:利益/損失情報を配列に格納する関数