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にすべてを書かない
- エントリー判定を段階ごとに分ける
- signal責務:エントリー候補を作る
- filter責務:取引してよい環境か確認する
- risk責務:発注前の安全確認を行う
- execution責務:注文処理をまとめる
- exit責務:決済・ポジション管理を分ける
- notification責務:通知と外部連携を分ける
- UI責務:パネルEAと半裁量EAの分離
- log責務:検証できるログを設計する
- input初期値とruntime状態を混同しない
- EAのデータを配列・構造体・クラスへ整理する
- 複数銘柄・複数時間足EAでの状態管理
- 開発・検証向け機能と通常利用向け機能を分ける
- バックテストで確認すること
- よくある設計ミスと確認ポイント
- 開発依頼前に整理する情報
- 実務チェック表
- データ構造を説明・検証するための確認項目
- よくある質問
- 関連する確認ページ
- あわせて確認したいページ
- まとめ
- お問い合わせはEAFC公式LINEへ
- 利用条件・相談窓口
この記事で確認すること
- MQL5 EA設計で責務分離が必要な理由
- OnTickにすべてを書かない設計方針
- signal / filter / risk / execution / exit の分離
- notification / UI / log / state の分離
- input初期値とruntime状態を混同しない考え方
- エントリー判定を段階ごとに分ける考え方
- OrderCheck、OrderSend、CTradeをどこに置くか
- インジケーター値取得とシグナル判定の分離
- 複数銘柄・複数時間足EAでの状態管理
- 半裁量EA・パネルEAでのUIと注文処理の分離
- 通知・外部連携をEA本体から分離する方法
- ログで検証しやすいEA設計
- バックテストと実チャートで確認すること
- 開発依頼前に整理する仕様
MQL5 EA設計パターンとは
MQL5のイベント関数は実行の入口です。OnTick、OnTimer、OnTradeTransactionなどのイベントハンドラ自体に全処理を詰め込まず、signal・filter・risk・execution・exit・logなどの責務へ分けると、イベント起点と業務ロジックを切り離して検証しやすくなります。
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は全体の流れを読むための入口にします。実際の判定や注文処理は別関数へ分けることで、ログ確認や単体修正がしやすくなります。
エントリー判定を段階ごとに分ける
EAのエントリー判定では、すべての条件を同じ階層で扱うのではなく、取引可能状態の確認、条件評価、例外条件の確認、発注前確認と注文処理のように段階を分けると整理しやすくなります。
最初に、自動売買許可、取引時間、必要データ、最大ポジション数、スプレッドなどを確認します。ここで条件を満たしていない場合は、シグナルの良し悪しとは分けて、取引へ進めない状態として扱います。
次に、移動平均、RSI、ATR、ローソク足条件、上位足方向など、エントリー候補を作るための条件を評価します。複数条件を組み合わせる場合は、どの条件が成立したかをログへ残すと検証しやすくなります。
通常の判定とは別に、急変動、重要時間帯、指標発表時間、セッション制限、連続損失後の停止など、特定条件で取引を止める確認も分けておくと、停止理由を追いやすくなります。
これらを通過した後に、ロット、証拠金、SL/TP、OrderCheckなどの発注前確認を行い、OrderSendやCTradeによる注文処理へ進みます。
| 段階 | 役割 | ログ例 |
|---|---|---|
| 取引可能状態の確認 | 判定へ進める状態か確認します。 | PRECHECK_BLOCK reason=SPREAD |
| 条件評価 | エントリー候補となる条件の成立状況を確認します。 | SIGNAL_CHECK matched=3 required=3 |
| 例外条件の確認 | 急変動や特定時間帯などの停止条件を確認します。 | CONDITION_BLOCK reason=NEWS_TIME |
| 発注前確認・注文 | 注文条件を確認して発注処理へ進みます。 | ORDER_SEND type=BUY |
bool CanEvaluateEntry()
{
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
{
Print("PRECHECK_BLOCK reason=TERMINAL_TRADE_NOT_ALLOWED");
return false;
}
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
{
Print("PRECHECK_BLOCK reason=MQL_TRADE_NOT_ALLOWED");
return false;
}
double spread_points = GetSpreadPoints(_Symbol);
if(spread_points > 30.0)
{
PrintFormat("PRECHECK_BLOCK reason=SPREAD spread=%.1f", spread_points);
return false;
}
return true;
}この例では、取引許可状態とスプレッドを、エントリー条件を評価する前の確認項目として分けています。ここで止まった場合はシグナル不成立とはせず、「取引へ進めない状態」としてログを分けることが重要です。
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には入れていません。
EAタイプごとにsignalの意味を分ける
同じBUY・SELL候補でも、トレンド追随、反転、レンジではsignalの意味が異なります。トレンド型では方向へ乗る候補、反転型では反転を狙う候補、レンジ型ではレンジ内の反応候補として整理すると、後段のfilter、risk、execution、exitを分けて設計しやすくなります。
特に反転型やレンジ型では、急変動時の停止条件やトレンド化した場合の扱いを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 |
新規エントリー停止と既存ポジション管理を分ける
EAを停止する条件では、新規エントリーを止めることと、既存ポジションの管理まで止めることを分けて考えます。
例えば外部停止やリスク条件によって新規エントリーを停止しても、既存ポジションの損切り、トレーリングストップ、建値移動、時間決済などは継続した方がよい場合があります。どの停止条件で何を継続するかは、EAの仕様として明確にしておく必要があります。
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へ連結する前提ではありません。初期化、値の取得、状態更新、終了処理をそれぞれの例で区別して確認してください。
変数・配列・構造体・クラスの違い
MQL5では、単一の値を扱う変数、同じ型の値を複数扱う配列、関連する値をまとめる構造体、データと処理をまとめるクラスを使い分けます。
どれを使うかは、コードの規模、扱うデータの種類、将来の改修予定、ログ確認のしやすさによって変わります。
| 種類 | 主な役割 | 向いている場面 | 注意点 |
|---|---|---|---|
| 変数 | 1つの値を保持する | ロット値、ON/OFF、カウント、価格、時刻 | 数が増えすぎると影響範囲を追いにくくなります。 |
| 配列 | 同じ型の値を複数保持する | 価格データ、インジケーター値、複数ロジック状態、履歴 | 範囲外アクセスや取得本数不足に注意します。 |
| 構造体 | 関連する複数の値をまとめる | ポジション情報、signal結果、risk結果、ロジック状態 | 処理までまとめたい場合はクラス化も検討します。 |
| クラス | データと処理をまとめる | 注文管理、ログ管理、外部連携、UI管理、状態管理 | 過剰にクラス化すると逆に読みにくくなる場合があります。 |
| include / mqh | コードを別ファイルへ分ける | 共通型、クラス、ユーティリティ、ログ処理、注文処理 | 依存関係と読み込み順を整理する必要があります。 |
小さなEAでは、変数と関数だけで十分な場合があります。一方で、複数ロジック、複数ポジション、外部連携、通知、パネル表示、ログ制御を扱うEAでは、構造体やクラスを使って状態を整理すると保守しやすくなります。
配列を使う場面
配列は、同じ種類の値を複数まとめて扱う時に使います。
MQL5では、価格データ、インジケーターバッファ、複数ロジックの状態、複数ポジションの情報、ログ用の集計値などで配列を使う場面があります。
| 用途 | 配列で扱う例 | 確認すること |
|---|---|---|
| 価格データ | 終値、高値、安値、MqlRates配列 | 取得本数、shift、時系列順、AS_SERIES |
| インジケーター値 | CopyBufferで取得した値 | バッファ番号、取得本数、EMPTY_VALUE |
| 複数ロジック | ロジック別ON/OFF、Magic、状態 | ロジック番号と配列indexの対応 |
| 複数ポジション | ticket、volume、price、profit | 件数、symbol、magic、direction |
| ログ集計 | GATE通過数、エラー数、通知数 | 初期化、リセット、出力タイミング |
| 最適化・検証 | 条件別のカウント、検証結果、内部統計 | テスト開始時の初期化と終了時の集計 |
配列を使う時は、配列サイズとindex範囲の確認が重要です。特に、CopyRatesやCopyBufferの戻り値が想定本数より少ない時に、そのまま配列を参照すると問題につながる場合があります。
構造体を使う場面
構造体は、関連する複数の値を1つのまとまりとして扱いたい時に使います。
たとえば、ロジック状態、ポジション情報、signal判定結果、risk判定結果、ログ出力用の状態などは、構造体にまとめると読みやすくなる場合があります。
struct LogicState
{
int logic_id;
long magic;
bool enabled;
datetime last_signal_time;
string last_reason;
};
上記は、状態をまとめる考え方を示す最小例です。取引条件や運用設定を示すものではありません。
| 構造体にまとめる候補 | 含める値の例 | メリット |
|---|---|---|
| LogicState | logic_id、magic、enabled、last_reason | ロジック別状態をまとめて確認できます。 |
| PositionSnapshot | ticket、symbol、magic、volume、profit | 保有状態を一覧化しやすくなります。 |
| SignalResult | direction、score、gate_ok、reason | 判定結果と理由を分けて渡せます。 |
| RiskResult | allowed、block_reason、normalized_lot | 新規許可と停止理由を整理できます。 |
| ExitState | trail_active、close_reason、last_exit_time | 保有後の決済管理を追いやすくなります。 |
| NotifyState | last_send_time、last_result、throttle_state | 通知状態と通知抑制を管理しやすくなります。 |
構造体は、関連する値をまとめたい時に便利です。ただし、1つの構造体にすべてを詰め込みすぎると意味が分かりにくくなるため、目的ごとに小さく分ける方が確認しやすくなります。
クラスを使う場面
クラスは、データと処理をひとまとまりにしたい時に使います。
MQL5では、注文管理、ポジション管理、ログ管理、外部連携、UIパネル管理、設定管理などでクラスを使うことがあります。
class CLogLimiter
{
private:
datetime m_last_time;
public:
CLogLimiter()
{
m_last_time = 0;
}
bool CanPrint(const int interval_sec)
{
datetime now_time = TimeCurrent();
if(now_time - m_last_time >= interval_sec)
{
m_last_time = now_time;
return true;
}
return false;
}
};
上記は、ログ出力間隔を管理する考え方を示す最小例です。特定のEA運用や取引設定を示すものではありません。
| クラス化しやすい処理 | まとめる内容 | 注意点 |
|---|---|---|
| 注文処理 | 注文送信、retcode確認、実行ログ | signal層とexecution層を混同しないようにします。 |
| ポジション管理 | 保有検索、snapshot作成、Magic別集計 | Hedging / Nettingの違いを確認します。 |
| ログ管理 | カテゴリ、抑制、出力形式、集計 | ログ過多を避ける設計にします。 |
| 外部連携 | WebRequest、CSV、送信結果、外部状態取得 | URLやtokenの実値をログに出さないようにします。 |
| UIパネル | ボタン、ラベル、表示更新、クリック処理 | 表示系と判定系を分けます。 |
| 設定管理 | input値、runtime値、保存値の整理 | input初期値と稼働中状態を混同しないようにします。 |
クラスは便利ですが、すべてをクラス化すればよいわけではありません。小さな処理まで過度にクラス化すると、かえって追いにくくなる場合があります。
includeファイルとmqhの役割
EA本体のmq5ファイルが大きくなる場合、共通処理やクラス定義をincludeファイルへ分けることがあります。
MQL5では、mqhファイルを使って、共通定義、構造体、クラス、ユーティリティ関数、ログ処理などを分離できます。
| 分割対象 | ファイル例 | 入れる内容 | 確認すること |
|---|---|---|---|
| 共通定義 | CommonTypes.mqh | enum、struct、共通常数、型定義 | 他ファイルから参照される基本型をまとめます。 |
| ログ処理 | LogUtil.mqh | PrintFormat補助、ログ抑制、カテゴリ管理 | ログ形式と出力量を揃えます。 |
| 注文処理 | TradeExecutor.mqh | 注文リクエスト、retcode確認、実行ログ | signal判定と注文実行を分けます。 |
| ポジション管理 | PositionSnapshot.mqh | 保有検索、snapshot作成、Magic別集計 | riskやexitへ渡すデータを整理します。 |
| 外部連携 | ExternalControl.mqh | WebRequest、CSV、外部状態取得 | 機密値をログへ出さない設計にします。 |
| UI表示 | PanelView.mqh | ボタン、ラベル、ステータス表示 | 表示系と判定系を分離します。 |
include分割では、ファイルを分けること自体が目的ではありません。責務ごとに分け、どこを見ればよいか分かる状態にすることが目的です。
グローバル変数を増やしすぎない考え方
MQL5では、グローバル変数を使うこと自体は珍しくありません。
ただし、EAの規模が大きくなるにつれて、グローバル変数が増えすぎると、どの関数がどの状態を変更しているのか分かりにくくなります。
| 状態 | 問題点 | 整理方法 |
|---|---|---|
| グローバル変数が多い | 影響範囲が追いにくい | 関連する値を構造体やクラスにまとめます。 |
| 似た名前の変数が多い | 誤参照しやすい | 命名ルールを決めます。 |
| runtime状態が混在 | 再起動時の挙動が分かりにくい | input、runtime、persistを分けます。 |
| 複数ロジックが同じ変数を使う | ロジック間で干渉する | ロジック別stateを用意します。 |
| 表示用と判定用が混在 | UI不具合が判定に影響する | 表示系と判定系を分離します。 |
| 外部連携状態が散在 | 通信失敗や認証状態を追いにくい | ExternalStateやAuthStateとして整理します。 |
すべてのグローバル変数をなくす必要はありません。重要なのは、何をグローバルに置くのか、何を構造体やクラスへまとめるのかを意図的に決めることです。
EAの状態管理を構造化する方法
EAでは、現在の稼働状態、認証状態、外部制御状態、signal結果、risk結果、exit状態、通知状態などを管理することがあります。
これらを1つずつ別変数にすると、規模が大きくなった時に追いにくくなります。状態を目的別に構造化すると、ログ確認や改修がしやすくなります。
| 状態カテゴリ | 含める値の例 | 確認するログ |
|---|---|---|
| RuntimeState | initialized、paused、last_tick_time | INIT、RUNTIME |
| SignalState | direction、score、gate_ok、reason | SIGNAL、GATE、SCORE |
| RiskState | allowed、block_reason、normalized_lot | RISK |
| ExitState | trail_active、close_reason、last_exit_time | EXIT |
| AuthState | auth_ok、masked_account、expiry_state | AUTH |
| NotifyState | last_send_time、last_result、throttle_state | NOTIFY |
| PanelState | visible、selected_tab、last_update_time | UI、OBJECT |
EAの状態管理では、input初期値とruntime状態を混同しないことが重要です。Inputs欄に表示される値と、稼働中に変化した状態は分けて管理してください。
複数ロジックEAでのデータ分離
複数ロジックEAでは、ロジックごとにsignal、ポジション、Magic Number、risk、exit、ログ、設定を分ける必要があります。
ロジック1とロジック2が同じ変数や同じMagic Numberを使っていると、どちらの処理でエントリーしたのか、どちらのexit条件で決済したのかが分かりにくくなります。
| 分離する項目 | 理由 | 確認すること |
|---|---|---|
| Logic ID | ロジックを識別するため | 配列indexとLogic IDの対応を固定します。 |
| Magic Number | ポジション識別のため | ロジックごとに重複していないか確認します。 |
| SignalState | 判定結果を分けるため | BUY / SELL / NO ENTRYの理由を分けます。 |
| RiskState | 停止理由を分けるため | ロット、最大数、外部停止を分けます。 |
| ExitState | 保有後の管理を分けるため | トレール、時間決済、反対条件を分けます。 |
| PositionSnapshot | 保有状態を分けるため | symbol、magic、direction、ticketを分けます。 |
| LogTag | ログ追跡しやすくするため | LOGIC01、LOGIC02などの表記を揃えます。 |
struct LogicRuntime
{
int logic_id;
long magic;
bool enabled;
string last_signal_reason;
};
LogicRuntime g_logic_states[4];この例は、複数ロジックの状態を配列で持つ考え方を示すものです。実際のEAでは、初期化、index範囲、Magic Numberの重複確認、ログ出力をあわせて設計してください。
position snapshotの考え方
position snapshotとは、現在保有しているポジション情報を、ある時点の状態として整理しておく考え方です。
MQL5では、保有ポジションをその場で毎回探すだけでなく、symbol、magic、ticket、volume、price、profit、directionなどを一度整理してから、riskやexitで使う設計にすることがあります。
| snapshot項目 | 内容 | 使う場面 |
|---|---|---|
| ticket | ポジションまたは注文識別 | 決済、変更、ログ照合 |
| symbol | 対象銘柄 | チャート銘柄・対象銘柄の確認 |
| magic | ロジック識別 | EA別・ロジック別管理 |
| direction | BUY / SELL | 方向別集計、方向別決済 |
| volume | 保有量 | ロット集計、部分決済確認 |
| open_price | 建値 | 損益、距離、exit条件 |
| profit | 現在損益 | risk、exit、表示 |
| comment | 注文コメント | EAやロジック識別の補助 |
snapshotは、保有状態を固定的に記録するというより、特定時点の状態を整理して後続処理へ渡す考え方です。最新状態が必要な処理では、取得タイミングと更新タイミングを明確にしてください。
配列範囲エラーを避ける方法
配列範囲エラーは、配列の存在しないindexへアクセスした時に発生します。
MQL5では、CopyRatesやCopyBufferで取得できた本数が不足しているのに、想定本数がある前提で配列を読むと、実行時エラーや誤判定につながる場合があります。
| 原因 | 起きやすい場面 | 対策 |
|---|---|---|
| 取得本数不足 | CopyRates / CopyBufferの戻り値未確認 | 戻り値を確認してから参照します。 |
| index計算ミス | ロジック番号と配列indexのずれ | index変換ルールを固定します。 |
| 配列初期化漏れ | 起動直後やリセット後 | ArrayResizeや初期値設定を確認します。 |
| ループ条件ミス | for文の終了条件 | i < ArraySize(array)を確認します。 |
| 時系列配列の向き | AS_SERIESの扱い違い | shift 0と配列indexの意味を確認します。 |
| 空配列参照 | 対象ポジションがない時 | 件数0の場合の分岐を用意します。 |
| 削除中の配列参照 | 要素削除後に同じindexを読む時 | 削除順序や再取得を確認します。 |
double close_values[];
int copied = CopyClose(_Symbol, PERIOD_M15, 0, 3, close_values);
if(copied < 3)
{
PrintFormat("DIAG/DATA close_not_enough requested=%d copied=%d", 3, copied);
return;
}
double last_closed_bar = close_values[1];この例は、配列を読む前に取得本数を確認する考え方を示すものです。実際の処理では、配列の向き、shift、必要本数、データ不足時の扱いを仕様として決めてください。
初期化・リセット・runtime状態の分離
EAでは、input初期値、OnInitで作る初期状態、稼働中に変わるruntime状態、再起動後も保持する状態を分けて考えます。
これらを混同すると、設定を変えていないのに状態が変わる、リセット後に一部だけ古い状態が残る、時間足変更後に挙動が変わる、といった問題につながる場合があります。
| 分類 | 意味 | 例 | 確認すること |
|---|---|---|---|
| input初期値 | EA起動時に読み込む設定 | ロット、時間、ON/OFF、Magic Number | Inputs欄とsetファイルで確認します。 |
| OnInit初期化 | 起動時に作る内部状態 | handle、配列、state初期化 | INITログで確認します。 |
| runtime状態 | 稼働中に変わる状態 | ボタンON/OFF、last signal、last send time | 再起動時の扱いを決めます。 |
| persist状態 | 再起動後も保持したい状態 | 一部のUI状態、許可状態、前回選択タブ | 保存・復元ルールを決めます。 |
| リセット処理 | 状態を初期化する処理 | 配列クリア、オブジェクト削除、カウンタ初期化 | 削除対象と保持対象を分けます。 |
input初期値とruntime状態の分離は、不具合調査やサポート対応でも重要です。再現条件を確認する時は、setファイルだけでなく、稼働中に変更された状態がないかも確認してください。
保守しやすい命名とコメント
変数、構造体、クラス、includeファイルを整理しても、命名が曖昧だと保守しにくくなります。
名前は、何を表すのか、どの層のデータなのか、runtime状態なのか、input設定なのかが分かるように付けると確認しやすくなります。
| 対象 | 分かりにくい例 | 整理した例 |
|---|---|---|
| ロジック状態 | flag1、state2 | logic_state、signal_state |
| 停止理由 | msg、reason | risk_block_reason、gate_skip_reason |
| 配列 | arr、data | close_values、logic_states |
| クラス | CMain、CWork | CTradeExecutor、CLogLimiter |
| include | common.mqh | PositionSnapshot.mqh、LogUtil.mqh |
| runtime状態 | enabled | runtime_entry_enabled |
| 表示状態 | text、color | panel_status_text、panel_status_color |
コメントは、多すぎても少なすぎても読みづらくなります。何をしているかではなく、なぜその処理が必要なのか、どの責務に属するのかを補足するコメントが有効です。
悪い設計と整理された設計の比較
MQL5の設計では、動くことだけを優先すると、後から改修しにくいコードになることがあります。
特に、大規模EA、複数ロジックEA、外部連携EA、パネル付きEAでは、状態管理と責務分離が重要になります。
| 項目 | 整理しにくい設計例 | 整理された設計例 |
|---|---|---|
| 状態管理 | グローバル変数が散らばっている | RuntimeStateやLogicStateにまとめる |
| 複数ロジック | ロジックごとの状態が混在している | logic_idとMagicで分離する |
| ログ | Printが大量に散在している | カテゴリ別ログにする |
| 配列 | 戻り値を見ずにindex参照している | 取得本数を確認してから読む |
| include | 何でも1ファイルへ詰め込む | 責務ごとにmqhへ分ける |
| UI | 表示状態と判定状態が同じ変数になっている | 表示系と判定系を分ける |
| 初期化 | 一部の状態がリセットされない | OnInit、reset、persistを分ける |
| 外部連携 | URLや送信状態が各所に散らばっている | ExternalStateやNotifyStateにまとめる |
整理された設計とは、必ず複雑なクラス構成にすることではありません。変更点、影響範囲、ログ、再現条件を追いやすい構造にすることです。
複数銘柄・複数時間足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のログ確認がしやすくなります。どの銘柄・どの時間足で止まっているのかを明確にできます。
開発・検証向け機能と通常利用向け機能を分ける
EAを開発・検証し、その後に通常運用する場合は、開発や検証で必要な機能と、通常利用時に必要な機能を分けて考えると扱いやすくなります。
開発・検証時には、詳細ログ、内部状態、診断情報、デバッグ用設定などが役立ちます。一方、通常利用時にそれらをすべて表示すると、設定項目が増えて操作が複雑になり、確認すべき情報も分かりにくくなる場合があります。
通常利用では必要な設定、状態表示、通知、基本ログを中心にし、詳細な診断情報は必要な時だけ確認できる構成にすると、日常運用と不具合調査の両方に対応しやすくなります。
| 区分 | 主な対象 | 内容 |
|---|---|---|
| 開発・検証向け | 開発・検証担当者 | 詳細ログ、内部状態、診断、デバッグ情報 |
| 通常利用向け | 通常利用者 | 必要な設定、状態表示、通知、基本操作 |
| 共通 | どちらでも使用 | 安全な初期値、エラー表示、基本ログ |
| 通常画面へ表示しない情報 | 認証・内部情報 | 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の改修、大規模EAの整理、複数ロジック化、外部連携追加を相談する場合は、現在の構造情報を整理しておくと確認しやすくなります。
ソースコードがある場合は、mq5本体、includeファイル、setファイル、ログ、再現条件をセットで整理してください。ソースコードがない場合でも、Inputs欄、画面表示、ログ、動作説明から構造を推定できる場合があります。
| 伝える情報 | 内容 | 注意点 |
|---|---|---|
| 対象ファイル | mq5、mqh、ex5など | 最新版か確認します。 |
| include構成 | 分割ファイル一覧 | 不足ファイルがないか確認します。 |
| ロジック数 | Logic1〜LogicNなど | Magic Numberとの対応を整理します。 |
| 状態管理 | グローバル変数、構造体、クラス | どこで更新されるか確認します。 |
| setファイル | input設定 | 認証情報やURLを確認してから送ります。 |
| Expertsログ | INIT、GATE、RISK、EXECUTIONなど | 発生前後を含めます。 |
| 再現条件 | 銘柄、時間足、発生時刻、操作 | 同条件で確認できるようにします。 |
| 要望内容 | 何を整理・追加・改修したいか | 表示変更か判定変更かを分けます。 |
送ってはいけない情報
ソース、setファイル、ログ、スクリーンショットを送る場合でも、機密情報はそのまま送らないでください。
| 送らない情報 | 理由 | 対応 |
|---|---|---|
| 口座番号の全桁 | 確認に不要な範囲まで共有される可能性があります。 | 必要に応じて一部をマスクします。 |
| ログインパスワード | 口座へアクセスできる情報です。 | 送らないでください。 |
| 投資家パスワード | 閲覧専用であっても共有不要です。 | 送らないでください。 |
| Webhook URL | 第三者が通知先へ送信できる可能性があります。 | 全文をマスクします。 |
| GAS URL | 外部連携先に関係する情報です。 | 全文をマスクします。 |
| APIキー・認証トークン | 外部サービスや認証に関係します。 | 送らないでください。 |
| 個人情報 | 確認に不要な情報が含まれる可能性があります。 | スクリーンショットやログから削除します。 |
| 第三者の情報 | 関係者以外の情報が含まれる場合があります。 | 共有前に削除します。 |
送ってよいか判断できない情報は、そのまま送らず、伏せ字にするか削除してください。
MQL5クラス・構造体・配列設計チェック表
クラス・構造体・配列設計を見直す時は、以下を確認してください。
| チェック項目 | 確認内容 |
|---|---|
| 変数、配列、構造体、クラスの役割を分けている | 単一値、複数値、関連データ、処理付き状態を混同していないか確認します。 |
| グローバル変数を増やしすぎていない | 関連する値を構造体やクラスへまとめられるか確認します。 |
| 関連する値を構造体へまとめている | LogicState、PositionSnapshot、RiskResultなどのまとまりを確認します。 |
| 必要な箇所だけクラス化している | 注文処理、ログ管理、外部連携、UI管理などを目的ごとに確認します。 |
| include / mqhの分割目的が明確になっている | ファイルを分ける理由と責務を説明できるか確認します。 |
| 複数ロジックEAではロジック別stateを分けている | Logic ID、Magic Number、signal、risk、exitを分離します。 |
| position snapshotの項目を整理している | ticket、symbol、magic、direction、volume、profitを確認します。 |
| 配列を読む前に戻り値を確認している | CopyRates、CopyBuffer、CopyCloseなどの取得本数を確認します。 |
| input初期値、runtime状態、persist状態を分けている | Inputs欄と稼働中に変化する状態を混同しないようにします。 |
| 表示系と判定系を分けている | UIの表示更新が判定ロジックへ不要に影響しないようにします。 |
| 命名とコメントが責務に沿っている | 何の値か、どの層の状態か、どこで更新されるかを追えるようにします。 |
| setファイル、Expertsログ、Journalログを保存している | 再現条件を後から確認できるようにします。 |
| 口座番号や認証情報をマスクしている | 共有前に機密情報を確認します。 |
よくある質問
EA設計で最初に分けるべき責務は何ですか?
まず、signal、filter、risk、execution、exitを分けるのが基本です。シグナル候補、取引環境、注文前安全確認、注文実行、決済を分けると、注文しない理由や決済理由を追いやすくなります。
OnTickにすべて書くと何が問題ですか?
処理が混在し、原因追跡が難しくなります。シグナル、フィルター、ロット、注文、通知、パネル更新をOnTickに詰め込むと、ログ確認や修正時の影響範囲が広がります。
signalの中でOrderSendしてもよいですか?
推奨しません。signalはエントリー候補を返すだけにし、filter、risk、executionを通してから注文する方が安全です。発注前チェックを飛ばさない設計にしてください。
通知処理はどこに置くべきですか?
通知はnotification責務として分けるのが扱いやすいです。取引イベントをOnTradeTransactionで検出し、OnTimerで通知キューを送信するように分けると、連続送信や外部連携失敗の影響を抑えやすくなります。
パネルEAではどの責務が重要ですか?
UI、risk、execution、logが重要です。ボタン操作はOnChartEventで受け取り、実際の注文前にはロット、証拠金、スプレッド、最大ポジション数などを確認してからexecutionへ進めます。
ログはどの程度出せばよいですか?
通常運用では重要な状態変化とエラーを中心にし、検証時は詳細ログを出せるようにします。毎ティックで大量ログを出すと読みにくくなるため、通常ログ、詳細ログ、エラーログを分ける設計が有効です。
クラスを使わなくてもEAは作れますか?
作れます。小規模なEAでは、変数と関数だけで十分な場合もあります。ただし、複数ロジック、外部連携、通知、UI、ログ管理が増えると、クラスや構造体で整理した方が保守しやすくなる場合があります。
構造体とクラスはどう使い分けますか?
関連する値をまとめたいだけなら構造体が向いています。状態に加えて、その状態を操作する処理もまとめたい場合はクラスを検討します。過剰にクラス化すると読みにくくなるため、目的に応じて使い分けます。
配列範囲エラーはなぜ起きますか?
存在しないindexを参照した時に起きます。CopyRatesやCopyBufferの取得本数不足、ループ条件ミス、配列初期化漏れ、ロジック番号と配列indexのずれなどが原因になる場合があります。
includeファイルを分けるメリットは何ですか?
責務ごとにコードを分けることで、注文処理、ログ処理、ポジション管理、外部連携、UIなどの確認場所を整理できます。ただし、分割しすぎると依存関係が追いにくくなるため、目的を明確にして分けることが重要です。
複数ロジックEAでは何を分けるべきですか?
Magic Number、signal状態、risk状態、exit状態、position snapshot、ログタグ、input設定の意味をロジックごとに分ける必要があります。ロジック間で同じ状態を共有すると、調査や改修が難しくなる場合があります。
グローバル変数が多いと何が問題ですか?
どの関数がどの状態を変更しているのか追いにくくなります。影響範囲が広くなり、改修時に予期しない挙動が出る場合があります。関連する値は構造体やクラスにまとめると確認しやすくなります。
配列と構造体を組み合わせてもよいですか?
可能です。複数ロジックの状態を管理する場合などは、構造体を配列で持つ設計が有効な場合があります。ただし、indexとロジック番号の対応、初期化、範囲チェックを明確にしてください。
UIパネルの状態も構造体で管理できますか?
管理できます。ただし、表示用の状態とEAの判定用状態は分けてください。UIの表示更新が遅れたり失敗したりしても、判定ロジックに不要な影響を与えない設計が重要です。
関連する確認ページ
確認したい内容に応じて、次のページを参照してください。本文中ですでに案内したページは、ここでは重ねて掲載していません。
あわせて確認したいページ
今回の確認内容に関連する補足ページです。必要な項目だけ確認してください。
| 確認目的 | 関連ページ |
|---|---|
| 読後の任意の手続き・一覧入口。本文解決や購入の必須前提にしない | MT4/MT5 技術講座 |
| 「MQL5 EA 設計 責務分離」の読後に「MQL5 Multi-Logic EA設計を実コードで解説」を補足確認 | MQL5 Multi-Logic EA設計を実コードで解説 |
| 「MQL5 EA 設計 責務分離」の読後に「MQL5重複エントリー防止を実コードで解説」を補足確認 | MQL5重複エントリー防止を実コードで解説 |
| 「MQL5 EA 設計 責務分離」の読後に「自動売買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は「動けばよい」だけでなく、「なぜ動いたか、なぜ止まったかを確認できる」設計にすることが重要です。
お問い合わせはEAFC公式LINEへ
EAFCサイト内の記事、商品、導入前確認、設定確認、ログ確認、開発依頼前のご相談など、各種お問い合わせはEAFC公式LINEより受け付けています。
※投資助言、売買指示、利益保証、推奨ロット、推奨銘柄の案内は行っておりません。
口座番号、認証情報、Webhook URL、GAS URL、APIキーなどの機密情報は送信しないでください。
利用条件・相談窓口
| 確認内容 | 案内ページ |
|---|---|
| MT5 開発依頼・作成代行|EA・インジケーター新規開発・MT4/MT5既存改修 | 開発・改修の相談ページ |
| EA・MT5・MQL5 技術辞典|用語・関数・設定を目的別に探す | EA・MT5・MQL5 技術辞典|用語・関数・設定を目的別に探す |
