OnTesterInit関数について解説した記事内↓にて、
OnTesterInit関数を利用したサンプルコードも掲載しているのですが、同じ記事内で解説するには、ちょっと長くて複雑だったもので、別記事にして数回に分けて解説しています。
今回解説する記述は以下の箇所です。
//+------------------------------------------------------------------+
//| 取引シグナルを生成する関数 |
//| 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); // シグナル値を返す
}
このコードは、取引シグナルを生成するためのCheckSignal関数を定義しています。ATR(Average True Range)指標を基に、ローソク足の変動が一定の基準を超えた場合にシグナルを生成します。買いシグナルの場合は1、売りシグナルの場合は-1を返し、シグナルがない場合は0を返します。
コードの詳細解説
- 最初にシグナルの初期値として0を設定します。この値はシグナルがない状態を表します。
- 次に、ATR指標の最新値を取得します。atr_valueという配列にATRの最新値を格納するため、CopyBuffer関数を使用してatr_handleからATR値を取得します。取得が成功すると、ATR値をlast_atr変数に格納します。
- 続いて、最新のローソク足データを取得します。MqlRates構造体型のbar配列にローソク足の情報を保存し、CopyRates関数を使用して最新のローソク足データを取得します。取得に成功すると、ローソク足の実体サイズ(始値と終値の差)を計算し、last_body変数に格納します。
- 実体サイズがATR値の指定倍率(kATR)を超えた場合、取引シグナルが生成されます。ここで、MathAbs関数を使ってlast_bodyの絶対値を計算しています。この絶対値がATR値の指定倍率を超えるかどうかを判断し、条件を満たす場合のみ取引シグナルが発生します。
- 三項演算子を使用して、シグナルの方向を設定します。last_bodyが正の値(上昇ローソク)であれば買いシグナルとして1を、負の値(下降ローソク)であれば売りシグナルとして-1をresに格納します。
- ATR値またはローソク足データの取得に失敗した場合は、エラーメッセージを出力します。このとき、PrintFormat関数を使用して、関数名( __FUNCTION__)とエラーコードを含むメッセージをフォーマットし、ログに出力します。 __FUNCTION__は現在の関数名を文字列として返す特殊な識別子で、デバッグ時にどの関数でエラーが発生したかを確認するのに便利です。GetLastError関数を使用して最後のエラーコードを取得し、その内容をログ出力に含めることで、エラーの詳細な原因を把握できます。
- 最後に、シグナル反転の設定が有効な場合、シグナルの符号を反転します。reversがtrueであれば、resに格納されたシグナルの符号が反転し、買いシグナルが売りに、売りシグナルが買いに変わります。
- シグナル値がresに格納された状態で関数の処理を終了し、最終的なシグナル値を返します。
サンプルコードの全体記述
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を返す
}