English Русский
preview
初心者からエキスパートへ:FX市場の取引期間

初心者からエキスパートへ:FX市場の取引期間

MetaTrader 5トレーディングシステム |
26 1
Clemence Benjamin
Clemence Benjamin

内容



はじめに

私の住む町で、かつて地元の青果市場が日々どのように運営されているのかを観察したことがあります。そこで気づいたことは、実に示唆に富むものでした。毎朝、農家は新鮮な農産物を携えて早朝から市場に集まり、小売業者もまた、最良の卸売条件を確保するために同じく早い時間帯にやって来ます。市場が開いてから最初の数時間は最も活気に満ち、取引は次々と成立し、価格も状況に応じて頻繁に変動します。やがて時間が進み、正午や昼食時間帯に近づくにつれて、市場の動きは徐々に落ち着き始めます。訪れる小売業者は減り、多くの農家が店じまいの準備に入ります。しかし夕方が近づき、市場が閉場に向かう頃になると、再び活発な動きが戻り、遅れてやって来た買い手と売り手が、日没前に最後の取引を交わします。

この素朴な市場の振る舞いは、FX市場の仕組みと驚くほどよく似ています。農家や小売業者と同様に、世界中のトレーダーもまた、明確に区切られた取引セッションの中で活動しています。これらのセッションには活発な時間帯と静かな時間帯があり、そのリズムが市場全体の流れを形づくっています。主要な取引が集中するピーク時間帯が存在し、それが取引日の性格やモメンタムを決定づけるのです。

本記事の目的は、MQL5を用いてこうした市場セッションを視覚的に表現し、それを上位足、下位足といった異なる時間軸の構造と整合させることにあります。各セッションの開始と終了を正確に同期させることで、あるセッションが次のセッションにどのような影響を与えるのか、そして「タイミング」がいかに価格変動の原動力となっているのかを理解することを目指します。さらに興味深い点として、金融市場のローソク足そのものが時間に基づくバーであり、通常は1時間足、日足、週足といった一定の期間を表しているという事実があります。この考え方を市場セッションに応用すれば、セッション単位のローソク足を作成することが可能になります。従来の時間足と同様に、各セッションはそれぞれ固有の始値、高値、安値、終値(OHLC)を持ち、その取引時間帯における活動量、市場心理、そしてボラティリティを物語ります。

ただし、最も大きな違いは、セッションローソク足がしばしば互いに重なり合う点にあります。特に、取引時間が重複するグローバル市場ではこの特徴が顕著です。たとえばロンドンセッションとニューヨークセッションは、図1に示すように数時間にわたって重なり合い、その結果としてボラティリティと流動性が一段と高まる時間帯が生まれます。標準的な時間足が順序立てて連続的に形成されるのとは異なり、セッションローソク足は同時に存在し、複数の地域が並行して市場に参加している状況を反映します。このセッションの重なりこそが、市場センチメントのダイナミックな移り変わりを生み出す要因であり、セッションベースの分析が従来の時間足分析を補完する重要な理由なのです。

ロンドンおよびニューヨークFX市場セッションの概念

図1:ロンドンとニューヨークの取引セッションの概念的重複

この開発を通じて、セッションローソク足を標準的なローソク足と同じ分析言語で可視化することを目指します。これにより、トレーダーはセッションごとの特有の挙動を把握し、流動性が最も集中するポイントを見極めるとともに、あるセッションで形成されたセンチメントが次のセッションへどのように引き継がれていくのかを比較し、分析できるようになります。最終的にこの機能は、従来の時間足分析と、市場セッションに内在する隠れたリズムとの橋渡しをおこない、時間、構造、価格変動の相互関係に対する理解を一層深めることを目的としています。

この構想を具体的な形にするため、以下ではまず基礎となる考え方を詳しく解説し、その後のセクションにて、ディスカッションで使用する完全なMQL5コードを提示します。



実装

市場期間に関する検討は、さらに発展を続けています。このセクションでは、従来の時間足分析とグローバルなFX取引セッションの動態を統合する、もう1つの強力な機能を導入します。この拡張機能は、構造化された時間区分と実際のセッション挙動との間に存在する隔たりを埋め、市場が1日の中でどのように推移しているのかを、より現実的に把握できる視点を提供します。この新機能を実装するにあたり、まずは既存のMarket Periods Synchronizerに統合する前に、機能を単体で開発およびテストし、正確性とパフォーマンスを確認します。その結果、時間的構造とセッション主導の市場挙動の両方を同時に捉える、より高度で洞察に富んだツールが完成します。

この機能を単体で実装するもう1つの理由は、統合前にすべての読者が概念を完全に理解できるようにすることです。また、プログラムが新機能の追加によって複雑化していく中で、これは優れた開発手法でもあります。コンポーネントを分離することで、システムはテストしやすく、デバッグしやすくなり、学習者にとっても、将来このツールを拡張する開発者にとっても、理解しやすい構造となります。

この概念を実装するため、本記事では2段階のワークフローを採用します。最初に機能を単体で開発し、その後、検証済みのコンポーネントをMarket Periods Synchronizer EAに統合します。この手法により、ロジックはモジュール化された状態を保ち、テストが容易であり、将来的な拡張にも対応しやすくなります。

  • ステップ1 - 分離開発:取引セッションを定義し、それらのOHLC値を算出し、ローソク足を模した塗りつぶし矩形として描画する、スタンドアロンのCSessionVisualizerクラスを作成します(必要に応じてヒゲやラベルも表示可能です)。このスタンドアロンバージョンは、たとえばEURUSDのH1チャートにセッションを描画するなどして、セッション境界および可視化データの正確性を確認するため、独立してテストされるべきです。

  • ステップ2 - 統合フェーズ: 検証が完了した後、セッションビジュアライザをメインEAに統合します。これには、[ShowSessions:ON/OFF]トグル、セッションカラー選択、表示する過去セッション数を指定するルックバック設定といった、新たなUIコントロールの追加が含まれます。RefreshLines()関数は、メジャーラインおよびマイナーラインの描画後にセッションビジュアライザを呼び出すよう拡張され、すべての要素が同期した状態を維持します。g_Lookbackのような共有グローバル変数は一貫性を保つために使用され、セッション間の視覚的な重なりは、半透明レイヤーを用いて明瞭に管理されます。

ステップ1 - 分離開発

このセクションでは、FX市場セッションを検出、計算、および可視化するためのモジュール化されたクラスCSessionVisualizerを構築します。このクラスは、ローソク足風のボディ、任意のヒゲ、およびラベルを含む描画をおこなうよう設計されています。このアプローチでは、モジュール性、可読性、再利用性を重視しており、後にMarket Periods Synchronizer EAのような、より大規模なフレームワークへ統合することを前提としています。

1. ヘッダの設定と目的

優れたMQL5モジュールは、スクリプトの内容、作成者、目的を明確に定義する適切なヘッダから始まります。これにより、他の開発者がコードを理解しやすくなり、バージョン管理における開発の整合性も保たれます。

また、ここではARGBマクロも定義します。このマクロは、透過度を含む色を簡単に生成できる小規模ながら強力な仕組みであり、チャート上でセッションが重なり合う場合など、複数の視覚レイヤーを自然にブレンドするために不可欠です。

//+------------------------------------------------------------------+
//| SessionVisualizerTest.mqh                                        |
//| Modular class for Forex session OHLC visualization               |
//| Author: Clemence Benjamin                                        |
//| Version: 1.00                                                    |
//+------------------------------------------------------------------+
#property strict

// ARGB Macro: Creates a color with alpha (transparency)
#define ARGB(a,r,g,b) ((color)(((uchar)(a))<<24)|(((uchar)(r))<<16)|(((uchar)(g))<<8)|((uchar)(b)))

#include <Object.mqh>  // Provides access to chart object manipulation

ここでは、ビジュアライザが複数のチャートオブジェクト(矩形、ラベル、線)を作成および管理するため、Object.mqhを使用しています。ARGB()マクロを用いることで、ARGB(120,255,0,0)のような半透明カラーを生成でき、透過した赤色の塗りつぶしを実現できます。これは、視覚的なレイヤリングをおこなううえで不可欠です。ただし、後の段階ではARGBマクロを削除し、CCanvasに用意されているARGB実装を使用します。

2. セッション定義と構造体の設定

描画処理をおこなう前に、ここでいう「セッション」が何を意味するのかを定義する必要があります。各FXセッション、すなわちシドニー、東京、ロンドン、ニューヨークは、それぞれ固有の開始時刻と終了時刻を持っています。これらを象徴的に定義するためにenumを使用し、さらにstructを用いて、セッションの名称、時間帯、色、そして現在アクティブかどうかといった各セッションのプロパティを保持します。

enum SESSION_TYPE {
   SESSION_SYDNEY = 0,  // 22:00-07:00 GMT
   SESSION_TOKYO   = 1,  // 00:00-09:00 GMT
   SESSION_LONDON  = 2,  // 08:00-17:00 GMT
   SESSION_NEWYORK = 3   // 13:00-22:00 GMT
};

struct SessionInfo {
   SESSION_TYPE type;
   string       name;       // Short code e.g., "LON"
   int          open_hour;  // GMT open hour
   int          close_hour; // GMT close hour
   color        sess_color; // Visual color on chart
   bool         enabled;    // Whether to render this session
};

enumを使用することで、コードの可読性が高まり、将来的な拡張も容易になります。たとえば、「カスタムセッション」を追加する場合でも、enumに1項目追加し、対応するstructを初期化するだけで済みます。

3. 入力パラメータとクラスの初期化

次に、入力パラメータを定義し、クラスのコンストラクタを準備します。この設定により、トレーダーや開発者は、EAの入力ダイアログからルックバック期間、カラー、ヒゲの表示有無といった項目を直接カスタマイズできるようになります。

input int    InpSessionLookback = 10;  // Days of historical sessions
input bool   InpShowSessionWicks = false;
input int    InpWickAlpha = 120;
input color  InpFillBull = clrLime;
input color  InpFillBear = clrPink;

class CSessionVisualizer : public CObject {
private:
   SessionInfo m_sessions[4];
   int         m_gmt_offset;
   string      m_prefix;

public:
   CSessionVisualizer(string prefix = "SESS_") : m_prefix(prefix) {
      // Initialize all session parameters (GMT-based)
      m_sessions[SESSION_SYDNEY] = {SESSION_SYDNEY, "SYD", 22, 7, clrAqua, true};
      m_sessions[SESSION_TOKYO]  = {SESSION_TOKYO,  "TOK", 0, 9, clrYellow, true};
      m_sessions[SESSION_LONDON] = {SESSION_LONDON, "LON", 8, 17, clrRed, true};
      m_sessions[SESSION_NEWYORK]= {SESSION_NEWYORK,"NY", 13, 22, clrBlue, true};
      
      // Basic GMT offset estimation
      MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);
      m_gmt_offset = -dt.hour % 24;  
   }

   ~CSessionVisualizer() {
      DeleteAllSessionObjects();
   }

ここで使用しているm_prefixは、チャート上におけるオブジェクト名の競合を防ぐためのものです。特に、複数のツールを同時に使用する場合に有効です。たとえば、各オブジェクトは「SESS_LON_O_1730764800」のような形式で命名されます。

4. リフレッシュと描画セッション

RefreshSessions()メソッドは、このビジュアライザの中核となる処理です。このメソッドは、既存のセッション描画をすべて削除し、ユーザーが指定したルックバック期間に基づいてセッションを再生成します。このようにモジュール化された設計により、新しいバーの形成時やタイマーイベントごとに描画を更新しても、チャートが不要なオブジェクトで煩雑になることを防げます。

void RefreshSessions(int lookback_days = 10) {
   DeleteAllSessionObjects();  // Clear previously drawn sessions
   datetime end_time = TimeCurrent();
   datetime start_time = end_time - (lookback_days * 86400);

   for (int day = 0; day < lookback_days; day++) {
      datetime day_start = start_time + (day * 86400);
      for (int s = 0; s < 4; s++) {
         if (!m_sessions[s].enabled) continue;
         DrawSessionForDay(m_sessions[s], day_start);
      }
   }
   ChartRedraw();
}

この構造は、効率性とモジュール性を考慮したループベースの設計となっています。各日付および各セッションが個別に処理されるため、セッション描画の重複や、価格データ範囲の欠落を回避できます。

5. 個別セッションの描画

DrawSessionForDay()関数は、各セッションを実際に描画する処理を担当します。この関数では、セッションの開始時刻と終了時刻(GMTオフセットを考慮)を決定し、その時間帯の価格データを取得したうえで、任意でヒゲを含むローソク足状の矩形としてセッションを可視化します。

void DrawSessionForDay(const SessionInfo &sess, datetime day_start) {
   MqlDateTime dt_open, dt_close;
   TimeToStruct(day_start, dt_open);
   dt_open.hour = sess.open_hour + m_gmt_offset;
   datetime t_open = StructToTime(dt_open);
   
   TimeToStruct(day_start, dt_close);
   dt_close.hour = sess.close_hour + m_gmt_offset;
   datetime t_close = StructToTime(dt_close);
   
   if (sess.close_hour < sess.open_hour) t_close += 86400;  // Overnight fix

   double o,h,l,c;
   if (!GetOHLCInTimeRange(t_open, t_close, o,h,l,c)) return;

   color body_col = (c > o) ? InpFillBull : InpFillBear;
   string rect = m_prefix + sess.name + "_B_" + IntegerToString((int)t_open);
   ObjectCreate(0, rect, OBJ_RECTANGLE, 0, t_open, MathMin(o,c), t_close, MathMax(o,c));
   ObjectSetInteger(0, rect, OBJPROP_BGCOLOR, body_col);
   ObjectSetInteger(0, rect, OBJPROP_FILL, true);
   ObjectSetInteger(0, rect, OBJPROP_BACK, true);
}

この手法により、セッションローソク足はローソク足と同様の振る舞いを示すようになり、セッションを独立したマイクロ時間足として分析するという新たな視点が得られます。

6. セッションOHLCデータの取得

GetOHLCInTimeRange()関数は、各セッションの継続時間内において、選択された時間足から正確な始値、高値、安値、終値のデータを取得します。iBarShift()を使用することで、既存のチャートバーとの厳密な整合性が確保されます。

bool GetOHLCInTimeRange(datetime start, datetime end, double &open, double &high, double &low, double &close) {
   int shift_start = iBarShift(_Symbol, _Period, start, false);
   int shift_end   = iBarShift(_Symbol, _Period, end, false);
   if (shift_start < 0 || shift_end < 0) return false;
   int count = shift_start - shift_end + 1;
   if (count <= 0) return false;

   double o[],h[],l[],c[];
   ArraySetAsSeries(o,true); ArraySetAsSeries(h,true); ArraySetAsSeries(l,true); ArraySetAsSeries(c,true);
   CopyOpen(_Symbol,_Period,shift_end,count,o);
   CopyHigh(_Symbol,_Period,shift_end,count,h);
   CopyLow(_Symbol,_Period,shift_end,count,l);
   CopyClose(_Symbol,_Period,shift_end,count,c);

   open = o[count-1]; high = h[ArrayMaximum(h,0,count)];
   low = l[ArrayMinimum(l,0,count)]; close = c[0];
   return true;
}

これにより、外部ソースを再取得することなく、標準の価格系列から「セッションローソク足」のOHLCデータを取得できます。

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

良いコードは、常に後処理をおこないます。DeleteAllSessionObjects()関数は、リフレッシュ時やdeinit時に、古いオブジェクトが確実に削除されるようにします。これをおこなわない場合、長時間のテストを続けることで、数千個もの不要な矩形オブジェクトがチャート上に残ることになります。

void DeleteAllSessionObjects() {
   int total = ObjectsTotal(0);
   for (int i = total - 1; i >= 0; i--) {
      string name = ObjectName(0, i);
      if (StringFind(name, m_prefix) == 0)
         ObjectDelete(0, name);
   }
}

テストEAの開発

まず、コンパクトなヘッダとインクルードから始めます。このEAは意図的に最小限の構成となっており、ビジュアライザを起動し、定期的なリフレッシュ用のタイマーを開始し(市場が動いている間にセッションが更新される様子を確認できるようにするため)、終了時に後処理をおこなうだけです。テスト用ハーネスを小さく保つことで、問題を切り分けやすくなり、クラスの挙動を大規模システムに統合する前に検証することができます。

//+------------------------------------------------------------------+
//| SessionTest.mq5 - Standalone test for CSessionVisualizer         |
//| Purpose: lightweight EA to exercise the SessionVisualizer class  |
//+------------------------------------------------------------------+
#property strict

#include <SessionVisualizerTest.mqh>

次に、ランタイムインスタンスを宣言します。ポインタとnewを使用することで、ライフタイムを明示的に制御できます。VisualizerはOnInit()で生成し、OnDeinit()で削除するため、デストラクタが即座に実行され、クラスが作成したオブジェクトがすべて削除されます。このパターンはテストに便利であり、サブシステムを動的に生成、破棄する大規模EAでのオブジェクト管理方法と同様の扱いとなります。

// runtime pointer to the session visualizer
CSessionVisualizer *g_session_vis = NULL;

OnInit()では、ビジュアライザの構築、リフレッシュ用タイマーの設定、および初回描画をおこないます。例では30秒のデモタイマーを使用しており、更新の頻度を十分に確認できる一方で、CPU負荷が過剰にならない長さに設定しています。ビジュアライザにはユニークな接頭辞を渡してオブジェクトが明確に名前空間化されるようにしています(同一チャート上で複数ツールをテストする際に便利です)。さらに、RefreshSessions(5)を即座に呼び出すことで、過去5日分のセッションが描画され、履歴のプロットをすぐに確認できます。

int OnInit()
{
   // allocate the session visualizer with a test-specific prefix to avoid name clashes
   g_session_vis = new CSessionVisualizer("TEST_SESS_");

   // Refresh every 30 seconds during testing (demo). Adjust for production usage.
   EventSetTimer(30);

   // initial rendering of the last 5 days (quick sanity check)
   if(g_session_vis != NULL)
      g_session_vis.RefreshSessions(5);

   return(INIT_SUCCEEDED);
}

なお、メソッドを呼び出す前にnullチェックをおこないます。newが何らかの理由で失敗した場合(通常のデスクトップでは非常に稀です)、クラッシュを防ぎ、ログ上で失敗を検出しやすくします。

OnTimer()は定期更新を担当します。セッションの可視化においてこれは有用で、ライブバー上ではセッションのOHLCがわずかに変化することがあり(使用するOHLC算出時間足によります)、頻繁に再描画することでセッションのヒゲやボディの変化を観察できます。実運用のEAでは、新しいバー生成時やより長い間隔でのリフレッシュが好ましい場合がありますが、テストでは30秒間隔で問題ありません。

void OnTimer()
{
   // periodic refresh of the last 5 days
   if(g_session_vis != NULL)
      g_session_vis.RefreshSessions(5);
}

OnDeinit()では後処理をおこないます。タイマーを停止し、ビジュアライザオブジェクトを削除します。オブジェクトを削除するとデストラクタが呼ばれ、実装どおりであれば、クラスがチャート上に作成したすべてのセッションオブジェクトが削除されます。これにより、テスト後もチャートがクリーンな状態に保たれます。明示的な後処理は、EAの繰り返し読み込み環境(複数EAのロード/アンロード)において、チャートの膨張や残存オブジェクトを防ぐために不可欠です。

void OnDeinit(const int reason)
{
   EventKillTimer();

   if(g_session_vis != NULL)
   {
      // destructor will call DeleteAllSessionObjects()
      delete g_session_vis;
      g_session_vis = NULL;
   }
}

初期テスト結果

初期テスト段階は非常に簡単でした。Session Test EAをコンパイル後、ライブのMetaTrader 5チャートにアタッチするだけですぐに、FXセッションが可視化されました。下の画像では、セッションがどのように重なっているかが明確に確認でき、それぞれのセッションは識別しやすいように異なる色で表示されています。塗りつぶされたセッション矩形はセンチメントも示しており、ライムグリーンは強気のセッションクローズ、ピンクは弱気を示します。この色分けされた可視化により、各取引セッションの支配的方向を一目で把握することが容易になります。

初期テスト

図2:セッションテスト

ステップ2 - 統合フェーズ

あらゆる優れた取引ツールは、実験的なスクリプトから本番用の統合モジュールへと進化する過程をたどります。私たちのセッションビジュアライザも例外ではありません。当初のテスト用ヘッダ(SessionVisualizerTest.mqh)は、基礎の確立を目的としていました。具体的には、取引セッションの定義、OHLCデータの取得、そして各FX市場時間帯を表す色付き矩形の描画です。テスト段階では十分に役割を果たしていましたが、本番環境でMarket Periods Synchronizer EAに統合するには、視認性、拡張性、互換性の面で改善が必要でした。

セッションボディの塗りつぶしを廃止した理由

テスト版では、セッションは単色で塗りつぶされた矩形として描画され、各取引時間帯を強調していました。この方法は見た目こそ鮮やかでしたが、セッションの重なりや履歴データのレイヤー表示が増えるにつれ、視覚的に過密になってしまいました。さらに、塗りつぶし表示は下にあるチャートの情報を隠してしまい、「情報を提供しつつ、チャートを邪魔しない」という本来の目的に反していました。

そこで新バージョンでは、塗りつぶしなしで破線枠付きの矩形をボディに採用しました。これは中身のないローソク足のようなもので、価格チャートを覆うことなく、各セッションの始値〜終値の値幅を示します。この設計により、チャート全体に余白が生まれ、セッションマーカーの下にある値動きも明瞭に確認できます。

開発者としての重要な教訓はここにあります。色を減らすことで、情報量はむしろ増えるのです。不透明な塗りつぶしを取り除くことで、視認性とプロフェッショナルな外観の両立を実現できました。

CCanvasとARGBの統合:単一の描画エンジンへ

新しいヘッダにおける重要な技術的変更のひとつが、「#include <Canvas/Canvas.mqh>」を使用したARGBベース描画の採用です。これにより、EA内ですでに使用しているCCanvas機能(背景描画や透明度制御)と完全に互換性を持たせています。ARGBを独自定義するのではなく、Canvasライブラリに委譲することで、EA全体の描画仕様を統一しました。

これは設計上かつ実用的な判断です。モジュールを再利用するEAでは、ARGBマクロの重複定義が競合や予期しない挙動を引き起こす可能性があります。特に透明度の扱いが異なると、描画結果が不安定になります。単一の描画エンジン(CCanvas)を共有することで、描画ロジックの一貫性を維持し、保守コストを削減できます。

要するに、Canvasをグローバルに宣言することで、セッションボックス、ヒゲ、オーバーレイなど、すべてのビジュアル要素が同じ「描画言語」を話すようにしたのです。

セッション定義の再構築:色ブロックから市場フレームへ

セッション構造自体は大きく変わっていません。依然としてSessionInfo構造体を使用し、名前、タイプ、開始および終了時刻、色を保持しています。しかし、それらの視覚表現を再定義しました。

各セッションは以下を描画します。

  • 始値〜終値の値幅を示す破線の矩形
  • セッション全体の高値〜安値を示すグレーの「ヒゲ」矩形(任意)
  • 開始および終了時刻を示すテキストラベルと垂直線
  • 矩形はローソク足の背後に、線とラベルは前面に表示される前景/背景レイヤー制御

これにより、分析的でありながら洗練された外観が生まれました。市場のリズムを透明な設計図のように可視化できます。トレーダーは、セッションの拡大・収縮を一目で把握しつつ、プライスアクションを失うことがありません。新しい「塗りつぶしなし」コンセプトは、市場を覆うのではなく、市場を縁取るフレームとして機能します。

ランタイム設定:ビジュアライザを動的にする

柔軟性を高めるため、これまで静的だった設定をランタイム変数に変更しました。たとえば、m_show_wicks、m_wick_alpha、m_gmt_offsetはすべてグローバル設定可能です。トレーダーや開発者は、再コンパイルせずにヒゲの可視性を切り替えたり、透明度を変更したり、GMTオフセットを調整したりできます。

次のような関数にお気づきでしょう。

void SetShowWicks(bool show) { m_show_wicks = show; }
void SetWickAlpha(int alpha) { m_wick_alpha = MathMax(0, MathMin(255, alpha)); }
void SetGMTOffset(int offset){ m_gmt_offset = offset; }

これらの小さいながらも重要な追加要素により、EAのインターフェースと可視化レイヤーの間でリアルタイムな相互作用が可能となります。これは、MQL5における現代的なモジュール設計の根幹を成す要素です。

文脈的インサイト:現在および直前のセッション表示

テスト版では、静的な過去セッションのみを表示していましたが、新しいヘッダでは現在および直近のセッションを追跡できるようになりました。ライブ取引中、システムはどのセッションがアクティブであるかを識別し、そのセッションに「Live」ラベルを重ねて表示します。また、直前に終了したセッションについても、終了後1時間は表示を維持し、前のセッションがどのような動きをしたかを即座に視覚的に確認できるようにしています。

これで、効果的な統合に向けておこなわれた調整内容を理解できたと思います。ここからは、新しいSessionVisualizerヘッダの構造と機能を詳細に分解し、解説していきます。その後、このヘッダをエキスパートアドバイザー(EA)へどのように統合するかを説明し、さらに、市場セッション時間帯をより効率的に活用するために追加された新しいEA機能について紹介します。これらは、トレーダーの意思決定を改善することを目的としています。

1) モジュールの説明とコンパイル厳格性

まずは明確なモジュール説明から始めます。なぜなら、公開されるコードそのものがドキュメントでもあるからです。バージョン情報は、読者に「何が新しくなったのか」(例:塗りつぶしなしの矩形、破線枠、グレーのヒゲ)を伝えます。また、「#property strict」はMQL5における現代的な型安全性を強制し、実験用ヘッダでは見逃されがちなサイレントバグをコンパイル時に検出できるようにします。

//+------------------------------------------------------------------+
//| SessionVisualizer.mqh                                            |
//| Modular class for Forex session OHLC visualization               |
//| Copyright 2025: Clemence Benjamin                                |
//| Version: 1.02                                                    |
//+------------------------------------------------------------------+
#property strict

2) 共有依存関係(Object API + Canvasによる統一ARGB)

テスト段階では、独自にARGBマクロを定義することも珍しくありません。しかし本番環境では、競合を避けるためにEA全体で単一のビジュアルエンジンに統一することが重要になります。Canvas.mqhをインクルードすることで、正規のARGB()実装を利用でき、Market Periods Synchronizer EA内の他の描画機能との完全な互換性が確保されます。

#include <Object.mqh>
#include <Canvas/Canvas.mqh>   // for ARGB()

3) セッションのドメインモデル(列挙型 + 構造体)

ここで、問題領域を明確に定義します。4つの主要市場セッション、人間にとって分かりやすい名称、そしてシンプルなSessionInfoコンテナです。たとえ、あるプロパティがすべての箇所で使用されていなくても(例:sess_colorはもはやボディの塗りつぶしには使われていない)、あえて保持しています。理由は、色が依然としてラベルやライン描画に使用されていること、そして過去のツールやユーザーの使用習慣との後方互換性を維持するためです。

//----------------------------------------------
// Session enum and info
//----------------------------------------------
enum SESSION_TYPE {
   SESSION_SYDNEY = 0,   // 22:00-07:00 GMT
   SESSION_TOKYO  = 1,   // 00:00-09:00 GMT
   SESSION_LONDON = 2,   // 08:00-17:00 GMT
   SESSION_NEWYORK= 3    // 13:00-22:00 GMT
};

struct SessionInfo {
   SESSION_TYPE type;
   string       name;       // "SYD","TOK","LON","NY"
   int          open_hour;  // GMT open hour
   int          close_hour; // GMT close hour
   color        sess_color; // base tint (not used for border now)
   bool         enabled;
};

4) ランタイムパラメータ(EAから制御可能な「ノブ」)

機能を再コンパイルなしで切り替えられるよう、EAから調整可能なグローバル変数を宣言します。m_session_lookbackは履歴表示の密度を制御し、m_show_wicksとm_wick_alphaはヒゲの値幅の表示有無と透明度を管理します。また、m_gmt_offsetはサーバー時間とGMTを整合させるための調整値であり、現実の運用で誰もが直面する「定番の悩みどころ」に対応します。

//----------------------------------------------
// Runtime params (configured by EA)
//----------------------------------------------
int    m_session_lookback = 10;
bool   m_show_wicks       = false;
int    m_wick_alpha       = 120;
int    m_gmt_offset       = 0;

5) クラスの骨組みとコンストラクタの初期値

公開APIはコンパクトに保ち、非公開な状態は明示的に管理します。コンストラクタでは、標準的なGMTセッションスケジュールと、可読性の高い3文字タグを初期化しています。ここで注目すべき点として、m_prefixを保持していることが挙げられます。これは、安全なオブジェクト命名と後処理での確実な削除を可能にする重要な仕組みです。他のチャートオブジェクトとの意図しない名前衝突を防ぐための鍵でもあります。

class CSessionVisualizer : public CObject
{
private:
   SessionInfo m_sessions[4];
   string      m_prefix;

public:
   CSessionVisualizer(string prefix="SESS_") : m_prefix(prefix)
   {
      // Defaults (GMT schedule)
      m_sessions[SESSION_SYDNEY] .type=SESSION_SYDNEY;  m_sessions[SESSION_SYDNEY] .name="SYD"; m_sessions[SESSION_SYDNEY] .open_hour=22; m_sessions[SESSION_SYDNEY] .close_hour=7;  m_sessions[SESSION_SYDNEY] .sess_color=clrAqua;  m_sessions[SESSION_SYDNEY] .enabled=true;
      m_sessions[SESSION_TOKYO]  .type=SESSION_TOKYO;   m_sessions[SESSION_TOKYO]  .name="TOK"; m_sessions[SESSION_TOKYO]  .open_hour=0;  m_sessions[SESSION_TOKYO]  .close_hour=9;  m_sessions[SESSION_TOKYO]  .sess_color=clrYellow; m_sessions[SESSION_TOKYO]  .enabled=true;
      m_sessions[SESSION_LONDON] .type=SESSION_LONDON;  m_sessions[SESSION_LONDON] .name="LON"; m_sessions[SESSION_LONDON] .open_hour=8;  m_sessions[SESSION_LONDON] .close_hour=17; m_sessions[SESSION_LONDON] .sess_color=clrRed;   m_sessions[SESSION_LONDON] .enabled=true;
      m_sessions[SESSION_NEWYORK].type=SESSION_NEWYORK; m_sessions[SESSION_NEWYORK].name="NY";  m_sessions[SESSION_NEWYORK].open_hour=13; m_sessions[SESSION_NEWYORK].close_hour=22; m_sessions[SESSION_NEWYORK].sess_color=clrBlue;  m_sessions[SESSION_NEWYORK].enabled=true;
   }

   ~CSessionVisualizer() { DeleteAllSessionObjects(); }

6) 公開API:リフレッシュとスイッチ(ライブチャート向け設計)

RefreshSessions()は、ワンボタンでの再構築処理です。既存オブジェクトをクリアし、履歴セッションを再描画したうえで、現在進行中のライブセッション領域を描画します。また、EAのパネルや制御ロジックからセッション表示の切り替え、テーマカラーの変更、GMT同期の調整をリアルタイムにおこなえるよう、小さくても表現力のあるsetter関数を公開しています。テスト用ヘッダからの重要な変更点として、ボディの塗りつぶしを完全に廃止しました。そのため、SetFillColors()は現在、実際には何もしない関数(no-op)ですが、後方互換性を維持するためだけに残されています。

   //---------------------------------------------------------------
   // PUBLIC API
   //---------------------------------------------------------------
   void RefreshSessions(int lookback_days=10)
   {
      m_session_lookback = lookback_days;
      DeleteAllSessionObjects();

      const datetime now   = TimeCurrent();
      const datetime start = now - (m_session_lookback*86400);

      // Historical completed sessions
      for(int d=0; d<m_session_lookback; ++d)
      {
         datetime day_start = start + d*86400;
         for(int s=0; s<4; ++s)
         {
            if(!m_sessions[s].enabled) continue;
            DrawHistoricalSessionForDay(m_sessions[s], day_start);
         }
      }

      // Active session windows
      DrawCurrentSessions();

      ChartRedraw();
   }

   void SetSessionEnabled(SESSION_TYPE type, bool enabled)
   {
      for(int i=0;i<4;i++) if(m_sessions[i].type==type){ m_sessions[i].enabled=enabled; break; }
   }

   void SetSessionColor(SESSION_TYPE type, color col)
   {
      for(int i=0;i<4;i++) if(m_sessions[i].type==type){ m_sessions[i].sess_color=col; break; }
   }

   // Kept for compatibility with EA; ignored (we no longer fill bodies).
   void SetFillColors(color /*bull*/, color /*bear*/) { /* no-op */ }

   void SetShowWicks(bool show) { m_show_wicks = show; }
   void SetWickAlpha(int alpha) { m_wick_alpha = MathMax(0, MathMin(255, alpha)); }
   void SetGMTOffset(int offset){ m_gmt_offset = offset; }

   // public cleanup
   void ClearAll() { DeleteAllSessionObjects(); }

7) 履歴描画(完了済みセッションのみ)

セッション系ツールでよくある落とし穴は、未完了のデータを「履歴」として描画してしまうことです。ここでは、m_gmt_offsetを考慮してセッションの開始/終了時刻を計算し、日付をまたぐ(深夜0時を越える)ケースも正しく処理します。そのうえで、完全に過去に属していないセッションはすべてスキップします。セッションウィンドウが完了していることを確認してから初めて、OHLCを算出し、描画をおこないます。

private:
   //---------------------------------------------------------------
   // CORE DRAWING
   //---------------------------------------------------------------
   void DrawHistoricalSessionForDay(const SessionInfo &sess, datetime day_start)
   {
      // build open/close datetimes adjusted by GMT offset
      MqlDateTime dto, dtc; TimeToStruct(day_start, dto); dtc = dto;
      dto.hour = sess.open_hour  + m_gmt_offset; dto.min=0; dto.sec=0;
      dtc.hour = sess.close_hour + m_gmt_offset; dtc.min=0; dtc.sec=0;

      datetime t_open  = StructToTime(dto);
      datetime t_close = StructToTime(dtc);
      if(sess.close_hour < sess.open_hour) t_close += 86400; // wraps midnight

      // Only draw if fully in the past
      if(t_close >= TimeCurrent()) return;
      if(t_close < TimeCurrent() - (m_session_lookback*86400)) return;

      double o,h,l,c;
      if(!GetOHLCInTimeRange(t_open, t_close, o,h,l,c)) return;

      DrawSessionVisuals(sess, t_open, t_close, o,h,l,c, /*is_current=*/false);
   }

8) ライブセッション処理(直前セッションの1時間猶予を含む)

当日のセッションウィンドウを構築し、日付をまたぐセッションも考慮することで、現在アクティブなセッションをリアルタイムに検出します。セッション時間内にいる場合は、現在時点までのOHLCをストリーム的に更新し、さらに現在のBid価格で高値/安値を微調整することで、ボックスがリアルタイムの値動き(テープ)を正確に反映するようにします。該当セッションには「(Live)」ラベルを付けて表示します。また、直前に終了したセッションについては、終了後も1時間表示を維持します。これは、ロールオーバーのタイミングなどで、トレーダーが直前の市場状況を把握したい場合に非常に便利です。

   void DrawCurrentSessions()
   {
      const datetime now = TimeCurrent();
      MqlDateTime cur; TimeToStruct(now, cur);

      for(int i=0;i<4;i++)
      {
         if(!m_sessions[i].enabled) continue;
         const SessionInfo sess = m_sessions[i];

         // compute session window for "today" (adjusted for wrap)
         MqlDateTime ds, de; TimeToStruct(now, ds); de = ds;
         ds.hour = sess.open_hour  + m_gmt_offset; ds.min=0; ds.sec=0;
         de.hour = sess.close_hour + m_gmt_offset; de.min=0; de.sec=0;

         datetime session_start = StructToTime(ds);
         datetime session_end   = StructToTime(de);

         if(sess.close_hour < sess.open_hour)
         {
            if(cur.hour >= sess.open_hour + m_gmt_offset) session_end += 86400; // ends tomorrow
            else session_start -= 86400; // started yesterday
         }

         if(now >= session_start && now <= session_end)
         {
            // pull OHLC until now (close = current)
            double o,h,l,c;
            if(!GetOHLCInTimeRange(session_start, now, o,h,l,c))
               o = h = l = c = SymbolInfoDouble(_Symbol, SYMBOL_BID);

            const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            h = MathMax(h, bid);
            l = MathMin(l, bid);
            c = bid;

            DrawSessionVisuals(sess, session_start, session_end, o,h,l,c, /*is_current=*/true);
         }
         else if(now > session_end && now <= session_end + 3600)
         {
            // Keep just-closed session visible for 1 hour
            double o,h,l,c;
            if(!GetOHLCInTimeRange(session_start, session_end, o,h,l,c)) continue;
            DrawSessionVisuals(sess, session_start, session_end, o,h,l,c, /*is_current=*/false);
         }
      }
   }

9) ビジュアル文法:塗りつぶしなしボディ、破線枠、任意のグレーのヒゲ

これは、テスト用ヘッダからの最も象徴的な変更点です。セッションボディは塗りつぶしなしとし、「OBJPROP_BACK = true」を設定して価格チャートの背後に配置することで、ローソク足を一切遮らないようにしています。枠はセンチメントを符号化します。強気の場合は深緑、弱気の場合は赤、それ以外の場合はセッション固有の色を使用します。ヒゲは有効化された場合のみ、柔らかなグレーのARGB矩形として背景に描画され、視覚的な混雑を招くことなく、セッション全体の高値〜安値の値幅を可視化します。ラベルおよびタイムラインは、常に前景に表示され、明確な視認性を保ちます。

   // Renders:
   //  - unfilled body rectangle (Open..Close) with dashed border
   //    * border = clrDarkGreen if bullish, clrRed if bearish (Neutral uses session color)
   //  - optional GREY filled wick rectangles in background for visibility
   //  - open/close vlines + labels in foreground (thin)
   void DrawSessionVisuals(const SessionInfo &sess,
                           datetime t_open, datetime t_close,
                           double o,double h,double l,double c,
                           bool is_current)
   {
      const bool bullish = (c > o);
      const bool bearish = (c < o);

      // ---------- vertical lines ----------
      string vopen = m_prefix + sess.name + "_O" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
      if(ObjectFind(0, vopen) == -1) ObjectCreate(0, vopen, OBJ_VLINE, 0, t_open, 0);
      ObjectSetInteger(0, vopen, OBJPROP_COLOR, sess.sess_color);
      ObjectSetInteger(0, vopen, OBJPROP_WIDTH, is_current?2:1);
      ObjectSetInteger(0, vopen, OBJPROP_STYLE, is_current?STYLE_SOLID:STYLE_DASH);
      ObjectSetInteger(0, vopen, OBJPROP_BACK,  false);
      ObjectSetInteger(0, vopen, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, vopen, OBJPROP_HIDDEN, false);

      string vclose = m_prefix + sess.name + "_C" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_close);
      if(ObjectFind(0, vclose) == -1) ObjectCreate(0, vclose, OBJ_VLINE, 0, t_close, 0);
      ObjectSetInteger(0, vclose, OBJPROP_COLOR, sess.sess_color);
      ObjectSetInteger(0, vclose, OBJPROP_WIDTH, is_current?2:1);
      ObjectSetInteger(0, vclose, OBJPROP_STYLE, is_current?STYLE_SOLID:STYLE_DASH);
      ObjectSetInteger(0, vclose, OBJPROP_BACK,  false);
      ObjectSetInteger(0, vclose, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, vclose, OBJPROP_HIDDEN, false);

      // ---------- body rectangle (UNFILLED, dashed) ----------
      const double body_top = MathMax(o,c);
      const double body_bot = MathMin(o,c);

      color border_col = sess.sess_color;  // neutral fallback
      if(bullish) border_col = clrDarkGreen;
      else if(bearish) border_col = clrRed;

      string rect = m_prefix + sess.name + "_R" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
      if(ObjectFind(0, rect) == -1)
      {
         if(!ObjectCreate(0, rect, OBJ_RECTANGLE, 0, t_open, body_bot, t_close, body_top))
            PrintFormat("Failed to create session rectangle %s err=%d", rect, GetLastError());
      }
      // unfilled rectangle with dashed edge, kept in background
      ObjectSetInteger(0, rect, OBJPROP_FILL,   false);
      ObjectSetInteger(0, rect, OBJPROP_COLOR,  border_col);
      ObjectSetInteger(0, rect, OBJPROP_STYLE,  STYLE_DASH);
      ObjectSetInteger(0, rect, OBJPROP_WIDTH,  1);
      ObjectSetInteger(0, rect, OBJPROP_BACK,   true);
      ObjectSetInteger(0, rect, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, rect, OBJPROP_HIDDEN, false);
      // keep coordinates fresh
      ObjectMove(0, rect, 0, t_open,  body_bot);
      ObjectMove(0, rect, 1, t_close, body_top);

      // ---------- WICKS (optional) as GREY filled rectangles in background ----------
      if(m_show_wicks)
      {
         uint wick_col = ARGB(m_wick_alpha, 128,128,128); // semi-transparent grey

         // Upper wick: [body_top .. high]
         string wu = m_prefix + sess.name + "_WU" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
         if(h > body_top)
         {
            if(ObjectFind(0, wu) == -1)
            {
               if(!ObjectCreate(0, wu, OBJ_RECTANGLE, 0, t_open, body_top, t_close, h))
                  PrintFormat("Failed to create upper wick %s err=%d", wu, GetLastError());
            }
            ObjectSetInteger(0, wu, OBJPROP_BGCOLOR, wick_col);
            ObjectSetInteger(0, wu, OBJPROP_COLOR,  wick_col);
            ObjectSetInteger(0, wu, OBJPROP_FILL,   true);
            ObjectSetInteger(0, wu, OBJPROP_BACK,   true);
            ObjectSetInteger(0, wu, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, wu, OBJPROP_HIDDEN, false);
            ObjectMove(0, wu, 0, t_open,  body_top);
            ObjectMove(0, wu, 1, t_close, h);
         }
         else ObjectDelete(0, wu);

         // Lower wick: [low .. body_bot]
         string wl = m_prefix + sess.name + "_WL" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
         if(l < body_bot)
         {
            if(ObjectFind(0, wl) == -1)
            {
               if(!ObjectCreate(0, wl, OBJ_RECTANGLE, 0, t_open, l, t_close, body_bot))
                  PrintFormat("Failed to create lower wick %s err=%d", wl, GetLastError());
            }
            ObjectSetInteger(0, wl, OBJPROP_BGCOLOR, wick_col);
            ObjectSetInteger(0, wl, OBJPROP_COLOR,  wick_col);
            ObjectSetInteger(0, wl, OBJPROP_FILL,   true);
            ObjectSetInteger(0, wl, OBJPROP_BACK,   true);
            ObjectSetInteger(0, wl, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, wl, OBJPROP_HIDDEN, false);
            ObjectMove(0, wl, 0, t_open,  l);
            ObjectMove(0, wl, 1, t_close, body_bot);
         }
         else ObjectDelete(0, wl);
      }
      else
      {
         // Ensure wicks are removed if disabled
         ObjectDelete(0, m_prefix + sess.name + "_WU" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open));
         ObjectDelete(0, m_prefix + sess.name + "_WL" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open));
      }

      // ---------- Labels in foreground ----------
      string session_tag = sess.name + (is_current ? " (Live)" : "");
      string lbl_o = m_prefix + sess.name + "_OL" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_open);
      if(ObjectFind(0, lbl_o) == -1) ObjectCreate(0, lbl_o, OBJ_TEXT, 0, t_open, o);
      ObjectSetString (0, lbl_o, OBJPROP_TEXT, session_tag + " Open");
      ObjectSetInteger(0, lbl_o, OBJPROP_COLOR, sess.sess_color);
      ObjectSetInteger(0, lbl_o, OBJPROP_FONTSIZE, 8);
      ObjectSetInteger(0, lbl_o, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, lbl_o, OBJPROP_HIDDEN, false);
      ObjectSetInteger(0, lbl_o, OBJPROP_BACK, false);

      string lbl_c = m_prefix + sess.name + "_CL" + (is_current?"_CUR_":"_HIS_") + IntegerToString((int)t_close);
      if(ObjectFind(0, lbl_c) == -1) ObjectCreate(0, lbl_c, OBJ_TEXT, 0, t_close, c);
      ObjectSetString (0, lbl_c, OBJPROP_TEXT, session_tag + " Close");
      ObjectSetInteger(0, lbl_c, OBJPROP_COLOR, sess.sess_color);
      ObjectSetInteger(0, lbl_c, OBJPROP_FONTSIZE, 8);
      ObjectSetInteger(0, lbl_c, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, lbl_c, OBJPROP_HIDDEN, false);
      ObjectSetInteger(0, lbl_c, OBJPROP_BACK, false);
   }

10) 強固なOHLC集計(バーシフト基準)

各時間軸で正確な値を算出するため、セッションのOHLCはiBarShiftを使って境界を特定し、必要最小限の配列スライスを取得してから集計します。その際、ArrayMaximum/ArrayMinimumを用いて高値/安値を正確に求めます。この方法により、Off-by-oneエラーを回避でき、時間軸の粒度がセッション端に正確に揃っていなくても正しく動作します。

   //---------------------------------------------------------------
   // Data helpers
   //---------------------------------------------------------------
   bool GetOHLCInTimeRange(datetime start, datetime end,
                           double &open, double &high, double &low, double &close)
   {
      int shift_start = iBarShift(_Symbol, _Period, start, false);
      int shift_end   = iBarShift(_Symbol, _Period, end,   false);
      if(shift_start < 0 || shift_end < 0) return false;

      int bars = shift_start - shift_end + 1;
      if(bars <= 0) return false;

      double opens[], highs[], lows[], closes[];
      ArraySetAsSeries(opens,  true);
      ArraySetAsSeries(highs,  true);
      ArraySetAsSeries(lows,   true);
      ArraySetAsSeries(closes, true);

      if(CopyOpen (_Symbol,_Period, shift_end, bars, opens)  != bars) return false;
      if(CopyHigh (_Symbol,_Period, shift_end, bars, highs)  != bars) return false;
      if(CopyLow  (_Symbol,_Period, shift_end, bars, lows)   != bars) return false;
      if(CopyClose(_Symbol,_Period, shift_end, bars, closes) != bars) return false;

      open  = opens[bars-1];
      high  = highs[ArrayMaximum(highs,0,bars)];
      low   = lows [ArrayMinimum(lows ,0,bars)];
      close = closes[0];
      return true;
   }

11) 決定論的クリーンアップ(接頭辞コープ付きGC)

プロフェッショナルなチャートツールは、使用後にチャートをきれいに保つことが求められます。リフレッシュやEAの削除時には、オブジェクトを逆順に走査し、自身のm_prefixを持つオブジェクトのみを削除します。これにより、他のインジケーターやEAと礼儀正しく共存でき、ユーザーを苛立たせるような「ゴースト描画」を防ぐことができます。

   //---------------------------------------------------------------
   // Cleanup
   //---------------------------------------------------------------
   void DeleteAllSessionObjects()
   {
      int total = ObjectsTotal(0);
      for(int i = total-1; i >= 0; --i)
      {
         string name = ObjectName(0, i);
         if(StringFind(name, m_prefix) == 0) ObjectDelete(0, name);
      }
   }
};

CSessionVisualizerとセッション情報をMarketPeriodsSynchronizerEAに統合する

アップグレード前の旧EAバージョンでは、主要/副次期間の構造、ボディの塗りつぶし、ヒゲ、UIコントロールに重点を置いており、市場セッションやセッション概要という概念は存在しませんでした。今回のバージョンでは、新しいCSessionVisualizerクラスを組み込み、さらにコンパクトな「セッション情報パネル」を追加しました。加えて、主要期間の描画とは独立して、セッションやセッションヒゲを制御できるトグルを整理して搭載しています。以下では、旧EAをステップごとに進化させるイメージで統合手順を説明します。各変更の理由を解説し、実際に実装されているコードスニペットも提示します(参考:旧EAのベースラインはv1.01で、セッション表示も情報パネルも未搭載の状態です)。 

1) セッションビジュアライザをEAに組み込む

新しいセッション描画用ヘッダを、Canvasヘルパーと併せてインクルードします。テストヘッダではすでに、塗りつぶしなし、ボディの破線枠、任意のグレーのヒゲを背景に描画するCSessionVisualizerを使用しているため、視覚的にHTFオーバーレイと干渉しません。インクルードは最小限かつ明示的にして、ODRや名前衝突を回避します。

#include <Canvas/Canvas.mqh>   // Canvas helper library (expects Canvas.mqh to be present)
#include <SessionVisualizer.mqh>  // Integrated Session Visualizer class

2) セッションと情報パネル用の新入力

ユーザーとの契約(インターフェース)として、セッション表示の切替、履歴日数、ブローカーGMTオフセット、セッション専用ヒゲ表示・透明度、セッション情報パネル表示の入力を追加します。主要期間のヒゲ設定とセッション専用ヒゲを意図的に分離している点に注目してください。トレーダーは、片方だけヒゲ表示を希望することが多いためです。

// Session inputs
input bool           InpShowSessions      = true;     // Show Forex sessions
input int            InpSessionLookback   = 10;       // Session lookback (days)
input int            InpGMTOffset         = 0;        // GMT offset (broker hours)

// Wick visualization (separated for majors and sessions)
input bool           InpShowSessionWicks  = false;    // Show wick regions for sessions
input int            InpSessionWickAlpha  = 120;      // Alpha transparency for session wicks 0..255

// Session information panel
input bool           InpShowSessionInfo   = true;     // Show session info panel

以前のバージョンはセッション固有入力を持たず、すべて主要/副次期間と1つのヒゲ切替に依存していました。今回、両機能が同時に有効でもレイアウトが乱れないよう、関心事を分離しています。 

3) ランタイムグローバル変数に入力を反映

EAのダッシュボードでは、ランタイムに状態が変更されるため、入力値の可変コピーを保持します。ここでは、g_ShowSessions、セッションヒゲのランタイム変数(g_ShowSessionWicks、g_SessionWickAlpha)、情報パネル用フラグを追加します。入力 → ランタイム → UIという概念モデルを保つため、他のグローバル変数の近くに置きます。

// Wick runtime (separated for majors and sessions)
bool            g_ShowWicks;
int             g_WickAlpha;
bool            g_ShowSessionWicks;
int             g_SessionWickAlpha;

// Session runtime
bool            g_ShowSessions;

// Session information panel
bool            g_ShowSessionInfo;

4) 単一の共有CSessionVisualizerインスタンス

すべてのセッション描画は、安定したプレフィックスとライフサイクルを共有すべきです。短い接頭辞を用いて1つのビジュアライザを生成することで、オブジェクト名の一意性を保ち、他のチャートオブジェクトに触れずにまとめて削除可能にします。

// Session visualizer instance
CSessionVisualizer g_sessions("SESS_");

5) セッション情報:ティックでリフレッシュする最小状態

トレーダーは一目で状況を把握したいものです。「今どのセッションか?前回のセッションはどう終わったか?」パネル描画に必要な最小データを保持します。セッション名、O/C、性質(強気/弱気/中立)、最後の更新時刻(無駄な再計算を避けるため)です。

// Session data storage
string   g_CurrentSessionName = "";
double   g_CurrentSessionOpen = 0;
double   g_CurrentSessionCurrent = 0;
string   g_CurrentSessionNature = "";
datetime g_CurrentSessionStart = 0;

string   g_PrevSessionName = "";
double   g_PrevSessionOpen = 0;
double   g_PrevSessionClose = 0;
string   g_PrevSessionNature = "";
datetime g_LastSessionUpdate = 0;

6) 新パネルに合わせてキャンバス背景を拡張

セッション情報ブロックは、現在と前回のセッション概要を3行表示します。パネルの幅と高さを微調整して、レイアウトが窮屈にならず、テキストが改行されないようにします。

// Canvas background - INCREASED WIDTH
CCanvas g_bgCanvas;
string  g_bg_name = "";
int     g_bg_x = 6;
int     g_bg_y = 44;  // uses Y_OFFSET in OnInit
int     g_bg_w = 450; // increased from 430
int     g_bg_h = 340; // increased from 300 for session info

7) OnInit()で新しいノブを初期化

OnInit()では入力値をランタイム状態に反映させます。ビジュアライザには、セッション専用のサイドチャンネル設定(GMTオフセット、ヒゲ切替、ヒゲ透明度)を伝えます。SetFillColors()は現在のセッション描画では塗りつぶしを使用していませんが、将来の互換性のために呼び出しは保持します。

// Initialize session visualizer with separate session wick params
g_sessions.SetGMTOffset(InpGMTOffset);
g_sessions.SetFillColors(g_FillBull, g_FillBear);   // kept for compatibility (no body fill in visualizer)
g_sessions.SetShowWicks(g_ShowSessionWicks);        // session-specific wick setting
g_sessions.SetWickAlpha(g_SessionWickAlpha);        // session-specific wick alpha

// copy panel flag
g_ShowSessionInfo = InpShowSessionInfo;

8) 「セッション情報」UIの追加

 コントロールパネル内に小さなラベルを描画し、現在セッション(名前、始値、性質)と前回セッション(名前、O/C、性質)を表示します。色は強気/弱気をやさしく補助します。パネルは一度作成され、内容は毎ティック更新されます。

void UpdateSessionInfo()
{
   if(!g_ShowSessionInfo) return;

   datetime now = TimeCurrent();

   // Always refresh current session nature
   UpdateCurrentSessionInfo(now);

   // Update previous session only at boundaries
   if(g_LastSessionUpdate == 0 || IsNewSessionStart(now))
   {
      UpdatePreviousSessionInfo(now);
      g_LastSessionUpdate = now;
   }

   UpdateSessionDisplay();
}

9) スマートなセッション管理(現在+前回)

関心事を3つのヘルパーに分離しています。

  1. UpdateCurrentSessionInfo:現在のセッションを(GMT基準で)検出し、始値をキャッシュ、ライブBidと比較して性質を判定します。
  2. UpdatePreviousSessionInfo:新しいセッション開始時に、前回セッションのO/Cと性質のスナップショットを作成します。
  3. UpdateSessionDisplay:最新の文字列/色をラベルオブジェクトに反映します。

これは意図的に軽量に設計されており、EAのHTF更新サイクルと干渉しません。対照的に、以前のEAにはこの概念がないため、パネルと境界ロジックはこのバージョンで完全に新設されています。 

void UpdateSessionInfo()
{
   if(!g_ShowSessionInfo) return;

   datetime now = TimeCurrent();

   // Always refresh current session nature
   UpdateCurrentSessionInfo(now);

   // Update previous session only at boundaries
   if(g_LastSessionUpdate == 0 || IsNewSessionStart(now))
   {
      UpdatePreviousSessionInfo(now);
      g_LastSessionUpdate = now;
   }

   UpdateSessionDisplay();
}

10) ダッシュボードでのセッションコントロール

トレーダーが瞬時に切替できるよう、新しいボタンを2つ追加しました。1つはセッションのオン/オフを切り替えるボタン、もう1つはセッションの髭を切り替えるボタンです。主要期間コントロールは独立したままです。ボタンは既存切替の横に配置し、操作の習慣を損なわないようにしています。

// NEW ROW: Session wicks, Sessions, Major VLines (swapped positions for clarity)
CreateButton(btn_toggle_session_wicks, 12,  86 + Y_OFFSET, 130, 22, g_ShowSessionWicks ? "Sess Wicks: ON" : "Sess Wicks: OFF");
CreateButton(btn_toggle_sessions,      152, 86 + Y_OFFSET, 130, 22, g_ShowSessions ? "Sessions: ON" : "Sessions: OFF");
CreateButton(btn_toggle_maj_vlines,    292, 86 + Y_OFFSET, 130, 22, g_ShowMajorVLines ? "Maj VLines: ON" : "Maj VLines: OFF");

クリックハンドラは、状態を反転させてリフレッシュするだけです。

if(obj == btn_toggle_session_wicks)
{
   g_ShowSessionWicks = !g_ShowSessionWicks;
   ObjectSetString(main_chart_id, btn_toggle_session_wicks, OBJPROP_TEXT,
                   g_ShowSessionWicks ? "Sess Wicks: ON" : "Sess Wicks: OFF");
   RefreshLines();
   return;
}

if(obj == btn_toggle_sessions)
{
   g_ShowSessions = !g_ShowSessions;
   ObjectSetString(main_chart_id, btn_toggle_sessions, OBJPROP_TEXT,
                   g_ShowSessions ? "Sessions: ON" : "Sessions: OFF");
   RefreshLines();
   return;
}

11) RefreshLines()内でセッションを描画(集中管理)

EAの再描画の中心はRefreshLines()です。HTFオブジェクトの管理が終わった後、最新のセッション設定に基づいて、ビジュアライザにセッション描画をおこなわせるか、あるいはクリアさせることができます。ここに処理を集中させることで、描画の更新サイクルが一元化され、クリーンアップも1回で済むことを保証します。

// Draw sessions if toggled - NOW WITH SEPARATE WICK SETTINGS
if(g_ShowSessions)
{
   g_sessions.SetFillColors(g_FillBull, g_FillBear);     // no-op for bodies today; future-proof
   g_sessions.SetShowWicks(g_ShowSessionWicks);
   g_sessions.SetWickAlpha(g_SessionWickAlpha);
   g_sessions.RefreshSessions(InpSessionLookback);
}
else
{
   g_sessions.ClearAll();
}

旧バージョンのEAでは、RefreshLines()はHTFおよび副次表示のみを処理していました。今回、セッション描画をここに集約することで、単一のタイマー/ティックですべての描画を管理でき、ちらつきや競合条件を防ぐことができます。 

12) Tickごとの更新とDeinitでのクリーンアップ

毎ティックで、高レベル状態を再計算します。新しいバーの状態、そしてセッション情報ブロックも更新されます。deinit時には、セッション描画もクリアされ、再アタッチ時に常に初期状態から開始できるようにします。

void OnTick()
{
   bool need_refresh = false;
   // ... (bar-change checks for majors/minors)

   // Always update session info on tick for real-time current session nature
   UpdateSessionInfo();

   if(need_refresh) RefreshLines();
}

void DeleteAllHTFLines()
{
   // remove HTF objects
   // ...
   // Also clear sessions
   g_sessions.ClearAll();
}

13) 復元時のパネル高さ調整(新セクションに対応)

最小化/復元時には、背景の高さがコンテンツに合わせて調整される必要があります。復元時の高さを引き上げることで、セッション情報が他のチャート要素と重ならずに完全に表示されるようにします。

void RestoreUI()
{
   UpdateBackgroundHeight(340);  // was 250; space for the Session Info
   CreateAllOtherUIObjects();
   ObjectSetString(main_chart_id, btn_minimize, OBJPROP_TEXT, "Minimize");
}


テスト

このテストでは、EAをストラテジーテスターではなくライブチャートに直接アタッチします。今回の目的はチャート上の描画表示の確認に限定しているためです。これにより、描画やコントロールが意図通りに機能しているかを検証できます。以下の画像は、まとめられた機能が実際に動作している様子を示しています。

MPSにおけるSessionVisualizerの表示例

図3:FXセッションと情報パネルを表示した最終結果



結論

外国為替市場や株式市場におけるセッションは、正しく理解し、適切に可視化することで、トレーダーや金融アナリストに多くの有益な洞察をもたらします。本記事で実装した手法では、取引セッションをローソク足の期間として表現することで、各セッションの始値、高値、安値、終値(OHLC)といった重要な情報を明確に捉えることが可能になります。このアプローチにより、従来のローソク足分析の知識をセッション単位の分析へと自然に拡張でき、過去のセッションの性質をもとに、次のセッションで起こり得る値動きを考察する手助けとなります。

今回の開発では、チャート上でセッション表示の切り替え、調整、同期を直感的に行えるビジュアルマーカーと、動的制御ユーティリティであるMarket Periods Synchronizerを導入しました。さらに、セッション情報パネルでは、現在および直前のセッションについて、始値、終値、方向性(強気/弱気)を要約して表示し、市場に作用している力関係を素早く把握できるようにしています。

セッションの強気や弱気といった性質は、比較的長い時間幅で測定されるため、短期的なノイズを超えた市場全体のバイアスやトレンド傾向を反映することが少なくありません。これらの振る舞いを理解することは、テクニカル分析の精度を高めるだけでなく、市場のダイナミクスを視覚的に捉えることで、取引における判断力や心理面の強化にもつながります。

読者の皆さんも、ぜひ実験や改良をおこない、コメント欄で洞察を共有してください。参考用のソースファイルも添付されており、継続的な学習と探索に活用できます。



重要な学び

重要な学び説明
クラス統合とモジュール設計CSessionVisualizerのような再利用可能モジュールを構築することで、より整理されたアーキテクチャと保守性の向上が可能になります。可視化ロジックをクラスに分離することで、EAは柔軟性を獲得し、セッション描画の変更や他プロジェクトでの再利用が、コアコードを書き直すことなくおこなえます。
ランタイム変数とUI同期入力パラメータをランタイムグローバル変数に反映させることで、スライダー、ボタン、切替などのリアルタイムUIコントロールが、EAの再初期化なしで描画挙動を動的に更新できます。これは、オブジェクトベースUIを用いた応答性の高いチャートユーティリティ設計の方法を示しています。
セッション管理とデータマッピング構造化されたセッションデータ(GMTベースの開始/終了時刻、始値/終値計算など)の利用は、ライブチャートデータと時間ベースの取引ロジックの橋渡しを示しています。分析と可視化を融合させることで、市場状況をより直感的に把握できることを学べます。


添付ファイル

ソースファイル名バージョン説明
SessionVisualizerTest.mqh1.0CSessionVisualizerクラスの描画と挙動を検証するためのスタンドアロンテスト用スクリプト。セッション矩形、色、時刻マッピングが正しく表示されるかを確認し、より大きなシステムへの統合前に視覚ロジックを分離して検証できます。
SessionTest.mq51.0SessionVisualizerTestヘッダの機能をテスト・デモ用に簡略化したEA
SessionVisualizer.mqh1.0FXセッションの可視化を管理するメインクラス実装。タイムゾーン調整、色マッピング、セッション境界のチャート描画を扱い、任意のEAやインジケーターでセッションベースの文脈を再利用できるモジュールとして機能します。
MarketPeriodsSychronizer_EA.mq51.02SessionVisualizerと新しいセッション情報パネルを統合したアップグレード版EA。複数時間軸のマーカー、セッション表示、リアルタイム分析を統合したコントロールダッシュボードを提供し、トレーダーにインタラクティブで教育的な市場期間可視化ツールを提供します。
目次に戻る

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

最後のコメント | ディスカッションに移動 (1)
stu48
stu48 | 16 11月 2025 において 07:39
こんにちは、クレマンス。
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MQL5のテーブルモデルに基づくテーブルクラスとヘッダクラス:MVC概念の適用 MQL5のテーブルモデルに基づくテーブルクラスとヘッダクラス:MVC概念の適用
これは、MQL5でのテーブルモデル実装をMVC (Model-View-Controller)アーキテクチャパラダイムに基づいて解説する記事の第2部です。本記事では、前回作成したテーブルモデルをもとに、テーブルクラスおよびテーブルヘッダの開発について説明します。開発したクラスは、次回の記事で扱うビューおよびコントローラーコンポーネントの実装の基礎となります。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
FX裁定取引:合成通貨の動きとその平均回帰の分析 FX裁定取引:合成通貨の動きとその平均回帰の分析
本記事では、PythonおよびMQL5を用いて合成通貨の動きを分析し、現在のFX裁定取引の実現可能性について検討します。また、合成通貨を分析するための既製Pythonコードを紹介するとともに、FXにおける合成通貨の概念についても詳しく解説します。