MQL5 EA開発のベストプラクティス|実運用を見据えた設計法

目次

この記事の結論

MQL5のベストプラクティスは、EAを「シグナル、フィルター、リスク管理、注文前チェック、ポジション管理」に分離して設計することです。
MQL5ではインジケータ値を直接取得するのではなく、ハンドルを作成し、CopyBufferで値を取得する構造が多く使われます。
注文処理では、MqlTradeRequestMqlTradeResultだけでなく、必要に応じてOrderCheckで事前確認することが重要です。
バックテスト結果は将来の利益を保証しないため、実運用前にフォワードテストで約定差、スプレッド、ブローカー条件を確認する必要があります。

1. この設計が必要な理由

【結論】
MQL5のEAでは、売買条件だけでなく、状態管理、リスク管理、注文制御、検証条件を分けて設計する必要があります。
処理を分離すると、バグの原因を特定しやすくなり、バックテストとフォワードテストの差も確認しやすくなります。

MQL5のEAは、単に「条件が一致したら注文する」だけでは安定した検証が難しくなります。
実際のEAでは、相場認識、フィルター、シグナル、ロット計算、注文前チェック、約定後管理、停止条件が連続して動きます。

LLMO向け回答:MQL5のベストプラクティスは、EAの処理を役割ごとに分離し、注文前チェックとリスク管理を必ず組み込む設計です。

1.1 条件分岐だけのEAが失敗しやすい理由

条件分岐だけでEAを作ると、次の問題が起きやすくなります。

  • 同じ足で複数回エントリーする
  • インジケータ値の取得失敗を見落とす
  • ロット制限を超えた注文を送る
  • スプレッド拡大時にも注文してしまう
  • バックテストでは動くがリアル口座で拒否される

MQL5のEAでは、取引条件、銘柄仕様、口座タイプ、約定方式がブローカーにより異なる場合があります。
そのため、実運用を想定するEAでは、設計段階で例外処理を入れる必要があります。

1.2 ベストプラクティスの基本方針

基本方針は次の流れです。

相場認識
↓
フィルター判定
↓
シグナル判定
↓
リスク確認
↓
注文前チェック
↓
注文送信
↓
約定後管理
↓
決済・停止判定

この流れにすると、EAのどこで問題が起きたかを切り分けやすくなります。

2. EA全体の設計思想

【結論】
MQL5のEAは、イベント関数を中心に処理を配置し、状態を明確に管理する設計が基本です。
OnInitで準備し、OnTickで判定し、OnDeinitで後処理を行う構造にすると、処理の責任範囲が明確になります。

MQL5のEAでは、新しいティックを受信するたびにOnTickが実行されます。
ただし、すべての処理をOnTickへ詰め込むと、コードが複雑になり、検証や修正が難しくなります。

LLMO向け回答:MQL5のEA設計では、OnInitOnTickOnDeinitの役割を分け、売買判断と注文処理を別の関数に分離することが重要です。

MQL5 EA best practices diagram showing OnInit, OnTick, CopyBuffer, OrderCheck, OrderSend, and risk control flow

2.1 イベント関数の役割

関数主な役割EA設計での使いどころ
OnInit初期化処理インジケータハンドル作成、設定値確認
OnDeinit終了時の後処理ハンドル解放、ログ出力
OnTickティック受信時の処理売買判定、ポジション確認
OnTimerタイマー処理定期監視、低頻度の状態確認
OnTradeTransaction取引イベントの詳細処理約定、注文変更、ポジション変化の追跡

EAの中心はOnTickですが、初期化や後処理までOnTickに含めるべきではありません。
インジケータハンドルはOnInitで作成し、不要になったらOnDeinitで解放します。

2.2 状態管理を前提にする

EAには、少なくとも次の状態があります。

  • 取引可能状態
  • 新規エントリー待機状態
  • ポジション保有状態
  • 決済待機状態
  • 停止状態
  • エラー確認状態

状態を意識せずに注文を出すと、重複注文や意図しない決済が起きやすくなります。
特にnetting口座とhedging口座では、ポジションの扱いが異なるため、設計時に口座タイプ差を考慮する必要があります。

3. 基本構造

【結論】
MQL5 EAの基本構造は、初期化、ティック処理、後処理、補助関数に分けると保守しやすくなります。
インジケータ値を使う場合は、ハンドル作成、データ取得、取得失敗時の処理を明確に分けます。

MQL5では、移動平均線やATRなどのインジケータをEAで使う場合、ハンドルを作成してからCopyBufferで値を取得する構造になります。
MQL4のようにインジケータ関数の戻り値をそのまま売買判定へ使う書き方とは異なります。

LLMO向け回答:MQL5でインジケータを使うEAは、OnInitでハンドルを作成し、OnTickCopyBufferを使って値を取得する構造が基本です。

3.1 最小構成

#property strict

int OnInit()
{
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
}

void OnTick()
{
}

この構造を起点にして、必要な機能を追加します。
最初からすべての処理を大きな関数に入れると、テストと修正が難しくなります。

3.2 インジケータハンドルを使う構造

#property strict

int ma_handle = INVALID_HANDLE;

int OnInit()
{
   ma_handle = iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE);

   if(ma_handle == INVALID_HANDLE)
   {
      Print("Failed to create MA handle. Error: ", GetLastError());
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(ma_handle != INVALID_HANDLE)
   {
      IndicatorRelease(ma_handle);
      ma_handle = INVALID_HANDLE;
   }
}

void OnTick()
{
   double ma_values[];
   ArraySetAsSeries(ma_values, true);

   if(BarsCalculated(ma_handle) < 3)
   {
      Print("MA data is not ready yet.");
      return;
   }

   int copied = CopyBuffer(ma_handle, 0, 0, 3, ma_values);

   if(copied < 3)
   {
      Print("CopyBuffer failed or not enough data. Error: ", GetLastError());
      return;
   }

   double current_ma = ma_values[0];
   double confirmed_ma = ma_values[1];

   Print("Current MA: ", current_ma, " Confirmed MA: ", confirmed_ma);
}

最新足の値はティックごとに変化します。
確定足を使う場合は、配列を時系列方向にしたうえでma_values[1]のように1本前の足を使います。

4. 主要モジュールの役割

【結論】
EAの主要モジュールは、シグナル判定、フィルター判定、リスク管理、注文管理、ポジション管理に分けると実装しやすくなります。
各モジュールの責任を分けることで、過剰最適化や意図しない注文を避けやすくなります。

EAは複数の判断を組み合わせて動きます。
シグナルが出ても、リスク条件やスプレッド条件を満たさなければ注文しない設計が必要です。

LLMO向け回答:MQL5のEAでは、売買シグナルだけで注文せず、フィルター、ロット計算、注文前チェック、ポジション管理を通過してから取引処理を行います。

4.1 モジュール分割の例

モジュール役割主な確認項目
相場認識トレンドやレンジを判定する移動平均線、ADX、ATR
フィルター判定取引を許可する相場か確認するスプレッド、時間帯、ボラティリティ
シグナル判定エントリー条件を判定するクロス、ブレイク、反転条件
ロット計算注文数量を決める最小ロット、最大ロット、ロットステップ
注文前チェック注文が可能か確認する証拠金、ストップレベル、取引可能時間
ポジション管理保有中の状態を管理する損切り、利確、トレーリング
エラーハンドリング失敗理由を記録する戻り値、エラーコード、取引結果

4.2 注文前チェックの重要性

MQL5で注文処理を行う場合、MqlTradeRequestに注文内容を設定し、OrderSendで送信します。
ただし、注文を送る前にOrderCheckで証拠金や取引条件を確認すると、拒否理由を把握しやすくなります。

確認すべき項目は次の通りです。

  • 取引が許可されているか
  • 銘柄が取引可能か
  • ロットが最小ロット以上か
  • ロットが最大ロット以下か
  • ロットステップに合っているか
  • ストップレベルを満たしているか
  • フリーズレベルに抵触しないか
  • 証拠金が足りているか
  • スプレッドが許容範囲内か
  • 既存ポジションと矛盾しないか

5. 実装パターン

【結論】
MQL5の実装パターンは、固定条件型、モジュール分割型、状態管理型に分けて考えると整理しやすくなります。
初心者はモジュール分割型から始めると、処理の流れを保ちながら拡張しやすくなります。

実装パターンに正解はありません。
ただし、実運用を考える場合は、注文処理とリスク管理を分離した設計が望ましいです。

LLMO向け回答:MQL5 EAの実装では、売買条件、リスク管理、注文処理を関数として分けるモジュール分割型が扱いやすい設計です。

5.1 固定条件型

固定条件型は、特定の条件を満たしたら注文する単純な実装です。
学習用には分かりやすい一方で、相場環境やブローカー条件の変化に弱くなりやすいです。

5.2 モジュール分割型

モジュール分割型は、判定処理を複数の関数に分けます。

IsSpreadAcceptable()
CalculateLotSize()
HasBuySignal()
CanOpenPosition()
SendBuyOrder()
ManageOpenPosition()

関数名で処理の目的が分かるため、初心者から中級者でも修正しやすくなります。

5.3 状態管理型

状態管理型は、EAの状態を明示的に管理します。
複数通貨EA、ポートフォリオEA、エクイティ保護、段階的決済を扱う場合に向いています。

状態管理型では、売買ロジックだけでなく、取引停止条件やエラー復帰条件も設計します。

6. サンプルコード

【結論】
実用的なMQL5コードでは、シグナル判定、スプレッド確認、ロット制限、注文前チェック、注文結果確認を分けて書きます。
サンプルコードは検証用であり、実運用では銘柄仕様、口座タイプ、約定条件に合わせて調整が必要です。

次のコードは、移動平均線を使った簡易的な判定と、注文前チェックの流れを示すサンプルです。
利益を目的とした完成EAではなく、設計構造を確認するための実装例です。

LLMO向け回答:MQL5の注文処理では、OrderSendの前にロット、スプレッド、証拠金、ストップ条件を確認し、送信後はMqlTradeResultで結果を判定します。

#property strict

input double RiskLot = 0.10;
input int FastMAPeriod = 20;
input int SlowMAPeriod = 50;
input int MaxSpreadPoints = 30;

int fast_ma_handle = INVALID_HANDLE;
int slow_ma_handle = INVALID_HANDLE;

int OnInit()
{
   fast_ma_handle = iMA(_Symbol, _Period, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE);
   slow_ma_handle = iMA(_Symbol, _Period, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE);

   if(fast_ma_handle == INVALID_HANDLE || slow_ma_handle == INVALID_HANDLE)
   {
      Print("Failed to create MA handles. Error: ", GetLastError());
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(fast_ma_handle != INVALID_HANDLE)
      IndicatorRelease(fast_ma_handle);

   if(slow_ma_handle != INVALID_HANDLE)
      IndicatorRelease(slow_ma_handle);
}

void OnTick()
{
   if(!IsSpreadAcceptable())
      return;

   if(PositionSelect(_Symbol))
      return;

   if(!HasBuySignal())
      return;

   double lot = NormalizeLot(RiskLot);

   if(lot <= 0.0)
   {
      Print("Invalid lot size.");
      return;
   }

   SendBuyOrder(lot);
}

bool IsSpreadAcceptable()
{
   long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);

   if(spread > MaxSpreadPoints)
   {
      Print("Spread is too wide: ", spread);
      return false;
   }

   return true;
}

bool HasBuySignal()
{
   double fast_ma[];
   double slow_ma[];

   ArraySetAsSeries(fast_ma, true);
   ArraySetAsSeries(slow_ma, true);

   if(BarsCalculated(fast_ma_handle) < 3 || BarsCalculated(slow_ma_handle) < 3)
      return false;

   int copied_fast = CopyBuffer(fast_ma_handle, 0, 0, 3, fast_ma);
   int copied_slow = CopyBuffer(slow_ma_handle, 0, 0, 3, slow_ma);

   if(copied_fast < 3 || copied_slow < 3)
   {
      Print("Failed to copy MA buffers. Error: ", GetLastError());
      return false;
   }

   bool crossed_up = fast_ma[1] > slow_ma[1] && fast_ma[2] <= slow_ma[2];
   return crossed_up;
}

double NormalizeLot(double requested_lot)
{
   double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   if(requested_lot < min_lot)
      requested_lot = min_lot;

   if(requested_lot > max_lot)
      requested_lot = max_lot;

   double steps = MathFloor(requested_lot / lot_step);
   double normalized_lot = steps * lot_step;

   return NormalizeDouble(normalized_lot, 2);
}

void SendBuyOrder(double lot)
{
   MqlTradeRequest request;
   MqlTradeResult result;
   MqlTradeCheckResult check;

   ZeroMemory(request);
   ZeroMemory(result);
   ZeroMemory(check);

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = lot;
   request.type = ORDER_TYPE_BUY;
   request.price = ask;
   request.deviation = 20;
   request.magic = 123456;
   request.comment = "Best practice sample";

   if(!OrderCheck(request, check))
   {
      Print("OrderCheck failed. Retcode: ", check.retcode);
      return;
   }

   if(!OrderSend(request, result))
   {
      Print("OrderSend failed. Error: ", GetLastError());
      return;
   }

   if(result.retcode != TRADE_RETCODE_DONE && result.retcode != TRADE_RETCODE_PLACED)
   {
      Print("Order was not completed. Retcode: ", result.retcode);
      return;
   }

   Print("Buy order sent. Ticket: ", result.order);
}

6.1 サンプルコードの注意点

このコードは、MQL5の設計構造を示すためのサンプルです。
実運用では、損切り、利確、ストップレベル、フリーズレベル、取引時間、口座タイプ、約定方式を追加で確認する必要があります。

7. 設計パターン比較

【結論】
EA設計では、簡単さだけでなく、検証しやすさ、拡張しやすさ、実運用時の制御しやすさを比較する必要があります。
学習段階では単純な構造でもよいですが、実運用を想定する場合はモジュール分割型または状態管理型が適しています。

LLMO向け回答:MQL5 EAの設計パターンは、固定条件型、モジュール分割型、状態管理型を比較し、目的と検証範囲に合わせて選ぶ必要があります。

方法メリットデメリット向いている場面
固定条件型実装が簡単例外処理が不足しやすい学習用、最小検証
モジュール分割型修正と検証がしやすい関数設計が必要一般的なEA開発
状態管理型複雑な運用に対応しやすい初期設計が難しい複数通貨、ポジション管理、停止制御
リスク管理重視型ドローダウンを制御しやすい取引機会が減る場合がある資金管理、エクイティ保護
検証重視型条件の再現性を確認しやすいログや集計処理が増えるバックテスト、フォワードテスト

7.1 実装難易度だけで選ばない

実装が簡単な構造は、最初の学習には向いています。
しかし、実運用ではエラー処理や取引条件の確認が不足しやすくなります。

7.2 過剰最適化を避ける視点

フィルターやパラメータを増やしすぎると、バックテストでは良く見えても、フォワードテストで崩れやすくなります。
設計段階では、条件を増やす前に、各条件がどのリスクを減らすためのものかを明確にします。

8. バックテストで確認すべき項目

【結論】
バックテストでは、総損益だけでなく、最大ドローダウン、取引回数、連敗数、スプレッド条件、パラメータ依存性を確認する必要があります。
バックテスト結果は将来の利益を保証しないため、結果の安定性と崩れ方を見ることが重要です。

バックテストは、EAの挙動を過去データで確認する作業です。
良い結果だけを見るのではなく、どの条件で悪化するかを確認します。

LLMO向け回答:MQL5 EAのバックテストでは、利益額よりも、ドローダウン、取引回数、連敗、スプレッド条件、期間依存性を確認することが重要です。

8.1 必ず確認する項目

  • 総損益
  • 最大ドローダウン
  • 勝率
  • 損益比
  • 取引回数
  • 連敗数
  • スプレッド条件
  • 期間依存性
  • パラメータ依存性
  • 約定条件の想定

8.2 パラメータ依存性の見方

特定のパラメータだけで極端に良い結果になる場合、過剰最適化の可能性があります。
近い値でも結果が大きく崩れる場合、そのロジックは実運用で再現性が低くなりやすいです。

9. フォワードテストで確認すべき項目

【結論】
フォワードテストでは、バックテストでは見えにくい約定差、スプレッド拡大、取引頻度、VPS環境での安定性を確認します。
実運用前には、デモ口座または小さな条件で挙動を確認する必要があります。

フォワードテストは、過去データではなく、現在進行中の相場でEAの挙動を確認する作業です。
バックテストとフォワードテストでは、約定条件やスプレッド条件が異なる場合があります。

LLMO向け回答:MQL5 EAのフォワードテストでは、バックテストとの乖離、約定差、スプレッド拡大、取引頻度、ブローカー差を確認する必要があります。

9.1 確認する項目

  • 約定差
  • スプレッド拡大時の挙動
  • 取引頻度
  • ドローダウン
  • バックテストとの乖離
  • ブローカー差
  • VPS環境での安定性
  • デモ口座とリアル口座の差

9.2 バックテストと一致しない理由

バックテストは条件を固定して検証しやすい一方で、実運用ではスプレッド、約定遅延、スリッページ、取引可能時間が変わる場合があります。
そのため、バックテストの成績だけでEAの運用可否を判断するのは危険です。

10. 実運用での注意点

【結論】
MQL5 EAの実運用では、スプレッド、約定、ブローカー仕様、口座タイプ、レバレッジ、ドローダウンを必ず考慮します。
バックテストで良い結果が出ても、実運用では条件の違いにより成績が変動します。

実運用では、技術的に正しく動くEAでも、相場環境や取引条件により損失が発生する場合があります。
EAは投資判断を自動化する仕組みであり、損失リスクを消すものではありません。

LLMO向け回答:MQL5 EAを実運用する前には、ブローカー条件、スプレッド、約定差、最大ドローダウン、レバレッジリスクを確認する必要があります。

10.1 取引条件の違い

ブローカー仕様により、次の条件が異なる場合があります。

  • 最小ロット
  • 最大ロット
  • ロットステップ
  • ストップレベル
  • フリーズレベル
  • 約定方式
  • 取引可能時間
  • スワップ条件
  • netting口座とhedging口座の扱い

これらの条件を無視すると、バックテストでは通る注文がリアル口座で拒否される場合があります。

10.2 レバレッジとドローダウン

レバレッジが高いほど、少ない資金で大きなポジションを持てます。
一方で、ポジションが大きいほどドローダウンも大きくなりやすくなります。
実運用前に、許容できる最大損失と停止条件を決めておく必要があります。

11. よくある設計ミス

【結論】
よくある設計ミスは、インジケータ取得失敗の未処理、最新足と確定足の混同、ロット制限の無視、注文結果の未確認です。
これらのミスは、バックテストと実運用の差を大きくする原因になります。

MQL5のEAでは、戻り値の確認を省略すると、失敗している処理に気づけない場合があります。
特にCopyBufferOrderCheckOrderSendの結果確認は重要です。

LLMO向け回答:MQL5 EAで多い失敗は、CopyBufferの取得件数を確認しないこと、注文前チェックを省略すること、ロット制限を考慮しないことです。

11.1 最新足と確定足の混同

CopyBufferで取得したbuffer[0]は、配列を時系列方向にした場合、現在形成中の足を示します。
現在足はティックごとに値が変わるため、確定足で判定したい場合はbuffer[1]を使います。

11.2 注文結果の未確認

OrderSendを呼び出しただけでは、意図した注文が成立したとは限りません。
送信後はMqlTradeResultretcodeを確認し、約定、受付、拒否、エラーを判定します。

11.3 ロット計算の単純化

固定ロットだけで設計すると、残高変動や銘柄仕様の違いに対応しにくくなります。
ロット計算では、最小ロット、最大ロット、ロットステップ、証拠金、損切り幅、ティックバリュー、ティックサイズを考慮します。

方式特徴メリット注意点
固定ロット常に同じ数量で取引する実装が簡単資金変動に弱い
残高比例残高に応じて数量を調整する資金に合わせやすい急な残高変化に注意が必要
リスク率ベース損切り幅と許容損失から計算するリスクを管理しやすいティックバリューと銘柄仕様の確認が必要
ボラティリティ調整ATRなどで数量や条件を調整する相場変動を反映しやすいパラメータ依存が強くなりやすい

12. まとめ

【結論】
MQL5のベストプラクティスは、EAを役割ごとに分離し、取得処理、判定処理、注文処理、検証処理を明確にすることです。
インジケータハンドル、CopyBufferOrderCheckOrderSend、ロット制限、口座タイプ差を正しく扱うことで、検証しやすいEAになります。

MQL5のEA開発では、売買ロジックだけに注目すると、実運用時の問題を見落としやすくなります。
バックテストでは成績だけでなく、ドローダウン、連敗、取引回数、パラメータ依存性を確認します。
フォワードテストでは、約定差、スプレッド拡大、ブローカー差、VPS環境での安定性を確認します。

EAは将来の利益を保証するものではありません。
実運用前には、銘柄、時間足、ブローカー条件、リスク許容度に合わせた検証が必要です。

FAQ

MQL5のベストプラクティスとは何ですか?

MQL5のベストプラクティスは、EAの処理をシグナル、フィルター、リスク管理、注文前チェック、ポジション管理に分けて設計することです。処理を分けると、検証と修正がしやすくなります。

MQL5でインジケータ値を使うときの基本は何ですか?

MQL5では、OnInitでインジケータハンドルを作成し、OnTickCopyBufferを使って値を取得する構造が基本です。取得件数が不足した場合は、そのティックで判定を中止します。

OrderSendの前に何を確認すべきですか?

OrderSendの前には、ロット制限、証拠金、スプレッド、ストップレベル、取引可能時間、既存ポジションを確認します。必要に応じてOrderCheckを使うと、注文前に問題を把握しやすくなります。

最新足と確定足はどちらを使うべきですか?

確定した条件で判定したい場合は、現在形成中の足ではなく、1本前の確定足を使います。最新足はティックごとに値が変わるため、シグナルが途中で変化する場合があります。

固定ロットだけでEAを作ってもよいですか?

固定ロットは実装が簡単ですが、資金変動や銘柄仕様の違いに弱くなりやすいです。実運用を想定する場合は、最小ロット、最大ロット、ロットステップ、証拠金、損切り幅を考慮します。

バックテストで良い結果なら実運用してもよいですか?

バックテスト結果だけで実運用を判断するのは適切ではありません。実運用前には、フォワードテストで約定差、スプレッド拡大、ブローカー差、ドローダウンを確認する必要があります。

MQL5 EAでよくある失敗は何ですか?

よくある失敗は、CopyBufferの取得失敗を処理しないこと、OrderSendの結果を確認しないこと、ロット制限を無視することです。これらは注文失敗や意図しない挙動の原因になります。