初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(IX) - ニュース取引のための単一チャートでのマルチペア管理
内容
はじめに
経済ニュースの発表など、ボラティリティが高い局面では、トレーダーはブレイクアウトを狙うことが多いです。市場の即時反応は予測が難しく、主要ニュース発表時には価格が急激にスパイクした後、修正やトレンドの継続が生じることがあります。このような状況では、複数の通貨ペアを同時に取引したい場合がありますが、MetaTrader 5のデフォルト設定では実現が難しいです。デフォルトでは、1つのチャートにつき1つのエキスパートアドバイザー(EA)しか稼働できないため、複数の通貨ペアを取引するには、複数のチャートを開き、それぞれにEAをアタッチする必要があります。
本記事では、この制約を克服する方法として、News Headline EAに統合したマルチペア取引機能を紹介します。この機能により、1つのチャートから複数の通貨ペアを直感的な取引ボタンで管理することが可能になります。MQL5の標準ライブラリとカスタム取引クラスの両方を活用することで、1つのチャート上で複数通貨ペアをシームレスに管理できる高度なEAを構築できます。
図1:MetaTrader 5では、1つのチャートに1つのEAしか稼働できない
上図は、1つのチャートで1つのEAしか実行できないという、MetaTrader 5の制約を示しています。1つのチャートで複数の通貨ペアを効率的に取引するためには、現在のチャートの通貨ペアだけでなく、他のペアも同時に管理できる高度なEAが必要です。
本記事の終わりまでに、以下を達成することを目指します。
- より高度なEAを開発する
- 既存のMQL5ヘッダークラスに新機能を追加する
- MQL5標準ライブラリを活用して新しいクラスを構築する
- 既存のEAに新機能を統合する
- 入力のモジュール化と構造的なグループ化を適用する
概念の理解
この段階では、まず前回までの作業を簡単に振り返ります。私たちは、シンプルなアニメーション付きNews Headline EAから始め、経済指標カレンダーやAlpha Vantageなどの外部ニュースAPIからデータを取得していました。その後、ローカルでホストしたAIモデル、ニュース取引自動化戦略、手動取引ボタンを統合することで、EAの信頼性を向上させてきました。
これらの改善によりシステムは強化されましたが、完全な解決策にはなりませんでした。アルゴリズム取引は常に進化しており、技術の進歩に伴って新たな課題が生じ、システムのアップグレードが求められます。本記事では、そのような課題の一つとして、同一EA内でのマルチペア取引に対応する方法を取り上げます。
なぜ必要なのか
ここで自然に出る疑問は、なぜこの機能が必要なのかという点です。
経済ニュース発表などの高ボラティリティイベントでは、トレーダーは迅速に反応する必要があり、場合によっては数秒以内に複数のポジションや通貨ペアを管理しなければなりません。この機能は、アルゴリズム取引と手動取引を一つのプラットフォームで統合することで、効率性と制御力を大幅に向上させます。ワンクリックで複数の通貨ペアに取引を仕掛け、複数ポジションを同時に管理できるため、スピードとパフォーマンス効率の両方を得ることができます。
機能統合のプロセス
この前提を踏まえ、次に新機能をどのように追加するかを簡単に説明します。EAの拡張には、ヘッダーファイルのインクルードとカスタム取引ボタンクラスを活用します。これにより、メインコードベースをクリーンかつモジュール化したまま機能を統合できます。
マルチペア取引では、手動取引ボタンを押した際に、現在のチャートペアに加えて実行する任意の通貨ペアを選択できる必要があります。このため、MQL5標準ライブラリのCCheckBoxとCLabelクラスを使用します。これらのコンポーネントにより、選択可能なペアを表示し、ユーザー入力を管理し、選択状態をボタンのイベントハンドラに直接リンクさせることが可能になります。
最後に、CTradingButtonsクラスを拡張し、これらの新機能をシームレスに組み込む予定です。
実装
実装は、大きく2つのステージに分けて進めます。まず第1ステージとして、TradingButtonsヘッダー内のCTradingButtonsクラスを修正し、設計で示したマルチペア取引機能を実装します。第2ステージでは、News Headline EAを新機能に対応させるための調整をおこないます。
コードの各部分がどのように機能を実現するかを丁寧に解説しながら進めます。明確にするため、各コードセクションとその説明は上から順に番号を付け、特に新しい機能に重点を置いて説明します。
基礎部分のコードについて復習したい場合は、過去の連載記事を参照すると、初期バージョンについて詳細に解説していますのでおすすめです。
マルチペア取引のためのCtradingButtonsクラスの変更
このヘッダーは前回の記事で初めて紹介したので、詳細はそちらを参照してください。本セクションでは、新機能を追加して拡張します。
高レベルの概要
このCTradingButtonsクラスは、以下の3つの役割をまとめており、EAにそのまま組み込めるコンパクトなマルチペア取引モジュールとして機能します。(1) UI:キャンバス、ボタン、そしてペア用に動的に作成されるチェックボックスを提供します。(2) 小規模取引ラッパーとしてCTradeインスタンスを用い、注文を送信します。(3) ペア解決およびマルチペアエンジンでは、EURUSDのような基軸名をブローカーの通貨ペアにマッピングし、選択されたすべての通貨ペアに対してアクションを適用します。高レベル設計では、配列間のインデックスの整合性が保持されており、リクエストリスト(EAが渡すもの)、解決済みブローカーペア(ターミナルが実際に取引するもの)、チェックボックス(ユーザーが操作するもの)のすべてで、同じインデックスiが同じ通貨ペアを表すようになっています。これにより、UIからEAの選択配列への結線がシンプルかつ予測可能になります。
// top-of-file: class skeleton + key members (from TradingButtons.mqh) class CTradingButtons { private: // UI & buttons CButton btnMultiToggle; CButton btnBuy, btnSell, btnCloseAll, btnDeleteOrders, btnCloseProfit, btnCloseLoss, btnBuyStop, btnSellStop; CCanvas buttonPanel; // trading CTrade trade; // multipair UI & resolution CCheckBox *pairChecks[]; // dynamic checkbox pointers, index-aligned with requested list string availablePairs[]; // resolved broker symbols (index-aligned) string resolvedBases[]; // original requested bases (for logging) bool multiEnabled; public: double LotSize; int StopLoss; int TakeProfit; int StopOrderDistancePips; double RiskRewardRatio; CTradingButtons() { /* default init inlined in full file */ } void Init(); void Deinit(); // ... other methods follow };
フィールドとコンストラクタ:クラスが保持する内容
このクラスは、ボタンの幅、高さ、間隔などのレイアウトパラメータ、チェックボックスのサイズと開始座標、動的に生成されるチェックボックス配列、解決済みペアの配列、そして取引設定(ロットサイズ、ストップ、リスクリワードなど)を保持します。コンストラクタでは、LotSize=0.01、StopLoss=50、TakeProfit=100、multiEnabled=trueなどの妥当なデフォルト値を設定しており、EAはすぐに動作可能な設定で開始でき、必要に応じて後から上書きできます。取引パラメータはpublicに、UI内部はprivateに保持することで、インターフェースをシンプルかつ安全に保っています。
// constructor + key field defaults (actual defaults in your file) CTradingButtons() : buttonWidth(100), buttonHeight(30), buttonSpacing(10), checkWidth(120), checkHeight(20), checkSpacing(6), checkStartX(10), LotSize(0.01), StopLoss(50), TakeProfit(100), StopOrderDistancePips(8), RiskRewardRatio(2.0), multiEnabled(true) { // constructor body intentionally minimal — Init() performs heavier setup }
初期化とクリーンアップ
Init()メソッドでは、CTradeラッパーの設定(マジックナンバーやスリッページ許容値)をおこない、UI(パネル、ボタン、マルチトグル)を構築します。Deinit()メソッドでは、動的に生成されたオブジェクト(チェックボックス、ボタン、キャンバス)を慎重に破棄し、配列を解放することで、孤立したチャートオブジェクトやメモリリークを防ぎます。クリーンアップ処理では、pairChecks[]配列をループで回し、動的ポインタに対してDestroy()とdeleteを順に実行した後、配列自体を解放します。これは、EAを開発中に繰り返し起動・終了する際に非常に重要です。
// Init & Deinit excerpt void Init() { trade.SetExpertMagicNumber(123456); trade.SetDeviationInPoints(10); CreateButtonPanel(); CreateButtons(); CreateMultiToggle(); UpdateMultiToggleVisual(); } void Deinit() { // destroy checkboxes for(int i = 0; i < ArraySize(pairChecks); i++) { if(CheckPointer(pairChecks[i]) == POINTER_DYNAMIC) pairChecks[i].Destroy(); delete pairChecks[i]; } } ArrayFree(pairChecks); // destroy buttons and panel btnMultiToggle.Destroy(); btnBuy.Destroy(); btnSell.Destroy(); btnCloseAll.Destroy(); btnDeleteOrders.Destroy(); btnCloseProfit.Destroy(); btnCloseLoss.Destroy(); btnBuyStop.Destroy(); btnSellStop.Destroy(); buttonPanel.Destroy(); ObjectDelete(0, "ButtonPanel"); }
ペア解決(マルチペア対応で重要)
「EURUSD」のようなフレンドリーネームで取引する場合、EAはこれをブローカーの正確な通貨ペア文字列にマッピングする必要があります(例:EURUSD、EURUSD.ecn、FX.EURUSD)。ResolveSymbol(base)メソッドはまず、完全一致を試みます(高速経路)。一致しない場合は、無効化されている通貨ペアは除外してターミナル内の全通貨ペアをループし、先頭一致や部分一致を検索(先頭一致を優先)します。このペア解決処理により、availablePairs[i]エントリが生成され、取引ルーチンやUIのチェックボックスで使用されます。つまり、EAが要求する通貨ペア名とブローカーが実際に取引可能な通貨ペアとの橋渡しとなる重要なステップです。
// ResolveSymbol implementation (exact + starts-with + contains search) string ResolveSymbol(const string base) { if(StringLen(base) == 0) return(""); // 1) Try exact symbol name first string baseName = base; if(SymbolInfoInteger(baseName, SYMBOL_SELECT) != 0 || SymbolSelect(baseName, false)) { if(SymbolInfoInteger(baseName, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_DISABLED) { PrintFormat("ResolveSymbol: exact match found %s", baseName); return(baseName); } } // 2) search all terminal symbols int total = SymbolsTotal(false); string base_u = base; StringToUpper(base_u); string firstStarts = ""; string firstContains = ""; for(int i = 0; i < total; i++) { string sym = SymbolName(i, false); string sym_u = sym; StringToUpper(sym_u); if(sym_u == base_u) continue; if(SymbolInfoInteger(sym, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) continue; if(StringFind(sym_u, base_u) == 0) { if(firstStarts == "") firstStarts = sym; } else if(StringFind(sym_u, base_u) >= 0) { if(firstContains == "") firstContains = sym; } } if(firstStarts != "") { PrintFormat("ResolveSymbol: resolved %s -> %s (starts-with)", base, firstStarts); return(firstStarts); } if(firstContains != "") { PrintFormat("ResolveSymbol: resolved %s -> %s (contains)", base, firstContains); return(firstContains); } PrintFormat("ResolveSymbol: no match for %s", base); return(""); }
マルチペアUIの作成:CreatePairCheckboxes(...)
CreatePairCheckboxes(inMajorPairs[], inPairSelected[], yPos)は、(a) 各リクエストされたベース通貨をブローカーのペアに解決し、(b) そのペアが気配値表示に存在し取引可能であることを確認し、(c) 解決された各ペアとのインデックス整合性を保持する処理をおこないます。解決できなかった通貨ペアや無効なエントリーはプレースホルダーとして保持され、availablePairs[i]が元のinMajorPairs[i]に正しく対応するようにします。作成された各チェックボックスの初期状態はinPairSelected[i]から取得されるため、UIとEAの選択配列は最初から同期された状態になります。
// CreatePairCheckboxes: resolve requested bases -> create checkboxes aligned by index void CreatePairCheckboxes(string &inMajorPairs[], bool &inPairSelected[], int yPos) { // cleanup previous for(int i = 0; i < ArraySize(pairChecks); i++) { if(CheckPointer(pairChecks[i]) == POINTER_DYNAMIC) { pairChecks[i].Destroy(); delete pairChecks[i]; } } ArrayFree(pairChecks); ArrayResize(availablePairs, ArraySize(inMajorPairs)); ArrayResize(resolvedBases, ArraySize(inMajorPairs)); for(int k = 0; k < ArraySize(availablePairs); k++) { availablePairs[k] = ""; resolvedBases[k] = ""; } int count = ArraySize(inMajorPairs); if(count == 0) return; // Resolve each requested base for(int i = 0; i < count; i++) { string requested = inMajorPairs[i]; string resolved = ResolveSymbol(requested); if(resolved == "") { PrintFormat("CreatePairCheckboxes: could not resolve %s -> skipping checkbox", requested); availablePairs[i] = ""; resolvedBases[i] = requested; continue; } if(!SymbolSelect(resolved, true)) PrintFormat("CreatePairCheckboxes: SymbolSelect failed for %s (from %s) Err=%d", resolved, requested, GetLastError()); if(SymbolInfoInteger(resolved, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) { PrintFormat("CreatePairCheckboxes: resolved symbol %s is disabled (from %s) - skipping", resolved, requested); availablePairs[i] = ""; resolvedBases[i] = requested; continue; } availablePairs[i] = resolved; resolvedBases[i] = requested; } // Create checkbox controls (preserve index alignment) ArrayResize(pairChecks, count); int xPos = checkStartX; int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int wrapX = chartW - (buttonWidth * 3) - 30; for(int i = 0; i < count; i++) { if(StringLen(availablePairs[i]) == 0) { pairChecks[i] = NULL; continue; } pairChecks[i] = new CCheckBox(); string objName = "Chk_" + availablePairs[i]; if(!pairChecks[i].Create(ChartID(), objName, 0, xPos, yPos, xPos + checkWidth, yPos + checkHeight)) { PrintFormat("CreatePairCheckboxes: failed to create checkbox %s Err=%d", objName, GetLastError()); delete pairChecks[i]; pairChecks[i] = NULL; availablePairs[i] = ""; continue; } pairChecks[i].Text(" " + availablePairs[i]); pairChecks[i].Color(clrBlack); bool checked = false; if(i < ArraySize(inPairSelected)) checked = inPairSelected[i]; pairChecks[i].Checked(checked); xPos += checkWidth + checkSpacing; if(xPos + checkWidth > wrapX) { xPos = checkStartX; yPos += checkHeight + checkSpacing; } } ChartRedraw(); PrintFormat("CreatePairCheckboxes: created checkboxes (resolved count=%d)", CountResolvedPairs()); }
カウント/ヘルパー:CountResolvedPairs()
小さなヘルパー関数はコードの可読性を保つうえで重要です。CountResolvedPairs()はavailablePairs[]の中で空でないエントリを数えるだけのシンプルな関数ですが、ログ出力やUIテキストの更新に利用されます。たった一行の処理ですが、初期化段階やトラブルシューティング時にとても役立ちます。
// Count resolved availablePairs entries int CountResolvedPairs() { int c = 0; for(int i = 0; i < ArraySize(availablePairs); i++) if(StringLen(availablePairs[i]) > 0) c++; return c; }
イベント処理:HandleChartEvent(...)
すべてのチャートオブジェクトからのクリックイベントはHandleChartEventに集約されます。この関数は3種類のクリックを識別します。(A) チェックボックスのクリック(オブジェクト名が「Chk_」で始まるもの。どの解決済み通貨ペアがクリックされたかを特定し、inPairSelected[i]配列を同期します)、(B) マルチトグルボタン(multiEnabledを切り替え、見た目を更新します)、(C) アクションボタン(Buy、Sell、Close all、Delete pending、Place stops)。これらのボタンはクリック時に各操作へ処理を委譲し、EA側の要求ペア配列と選択フラグを渡します。HandleChartEventはUIからエンジンへのルーターとして機能し、UIとEAの配列を常に同期させる役割を担っています。
// HandleChartEvent: routes object clicks to checkboxes / toggle / actions void HandleChartEvent(const int id, const string &sparam, string &inMajorPairs[], bool &inPairSelected[]) { if(id == CHARTEVENT_OBJECT_CLICK) { // Checkbox click handling if(StringFind(sparam, "Chk_") == 0) { for(int i = 0; i < ArraySize(availablePairs); i++) { if(StringLen(availablePairs[i]) == 0) continue; string expected = "Chk_" + availablePairs[i]; if(expected == sparam) { if(CheckPointer(pairChecks[i]) == POINTER_DYNAMIC) { bool current = pairChecks[i].Checked(); if(i < ArraySize(inPairSelected)) inPairSelected[i] = current; else { ArrayResize(inPairSelected, i+1); inPairSelected[i] = current; } PrintFormat("HandleChartEvent: checkbox for %s toggled -> %s", availablePairs[i], current ? "true":"false"); } break; } } return; } // Multi toggle if(sparam == btnMultiToggle.Name()) { multiEnabled = !multiEnabled; UpdateMultiToggleVisual(); PrintFormat("HandleChartEvent: Multi toggle clicked. New multiEnabled=%s", (multiEnabled ? "true":"false")); return; } // Buttons - delegate to command handlers if(sparam == btnBuy.Name()) OpenBuyOrder(inMajorPairs, inPairSelected); else if(sparam == btnSell.Name()) OpenSellOrder(inMajorPairs, inPairSelected); else if(sparam == btnCloseAll.Name()) CloseAllPositions(inMajorPairs, inPairSelected); else if(sparam == btnDeleteOrders.Name()) DeleteAllPendingOrders(inMajorPairs, inPairSelected); else if(sparam == btnCloseProfit.Name()) CloseProfitablePositions(inMajorPairs, inPairSelected); else if(sparam == btnCloseLoss.Name()) CloseLosingPositions(inMajorPairs, inPairSelected); else if(sparam == btnBuyStop.Name()) PlaceBuyStop(inMajorPairs, inPairSelected); else if(sparam == btnSellStop.Name()) PlaceSellStop(inMajorPairs, inPairSelected); } }
UI作成ヘルパー
UIの生成は小さなヘルパー関数に分割されています。CreateButtonPanel()はキャンバスのビットマップ(パネル背景と装飾用の矩形)を作成し、CreateButtons()は各アクションボタンを一貫したフォント、サイズ、配置で生成し、スタイル設定します。CreateMultiToggle()はメインのアクションボタン群の上に配置されるトグルボタンを作成します。UpdateMultiToggleVisual()はマルチペアモードが有効かどうかを示すため、トグルのテキストと色を更新します。これらのヘルパーによって、ビジュアル処理がビジネスロジックから切り離され、スタイルの変更が容易になります。
// Create panel + buttons + multi-toggle visual helpers void CreateButtonPanel() { int panelWidthLocal = buttonWidth + 20; int panelHeightLocal = (buttonHeight + buttonSpacing) * 9 + buttonSpacing + 40; int x = 0, y = 40; if(!buttonPanel.CreateBitmap(0, 0, "ButtonPanel", x, y, panelWidthLocal, panelHeightLocal, COLOR_FORMAT_ARGB_NORMALIZE)) { Print("Failed to create button panel: Error=", GetLastError()); return; } ObjectSetInteger(0, "ButtonPanel", OBJPROP_ZORDER, 10); buttonPanel.FillRectangle(0, 0, panelWidthLocal, panelHeightLocal, ColorToARGB(clrDarkGray, 200)); buttonPanel.Rectangle(0, 0, panelWidthLocal - 1, panelHeightLocal - 1, ColorToARGB(clrRed, 255)); buttonPanel.Update(true); ChartRedraw(0); } void CreateMultiToggle() { int x = 10, y = 120; string font = "Calibri"; int fontSize = 8; color buttonBgColor = clrBlack; if(btnMultiToggle.Create(0, "btnMultiToggle", 0, x, y, x + buttonWidth, y + buttonHeight)) { ObjectSetString(0, "btnMultiToggle", OBJPROP_FONT, font); ObjectSetInteger(0, "btnMultiToggle", OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(0, "btnMultiToggle", OBJPROP_BGCOLOR, buttonBgColor); ObjectSetInteger(0, "btnMultiToggle", OBJPROP_ZORDER, 11); } } void UpdateMultiToggleVisual() { if(multiEnabled) { btnMultiToggle.Text("MULTI:ON"); btnMultiToggle.ColorBackground(clrGreen); btnMultiToggle.Color(clrWhite); } else { btnMultiToggle.Text("MULTI:OFF"); btnMultiToggle.ColorBackground(clrRed); btnMultiToggle.Color(clrWhite); } }
取引ヘルパー関数
これらのヘルパーは、低レベルの売買処理をカプセル化しています。PipSize(symbol)はピップの大きさ(コードではSYMBOL_POINT × 10.0を使用)を返し、IsSymbolValid(symbol)はBid/Askが取得できるかを確認します。TradeBuySingle()とTradeSellSingle()は、通貨ペアが取引可能か、ロットが許容範囲内かを検証し、ピップ値から価格、SL、TPを計算し、執行タイプを設定したうえでtrade.Buy()/trade.Sell()によって注文を送信します。これらのヘルパー関数により、注文送信ロジックが一か所に集約され、マルチペア処理側では通貨ペア単位でこれらを呼び出すだけで済むようになります。
// Pip size, validation, and single-symbol trade helpers double PipSize(string symbol) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(point <= 0) { Print("Invalid point size for ", symbol, ": Error=", GetLastError()); return 0; } return point * 10.0; } bool IsSymbolValid(string symbol) { bool inMarketWatch = SymbolInfoDouble(symbol, SYMBOL_BID) > 0 && SymbolInfoDouble(symbol, SYMBOL_ASK) > 0; if(!inMarketWatch) Print("Symbol ", symbol, " invalid: Not in Market Watch or no valid bid/ask price."); return inMarketWatch; } bool TradeBuySingle(const string symbol) { if(!IsSymbolValid(symbol)) return false; long tradeMode = SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE); if(tradeMode != SYMBOL_TRADE_MODE_FULL) { Print("TradeBuySingle: Skipping ", symbol, ": Trading disabled"); return false; } double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(LotSize < minLot || LotSize > maxLot) { Print("TradeBuySingle: invalid lot"); return false; } double price = SymbolInfoDouble(symbol, SYMBOL_ASK); double sl = StopLoss > 0 ? price - StopLoss * PipSize(symbol) : 0; double tp = TakeProfit > 0 ? price + TakeProfit * PipSize(symbol) : 0; trade.SetTypeFillingBySymbol(symbol); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); price = NormalizeDouble(price, digits); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); if(trade.Buy(LotSize, symbol, price, sl, tp)) { Print("Buy order placed on ", symbol, ": Ticket #", trade.ResultOrder()); return true; } else { Print("Buy order failed on ", symbol, ": Retcode=", trade.ResultRetcode()); return false; } } bool TradeSellSingle(const string symbol) { if(!IsSymbolValid(symbol)) return false; long tradeMode = SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE); if(tradeMode != SYMBOL_TRADE_MODE_FULL) { Print("TradeSellSingle: Skipping ", symbol, ": Trading disabled"); return false; } double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(LotSize < minLot || LotSize > maxLot) { Print("TradeSellSingle: invalid lot"); return false; } double price = SymbolInfoDouble(symbol, SYMBOL_BID); double sl = StopLoss > 0 ? price + StopLoss * PipSize(symbol) : 0; double tp = TakeProfit > 0 ? price - TakeProfit * PipSize(symbol) : 0; trade.SetTypeFillingBySymbol(symbol); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); price = NormalizeDouble(price, digits); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); if(trade.Sell(LotSize, symbol, price, sl, tp)) { Print("Sell order placed on ", symbol, ": Ticket #", trade.ResultOrder()); return true; } else { Print("Sell order failed on ", symbol, ": Retcode=", trade.ResultRetcode()); return false; } }
主な操作:マルチペアコマンドの適用方法
主要な操作(OpenBuyOrder、OpenSellOrder、CloseAllPositions、PlaceBuyStop、PlaceSellStopなど)は、EAから提供されるinMajorPairs[]とinPairSelected[]配列を受け取ります。multiEnabledがtrueの場合、ルーチンは全インデックスを走査し、選択されているインデックスごとにavailablePairs[i]を用いて取引用ヘルパーを呼び出します。multiEnabledがfalseの場合は、チャート上の通貨ペアのみを対象として取引をおこないます。OpenBuyOrder/OpenSellOrderは、チェックボックスでチャート通貨ペアが選択されているかどうかも追跡し、選択されていない場合はチャート通貨ペアを取引対象として扱います。これにより、チャート中心のモードとマルチペアモードを切り替える際にも、ユーザーが期待する挙動が正しく保たれるようになっています。
// OpenBuyOrder / OpenSellOrder excerpt (multipair iteration + fallback to chart symbol) void OpenBuyOrder(string &inMajorPairs[], bool &inPairSelected[]) { Print("Starting OpenBuyOrder"); string chartSym = Symbol(); if(!multiEnabled) { PrintFormat("OpenBuyOrder: multipair disabled => trading only chart symbol %s", chartSym); if(SymbolInfoInteger(chartSym, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) { Print("chart symbol not tradeable"); return; } TradeBuySingle(chartSym); return; } bool chartTraded = false; for(int i = 0; i < ArraySize(inMajorPairs); i++) { if(i < ArraySize(inPairSelected) && inPairSelected[i] && StringLen(availablePairs[i]) > 0) { string symbol = availablePairs[i]; Print("Attempting Buy order on ", symbol, " (requested ", resolvedBases[i], ")"); if(TradeBuySingle(symbol) && symbol == chartSym) chartTraded = true; } else { if(i < ArraySize(inMajorPairs)) Print("Skipping ", inMajorPairs[i], ": Not selected or unresolved"); } } if(!chartTraded && SymbolInfoInteger(chartSym, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_DISABLED) { PrintFormat("OpenBuyOrder: attempting BUY on chart symbol %s", chartSym); TradeBuySingle(chartSym); } } void OpenSellOrder(string &inMajorPairs[], bool &inPairSelected[]) { // (same structure as OpenBuyOrder but calling TradeSellSingle) // Implementation mirrors the buy flow but uses SELL specifics. }
New Headline EAへのマルチペア取引機能の統合
マルチペアシステムを導入する箇所(includesとinput)
EAの先頭では、マルチペア対応のUI/取引ヘッダを読み込み、さらにマルチペア機能を初期状態で有効にするかどうかの入力パラメータを公開します。ここがEA側で宣言すべき唯一のポイントであり、(a) 外部のマルチペアUI/取引オブジェクトを使用する、(b) 初期状態の動作をユーザーが入力で選択できる、という2点を明確にします。これにより、この機能はユーザーが明示的に選択して使うオプトイン形式となります。
#include <TradingButtons.mqh> // header that implements the multipair UI & trading logic. // ... other includes ... input bool EnableMultipair = true; // initial multipair enabled state
初期化時の期待動作をユーザーが選択できるようにするため、ヘッダをインクルードし、入力パラメータとして公開する必要があります。
マルチペア用データの所在(majorPairsと選択フラグ)
EAでは、取引対象として希望する通貨ペア名を格納したmajorPairs[]文字列配列と、どのペアが選択されているかを記録するpairSelected[]のboolean配列を定義します。これら2つの配列は、EAとヘッダ間の「契約」となるもので、両配列の同じインデックスiが同じ通貨ペアを指すという対応関係を維持します。ヘッダ側では、この配列を基にチェックボックスを生成し、さらにboolean配列を用いてユーザーがどのペアを選択しているかを把握します。
// MULTIPAIR arrays (provided to the header) // default major pairs (you can edit or later replace with resolved broker symbols) string majorPairs[] = {"EURUSD","GBPUSD","USDJPY","USDCHF","AUDUSD","USDCAD","NZDUSD"}; bool pairSelected[];
シンプルでインデックス整合されたペアリストと選択フラグを維持するための設計であり、可読性が高く、参照渡しもしやすく、同期処理も明快になります。
選択状態の初期化とヘッダへの入力引き渡し(OnInitセットアップ)
OnInit()では、まずEAがpairSelected配列のサイズをmajorPairsに合わせてリサイズし、すべての要素をtrueで初期化します。その後、EAはbuttonsEAの公開パラメータ(ロットサイズ、ストップ設定、リスク関連設定)を構成し、Init()とSetMultiEnabled(EnableMultipair)を呼び出してヘッダを初期化します。これにより、ヘッダはEAで選択されたモードで起動し、同じ取引パラメータを使用するようになります。
// In OnInit() ArrayResize(pairSelected, ArraySize(majorPairs)); for(int i = 0; i < ArraySize(pairSelected); i++) pairSelected[i] = true; // Initialize TradingButtons buttonsEA.LotSize = ButtonLotSize; buttonsEA.StopLoss = ButtonStopLoss; buttonsEA.TakeProfit = ButtonTakeProfit; buttonsEA.StopOrderDistancePips = StopOrderDistancePips; buttonsEA.RiskRewardRatio = RiskRewardRatio; buttonsEA.Init(); buttonsEA.SetMultiEnabled(EnableMultipair); // pass initial multipair state
UI/取引オブジェクトを初期化する前に設定を同期させます。まずpublicフィールドとモードを設定し、その後Init()を呼び出すことで、ヘッダが正しいランタイムパラメータで動作するようにします。
チェックボックスの作成(UIの配置):CreatePairCheckboxesの呼び出し
ここでは、EAがcheckboxYの縦オフセットを計算し(チェックボックスがニュースレーンの下に表示されるように)、CreatePairCheckboxes(majorPairs, pairSelected, checkboxY)を呼び出します。これにより、ヘッダ内にチェックボックスが作成され、majorPairsとのインデックス整合性も保持されます。ヘッダはまた、pairSelected[]から各チェックボックスの初期状態を設定するため、UIとEAの状態が同期されます。これにより、UIコンポーネントはコントロールを描画しつつ、EAの配列を参照渡しで扱うことが可能になり、EAがどのペアが存在し選択されているかの権威ある管理者として振る舞い、ヘッダは同じ配列を操作する形になります。
// create pair checkboxes aligned below the canvas lanes int checkboxY = InpTopOffset + (InpSeparateLanes ? 8 : 28) * lineH + 6; // adjust +6 px margin if needed buttonsEA.CreatePairCheckboxes(majorPairs, pairSelected, checkboxY);
イベントルーティング:チャートイベントをヘッダに転送する
EA自身はボタンクリックのロジックを実装せず、OnChartEventからすべてのチャートオブジェクトのクリックをbuttonsEA.HandleChartEvent(...)に転送します。この単一呼び出しの契約により、EAは簡素化され、ヘッダがマルチペア切替、チェックボックスクリック、および手動取引ボタンの操作を担当する形になります。
// OnChartEvent: forward to the header with majorPairs and pairSelected arrays void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Header will handle multipair toggle and trade behaviour buttonsEA.HandleChartEvent(id, sparam, majorPairs, pairSelected); }
別個のイベントルーティングを適用することで、EAは単なるイベント伝達役となり、ヘッダがUIイベントと取引判断をきれいに処理します。これにより責務が明確に分離されます。
手動マルチペア操作のトリガー方法(ヘッダの役割)
ヘッダはマルチプレックスされた手動取引の実行エンジンとして機能し、EAは単純な配列を渡すだけで、ヘッダが通貨ペアを解決しながらループ処理をおこないます。これによりEA側のコードは整理されたままになります。 ユーザーがBuy/Sellボタンをクリックしたりマルチペアを切り替えたりすると、ヘッダのHandleChartEventは渡されたmajorPairsとpairSelected配列を参照して、どのペアでアクションを実行するかを判断します。具体的には、配列のインデックスをループして「pairSelected[i] == true」のものだけを取引します。EAは単に配列と設定を渡すだけで、ヘッダが複数通貨ペアの解決と取引を実行します(通貨ペア単位の取引ヘルパーやマルチペアのループ処理はヘッダを参照してください)。// (conceptual) header receives arrays and performs per-index iteration: // Pseudocode excerpt of header behavior (actual code in TradingButtons.mqh) for(i = 0; i < ArraySize(majorPairs); i++) { if(i < ArraySize(pairSelected) && pairSelected[i]) { // resolve broker symbol for majorPairs[i] // call TradeBuySingle(resolvedSymbol) or TradeSellSingle(...) } }
自動注文ロジックはチャート固有のまま(マルチペアと自動化の共存方法)
このEAにおける自動の事前ストップ配置やインパクト後の注文は、majorPairsではなくチャート通貨ペア(_Symbol)に対して実行されます。マルチペアの手動システムは別系統であり、手動のマルチペア取引(ボタン操作)と自動のイベント駆動取引は異なるフローとして扱われます。この分離により、明示的に自動化をmajorPairsに拡張しない限り、誤って自動で複数通貨ペアの注文が出ることを防ぎます。
// Example: automated BuyStop/SellStop placement uses _Symbol (chart symbol) if(trade.BuyStop(InpOrderVolume, buyPrice, _Symbol, buySL, buyTP)) ticketBuyStop = trade.ResultOrder(); if(trade.SellStop(InpOrderVolume, sellPrice, _Symbol, sellSL, sellTP)) ticketSellStop = trade.ResultOrder();
上記コードから得られる重要な教訓は、手動マルチペア操作は、自動化されたチャート固有戦略とは別に管理することです。自動化を意図的に複数通貨ペアに拡張しない限り、明確な分離により予期せぬ取引を防げます。
同期パターン:EAがデータを所有し、ヘッダがUI/ロジックを管理
EAはmajorPairs[]とpairSelected[]を定義・保持し、これが権威ある状態となります。ヘッダはこれらを参照して(コントロール作成や選択項目の操作をおこない)、変更があれば書き戻します(チェックボックスクリックでpairSelected[i]が更新される)。配列を参照渡しすることで、この双方向同期パターンはシンプルかつ堅牢です。EAは任意のタイミングでpairSelected[]を確認でき、ユーザー操作時にはヘッダが自動で更新します。
// EA owns arrays; header is given references and updates them when checkboxes are toggled buttonsEA.CreatePairCheckboxes(majorPairs, pairSelected, checkboxY); ... // header's HandleChartEvent updates pairSelected[] in-place when checkboxes are clicked
共有ランタイム状態には参照渡しを使用します。オーバーヘッドが少なく、追加のメッセージパスなしでUIとEAの同期を維持します。
配置およびビジュアルレイアウトの考慮(ニュースレーン下のチェックボックス)
EAはニュースレーンおよび設定オプション(InpTopOffset、InpSeparateLanes、lineH)に基づいてcheckboxYを計算し、マルチペアチェックボックスがニュース/インジケーターレーンの下に正しく表示されるようにします。他モジュールのUI要素を統合する際は、単にロジックではなくレイアウトタスクでもあり、オフセットを動的に計算することで、レーンの高さや位置が変わった場合でもUIが適応します。
// compute vertical position for checkboxes so they sit below the lanes int checkboxY = InpTopOffset + (InpSeparateLanes ? 8 : 28) * lineH + 6; buttonsEA.CreatePairCheckboxes(majorPairs, pairSelected, checkboxY);
キャンバスと外部UIパネルを組み合わせる場合、レイアウト計算をEA側に集中させることで、両システム間で一貫した間隔ルールを共有できます。
テスト
MetaTrader 5ターミナル上でEAを展開したところ、非常に良好な結果が得られました。取引したい通貨ペアを選択でき、ボタン操作に即座に反応しました。注文はアルゴリズム速度で実行され、ワンクリックで複数ペアの全ポジションを決済できるため、ニュース取引や高ボラティリティのスキャルピング戦略において非常に有用な機能です。
以下の図は、ライブチャートでのテスト結果を示しています。手動操作の機能はリアルタイムでチャートと連動する必要がありますが、EAの自動化コンポーネントはストラテジーテスターで効率性を十分に検証することが可能です。

News Headline EAを使用したマルチペア取引
結論
パートIXでは、News Headline EAの進化におけるもう一つの重要なマイルストーン、マルチペア取引の統合が実現しました。この進化により、従来の制約が解消され、トレーダーは1つのチャート上で複数ペアを管理できるようになりました。完全自動取引システムではありませんが、この機能はアルゴリズムによる実行を活用した手動取引インターフェースとして機能し、高ボラティリティ環境下でもトレーダーが意思決定をおこなう中で速度と正確性を提供します。
開発プロセス自体も非常に示唆に富むもので、モジュール化と関心の分離という原則を適用して、コンパクトでありながら強力なシステムを構築しました。シンプルなカレンダーとニュースフィード表示として始まったものが、手動機能と自動化機能を融合させ、ニューストレーダーが直面する実務上の課題を解決する多用途なフレームワークへと成長しました。主要通貨ペアを前提として設計されていますが、少しの互換性調整でカスタム通貨ペアにも適用可能です。
開発上の主要な課題の一つは、ブローカーごとの通貨ペア命名でした。たとえば、取引はEURUSD.0を使用していたため、標準のEURUSDではないことで最初は失敗しました。これを解決するため、EAは主要ペアのブローカー命名規則に動的に適応できるよう強化しました。
さらに、MQL5標準ライブラリのCCheckBoxクラスを活用してスムーズなペア選択を可能にし、MQL5言語の柔軟性と拡張性を実証しました。
本記事が有用な洞察と実践的な教訓を提供できたことを願っています。完全なソースコードは本文に添付しており、この記事と併せて実装を参照できます。便宜上、主要なポイントも表形式でまとめています。フィードバックやコメントは常に歓迎いたします。
重要な学び
| 重要な学び | 説明 |
|---|---|
| インデックス整列配列 | どちらのプロジェクトも、インデックスで整列されたmajorPairs[]配列とpairSelected[]配列を使用します。これにより、チェックボックス、リクエストされたベース名、解決済みブローカー通貨ペアが常に同じ通貨ペアを参照することが保証されます。 |
| 参照渡しによる同期 | 配列はEAからTradingButtons.mqhに参照によって渡され、チェックボックスが切り替えられたときにヘッダが選択状態を直接更新して、EAの状態を即座に同期させることができます。 |
| 明示的なイベント転送 | EAはボタンのクリックを直接処理しません。代わりに、OnChartEventはイベントをbuttonsEA.HandleChartEvent()に転送し、そこでヘッダはマルチペアのトグル、チェックボックスのクリック、および取引アクションを解釈します。 |
| 通貨ペア解決の抽象化 | ヘッダのResolveSymbol()は、ユーザーフレンドリーなベース(例:EURUSD)をブローカー固有の通貨ペア(例:EURUSD.ecn)にマッピングします。EAはブローカーの命名規則の影響を受けずに済みます。 |
| 手動フローと自動フローの分離 | EAでは、自動化されたイベント前後の注文は常にチャート通貨ペアに基づいて実行されますが、マルチペア機能は手動のボタンアクション用に予約されています。この分離により、予期しない大量取引を回避できます。 |
| 動的なUIの作成とクリーンアップ | ヘッダは、Init()/Deinit()中にチェックボックスとボタンを動的に作成/破棄します。EAはレイアウトオフセット(ニュースレーンの下)を計算し、コンポーネントがUIにシームレスに適合するようにします。 |
| Init()の前に初期化する | OnInit()では、EAはbuttonsEA.Init()を呼び出す前に、LotSize、StopLoss、TakeProfit、EnableMultipairを設定します。これにより、ヘッダが正しい構成で構築されることが保証されます。 |
| 集中型取引ヘルパー | 取引ロジックは、TradeBuySingle()やTradeSellSingle()などの再利用可能なヘルパーにカプセル化されています。これにより、複数ペアのループとボタンハンドラ間でのコードの重複が回避されます。 |
| マルチペアトグル動作 | btnMultiToggleボタンは、単一通貨ペアモードと複数ペアモードを切り替えます。マルチペアモードでは、アクションは選択されたすべてのペアを反復します。シングルモードでは、アクションはチャート通貨ペアにのみ適用されます。 |
| チャート通貨ペアへのフォールバック | マルチペアモードが有効になっていてもチャート通貨ペアが選択されていない場合でも、ヘッダによってチャート通貨ペア上に取引が配置されていることが確認されます。これにより、チャートに集中しているユーザーには予測可能な結果が提供されます。 |
| レイアウト調整 | EAはcheckboxYを計算して、スクロールするニュースキャンバスの下に複数ペアのチェックボックスをきちんと配置します。ここでは、サードパーティのUIパネルを重複なくカスタムインジケーターオーバーレイと統合する方法を示します。 |
| エラーログと明確化 | どちらのモジュールも、詳細なログ(通貨ペア解決の失敗、注文配置の再コードなど)を出力します。このトレーサビリティにより、プログラマーとユーザーは構成の問題を迅速に診断できます。 |
| 双方向同期パターン | チェックボックスの状態はpairSelected[]から初期化(EAからUI)され、クリックするとpairSelected[]が更新(UIからEA)されます。この継続的なループにより、両方のモジュールが同じ選択状態を共有することが保証されます。 |
| 安全な動的メモリの使用 | ヘッダは各ペアに対して新しいCCheckBox()を使用し、Deinit()でそれらを慎重に削除します。これは、MQL5プログラマーに、長時間実行されるEAでGUIオブジェクトを安全に管理する方法を教えます。 |
| モジュール化による拡張性 | マルチペア取引をスタンドアロンヘッダにカプセル化することで、マルチペアロジックを書き換えることなく、同じクラスを複数のEA(News Headline EAなど)で再利用できます。これは、コード再利用のためのスケーラブルなパターンです。 |
添付ファイル
| ファイル名 | バージョン | 説明 |
|---|---|---|
| News_Headline_EA.mq5 | 1.13 | 経済カレンダーのイベントとニュースの見出しをチャート上に直接統合するEA。イベント前のストップ注文、インパクト後の取引を管理し、スクロールするニュースを表示します。バージョン1.13では、TradingButtonsモジュールによるマルチペア取引のサポートにより機能が拡張され、チャートベースのイベント取引の自動化と並行して、手動でのマルチペア注文の実行が可能になります。 |
| TradingButtons.mqh | 1 | マルチペア取引インターフェースを提供するモジュラーヘッダBuy、Sell、Close、Delete、Stop ordersと、複数の通貨ペアを選択するためのチェックボックスが作成されます。これには、通貨ペア解決ロジック、注文配置ヘルパー、および単一通貨ペアと複数ペアの取引間の切り替えが含まれます。News Headline EAを含むさまざまなEA間で再利用できるように設計されています。 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19008
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
知っておくべきMQL5ウィザードのテクニック(第79回):教師あり学習でのゲーターオシレーターとA/Dオシレーターの使用
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
古典的な戦略を再構築する(第15回):デイリーブレイクアウト取引戦略
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索

このようなことの本当の意味は何でしょうか?なぜなら、EAは設計上(箱から出して)どのチャートからでも多くのシンボルを取引することが可能であり、EAに影響を与えることなく簡単にシンボル間のチャートを切り替えることができるからです - チャートのシンボル/タイムフレームが変更されても、EAはリロードされません。
追記元のコメントは英語で投稿されています - 正しい理解のためにお読みください - 自動翻訳ではばかげた文章が作成される可能性があります。
このようなことの本当の意味は何でしょうか?EAは設計上(箱から出して)どのチャートからでも多くのシンボルを取引することが可能であり、EAに影響を与えることなく簡単にシンボル間のチャートを切り替えることができます - チャートのシンボル/タイムフレームが変更されたときに再ロードされることはありません。
追記元のコメントは英語で投稿されています - 正しい理解のためにお読みください - 自動翻訳ではばかげた文章が作成される可能性があります。
Stanislav Korotkyさん、こんにちは、
ご意見ありがとうございます。確かに、EAは1つのチャートから複数のシンボルを取引することができ、手動でシンボルを切り替えても、実行中のEAをリロードしたり中断したりすることはありません。
しかし、私のアイデアは、特に複数のペアで同時執行が必要な状況を対象としています。例えば、インパクトの大きいニュースイベントの際に、GBPUSDとEURUSDの両方でまったく同じ瞬間に同期注文を出したい場合などです。このような場合、手動でのシンボル切り替えは現実的ではありません。
そのため、私は複数のシンボルをプログラムで管理することを重視しています。EAが異なる基本シンボルのチャートに接続されている場合でも、選択したペアを自動的に処理し、取引を実行できるようにします。