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

この記事の3行まとめ
- MT4の内部メッセージ送信や無限ループを使わず、MT5標準の
OnTimer()でTick非依存の周期処理へ置き換えます - 要求周期、実測間隔、価格時刻が変わらなかった回数、統合されたTimer枠の推定値を同じ画面へ表示します
- 専用MT5では無配信銘柄の価格時刻変化0のままTimerが13回進み、14/14診断と注文・ポジション不変を確認しました
今回の原典は「Tickを待たずにEAを呼ぶ」¶
投稿日順で次に当たる2010年3月19日の記事は、相場のTickが来ない時間にもEAを1秒ごとに動かす方法を扱っています。
元記事「Tick更新を待たずに定期的にEA を呼び出す。」を読む
原典で示された方法は2つです。
- Windows DLLからMT4の内部メッセージを送り、チャートへ偽のTick更新を通知する
- EA自身を
Sleep(1000)付きの無限ループに入れ、1秒ごとに処理する
当時の目的は明快でした。薄商い、週末、接続直後など、Tickが来ない間も時計表示や監視処理を止めたくなかったのです。
一方、原典には重要な注意もありました。ループだけを回しても、あらかじめ受け取ったBidが自動的に最新値へ変わるわけではありません。周期処理が動くことと、新しい価格が届くことは別です。
この区別はMT5でも変わりません。
DLLと偽Tickを採用しない¶
MT5には、EAとインジケーターが専用のTimerイベントを受け取る標準機能があります。
- 1秒以上なら
EventSetTimer() - 1秒未満なら
EventSetMillisecondTimer() - 周期処理は
OnTimer() - 終了時は
EventKillTimer()
基本形は次のとおりです。
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()で各コールバックの実時間間隔を測ります。
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が前回から変わったかを比べます。
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を引いた値を合計しています。
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個の表示オブジェクトを別に照合しました。検証中の注文数とポジション数も開始前後で変わっていません。
使い方¶
- 下のMQ5ファイルをダウンロードする
- MT5のデータフォルダにある
MQL5/Indicatorsへ置いてコンパイルする - 監視したいチャートへ装着する
- 通常は観測銘柄を空欄、Timerを250ms、検証負荷を0msにする
CALLBACKSとQUOTE 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