コンテンツにスキップ

【移植#71】偽Tickを送らない — MT5タイマーの周期と取りこぼしを測る

EURUSDのH1チャート上で100msタイマーと無配信カスタム銘柄を比較し、価格時刻変化0のままTimerが進むことを確認したMT5画面

この記事の3行まとめ

  • MT4の内部メッセージ送信や無限ループを使わず、MT5標準のOnTimer()でTick非依存の周期処理へ置き換えます
  • 要求周期、実測間隔、価格時刻が変わらなかった回数、統合されたTimer枠の推定値を同じ画面へ表示します
  • 専用MT5では無配信銘柄の価格時刻変化0のままTimerが13回進み、14/14診断と注文・ポジション不変を確認しました

今回の原典は「Tickを待たずにEAを呼ぶ」

投稿日順で次に当たる2010年3月19日の記事は、相場のTickが来ない時間にもEAを1秒ごとに動かす方法を扱っています。

元記事「Tick更新を待たずに定期的にEA を呼び出す。」を読む

原典で示された方法は2つです。

  1. Windows DLLからMT4の内部メッセージを送り、チャートへ偽のTick更新を通知する
  2. EA自身をSleep(1000)付きの無限ループに入れ、1秒ごとに処理する

当時の目的は明快でした。薄商い、週末、接続直後など、Tickが来ない間も時計表示や監視処理を止めたくなかったのです。

一方、原典には重要な注意もありました。ループだけを回しても、あらかじめ受け取ったBidが自動的に最新値へ変わるわけではありません。周期処理が動くことと、新しい価格が届くことは別です。

この区別はMT5でも変わりません。


DLLと偽Tickを採用しない

MT5には、EAとインジケーターが専用のTimerイベントを受け取る標準機能があります。

基本形は次のとおりです。

MQL
int OnInit()
{
   if(!EventSetMillisecondTimer(InpTimerMilliseconds))
   {
      PrintFormat("timer setup failed error=%d", GetLastError());
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

void OnTimer()
{
   RunPeriodicTask();
}

void OnDeinit(const int reason)
{
   EventKillTimer();
}

これなら、DLL許可、Windowsメッセージ、偽の価格イベント、停止まで戻らない無限ループは不要です。

配布版は売買を行うEAではなく、Timerと価格時刻を横に並べて観察するインジケーターにしました。周期処理の仕組みはEAでも同じですが、まずは注文から切り離して挙動を確認できます。


要求した周期どおりに必ず呼ばれるわけではない

MT5公式仕様では、各MQL5プログラムは自分専用のTimerを1つ持ちます。ただし、Timerイベントがすでに待ち行列にあるか、OnTimer()を実行中なら、新しいTimerイベントは追加されません。

たとえば100ms周期を要求しても、1回の処理に350msかかれば、終了後に「遅れた3回分」がまとめて連続実行されるわけではありません。

そこで、配布版はGetTickCount64()で各コールバックの実時間間隔を測ります。

MQL
const ulong entered_ms = GetTickCount64();
const ulong interval_ms = entered_ms - previous_timer_ms;
previous_timer_ms = entered_ms;

GetTickCount64()はOS起動後の経過ミリ秒をulongで返します。約49.7日で周回する32bit版のGetTickCount()と違い、長時間稼働でも実用上の桁あふれを避けられます。

公式仕様では、高分解能Timerも実時間ではハードウェア制約により概ね10〜16msより細かく生成されません。Timerはリアルタイム制御ではなく、端末負荷や処理時間の影響を受けるスケジューラーとして扱う必要があります。


Timerが進んでも価格は更新されたとは限らない

各TimerでSymbolInfoTick()を呼び、MqlTick.time_mscが前回から変わったかを比べます。

MQL
MqlTick tick = {};
const bool available =
   SymbolInfoTick(target_symbol, tick) && tick.time_msc > 0;

if(available && tick.time_msc != last_quote_msc)
   ++quote_timestamp_changes;
else
   ++callbacks_without_quote_change;

ここで数えているのは「Timerから価格を更新した回数」ではありません。

  • Timerコールバック数
  • 最終価格のタイムスタンプが変わった回数
  • Timerは動いたが価格時刻は変わらなかった回数

この3つを分けています。

TimeCurrent()も、OnTimer()内ではPCの現在時刻ではなく、Market Watchにある銘柄で最後に受け取ったサーバー時刻です。相場が止まれば値も進まないため、周期間隔の計測には使っていません。


「取りこぼし」は推定値として表示する

実測間隔を要求周期で割り、丸めた枠数から1を引いた値を合計しています。

Text Only
推定統合枠 = max(0, round(実測間隔 ÷ 要求周期) - 1)

100ms要求に対して実測間隔が約350msなら、2〜3枠が統合またはスキップされたと推定します。

これは端末内部のイベントキューを直接読んだ件数ではありません。OSのタイマー分解能、描画、他プログラムの負荷も間隔へ含まれるため、「失われたイベントの厳密な個数」ではなく、処理設計を見直す目安です。

周期処理では、コールバック回数を時計代わりにせず、経過時間から期限を判定する方が安全です。


パネルで確認できる項目

表示 意味
TARGET 価格時刻を観測する銘柄
REQUESTED TIMER 要求したTimer周期
TEST WORK 検証専用の処理時間。通常は0ms
WALL ELAPSED GetTickCount64()による経過時間
CALLBACKS 実際に入ったTimerコールバック数
QUOTE TIMESTAMP CHANGES time_mscが変わった回数
CALLBACKS WITHOUT CHANGE 価格時刻が同じままTimerだけ進んだ回数
LAST / AVG / MAX Timer実測間隔
ESTIMATED COALESCED / SKIPPED 要求周期との差から求めた統合枠の推定
VERDICT 価格時刻が変わらないTimer回を観測できたか

診断値は14個の計算バッファにも公開しています。画面表示とは別に、EAからCopyBuffer()で同じ値を検査できます。

主なパラメーター

パラメーター 初期値 役割
InpObservedSymbol 空欄 観測銘柄。空欄は装着チャートと同じ銘柄
InpTimerMilliseconds 250 Timerの要求周期。50〜5,000ms
InpObservationSeconds 5 PASS判定までの観測時間
InpSimulatedWorkMilliseconds 0 検証専用の有限負荷。通常運用では0
InpShowPanel true 状態パネルの表示

TEST WORKは通常0msのまま使います

InpSimulatedWorkMillisecondsは、Timer処理が要求周期より長い場合を再現するための診断用です。非0にすると、そのインジケーターの処理を意図的に占有します。実運用の監視、発注、決済には使わず、専用の検証端末で短時間だけ確認してください。


専用MT5で実機検証

配布インジケーターと独立検証EAを、専用MT5へ配置してコンパイル・実行しました。

コンパイル結果

対象 エラー 警告
配布インジケーター 0 0
独立検証EA 0 0

実行条件

項目
MT5 Build 6180
装着チャート EURUSD H1
観測対象 無配信の検証用カスタム銘柄
Timer要求 100ms
検証専用処理 350ms
判定観測時間 4秒

カスタム銘柄にはレートやTickを投入していません。チャート側のEURUSDが動くかどうかに左右されず、「観測対象の価格時刻が変わらない状態」を固定するためです。

合否判定時の実測結果

確認項目 結果
経過時間 4,781ms
Timerコールバック 13回
価格タイムスタンプ変化 0回
価格時刻が変わらないコールバック 13回
実測間隔・直近 360ms
実測間隔・平均 340.2ms
実測間隔・最大 360ms
統合・スキップ枠の推定 36枠
経過時間からの独立推定 34枠
診断バッファ 14/14
パネルオブジェクト 12/12

100msごとに13回なら1.3秒分ですが、壁時計では4.781秒が経過しています。350msの検証処理中にTimerイベントが無制限に積み上がらず、実測間隔が約340〜360msになった結果と整合します。

画面撮影までの約1秒にもTimerは進み、掲載画像ではコールバック16回、経過5,859ms、平均343.8ms、推定45枠になっています。合否判定後もTimerが継続したためで、表との食い違いではありません。

独立検証EAは、14個のバッファを14/14取得し、要求周期、検証負荷、経過時間、価格時刻変化0、Timer回数、実測間隔、推定枠、12個の表示オブジェクトを別に照合しました。検証中の注文数とポジション数も開始前後で変わっていません。


使い方

  1. 下のMQ5ファイルをダウンロードする
  2. MT5のデータフォルダにあるMQL5/Indicatorsへ置いてコンパイルする
  3. 監視したいチャートへ装着する
  4. 通常は観測銘柄を空欄、Timerを250ms、検証負荷を0msにする
  5. CALLBACKSQUOTE TIMESTAMP CHANGESを分けて確認する

71_Timer_Quote_Independence_Monitor_v1_00.mq5 をダウンロード

使えないこと・注意点

  • Timer周期は実行時刻の保証ではありません。OS、端末、他プログラム、処理時間の影響を受けます。
  • Timerイベントは蓄積されないため、コールバック回数だけで期限や経過時間を決めないでください。
  • OnTimer()が呼ばれても、新しい価格が届いた証明にはなりません。価格を使う直前に取得結果と時刻を確認します。
  • 表示する統合・スキップ枠は実測間隔からの推定で、端末内部キューの厳密な計数ではありません。
  • 1秒未満のTimerを多用すると、ストラテジーテスターの所要時間も増えます。
  • 検証負荷を非0にすると処理を意図的に占有します。通常運用では0のまま使います。
  • このインジケーターは監視専用で、売買、価格更新、Tick生成、DLL呼び出しを行いません。
  • Timerを使ったこと自体が、EAの収益性や約定品質を改善する証拠にはなりません。

まとめ

MT4時代の偽Tick送信と無限ループは、「相場が止まっても周期処理を続けたい」という要求への工夫でした。MT5では、その要求を標準のTimerイベントへ正面から分離できます。

ただし、Timerは価格更新ではなく、要求周期も実行保証ではありません。価格のタイムスタンプ、壁時計の経過時間、実際のコールバック間隔を別々に測ることで、「処理は動いた」「価格は古い」「予定枠は統合された」を混同せず判断できます。


前後の記事

前: 【移植#70】1970年ダミー足をやめる — MT5履歴を指定日まで段階取得

次: 【移植#72】必要な日だけPivotを引く — 前日値幅で描く4つのFiboZone

— SPONSORED —