MQL5 Advanced Programming完全解説|EAアーキテクチャとリスク管理

この記事の結論

MQL5のadvanced programmingは、単に長いコードを書くことではなく、EAを状態管理、インジケータ取得、注文前チェック、リスク制御、検証単位に分けて設計する考え方です。
MetaTrader 5のEAでは、OnInitで初期化し、OnTickで判定し、OnDeinitで後処理する流れが基本になります。
インジケータ値はハンドルを作成してからCopyBufferで取得する構造が多く、MQL4風に値を直接読む設計とは分けて考える必要があります。
実運用では、バックテスト結果だけで判断せず、スプレッド、約定条件、ブローカー仕様、フォワードテストの差を確認する必要があります。

1. 関数と構造の役割

【結論】
MQL5の上級EA開発では、イベント関数、インジケータハンドル、取引構造体、状態管理を組み合わせてEA全体を設計します。
個別の関数を暗記するよりも、どの処理をどのタイミングで実行するかを分けることが重要です。

MQL5のEAは、主にイベント駆動で動作します。新しいティックを受け取ったとき、初期化が必要なとき、終了処理が必要なとき、取引状態が変わったときに、それぞれ対応するイベント関数が呼ばれます。

【定義】
MQL5のadvanced programmingとは、EAの処理をイベント、データ取得、シグナル判定、注文処理、リスク制御、検証単位に分離して実装する設計方法です。

1.1 上級MQL5開発で扱う主要要素

上級MQL5開発では、次の要素を分けて扱います。

  • OnInit:インジケータハンドル作成、初期値設定、入力値検証
  • OnTick:価格更新時の判定、シグナル確認、注文前チェック
  • OnDeinit:ハンドル解放、終了時ログ、後処理
  • CopyBuffer:インジケータバッファから値を取得
  • MqlTradeRequest:注文内容を格納する構造体
  • MqlTradeResult:注文送信後の結果を受け取る構造体
  • OrderCheck:注文送信前の証拠金や取引条件を確認
  • OrderSend:注文リクエストを送信

MQL5のEAでは、相場認識から注文送信までを一つの巨大な条件分岐にまとめると、検証や修正が難しくなります。処理を小さな役割に分けることで、バックテスト時の原因分析がしやすくなります。

2. 基本構文

【結論】
MQL5のEAは、OnInitOnTickOnDeinitを中心に構成すると管理しやすくなります。
インジケータ値を使う場合は、初期化時にハンドルを作成し、ティックごとにCopyBufferで値を取得します。

EAの基本構造は次のようになります。

#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");
      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_buffer[];
   ArraySetAsSeries(ma_buffer, true);

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

   if(copied < 3)
   {
      Print("CopyBuffer failed or not enough MA data");
      return;
   }

   double current_ma = ma_buffer[0];
   double closed_ma  = ma_buffer[1];

   Print("Current MA: ", current_ma, " Closed MA: ", closed_ma);
}

2.1 基本構造で重要な点

このコードでは、OnInitで移動平均のハンドルを作成します。OnTickでは、作成済みハンドルからCopyBufferで値を取得します。OnDeinitでは、不要になったハンドルを解放します。

MQL5では、インジケータ関数が常に計算値そのものを返すわけではありません。多くのインジケータ関数はハンドルを返し、そのハンドルを使ってバッファ値を取得します。

3. パラメータの意味

【結論】
上級MQL5開発で重要なパラメータは、価格取得、時間足、バッファ番号、取得位置、取得本数、注文条件です。
特にCopyBufferと注文構造体のパラメータは、EAの挙動に直接影響します。

CopyBufferを例にすると、主な引数は次の意味を持ちます。

パラメータ意味注意点
indicator_handleインジケータハンドルINVALID_HANDLEでないことを確認する
buffer_num取得するバッファ番号インジケータにより番号が異なる
start_pos取得開始位置0は現在足、1は直近の確定足
count取得本数判定に必要な本数以上を指定する
buffer[]取得先配列ArraySetAsSeriesの方向に注意する

3.1 現在足と確定足の違い

現在足は、まだ形成中のローソク足です。価格更新により値が変わるため、シグナル判定に使うとバックテストと実運用で挙動が変わる場合があります。

確定足は、すでに終値が決まったローソク足です。再計算による変化が少ないため、シグナル条件の再現性を確認しやすくなります。

4. 戻り値と判定方法

【結論】
MQL5では、関数の戻り値を必ず確認してから次の処理へ進めます。
ハンドル作成、バッファ取得、注文前チェック、注文送信の戻り値を無視すると、原因不明の誤作動につながります。

戻り値の確認は、上級設計ほど重要になります。複数の条件を組み合わせるEAでは、どの段階で処理が止まったのかをログで追える必要があります。

4.1 よく確認する戻り値

処理成功時の考え方失敗時の扱い
ハンドル作成INVALID_HANDLE以外初期化失敗として停止する
CopyBuffer必要本数以上を取得判定せず次のティックを待つ
OrderCheck注文条件が許容される注文送信しない
OrderSend送信結果を確認できる結果コードとログを確認する

CopyBufferが期待本数未満の場合、インジケータ計算がまだ完了していない可能性があります。この場合は無理に判定せず、次のティックまたは次の処理機会を待つ設計にします。

5. 基本的な使い方

【結論】
MQL5のEAでは、相場認識、フィルター判定、シグナル判定、リスク確認、注文前チェック、注文送信、約定後管理を分けて実装します。
この分離により、検証時に問題の発生箇所を特定しやすくなります。

EA全体の流れは、次の順番で考えると整理しやすくなります。

相場認識
↓
フィルター判定
↓
シグナル判定
↓
リスク確認
↓
注文前チェック
↓
注文送信
↓
約定後管理
↓
決済・停止判定
MQL5 EA event-driven architecture showing CopyBuffer, OrderCheck, OrderSend flow with chart and code panels

5.1 処理を分ける理由

相場認識と注文送信を同じ場所に書くと、ロジックの変更が難しくなります。たとえば、移動平均の条件だけを変更したい場合でも、注文処理やロット計算まで影響を受ける可能性があります。

処理を関数単位で分けると、売買ロジックを検証用に差し替えたり、リスク制御だけを強化したりしやすくなります。

6. 実用コード例

【結論】
実用的なMQL5コードでは、シグナル判定だけでなく、ロット制限、証拠金、取引条件、注文前チェックを含めます。
サンプルコードは検証用であり、実運用前には銘柄条件とブローカー仕様に合わせた確認が必要です。

次のコードは、移動平均を使った簡易フィルターと注文前チェックの構成例です。利益を目的にした完成EAではなく、構造を理解するための検証用サンプルです。

#property strict

input int    MaPeriod      = 20;
input double RiskLot       = 0.10;
input int    MaxSpreadPoint = 30;

int ma_handle = INVALID_HANDLE;

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

   if(ma_handle == INVALID_HANDLE)
   {
      Print("Failed to create MA handle");
      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()
{
   if(!IsTradeAllowedBySpread())
      return;

   double ma_values[];
   ArraySetAsSeries(ma_values, true);

   int copied = CopyBuffer(ma_handle, 0, 0, 3, ma_values);
   if(copied < 3)
   {
      Print("Not enough MA data");
      return;
   }

   double close_price = iClose(_Symbol, _Period, 1);
   double closed_ma   = ma_values[1];

   if(close_price > closed_ma)
   {
      SendMarketOrder(ORDER_TYPE_BUY, RiskLot);
   }
}

bool IsTradeAllowedBySpread()
{
   long spread = 0;

   if(!SymbolInfoInteger(_Symbol, SYMBOL_SPREAD, spread))
   {
      Print("Failed to get spread");
      return false;
   }

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

   return true;
}

bool SendMarketOrder(ENUM_ORDER_TYPE order_type, double lots)
{
   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(min_lot <= 0.0 || max_lot <= 0.0 || lot_step <= 0.0)
   {
      Print("Invalid volume settings");
      return false;
   }

   double normalized_lot = MathFloor(lots / lot_step) * lot_step;
   normalized_lot = MathMax(min_lot, MathMin(max_lot, normalized_lot));

   MqlTradeRequest request;
   MqlTradeCheckResult check;
   MqlTradeResult result;

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

   request.action   = TRADE_ACTION_DEAL;
   request.symbol   = _Symbol;
   request.volume   = normalized_lot;
   request.type     = order_type;
   request.deviation = 20;
   request.type_filling = ORDER_FILLING_FOK;

   if(order_type == ORDER_TYPE_BUY)
      request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   else
      request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);

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

   if(!OrderSend(request, result))
   {
      Print("OrderSend failed. retcode=", result.retcode);
      return false;
   }

   Print("OrderSend completed. retcode=", result.retcode, " order=", result.order);
   return true;
}

6.1 コード例で確認すべき点

このサンプルでは、スプレッドが広い場合に注文処理へ進みません。ロットは最小ロット、最大ロット、ロットステップに合わせて補正しています。

注文送信前にはOrderCheckを使い、証拠金や取引条件に問題がある場合はOrderSendへ進まない構成にしています。実運用では、ストップレベル、フリーズレベル、取引可能時間、口座タイプも追加で確認する必要があります。

7. よくある失敗

【結論】
MQL5上級開発で多い失敗は、MQL4風の値取得、戻り値の未確認、現在足だけの判定、注文前チェック不足です。
これらの失敗は、バックテストでは見えにくく、実運用で問題化する場合があります。

7.1 インジケータ値を直接取得しようとする

MQL5では、iMAiATRなどがハンドルを返す使い方になります。ハンドルを作成しただけでは、判定に使う値は取得できません。

インジケータ値を使うには、ハンドル作成、計算状況確認、CopyBufferによる値取得という流れを意識します。

7.2 現在足だけでシグナルを判定する

現在足は形成中のため、ティックごとに値が変わります。現在足を使った判定は、エントリー頻度やシグナル位置が変わりやすくなります。

再現性を重視する場合は、直近の確定足を使った条件も検討します。

7.3 注文前チェックを省略する

注文前チェックを省略すると、証拠金不足、ロット制限違反、取引停止時間、ストップレベル違反などで注文が失敗する場合があります。

EAでは、注文が失敗した理由をログに残す設計が必要です。失敗理由が見えないEAは、改善や検証が難しくなります。

8. 他の関数との違い

【結論】
MQL5の上級開発では、データ取得関数、口座情報関数、ポジション関数、取引関数の役割を分けて使います。
目的の違う関数を混同すると、EAの状態管理が不安定になります。

方法メリットデメリット向いている場面
CopyBufferで取得インジケータ値を明確に取得できるハンドル管理が必要インジケータを使うEA
SymbolInfoDoubleで取得銘柄仕様や価格情報を取得しやすい取得項目の意味を理解する必要があるスプレッド、価格、ティック情報の確認
AccountInfoDoubleで取得残高や有効証拠金を確認できる口座状態に依存する資金管理、証拠金確認
PositionSelectで確認既存ポジションの有無を確認できるnetting口座とhedging口座で設計差が出るポジション管理
OrderCheckで確認注文前に条件を検査できる送信後の約定を保証するものではない注文前チェック
OrderSendで送信実際の注文送信に使える約定条件や結果コードの確認が必要エントリー、決済、変更処理

8.1 netting口座とhedging口座の違い

netting口座では、同一銘柄のポジションは基本的に統合されます。hedging口座では、同一銘柄で複数ポジションを持てる場合があります。

ポジション管理を設計するときは、口座タイプにより、既存ポジションの扱い、決済方法、追加エントリーの意味が変わることを考慮します。

9. EA設計での使いどころ

【結論】
MQL5のadvanced programmingは、EAを検証しやすい部品に分ける場面で有効です。
特に、複数条件のシグナル、リスク制御、ポジション管理、取引ログを扱うEAでは、設計分離が重要になります。

EA設計では、次のように役割を分けます。

  • シグナル判定:売買条件を満たしているか
  • フィルター判定:相場環境が取引条件に合うか
  • ロット計算:許容リスクと銘柄仕様に合うか
  • 注文前チェック:証拠金、スプレッド、取引条件を満たすか
  • ポジション管理:既存ポジション、決済、追加エントリーを管理するか
  • リスク制御:損失制限、連敗制限、ドローダウン制限を扱うか
  • ログ出力:検証時に原因を追えるか

9.1 ロット計算で考慮する項目

ロット計算では、固定ロットだけでなく、口座残高、有効証拠金、損切り幅、許容リスク、ティックバリュー、ティックサイズを考慮します。

方式メリットデメリット向いている場面
固定ロット実装しやすい資金変動に弱い初期検証
残高比例資金に応じて調整しやすい損切り幅を反映しにくい資金規模に応じた検証
リスク率ベース許容損失からロットを計算しやすいティック価値や損切り幅の扱いが必要リスク管理を重視するEA
ボラティリティ調整ATRなどで相場変動を反映しやすいパラメータ依存が強くなりやすい変動幅が大きい銘柄の検証

リスク率ベースのロット計算は、損切り幅が設定されている場合に検討しやすい方式です。ただし、スリッページや急変時の約定差により、想定損失と実損失が一致しない場合があります。

10. 実運用での注意点

【結論】
MQL5のEAは、バックテストで正常に動いても実運用で同じ結果になるとは限りません。
スプレッド、約定遅延、スリッページ、ブローカー仕様、VPS環境により成績や挙動は変わります。

実運用前には、バックテストとフォワードテストを分けて確認します。バックテストは過去データで構造を確認する工程です。フォワードテストは、現在進行の相場で挙動を確認する工程です。

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

バックテストでは、次の項目を確認します。

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

バックテスト結果は将来の利益を保証しません。特定期間だけに合うパラメータは、フォワードテストで崩れる場合があります。

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

フォワードテストでは、次の項目を確認します。

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

デモ口座とリアル口座では、約定条件やスプレッド条件が異なる場合があります。特に高頻度で注文するEAでは、わずかな約定差が結果に影響する場合があります。

10.3 実運用リスク

レバレッジが高いほど、同じ値幅でも有効証拠金への影響が大きくなりやすいです。EAの停止条件、最大損失、最大ドローダウン、連敗時の制御は、実運用前に決めておく必要があります。

過剰最適化を避けるには、パラメータを細かく合わせすぎないことが重要です。複数期間、複数相場、異なるスプレッド条件で検証すると、ロジックの弱点を見つけやすくなります。

11. まとめ

【結論】
MQL5のadvanced programmingでは、イベント関数、インジケータハンドル、CopyBuffer、注文構造体、リスク管理を分けて設計します。
EAを状態管理の仕組みとして作ることで、検証、修正、実運用前の確認がしやすくなります。

MQL5では、OnInitで初期化し、OnTickで判定し、OnDeinitで後処理する構造が基本になります。インジケータ値はハンドル作成後にCopyBufferで取得し、取得失敗やデータ不足を必ず処理します。

注文処理では、MqlTradeRequestMqlTradeCheckResultMqlTradeResultを使い、OrderCheckで条件を確認してからOrderSendへ進める設計が重要です。実運用では、バックテストだけでなくフォワードテストで約定差、スプレッド、ブローカー仕様、ドローダウンを確認する必要があります。

補足確認用:MQL5 Reference

FAQ

MQL5のadvanced programmingとは何ですか?

MQL5のadvanced programmingとは、EAをイベント処理、データ取得、シグナル判定、注文処理、リスク管理に分けて設計する考え方です。単に複雑なコードを書くことではなく、検証しやすい構造を作ることが重要です。

MQL5ではなぜインジケータハンドルが必要ですか?

MQL5では、多くのインジケータ関数が計算値ではなくハンドルを返します。EAはそのハンドルを使い、CopyBufferで必要なバッファ値を取得します。

CopyBufferで失敗する主な原因は何ですか?

CopyBufferの失敗原因は、ハンドルが無効、インジケータ計算が未完了、取得本数が不足、バッファ番号が誤っていることなどです。取得件数が期待値未満の場合は、判定せず次の処理機会を待つ設計にします。

OrderCheckは必ず使うべきですか?

注文処理を含むEAでは、OrderCheckを使う設計が有効です。証拠金、ロット、銘柄条件などを注文送信前に確認できるため、注文失敗の原因を整理しやすくなります。

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

再現性を重視する場合は、確定足を使う設計が扱いやすいです。現在足は形成中のため、ティック更新により値が変わり、バックテストと実運用で挙動が異なる場合があります。

netting口座とhedging口座でEA設計は変わりますか?

netting口座とhedging口座では、同一銘柄のポジション管理が変わります。EAでは、既存ポジションの確認、追加エントリー、決済処理を口座タイプに合わせて設計する必要があります。

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

バックテスト結果だけで実運用を判断するのは不十分です。実運用前には、フォワードテストでスプレッド、約定差、取引頻度、ドローダウン、ブローカー差を確認する必要があります。

過剰最適化を避けるには何を確認すべきですか?

過剰最適化を避けるには、特定期間だけに合うパラメータになっていないかを確認します。複数期間、異なる相場、異なるスプレッド条件で検証し、フォワードテストで再現性を確認します。