初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(X) - ニュース取引のための多銘柄チャート表示
内容
はじめに
前回の記事では、トレーダーが好みの通貨ペアを素早く選択できる複数通貨ペア管理機能を紹介しました。これにより、取引のためにチャートを切り替えてペアを設定する手間を大幅に削減できました。
しかし課題が残っていました。選択した通貨ペアを管理できるものの、チャートAPIに直接アクセスできず、分析用の価格情報を便利に確認できない点です。そこで本日は、マルチチャート表示専用クラスを開発し、チャート内でシームレスに操作できるようにすることで、News Headline EAの機能セットの一部として統合し、複数通貨ペア取引の利便性をさらに高めます。
前提を整理するため、ここでこれまでの進捗を簡単に振り返ります。より詳細な理解のために、過去の関連公開記事を再読することも推奨します。
News Headline EAの初期バージョンは、予定された経済指標イベントをチャート上の明確な「レーン」として表示し、重要度別に分類することに重点を置いていました。この「レーン」の概念はすぐに拡張され、Alpha VantageのニュースフィードAPI統合、ローカルAIモデルによるインサイトの追加、内蔵インジケーターを用いたインサイト用のレーン作成がおこなわれました。これを基盤として、NFP(非農業部門雇用者数)取引を含む自動ニュース取引機能や、手動取引ボタンとの統合機能を追加しました。
最近では、複数通貨ペア取引に進化させ、高ボラティリティ時の迅速な意思決定を支援できるようにしました。この機能により、複数通貨ペアの管理や取引が高速市場環境下でも格段に容易になっています。
本日は、複数通貨ペア取引のアプローチをさらに洗練し、EA内でリアルタイムのマルチチャート表示を可能にすることで、トレーダーの判断力を強化することに焦点を当てます。
実装戦略
私たちの重要な優先事項の一つは、初心者の読者であっても概念を明確に理解できる形で知識を提供することです。本セクションでは、その目的を達成するために適用するアプローチと実装方法を分解して解説します。
プログラムが成長するにつれ、新機能の統合には構造化され保守可能なアプローチを採用することが重要です。私がよく用いる手法の一つがモジュール化で、これにより開発をスムーズに進めると同時に、ヘッダファイルやクラスとして再利用可能なユーティリティを作成することができます。
News Headline EAのような高度なプログラムでは、私は以下の一貫したワークフローに従っています。
- 新機能をテストするための独立したミニプログラムをまず開発する。
- 機能が実現可能かつ安定していることが確認できたら、メインのEAに統合する。
このプロセスにより、作業は集中化され、エラーが減少し、新しい機能が既存の機能を損なうことなくシームレスに組み込まれることが保証されます。
本日は、CChartMiniTilesクラスを作成する設計タスクに取り組みます。このクラスは、単一のチャート内で複数の通貨ペアチャートを表示し、それぞれのサイズをカスタマイズ可能にする役割を担います。その後、概念を検証するためにダミーEA (MiniChartsEA)でクラスを実装します。確認後、このクラスをNews Headline EAに統合し、スムーズに機能するよう調整します。
なお、CChartMiniTilesやMiniChartsEAといった名称は、あくまでこの手順で使う仮の名称です。プログラムの動作原理が理解できていれば、任意の名前を使用して構いません。
次の4つのサブセクションでは、具体的な実装に焦点を当てます。
- ChartMiniTilesヘッダ
- ヘッダをテストする例示用EA(MiniChartsEA)
- 初期テスト
- ChartsMiniTilesのNews Headline EAへの統合
1.0 ChartMiniTilesヘッダ
このヘッダファイルでは、CChartMiniTilesクラスを定義します。本クラスは、単一のMetaTrader 5チャート内で複数のミニチャートを表示・管理するための再利用可能なユーティリティです。このクラスの目的は、News Headline EAのような大規模プロジェクトに複数通貨ペアチャート表示機能を統合する際に、コードをモジュール化し、保守性を高めながら実装を簡素化することにあります。
この機能を独立したヘッダに分離することで、たとえばダミーEAを用いた単独テストが可能となり、その後より複雑なシステムにシームレスに統合することができます。このアプローチにより、開発フローが効率化され、エラー発生の可能性も低減します。
以下のセクションでは、プログラムの構造や機能ごとに、コード開発プロセスをステップごとに分解して解説します。
1.1. クラスの概要と目的
この冒頭ブロックでは、ヘッダファイルの目的を明示するとともに、クラス全体で使用されるコンパイル時のデフォルト設定を定義しています。マクロを使用することで、レイアウト、タイミング、トグルのデフォルト設定を集中管理でき、実装の詳細を確認せずとも挙動を素早く調整できるようになっています。コメントヘッダには、本クラスの機能セットが記載されています。具体的には、OBJ_CHARTを用いて作成される下部固定のミニチャートタイル、異なるブローカー環境でも動作する通貨ペア自動判別機能、素早く表示を切り替えられるトグルボタン、レスポンシブに計算されるレイアウト調整機能、および他のUI要素と干渉しないように上部を確保するオプション領域などです。このセクションにより、読者はクラスの構成と目的、提供される機能を理解したうえで、以降のコード解説に臨むことができます。
//+------------------------------------------------------------------+ //| ChartMiniTilesClass.mqh | //| Class wrapper for ChartMiniTiles functionality | //| - CChartMiniTiles class | //| - bottom-anchored mini-chart tiles using OBJ_CHART | //| - broker-adaptive symbol lookup, toggle button, responsive layout | //| - supports top-reserved area to avoid overlapping top UI | //+------------------------------------------------------------------+ #ifndef __CHART_MINI_TILES_CLASS_MQH__ #define __CHART_MINI_TILES_CLASS_MQH__ //--- compile-time defaults (macros are safe in MQL5) #define CMT_DEFAULT_WIDTH 120 #define CMT_DEFAULT_HEIGHT 112 // quadrupled default #define CMT_DEFAULT_X_OFFSET 10 #define CMT_DEFAULT_Y_OFFSET 40 // bottom offset from bottom #define CMT_DEFAULT_SPACING 6 #define CMT_DEFAULT_PERIOD PERIOD_M1 #define CMT_DEFAULT_CHART_SCALE 2 // toggle button defaults #define CMT_TOG_X 8 #define CMT_TOG_Y 6 #define CMT_TOG_W 72 #define CMT_TOG_H 20 #define CMT_TOG_NAME "CMT_ToggleButton"
1.2. コンストラクタとデストラクタ
コンストラクタは、クラスを既知で安全な状態に初期化し、コンパイル時のデフォルト設定を適用します。内部配列やデフォルトのタイルサイズ・オフセット、トグルボタンの設定を準備し、m_top_reservedを0に設定して、デフォルトでは上部に予約領域を持たない状態とします。デストラクタではDelete()を呼び出し、クラスが生成したオブジェクトをインスタンスのスコープ終了時に確実に削除します。この決定論的な初期化と終了処理により、チャート上に残留オブジェクトが残ることを防ぎ、デバッグ時の手間を大幅に軽減できます。
//+------------------------------------------------------------------+ //| CChartMiniTiles class declaration | //+------------------------------------------------------------------+ class CChartMiniTiles { public: CChartMiniTiles(void); ~CChartMiniTiles(void); bool Init(const string majorSymbols, const int width = -1, const int height = -1, const int xOffset = -1, const int yOffset = -1, // bottom offset (pixels from bottom) const int spacing = -1, const int period = -1, const bool dateScale = true, const bool priceScale = false, const int chartScale = -1); void UpdateLayout(void); void Delete(void); bool HandleEvent(const int id,const string sparam); // forward OnChartEvent void SetToggleButtonPos(const int x,const int y); void SetTilesVisible(const bool visible); void Toggle(void); // NEW: reserve top area height (pixels from top) that tiles must NOT cover void SetTopReservedHeight(const int pixels); private: // state string m_object_names[]; // object names created string m_symbols[]; // resolved broker symbols int m_count; int m_width; int m_height; int m_xoffset; int m_yoffset; // bottom offset int m_spacing; int m_period; bool m_date_scale; bool m_price_scale; int m_chart_scale; bool m_visible; int m_tog_x; int m_tog_y; int m_tog_w; int m_tog_h; string m_tog_name; // NEW int m_top_reserved; // pixels from top that must be left free for other UI private: // helpers string MakeObjectName(const string base); string TrimString(const string s); string FindBrokerSymbol(const string baseSymbol); int ComputeBaseYFromTop(void); void CreateToggleButton(void); void DeleteToggleButton(void); void CollapseAll(void); }; //+------------------------------------------------------------------+ //| Constructor / Destructor | //+------------------------------------------------------------------+ CChartMiniTiles::CChartMiniTiles(void) { m_count = 0; ArrayResize(m_object_names,0); ArrayResize(m_symbols,0); m_width = CMT_DEFAULT_WIDTH; m_height = CMT_DEFAULT_HEIGHT; m_xoffset = CMT_DEFAULT_X_OFFSET; m_yoffset = CMT_DEFAULT_Y_OFFSET; m_spacing = CMT_DEFAULT_SPACING; m_period = CMT_DEFAULT_PERIOD; m_date_scale = false; m_price_scale = false; m_chart_scale = CMT_DEFAULT_CHART_SCALE; m_visible = true; m_tog_x = CMT_TOG_X; m_tog_y = CMT_TOG_Y; m_tog_w = CMT_TOG_W; m_tog_h = CMT_TOG_H; m_tog_name = CMT_TOG_NAME; m_top_reserved = 0; // default: no reserved area } CChartMiniTiles::~CChartMiniTiles(void) { Delete(); }
1.3. ヘルパーメソッド
これらの特化したヘルパー関数は、入力値を正規化し、ブローカー固有の挙動を隠蔽することで、クラスの他の部分をよりシンプルに保つ役割を果たします。MakeObjectNameは文字列を安全なオブジェクト名に変換します(スペースや特殊文字を置換)。TrimStringは、文字列の先頭および末尾の空白、タブ、改行を削除します。FindBrokerSymbolは、まずSymbolSelectで正確な一致を試み、失敗した場合はブローカーのシンボルリストを大文字小文字を区別せずに検索して一致するものを探します。この処理は、接尾辞を付加したり異なる命名規則を使用するブローカー間での移植性を確保するうえで重要です。ComputeBaseYFromTopは、下部固定タイルの縦方向基準線を算出し、無効なチャート高さ値を回避します。
//+------------------------------------------------------------------+ //| Helpers implementation | //+------------------------------------------------------------------+ string CChartMiniTiles::MakeObjectName(const string base) { string name = base; StringReplace(name, " ", "_"); StringReplace(name, ".", "_"); StringReplace(name, ":", "_"); StringReplace(name, "/", "_"); StringReplace(name, "\\", "_"); return(name); } string CChartMiniTiles::TrimString(const string s) { if(s == NULL) return(""); int len = StringLen(s); if(len == 0) return(""); int left = 0; int right = len - 1; while(left <= right) { int ch = StringGetCharacter(s, left); if(ch == 32 || ch == 9 || ch == 10 || ch == 13) left++; else break; } while(right >= left) { int ch = StringGetCharacter(s, right); if(ch == 32 || ch == 9 || ch == 10 || ch == 13) right--; else break; } if(left > right) return(""); return StringSubstr(s, left, right - left + 1); } string CChartMiniTiles::FindBrokerSymbol(const string baseSymbol) { if(StringLen(baseSymbol) == 0) return(""); if(SymbolSelect(baseSymbol, true)) return(baseSymbol); string baseUpper = baseSymbol; StringToUpper(baseUpper); int total = SymbolsTotal(true); for(int i = 0; i < total; i++) { string s = SymbolName(i, true); if(StringLen(s) == 0) continue; string sUpper = s; StringToUpper(sUpper); if(StringFind(sUpper, baseUpper) >= 0) return(s); } return(""); } int CChartMiniTiles::ComputeBaseYFromTop(void) { int chartTotalHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(chartTotalHeight <= 0) chartTotalHeight = 600; int base_y_from_top = chartTotalHeight - m_yoffset - m_height; if(base_y_from_top < 0) base_y_from_top = 0; if(base_y_from_top + m_height > chartTotalHeight) base_y_from_top = MathMax(0, chartTotalHeight - m_height); return base_y_from_top; }
1.4. トグルボタンコントロール
インタラクティブな操作はトグルボタンを通じて提供されます。CreateToggleButtonはボタンオブジェクトの存在を確認し(存在しなければ作成)、位置、サイズ、フォント、背景色、文字色、選択可否、m_visibleに基づく初期表示テキストといった視覚プロパティを設定します。DeleteToggleButtonはボタンを安全に削除します。CollapseAllは軽量な非表示手法で、タイルを削除するのではなく、画面外に移動させサイズをゼロに設定することで実質的に非表示にします。これにより状態が保持され、再表示が高速になり、オブジェクトを再作成するオーバーヘッドも回避できます。
//+------------------------------------------------------------------+ //| Toggle button helpers | //+------------------------------------------------------------------+ void CChartMiniTiles::CreateToggleButton(void) { if(ObjectFind(ChartID(), m_tog_name) == -1) ObjectCreate(ChartID(), m_tog_name, OBJ_BUTTON, 0, 0, 0, 0, 0); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_XDISTANCE, m_tog_x); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_YDISTANCE, m_tog_y); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_XSIZE, m_tog_w); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_YSIZE, m_tog_h); ObjectSetString(ChartID(), m_tog_name, OBJPROP_FONT, "Arial"); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_FONTSIZE, 10); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_BGCOLOR, clrDarkSlateGray); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_COLOR, clrWhite); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_SELECTABLE, 1); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_STATE, m_visible ? 1 : 0); ObjectSetString(ChartID(), m_tog_name, OBJPROP_TEXT, m_visible ? "Tiles: ON" : "Tiles: OFF"); } void CChartMiniTiles::DeleteToggleButton(void) { if(ObjectFind(ChartID(), m_tog_name) >= 0) ObjectDelete(ChartID(), m_tog_name); } void CChartMiniTiles::CollapseAll(void) { for(int i = 0; i < ArraySize(m_object_names); i++) { string name = m_object_names[i]; if(StringLen(name) == 0) continue; if(ObjectFind(ChartID(), name) == -1) continue; ObjectSetInteger(ChartID(), name, OBJPROP_XDISTANCE, -1); ObjectSetInteger(ChartID(), name, OBJPROP_YDISTANCE, -1); ObjectSetInteger(ChartID(), name, OBJPROP_XSIZE, 0); ObjectSetInteger(ChartID(), name, OBJPROP_YSIZE, 0); } }
1.5. ミニチャートの初期化
Initはミニチャートの初期化処理の中心です。まず以前の状態を消去し、渡されたパラメータを適用(未指定の場合はデフォルト値を使用)し、カンマ区切りのmajorSymbolsリストを解析します。その後、各文字列をトリムし、FindBrokerSymbolを通じてブローカー固有のシンボルに解決し、m_symbols配列を構築します。チャート幅とタイルサイズからレイアウト制約を計算し、上部予約領域m_top_reservedに侵入しないよう行数制限を適用します。解決済みの各シンボルに対して、OBJ_CHARTを作成し、プロパティ(シンボル、時間足、距離、サイズ、日付/価格スケール、チャートスケール)を設定し、後で更新や削除ができるようオブジェクト名を保持します。作成後はトグルボタンを生成し、初期表示フラグを反映してチャートを再描画し、処理の成功を返します。また、このメソッドではシンボル解決や作成オブジェクトのログを記録しており、実行時の挙動を明確に確認できます。
//+------------------------------------------------------------------+ //| NEW: set top reserved height (pixels from top) | //+------------------------------------------------------------------+ void CChartMiniTiles::SetTopReservedHeight(const int pixels) { m_top_reserved = MathMax(0, pixels); } //+------------------------------------------------------------------+ //| Init: create mini tiles | //+------------------------------------------------------------------+ bool CChartMiniTiles::Init(const string majorSymbols, const int width, const int height, const int xOffset, const int yOffset, const int spacing, const int period, const bool dateScale, const bool priceScale, const int chartScale) { Delete(); m_width = (width <= 0) ? CMT_DEFAULT_WIDTH : width; m_height = (height <= 0) ? CMT_DEFAULT_HEIGHT : height; m_xoffset = (xOffset <= 0) ? CMT_DEFAULT_X_OFFSET : xOffset; m_yoffset = (yOffset <= 0) ? CMT_DEFAULT_Y_OFFSET : yOffset; m_spacing = (spacing <= 0) ? CMT_DEFAULT_SPACING : spacing; m_period = (period <= 0) ? CMT_DEFAULT_PERIOD : period; m_date_scale = dateScale; m_price_scale = priceScale; m_chart_scale = (chartScale <= 0) ? CMT_DEFAULT_CHART_SCALE : chartScale; ArrayFree(m_object_names); ArrayFree(m_symbols); m_count = 0; string raw[]; StringSplit(majorSymbols, ',', raw); int rawCount = ArraySize(raw); if(rawCount == 0) return(false); for(int i = 0; i < rawCount; i++) { string base = TrimString(raw[i]); if(StringLen(base) == 0) continue; string resolved = FindBrokerSymbol(base); if(resolved == "") { PrintFormat("CMT: symbol not found on this broker: %s", base); continue; } int n = ArraySize(m_symbols); ArrayResize(m_symbols, n + 1); m_symbols[n] = resolved; } m_count = ArraySize(m_symbols); PrintFormat("CMT: %d symbols resolved for mini-tiles.", m_count); for(int i=0;i<m_count;i++) PrintFormat("CMT: symbol[%d] = %s", i, m_symbols[i]); if(m_count == 0) return(false); ArrayResize(m_object_names, m_count); for(int i = 0; i < m_count; i++) m_object_names[i] = ""; int base_y_from_top = ComputeBaseYFromTop(); int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); if(chartW <= 0) chartW = 800; int columns = MathMax(1, chartW / (m_width + m_spacing)); // --- NEW: limit rows to avoid top reserved region int rows = (m_count + columns - 1) / columns; int availableAbove = MathMax(0, base_y_from_top - m_top_reserved); int maxRowsAllowed = 1 + (availableAbove / (m_height + m_spacing)); // bottom row + how many rows can fit above if(maxRowsAllowed < 1) maxRowsAllowed = 1; if(rows > maxRowsAllowed) { // increase columns to fit within allowed rows columns = (m_count + maxRowsAllowed - 1) / maxRowsAllowed; if(columns < 1) columns = 1; rows = (m_count + columns - 1) / columns; } // --- int createdCount = 0; for(int i = 0; i < m_count; i++) { string sym = m_symbols[i]; string objName = MakeObjectName("CMT_" + sym + "_" + IntegerToString(i)); m_object_names[i] = objName; int col = i % columns; int row = i / columns; int xdist = m_xoffset + col * (m_width + m_spacing); int ydist = base_y_from_top - row * (m_height + m_spacing); if(ydist < 0) ydist = 0; bool created = ObjectCreate(ChartID(), objName, OBJ_CHART, 0, 0, 0, 0, 0); if(!created) { PrintFormat("CMT: failed to create OBJ_CHART for %s (obj=%s)", sym, objName); m_object_names[i] = ""; continue; } ObjectSetString(ChartID(), objName, OBJPROP_SYMBOL, sym); ObjectSetInteger(ChartID(), objName, OBJPROP_PERIOD, m_period); ObjectSetInteger(ChartID(), objName, OBJPROP_XDISTANCE, xdist); ObjectSetInteger(ChartID(), objName, OBJPROP_YDISTANCE, ydist); ObjectSetInteger(ChartID(), objName, OBJPROP_XSIZE, m_width); ObjectSetInteger(ChartID(), objName, OBJPROP_YSIZE, m_height); ObjectSetInteger(ChartID(), objName, OBJPROP_DATE_SCALE, (int)m_date_scale); ObjectSetInteger(ChartID(), objName, OBJPROP_PRICE_SCALE, (int)m_price_scale); ObjectSetInteger(ChartID(), objName, OBJPROP_SELECTABLE, 1); ObjectSetInteger(ChartID(), objName, OBJPROP_CHART_SCALE, m_chart_scale); createdCount++; } PrintFormat("CMT: created %d / %d mini-chart objects.", createdCount, m_count); CreateToggleButton(); if(!m_visible) SetTilesVisible(false); ChartRedraw(); return(true); }
1.6. レイアウトの更新
UpdateLayoutは、チャートの形状や表示状態が変化した際に、タイルの位置とサイズを再計算します。まず非表示の場合は、タイルを折りたたみ、トグルボタンをオフにします。表示状態の場合は、チャート幅から列数を再計算し、上部予約領域に侵入しないよう行数を制限し、各OBJ_CHARTに対してOBJPROP_XDISTANCE、OBJPROP_YDISTANCE、サイズ、チャートスケールを更新します。最後に、トグルボタンをONに設定し、ChartRedraw()を呼び出してUIを再描画します。このメソッドは、チャートサイズが変更された場合や、EAがレイアウト更新を必要とする場合に呼び出されることを想定しています。
//+------------------------------------------------------------------+ //| UpdateLayout - reposition tiles (respects m_visible and top reserved)| //+------------------------------------------------------------------+ void CChartMiniTiles::UpdateLayout(void) { if(!m_visible) { CollapseAll(); if(ObjectFind(ChartID(), m_tog_name) >= 0) { ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_XDISTANCE, m_tog_x); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_YDISTANCE, m_tog_y); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_STATE, 0); ObjectSetString(ChartID(), m_tog_name, OBJPROP_TEXT, "Tiles: OFF"); } ChartRedraw(); return; } if(m_count == 0) return; int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); if(chartW <= 0) chartW = 800; int chartTotalHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(chartTotalHeight <= 0) chartTotalHeight = 600; int columns = MathMax(1, chartW / (m_width + m_spacing)); int base_y_from_top = ComputeBaseYFromTop(); // --- NEW: ensure rows don't surpass top-reserved area int rows = (m_count + columns - 1) / columns; int availableAbove = MathMax(0, base_y_from_top - m_top_reserved); int maxRowsAllowed = 1 + (availableAbove / (m_height + m_spacing)); if(maxRowsAllowed < 1) maxRowsAllowed = 1; if(rows > maxRowsAllowed) { columns = (m_count + maxRowsAllowed - 1) / maxRowsAllowed; if(columns < 1) columns = 1; rows = (m_count + columns - 1) / columns; } // --- for(int i = 0; i < m_count; i++) { string name = m_object_names[i]; if(StringLen(name) == 0) continue; if(ObjectFind(ChartID(), name) == -1) continue; int col = i % columns; int row = i / columns; int xdist = m_xoffset + col * (m_width + m_spacing); int ydist = base_y_from_top - row * (m_height + m_spacing); if(ydist < 0) ydist = 0; ObjectSetInteger(ChartID(), name, OBJPROP_XDISTANCE, xdist); ObjectSetInteger(ChartID(), name, OBJPROP_YDISTANCE, ydist); ObjectSetInteger(ChartID(), name, OBJPROP_XSIZE, m_width); ObjectSetInteger(ChartID(), name, OBJPROP_YSIZE, m_height); ObjectSetInteger(ChartID(), name, OBJPROP_CHART_SCALE, m_chart_scale); } if(ObjectFind(ChartID(), m_tog_name) >= 0) { ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_XDISTANCE, m_tog_x); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_YDISTANCE, m_tog_y); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_STATE, 1); ObjectSetString(ChartID(), m_tog_name, OBJPROP_TEXT, "Tiles: ON"); } ChartRedraw(); }
1.7. 表示と切り替え
この小さなメソッド群は、タイルの表示状態を制御します。SetTilesVisibleはm_visibleフラグを更新し、新しい状態に応じてタイルを再配置するか折りたたみます。また、トグルボタンの状態と表示テキストも更新します。Toggleは表示状態を反転させ、SetTilesVisibleを呼び出す便利なラッパーです。SetToggleButtonPosはトグルボタンの位置を動的に変更でき、既存のボタンがある場合は OBJPROP_XDISTANCEとOBJPROP_YDISTANCEを更新します。これらのメソッドは、プログラムまたはUIからのタイルの表示切り替えと位置変更の入口となります。
//+------------------------------------------------------------------+ //| Set visibility programmatically | //+------------------------------------------------------------------+ void CChartMiniTiles::SetTilesVisible(const bool visible) { m_visible = visible; if(m_count == 0) { if(ObjectFind(ChartID(), m_tog_name) >= 0) { ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_STATE, m_visible ? 1 : 0); ObjectSetString(ChartID(), m_tog_name, OBJPROP_TEXT, m_visible ? "Tiles: ON" : "Tiles: OFF"); ChartRedraw(); } return; } if(m_visible) UpdateLayout(); else { CollapseAll(); ChartRedraw(); } if(ObjectFind(ChartID(), m_tog_name) >= 0) { ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_STATE, m_visible ? 1 : 0); ObjectSetString(ChartID(), m_tog_name, OBJPROP_TEXT, m_visible ? "Tiles: ON" : "Tiles: OFF"); } } //+------------------------------------------------------------------+ //| Toggle | //+------------------------------------------------------------------+ void CChartMiniTiles::Toggle(void) { SetTilesVisible(!m_visible); } //+------------------------------------------------------------------+ //| Set toggle position | //+------------------------------------------------------------------+ void CChartMiniTiles::SetToggleButtonPos(const int x,const int y) { m_tog_x = x; m_tog_y = y; if(ObjectFind(ChartID(), m_tog_name) >= 0) { ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_XDISTANCE, m_tog_x); ObjectSetInteger(ChartID(), m_tog_name, OBJPROP_YDISTANCE, m_tog_y); } }
1.8. クリーンアップとイベント処理
最後に、Deleteは徹底的なクリーンアップをおこないます。オブジェクト名配列を順に処理し、作成した各OBJ_CHARTを削除します。その後、トグルボタンを削除し、ArrayFreeで配列を解放、m_countをリセットし、チャートを再描画します。HandleEventはEAのOnChartEventから呼び出すことを想定したイベント転送メソッドで、CHARTEVENT_OBJECT_CLICKとトグルボタン名でフィルタリングします。トグルがクリックされた場合はToggle()を呼び出し、イベントを処理した場合はtrueを返します。これにより、EAへの統合はシンプルになり、イベントを転送し、OnInit/OnDeinitでInit/Deleteを呼び出すだけで済みます。
//+------------------------------------------------------------------+ //| Delete all objects | //+------------------------------------------------------------------+ void CChartMiniTiles::Delete(void) { for(int i = 0; i < ArraySize(m_object_names); i++) { string name = m_object_names[i]; if(StringLen(name) == 0) continue; if(ObjectFind(ChartID(), name) >= 0) ObjectDelete(ChartID(), name); } DeleteToggleButton(); ArrayFree(m_object_names); ArrayFree(m_symbols); m_count = 0; ChartRedraw(); } //+------------------------------------------------------------------+ //| HandleEvent - forward OnChartEvent to class (returns true if handled)| //+------------------------------------------------------------------+ bool CChartMiniTiles::HandleEvent(const int id,const string sparam) { if(id != CHARTEVENT_OBJECT_CLICK) return(false); if(StringLen(sparam) == 0) return(false); if(sparam == m_tog_name) { Toggle(); return(true); } return(false); } //+------------------------------------------------------------------+ #endif // __CHART_MINI_TILES_CLASS_MQH__
このヘッダの完全なソースコードは、ディスカッションの最後に、これまでに参照した他のファイルとともに掲載しています。
2.0 ヘッダをテストする例示用EA(MiniChartsEA)
2.1. EAの概要と目的
このEAは、CChartMiniTilesクラスのテスト用の環境です。クラスをインスタンス化し、複数の通貨ペアのミニチャートを初期化したうえで、トグル表示、サイズ変更、更新機能が正しく動作するかを検証し、より大規模なプロジェクトへの統合に備えます。
//+------------------------------------------------------------------+ //| MiniChartsEA.mq5| //| Dummy EA to test CChartMiniTiles (ChartMiniTilesClass) | //+------------------------------------------------------------------+ #property copyright "2025" #property link "https://www.mql5.com/ja/users/billionaire2024/seller" #property version "1.00" #property description "Mini-charts EA using CChartMiniTiles class" // --- Include the class header #include <ChartMiniTiles.mqh>
2.2. 入力とグローバル変数
このセクションでは、ミニチャートを制御するパラメータと状態変数(シンボルリスト、ディメンション、更新動作)を定義します。
//--- Inputs input string MajorSymbols = "EURUSD,GBPUSD,USDJPY,USDCHF,USDCAD,AUDUSD,NZDUSD"; input int BarsWidth = 20; // bars used to estimate tile pixel width input int TileHeightPx = 112; // tile height in pixels input int HorizontalSpacing = 6; // spacing between tiles input int UpdateInterval = 1000; // ms timer update interval input int XOffset = 10; // left margin in pixels input int BottomOffset = 40; // distance from bottom in pixels input int ToggleButtonX = 8; // toggle button X input int ToggleButtonY = 6; // toggle button Y input bool DateScale = false;// show date scale input bool PriceScale = false;// show price scale input int ChartScale = 2; // chart zoom level //--- object instance of our class CChartMiniTiles tiles; //--- internal state int pixelWidth = 120;
2.3. ヘルパーメソッド
このメソッドは、現在表示されているバー数に基づき、タイルの幅をピクセル単位で動的に算出します。
//+------------------------------------------------------------------+ //| Helper: estimate pixel width from BarsWidth | //+------------------------------------------------------------------+ int CalculateChartWidthFromBars(int barsWidth) { int mainChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int visibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); if(visibleBars <= 0 || mainChartWidth <= 0) return MathMax(80, BarsWidth * 6); // fallback return MathMax(80, barsWidth * mainChartWidth / visibleBars); }
2.4. トグルボタンコントロール
トグルボタンの処理はCChartMiniTilesクラスが担当します。EA自体は、トグルボタンの初期位置のみを設定します。
// Inside OnInit we call:
tiles.SetToggleButtonPos(ToggleButtonX, ToggleButtonY);
2.5. ミニチャートの初期化
この段階では、ユーザー定義の設定を使用してすべてのミニチャートを作成します。
//+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { // compute pixel width from BarsWidth heuristic pixelWidth = CalculateChartWidthFromBars(BarsWidth); // set toggle button position tiles.SetToggleButtonPos(ToggleButtonX, ToggleButtonY); // initialize tiles bool ok = tiles.Init(MajorSymbols, pixelWidth, TileHeightPx, XOffset, BottomOffset, HorizontalSpacing, PERIOD_M1, DateScale, PriceScale, ChartScale); if(!ok) { Print("MiniChartsEA: tiles.Init() failed. Check Experts log for symbol issues."); return(INIT_FAILED); } // start timer for adaptive updates EventSetMillisecondTimer(UpdateInterval); Print("MiniChartsEA initialized."); return(INIT_SUCCEEDED); }
2.6. レイアウトの更新
UpdateLayoutにより、チャートが変化したり時間が経過した場合でも、タイルが正しくリサイズおよび再配置されることが保証されます。
//+------------------------------------------------------------------+ //| Timer: update layout | //+------------------------------------------------------------------+ void OnTimer() { tiles.UpdateLayout(); }
2.7. 表示と切り替え
ここでは、EA がトグルボタンクリックをクラスに委譲し、チャートの変化を監視してすべての表示を整列させます。
//+------------------------------------------------------------------+ //| Chart events - forward to tiles | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Let tiles handle toggle button clicks if(tiles.HandleEvent(id, sparam)) return; // If chart resized or layout changed, reflow tiles if(id == CHARTEVENT_CHART_CHANGE) tiles.UpdateLayout(); }
2.8. クリーンアップとイベント処理
EA が停止したときにオブジェクトが確実に削除されるようにします。
//+------------------------------------------------------------------+ //| Deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); tiles.Delete(); Print("MiniChartsEA deinitialized."); }
3.0 初期テスト
この段階では、例示用EAをチャートに追加し、その結果を確認します。テストでは、選択した主要通貨ペアのタイルがチャート上に表示され、各タイルはブローカーのシンボル形式(.0接尾辞付き)に合わせて調整されていました。トグルボタンも問題なく動作し、タイルの表示/非表示を簡単に切り替えることができました。この機能により、必要に応じてメインチャートを完全に表示できる柔軟性がトレーダーに提供されます。シンプルながら非常に便利な操作です。
この機能は、クラスがNews Headline EAに統合され、複数のコンポーネントがより複雑なチャート環境を作り出す際に、さらに価値を発揮します。ミニタイルを素早く切り替えられることで、作業スペースを整理された、管理しやすい状態に保つことができます。
以下の画像は、MiniChartsEAの展開および出力例を示しています。

図1:EURUSDチャートでのMiniCharts EAのテスト結果
これで、CChartMiniTilesクラスをNews Headline EAに統合する準備が整いました。次のセクションでは、更新されたコードを詳細に確認し、その後チャート上での動作結果をレビューします。
4.0 ChartsMiniTilesのNews Headline EAへの統合
4.1. ChartMiniTilesヘッダをインクルードして、EAでクラスを利用できるようにする
クラスを利用するには、EAの冒頭付近でヘッダファイルをインクルードする必要があります。これにより、クラスの宣言と実装がEAのコンパイル単位に読み込まれ、インスタンス生成やメソッド呼び出しが可能になります。TradingButtons、Canvas、Tradeなど他のヘッダと並べて配置すると、インポートが整理され、依存関係が後続の開発者にも分かりやすくなります。ファイルがMQL5/Include/に存在しない場合はこの段階でコンパイルエラーが発生するため、これが最初におこなうべき統合ステップとなります。
#include <TradingButtons.mqh> #include <Trade\Trade.mqh> #include <Canvas\Canvas.mqh> #include <ChartMiniTiles.mqh> // <-- CTM class include (make sure this file is in MQL5/Include/)
4.2. CChartMiniTilesインスタンスを宣言し、EAのタイルマネージャを作成する
グローバルに「CChartMiniTiles tiles」を宣言することで、EA全体が単一のタイルマネージャにアクセスできるようになります。このインスタンスは、作成したオブジェクト名、シンボルリスト、表示フラグ、上部予約領域の高さといった状態を保持し、初期化、レイアウト更新、イベント処理、クリーンアップ用のメソッドを提供します。他のグローバル変数と並べて宣言することで、OnInit / OnTimer / OnChartEvent / OnDeinit内でライフサイクルを管理しやすくなります。
//+------------------------------------------------------------------+ //| ChartMiniTiles instance (CTM) | //+------------------------------------------------------------------+ CChartMiniTiles tiles; // class instance for mini tiles
4.3. シンボルリストを構築し、タイルのピクセル幅を推定する - CMT入力を準備する
ttiles.Initを呼び出す前に、カンマ区切りのシンボル文字列と、タイルに使用する適切なピクセル幅を用意する必要があります。このセクションでは、majorPairs[]配列からJoinSymbolArray()を使ってctmSymbolsを構築し、メインチャートの幅と可視バー数に応じてピクセル幅へ換算する簡易ロジックでpixelWidthを算出します。これらを初期化段階で準備しておくことで、タイルサイズが現在のチャートレイアウトに適応し、ブローカー固有の銘柄名はクラス内部のFindBrokerSymbolによって正しく解決されます。
// Build a comma-separated symbol list from majorPairs[] and initialize CTM. string ctmSymbols = JoinSymbolArray(majorPairs); // Estimate pixel width from Bars heuristic (simple fallback) int mainChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int visibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); int pixelWidth = 120; if(visibleBars > 0 && mainChartWidth > 0) pixelWidth = MathMax(80, CTM_TileWidthBars * mainChartWidth / visibleBars);
4.4. トグルボタンを配置して位置を設定し、UIにアクセスできる状態を維持する
トグルボタンは、タイルの表示/非表示を切り替えるためのユーザー向け操作要素です。これを取引パネルのすぐ下に配置することで、重要なUI操作領域と重ならないようにしています。tradingPanelBottomYを算出した後、EAはtiles.SetToggleButtonPos(toggleX, toggleY)を呼び出し、ボタンをどこに配置するかをクラスへ指示します。この配置処理はタイル初期化より前におこなわれ、続く予約領域ロジックと適切に整合する位置にボタンが確保されます。
// Place the CTM toggle button JUST BELOW the trading panel bottom int toggleX = 8; int toggleY = tradingPanelBottomY + 6; // +6 px margin so it doesn't touch trading controls tiles.SetToggleButtonPos(toggleX, toggleY);
4.5. 取引コントロール用に上部のUIスペースを予約して、重複を防止する
ミニタイルが取引パネルやトグルボタンと重ならないようにするため、EAはtopReserveを算出し、tiles.SetTopReservedHeight(topReserve)を呼び出します。tilesクラスはこの値を用いて、タイルが下側から積み上がる際の行数を制限し、上部領域をボタンやキャンバスなどのUI要素用に確保します。Initを呼ぶ前に上部領域を予約しておくことで、レイアウト計算が初回から正しくそれを反映するようになります。
// Reserve the area above tiles so the trading UI remains free. We reserve up to the toggle bottom. int topReserve = toggleY + CMT_TOG_H + 4; // leave a few px extra tiles.SetTopReservedHeight(topReserve);
4.6. タイルを初期化する - 各シンボルのOBJ_CHARTオブジェクトを作成する
これが統合処理の中核となる呼び出しです。EAは、解決済みのシンボル一覧、計算したピクセル幅、タイルの高さ、オフセット、間隔、チャートの時間軸、スケールといった値をtiles.Init(...)に渡します。クラス側では、ブローカー固有のシンボル名を解決し、OBJ_CHARTオブジェクトを生成して、シンボル、時間足、スケール、サイズなどのプロパティを設定し、さらにトグルボタンも作成します。EAは返り値(ctm_ok)をチェックし、(たとえばそのブローカーでシンボルが1つも解決できなかった場合など)初期化が失敗した際に適切に対応できるようにします。
// Initialize tiles: (symbols, widthPx, heightPx, xOffset, bottomOffset, spacing, period, dateScale, priceScale, chartScale) bool ctm_ok = tiles.Init(ctmSymbols, pixelWidth, CTM_TileHeightPx, CTM_XOffset, CTM_BottomOffset, CTM_Spacing, PERIOD_M1, false, false, CTM_ChartScale); if(!ctm_ok) { Print("CTM: initialization failed (no matching symbols?); tiles disabled."); }
4.7. レイアウトの応答性を維持する - チャートが変更されたときやタイマーでUpdateLayoutを呼び出す
初期化が完了したら、EAはチャートサイズやレイアウトの変化に合わせてタイルの位置を常に正しく保つ必要があります。統合処理では次の3点をカバーします。
- チャート変更イベントを転送し、タイルを再配置できるようにする(ユーザーのチャートリサイズやワークスペース変更に対応するため)。
- OnTimer内で定期的にtiles.UpdateLayout()を呼び出し、動的なUI変化や外部要因によるずれを補正する。
- OnTimerのメイン処理ループの最後でもtiles.UpdateLayout()を呼び、他のUI更新(キャンバス、ニュース、AIインサイト等)の後に必ずCTMが整合するようにする。
以下は、これらを実現するためにEA内で使用している具体的なコードスニペットです。
OnChartEventの転送とチャート変更への対応:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Let CTM handle object clicks first (toggle button) if(tiles.HandleEvent(id, sparam)) return; // Forward to the TradingButtons header afterward buttonsEA.HandleChartEvent(id, sparam, majorPairs, pairSelected); // Also respond to chart change events for CTM layout if(id == CHARTEVENT_CHART_CHANGE) tiles.UpdateLayout(); }
OnTimer終了時の更新(タイマーの刻みごとにCTMを同期させる):
// Keep CTM updated every tick (timer-driven)
tiles.UpdateLayout();
}
4.8. 初期化時のクリーンアップ - タイルとトグルボタンを削除する
EAの停止時やチャートから削除される際には、EAが作成したオブジェクトを必ず後処理として消去する必要があります。tiles.Delete()を呼び出すことで、作成されたすべてのOBJ_CHARTとトグルボタンが削除され、内部配列も解放され、チャートが再描画されます。これが最終的な統合ステップとなり、チャート上にオブジェクトが残存する問題を防ぎます。
// delete CTM tiles and toggle button
tiles.Delete();クイック統合チェックリスト(要約)
- EA冒頭で「#include <ChartMiniTiles.mqh>」を追加する。
- グローバル領域で「CChartMiniTiles tiles;」を宣言する。
- Initを呼ぶ前にctmSymbolsの構築とpixelWidthの算出をおこなう。
- tiles.SetToggleButtonPos(...)でトグルボタンの位置を指定する。
- tiles.SetTopReservedHeight(...)で上部UIの予約領域を設定する。
- tiles.Init(...)を実行し、戻り値で初期化成功を確認する。
- OnChartEventをtiles.HandleEvent(...)に転送し、チャート変更時および定期更新でtiles.UpdateLayout()を呼び出す。
- OnDeinitでtiles.Delete()を呼び出す。
テストと結果
このセクションは、統合後のNews Headline EAに対する最終テスト段階を示します。MetaEditor 5でコンパイルをおこない、エラーがないことを確認したうえで、MetaTrader 5 のチャートに適用し、その動作を検証します。以下のスクリーンショットは、筆者のテスト結果を示したものです。

図2:AUDUSDチャート上にNews Headline EAを展開し、複数通貨ペア用のミニチャートタイルを表示した様子

図3:News Headline EA内で動作するChartMiniTiles
上記の結果をご覧いただくと、当初の課題に対して本ソリューションを適切に統合できたことがわかります。これにより、トレーダーは単一チャート内で複数の通貨ペアを同時に確認でき、各種コントロールボタンも期待どおりに動作します。さらに、適切なレイアウト管理とMQL5言語の機能により、News Headline EAの既存機能と干渉することなく新機能を実装できた点も大きな成果です。次のセクションでは、本記事で達成した主要ポイントをまとめて結論とします。
結論
本記事では、News Headline EAを用いて、単一のメインチャート上で複数のミニチャートビューを管理するクラスを無事に統合できました。この手法により、トレーダーは複数通貨ペアを同時に監視し取引できる強力なツールを手に入れ、特に主要ニュース発表時などの高ボラティリティ環境では、取引効率が向上します。異なる通貨ペアで複数ポジションを保有する場合でも、このツールを用いることで単一のインターフェースから管理し追跡でき、取引操作と実行を迅速におこなえます。
教育的観点からも、本記事ではカスタムクラス開発、複数通貨ペア取引戦略、高速な注文実行、通貨ペア選択といった知識を、現在のチャートから離れることなく学べます。
今回の実装によりマルチペア取引の実現可能性は示されましたが、改善の余地はまだ大きく残っています。特に、選択した通貨ペアをより柔軟に分析する手法を洗練させることが重要です。さらに開発を進めることで、現行システムを補完し強化する自動分析機能の組み込みも期待できます。今後の公開では、これらの拡張機能についても紹介する予定です。
皆様からのアイデアやご意見もぜひお寄せください。下記には、今回のソースコードと、メインチャートとChartMiniTilesの配置例を示す画像を添付しています。
重要な学び
| 重要な学び | 説明 |
|---|---|
| 複数通貨ペア管理 | トレーダーはチャートを切り替えることなく、複数通貨ペアを効率的に選択し監視できます。 |
| 専用マルチチャートクラス | 独立したクラスで複数のミニチャートを管理することで、統合を容易にし、コードのモジュール性を維持できます。 |
| 統合前の単独テスト | 機能をテストEAで独立して開発し検証することで、メインEAに統合する前に安定性を確保できます。 |
| カスタマイズ可能なミニチャートレイアウト | ミニチャートはユーザーの好みに応じてサイズや配置を変更でき、視認性と作業効率が向上します。 |
| リアルタイムでの複数通貨ペア可視化 | 単一チャート上で複数通貨ペアを表示することで、高ボラティリティ時にも迅速に対応できます。 |
| モジュラープログラミング | 機能をヘッダやクラスに分離することで、保守性や再利用性を向上させます。 |
| 段階的な機能開発 | 新機能をステップごとに追加することで、エラーを減らし、既存システムへの統合をスムーズにします。 |
| ミニチャートでのイベント処理 | 各ミニチャートがユーザー操作に応答でき、インタラクティブで動的な分析が可能になります。 |
| メインEAとの統合 | テスト後にミニチャートクラスをメインEAに統合し、シームレスな複数通貨ペア取引機能を提供します。 |
| 再利用可能なユーティリティクラス | CChartMiniTilesのようなクラスは、他のプロジェクトやEAにも応用できるモジュールツールとして機能します。 |
| 効率的なワークフロー | プロトタイプ、テスト、統合という構造化されたワークフローにより、開発速度が向上しバグを減らせます。 |
| 迅速な意思決定支援 | ミニチャートにより、複数市場の情報を統合して確認でき、迅速な取引判断を支援します。 |
| 動的オブジェクト管理 | 各ミニチャート用にチャートオブジェクトを生成・更新・削除する方法を示しており、スケーラブルなEA設計に不可欠です。 |
| 効率的なメモリ使用 | ミニチャートを個別オブジェクトとして管理し、表示中の要素のみ更新することで、EAのメモリ・CPU使用を最適化します。 |
| シームレスなイベント伝播 | ミニチャートクラスはチャートイベントを階層的に処理する方法を示しており、イベントが正しいチャートインスタンスへ競合なく伝播するようにします。 |
添付ファイル
| ファイル名 | バージョン | 説明 |
|---|---|---|
| ChartMiniTiles.mqh | 1.0 | CChartMiniTilesクラスを定義。単一のメインチャート上で複数のミニチャートビューを管理および表示し、モジュール化された複数通貨ペア可視化とインタラクション機能を提供します。 |
| MiniChartsEA.mq5 | 1.0 | CChartMiniTilesクラスを統合前に独立してテストするために作成されたダミーEA。ミニチャートのレイアウト、リサイズ、イベント処理を検証します。 |
| NewsHeadlineEA.mq5 | 1.14 | 複数機能を統合したメインEA。ニュースレーンの可視化、カレンダーイベントに基づく自動取引、ミニチャートによる複数通貨ペア取引を提供します。 |
| TradingButtons.mqh | 1.0 | チャート上で取引実行、通貨ペア選択、注文管理をおこなうボタン操作を提供。EA内での高速な複数通貨ペア取引操作をサポートします。 |
| terminal64_Dp0JGQhX5.png | 該当なし | News Headline EAのChartMiniTiles機能を示すワイドターミナルチャートのスクリーンショット。複数通貨ペア取引、リアルタイム監視、迅速な視覚的分析を確認できます。 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19299
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
SMC (Smart Money Concepts)で取引のレベルアップを実現する:OB、BOS、FVG
プライスアクション分析ツールキットの開発(第39回):MQL5でBOSとChoCHの検出を自動化する
MQL5での取引戦略の自動化(第30回):視覚的フィードバックによるプライスアクションAB-CDハーモニックパターンの作成
平均足を使ったプロフェッショナルな取引システムの構築(第1回):カスタムインジケーターの開発
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索