こんにちは、潤奈です( ゚Д゚)!
前回の記事では、口座残高と損切り幅に合わせて自動的に取引ロット数を算出し、すべてのトレードで一定のリスクを保つための資金管理ロジックを解説しました。
注文、決済、インジケーターの取得、そして資金管理までをMQL5で書けるようになったら、次はいよいよ「複数通貨ペアを同時に監視してトレードするEA(マルチカレンシーEA)」の構築に挑戦してみましょう!
MQL4からMQL5へ移行する最大のメリットと言っても過言ではないのが、このマルチカレンシー機能の強化です。MQL4では非常に難しかった「他通貨ペアのデータ監視とバックテスト」が、MQL5では驚くほど簡単に、そして高速に実行できるようになっています。
今回も、実際にコピペして動かせるサンプルコード付きで、マルチカレンシーEAの仕組みと堅牢な書き方を分かりやすく解説します!
1. MQL5のマルチカレンシーが優れている理由(MQL4との違い)
まずは、なぜMQL5(MT5)が複数通貨ペアの取引に向いているのか、背景を理解しておきましょう。
MQL4でも、プログラム内で iMA("EURUSD", ...) などのように他通貨のペア名を指定すれば、データ自体は取得できました。
しかし、「バックテスト(過去検証)」の仕様に致命的な問題がありました。
MQL4では、バックテスト時に「EAをセットした1つの通貨ペア」のヒストリカルデータしか正しく読み込めず、他通貨ペアのデータは手動でインポートするなどの力技を使わない限り、まともにバックテストができませんでした。
一方、MQL5(MT5)では、ストラテジーテスターが最初からマルチカレンシーテストに対応しています。
EAのコード内で「EURUSD」や「GBPJPY」といった他通貨のデータを要求すると、MT5が自動的に裏側で該当する通貨ペアの過去データを同期・取得し、正確なバックテストを実行してくれます。1回のテストで複数通貨ペアの売買を同時に検証できるため、ポートフォリオ運用のEAなどを開発するのに最適な環境となっています。
2. 他通貨ペアのデータ取得における重要な注意点
他通貨ペアのデータを監視する際、単に通貨ペアの名前(文字列)を指定するだけですが、実戦でバグを起こさないために以下の2つの安全対策が必要です。
① インジケーターハンドルの作成方法
他通貨のインジケーター値を取得する場合も、これまでに学んだ「3ステップ(ハンドル取得 → 配列準備 → コピー)」と同じです。
違いは、iMA 関数の第1引数に _Symbol(現在のチャートの通貨ペア)ではなく、"EURUSD" などの具体的な通貨ペア名を指定する点だけです。
|
1 2 3 4 5 6 7 |
int fastMAHandle; int OnInit() { // 現在のチャートではなく、EURUSDの移動平均線ハンドルを作成する fastMAHandle = iMA("EURUSD", _Period, 10, 0, MODE_SMA, PRICE_CLOSE); } |
② 【重要】データの同期待ち(未同期エラーの回避)
他通貨ペアのデータ(ローソク足やインジケーター値)を初めて要求した瞬間は、MT5側にデータがまだ読み込まれておらず、一時的に CopyBuffer() がエラー(戻り値が <= 0)を返すことがあります。
起動した直後にこの「データ未同期」によるエラーが発生しても、EAが止まってしまわないよう、「データがしっかりと同期されるまでは処理をスキップして待つ」というロジックを必ず組み込むのが、実践向けの安全な設計です。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
double FastMAValues[]; void OnTick() { // CopyBufferを実行し、データが正常にコピーできるか確認する // `<= 0` であれば未同期またはエラーのため、今回の処理をスキップして次のチックで再試行する if(CopyBuffer(fastMAHandle, 0, 0, 3, FastMAValues) <= 0) { Print("EURUSDのデータがまだ同期されていません。再試行します..."); return; } } |
③ 監視する「時間足」の指定について
他通貨のデータやインジケーターハンドルを作成する際、時間足に _Period を指定すると、EAをセットしている「現在のチャートの時間足(例:1時間足チャートなら1時間足)」が自動で割り当てられます。
これにより、どの通貨ペアも同じ時間足として処理されるためシンプルですが、もし「どのチャートにセットしても、他通貨の『1時間足(H1)』のデータを固定で監視したい」という場合は、_Period の代わりに PERIOD_H1 のように定数を明示的に指定しておきましょう。
[!TIP]
コラム:マルチカレンシーEAにおける「OnTick」の稼働タイミング
今回作成するマルチカレンシーEAは、基本となるOnTick()関数の中でループを回して3通貨ペアを監視します。
ここで注意が必要なのは、「EAをセットしたチャートの通貨ペアにチック(価格変動)が発生したタイミングでしか OnTick は動かない」というMQL5の仕様です。例えば、このEAを「USDJPY」のチャートにセットした場合、EURJPYやGBPJPYが激しく動いていても、USDJPYの価格が1ピップも動かなければ、その瞬間は
OnTick内の処理が走りません。
バックテスト時はテスターがすべての通貨ペアのチックを擬似的に同期して動かすため問題にはなりませんが、実際のリアル稼働時に完全にリアルタイムで他通貨の変動を監視したい場合は、タイマー機能(OnTimer())を併用して一定秒数ごとに監視処理を走らせる、といった工夫をすることもあります。まずは基本となるOnTickでの書き方をしっかりとマスターしていきましょう!
3. コピペで動く!3通貨ペア(USDJPY, EURJPY, GBPJPY)監視サンプルEA
それでは、実際の挙動を確認するためのサンプルコードを紹介します。
このEAは、どのチャートにセットしても、指定した3つの通貨ペア(USDJPY、EURJPY、GBPJPY)の短期MA(期間10)と長期MA(期間30)を同時に監視します。
いずれかの通貨ペアでゴールデンクロス(またはデッドクロス)が発生したら、その通貨ペアに対して個別にエントリーと決済(ドテン)を行う、本格的なマルチカレンシーEAのテンプレートコードです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
//+------------------------------------------------------------------+ //| MultiCurrencyEA.mq5 | //| 潤奈FX | //+------------------------------------------------------------------+ #property copyright "潤奈FX" #property link "https://zyunafx.com" #property version "1.00" // --- 注文ライブラリの読み込み --- #include <Trade\Trade.mqh> CTrade trade; // --- 入力パラメータ --- input group "--- 取引設定 ---" input int MagicNumber = 555555; // マジックナンバー input double TradeLots = 0.05; // 取引ロット数 input int Slippage = 100; // 許容スリッページ (ポイント) // --- 監視する通貨ペアの定義 --- #define SYMBOL_COUNT 3 string WatchSymbols[SYMBOL_COUNT] = {"USDJPY", "EURJPY", "GBPJPY"}; // --- グローバル変数 --- int FastMAHandles[SYMBOL_COUNT]; // 短期MAハンドル用配列 int SlowMAHandles[SYMBOL_COUNT]; // 長期MAハンドル用配列 double FastMAValues[]; // 短期MAの一時データ用動的配列 double SlowMAValues[]; // 長期MAの一時データ用動的配列 datetime LastBarTimes[SYMBOL_COUNT]; // 通貨ペア毎の新バー判定用配列 //+------------------------------------------------------------------+ //| 初期化関数 | //+------------------------------------------------------------------+ int OnInit() { // CTradeの初期設定 trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(Slippage); // 各通貨ペアのインジケーターハンドルを作成する for(int i = 0; i < SYMBOL_COUNT; i++) { string symbol = WatchSymbols[i]; // 短期MAハンドルの作成(期間10) FastMAHandles[i] = iMA(symbol, _Period, 10, 0, MODE_SMA, PRICE_CLOSE); if(FastMAHandles[i] == INVALID_HANDLE) { PrintFormat("%s の短期MAハンドルの作成に失敗しました。", symbol); return(INIT_FAILED); } // 長期MAハンドルの作成(期間30) SlowMAHandles[i] = iMA(symbol, _Period, 30, 0, MODE_SMA, PRICE_CLOSE); if(SlowMAHandles[i] == INVALID_HANDLE) { PrintFormat("%s の長期MAハンドルの作成に失敗しました。", symbol); return(INIT_FAILED); } // 新バー判定用の時間を初期化 LastBarTimes[i] = 0; } // データコピー用の配列を時系列順に並び替え(OnInit内で1回実行すればOK) ArraySetAsSeries(FastMAValues, true); ArraySetAsSeries(SlowMAValues, true); Print("マルチカレンシーEAが正常に初期化されました。"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 後片付け関数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // メモリ解放のため、すべてのハンドルを解放する for(int i = 0; i < SYMBOL_COUNT; i++) { IndicatorRelease(FastMAHandles[i]); IndicatorRelease(SlowMAHandles[i]); } Print("すべてのハンドルが解放されました。"); } //+------------------------------------------------------------------+ //| チック毎の処理関数 | //+------------------------------------------------------------------+ void OnTick() { // 監視対象の通貨ペアをループ処理 for(int s = 0; s < SYMBOL_COUNT; s++) { string symbol = WatchSymbols[s]; // ① 通貨ペア毎に新しい足が確定したかチェック(チャタリング防止) datetime currentBarTime = iTime(symbol, _Period, 0); if(currentBarTime == 0) continue; // データ未同期のときはスキップ if(currentBarTime == LastBarTimes[s]) continue; // 同じバーならスキップ // ② 短期・長期MAの最新3本分のデータを配列にコピー(未同期時はスキップ) if(CopyBuffer(FastMAHandles[s], 0, 0, 3, FastMAValues) <= 0) continue; if(CopyBuffer(SlowMAHandles[s], 0, 0, 3, SlowMAValues) <= 0) continue; // 処理を実行したことを記録(同じバーでの処理は1回に制限) LastBarTimes[s] = currentBarTime; // 確定足の値を取得(1 = 1本前の足、2 = 2本前の足) double fastMA_1 = FastMAValues[1]; double fastMA_2 = FastMAValues[2]; double slowMA_1 = SlowMAValues[1]; double slowMA_2 = SlowMAValues[2]; // 現在の該当通貨ペアのポジション確認 bool hasBuy = false; bool hasSell = false; for(int p = PositionsTotal() - 1; p >= 0; p--) { ulong ticket = PositionGetTicket(p); // PositionGetTicket(p) を呼び出した時点で、そのポジションが自動的に内部選択されています if(ticket > 0) { // 通貨ペアとマジックナンバーがこのEAのものか確認 if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { long type = PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) hasBuy = true; if(type == POSITION_TYPE_SELL) hasSell = true; } } } // 注文用の現在価格(Ask/Bid)を取得する double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(symbol, SYMBOL_BID); // --- A. ゴールデンクロスの判定(買いエントリー) --- if(fastMA_2 <= slowMA_2 && fastMA_1 > slowMA_1) { PrintFormat("%s でゴールデンクロス発生!", symbol); // 既存の売りポジションがあれば決済 if(hasSell) ClosePosition(symbol, POSITION_TYPE_SELL); // 新規買い(シンボル名を明示的に指定して発注) if(!hasBuy) { trade.Buy(TradeLots, symbol, ask, 0, 0, "MultiEA"); } } // --- B. デッドクロスの判定(売りエントリー) --- if(fastMA_2 >= slowMA_2 && fastMA_1 < slowMA_1) { PrintFormat("%s でデッドクロス発生!", symbol); // 既存の買いポジションがあれば決済 if(hasBuy) ClosePosition(symbol, POSITION_TYPE_BUY); // 新規売り(シンボル名を明示的に指定して発注) if(!hasSell) { trade.Sell(TradeLots, symbol, bid, 0, 0, "MultiEA"); } } } } //+------------------------------------------------------------------+ //| 特定通貨ペアのポジションを一括決済する関数 | //+------------------------------------------------------------------+ void ClosePosition(string symbol, long posType) { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); // PositionGetTicket(i) で選択されるため、PositionSelectByTicketは不要です if(ticket > 0) { if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetInteger(POSITION_TYPE) == posType) { PrintFormat("%s のポジションをクローズします。チケット: %I64u", symbol, ticket); trade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ |
4. マルチカレンシーEAのバックテストを実行する
このEAをMT5の「ストラテジーテスター」で動かす手順は非常にシンプルです。
- ストラテジーテスターを開き、設定の「プログラム」で
MultiCurrencyEAを選択します。 - 「通貨ペア」は、監視対象に含まれているいずれかのペア(例:
USDJPY)を選択します。 - テスト期間や時間足(例:
H1)を指定して「スタート」ボタンをクリックします。 - テスト完了後、取引履歴やグラフを確認してみてください。USDJPYだけでなく、EURJPYやGBPJPYの取引も同時に自動で行われ、資金が合算されて推移している様子が確認できるはずです!
MT5では他通貨ペアのデータダウンロードなどもすべて全自動で処理されるため、複雑な下準備なしで即座に検証結果が得られます。
5. まとめ&次のステップ
今回は、MQL5の強みを最大限に活かした「複数通貨ペアを監視・取引するマルチカレンシーEA」の仕組みと書き方を解説しました。
- MQL5(MT5)は標準でマルチカレンシーのバックテストに対応している。
- 他通貨のデータやインジケーター値を要求する際は、
iMAなどの関数に通貨ペア名を直接指定する。 - 起動直後などの未同期状態によるエラーを防ぐため、データの同期チェックとロード待ちロジックは必須。
- 注文・決済時にも、対象となる通貨ペア名を明示的に指定して
trade.Buy()などを呼ぶ。
これで、単一の通貨ペアに縛られない、幅広い戦略を持ったEAの基礎が完成しました!
次回は、EAに「ボタン」や「入力ボックス」などの操作パネルを表示させ、チャート上からワンクリックで決済や設定変更ができるようにする「チャート上のUIボタン・操作パネル表示の基本」について解説します。EAの使いやすさを向上させるためのビジュアル要素の実装に挑戦しましょう!
今回のマルチカレンシーコードで「特定の通貨だけ取引されない」「エラーログが頻出する」などの疑問があれば、YouTubeのコメント欄やXでお気軽にご連絡くださいね!


コメント