MQL5 EA設計パターン完全ガイド|signal・execution・risk・exitの責務分離
MQL5でEAを作る時、最初は「条件が成立したら注文する」というシンプルな構成でも動かすことはできます。しかし、実務で使うEAや検証しやすいEAにするには、シグナル判定、フィルター、ロット計算、発注前チェック、注文実行、決済、通知、UI、ログ、状態管理を分けて設計することが重要です。
すべての処理をOnTickへ直接書くと、最初は分かりやすく見えますが、条件追加、通知追加、複数銘柄対応、インジケーター連携、パネル操作、バックテスト検証、ログ確認を進めるほど保守しにくくなります。どこで止まったのか、なぜ注文しなかったのか、どの条件がブロックしたのかを追えないEAは、開発後の修正や検証で時間がかかります。
この記事では、MQL5 EA開発で使いやすい設計パターンとして、signal、filter、risk、execution、exit、notification、UI、log、input、stateの責務分離を整理します。EAファンクラブで扱うEA、半裁量EA、パネルEA、通知ツール、検証補助ツールにも応用できるように、実装方針、ログ設計、バックテスト確認、開発依頼前の整理項目までまとめます。
この記事は、MT5 / MQL5のEA開発、設計整理、検証補助、ログ確認を目的とした技術記事です。特定の売買判断、利益、勝率、損失回避、推奨ロット、推奨銘柄を案内するものではありません。
- この記事で確認すること
- MQL5 EA設計パターンとは
- OnTickにすべてを書かない
- GATE / SCORE / SPECIAL FILTERS / ENTRY の構造
- signal責務:エントリー候補を作る
- filter責務:取引してよい環境か確認する
- risk責務:発注前の安全確認を行う
- execution責務:注文処理をまとめる
- exit責務:決済・ポジション管理を分ける
- notification責務:通知と外部連携を分ける
- UI責務:パネルEAと半裁量EAの分離
- log責務:検証できるログを設計する
- input初期値とruntime状態を混同しない
- 複数銘柄・複数時間足EAでの状態管理
- AdminDevとUserLiveを分ける考え方
- バックテストで確認すること
- よくある設計ミスと確認ポイント
- 開発依頼前に整理する情報
- 実務チェック表
- よくある質問
- 関連ページ
- まとめ
この記事で確認すること
- MQL5 EA設計で責務分離が必要な理由
- OnTickにすべてを書かない設計方針
- signal / filter / risk / execution / exit の分離
- notification / UI / log / state の分離
- input初期値とruntime状態を混同しない考え方
- GATE / SCORE / SPECIAL FILTERS / ENTRY の判定構造
- OrderCheck、OrderSend、CTradeをどこに置くか
- インジケーター値取得とシグナル判定の分離
- 複数銘柄・複数時間足EAでの状態管理
- 半裁量EA・パネルEAでのUIと注文処理の分離
- 通知・外部連携をEA本体から分離する方法
- ログで検証しやすいEA設計
- バックテストと実チャートで確認すること
- 開発依頼前に整理する仕様
MQL5 EA設計パターンとは
MQL5 EA設計パターンとは、EAの処理を役割ごとに分け、後から検証・修正・拡張しやすくするための考え方です。MQL5では、OnInit、OnTick、OnTimer、OnDeinit、OnTradeTransaction、OnChartEventなどのイベント関数がありますが、それぞれの中に処理を直接詰め込むのではなく、役割別の関数へ分けて呼び出す構成にします。
たとえば、エントリー条件を判定する処理と、ロットを計算する処理と、注文を送信する処理は別の責務です。これらが混ざっていると、注文しない原因がシグナル不成立なのか、スプレッドフィルターなのか、証拠金不足なのか、OrderSend失敗なのかを切り分けにくくなります。
EAの設計では、「何を判断する処理か」「何を実行する処理か」「何を記録する処理か」を分けます。これにより、バックテスト、デモ口座確認、実チャート確認、ログ確認、改修依頼、機能追加が進めやすくなります。
| 責務 | 役割 | 代表的な処理 |
|---|---|---|
| signal | エントリー候補を作る。 | インジケーター値、価格条件、パターン判定。 |
| filter | 取引してよい環境か確認する。 | スプレッド、時間帯、曜日、上位足、最大ポジション。 |
| risk | 注文前の安全確認を行う。 | ロット、証拠金、SL/TP距離、StopLevel。 |
| execution | 注文を送信する。 | OrderCheck、OrderSend、CTrade。 |
| exit | 決済・管理を行う。 | 利確、損切り、トレーリング、建値移動。 |
| notification | 通知や外部連携を行う。 | Discord、Webhook、メール、Google Sheets。 |
| UI | 画面操作や表示を扱う。 | ボタン、パネル、ラベル、表示切替。 |
| log | 判断と結果を記録する。 | INIT、SIGNAL、FILTER、ORDER、EXIT、ERROR。 |
| state | EAの現在状態を保持する。 | 最終バー時刻、保有状態、通知済み状態、稼働モード。 |
OnTickにすべてを書かない
EA開発でよくある問題は、OnTickの中にすべての処理を書いてしまうことです。シグナル判定、インジケーター値取得、スプレッド確認、ロット計算、OrderSend、決済、通知、パネル更新、ログ出力がOnTickに混在すると、後から条件を追加するだけでも全体に影響が出ます。
OnTickは価格更新時に呼ばれるイベント関数です。頻度が高いため、毎ティックで重い処理や外部送信を行うと、動作が重くなったり、ログが大量に出たり、通知が連続送信されたりします。OnTickでは、価格更新に直接関係する処理を入口として呼び出すだけにし、定期監視や通知、パネル更新はOnTimerへ分けると整理しやすくなります。
OnTickの中身は、できるだけ短くします。状態確認、新バー判定、シグナル判定、フィルター、リスク確認、注文処理の順番を明確にし、それぞれを別関数へ分けます。
void OnTick()
{
if(!IsRuntimeReady())
return;
if(!IsNewBar(_Symbol, PERIOD_CURRENT))
return;
int signal = EvaluateEntrySignal();
if(signal == 0)
{
Print("ENTRY_SKIP reason=NO_SIGNAL");
return;
}
if(!PassEntryFilters(signal))
return;
if(!CheckEntryRisk(signal))
return;
ExecuteEntry(signal);
}このように、OnTickは全体の流れを読むための入口にします。実際の判定や注文処理は別関数へ分けることで、ログ確認や単体修正がしやすくなります。
GATE / SCORE / SPECIAL FILTERS / ENTRY の構造
EAのエントリー判定では、すべての条件を同じ階層で扱うよりも、GATE、SCORE、SPECIAL FILTERS、ENTRYのように段階を分けると整理しやすくなります。
GATEは、そもそも判定へ進んでよいかを確認する入口条件です。たとえば、自動売買許可、取引時間、必要データ、最大ポジション数、スプレッド上限などです。GATEで止まる条件は、シグナルの良し悪し以前に、取引してはいけない状態として扱います。
SCOREは、複数の条件を点数化または合格数として扱う判定です。移動平均、RSI、ATR、ローソク足条件、上位足方向などを組み合わせる場合、どの条件が成立しているかをログに残すと検証しやすくなります。
SPECIAL FILTERSは、通常条件とは別に、特定のリスクや例外を止めるための条件です。たとえば、重要時間帯、急変動、指標時間、連続負け後の停止、セッション制限、ニュース時間帯停止などが該当します。
ENTRYは、最終的に注文へ進む段階です。ここでは、ロット、証拠金、SL/TP、OrderCheck、OrderSend、CTradeなどの実行系へ接続します。
| 段階 | 役割 | ログ例 |
|---|---|---|
| GATE | 判定へ進める状態か確認します。 | GATE_BLOCK reason=SPREAD |
| SCORE | 条件成立数や点数を確認します。 | SCORE buy=3 sell=1 required=3 |
| SPECIAL FILTERS | 例外的な停止条件を確認します。 | SPECIAL_BLOCK reason=NEWS_TIME |
| ENTRY | 注文前確認と発注を行います。 | ENTRY_SEND type=BUY |
bool CanEvaluateEntry()
{
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
{
Print("GATE_BLOCK reason=TERMINAL_TRADE_NOT_ALLOWED");
return false;
}
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
{
Print("GATE_BLOCK reason=MQL_TRADE_NOT_ALLOWED");
return false;
}
double spread_points = GetSpreadPoints(_Symbol);
if(spread_points > 30.0)
{
PrintFormat("GATE_BLOCK reason=SPREAD spread=%.1f", spread_points);
return false;
}
return true;
}この例では、取引許可状態とスプレッドをGATEとして確認しています。GATEで止まった場合は、シグナル不成立ではなく「取引環境が不適切」としてログを分けることが重要です。
signal責務:エントリー候補を作る
signalは、エントリー候補を作る責務です。移動平均、RSI、ATR、ローソク足、ブレイクアウト、インジケーター値、上位足方向などから、BUY候補、SELL候補、または候補なしを返します。
signalの中では、注文を出さない方が安全です。signalはあくまで「買い候補か、売り候補か、何もしないか」を返すだけにします。注文処理まで入れてしまうと、フィルターやリスク確認を通さずに発注してしまう危険があります。
また、signalではインジケーター値の取得と判定を分けると保守しやすくなります。CopyBufferの取得失敗、EMPTY_VALUE、現在足と確定足の違いをログで確認できるようにしてください。
enum EntrySignal
{
SIGNAL_NONE = 0,
SIGNAL_BUY = 1,
SIGNAL_SELL = -1
};
EntrySignal EvaluateEntrySignal()
{
double fast_ma = 0.0;
double slow_ma = 0.0;
if(!GetMovingAverageValues(fast_ma, slow_ma))
{
Print("SIGNAL_SKIP reason=MA_VALUE_NOT_READY");
return SIGNAL_NONE;
}
if(fast_ma > slow_ma)
{
PrintFormat("SIGNAL_BUY fast=%.5f slow=%.5f", fast_ma, slow_ma);
return SIGNAL_BUY;
}
if(fast_ma < slow_ma)
{
PrintFormat("SIGNAL_SELL fast=%.5f slow=%.5f", fast_ma, slow_ma);
return SIGNAL_SELL;
}
PrintFormat("SIGNAL_NONE fast=%.5f slow=%.5f", fast_ma, slow_ma);
return SIGNAL_NONE;
}この例では、移動平均の値からシグナル候補だけを返しています。注文処理、ロット計算、証拠金確認はsignalには入れていません。
filter責務:取引してよい環境か確認する
filterは、シグナルが出た後に、取引してよい環境かを確認する責務です。スプレッド、時間帯、曜日、セッション、上位足方向、最大ポジション数、同一バー制限、連続エントリー制限などを扱います。
filterで止まった場合は、シグナルが悪いわけではなく、取引環境や運用条件により見送ったという扱いになります。そのため、ログではSIGNAL_NONEとFILTER_BLOCKを分けることが重要です。
| フィルター | 確認内容 | ログ例 |
|---|---|---|
| スプレッド | ask – bid が許容範囲か。 | FILTER_BLOCK reason=SPREAD |
| 時間帯 | 取引可能時間か。 | FILTER_BLOCK reason=TIME |
| 曜日 | 稼働曜日か。 | FILTER_BLOCK reason=DAY |
| 上位足 | 方向が一致しているか。 | FILTER_BLOCK reason=HTF |
| 最大ポジション | 保有数が上限以内か。 | FILTER_BLOCK reason=MAX_POSITION |
| クールダウン | 前回注文から十分経過したか。 | FILTER_BLOCK reason=COOLDOWN |
bool PassEntryFilters(EntrySignal signal)
{
double spread_points = GetSpreadPoints(_Symbol);
if(spread_points > 25.0)
{
PrintFormat("FILTER_BLOCK reason=SPREAD spread=%.1f", spread_points);
return false;
}
if(CountOpenPositionsByMagic(_Symbol, 12345) >= 1)
{
Print("FILTER_BLOCK reason=MAX_POSITION");
return false;
}
PrintFormat("FILTER_PASS signal=%d spread=%.1f", signal, spread_points);
return true;
}このように、filterでは「なぜ見送ったのか」を具体的に残します。後から検証する時、シグナルが少ないのか、フィルターが厳しすぎるのかを切り分けやすくなります。
risk責務:発注前の安全確認を行う
riskは、注文前の安全確認を行う責務です。ロット、証拠金、StopLevel、FreezeLevel、SL/TP距離、volume step、最大損失、最大ロット、口座余力などを確認します。
riskをsignalやexecutionに混ぜると、注文できない原因が分かりにくくなります。riskは、注文を送る直前に「この注文リクエストを作ってよいか」を確認する段階として分けます。
たとえば、signalはBUY候補を返していて、filterも通過しているのに、riskで証拠金不足やロット不正により停止することがあります。この場合は、ENTRY失敗ではなくRISK_BLOCKとしてログを残すと原因が明確になります。
bool CheckEntryRisk(EntrySignal signal)
{
double volume = NormalizeVolumeForOrder(_Symbol, 0.10);
if(volume <= 0.0)
{
Print("RISK_BLOCK reason=BAD_VOLUME");
return false;
}
if(!CheckRequiredMargin(_Symbol, signal, volume))
{
Print("RISK_BLOCK reason=NOT_ENOUGH_MARGIN");
return false;
}
if(!CheckStopDistance(_Symbol, signal))
{
Print("RISK_BLOCK reason=BAD_STOPS");
return false;
}
PrintFormat("RISK_PASS signal=%d volume=%.2f", signal, volume);
return true;
}この例では、ロット、証拠金、SL/TP距離をriskとして確認しています。実際には、OrderCheck前の準備として、銘柄仕様、StopLevel、volume step、余剰証拠金をログに残すと検証しやすくなります。
execution責務:注文処理をまとめる
executionは、注文リクエストを作成し、OrderCheck、OrderSend、またはCTradeを使って注文を送る責務です。ここでは、signal判定やfilter判定を行わず、すでに通過した注文候補を実際の注文処理へ変換します。
executionでは、注文前にMqlTradeRequestの内容をログに残し、OrderCheckの結果、OrderSendの結果、CTradeのResultRetcodeなどを確認します。注文送信後の結果は、OnTradeTransactionでも追跡できるようにします。
bool ExecuteEntry(EntrySignal signal)
{
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = 0.10;
request.magic = 12345;
request.type = (signal == SIGNAL_BUY) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
request.price = (signal == SIGNAL_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
PrintFormat("ORDER_REQUEST symbol=%s type=%d volume=%.2f price=%.5f",
request.symbol,
request.type,
request.volume,
request.price);
if(!OrderSend(request, result))
{
PrintFormat("ORDER_SEND_FAIL retcode=%d comment=%s", result.retcode, result.comment);
return false;
}
PrintFormat("ORDER_SEND_OK retcode=%d order=%I64u deal=%I64u comment=%s",
result.retcode,
result.order,
result.deal,
result.comment);
return true;
}この例ではOrderSendを使っていますが、CTradeを使う場合でも考え方は同じです。注文前のrequest、送信結果、retcode、commentをログに残し、注文結果を後から追えるようにします。
exit責務:決済・ポジション管理を分ける
exitは、決済やポジション管理を扱う責務です。利確、損切り、トレーリングストップ、建値移動、時間決済、条件反転決済、全決済、部分決済などを扱います。
entryとexitを同じ関数に混ぜると、エントリー条件と決済条件が混在し、どの条件でポジションを閉じたのかが分かりにくくなります。exitは、保有ポジションを確認し、決済条件を評価し、必要に応じて決済実行へ渡す構成にします。
| exit処理 | 内容 | ログ例 |
|---|---|---|
| 利確 | 目標価格や利益条件で決済。 | EXIT_TAKE_PROFIT |
| 損切り | 損失条件で決済。 | EXIT_STOP_LOSS |
| トレーリング | 利益方向にSLを追従。 | EXIT_TRAILING |
| 建値移動 | 一定利益後にSLを建値へ移動。 | EXIT_BREAKEVEN |
| 時間決済 | 一定時間経過で決済。 | EXIT_TIME |
| 手動全決済 | パネル操作で全決済。 | EXIT_MANUAL_CLOSE_ALL |
notification責務:通知と外部連携を分ける
notificationは、Discord通知、メール通知、プッシュ通知、Webhook、Google Sheets記録などを扱う責務です。売買ロジックと通知処理を直接結合しすぎると、通知失敗がEA本体の判定へ影響する可能性があります。
実務では、取引イベントやエラーイベントが発生した時に通知候補を作り、OnTimerで送信キューを処理する構成が扱いやすいです。OnTradeTransactionで約定イベントを検出し、OnTimerでDiscordへ通知するような分離も有効です。
通知ログでは、Webhook URL、APIキー、tokenなどの機密情報を出してはいけません。ログには、通知種別、対象注文、送信件数、HTTPステータス、エラー種別だけを残すようにします。
| 通知対象 | 発生元 | 処理先 |
|---|---|---|
| EA起動 | OnInit。 | notification queue。 |
| 注文送信 | execution。 | notification queue。 |
| 約定 | OnTradeTransaction。 | notification queue。 |
| 決済 | OnTradeTransaction / exit。 | notification queue。 |
| エラー | 各責務。 | error log / notification queue。 |
| 定期稼働報告 | OnTimer。 | notification queue。 |
UI責務:パネルEAと半裁量EAの分離
UIは、ボタン、ラベル、パネル、表示更新、クリック操作を扱う責務です。半裁量EAやパネルEAでは、OnChartEventでボタン操作を受け取り、実際の発注前チェックや注文処理は別責務へ渡します。
UI操作と注文処理を直接結合すると、ボタンを押しただけでロット確認や証拠金確認を通さず注文してしまう可能性があります。ボタン操作は「ユーザーからのリクエスト」として扱い、riskとexecutionを通してから注文する設計が安全です。
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id != CHARTEVENT_OBJECT_CLICK)
return;
if(sparam == "btn_buy")
{
Print("UI_REQUEST action=BUY");
RequestManualEntry(SIGNAL_BUY);
return;
}
if(sparam == "btn_close_all")
{
Print("UI_REQUEST action=CLOSE_ALL");
RequestManualCloseAll();
return;
}
}この例では、OnChartEventではUI操作を受けるだけにしています。RequestManualEntryの中で、ロット、証拠金、スプレッド、最大ポジション数などを確認してからexecutionへ進めます。
log責務:検証できるログを設計する
EA設計では、ログを後付けで入れるのではなく、最初から検証できるログを設計しておくことが重要です。ログがなければ、EAが注文しない理由、停止した理由、通知しない理由、バックテストと実チャートで違う理由を確認できません。
ログは、責務ごとに名前を分けます。INIT、SIGNAL、FILTER、RISK、ORDER_CHECK、ORDER_SEND、TRADE_TX、EXIT、NOTIFY、UI、ERRORのように分けると、検索しやすくなります。
| ログ名 | 残す内容 | 目的 |
|---|---|---|
INIT_OK | 初期化成功、input概要、タイマー設定。 | 起動確認。 |
SIGNAL_BUY | 買い候補の条件値。 | シグナル確認。 |
SIGNAL_NONE | 候補なしの理由。 | 判定不成立確認。 |
FILTER_BLOCK | 見送り理由。 | 環境条件の確認。 |
RISK_BLOCK | ロット、証拠金、SL/TPの問題。 | 注文前停止の確認。 |
ORDER_CHECK | OrderCheck結果。 | 発注前確認。 |
ORDER_SEND | OrderSend結果。 | 注文結果確認。 |
TRADE_TX | 注文・約定・決済イベント。 | 取引追跡。 |
EXIT | 決済理由。 | ポジション管理確認。 |
NOTIFY | 通知送信結果。 | 外部連携確認。 |
UI_REQUEST | ボタン操作。 | 半裁量操作確認。 |
input初期値とruntime状態を混同しない
EA設計では、input初期値とruntime状態を混同しないことが重要です。inputは、EA起動時にユーザーが設定する値です。一方で、runtime状態は、EA稼働中に変化する内部状態です。
たとえば、inputのロット設定、Magic Number、スプレッド上限、通知ON/OFFは設定値です。一方で、最終エントリー時刻、最終バー時刻、通知済みフラグ、現在の稼働モード、パネルの押下状態はruntime状態です。
これらを混ぜると、パラメータ変更、再初期化、バックテスト、テンプレート保存、パネル操作時に状態が分かりにくくなります。input、global state、temporary state、UI stateを分けて管理してください。
| 区分 | 例 | 注意点 |
|---|---|---|
| input初期値 | ロット、Magic Number、スプレッド上限。 | EA起動時の設定値として扱います。 |
| runtime状態 | 最終バー時刻、最終注文時刻。 | 稼働中に変化します。 |
| UI状態 | ボタンON/OFF、パネル表示状態。 | 表示と内部判定を分けます。 |
| 通知状態 | 通知済みフラグ、送信待ちキュー。 | 重複送信防止に使います。 |
| 検証状態 | 詳細ログON/OFF、テストモード。 | 本番動作と混同しないようにします。 |
複数銘柄・複数時間足EAでの状態管理
複数銘柄・複数時間足EAでは、状態管理を銘柄・時間足ごとに分ける必要があります。単一のlast_bar_timeだけで管理すると、USDJPYの新バー判定がEURUSDにも影響する、M5の状態がH1にも影響する、といった不具合が起きます。
対象symbolとtimeframeを組み合わせた状態を持つと、バー同期、シグナル判定、通知、ポジション管理を分けやすくなります。状態構造体を作り、銘柄名、時間足、最終バー時刻、データ取得状態、直近エラーなどを管理すると整理しやすくなります。
struct SymbolTfState
{
string symbol;
ENUM_TIMEFRAMES timeframe;
datetime last_bar_time;
bool data_ready;
int last_error;
};このように、状態を構造体で管理すると、マルチシンボルEAやMTF EAのログ確認がしやすくなります。どの銘柄・どの時間足で止まっているのかを明確にできます。
AdminDevとUserLiveを分ける考え方
EAを開発・検証・販売・導入補助する場合、開発者向けのAdminDev機能と、利用者向けのUserLive機能を分けて考えることが重要です。AdminDevは、検証、詳細ログ、診断、内部状態確認、開発用スイッチなどを扱います。UserLiveは、通常利用者が安全に使うための設定、表示、通知、操作に絞ります。
AdminDev向けの詳細ログや診断機能をそのままUserLiveへ出すと、画面が複雑になり、誤操作や設定ミスが増える可能性があります。一方で、UserLive向けに情報を削りすぎると、トラブル時に原因確認ができません。
実務では、input、表示、ログ、通知、診断をモードで分けると扱いやすくなります。通常運用では簡潔にし、検証時だけ詳細ログを有効化する構成が安全です。
| 区分 | 対象 | 内容 |
|---|---|---|
| AdminDev | 開発・検証担当者。 | 詳細ログ、内部状態、診断、デバッグ情報。 |
| UserLive | 通常利用者。 | 必要最小限の設定、状態表示、通知。 |
| 共通 | 両方。 | 安全な初期値、エラー表示、基本ログ。 |
| 非表示推奨 | 通常利用者。 | token、Webhook URL、内部ID、過剰な診断情報。 |
バックテストで確認すること
EA設計パターンを整理した後は、バックテストで処理の流れを確認します。単に損益を見るのではなく、どの段階でエントリー候補が出て、どのフィルターで止まり、どの注文が通り、どの決済条件で閉じたかを確認します。
バックテストでは、signal、filter、risk、execution、exitのログを確認すると、EAの挙動を追いやすくなります。たとえば、エントリーが少ない場合、シグナルが少ないのか、フィルターが厳しいのか、riskで止まっているのかを分けて確認できます。
パネル操作、外部通知、WebRequest、Google Sheets連携などは、バックテストだけでは確認しにくい場合があります。これらは実チャートやデモ口座で別途確認します。
- INITログで初期化状態を確認する
- SIGNALログで候補発生数を確認する
- FILTERログで見送り理由を確認する
- RISKログでロット・証拠金・SL/TP距離を確認する
- ORDER_CHECKログで発注前確認を確認する
- ORDER_SENDログで注文結果を確認する
- TRADE_TXログで約定・決済イベントを確認する
- EXITログで決済理由を確認する
- バックテスト結果を将来成績保証として扱わない
よくある設計ミスと確認ポイント
EA設計でよくある問題は、責務が混ざっていることです。シグナル関数の中で注文している、フィルターの中でロットを変えている、通知処理の失敗で売買判定まで止まる、UI操作が直接OrderSendを呼んでいる、といった構成は保守しにくくなります。
| 設計ミス | 問題点 | 修正方針 |
|---|---|---|
| OnTickに全処理を書く | 原因追跡が難しくなります。 | 責務別関数へ分けます。 |
| signal内で注文する | filterやriskを通らない可能性があります。 | signalは候補返却だけにします。 |
| filterとriskが混在する | 見送り理由が曖昧になります。 | 環境条件と注文前確認を分けます。 |
| UIボタンが直接注文する | 発注前チェックを飛ばす危険があります。 | UIリクエストからrisk / executionへ渡します。 |
| 通知失敗でEA本体が止まる | 外部連携が売買処理に影響します。 | 通知キューと本体処理を分けます。 |
| ログ名が統一されていない | 検証時に検索しにくくなります。 | 責務ごとにログ名を固定します。 |
| inputとruntime状態が混ざる | 再初期化やUI操作時に混乱します。 | 設定値と稼働状態を分けます。 |
開発依頼前に整理する情報
EA開発や改修を依頼する場合は、売買条件だけでなく、EA全体の責務分離を整理しておくと、実装後のズレを減らしやすくなります。どの条件がシグナルなのか、どの条件がフィルターなのか、どの条件がリスク管理なのかを分けて伝えることが重要です。
「移動平均がクロスしたら買う」という条件だけでは、スプレッド上限、時間帯、ロット、SL/TP、最大ポジション数、決済条件、通知、ログ、パネル操作が不明です。これらを最初に整理すると、開発・検証・修正が進めやすくなります。
| 整理する情報 | 内容 | 理由 |
|---|---|---|
| シグナル条件 | 買い条件、売り条件、参照足、確定足。 | signal責務を設計するため。 |
| フィルター条件 | スプレッド、時間帯、曜日、上位足。 | filter責務を設計するため。 |
| リスク条件 | ロット、証拠金、SL/TP、最大損失。 | risk責務を設計するため。 |
| 注文方法 | 成行、指値、逆指値、CTrade、OrderSend。 | execution責務を設計するため。 |
| 決済条件 | 利確、損切り、トレーリング、時間決済。 | exit責務を設計するため。 |
| 通知 | Discord、メール、Webhook、CSV。 | notification責務を設計するため。 |
| UI | ボタン、パネル、半裁量操作。 | UI責務を設計するため。 |
| ログ | 通常ログ、詳細ログ、エラーログ。 | 検証と保守に必要です。 |
| 送らない情報 | 口座番号、パスワード、APIキー、Webhook URL。 | 機密情報を保護するため。 |
実務チェック表
- OnTickにすべての処理を書いていない
- signalはエントリー候補だけを返している
- filterは取引環境の確認に限定している
- riskはロット・証拠金・SL/TP確認を担当している
- executionは注文処理に集中している
- exitは決済・ポジション管理として分離している
- notificationはEA本体の売買判定から分離している
- UI操作は発注前チェックを通してから注文している
- input初期値とruntime状態を混同していない
- ログ名を責務ごとに分けている
- バックテストと実チャートで確認する項目を分けている
- 口座情報や認証情報をログへ出していない
よくある質問
EA設計で最初に分けるべき責務は何ですか?
まず、signal、filter、risk、execution、exitを分けるのが基本です。シグナル候補、取引環境、注文前安全確認、注文実行、決済を分けると、注文しない理由や決済理由を追いやすくなります。
OnTickにすべて書くと何が問題ですか?
処理が混在し、原因追跡が難しくなります。シグナル、フィルター、ロット、注文、通知、パネル更新をOnTickに詰め込むと、ログ確認や修正時の影響範囲が広がります。
signalの中でOrderSendしてもよいですか?
推奨しません。signalはエントリー候補を返すだけにし、filter、risk、executionを通してから注文する方が安全です。発注前チェックを飛ばさない設計にしてください。
通知処理はどこに置くべきですか?
通知はnotification責務として分けるのが扱いやすいです。取引イベントをOnTradeTransactionで検出し、OnTimerで通知キューを送信するように分けると、連続送信や外部連携失敗の影響を抑えやすくなります。
パネルEAではどの責務が重要ですか?
UI、risk、execution、logが重要です。ボタン操作はOnChartEventで受け取り、実際の注文前にはロット、証拠金、スプレッド、最大ポジション数などを確認してからexecutionへ進めます。
ログはどの程度出せばよいですか?
通常運用では重要な状態変化とエラーを中心にし、検証時は詳細ログを出せるようにします。毎ティックで大量ログを出すと読みにくくなるため、通常ログ、詳細ログ、エラーログを分ける設計が有効です。
関連ページ
MQL5 EA設計を整理する時は、イベント処理、ログ確認、注文処理、OrderCheck、CTrade、インジケーター連携、マルチシンボル、ファイル出力、バックテストをあわせて確認すると、実装と検証の切り分けがしやすくなります。
| 確認したい内容 | 関連ページ |
|---|---|
| イベント処理を確認する | MQL5イベント処理完全ガイド |
| ログ確認とデバッグを確認する | MQL5デバッグ・ログファースト開発完全ガイド |
| 注文・ポジション・履歴管理を確認する | MQL5注文・ポジション・履歴管理完全ガイド |
| CTradeを使った注文・決済を確認する | MQL5標準ライブラリ・CTrade完全ガイド |
| OrderCheckで発注前確認をする | MQL5 OrderCheckで証拠金・ロット・StopLevelを確認する方法 |
| ロット・証拠金・銘柄仕様を確認する | MQL5ロット・証拠金・銘柄仕様完全ガイド |
| インジケーター開発とEA連携を確認する | MQL5インジケーター開発・EA連携完全ガイド |
| EAからCopyBufferで値を取得する | MQL5でインジケーター値をEAに取り込む方法 |
| マルチシンボル・MTF対応を確認する | MQL5マルチシンボル・マルチタイムフレーム完全ガイド |
| CSVログ出力を確認する | MQL5ファイル操作・CSVログ出力完全ガイド |
| バックテストと最適化を確認する | MT5ストラテジーテスター・最適化完全ガイド |
| 長時間稼働と安定化を確認する | MQL5長時間稼働・安定化完全ガイド |
| EAログを問い合わせ前に確認する | EAのログを問い合わせ前に確認する方法 |
| 開発・改修相談の入口を確認する | 開発・改修の相談ページ |
まとめ
MQL5 EA設計では、signal、filter、risk、execution、exit、notification、UI、log、stateを分けることが重要です。すべてをOnTickへ書くのではなく、イベント関数を入口として扱い、実際の処理を責務ごとの関数へ分離すると、検証と保守が進めやすくなります。
シグナル判定はエントリー候補を返すだけにし、フィルターで取引環境を確認し、riskでロット・証拠金・SL/TP距離を確認し、executionでOrderCheckやOrderSendを実行します。決済はexit、通知はnotification、パネル操作はUIとして分けると、EAが注文しない理由や決済した理由をログで追いやすくなります。
開発や改修を依頼する場合も、売買条件だけでなく、フィルター、リスク管理、注文方法、決済、通知、UI、ログ、状態管理を整理しておくと、実装後のズレを減らせます。EAは「動けばよい」だけでなく、「なぜ動いたか、なぜ止まったかを確認できる」設計にすることが重要です。
