開発実務ノート

MQL5外部連携EA実務ノート|WebRequest・JSON・Webhookを安全に扱う設計

MQL5外部連携EA実務ノート|WebRequest・JSON・Webhookを安全に扱う設計の内容を表す、WebRequest・JSON・Webhookの確認用アイキャッチ画像
EAファンクラブ

MQL5 EAでは、WebRequest、JSON、Webhookなどを使って、外部サービスと連携する場面があります。

たとえば、外部通知、稼働状態の送信、設定確認、運用メモの取得、Discord通知、検証ログの送信などです。外部連携を使うと便利ですが、設計を間違えると、通信失敗でEA本体が止まる、tokenやURLがログへ出る、通知失敗を売買失敗と誤解する、外部応答遅延でOnTickが重くなる、といった問題が起きます。

外部連携EAで重要なのは、通信処理を売買ロジックへ直接混ぜないことです。signal、execution、risk、exit、auth、external control、notificationを分け、通信失敗時にどの処理を止めるのか、どの処理を継続するのかを明確にします。

この記事では、MQL5 EAでWebRequest、JSON、Webhookを安全に扱うための設計判断を、開発実務ノートとして整理します。完成EAソース全文、外部シート制御ロジック、認証ロジック、endpoint、token、URL実値、実運用set値、中核Entry条件は公開しません。

本記事は、投資判断や売買指示ではなく、外部連携を含むMQL5 EAを安全に設計・検証・サポートするための技術記事です。

この記事で扱う実務課題

項目この記事で扱う内容扱わない内容
目的WebRequest、JSON、WebhookをEAで安全に扱う設計利益を狙う売買ロジック、具体的なEntry条件
主な対象外部通信、通知、レスポンス確認、通信失敗時の安全側処理完成EAソース全文、実運用コード全文
設計観点責務分離、ログ設計、タイムアウト、通信抑制、機密情報マスク認証ロジック、外部シート制御、実運用set値
コード例一般化した通信結果構造、ログ例、通知分離、通信失敗時の扱い実endpoint、token、URL、GAS URL、Webhook URL
読者MQL5開発者、外部通知EAを作りたい人、検証担当者、販売前チェック担当者売買判断や推奨設定を知りたい人

なぜ外部連携EAでは責務分離が重要なのか

外部連携をEAへ追加すると、EA本体の処理に通信という不安定要素が入ります。

通信は、EA内部だけでは完結しません。回線、MT5のWebRequest許可設定、外部サーバーの応答、タイムアウト、証明書、payload形式、HTTPステータス、サービス側の制限など、多くの要因に影響されます。

問題起きる原因分けるべき責務
EAが重くなるOnTick内で毎回通信しているexternal communication / runtime
通知が届かないWebhook設定不備、通信失敗、送信抑制notification
売買まで止まる通知失敗と売買処理を同じ失敗として扱っているexecution / notification
ログに機密情報が出るendpointやtokenをそのままPrintしているlog / secret management
外部停止の範囲が曖昧新規Entry停止と既存ポジション管理を混同しているexternal control / exit
問い合わせ時に確認できないHTTP結果、reason、detailがログにないlog design

外部連携は便利ですが、EA本体の売買判定や決済処理と密結合にすると、通信失敗がそのまま売買不具合のように見えてしまいます。

外部連携で分けるべき責務

外部連携EAでは、少なくとも次の責務を分けて設計します。

責務役割混同すると起きる問題
signalEntry候補を作る外部通信失敗をシグナル不成立と誤解する
executionOrderSend、決済、modifyを実行する通知失敗と注文失敗を混同する
riskロット、保有上限、損失制御を扱う外部状態と内部リスクを混同する
exit既存ポジション管理を行う新規Entry停止時に保護決済まで止めてしまう
auth利用許可や認証状態を扱う認証NG時の停止範囲が曖昧になる
external control外部状態による動作制御を扱う通信失敗時に売買全体の扱いが不明になる
notificationDiscord通知や外部通知を扱う通知失敗が売買失敗に見える
log通信結果、reason、detailを記録する問い合わせ時に原因を追えない

特に重要なのは、notificationとexecutionを分けることです。通知が失敗しても、注文自体は成功している場合があります。逆に、注文が失敗しても通知だけ成功している場合もあります。

WebRequestを使う時に決めること

WebRequest()は同期関数で、EAとスクリプトから利用できますがインジケーターから直接呼び出すことはできません。またStrategy Testerでは実行されません。外部通信はOnTickの売買判定へ直結させず、timeout・失敗時の既定動作・再試行・ログ・機密情報マスクを別責務として設計します。

MQL5でWebRequestを使う場合、実装前に次の方針を決めておく必要があります。

確認項目設計判断
通信タイミングOnTickごとか、OnTimerか、状態変化時か
通信頻度秒単位で抑制するか、イベント発生時だけにするか
失敗時の扱い新規Entryを止めるか、通知だけ失敗扱いにするか
タイムアウト応答遅延時にEA本体を止めないか
ログ粒度HTTP status、reason、masked detailをどこまで出すか
機密情報endpoint、token、URLをログや画面に出さないか
外部依存範囲外部通信が失敗しても既存ポジション管理を続けるか

外部通信は、OnTick内で無制限に呼ばない方が安全です。状態変化時、OnTimer、または一定間隔のsummary処理へ分けることで、EA本体の安定性を保ちやすくなります。

JSON応答を扱う時の注意点

外部サービスからJSON形式の応答を受け取る場合、文字列が返ってきたことと、EAが使える状態であることは別です。

JSON応答では、次のような確認が必要です。

確認項目確認する理由
空応答ではないか通信は成功しても中身が空の場合がある
期待するキーがあるか形式変更やエラー応答を検出するため
値の型が想定どおりかtrue / false、数値、文字列の誤解釈を防ぐため
時刻や有効期限があるか古い応答を使わないため
機密情報を含めていないかログへ出す前にマスクするため
失敗応答を成功扱いしていないかHTTP成功でも業務上NGの可能性があるため

JSONを受け取ったら、そのまま売買判断へ使うのではなく、通信結果、形式確認、値の妥当性、外部状態の適用範囲を分けて扱います。

Webhook通知を売買処理と分ける理由

Webhook通知は、EAの状態や注文結果を外部へ知らせるために便利です。

ただし、通知は売買処理そのものではありません。通知処理と売買処理を混同すると、次のような問題が起きます。

混同例起きる問題
通知成功を注文成功として扱う実際のOrderSend結果とずれる
通知失敗でEA全体を止める注文や保護決済まで止まる可能性がある
Webhook URLをログへ出す通知先が漏れる
毎tick通知する通知過多、外部サービス制限、ログ過多になる
通知payloadに口座情報を入れる個人情報や機密情報の漏えいにつながる

Webhook通知は、notification層として分け、送信成功・失敗をログで追えるようにします。通知失敗時に売買処理を止めるかどうかは、仕様として明確にします。

通信失敗時は安全側で扱う

外部連携EAでは、通信失敗時の扱いを最初に決めておく必要があります。

安全側の基本方針は、外部状態が不明な場合、無理に新規Entryへ進まないことです。一方で、既存ポジション管理まで止めるかどうかは慎重に判断します。

状態新規Entry既存ポジション管理理由
外部通信OK外部状態に従う通常どおり継続外部状態が確認できるため
外部通信失敗停止または前回状態の扱いを仕様化原則として保護処理は継続検討新規Entryと保護処理は責務が違うため
外部応答が不正停止候補必要な保護処理は継続検討不正な外部値を使わないため
通知だけ失敗売買可否とは分ける売買可否とは分ける通知と注文実行は別責務のため

「通信できないからEA全体を止める」のか、「新規Entryだけ止める」のか、「一定時間だけ前回状態を使う」のかは、EAの目的とリスク方針によって異なります。重要なのは、仕様として曖昧にしないことです。

実コード例1:外部通信結果を構造体で扱う

以下は、設計判断を理解するために一般化した学習用コード例です。完成EAのソース全文ではありません。実endpoint、token、URLは含めていません。

// 学習用に一般化した外部通信結果の例です。
// 実運用EAの完成ソースではありません。

enum ExternalStatus
{
   EXT_STATUS_UNKNOWN = 0,
   EXT_STATUS_OK,
   EXT_STATUS_FAIL,
   EXT_STATUS_TIMEOUT,
   EXT_STATUS_INVALID_RESPONSE
};

struct ExternalFetchResult
{
   ExternalStatus status;
   int httpStatus;
   string reason;
   string maskedDetail;
   datetime checkedAt;
};

ExternalFetchResult MakeExternalFail(string reason, string detail)
{
   ExternalFetchResult result;
   result.status = EXT_STATUS_FAIL;
   result.httpStatus = 0;
   result.reason = reason;
   result.maskedDetail = detail;
   result.checkedAt = TimeCurrent();

   return result;
}

外部通信の結果は、true / falseだけで扱うより、status、httpStatus、reason、maskedDetail、checkedAtを分けると確認しやすくなります。

特に、通信失敗、タイムアウト、形式不正、外部応答NGは別のreasonとして扱うべきです。

実コード例2:外部通信ログは機密情報をマスクする

以下は、設計判断を理解するために一般化した学習用コード例です。完成EAのソース全文ではありません。

// 学習用に一般化した外部通信ログ例です。
// 実運用EAの完成ソースではありません。

string MaskExternalText(string value)
{
   string lower = StringToLower(value);

   if(StringFind(lower, "token") >= 0)
      return "masked";

   if(StringFind(lower, "endpoint") >= 0)
      return "masked";

   if(StringFind(lower, "webhook") >= 0)
      return "masked";

   return value;
}

void WriteExternalLog(string eventName,
                      string reason,
                      const ExternalFetchResult &result)
{
   Print("DIAG/EXTERNAL: event=", eventName,
         " reason=", reason,
         " status=", (int)result.status,
         " http=", result.httpStatus,
         " detail=", MaskExternalText(result.maskedDetail));
}

外部連携ログでは、endpoint、token、Webhook URL、GAS URL、APIキー、口座番号、顧客情報を出さないことが重要です。

ログに必要なのは、実値ではなく、原因調査に使える分類です。たとえば、FETCH_FAIL、TIMEOUT、INVALID_RESPONSE、HTTP_STATUS_NGのように、reasonで整理します。

実コード例3:外部状態を新規Entryと既存管理へ分けて扱う

以下は、設計判断を理解するために一般化した学習用コード例です。完成EAのソース全文ではありません。外部シート制御や認証処理は含めていません。

// 学習用に一般化した外部状態の適用例です。
// 実運用EAの完成ソースではありません。

struct ExternalRuntimeState
{
   bool externalReady;
   bool allowNewEntry;
   bool allowNotification;
   string reason;
};

ExternalRuntimeState BuildExternalRuntimeState(const ExternalFetchResult &fetch)
{
   ExternalRuntimeState state;
   state.externalReady = false;
   state.allowNewEntry = false;
   state.allowNotification = false;
   state.reason = "INIT";

   if(fetch.status != EXT_STATUS_OK)
   {
      state.reason = "EXTERNAL_NOT_READY";
      return state;
   }

   state.externalReady = true;
   state.allowNewEntry = true;
   state.allowNotification = true;
   state.reason = "EXTERNAL_OK";

   return state;
}

void ApplyExternalStateToEntry(const ExternalRuntimeState &state)
{
   if(!state.allowNewEntry)
   {
      Print("DIAG/ENTRY: event=SKIP",
            " reason=", state.reason,
            " scope=NEW_ENTRY_ONLY");
      return;
   }

   Print("DIAG/ENTRY: event=EXTERNAL_GATE_PASS",
         " reason=", state.reason);
}

この例では、外部状態を新規Entryの許可判断へ反映する考え方を示しています。ただし、既存ポジション管理や保護決済まで同時に止めるとは限りません。

新規Entry停止と既存ポジション管理は、必ず分けて設計します。

実コード例4:通知失敗を売買失敗と分ける

以下は、設計判断を理解するために一般化した学習用コード例です。完成EAのソース全文ではありません。実Webhook URLやpayloadは含めていません。

// 学習用に一般化した通知結果の扱い例です。
// 実運用EAの完成ソースではありません。

struct NotifyResult
{
   bool sent;
   string reason;
   datetime sentAt;
};

NotifyResult SendNotificationSafe(string eventName, string messageSummary)
{
   NotifyResult result;
   result.sent = false;
   result.reason = "NOT_SENT";
   result.sentAt = TimeCurrent();

   if(eventName == "")
   {
      result.reason = "EMPTY_EVENT";
      return result;
   }

   // 実際のEAでは、ここで通知送信処理を別責務として扱います。
   // 実Webhook URL、token、payload詳細はこの記事では扱いません。

   result.sent = true;
   result.reason = "NOTIFY_SEND_OK";

   return result;
}

void TraceNotifyResult(const NotifyResult &result)
{
   if(result.sent)
   {
      Print("DIAG/NOTIFY: event=SEND_OK",
            " reason=", result.reason);
   }
   else
   {
      Print("DIAG/NOTIFY: event=SEND_FAIL",
            " reason=", result.reason);
   }
}

通知処理は、notification層として分けます。通知失敗は、通知失敗としてログに残し、注文実行や決済結果と混同しないようにします。

外部連携でログに残すべき情報

外部連携の不具合調査では、実値ではなく、分類された状態が重要です。

ログ項目残す内容出してはいけない内容
eventFETCH_START、FETCH_OK、FETCH_FAIL、SEND_OK、SEND_FAIL実endpoint、実Webhook URL
reasonTIMEOUT、HTTP_STATUS_NG、INVALID_RESPONSEtoken、APIキー
httpStatusHTTPステータス番号認証ヘッダー実値
scopeNEW_ENTRY_ONLY、NOTIFICATION_ONLY、LOG_ONLY口座番号、顧客情報
maskedDetailマスク済みの要約GAS URL、Google Sheet ID、Webhook URL
checkedAt確認時刻個人情報、購入者情報

外部連携では、ログが詳しすぎても危険です。問い合わせ時に共有される可能性を前提に、実値ではなく、分類・状態・reasonを残します。

外部通信をOnTickへ直接詰め込まない

外部通信は、OnTickへ直接詰め込まない方が安全です。

OnTickは価格更新ごとに呼ばれます。ここで毎回WebRequestを実行すると、EA全体が重くなり、バックテストやリアル運用でも不安定になりやすくなります。

避けたい設計推奨する設計
OnTickごとにWebRequestするOnTimer、状態変化時、一定間隔へ分離する
通信結果を即OrderSendへ直結するExternalRuntimeStateとして整理してからGATEへ渡す
通信失敗でEA全体を停止する新規Entry、既存管理、通知の停止範囲を分ける
通知成功を売買成功として扱うORDER_RESULTとNOTIFY_RESULTを分ける
外部応答をそのままログへ出す必要項目だけマスクして出す

外部通信は、EAの補助機能または外部状態取得として設計し、売買判定や注文実行と直接混ぜないことが重要です。

OnTimerで通信・通知・定期記録を分担する

以下のコード例は、個別の処理と確認項目を読むためのものです。複数の例をそのまま一つのEAへ連結する前提ではありません。初期化、値の取得、状態更新、終了処理をそれぞれの例で区別して確認してください。

OnTimerとは何か

OnTimerは、Timerイベントが発生した時に呼ばれるイベント関数です。

EAやインジケーターでタイマー処理を使う場合、通常はOnInitでEventSetTimerを呼び、指定した秒数ごとにOnTimerを実行します。

int OnInit()
{
   if(!EventSetTimer(10))
   {
      PrintFormat("[TIMER_SET_NG] seconds=10 last_error=%d", GetLastError());
      return INIT_FAILED;
   }

   Print("[TIMER_SET_OK] seconds=10");
   return INIT_SUCCEEDED;
}

void OnTimer()
{
   Print("[TIMER_TICK] periodic task");
}

void OnDeinit(const int reason)
{
   EventKillTimer();
   PrintFormat("[TIMER_KILL] reason=%d", reason);
}

OnTimerを使う場合は、開始、実行、終了をログで確認できるようにしてください。

OnTickとOnTimerの役割を分ける

EA開発では、OnTickとOnTimerの責務を混同しないことが重要です。

OnTickは価格変動に応じた売買判定や注文処理に向いています。一方、OnTimerは、一定間隔でよい処理、外部連携、状態監視、ログ抑制、通知再送などに向いています。

イベント主な役割入れやすい処理
OnTick価格変動に応じた処理signal判定、entry gate、発注前チェック、注文処理、決済判定
OnTimer一定間隔の処理外部制御取得、通知送信、CSV出力、稼働監視、ログ抑制、状態同期
OnInit初期化処理input確認、timer設定、handle生成、初期ログ
OnDeinit終了処理timer停止、handle解放、終了ログ

外部連携や通知処理をOnTickに直接入れると、tickが多い時間帯に処理回数が増えすぎる場合があります。定期処理でよいものはOnTimerへ分離してください。

OnTimerに向いている処理

OnTimerは、毎tickで実行する必要がない処理に向いています。

特に、外部連携、通知、状態監視、ログ抑制、定期スナップショットは、OnTimerへ分けると構造が分かりやすくなります。

処理OnTimerに向く理由確認ログ
Discord通知tickごとに送る必要がなく、送信間隔を制御しやすいNOTIFY_SEND / NOTIFY_SKIP
Google Sheets連携定期的な状態送信や集計送信に向いているSHEET_WRITE / SHEET_THROTTLE
外部制御取得外部シートやAPIから一定間隔で設定を読む場合に向いているEXTERNAL_FETCH / EXTERNAL_APPLY
CSV出力tickごとではなく、一定間隔やイベント後の記録に向いているCSV_WRITE / CSV_SKIP
状態監視口座状態、ポジション状態、稼働状態を周期的に確認できるRUNTIME_SNAPSHOT
ログ抑制同じ状態を毎tick出さず、一定間隔で要約できるLOG_THROTTLE / TIMER_SUMMARY

ただし、OnTimerに重い処理を入れすぎると、タイマーイベントの処理自体が詰まる場合があります。処理時間、通信失敗時の戻り、再送制御を設計してください。

OnTimerに入れすぎない方がよい処理

OnTimerは便利ですが、すべての処理を移す場所ではありません。

価格変動に即応する処理や、発注・決済の中心処理は、OnTickや専用のexecution処理へ分けた方が確認しやすくなります。

OnTimerに入れすぎない処理理由推奨する分離
毎tick必要なentry判定Timer間隔中の価格変化を見落とす場合があるOnTick / signal層
発注処理そのもの外部連携や通知と注文責務が混ざるexecution層へ分離
重いWebRequestの連続実行通信失敗時に処理が詰まりやすいthrottle、timeout、queue管理
大量CSV出力ファイルI/Oが増え、BTや長時間稼働で重くなるイベント時出力、要約出力
毎秒大量ログExpertsログが膨らみ、確認しづらくなるログ間隔、状態変化時のみ出力

OnTimerは、EA本体の売買ロジックを置き換えるものではありません。定期処理、外部処理、補助処理を整理するために使います。

EventSetTimerの基本

EventSetTimerは、Timerイベントの発生間隔を秒単位で設定します。

通常はOnInitで一度だけ呼び出し、失敗した場合はGetLastErrorをログに残します。

input int InpTimerSeconds = 10;

bool SetupTimer()
{
   if(InpTimerSeconds <= 0)
   {
      PrintFormat("[TIMER_CONFIG_NG] seconds=%d reason=must_be_positive", InpTimerSeconds);
      return false;
   }

   ResetLastError();

   if(!EventSetTimer(InpTimerSeconds))
   {
      PrintFormat("[TIMER_SET_NG] seconds=%d last_error=%d",
                  InpTimerSeconds,
                  GetLastError());
      return false;
   }

   PrintFormat("[TIMER_SET_OK] seconds=%d", InpTimerSeconds);
   return true;
}

int OnInit()
{
   if(!SetupTimer())
      return INIT_FAILED;

   return INIT_SUCCEEDED;
}

タイマー間隔は短くしすぎないようにしてください。外部連携や通知では、5秒、10秒、30秒、60秒など、用途に応じて十分な間隔を取る方が扱いやすくなります。

EventKillTimerの基本

OnInitでEventSetTimerを使った場合は、OnDeinitでEventKillTimerを呼びます。

EA削除、時間足変更、再コンパイル、チャート終了などの場面で、終了ログとあわせてTimerを停止しておくと確認しやすくなります。

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

   PrintFormat("[DEINIT_TIMER_KILL] reason=%d", reason);
}

OnDeinitでは、Timer停止、handle解放、必要最小限の終了ログに留める設計が安全です。広範囲のObject削除や重い通信処理を終了時に詰め込みすぎないようにしてください。

ミリ秒タイマーの扱い

EventSetMillisecondTimerを使うと、1秒未満の間隔でTimerイベントを発生させることができます。

ただし、短い間隔でOnTimerを呼び出すほど、処理回数が増え、バックテスト時間や端末負荷に影響します。通常の通知、外部連携、状態監視では、秒単位のEventSetTimerで十分な場合が多いです。

方式用途注意点
EventSetTimer秒単位の定期処理通常の通知、外部連携、状態監視に向く
EventSetMillisecondTimer1秒未満の高頻度処理処理負荷やBT時間が増えやすい

高頻度タイマーは、必要性が明確な場合だけ使ってください。ログ確認や外部連携目的で安易に短い周期を設定すると、検証しづらくなる場合があります。

外部連携処理をOnTimerへ分ける

WebRequest、Discord通知、Google Sheets連携などは、OnTimerで周期管理すると整理しやすくなります。

OnTick側では、売買判定や内部状態の更新だけを行い、外部へ送る必要がある情報をキューや状態変数として保持します。OnTimer側で、送信間隔、失敗回数、再送可否を確認してから外部連携を実行します。

datetime g_last_external_sync = 0;
input int InpExternalSyncSeconds = 60;

bool CanRunExternalSync()
{
   datetime now = TimeCurrent();

   if(g_last_external_sync > 0 && (now - g_last_external_sync) < InpExternalSyncSeconds)
   {
      PrintFormat("[EXTERNAL_SYNC_SKIP] reason=throttle wait_sec=%d",
                  InpExternalSyncSeconds - (int)(now - g_last_external_sync));
      return false;
   }

   g_last_external_sync = now;
   return true;
}

void OnTimer()
{
   if(!CanRunExternalSync())
      return;

   RunExternalSync();
}

外部連携は、失敗しても売買ロジックを直接止めない設計にすることが多くあります。ただし、外部シートによる売買停止など、取引制御に関係する場合は、影響範囲を明確にしてください。

WebRequestをOnTimerで扱う時の注意点

WebRequestを使う場合は、URL許可、timeout、HTTPステータス、戻り値、レスポンス内容、失敗時の扱いを分けて確認します。

WebRequestが失敗した時に、EAの注文処理まで止めるのか、外部連携だけ停止するのか、次回OnTimerで再試行するのかを仕様として決めてください。

確認項目内容ログ例
URL許可MT5のオプションでWebRequest許可URLに入っているかWEBREQUEST_URL_CHECK
timeout通信待ち時間を長くしすぎていないかWEBREQUEST_TIMEOUT
HTTPステータス200、400、403、500などを確認するWEBREQUEST_STATUS
レスポンスJSON、CSV、テキストなどを解析できるかWEBREQUEST_PARSE
再試行失敗時にすぐ連続送信しないかWEBREQUEST_RETRY_SKIP
売買への影響外部連携失敗時にentryを止めるかどうかEXTERNAL_CONTROL_STATE

外部連携失敗をすべて注文失敗として扱うと原因が分かりにくくなります。通信失敗、解析失敗、外部制御による停止、EA内部のentry gateは分けてログ化してください。

Discord通知をOnTimerで送る考え方

Discord通知は、状態変化やイベント発生時に送ることが多い処理です。

ただし、同じ状態を毎tick送ると通知が多くなりすぎます。OnTimer側で送信間隔や重複通知を制御すると、運用しやすくなります。

datetime g_last_notify_time = 0;
string   g_last_notify_key  = "";

bool CanNotify(const string notify_key, const int min_interval_sec)
{
   datetime now = TimeCurrent();

   if(g_last_notify_key == notify_key && (now - g_last_notify_time) < min_interval_sec)
   {
      PrintFormat("[NOTIFY_SKIP] key=%s reason=duplicate_interval", notify_key);
      return false;
   }

   g_last_notify_key = notify_key;
   g_last_notify_time = now;
   return true;
}

void SendTimerNotification()
{
   string key = "runtime_alive";

   if(!CanNotify(key, 300))
      return;

   Print("[NOTIFY_SEND] key=runtime_alive");
}

通知処理では、通知対象、送信間隔、失敗時の再送、Webhook URLの管理、ログの出し方を分けて設計してください。

Google Sheets連携をOnTimerで扱う考え方

Google Sheets連携では、EAの状態、ポジション数、外部制御、稼働状態、検証メモなどを一定間隔で読み書きすることがあります。

このような処理は、tickごとではなく、OnTimerで間隔を決めて実行する方が扱いやすくなります。

処理OnTimerで確認することログ例
外部制御取得停止フラグ、BUY/SELL許可、稼働時間などSHEET_FETCH_CONTROL
状態送信EA稼働状態、ポジション数、最終更新時刻SHEET_WRITE_STATUS
エラー記録通信失敗、解析失敗、認証失敗SHEET_ERROR
送信間隔制御短時間で連続送信しないSHEET_THROTTLE
取引への影響外部制御がentry gateに影響するかEXTERNAL_GATE_STATE

外部シートを使ってEAの動作制御を行う場合は、OnTimerで取得した制御値をruntime状態へ反映し、OnTick側ではその状態を参照する設計にすると責務を分けやすくなります。

状態スナップショットをOnTimerで出す

OnTimerは、EAの稼働状態を一定間隔でログ化する用途にも向いています。

毎tickログを出すと確認しづらくなるため、状態が変わった時、または一定間隔ごとに要約ログを出す設計が有効です。

void PrintRuntimeSnapshot()
{
   int positions = PositionsTotal();
   double equity = AccountInfoDouble(ACCOUNT_EQUITY);
   double margin = AccountInfoDouble(ACCOUNT_MARGIN);
   double margin_free = AccountInfoDouble(ACCOUNT_MARGIN_FREE);

   PrintFormat("[RUNTIME_SNAPSHOT] symbol=%s positions=%d equity=%.2f margin=%.2f margin_free=%.2f",
               _Symbol,
               positions,
               equity,
               margin,
               margin_free);
}

void OnTimer()
{
   PrintRuntimeSnapshot();
}

実際のEAでは、毎回すべての情報を出すのではなく、ログプロファイルやdebug設定に応じて出力範囲を切り替えると運用しやすくなります。

バックテストでOnTimerを使う時の注意点

バックテストでOnTimerを使う場合、Timerイベントの回数が多くなるとテスト時間が伸びることがあります。

特に、1秒タイマーやミリ秒タイマーで詳細ログ、WebRequest相当の処理、CSV出力、状態スナップショットを多く出すと、検証が重くなります。

注意点問題対策
短いTimer周期OnTimer実行回数が増えるBT用は周期を長めにする
詳細ログ過多Expertsログが膨らむNG時だけ詳細、通常時は要約にする
CSV出力過多ファイルI/Oが増えるイベント時だけ記録する
外部連携相当処理検証速度や再現性に影響するBT時は外部連携をOFFまたはモック化する
ミリ秒タイマー実行回数が非常に多くなる必要な場合だけ使う

Strategy Testerでの検証では、OnTimer用のログ設定、外部連携ON/OFF、Timer周期を別inputにしておくと、速度と確認しやすさを調整できます。

OnTimerログの設計

OnTimerのログは、定期処理の開始、スキップ、成功、失敗、次回実行予定を分けると確認しやすくなります。

ログ名意味確認内容
TIMER_SET_OKTimer設定成功interval、mode、EA version
TIMER_SET_NGTimer設定失敗interval、GetLastError
TIMER_TICKOnTimer開始現在時刻、処理対象
TIMER_SKIP処理見送りthrottle、外部連携OFF、BTモードなど
EXTERNAL_SYNC_OK外部同期成功HTTP status、更新件数、反映状態
EXTERNAL_SYNC_NG外部同期失敗last_error、HTTP status、理由
TIMER_SUMMARY定期要約ログpositions、state、last_sync、last_notify
TIMER_KILLTimer停止deinit reason

OnTimerログは、毎回同じ量を出すのではなく、通常時は要約、異常時は詳細に切り替えると確認しやすくなります。

ログ出力例

[TIMER_SET_OK] seconds=30 mode=external_sync
[TIMER_TICK] server_time=2026.05.26 10:15:00 task=external_sync
[EXTERNAL_SYNC_SKIP] reason=throttle wait_sec=18
[SHEET_FETCH_CONTROL] status=200 result=ok entry_control=ALLOW
[NOTIFY_SKIP] key=runtime_alive reason=duplicate_interval
[RUNTIME_SNAPSHOT] symbol=GOLD positions=2 equity=10000.50 margin=250.00 margin_free=9750.50
[TIMER_SUMMARY] last_sync=2026.05.26 10:15:00 last_notify=2026.05.26 10:10:00
[TIMER_KILL] reason=1

ログ例の時刻、数値、HTTP status、状態名は確認用です。実際のEAでは、外部連携先やログプロファイルに合わせて調整してください。

よくある失敗パターン

失敗パターン問題点確認方法
EventSetTimerを呼んでいないOnTimerが実行されないOnInitでTIMER_SET_OKが出ているか確認する
EventKillTimerを忘れる終了処理の見通しが悪くなるOnDeinitでTIMER_KILLを出す
Timer周期が短すぎるログ過多やBT低速化につながるBT用・実運用用で周期を分ける
WebRequestを毎tick実行する通信回数が多くなり、処理が重くなるOnTimerへ分けてthrottleする
外部連携失敗で売買処理まで止める影響範囲が不明確になる外部連携失敗とentry gateを分ける
通知を重複送信するDiscordやメール通知が多すぎるnotify keyと最小間隔で制御する
BTで外部連携ログを出しすぎる検証ログが膨らみ、速度が落ちるBT時は簡易ログまたはOFFにする

外部制御と認証を混同しない

外部連携EAでは、外部制御と認証を混同しないことも重要です。

外部制御は、外部状態に応じて動作を変える仕組みです。たとえば、新規Entryを止める、通知だけ止める、特定機能を一時停止するなどです。

一方、認証は、EAの利用許可や利用者権限を確認する仕組みです。

区分役割公開記事での扱い
外部制御外部状態に応じて動作を変える概念のみ扱う。詳細ロジックは公開しない
認証利用許可や購入者状態を確認する詳細ロジック、判定条件、tokenは公開しない
通知状態や結果を外部へ送る通知成功・失敗の扱いを説明する
ログ状態を追跡する機密情報をマスクしてreason中心にする

認証NG、外部制御NG、通信失敗、通知失敗は、それぞれ意味が違います。ログ上もAUTH、EXTERNAL、NOTIFY、ORDERを分けて確認できるようにします。

よくある失敗例

失敗1:WebRequest失敗で売買処理全体を止める

通信失敗時に何を止めるかは仕様で分ける必要があります。新規Entryを止めるのか、通知だけ止めるのか、既存ポジション管理まで止めるのかを曖昧にすると危険です。

失敗2:Webhook URLやtokenをログへ出している

endpoint、token、Webhook URL、GAS URL、Google Sheet ID、APIキーをログへ出してはいけません。問い合わせ時にログが共有される前提で、必ずマスクします。

失敗3:通知失敗を注文失敗として扱う

通知と注文は別責務です。通知が失敗してもOrderSendが成功している場合があります。ORDER_RESULTとNOTIFY_RESULTを分ける必要があります。

失敗4:OnTickで毎回外部通信する

OnTickで毎回WebRequestを実行すると、EAが重くなり、通信制限やタイムアウトの原因になります。OnTimerや状態変化時に分ける方が安全です。

失敗5:JSON応答をそのまま信じる

JSON文字列が返っただけでは、EAが使える状態とは限りません。形式、必須項目、値の型、有効期限、失敗応答を確認する必要があります。

失敗6:外部制御と認証を混同する

外部制御は運用状態の制御、認証は利用許可の確認です。意味が違うため、ログも停止範囲も分ける必要があります。

失敗7:通信失敗時のログがない

外部連携の問題は、ログがないと原因を追えません。HTTPステータス、reason、scope、checkedAtをマスク済みで残す設計が必要です。

導入前・開発依頼前に整理する情報

外部連携EAを開発・改修する場合、次の情報を整理しておくと仕様確認がスムーズになります。

  • 外部連携の目的
  • 通知だけなのか、外部状態取得も行うのか
  • WebRequestを使うタイミング
  • 通信頻度
  • 通信失敗時に止める範囲
  • 新規Entryと既存ポジション管理の扱い
  • 通知失敗時に売買へ影響させるか
  • JSON応答の必須項目
  • 外部応答の有効期限
  • ログに残す項目
  • ログに出してはいけない機密情報
  • 開発版と配布版で見せる設定項目の違い
  • 問い合わせ時に送るログとマスク方法

外部連携は、便利な機能である一方、通信失敗、機密情報、サポート対応の設計が必要です。最初に責務分離と失敗時の扱いを決めておくことが重要です。

定期処理の依頼前にそろえる条件

開発依頼前に整理したい情報

OnTimerを使った外部連携、通知、CSV出力、状態監視を相談する場合は、次の情報を整理してください。

整理する情報確認内容
対象EA新規EA、既存EA改修、通知ツール、外部連携ツールのどれか
Timer周期何秒ごとに処理したいか、BT用と実運用用を分けるか
処理内容通知、外部制御取得、Google Sheets書き込み、CSV出力、状態監視など
外部連携先Discord、Google Sheets、GAS、外部API、CSVファイルなど
売買への影響外部連携失敗時にentryを止めるか、通知だけ止めるか
ログ方針通常時は要約、失敗時は詳細、BT時は軽量化など
再送制御通知やWebRequestを連続送信しない条件
バックテスト対応外部連携をOFFにするか、モック化するか、ログだけ出すか
機密情報Webhook URL、GAS URL、APIキー、認証情報を公開ログに出さない設計

外部連携や通知の不具合を相談する場合は、Timer設定ログ、WebRequest結果、HTTP status、送信間隔、通知スキップ理由、Expertsログ、Journalログを合わせて確認してください。

MT4/MT5の開発・改修相談はこちら

よくある質問

WebRequestを使えばEAは外部サービスと自由に連携できますか?

WebRequestを使うことで外部通信は可能になりますが、MT5側の許可設定、通信先の応答、タイムアウト、payload形式、サービス側の制限などを考慮する必要があります。EA本体の売買処理とは責務を分けるべきです。

通信失敗時はEAを止めるべきですか?

目的によります。新規Entryを止めるのか、通知だけ失敗扱いにするのか、既存ポジション管理まで止めるのかを仕様として決める必要があります。一般には、新規Entryと既存ポジション管理は分けて扱う方が安全です。

Webhook通知が失敗した場合、注文も失敗したという意味ですか?

違います。Webhook通知とOrderSendは別責務です。通知が失敗しても注文が成功している場合があります。ORDER_RESULTとNOTIFY_RESULTを分けて確認します。

JSON応答はそのまま売買判断に使ってよいですか?

避けるべきです。空応答、形式不正、必須項目不足、古い応答、失敗応答を確認し、外部状態として整理したうえでGATEやexternal controlへ渡します。

外部連携ログには何を出すべきですか?

event、reason、HTTP status、scope、checkedAt、マスク済みdetailなどを出します。endpoint、token、Webhook URL、GAS URL、APIキー、口座番号、顧客情報は出してはいけません。

OnTickで毎回WebRequestしてもよいですか?

避けた方が安全です。OnTickは呼び出し頻度が高いため、通信処理はOnTimer、状態変化時、一定間隔などへ分離する方が安定しやすくなります。

外部制御と認証は同じですか?

違います。外部制御は外部状態に応じた動作制御、認証は利用許可や権限の確認です。ログも停止範囲も分けて設計する必要があります。

実装コードがなくても外部連携設計の相談はできますか?

可能です。連携目的、通信タイミング、失敗時の扱い、通知内容、ログに出す項目、マスクすべき情報が整理されていれば、外部連携設計の相談は進めやすくなります。

関連する確認ページ

確認したい内容に応じて、次のページを参照してください。本文中ですでに案内したページは、ここでは重ねて掲載していません。

確認分野関連ページ
開発依頼ガイドMT5 EA作成代行を依頼する前に決めておくべき仕様項目
技術講座MQL5 EA設計パターン完全ガイド|signal・execution・risk・exitの責務分離
技術講座MQL5デバッグ・ログファースト開発完全ガイド|Print・Expertsログ・再現条件の整理
技術講座MQL5 WebRequest・JSON・外部API実装完全ガイド|Discord通知・Google Sheets連携前に知ること
技術講座MQL5イベント処理完全ガイド|OnTimer・OnTradeTransaction・OnChartEventの使い分け
技術講座MQL5長時間稼働・安定化完全ガイド|メモリ・ハンドル・ログ・オブジェクト管理の基本
開発実務ノートMQL5ログファースト設計を実コードで解説|event・side・reason・detailで追跡するEA開発
開発実務ノートMQL5バックテスト実務ノート|検証条件・最適化・リアル差分を記録する考え方
開発実務ノートMQL5商品化・配布前チェック実務ノート|Inputs・HELP・Snapshot・manual・利用者向けの配布準備の基本
開発実務ノート自動売買EAを自作する前に確認すること|ロジック・バックテスト・ログ設計
開発依頼ガイドEAに外部シート連携を追加したい時に整理すること|Google Sheets・CSV・WebRequest

あわせて確認したいページ

今回の確認内容に関連する補足ページです。必要な項目だけ確認してください。

確認目的関連ページ
「MQL5 外部連携 EA 設計」の読後に「MT5外部連携完全ガイド」を補足確認MT5外部連携完全ガイド
「MQL5 外部連携 EA 設計」の読後に「MQL5ファイル操作・CSVログ出力完全ガイド」を補足確認MQL5ファイル操作・CSVログ出力完全ガイド

まとめ

MQL5 EAでWebRequest、JSON、Webhookを扱う場合、外部連携を売買ロジックへ直接混ぜないことが重要です。

特に、次の4点を分けておくと、通信失敗や通知失敗が起きた時に原因を確認やすくなります。

  • external communication:外部通信の成功・失敗・タイムアウトを扱う
  • external control:外部状態をEAの動作制御へどう反映するかを扱う
  • notification:Webhook通知や外部通知の送信結果を扱う
  • execution / exit:注文実行や既存ポジション管理を外部通信と混同しない

外部通信が失敗した時に、新規Entryを止めるのか、既存ポジション管理を継続するのか、通知だけ失敗扱いにするのかは、仕様として明確にする必要があります。

また、endpoint、token、Webhook URL、GAS URL、Google Sheet ID、APIキー、口座番号、顧客情報は、本文・コード例・ログ例・スクリーンショットのいずれにも出してはいけません。

完成EAの外部制御ロジックや認証処理を公開しなくても、外部連携の責務分離、通信失敗時の安全側処理、ログ設計を理解しておくことで、開発依頼、既存EA改修、検証、不具合調査、販売後サポートの品質を上げやすくなります。

利用条件・相談窓口

確認内容案内ページ
MT4/MT5 技術講座|EA・インジケーター・MQL開発を学ぶ技術講座ハブ
不具合報告の方法不具合報告・調査依頼について
よくある質問|EA・インジケーター・開発依頼・サポートFAQよくある質問
免責事項・リスク説明免責事項・リスク説明

お問い合わせはEAFC公式LINEへ

EAFCサイト内の記事、商品、導入前確認、設定確認、ログ確認、開発依頼前のご相談など、各種お問い合わせはEAFC公式LINEより受け付けています。

※投資助言、売買指示、利益保証、推奨ロット、推奨銘柄の案内は行っておりません。

口座番号、認証情報、Webhook URL、GAS URL、APIキーなどの機密情報は送信しないでください。

ABOUT ME
記事URLをコピーしました