English Русский
preview
初心者からエキスパートへ:市場期間同期化ツール

初心者からエキスパートへ:市場期間同期化ツール

MetaTrader 5 |
35 0
Clemence Benjamin
Clemence Benjamin

内容

  1. はじめに
  2. 実装
  3. テスト
  4. 結論
  5. 重要な学び
  6. 添付ファイル


はじめに

MetaTrader 5における標準期間可視化の限界

D1(1日足)やH4(4時間足)などの上位時間足でチャートを分析する際、トレーダーはしばしば、十字線やハンマー、陽の包み足など、重要な意味を持つ上位時間足のローソク足の形成を観察します。しかし、これらの上位時間足のローソク足は多数の下位時間足を集約したものであるため、下位時間足で発生した正確な価格の動きを視覚的に把握することは困難です。

上位時間足から下位時間足へチャートを切り替えても、上位時間足の期間の視覚的な配置は保持されません。そのため、トレーダーは各上位ローソク足内での価格変動(たとえば、モメンタムの変化や保ち合いの発生箇所)を簡単に特定できなくなります。

MetaTrader 5には期間区切り線を表示する組み込み機能がありますが、この機能は静的で柔軟性に欠け、特に1日未満の短期足を扱う場合や表示方法を細かく調整したい場合には不十分です。

Market Periods Synchronizerインジケーターの導入

この制限を解決するために、Market Periods Synchronizer(マーケット期間同期インジケーター)というカスタムソリューションを導入します。このインジケーターはMQL5で開発されており、複数の上位時間足の期間マーカーを高度にカスタマイズ可能です。ツールは下位時間足のチャート上で上位時間足の境界を視覚的に同期させ、上位時間足のコンテキストを維持しながら、期間内の詳細な価格動向を分析することができます。

このアプローチにより、ユーザーは以下のことができるようになります。

  1. 複数の上位時間足のマーカーを同時に表示し、色分けする
  2. 下位ローソク足がどのように上位ローソク足の構造を形成するかを検証する
  3. マーカーの間隔、表示/非表示、色の設定をカスタマイズする
  4. 上位ローソク足の実体をハイライトして塗りつぶす
  5. 各上位期間の始値と終値の水平線を描画する
  6. 描画対象を表示中のチャート範囲に限定し、パフォーマンスを最適化する
  7. 上位マーカーを妨げない補助時間足マーカーを使用する

下図は、MetaTrader 5の標準表示の例で、Market Periods Synchronizerが補完するギャップを示しています。

MetaTrader 5における標準期間区切り線の設定

図1:MetaTrader 5における標準期間区切り線の設定

実装段階では、視覚的な明瞭さと分析の深さの両方を重視して、Market Periods Synchronizerインジケーターを設計しました。このツールにより、M1やM5ローソク足がH1やD1ローソク足内でどのように形成されるかといった、ローソク足内の価格変化を視覚的に把握できます。これにより、モメンタムの変化や拒否反応、上位時間足パターンを構成する微細構造を確認することが可能です。さらに、上位ローソク足の実体を超えるヒゲの動きも観察でき、ボラティリティや価格拒否の挙動をより深く理解できます。

インジケーターは、下位時間足のチャート上に主要および任意の補助時間足の境界を表示し、それぞれ独立した色とラインスタイルで明確に区別します。表示する時間足の選択、過去ローソク足の参照範囲(ルックバック)、個別シリーズの表示/非表示、色や線幅、ラインスタイルのカスタマイズが可能です。

さらに、各上位ローソク足の価格帯(陽線、陰線、中立)を塗りつぶしたり、始値と終値に短い水平線を描画したりすることで、期間内の価格反応やピボットを分析しやすくしています。パフォーマンス向上のため、描画は表示中のチャート範囲内のマーカーのみをレンダリングする仕様で、M1やM5チャートの大きなルックバック時でもスムーズに動作します。補助時間足マーカーは、主要時間足の境界の間にのみ配置され、主要マーカーの上に重ならないよう設計されています。


実装

このインジケーターは、最小限のダミープロットバッファを使用して(MetaTrader 5の警告を抑えるため)、すべての視覚要素をチャートオブジェクト(OBJ_VLINE、OBJ_TEXTなど)で表示します。タイマーによってオブジェクトが定期的に更新されるため、下位時間足のチャートにアタッチされてもインジケーターが適応します。コアの処理はRefreshLines()で、上位時間足のローソク足時間をコピーし、それに応じてオブジェクトを作成または削除します。ヘルパーはオブジェクトのクリーンアップを管理します。

1. ファイルヘッダとダミープロット(メタデータ) 

インジケーターのメタデータとダミープロットバッファを宣言します。MQL5では、インジケーターは必ずOnCalculate()を実装する必要があります。通常、ターミナルに描画するためのプロットバッファを作成します。本ツールは価格系列データを描画するのではなく、チャートオブジェクト(垂直線、長方形、テキスト)を描画します。しかし、コンパイラやターミナルは少なくとも1つのバッファ/プロット定義を期待します。一般的な方法としては、単一の「ダミー」バッファを宣言し、PLOT_EMPTY_VALUEをEMPTY_VALUEに設定し、意味のある値を代入しないようにします。こうすることで、インジケーターはターミナル上で有効として認識されますが、実際には目に見えるバッファプロットは生成されません。このパターンにより、警告を防ぎつつ、視覚要素を完全にチャートオブジェクトで管理できます。

MQL5の注意点

  • #property indicator_chart_windowは、MetaTrader 5にインジケーターをメインチャートにアタッチするよう指示します(これによりオブジェクトが価格軸に沿って配置されます)。
  • ダミーバッファはシンプルに保ち、SetIndexBufferを呼び出す際にはINDICATOR_DATAを指定します。
  • バッファを価格系列のようにインデックスする場合(0 = 最新ローソク足)、OnCalculate()内で必ずArraySetAsSeries()を呼び出します。これは後ほど示します。

//+------------------------------------------------------------------+
//| MarketPeriodsSynchronizer.mq5                                    |
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property version   "1.01"
#property indicator_chart_window

// Dummy buffer to satisfy MT5 (we draw with chart objects)
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_label1  "HiddenDummy"
#property indicator_type1   DRAW_LINE
#property indicator_width1  1
#property indicator_color1  clrSilver

double PlotDummyBuffer[];

2. 入力パラメータ 

入力パラメータは、インジケーターの公開APIの役割を果たします。入力変数を宣言すると、MetaTraderはインジケーターのダイアログにUIフィールドを作成し、実行時には読み取り専用にします。コード上で入力変数は定数として扱われるため、内部で調整が必要な値は変更可能なグローバル変数(例:g_lookback)にコピーします。この分離により、入力値を誤って変更してしまうことを防ぎつつ、実行時変数は最適化などで柔軟に扱うことができます。

各入力タイプの重要なポイント

  • ENUM_TIMEFRAMES:M15やH1などの時間足を表す便利な型付き定数です。整数としてコンパイルされますが、可読性が高くなります。
  • 色:MQL5では、名前付きカラー定数(例:clrRed)や整数のARGB値を使用できます。名前付きカラーは可読性が高く、安全です。
  • 線のスタイル:STYLE_SOLIDやSTYLE_DASHなどのスタイル定数は整数です。入力変数はintとして宣言すると、異なるターミナル環境でも安定した動作が期待できます。
  • パフォーマンス関連の入力:InpLookbackはコピーする上位時間足のローソク足の数を制御します。M1/M5のチャートで大きな値を指定すると計算負荷が高くなる可能性があるので、保守的なデフォルト値(200)を設定し、必要に応じて可視範囲に応じた最適化を追加するとよいです。
  • ブール値:フィル、補助ライン、始値/終値ラインなどのオプション機能を有効/無効にできます。これにより、ユーザーは描画の見栄えとパフォーマンスをトレードオフできます。

//--- inputs (Major)
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_H1;   // Major higher timeframe to mark
input int            InpLookback   = 200;        // How many higher-TF bars to draw
input color          InpColorMajor = clrRed;     // Major line color
input int            InpWidthMajor = 2;          // Major line width
input int            InpRefreshSec = 5;          // Refresh interval in seconds

//--- inputs (Open/Close horizontals for Major)
input bool           InpShowOpenClose = true;    // Show open/close horizontal markers?
input color          InpColorOpen    = clrGreen; // Open line color
input color          InpColorClose   = clrLime;  // Close line color
input int            InpWidthOC      = 1;        // Open/Close line width
input int            InpStyleOC      = STYLE_DASH;// Open/Close line style (integer)
input int            InpHorizOffsetBars = 3;     // Horizontal length in current TF bars

//--- inputs (Body fill for Major)
input bool           InpShowFill     = true;     // Show body fill?
input color          InpFillBull     = clrLime// Bullish fill color
input color          InpFillBear     = clrTomato; // Bearish fill color

//--- inputs (Minor 1)
input bool           InpShowMinor1 = false;      // Show intermediate Minor 1?
input ENUM_TIMEFRAMES InpMinor1TF  = PERIOD_M30; // Minor1 TF (default M30)
input color          InpColorMin1  = clrOrange;  // Minor1 color
input int            InpWidthMin1  = 1;          // Minor1 width

//--- inputs (Minor 2)
input bool           InpShowMinor2 = false;      // Show intermediate Minor 2?
input ENUM_TIMEFRAMES InpMinor2TF  = PERIOD_M15; // Minor2 TF (default M15)
input color          InpColorMin2  = clrYellow;  // Minor2 color
input int            InpWidthMin2  = 1;          // Minor2 width

3. バッファ、変更可能コピー、命名規則

適切な命名と状態管理により、コードは保守しやすく、堅牢になります。いくつかの原則は以下の通りです。

  • ダミーバッファ:以前に宣言したPlotDummyBuffer[]は何も描画しません(EMPTY_VALUEで埋めます)。
  • 変更可能なコピー:g_lookbackはInpLookbackを反映していますが、内部で調整可能です(例:上限を設けるなど)。
  • オブジェクトの接頭辞:一貫した接頭辞(例:HTF_MAJ_、HTF_MIN1_)を使用することで、他のチャートオブジェクトとの名前衝突を防ぎ、クリーンアップも容易になります(削除時は「HTF_」で検索します)。
  • 配列の有効期間:配列をコピーする前にArrayFree()を使用して配列を空にしてください。インデックスで値を書き込みたい場合はArrayResize()でサイズを調整します。ArraySetAsSeries()はインデックスの順序に影響します(0 = 最新ローソク足)。配列を読み書きする際は向きに注意して明示してください。

//--- indicator buffer (dummy)
double PlotDummyBuffer[];

// mutable working copy of input(s)
int g_lookback = 200;

// name prefixes
static string PREFIX_MAJ  = "HTF_MAJ_";
static string PREFIX_MIN1 = "HTF_MIN1_";
static string PREFIX_MIN2 = "HTF_MIN2_";

4. 初期化(OnInit):ライフサイクルの仕組みと安全性

OnInit()は、次の処理をおこなう場所です。バッファのバインド、PLOT_EMPTY_VALUEの設定、入力値のコピーや検証、タイマーの設定、そして最初の描画です。重要なポイントと注意点は以下の通りです。

  • SetIndexBuffer(0, PlotDummyBuffer, INDICATOR_DATA)により、インデックス0にバッファをバインドします。INDICATOR_DATAはインジケーターバッファの共通フラグです。
  • PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE)により、ターミナルにEMPTY_VALUEが「プロットなし」を意味することを伝えます。バッファにEMPTY_VALUEを書き込むことで、プロットは目に見えません。
  • EventSetTimer(MathMax(1, InpRefreshSec))により、OnTimer()への定期的なコールバックを登録します。CPUに負荷をかけないよう、最小でも1秒以上の値を選びます。なお、OnDeinit()では必ずEventKillTimer()を呼んでタイマーを解除してください。
  • 初回のRefreshLines()を呼び出して、初期化直後にチャートを同期させます。

int OnInit()
{
   SetIndexBuffer(0, PlotDummyBuffer, INDICATOR_DATA);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   // copy input to mutable
   g_lookback = (InpLookback <= 0 ? 200 : InpLookback);

   // set timer (minimum 1s)
   EventSetTimer(MathMax(1, InpRefreshSec));

   // initial draw
   RefreshLines();

   return(INIT_SUCCEEDED);
}

5. 初期化解除とタイマー:安全な終了と制御された更新

OnDeinit()では、タイマーを適切に解除してください。そうしないと、インジケーターが削除された後も不要なタイマーイベントが残り、OnTimer()が呼ばれ続けてしまいます。OnTimer()は軽量に保つことが重要です。基本的にはRefreshLines()を呼び出して、オブジェクトを最新の状態に保つだけで十分です。後で重い処理(ファイルI/Oやネットワーク通信など)を追加する場合は、必ず条件チェックでラップし、OnTimer()が負荷の高い処理で重くならないようにしてください。

void OnDeinit(const int reason)
{
   EventKillTimer();
   // Optionally remove objects:
   // DeleteAllHTFLines();
}

void OnTimer()
{
   RefreshLines();
}

6. 最小限のOnCalculate:インジケーターモデルへの準拠とバッファの向き

MetaTrader 5では、すべてのインジケーターにOnCalculate()の実装が求められます。描画にバッファを使用しない場合でも、必ず実装する必要があります。読者向けに重要なMQL5の概念は以下の通りです。

  • ArraySetAsSeries(array, true)により、インデックス0が最新ローソク足となる系列の向きを設定します。ローソク足シフト(例:shift=0は現在のローソク足)で配列を参照する場合に非常に重要です。
  • prev_calculatedは前回計算されたローソク足の数を示します。これを使うことで、初期化処理を一度だけおこなったり、追加されたローソク足にEMPTY_VALUEを設定してダミープロットを静かに保つことができます。
  • OnCalculate()の最後では、必ずrates_total(処理したローソク足の数)を返します。MetaTrader 5はこの戻り値を使って、次回呼び出し時のprev_calculatedを設定します。

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   // make the dummy buffer a series (0 = newest)
   ArraySetAsSeries(PlotDummyBuffer, true);

   // initialize to EMPTY_VALUE so no visible plot appears
   if(prev_calculated < 1) ArrayInitialize(PlotDummyBuffer, EMPTY_VALUE);
   else
   {
      int to_fill = rates_total - prev_calculated;
      if(to_fill > 0)
         for(int i = 0; i < to_fill && i < rates_total; ++i)
            PlotDummyBuffer[i] = EMPTY_VALUE;
   }

   return(rates_total);
}

7. コア処理:RefreshLines()と可視範囲最適化

この関数はインジケーターで最も重要な部分です。ここでは処理手順、MQL5での典型的な注意点、そして可視範囲だけにマーカーを作成する最適化済みの実装について説明します。これは、M1/M5などの下位時間足で大きなルックバックを使う場合に大幅なパフォーマンス向上につながります。

なぜ可視範囲の最適化が必要か

長い上位時間足(HTF)の履歴をコピーし、下位時間足で何百、何千ものチャートオブジェクトを作成すると、計算コストが高くなります。ユーザーがチャートの一部分しか見ていない場合は、表示範囲に含まれるオブジェクトだけを作成すべきです。MetaTrader 5では、ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR)(最初の表示ローソク足のインデックス)とChartGetInteger(0, CHART_WIDTH_IN_BARS)(表示ローソク足の数)が提供されています。これらを使用すると、現在のチャートの時間足でtime_fromとtime_toを計算し、HTFタイムスタンプをその間隔にフィルター処理できます。

重要なMQL5関数

  • CopyTime(symbol, timeframe, start_shift, count, array):指定した時間足のタイムスタンプをコピーします。返り値はコピーした要素数です。注意点として、結果は最新ローソク足から順(shift=0が最新ローソク足)で返されるため、範囲比較をおこなう場合は古い順に反転することが多いです。
  • CopyOpen、CopyClose:CopyTimeと同様ですが、始値や終値をコピーします。
  • PeriodSeconds(tf):指定した時間足の秒数を返します。期間の終了時刻を計算する際に便利です。
  • ObjectCreate(chart_id, name, type, sub_window, time1, price1, time2, price2):オブジェクトの種類に応じてパラメータが異なります。
      • OBJ_VLINE:time1とpriceを指定できますが、実際にはtimeが重要です。
      • OBJ_RECTANGLE:time1、price1、time2、price2が必要です。low/high/open/closeを適切に使用します。
      • OBJ_TREND:2点(time1, price1, time2, price2)が必要です。

エラー処理

  • ObjectCreate()の返り値を常に確認し、作成に失敗した場合はGetLastError()を呼び出してログや診断をおこないます。
  • オブジェクトを作成する前にObjectFind()を使用し、存在確認をおこないます(存在すればインデックス、存在しなければ-1)。これにより重複作成を防げます。

可視範囲最適化済みの実装

  • CHART_FIRST_VISIBLE_BARとCHART_WIDTH_IN_BARSを取得して、現在のチャート時間足でiTime()を使い、t_fromとt_toを計算します。
  • CopyTimeで上位時間足のタイムスタンプを取得しますが、g_lookbackによる上限を設定します。必要に応じて、可視範囲に合わせてg_lookbackを減らすことも可能です。
  • HTFタイムスタンプをフィルタリングし、[t_from - period_seconds, t_to + period_seconds]に含まれるものだけオブジェクトとして作成します。境界線上のオブジェクトも拾えるように、少し余裕を持たせるのがポイントです。

//--- helper: get visible chart time range (returns false if couldn't obtain)
bool GetVisibleTimeRange(datetime &time_from, datetime &time_to)
{
   // ChartGetInteger uses constants CHART_FIRST_VISIBLE_BAR and CHART_WIDTH_IN_BARS
   long first_visible = ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
   long visible_bars  = ChartGetInteger(0, CHART_WIDTH_IN_BARS);

   if(first_visible < 0 || visible_bars <= 0)
      return(false);

   // first_visible is index in the time series (0 = newest)
   // iTime(symbol,period,shift) expects shift as bar index
   time_from = iTime(_Symbol, _Period, (int)first_visible);              // time of first visible bar (leftmost)
   int last_index = (int)(first_visible + visible_bars - 1);             // index of rightmost visible bar
   time_to   = iTime(_Symbol, _Period, last_index);                      // time of last visible bar (rightmost)
   // If iTime returns 0 or invalid, fail gracefully
   if(time_from == 0 || time_to == 0) return(false);
   return(true);
}

//--- RefreshLines with visible-range filter
void RefreshLines()
{
   // determine visible chart time window (optional optimization)
   datetime vis_from = 0, vis_to = 0;
   bool have_vis = GetVisibleTimeRange(vis_from, vis_to);
   // optional: expand the visible window by one HTF period on each side
   uint64 ht_period_secs = (uint64)PeriodSeconds(InpHigherTF);
   datetime vis_from_margin = (have_vis ? vis_from - (int)ht_period_secs : 0);
   datetime vis_to_margin   = (have_vis ? vis_to + (int)ht_period_secs   : 0);

   // copy HTF times (newest-first)
   datetime major_times[];
   ArrayFree(major_times);
   int copiedMaj = CopyTime(_Symbol, InpHigherTF, 0, g_lookback, major_times);
   if(copiedMaj <= 0) return;

   // copy opens & closes (same count)
   double major_opens[], major_closes[];
   if(CopyOpen(_Symbol, InpHigherTF, 0, copiedMaj, major_opens) != copiedMaj ||
      CopyClose(_Symbol, InpHigherTF, 0, copiedMaj, major_closes) != copiedMaj)
      return;

   // reverse to ascending order (oldest-first) — easier for interval checks
   datetime sorted_times[]; ArrayResize(sorted_times, copiedMaj);
   double sorted_opens[];  ArrayResize(sorted_opens, copiedMaj);
   double sorted_closes[]; ArrayResize(sorted_closes, copiedMaj);
   for(int k = 0; k < copiedMaj; ++k)
   {
      sorted_times[k]  = major_times[copiedMaj - 1 - k];
      sorted_opens[k]  = major_opens[copiedMaj - 1 - k];
      sorted_closes[k] = major_closes[copiedMaj - 1 - k];
   }

   // Build keep-list (only include HTF entries in the visible window when available)
   string keepNames[]; ArrayResize(keepNames, 0);

   for(int i = 0; i < ArraySize(sorted_times); ++i)
   {
      datetime t = sorted_times[i];
      // If we have a visible window, skip majors outside it (margin added)
      if(have_vis && (t < vis_from_margin || t > vis_to_margin))
         continue;

      // create/vet major VLINE
      string name = PREFIX_MAJ + EnumToString(InpHigherTF) + "_" + IntegerToString((int)t);
      if(ObjectFind(0, name) == -1)
      {
         double dummy_price = 0.0;
         if(!ObjectCreate(0, name, OBJ_VLINE, 0, t, dummy_price))
            PrintFormat("Failed to create major %s error %d", name, GetLastError());
         else
         {
            ObjectSetInteger(0, name, OBJPROP_COLOR, InpColorMajor);
            ObjectSetInteger(0, name, OBJPROP_WIDTH, InpWidthMajor);
            ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
            ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, name, OBJPROP_HIDDEN, false);
         }
      }
      // push to keepNames
      int sz = ArraySize(keepNames); ArrayResize(keepNames, sz+1); keepNames[sz] = name;

      // (rest: fills, open/close trend lines, and adding their names to keepNames)
      // ... same logic as before, but guarded by have_vis if you want to only create their labels when visible.
   }

   // Cleanup: delete HTF_ objects not in keepNames[]
   int total = ObjectsTotal(0);
   for(int idx = total - 1; idx >= 0; --idx)
   {
      string oname = ObjectName(0, idx);
      if(StringFind(oname, "HTF_") != -1)
      {
         bool found = false;
         for(int k = 0; k < ArraySize(keepNames); ++k) if(oname == keepNames[k]) { found = true; break; }
         if(!found) ObjectDelete(0, oname);
      }
   }
}

8. DrawMinorsBetweenIntervals

このヘルパー関数の役割は、中間(補助)時間足のマーカーを、正確に連続する時間足の間にのみ挿入することです。実装の詳細と考慮点は以下の通りです。

  • CopyTime()を使ってすべての補助時間足のタイムスタンプを取得します(最新ローソク足から順)。間隔ロジックのため、古い順に反転させます。
  • 各補助タイムスタンプmtに対して、まずタイムスタンプと完全に一致するか確認し、一致する場合はスキップします。
  • 補助タイムスタンプmtが属する区間jを探します。条件はmajor[j] < mt < major[j+1]です。時間足は昇順なので、線形走査でも簡単かつ確実に判定できます。
  • 時間足や補助時間足の数が多い場合は、以下の方法で処理を高速化できます。
    1. 時間足が大きくソート済みであれば、二分探索(ArrayBsearch)を使用する。
    2. 時間足と補助時間足を同時に1回の走査で処理する(マージジョイン方式)ことでO(n)の計算量に抑える(通常はO(n*m)になる)。
  • クリーンアップが正しくおこなわれるよう、作成済みオブジェクトの名前リストを常に更新しておきます。

void DrawMinorsBetweenIntervals(const string prefix,
                                const ENUM_TIMEFRAMES minorTF,
                                const color c,
                                const int width,
                                const datetime &major_times[],
                                string &keepNames[])
{
   datetime minor_times[];
   int copiedMin = CopyTime(_Symbol, minorTF, 0, g_lookback, minor_times);
   if(copiedMin <= 0) return;

   // Reverse to ascending (oldest-first)
   datetime sorted_minor_times[]; ArrayResize(sorted_minor_times, copiedMin);
   for(int k = 0; k < copiedMin; ++k) sorted_minor_times[k] = minor_times[copiedMin - 1 - k];

   // Merge-like linear pass (more efficient than nested loops) — optional improvement:
   // if you expect many entries, implement two-pointer merge; below is the simpler approach.
   for(int m = 0; m < copiedMin; ++m)
   {
      datetime mt = sorted_minor_times[m];

      // skip if equals any major time (linear check)
      bool equals_major = false;
      for(int kk = 0; kk < ArraySize(major_times); ++kk)
         if(major_times[kk] == mt) { equals_major = true; break; }
      if(equals_major) continue;

      // find interval where mt belongs
      bool placed = false;
      for(int j = 0; j < ArraySize(major_times)-1; ++j)
      {
         if(major_times[j] < mt && mt < major_times[j+1])
         {
            string name = prefix + EnumToString(minorTF) + "_" + IntegerToString((int)mt);
            if(ObjectFind(0, name) == -1)
            {
               if(ObjectCreate(0, name, OBJ_VLINE, 0, mt, 0.0))
               {
                  ObjectSetInteger(0, name, OBJPROP_COLOR, c);
                  ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
                  ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
                  ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
                  ObjectSetInteger(0, name, OBJPROP_HIDDEN, false);
               }
            }
            // add to keepNames
            int sz = ArraySize(keepNames); ArrayResize(keepNames, sz+1); keepNames[sz] = name;
            placed = true;
            break;
         }
      }
      if(!placed) continue;
   }
}

9. クリーンアップとオブジェクト管理

私たちの接頭辞を持つオブジェクトだけを削除することで、ユーザーや他のインジケーターのオブジェクトを誤って削除するのを防ぎます。その他の考慮点は以下の通りです。

  • ObjectCreate()を呼ぶ前にObjectFind(0, name)を使用して、既にオブジェクトが存在するか確認すると、重複作成を防げます。
  • ObjectCreate()が失敗した場合は、GetLastError()を呼び出してデバッグに役立てます。
  • インジケーターが多くのオブジェクトを作成する場合は、接頭辞ごとにグループ化したり、作成済みオブジェクト名のカンマ区切りリストを格納した「マスターインデックスオブジェクト」(小さなテキスト)を作成して存在チェックを高速化する方法もあります。ただし、ここで示したkeepNames[]技法でも通常は十分です。

void DeleteAllHTFLines()
{
   int total = ObjectsTotal(0);
   for(int idx = total - 1; idx >= 0; --idx)
   {
      string oname = ObjectName(0, idx);
      if(StringFind(oname, "HTF_") != -1)
         ObjectDelete(0, oname);
   }
}


テスト

コンパイル後のツールのテストは簡単です。MetaTrader 5のナビゲーターで「Indicators」フォルダ内のMarket Periods Synchronizerを探し、任意のチャートにアタッチします。すべての動作はインジケーターの入力パラメータから設定可能です。たとえば、期間マーカーに使用する時間足を選択したり、ルックバックの深さを設定したり、色や線の太さを調整できます。また、オプションの中間マーカーを有効にし、それらが連続する時間足の間に正確に表示されるよう時間足を選択することも可能です。

「Market Periods Synchronizer」という名称は、このツールの目的を反映しています。つまり、上位時間足の構造を下位時間足の価格動きと視覚的にリンクさせることです。実際には、上位時間足のヒゲがどの下位時間足のローソク足から形成されたかを正確にマッピングできるため、ヒゲの形成過程(反発、下ヒゲ/上ヒゲ、ローソク足内スパイクなど)を詳細に観察できます。下記の図は、テスト時に使用した典型的な結果と入力設定の例を示しています。

入力設定

図2:Market Periods Synchronizerの入力設定

MarketPeriodsSychronizerを実行する

図3:チャートにMarket Periods Synchronizerインジケーターを追加する

ローソク足実体トレンド

図4:H1期間フィリングによるトレンド

図4では、Market Periods Synchronizer が各期間の始値と終値をラベル付けしています。これにより、より細かいスケールで形成されるトレンドを観察できます。各マーカー区間内では、下位時間足の価格動きが明確に見え、上位時間足のローソク足の範囲内でのローソク足内構造の進化が把握できます。

この手法により、トレーダーは広いトレンドのコンテキストと、それを形成する詳細なミクロな変動の両方を同時に分析できる「二重の視点」を得られます。色付きのフィルは上位時間足のローソク足を視覚的に抽象化しており、チャートを市場動向の多層的表現に変換しています。

MarketPeriodsSynchronizerの概要

図5:ローソク足パーツのラベル付け 

図5では、赤い線で囲まれた領域が単一の1時間期間を表しています。この部分は拡大されたローソク足のように機能し、上位時間足ローソク足の始値、終値、実体、上ヒゲ、下ヒゲを包括しています。囲まれた領域内では、トレーダーは従来のローソク足の分析と同じ方法で価格動きを解釈できます。要するに、各ハイライトされた期間は、上位時間足のローソク足を視覚的に再構築したものであり、下位時間足の変動によってその構造がどのように形成されたかを詳細に確認できるようになっています。



結論

私たちは、概念的なアイデアをMQL5プログラミングの力によって、完全に機能する分析ツールへと変換することに成功しました。Market Periods Synchronizerインジケーターは、チャート分析に新たな次元をもたらします。これにより、トレーダーは上位時間足と下位時間足の構造を一つのシームレスなインターフェースで同時に確認できるようになります。このインジケーターは複数の時間足にわたる市場期間を視覚的に同期させ、小さなローソク足がどのように主要な市場フェーズやトレンド構造の形成に寄与しているかを明確に把握できます。この革新は、特に上位時間足の価格変動をより深く理解したい分析者にとって、MetaTrader 5の長年の可視化機能のギャップを埋めるものとなっています。

このツールの大きな強みの一つは、上位時間足のローソク足実体の視覚化です。上位時間足のローソク足実体を異なる色で塗り分けることで、トレーダーはその期間が強気、弱気、または中立であったかを即座に判断できます。その後、その領域内で下位時間足ローソク足がどのように動いたかを詳細に観察できます。さらに、ヒゲの形成の背景も明らかになります。下位時間足のボラティリティや流動性の動き、ローソク足内の反応を観察することで、長期間の高値や安値がどのように形成されたかを理解できます。このような洞察は、リバーサルやフェイクアウト、継続パターンなどを分析する際に非常に価値があります。通常の大きなローソク足の実体だけでは見えない情報が、このツールによって明らかになります。

同様に重要なのは、このツールに組み込まれた高いカスタマイズ性と最適化機能です。ユーザーは表示する時間足を設定でき、各時間足ごとに独立したラインスタイルや色を定義でき、ルックバックの深さを指定したり、各ラインシリーズの表示/非表示を切り替えて、視覚的に整理された情報量の多いチャートレイアウトを維持できます。これらの調整可能な機能により、インジケーターは様々な取引スタイルに対応可能です。M1/M5の動きを研究するデイトレーダーから、D1やW1の構造を分析する長期トレーダーまで、それぞれのアプローチに合わせて柔軟に使用できます。こうして、Market Periods Synchronizerは、ユーザーが自分の取引スタイルや認知ワークフローに合った方法で市場を分析できる環境を提供します。

将来のMQL5開発者にとって、このプロジェクトは、抽象的な取引コンセプトを視覚的かつインタラクティブなツールへと翻訳する方法を示す実例です。イベント処理、チャートオブジェクト管理、多時間足データの同期といった実践的スキルの重要性が強調されており、MQL5言語を習得したい人にとって中心的なテーマとなります。トレーダーにとっても、単に市場の動きを理解するだけでなく、入れ子になった複数時間足の中で価格がどのように形成されるかを観察する習慣を促すツールとなります。こうしたコーディングと市場分析の交差領域での理解は、技術的な習熟と分析の深さを同時に養い、将来的な取引ツール開発の革新に向けた強固な基盤となります。

要するに、Market Periods Synchronizerは、テクニカル分析における実務的進歩であると同時に、MQL5開発者の学習プラットフォームとしても機能します。現実の取引課題に基づいた創造的な問題解決が、チャート分析の明瞭性や精度、教育的価値を高める有意義なツールにつながることを示しています。

開発プロセス全体で得られた主な学びのポイントについては下表を参照してください。また、本記事の最後に添付したソースファイルもご覧ください。



重要な学び

重要な学び 説明
1. 多時間足データの処理 下位時間足チャート上で作業しながら、上位時間足データにアクセスし、比較し、整合させる方法を学びます。本プロジェクトでは、異なる期間のデータを同期させるために、iTime()、iOpen()、iClose()関数を正しく使用する方法を示しています。
2. オブジェクト作成とチャート描画 ObjectCreate()やObjectSetInteger()を使用して、垂直線、矩形、ラベルなどのチャートオブジェクトをプログラムから作成し管理する方法を理解します。適切な命名規則とリソースのクリーンアップを重視し、チャートの混雑やメモリリークを防ぐ点が強調されています。
3. 入力パラメータ設計 時間足の選択、色、線の太さ、マーカーの表示と非表示などをユーザーが自由にカスタマイズできる柔軟な入力パラメーターの定義方法を学びます。このインジケーターは、最大限の設定自由度を実現するための入力変数の実践的な使い方を示しています。
4. アルゴリズム最適化とCPU管理 オブジェクト描画をチャートの可視範囲に制限することで、インジケーターのパフォーマンスを最適化する方法を学びます。これは、M1やM5チャートのように更新頻度が高い環境で、効率的なループ処理、条件分岐、最小限のリソース使用がいかに重要かを理解する助けとなります。
5. 視覚的同期の概念 視覚要素が、時間足間の構造的な関係をどのように伝えられるかを理解します。主期間と中間期間をマークすることで、価格の推移を直感的に把握でき、下位時間足のローソク足がどのように上位時間足の形成につながっているかを観察できます。
6. 実践的なデバッグとテスト MetaTrader 5におけるカスタムインジケーターのコンパイル、適用、テストの流れを学びます。コンパイラメッセージの読み取り方法、「wrong parameter count」のようなエラーへの対処、ライブチャート上での段階的なロジック検証について理解を深めます。
7. モジュラー化されたコード構造 初期化、計算、可視化の各処理を分離することで、読みやすく保守性の高いコードになることを体験します。これは、再利用性と拡張性を重視したMQL5のベストプラクティスに沿った設計です。
8. 分析と自動化の橋渡し 時間足の整合やプライスアクションのマッピングといった分析的概念が、どのように自動化された視覚ツールへと変換できるかを理解します。コーディングと取引ロジックの融合により、プログラミング能力と分析思考の両方が強化されます。
9. 市場のミクロ構造の理解 視覚的な同期を通じて、ヒゲの形成、モメンタムの転換、ローソク足内の反応を観察できます。これにより、ミクロな価格変動がどのようにして上位時間足の実体やヒゲを構成しているかを理解する助けとなります。
10. 新規開発者にとっての教育的価値 本プロジェクトは、アイデアの着想から問題定義、コーディング、デバッグ、公開までの完全な開発サイクルを示しています。プロフェッショナルなインジケーターを自作したいと考える、これからのMQL5開発者にとって、実践的な学習テンプレートとなります。


添付ファイル

ファイル名 バージョン 説明
MarketPeriodsSynchronizer.mq5 1.01 このインジケーターは、下位時間足の価格変動がどのように上位時間足の期間構造を形成しているかを観察するための、視覚的な同期ツールを提供します。選択した上位時間足に対応する垂直マーカーやオプションのラベルを描画し、小さな時間足チャート上で、より大きな市場期間の開始点と終了点を直接識別できるようにします。このツールは自動的に更新され、マーカーの色、線の太さ、ルックバックの深さをカスタマイズできます。開発にはCopyTime()、ObjectCreate()、タイマーイベントなどのMQL5の中核機能が使用されており、チャートの視覚化と多時間足データ処理を組み合わせる実践的な学習ケースとなっています。

目次に戻る

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19841

添付されたファイル |
機械学習の限界を克服する(第5回):時系列交差検証の簡単な概要 機械学習の限界を克服する(第5回):時系列交差検証の簡単な概要
本連載では、機械学習を活用した取引戦略を実運用に展開する際に、アルゴリズムトレーダーが直面する課題について考察します。私たちのコミュニティには、より深い技術的理解を必要とするがゆえに、見過ごされがちな課題がいくつも存在します。本日の議論は、機械学習における交差検証の盲点を検討するための足がかりとなるものです。交差検証はしばしば定型的な手順として扱われますが、不注意に実施すると、誤解を招く、あるいは最適とは言えない結果を容易に生み出してしまいます。本記事では、その隠れた盲点をより深く考察する準備として、時系列交差検証の基本を簡単に振り返ります。
無効化されたオーダーブロックをミティゲーションブロックとして再利用する(SMC) 無効化されたオーダーブロックをミティゲーションブロックとして再利用する(SMC)
本記事では、以前に無効化されたオーダーブロックをスマートマネーコンセプト(SMC)におけるミティゲーションブロックとして再利用する方法を解説します。これらのゾーンは、オーダーブロックが失敗した後に機関投資家が再び市場に参入するポイントを示しており、支配的なトレンドに沿った取引継続の確率が高いエリアを提供します。
MQL5入門(第22回):5-0ハーモニックパターンを用いたエキスパートアドバイザーの構築 MQL5入門(第22回):5-0ハーモニックパターンを用いたエキスパートアドバイザーの構築
本記事では、MQL5において5-0ハーモニックパターンを検出して取引する方法、その妥当性をフィボナッチ比率で検証する方法、そしてチャート上に表示する方法について解説します。
知っておくべきMQL5ウィザードのテクニック(第83回): ストキャスティクスとFrAMAのパターンの使用 - 行動アーキタイプ 知っておくべきMQL5ウィザードのテクニック(第83回): ストキャスティクスとFrAMAのパターンの使用 - 行動アーキタイプ
ストキャスティクスとフラクタル適応型移動平均(FrAMA: Fractal Adaptive Moving Average)は、互いに補完し合う特性を持っており、MQL5のエキスパートアドバイザー(EA)で使える指標ペアの1つです。ストキャスティクスはモメンタムの変化を捉えるために使用し、FrAMAは現在のトレンドを確認するために利用します。本記事では、これら2つのインジケーターの組み合わせについて、MQL5ウィザードを活用して構築およびテストをおこない、その有効性を検証します。