English Русский Deutsch
preview
MQL5取引ツール(第23回):カメラ制御対応DirectX 3Dグラフによる二項分布分析

MQL5取引ツール(第23回):カメラ制御対応DirectX 3Dグラフによる二項分布分析

MetaTrader 5トレーディングシステム |
21 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

現在、二項分布を2次元(2D)でグラフ表示するツールがありますが、奥行きを利用した可視化がない場合、確率質量関数のパターンを詳細に確認することが難しくなることがあります。バーの重なりは平面的に見え、頻度の差が空間的なコントラストとして見えにくくなり、分析視点を切り替える際にも、単純な切り替えではなく再起動が必要になります。本記事は、より深い確率的洞察を得るために、インタラクティブな3次元(3D)描画機能を統計可視化ツールへ拡張したいMetaQuotes Language 5 (MQL5)開発者およびアルゴリズムトレーダーを対象としています。

前回の記事(第22回)では、インタラクティブなCanvas上で、シミュレーションサンプルのヒストグラムと理論的な確率質量関数曲線を表示する、二項分布可視化用のMQL5可視化ツールを構築しました。第23回となる今回は、MQL5の二項分布ビューワーにDirect3Dを統合し、2D/3Dモードの切り替えと、回転、ズーム、自動フィットを可能にするカメラ制御機能を実装します。本記事では、グラウンドプレーンや軸を含む3Dヒストグラムバーの描画方法、PMF曲線の投影方法、そして2D統計情報、凡例、テーマ設定を維持する方法について説明します。また、クラスベースのアーキテクチャ、マウス操作、リアルタイム更新、頻度やPMF形状の確認を改善するためのパラメータ調整についても扱います。本記事では以下のトピックを扱います。

  1. DirectX 3D可視化フレームワークのアーキテクチャを理解する
  2. MQL5での実装
  3. バックテスト
  4. 結論

この記事を読み終える頃には、二項分布分析のための3D機能を備えた実用的なMQL5ツールを手に入れ、さらにカスタマイズできる状態になります。それでは、始めましょう。


DirectX 3D可視化フレームワークのアーキテクチャを理解する

MQL5のDirectX 3D可視化フレームワークは、ハードウェアアクセラレーションによるグラフィックスを活用し、チャート上で複雑な3Dシーンを描画します。このフレームワークはCanvasシステムと統合されており、シームレスな2D/3Dモードの切り替えと、インタラクティブな操作を可能にします。DirectXは、ヒストグラムバー用のボックス、グラウンドプレーン、軸用のラインなど、3Dオブジェクトを効率的に描画するために使用されます。同時に、カメラ位置、ライティング、投影処理を管理することで、データ表示に奥行きと遠近感を与えます。このアーキテクチャは、回転、ズーム、自動フィットなどの動的なユーザー操作をサポートしており、2Dでは確認しにくいパターンを視覚的な奥行きによって明らかにできるため、トレード環境における分布データのような多次元データの探索に適しています。

本記事では、2D二項分布グラフ描画ツールを基盤として、3Dモードを追加します。この3Dモードでは、ヒストグラムバーを3Dで可視化し、グラウンドプレーンや方向確認用の色付き軸を組み込み、カメラ操作によって確率質量関数や頻度をより詳細に確認できるようにします。設計の概要は、Canvas作成、3Dオブジェクト初期化、シミュレーションデータの読み込み、リアルタイム応答のためのイベント駆動型更新処理を管理するクラスベース構造です。2Dおよび3D描画ロジックをカプセル化するビジュアライザークラスを定義し、ボックスプリミティブを使用して3D要素を作成します。また、カメラ制御のための投影行列とビュー行列を設定し、ドラッグ操作、サイズ変更、ホイールズームなどのインタラクティブ機能とモード切り替えを統合します。最終的に、このフレームワークは平面的なデータグラフをインタラクティブな3Dモデルへ変換し、より深い分析と洞察を可能にします。以下が完成イメージです。

DIRECTX 3DアーキテクチャGIF


MQL5での実装

3D対応のためのライブラリ、列挙型、入力設定の追加

3D描画を可能にするためには、まずプログラムの基盤を拡張する必要があります。そのため、3D CanvasおよびDirectXボックスをサポートする適切なライブラリを追加します。さらに、プログラムを再起動することなく、実行中に2Dモードと3Dモードを切り替えられるようにする列挙型を定義します。そして、プログラムの読み込み時点から3D環境の外観や動作を制御する入力パラメータと定数を設定します。そのためのロジックを以下に実装します。

//+------------------------------------------------------------------+
//|    Canvas Graphing PART 3.1 - Statistical Distributions (3D).mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict

#include <Canvas\Canvas.mqh>
#include <Canvas\Canvas3D.mqh>
#include <Canvas\DX\DXBox.mqh>
#include <Math\Stat\Binomial.mqh>
#include <Math\Stat\Math.mqh>

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ResizeDirection {
   NO_RESIZE,                                           // No resize
   RESIZE_BOTTOM_EDGE,                                  // Resize bottom edge
   RESIZE_RIGHT_EDGE,                                   // Resize right edge
   RESIZE_CORNER                                        // Resize corner
};

enum ViewModeType {
   VIEW_2D_MODE,                                        // 2D mode
   VIEW_3D_MODE                                         // 3D mode
};

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
sinput group "=== VIEW MODE SETTINGS ==="
input ViewModeType     viewMode = VIEW_2D_MODE;         // View Mode (2D or 3D)

sinput group "=== 3D VIEW SETTINGS ==="
input bool               autoFitCamera = true;          // Auto-Fit Camera on Load
input double             initialCameraDistance = 60.0;  // Initial Camera Distance (3D)
input double             initialCameraAngleX = 0.6;     // Initial Camera Angle X (3D)
input double             initialCameraAngleY = 0.8;     // Initial Camera Angle Y (3D)

sinput group "=== 3D GROUND AND AXES SETTINGS ==="
input color              groundPlaneColor = clrLightGray; // Ground Plane Color
input double             groundPlaneOpacity = 0.6;      // Ground Plane Opacity (0-1)
input float              groundPlaneWidth = 50.0;       // Ground Plane Width (X direction)
input float              groundPlaneDepth = 20.0;       // Ground Plane Depth (Z direction)
input float              groundPlaneThickness = 0.5;    // Ground Plane Thickness (Y direction)
input bool               show3DAxes = true;             // Show 3D Axes
input color              axisXColor = clrRed;           // X Axis Color
input color              axisYColor = clrGreen;         // Y Axis Color
input color              axisZColor = clrBlue;          // Z Axis Color
input float              axisLength = 20.0;             // Axis Length
input float              axisThickness = 0.1f;          // Axis Thickness

//+------------------------------------------------------------------+
//| Constants                                                        |
//+------------------------------------------------------------------+
const int MIN_CANVAS_WIDTH = 300;
const int MIN_CANVAS_HEIGHT = 200;
const int HEADER_BAR_HEIGHT = 35;
const int SWITCH_ICON_SIZE = 24;
const int SWITCH_ICON_MARGIN = 6;

実装は、まず必要な追加ライブラリをインクルードすることから始めます。#include <Canvas\Canvas3D.mqh>を追加して3D描画機能を有効化し、#include <Canvas\DX\DXBox.mqh>を追加して3Dモデルで使用するDirectXボックスプリミティブを利用できるようにします。次に、ユーザー表示モード用の列挙型を追加します。ViewModeType列挙型には、表示形式を切り替えるためのVIEW_2D_MODEとVIEW_3D_MODEが含まれます。続いて、整理のためにグループ化された追加の入力パラメータを設定します。まず、viewModeによって表示モードを選択できるようにし、デフォルト値はVIEW_2D_MODEに設定します。3D専用の設定として、カメラ位置を自動調整するautoFitCamera、初期カメラ距離を設定するinitialCameraDistance、初期カメラ角度を設定するinitialCameraAngleXおよびinitialCameraAngleYなどの入力項目を追加します。

さらに、3Dグラウンドおよび軸に関する入力項目も追加します。これには、groundPlaneColor、groundPlaneOpacity、groundPlaneWidth、groundPlaneDepth、groundPlaneThicknessなどのグラウンドプレーン設定、そしてshow3DAxesによる3D軸表示の切り替え、axisXColorなどの軸カラー設定、axisLengthおよびaxisThicknessによる軸の長さと太さの調整が含まれます。これにより、3D環境を柔軟にカスタマイズできます。最後に、モード切り替えボタン用として、SWITCH_ICON_SIZEやSWITCH_ICON_MARGINなどの追加アイコン関連定数を宣言します。変更箇所を明確にするため、該当部分をハイライトしています。 次に、すべてのグローバル変数と関数をクラスへリファクタリングし、管理を容易にするとともに、コードをモジュール化します。まずはグローバル変数から開始し、これらをクラスのメンバー変数へ移行します。

ビジュアライザークラスとメンバー変数の定義

コードを整理し、拡張可能な構造にするため、このツール全体を単一のクラスへリファクタリングします。これにより、すべての状態変数、描画ロジック、操作処理を1つのクラス内で管理します。このセクションでは、クラス定義を作成し、Canvas識別情報、ウィンドウサイズ、ユーザー操作状態、3Dカメラの向き、バー・グラウンドプレーン・軸などのDirectXシーンオブジェクト、さらに可視化を駆動するすべてのデータ配列や統計指標を管理するために必要なメンバー変数を宣言します。

//+------------------------------------------------------------------+
//| Distribution visualization window class                          |
//+------------------------------------------------------------------+
class DistributionVisualizer
  {
protected:
   CCanvas3D         m_mainCanvas;           // Main 3D-capable canvas
   string            m_canvasObjectName;     // Chart object name for the canvas bitmap

   int               m_currentPositionX;     // Current X position of the canvas on chart
   int               m_currentPositionY;     // Current Y position of the canvas on chart
   int               m_currentWidth;         // Current canvas width in pixels
   int               m_currentHeight;        // Current canvas height in pixels

   bool              m_isDragging;           // True while user is dragging the canvas
   bool              m_isResizing;           // True while user is resizing the canvas
   int               m_dragStartX;           // Mouse X when drag began
   int               m_dragStartY;           // Mouse Y when drag began
   int               m_canvasStartX;         // Canvas X when drag began
   int               m_canvasStartY;         // Canvas Y when drag began
   int               m_resizeStartX;         // Mouse X when resize began
   int               m_resizeStartY;         // Mouse Y when resize began
   int               m_resizeInitialWidth;   // Canvas width at resize start
   int               m_resizeInitialHeight;  // Canvas height at resize start
   ResizeDirection   m_activeResizeMode;     // Currently active resize direction
   ResizeDirection   m_hoverResizeMode;      // Resize direction under cursor hover

   bool              m_isHoveringCanvas;      // True when mouse is over the canvas
   bool              m_isHoveringHeader;      // True when mouse is over the header bar
   bool              m_isHoveringResizeZone;  // True when mouse is in a resize grip zone
   bool              m_isHoveringSwitchIcon;  // True when mouse is over the mode switch icon
   int               m_lastMouseX;            // Last recorded mouse X coordinate
   int               m_lastMouseY;            // Last recorded mouse Y coordinate
   int               m_previousMouseButtonState; // Mouse button state on previous event

   ViewModeType      m_currentViewMode;      // Active view mode (2D or 3D)
   bool              m_are3DObjectsCreated;  // True once 3D scene objects have been built

   CDXBox            m_histogramBars[];      // Array of 3D boxes representing histogram bars
   CDXBox            m_groundPlane;          // 3D box used as the ground reference plane
   CDXBox            m_axisX;                // 3D box representing the X axis
   CDXBox            m_axisY;                // 3D box representing the Y axis
   CDXBox            m_axisZ;                // 3D box representing the Z axis

   double            m_cameraDistance;       // Distance from camera to scene origin
   double            m_cameraAngleX;         // Camera elevation angle (radians)
   double            m_cameraAngleY;         // Camera azimuth angle (radians)
   int               m_mouse3DStartX;        // Mouse X when 3D rotation drag began
   int               m_mouse3DStartY;        // Mouse Y when 3D rotation drag began
   bool              m_isRotating3D;         // True while user is rotating the 3D scene

   double            m_sampleData[];                   // Raw binomial sample values
   double            m_histogramIntervals[];           // Histogram bin center positions
   double            m_histogramFrequencies[];         // Scaled frequency per histogram bin
   double            m_theoreticalXValues[];           // X values for the theoretical PMF curve
   double            m_theoreticalYValues[];           // Y values for the theoretical PMF curve
   double            m_minDataValue;                   // Minimum value in data range
   double            m_maxDataValue;                   // Maximum value in data range
   double            m_maxFrequency;                   // Peak raw frequency across all bins
   double            m_maxTheoreticalValue;            // Peak theoretical PMF value
   bool              m_isDataLoaded;                   // True once distribution data is ready

   double            m_sampleMean;                     // Computed sample mean
   double            m_sampleStandardDeviation;        // Computed sample standard deviation
   double            m_sampleSkewness;                 // Computed sample skewness
   double            m_sampleKurtosis;                 // Computed sample excess kurtosis
   double            m_percentile25;                   // 25th percentile (Q1)
   double            m_percentile50;                   // 50th percentile (median)
   double            m_percentile75;                   // 75th percentile (Q3)
   double            m_confidenceInterval95Lower;      // Lower bound of 95% confidence interval
   double            m_confidenceInterval95Upper;      // Upper bound of 95% confidence interval
   double            m_confidenceInterval99Lower;      // Lower bound of 99% confidence interval
   double            m_confidenceInterval99Upper;      // Upper bound of 99% confidence interval
};

ここでは、二項分布の2Dおよび3D可視化を管理する「ウィンドウマネージャ」として、グラフ描画ツール全体のロジックをカプセル化するDistributionVisualizerクラスを定義します。protectedセクションでは、まず3D対応の描画をおこなうためのCCanvas3Dオブジェクトであるm_mainCanvas、オブジェクト識別用の文字列m_canvasObjectName、現在位置やサイズを管理する整数変数であるm_currentPositionXやm_currentWidthなどを宣言します。また、ResizeDirection列挙型を使用したm_isDragging、m_dragStartX、m_activeResizeModeなど、ユーザー操作状態を管理するためのブール値や整数変数も含まれます。表示関連の変数として、ViewModeTypeから取得するm_currentViewMode、3Dオブジェクトが作成済みかを管理するm_are3DObjectsCreatedを用意します。さらに、3D要素を構築するため、CDXBox配列であるm_histogramBars、そして個別のボックスオブジェクトであるm_groundPlane、m_axisX、m_axisY、m_axisZを宣言します。

カメラ制御については、m_cameraDistance、m_cameraAngleX、m_cameraAngleYなどのdouble型変数を使用し、3D操作用のトラッカーとしてm_mouse3DStartXやm_isRotating3Dを用意します。データ保存には、サンプルデータ、ヒストグラム、理論値を保持する配列であるm_sampleDataやm_histogramFrequenciesを使用します。また、最小値・最大値を管理する変数、データ読み込み状態を示すフラグm_isDataLoaded、さらにm_sampleMeanやm_confidenceInterval95Lowerなどの統計情報を保持するメンバー変数も含まれます。ここでは、すべてのメンバー変数を個別に説明することはしません。その多くは以前のバージョンと同じものであり、今回追加された内容については理解しやすいように詳細なコメントを付けています。これで準備が整ったため、次に変数を初期化するためのpublicコンストラクタを作成します。

クラスインスタンスの初期化と破棄

クラス構造が完成したので、次にコンストラクタを用意します。コンストラクタは、描画やユーザー操作が開始される前に、すべてのメンバー変数を安全で予測可能な初期状態へ設定します。また、デストラクタでは、3Dバー、グラウンドプレーン、軸などに関連するDirectXリソースを明示的に解放します。これにより、オブジェクトが不要になった際にGPUメモリが残されたままになることを防ぎ、プログラムを正常に終了できるようにします。

public:
   //+------------------------------------------------------------------+
   //| Initialize all member variables to safe defaults                 |
   //+------------------------------------------------------------------+
   DistributionVisualizer(void)
     {
      //--- Set canvas object name
      m_canvasObjectName = "DistCanvas";
      //--- Set initial canvas X position from input
      m_currentPositionX = initialCanvasX;
      //--- Set initial canvas Y position from input
      m_currentPositionY = initialCanvasY;
      //--- Set initial canvas width from input
      m_currentWidth = initialCanvasWidth;
      //--- Set initial canvas height from input
      m_currentHeight = initialCanvasHeight;

      //--- Reset drag state
      m_isDragging = false;
      //--- Reset resize state
      m_isResizing = false;
      //--- Reset drag origin X
      m_dragStartX = 0;
      //--- Reset drag origin Y
      m_dragStartY = 0;
      //--- Reset canvas position snapshot X
      m_canvasStartX = 0;
      //--- Reset canvas position snapshot Y
      m_canvasStartY = 0;
      //--- Reset resize origin X
      m_resizeStartX = 0;
      //--- Reset resize origin Y
      m_resizeStartY = 0;
      //--- Reset width snapshot for resize
      m_resizeInitialWidth = 0;
      //--- Reset height snapshot for resize
      m_resizeInitialHeight = 0;
      //--- Reset active resize direction
      m_activeResizeMode = NO_RESIZE;
      //--- Reset hover resize direction
      m_hoverResizeMode = NO_RESIZE;

      //--- Reset canvas hover flag
      m_isHoveringCanvas = false;
      //--- Reset header hover flag
      m_isHoveringHeader = false;
      //--- Reset resize zone hover flag
      m_isHoveringResizeZone = false;
      //--- Reset switch icon hover flag
      m_isHoveringSwitchIcon = false;
      //--- Reset last mouse X
      m_lastMouseX = 0;
      //--- Reset last mouse Y
      m_lastMouseY = 0;
      //--- Reset previous mouse button state
      m_previousMouseButtonState = 0;

      //--- Set view mode from input
      m_currentViewMode = viewMode;
      //--- Mark 3D objects as not yet created
      m_are3DObjectsCreated = false;

      //--- Set camera distance from input
      m_cameraDistance = initialCameraDistance;
      //--- Set camera elevation angle from input
      m_cameraAngleX = initialCameraAngleX;
      //--- Set camera azimuth angle from input
      m_cameraAngleY = initialCameraAngleY;
      //--- Reset 3D rotation drag origin X
      m_mouse3DStartX = -1;
      //--- Reset 3D rotation drag origin Y
      m_mouse3DStartY = -1;
      //--- Mark 3D rotation as inactive
      m_isRotating3D = false;

      //--- Reset data range minimum
      m_minDataValue = 0.0;
      //--- Reset data range maximum
      m_maxDataValue = 0.0;
      //--- Reset peak histogram frequency
      m_maxFrequency = 0.0;
      //--- Reset peak theoretical PMF value
      m_maxTheoreticalValue = 0.0;
      //--- Mark data as not yet loaded
      m_isDataLoaded = false;

      //--- Reset sample mean
      m_sampleMean = 0.0;
      //--- Reset standard deviation
      m_sampleStandardDeviation = 0.0;
      //--- Reset skewness
      m_sampleSkewness = 0.0;
      //--- Reset kurtosis
      m_sampleKurtosis = 0.0;
      //--- Reset 25th percentile
      m_percentile25 = 0.0;
      //--- Reset median
      m_percentile50 = 0.0;
      //--- Reset 75th percentile
      m_percentile75 = 0.0;
      //--- Reset 95% CI lower bound
      m_confidenceInterval95Lower = 0.0;
      //--- Reset 95% CI upper bound
      m_confidenceInterval95Upper = 0.0;
      //--- Reset 99% CI lower bound
      m_confidenceInterval99Lower = 0.0;
      //--- Reset 99% CI upper bound
      m_confidenceInterval99Upper = 0.0;
}

DistributionVisualizerクラスのコンストラクタでは、インスタンス生成時にメンバ変数をデフォルト値または入力値に基づいて初期化します。まず、Canvas名をDistCanvasに設定し、初期位置やサイズにはユーザー入力から取得した値を割り当てます。次に、インタラクション用のフラグをfalseにリセットし、座標トラッカーを0に設定します。また、サイズ変更モードをNO_RESIZEに初期化します。ホバー状態もfalseに設定し、マウスのトラッカー値を0にします。表示モードにはviewModeの値を設定し、3Dオブジェクト用のフラグはfalseに初期化します。カメラについては、初期距離と角度を設定し、3Dマウスの値を-1に、回転フラグをfalseに設定します。最後に、データの最小値・最大値、およびロード状態を適切な初期値に設定し、統計的な指標もすべて0に初期化します。これにより、クラスは各種操作を実行できる準備が整います。次に、初期化解除時に要素を破棄する必要があるため、クラスのデストラクタでその処理を実装します。デストラクタは通常、コンストラクタと同じようにクラス名を使用しますが、その前にチルダ(~)を付ける点が異なります。

//+------------------------------------------------------------------+
//| Release all 3D scene objects on destruction                      |
//+------------------------------------------------------------------+
~DistributionVisualizer(void)
  {
   //--- Get total number of histogram bar boxes
   int count = ArraySize(m_histogramBars);
   //--- Loop over every bar and release its DirectX resources
   for(int i = 0; i < count; i++)
     {
      m_histogramBars[i].Shutdown();
     }
   //--- Release ground plane DirectX resources
   m_groundPlane.Shutdown();
   //--- Release X axis DirectX resources
   m_axisX.Shutdown();
   //--- Release Y axis DirectX resources
   m_axisY.Shutdown();
   //--- Release Z axis DirectX resources
   m_axisZ.Shutdown();
  }

ここではDistributionVisualizerクラスのデストラクタをを定義し、オブジェクトが破棄される際にリソースが適切に解放されるようにします。まず、m_histogramBarsに対してArraySizeを使用してヒストグラムバーの数を取得し、その後、各バーを順番にループ処理してShutdownメソッドを呼び出します。これにより、3Dバーに関連付けられたDirectXリソースが解放されます。次に、m_groundPlane、m_axisX、m_axisY、m_axisZに対してもShutdownを呼び出し、グラウンドプレーンや各軸オブジェクトのリソースを解放します。これにより、メモリリークを防ぎ、クリーンな終了処理を実現します。デストラクタは自動的に呼び出されるという点を理解しておくことが重要です。デストラクタの定義は必須ではありませんが、リソースを適切に解放するために定義することが推奨されます。

次に、クラスのメンバー関数を定義します。関数は、クラス内部で宣言した後、スコープ演算子(::)を使用してクラス外部で定義することもできます。しかし今回は、複雑さを減らすため、クラス内部のpublic領域で関数として宣言する方法を採用します。まずは3Dの可視化から始めます。

3Dヒストグラムバーとカメラの構築

ここでは、3Dシーンの中心となる部分を構築します。DirectXのボックスプリミティブとして各ヒストグラムバーを生成し、色を設定するための関数を定義します。また、実際のバーの高さに基づいて、シーン全体が画面内に収まるように自動調整されたカメラ距離を計算します。さらに、バーを視覚的に配置するための基準となるグラウンドプレーンを作成し、空間内での方向や位置関係を把握できるようにX軸、Y軸、Z軸のラインを構築します。加えて、現在の角度や距離の値に基づいて、各フレームごとにカメラのワールド座標位置とライティング方向を更新します。そして、元となる分布データが変更された場合には、各バーの位置やスケールを動的に再配置・再調整します。これらの処理によって、データ変化に対応したリアルタイムな3Dヒストグラム表示を実現します。

//+------------------------------------------------------------------+
//| Allocate and configure one DXBox per histogram bin               |
//+------------------------------------------------------------------+
bool create3DHistogramBars()
  {
   //--- Allocate the bar array to match the required bin count
   ArrayResize(m_histogramBars, histogramCells);

   //--- Decompose histogram colour into RGB byte components
   uchar r = (uchar)((histogramColor)       & 0xFF);
   uchar g = (uchar)((histogramColor >> 8)  & 0xFF);
   uchar b = (uchar)((histogramColor >> 16) & 0xFF);

   //--- Create and configure each bar box
   for(int i = 0; i < histogramCells; i++)
     {
      //--- Create unit box; actual transform applied later in update step
      if(!m_histogramBars[i].Create(m_mainCanvas.DXDispatcher(), m_mainCanvas.InputScene(),
                                    DXVector3(-0.5f, 0.0f, -0.5f),
                                    DXVector3(0.5f, 1.0f, 0.5f)))
        {
         Print("ERROR: Failed to create 3D box for bar ", i);
         return false;
        }

      //--- Set the bar's base diffuse colour from the histogram colour input
      m_histogramBars[i].DiffuseColorSet(DXColor(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f));
      //--- Add a subtle specular highlight for depth perception
      m_histogramBars[i].SpecularColorSet(DXColor(0.2f, 0.2f, 0.2f, 0.3f));
      //--- Set specular shininess exponent
      m_histogramBars[i].SpecularPowerSet(32.0f);
      //--- Disable self-emission (bars lit only by scene lights)
      m_histogramBars[i].EmissionColorSet(DXColor(0.0f, 0.0f, 0.0f, 0.0f));

      //--- Register the bar with the 3D scene
      m_mainCanvas.ObjectAdd(GetPointer(m_histogramBars[i]));
     }

   return true;
  }

//+------------------------------------------------------------------+
//| Compute an optimal camera distance and angles for the scene      |
//+------------------------------------------------------------------+
void autoFitCameraPosition()
  {
   //--- Abort if not in 3D mode or data has not been loaded
   if(m_currentViewMode != VIEW_3D_MODE || !m_isDataLoaded) return;

   //--- Fixed scene width used for distance estimation
   float totalWidth  = 30.0f;
   //--- Track the tallest bar in the scene
   float maxBarHeight = 0.0f;

   //--- Use the peak theoretical value as the Y scale reference
   double rangeY = m_maxTheoreticalValue;
   //--- Guard against division by zero
   if(rangeY == 0) rangeY = 1;

   //--- Find the maximum normalised bar height across all bins
   for(int i = 0; i < histogramCells; i++)
     {
      float normalizedHeight = (float)(m_histogramFrequencies[i] / rangeY);
      float barHeight = normalizedHeight * 15.0f;
      if(barHeight > maxBarHeight) maxBarHeight = barHeight;
     }

   //--- Determine bounding extents for the entire scene
   float sceneWidth  = totalWidth;
   float sceneHeight = MathMax(maxBarHeight, 15.0f);
   float sceneDepth  = 10.0f;

   //--- Compute the scene bounding diagonal for FOV-based fitting
   float diagonal = MathSqrt(sceneWidth  * sceneWidth  +
                             sceneHeight * sceneHeight +
                             sceneDepth  * sceneDepth);

   //--- Match the projection FOV used in initialize3DContext
   float fov = (float)(DX_PI / 6.0);
   //--- Derive camera distance so the scene fills the view with a 1.5x margin
   m_cameraDistance = (diagonal / 2.0f) / MathTan(fov / 2.0f) * 1.5;

   //--- Set a comfortable default elevation angle
   m_cameraAngleX = 0.5;
   //--- Set a comfortable default azimuth angle
   m_cameraAngleY = 0.7;

   //--- Enforce minimum distance to avoid clipping near-plane
   if(m_cameraDistance <  35.0) m_cameraDistance =  35.0;
   //--- Enforce maximum distance to keep bars visible
   if(m_cameraDistance > 100.0) m_cameraDistance = 100.0;

   Print("Auto-fit camera: Distance = ", m_cameraDistance,
         ", AngleX = ", m_cameraAngleX, ", AngleY = ", m_cameraAngleY);
  }

//+------------------------------------------------------------------+
//| Create the flat ground reference plane in 3D space               |
//+------------------------------------------------------------------+
bool createGroundPlane()
  {
   //--- Build a thin box spanning the configured width and depth
   if(!m_groundPlane.Create(m_mainCanvas.DXDispatcher(), m_mainCanvas.InputScene(),
                            DXVector3(-groundPlaneWidth / 2.0f, -groundPlaneThickness, -groundPlaneDepth / 2.0f),
                            DXVector3( groundPlaneWidth / 2.0f,  0.0f,                  groundPlaneDepth / 2.0f)))
     {
      Print("ERROR: Failed to create ground plane");
      return false;
     }

   //--- Decompose ground colour into RGB byte components (BGR layout)
   uchar r = (uchar)((groundPlaneColor >> 16) & 0xFF);
   uchar g = (uchar)((groundPlaneColor >> 8)  & 0xFF);
   uchar b = (uchar)( groundPlaneColor         & 0xFF);
   //--- Apply the configured ground colour and opacity
   m_groundPlane.DiffuseColorSet(DXColor(r / 255.0f, g / 255.0f, b / 255.0f,
                                         (float)groundPlaneOpacity));
   //--- Remove specular highlight for a flat matte look
   m_groundPlane.SpecularColorSet(DXColor(0.0f, 0.0f, 0.0f, 0.0f));

   //--- Register the ground plane with the 3D scene
   m_mainCanvas.ObjectAdd(GetPointer(m_groundPlane));

   return true;
  }

//+------------------------------------------------------------------+
//| Create colour-coded X, Y, and Z coordinate axis boxes            |
//+------------------------------------------------------------------+
bool create3DAxes()
  {
   //--- Create X axis as a thin horizontal box along positive X
   if(!m_axisX.Create(m_mainCanvas.DXDispatcher(), m_mainCanvas.InputScene(),
                      DXVector3(0.0f, 0.0f, 0.0f),
                      DXVector3(axisLength, axisThickness, axisThickness)))
     {
      Print("ERROR: Failed to create X axis");
      return false;
     }
   //--- Extract X axis colour components
   uchar rx = (uchar)((axisXColor >> 16) & 0xFF);
   uchar gx = (uchar)((axisXColor >> 8)  & 0xFF);
   uchar bx = (uchar)( axisXColor         & 0xFF);
   //--- Apply X axis diffuse colour
   m_axisX.DiffuseColorSet(DXColor(rx / 255.0f, gx / 255.0f, bx / 255.0f, 1.0f));
   //--- Remove specular for clean axis appearance
   m_axisX.SpecularColorSet(DXColor(0.0f, 0.0f, 0.0f, 0.0f));
   //--- Register X axis with the scene
   m_mainCanvas.ObjectAdd(GetPointer(m_axisX));

   //--- Create Y axis as a thin vertical box along positive Y
   if(!m_axisY.Create(m_mainCanvas.DXDispatcher(), m_mainCanvas.InputScene(),
                      DXVector3(0.0f, 0.0f, 0.0f),
                      DXVector3(axisThickness, axisLength, axisThickness)))
     {
      Print("ERROR: Failed to create Y axis");
      return false;
     }
   //--- Extract Y axis colour components
   uchar ry = (uchar)((axisYColor >> 16) & 0xFF);
   uchar gy = (uchar)((axisYColor >> 8)  & 0xFF);
   uchar by = (uchar)( axisYColor         & 0xFF);
   //--- Apply Y axis diffuse colour
   m_axisY.DiffuseColorSet(DXColor(ry / 255.0f, gy / 255.0f, by / 255.0f, 1.0f));
   //--- Remove specular for clean axis appearance
   m_axisY.SpecularColorSet(DXColor(0.0f, 0.0f, 0.0f, 0.0f));
   //--- Register Y axis with the scene
   m_mainCanvas.ObjectAdd(GetPointer(m_axisY));

   //--- Create Z axis as a thin depth box along positive Z
   if(!m_axisZ.Create(m_mainCanvas.DXDispatcher(), m_mainCanvas.InputScene(),
                      DXVector3(0.0f, 0.0f, 0.0f),
                      DXVector3(axisThickness, axisThickness, axisLength)))
     {
      Print("ERROR: Failed to create Z axis");
      return false;
     }
   //--- Extract Z axis colour components
   uchar rz = (uchar)((axisZColor >> 16) & 0xFF);
   uchar gz = (uchar)((axisZColor >> 8)  & 0xFF);
   uchar bz = (uchar)( axisZColor         & 0xFF);
   //--- Apply Z axis diffuse colour
   m_axisZ.DiffuseColorSet(DXColor(rz / 255.0f, gz / 255.0f, bz / 255.0f, 1.0f));
   //--- Remove specular for clean axis appearance
   m_axisZ.SpecularColorSet(DXColor(0.0f, 0.0f, 0.0f, 0.0f));
   //--- Register Z axis with the scene
   m_mainCanvas.ObjectAdd(GetPointer(m_axisZ));

   return true;
  }

//+------------------------------------------------------------------+
//| Recompute and apply the view matrix from spherical coordinates   |
//+------------------------------------------------------------------+
void updateCameraPosition()
  {
   //--- Only apply in 3D mode
   if(m_currentViewMode != VIEW_3D_MODE) return;

   //--- Start with a camera positioned along the negative Z axis
   DXVector4 camera = DXVector4(0.0f, 0.0f, (float)(-m_cameraDistance), 1.0f);

   //--- Rotate camera around the X axis by the elevation angle
   DXMatrix rotationX;
   DXMatrixRotationX(rotationX, (float)m_cameraAngleX);
   DXVec4Transform(camera, camera, rotationX);

   //--- Rotate the result around the Y axis by the azimuth angle
   DXMatrix rotationY;
   DXMatrixRotationY(rotationY, (float)m_cameraAngleY);
   DXVec4Transform(camera, camera, rotationY);

   //--- Apply the final camera world position
   m_mainCanvas.ViewPositionSet(DXVector3(camera));

   //--- Place the key light slightly above the camera position
   DXVector3 cameraPos = DXVector3(camera.x, camera.y, camera.z);
   DXVector3 lightPos  = DXVector3(cameraPos.x, cameraPos.y + 10.0f, cameraPos.z);
   //--- Scene origin is always the light target
   DXVector3 target    = DXVector3(0.0f, 0.0f, 0.0f);

   //--- Compute the raw light direction vector
   DXVector3 lightDir;
   lightDir.x = target.x - lightPos.x;
   lightDir.y = target.y - lightPos.y;
   lightDir.z = target.z - lightPos.z;

   //--- Compute the vector length for normalisation
   float length = MathSqrt(lightDir.x * lightDir.x +
                           lightDir.y * lightDir.y +
                           lightDir.z * lightDir.z);
   //--- Normalise the direction vector if it has non-zero length
   if(length > 0.0f)
     {
      lightDir.x /= length;
      lightDir.y /= length;
      lightDir.z /= length;
     }

   //--- Apply the normalised light direction to the scene
   m_mainCanvas.LightDirectionSet(lightDir);
  }

//+------------------------------------------------------------------+
//| Reposition and scale every 3D histogram bar to match data        |
//+------------------------------------------------------------------+
void update3DHistogramBars()
  {
   //--- Abort if data has not been loaded
   if(!m_isDataLoaded) return;

   //--- Compute the X data range for spatial mapping
   double rangeX = m_maxDataValue - m_minDataValue;
   //--- Use the peak PMF value as the height scale reference
   double rangeY = m_maxTheoreticalValue;
   //--- Guard against division by zero for X
   if(rangeX == 0) rangeX = 1;
   //--- Guard against division by zero for Y
   if(rangeY == 0) rangeY = 1;

   //--- Total scene width used to space the bars evenly
   float totalWidth = 30.0f;
   //--- Compute even spacing for each bar slot
   float barSpacing = totalWidth / (float)histogramCells;
   //--- Make each bar 80% of its slot to leave a small gap
   float barWidth   = barSpacing * 0.8f;
   //--- Shift origin so bars are centred on the scene
   float offsetX    = -totalWidth / 2.0f;

   //--- Update scale and position of every bar
   for(int i = 0; i < histogramCells; i++)
     {
      //--- Normalise this bin's frequency against the peak PMF
      float normalizedHeight = (float)(m_histogramFrequencies[i] / rangeY);
      //--- Map to scene height units (max 15 units tall)
      float barHeight = normalizedHeight * 15.0f;
      //--- Enforce a minimum visible height so bars are always rendered
      if(barHeight < 0.5f) barHeight = 0.5f;

      //--- Compute the bar's X centre in scene space
      float xPos = offsetX + (float)i * barSpacing + barWidth / 2.0f;

      //--- Build scale, translation and combined transform matrices
      DXMatrix scale, translation, transform;
      DXMatrixScaling(scale, barWidth, barHeight, barWidth);
      DXMatrixTranslation(translation, xPos, 0.0f, 0.0f);
      DXMatrixMultiply(transform, scale, translation);

      //--- Apply the combined world transform to this bar
      m_histogramBars[i].TransformMatrixSet(transform);
     }
  }

まず、create3DHistogramBars関数を定義し、ヒストグラム用の3Dバーを初期化します。m_histogramBars配列をArrayResizeを使用してhistogramCellsと同じサイズに変更します。次に、ビット演算を使用してhistogramColorからRGB成分を抽出します。その後、各セルをループ処理し、CDXBoxの各インスタンスを作成します。CreateメソッドにはDXディスパッチャ、入力シーン、および単位ボックス用のベクトル寸法を渡します。作成に失敗した場合は、エラーを出力してfalseを返します。成功した場合は、DiffuseColorSetを使用して正規化されたRGB値から拡散色を設定します。さらに、SpecularColorSetで鏡面反射色を設定し、SpecularPowerSetで光沢強度を指定します。EmissionColorSetでは発光値を0に設定します。最後に、ObjectAddを使用してポインタ経由でボックスをCanvasに追加し、成功時にはtrueを返します。

最適な表示位置になるようにカメラを自動調整するため、autoFitCameraPosition関数を実装します。この関数では、現在の表示モードがVIEW_3D_MODEではない場合、または、まだ読み込まれていない場合は、すぐに処理を終了します。まず、全体の幅を設定します。次に、各頻度値をY範囲で割り、15.0f倍にスケーリングした正規化バー高さを計算します。ループ処理によって最大のバー高さを取得します。その後、MathMaxを使用して高さを含むシーン全体の寸法を計算し、MathSqrtで対角線長を求めます。そして、視野角に基づいてMathTanを使用し、m_cameraDistanceを計算します。この値には1.5倍の補正係数を適用します。さらに、m_cameraAngleXとm_cameraAngleYに固定角度を設定し、カメラ距離を35.0から100.0の範囲内に制限します。最後に、デバッグ用として設定値を出力します。

次に、createGroundPlane関数を作成し、3D空間内に基準となる床面を追加します。m_groundPlaneに対してCreateメソッドを呼び出し、入力された寸法と厚みに基づいて原点を中心としたベクトルを渡します。作成に失敗した場合は、エラーを出力してfalseを返します。続いて、groundPlaneColorからRGB値を抽出し、DiffuseColorSetを使用して拡散色を設定します。この際、groundPlaneOpacityを使用して透明度も反映します。鏡面反射は0に設定し、ObjectAddを通じてCanvasへ追加します。正常に完了した場合はtrueを返します。方向を示すために、create3DAxes関数を定義し、X軸、Y軸、Z軸を構築します。show3DAxesがtrueの場合のみ軸を作成します。各軸について、それぞれの方向に応じた適切なベクトルサイズを指定してCreateを呼び出します。続いて、axisXColorなどの対応する色からRGB値を取得し、DiffuseColorSetを用いて拡散色を完全不透明(アルファ値255)に設定し、SpecularColorSetを用いて鏡面反射を無効化します。その後、ObjectAddを使用してオブジェクトをCanvasへ追加します。すべての処理が成功した場合はtrueを返し、途中でいずれかの処理が失敗した場合はエラーメッセージを出力してfalseを返します。

続いて、updateCameraPosition関数を実装し、3Dビューを調整します。3Dモードではない場合は処理を終了します。まず、Z軸方向にm_cameraDistanceだけ負方向へ移動したカメラベクトルを作成します。次に、DXMatrixRotationXとDXMatrixRotationYを使用し、設定された角度に基づいた回転行列を生成します。その後、DXVec4Transformを使用してベクトルを順番に変換し、ViewPositionSetによってカメラの表示位置を設定します。さらに、方向性ライトをシミュレートするため、カメラ上部にライト位置を設定します。そして、原点をターゲットとして、距離計算にMathSqrtを使用しながら方向ベクトルを算出して正規化します。最後に、LightDirectionSetを使用してライティング方向を適用します。

最後に、update3DHistogramBars関数を定義し、バーの位置とスケールを動的に更新します。データが存在しない場合は処理を終了します。まず、安全対策を含めて各範囲値を計算します。次に、全体幅を30.0fに設定し、間隔とバー幅を計算します。さらに、中央配置のためのオフセット値を求めます。その後、ループ処理によってバーの高さを正規化し、15.0f倍にスケーリングします。ただし、最低値は0.5fとして設定します。各バーのX座標を計算し、DXMatrixScalingを使用してスケール行列を作成します。また、DXMatrixTranslationで移動行列を作成し、それらをDXMatrixMultiplyで乗算します。最後に、生成した変換行列を各バーへTransformMatrixSetで適用し、3D空間上の位置と大きさを設定します。次におこなう処理は、3D表示用のヘッダーと枠線を描画することです。

3Dモードでのヘッダー、切り替えアイコン、枠線の描画

3Dモードであっても、ツールには適切なヘッダーバーが必要です。そこには分布タイトルを表示し、ユーザーがワンクリックで2Dモードへ戻れるように、明確に配置された切り替えボタンを表示します。さらに、Canvas全体を囲む枠線も必要です。これらはすべてDirectXシーン上に重ねて描画される2Dオーバーレイとして実装します。これにより、現在どちらの表示モードが有効であっても、ユーザーインターフェースの一貫性と操作性を維持できます。

//+------------------------------------------------------------------+
//| Draw the circular 2D/3D toggle icon in the header bar            |
//+------------------------------------------------------------------+
void drawSwitchIcon()
  {
   //--- Compute the icon's top-left corner from the canvas right margin
   int iconX = m_currentWidth  - SWITCH_ICON_SIZE - SWITCH_ICON_MARGIN;
   //--- Vertically centre the icon within the header bar
   int iconY = (HEADER_BAR_HEIGHT - SWITCH_ICON_SIZE) / 2;

   //--- Compute icon background colour from hover state
   color iconBgColor = m_isHoveringSwitchIcon
                       ? DarkenColor(themeColor, 0.1)     // Slightly darker on hover
                       : LightenColor(themeColor, 0.5);   // Default lighter shade

   uint argbIconBg = ColorToARGB(iconBgColor, 255);

   //--- Fill the circular icon background
   m_mainCanvas.FillCircle(iconX + SWITCH_ICON_SIZE / 2, iconY + SWITCH_ICON_SIZE / 2,
                           SWITCH_ICON_SIZE / 2, argbIconBg);

   //--- Draw the circular icon border using the theme colour
   uint argbBorder = ColorToARGB(themeColor, 255);
   m_mainCanvas.Circle(iconX + SWITCH_ICON_SIZE / 2, iconY + SWITCH_ICON_SIZE / 2,
                       SWITCH_ICON_SIZE / 2, argbBorder);

   //--- Set a small bold font for the mode label
   m_mainCanvas.FontSet("Arial Bold", 10);
   uint argbLabel = ColorToARGB(clrWhite, 255);

   //--- Display "2D" or "3D" depending on the active mode
   string modeLabel = (m_currentViewMode == VIEW_2D_MODE) ? "2D" : "3D";
   m_mainCanvas.TextOut(iconX + SWITCH_ICON_SIZE / 2, iconY + (SWITCH_ICON_SIZE - 10) / 2,
                        modeLabel, argbLabel, TA_CENTER);
  }

//+------------------------------------------------------------------+
//| Draw the header bar overlaid on the 3D rendered scene            |
//+------------------------------------------------------------------+
void drawHeaderBarOn3D()
  {
   //--- Compute the header fill colour from the current interaction state
   color headerColor;
   if(m_isDragging)
      headerColor = DarkenColor(themeColor, 0.1);       // Slightly darker while dragging
   else if(m_isHoveringHeader)
      headerColor = LightenColor(themeColor, 0.4);      // Medium light on hover
   else
      headerColor = LightenColor(themeColor, 0.7);      // Very light at rest

   uint argbHeader = ColorToARGB(headerColor, 255);

   //--- Paint over the top portion of the 3D render with the header colour
   m_mainCanvas.FillRectangle(0, 0, m_currentWidth - 1, HEADER_BAR_HEIGHT, argbHeader);

   //--- Overlay a border frame on the header if enabled
   if(showBorderFrame)
     {
      uint argbBorder = ColorToARGB(themeColor, 255);
      m_mainCanvas.Rectangle(0, 0, m_currentWidth - 1, HEADER_BAR_HEIGHT, argbBorder);
      m_mainCanvas.Rectangle(1, 1, m_currentWidth - 2, HEADER_BAR_HEIGHT - 1, argbBorder);
     }

   //--- Set the bold title font
   m_mainCanvas.FontSet("Arial Bold", titleFontSize);
   uint argbText = ColorToARGB(titleTextColor, 255);

   //--- Format the title string with current parameters and 3D label
   string titleText = StringFormat("Binomial Distribution (n=%d, p=%.2f) - 3D View",
                                   numTrials, successProbability);
   //--- Draw the title centred horizontally within the header
   m_mainCanvas.TextOut(m_currentWidth / 2, (HEADER_BAR_HEIGHT - titleFontSize) / 2,
                        titleText, argbText, TA_CENTER);

   //--- Draw the interactive 2D/3D mode switch icon
   drawSwitchIcon();
  }
//+------------------------------------------------------------------+
//| Draw the outer and inner border rectangles for 3D overlay        |
//+------------------------------------------------------------------+
void draw3DBorder()
  {
   //--- Use a slightly darker border when hovering a resize zone
   color borderColor = m_isHoveringResizeZone ? DarkenColor(themeColor, 0.2) : themeColor;
   uint  argbBorder  = ColorToARGB(borderColor, 255);

   //--- Draw the outermost border rectangle over the 3D render
   m_mainCanvas.Rectangle(0, 0, m_currentWidth - 1, m_currentHeight - 1, argbBorder);
   //--- Draw an inner border rectangle for a double-line effect
   m_mainCanvas.Rectangle(1, 1, m_currentWidth - 2, m_currentHeight - 2, argbBorder);
  }

ここでは、ビューを切り替えるためのトグルボタンをヘッダー内に描画するdrawSwitchIcon関数を定義します。まず、SWITCH_ICON_SIZEやSWITCH_ICON_MARGINなどの定数を基にアイコンの位置を計算します。次に、ホバー状態の場合はDarkenColorを使用して暗くした背景色を選択し、それ以外の場合はLightenColorを使用して明るくした背景色を設定します。その後、ColorToARGBを使用してARGB形式へ変換しFillCircleメソッドで円形の背景を塗りつぶします。続いて、Circleを使用して境界用の円を追加します。FontSetで太字フォントを設定し、白色のARGBテキストを準備します。「m_currentViewMode」の値に応じてラベルを「2D」または「3D」としてフォーマットし、TextOutを使用して中央に配置します。これにより、ユーザーが現在の表示状態を確認しながら、直感的にビューを切り替えられるインタラクティブな操作性を提供します。

次に、3Dモード用のヘッダーをオーバーレイ表示するdrawHeaderBarOn3D関数を作成します。これは2Dモードのヘッダーと似ていますが、タイトル表示が変更されています。まず、ドラッグ状態またはホバー状態に基づいてヘッダー色を決定します。色の調整にはDarkenColorまたはLightenColorを使用します。その後、FillRectangleで矩形領域を塗りつぶします。showBorderFrameがtrueの場合は、Rectangleを使用して枠線を追加します。続いてFontSetでフォントを設定し、StringFormatを使用して「- 3D View」を含むタイトル文字列を生成します。生成したタイトルはTextOutで中央に描画します。最後にdrawSwitchIconを呼び出し、ビュー切り替え用のアイコンを追加します。3D表示領域を囲むために、draw3DBorder関数を実装します。この関数では、サイズ変更時のホバー状態に応じて境界色を選択します。ホバー中の場合はDarkenColorを使用して色を暗くします。その後、ColorToARGBでARGB形式へ変換し、Rectangleを使用して内側と外側の矩形枠線を描画します。これにより、2Dモードの境界表示と一貫した外観を維持します。次に、3D平面との一貫性を保つため、3D理論曲線を描画します。今回はシンプルにするため、通常のライン曲線を使用します

理論曲線を3Dシーンへ投影する

ヒストグラムバーはDirectXシーン内で実際の3Dオブジェクトとして存在します。一方、理論的な確率質量関数曲線は、透視空間へ投影された2Dオーバーレイとして描画します。つまり、各曲線点に対して、ビュー行列と射影行列を組み合わせた変換処理を手動で適用し、画面上の配置位置を計算します。その後、Canvas上にアンチエイリアス処理されたラインとして描画します。さらに、ヘッダーバー上に曲線の一部が描画されることを防ぐため、クリッピング機能も追加します。これにより、3D表示中でもインターフェース領域とグラフ描画領域を明確に分離できます。

//+------------------------------------------------------------------+
//| Project the theoretical PMF curve into 3D screen space           |
//+------------------------------------------------------------------+
void draw3DTheoreticalCurve()
  {
   //--- Abort if data has not been loaded yet
   if(!m_isDataLoaded) return;

   //--- Compute the data ranges for world-space mapping
   double rangeX = m_maxDataValue - m_minDataValue;
   double rangeY = m_maxTheoreticalValue;
   if(rangeX == 0) rangeX = 1;
   if(rangeY == 0) rangeY = 1;

   //--- Match the total scene width used for the histogram bars
   float totalWidth = 30.0f;
   float offsetX    = -totalWidth / 2.0f;

   //--- Retrieve the current view-projection matrices for projection
   DXMatrix projection, view, worldToScreen;
   m_mainCanvas.ViewMatrixGet(view);
   m_mainCanvas.ProjectionMatrixGet(projection);
   //--- Combine view and projection into a single world-to-clip matrix
   DXMatrixMultiply(worldToScreen, view, projection);

   uint curveColor = ColorToARGB(theoreticalCurveColor, 255);

   //--- Transform and draw each consecutive pair of PMF samples
   for(int i = 0; i < ArraySize(m_theoreticalXValues) - 1; i++)
     {
      //--- Map PMF X and Y values into 3D world space
      float x1 = offsetX + (float)((m_theoreticalXValues[i]     - m_minDataValue) / rangeX * totalWidth);
      float y1 = (float)(m_theoreticalYValues[i]     / rangeY * 20.0);
      float x2 = offsetX + (float)((m_theoreticalXValues[i + 1] - m_minDataValue) / rangeX * totalWidth);
      float y2 = (float)(m_theoreticalYValues[i + 1] / rangeY * 20.0);

      //--- Build homogeneous 3D points on the Z = 0 plane
      DXVector4 p1_3d = DXVector4(x1, y1, 0.0f, 1.0f);
      DXVector4 p2_3d = DXVector4(x2, y2, 0.0f, 1.0f);

      //--- Project both points into clip space
      DXVec4Transform(p1_3d, p1_3d, worldToScreen);
      DXVec4Transform(p2_3d, p2_3d, worldToScreen);

      //--- Perform perspective divide only for points in front of the near plane
      if(p1_3d.w > 0.0f && p2_3d.w > 0.0f)
        {
         DXVec4Scale(p1_3d, p1_3d, 1.0f / p1_3d.w);
         DXVec4Scale(p2_3d, p2_3d, 1.0f / p2_3d.w);

         //--- Convert NDC coordinates to pixel coordinates
         int sx1 = (int)((float)m_currentWidth  * (0.5f + 0.5f * p1_3d.x));
         int sy1 = (int)((float)m_currentHeight * (0.5f - 0.5f * p1_3d.y));
         int sx2 = (int)((float)m_currentWidth  * (0.5f + 0.5f * p2_3d.x));
         int sy2 = (int)((float)m_currentHeight * (0.5f - 0.5f * p2_3d.y));

         //--- Clip line endpoints against the header bar boundary
         if(clipLineToHeader(sx1, sy1, sx2, sy2))
           {
            //--- Draw multiple offset passes for the configured line width
            for(int w = 0; w < curveLineWidth; w++)
               m_mainCanvas.LineAA(sx1, sy1 + w, sx2, sy2 + w, curveColor);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Clip a line segment to exclude the header bar region             |
//+------------------------------------------------------------------+
bool clipLineToHeader(int &x1, int &y1, int &x2, int &y2)
  {
   //--- Reject the segment entirely if both endpoints are inside the header
   if(y1 < HEADER_BAR_HEIGHT && y2 < HEADER_BAR_HEIGHT)
      return false;

   //--- Accept the segment entirely if both endpoints are below the header
   if(y1 >= HEADER_BAR_HEIGHT && y2 >= HEADER_BAR_HEIGHT)
      return true;

   //--- Clip the first endpoint when it falls inside the header
   if(y1 < HEADER_BAR_HEIGHT)
     {
      if(y2 != y1)
        {
         //--- Linearly interpolate X to find the intersection with the header boundary
         x1 = x1 + (x2 - x1) * (HEADER_BAR_HEIGHT - y1) / (y2 - y1);
         y1 = HEADER_BAR_HEIGHT;
        }
     }
   //--- Clip the second endpoint when it falls inside the header
   else if(y2 < HEADER_BAR_HEIGHT)
     {
      if(y2 != y1)
        {
         //--- Linearly interpolate X to find the intersection with the header boundary
         x2 = x1 + (x2 - x1) * (HEADER_BAR_HEIGHT - y1) / (y2 - y1);
         y2 = HEADER_BAR_HEIGHT;
        }
     }

   return true;
  }

ここでは、理論的な確率質量関数を3Dシーン上に2Dラインとして重ねて描画するdraw3DTheoreticalCurve関数を定義します。これにより、完全な3D曲線モデルを作成することなく、透視投影に従って正しく表示できます。完全な3D曲線として実装することも可能ですが、今回は3Dバーに焦点を当てるため採用しません。まず、データが読み込まれていない場合は早期に処理を終了します。次に、安全対策を含めてX範囲とY範囲を計算します。ヒストグラムとの位置合わせを維持するため、全体幅を設定し、中央配置用のオフセットを計算します。

3D座標を2Dスクリーン座標へ投影するために、行列を宣言します。ViewMatrixGetとProjectionMatrixGetを使用してビュー行列と射影行列を取得し、DXMatrixMultiplyによってそれらを乗算してワールド座標からスクリーン座標への変換行列を作成します。その後、ColorToARGBを使用して曲線色を準備します。理論曲線の連続する各ポイントをループ処理し、X座標とY座標を3D空間に収まるようにスケーリングします。次に、z=0の位置にDXVector4ポイントを作成します。作成したポイントをDXVec4Transformで変換し、w値が正であることを確認して表示可能かを判定します。その後、DXVec4Scaleで座標をw値によって正規化します。次に、Canvasサイズを基準にしてスクリーン座標へ変換し、整数座標へ変換します。さらに、clipLineToHeaderを使用してヘッダー領域にラインが描画されないようにクリッピングします。最後に、curveLineWidthで指定された太さに合わせて幅方向のループ処理を行い、 LineAAを使用してアンチエイリアス付きラインを描画します。

この投影手法は、3Dレンダリングと2Dオーバーレイを橋渡しする重要な技術です。ワールド座標を複合行列によって変換することで、Canvas上では平面的なラインとして描画しながら、奥行きを持った表示をシミュレートできます。その結果、曲線は3D空間内でバーの位置関係に合わせて浮かんでいるように見えます。これにより、複雑な3Dスプライン補間を実装することなく、ヒストグラムバーとの視覚的な関連性を高めることができます。ヘッダー上への描画を防ぐため、ヘッダー境界に対する単純なラインクリッピング処理としてclipLineToHeader関数を実装します。まず、2つのY座標が両方ともHEADER_BAR_HEIGHTより上にある場合は描画対象外として拒否します。逆に、両方とも下側にある場合はそのまま許可します。それ以外の場合は、境界となるY座標でX座標を補間して、ヘッダー領域にはみ出しているポイントをクリップします。その後、参照渡しされた座標を更新し、3Dビュー内で2D要素が正しく統合されるようにします。ここまでで3Dの初期化処理へ進む準備が整いました。次に、3D可視化を作成するためのロジックを定義します。

3Dコンテキストの作成と初期化

ここまでで個別の描画関数やカメラ制御関数がすべて定義できました。次は、それらを結び付ける全体的な処理を実装する必要があります。具体的には、Canvasの作成、DirectXコンテキストの設定、3Dオブジェクトの構築、分布データの読み込み、そして最初のレンダリング処理の実行を行います。これらの関数は、ツールの起動シーケンスを支える中心的な役割を持ちます。それぞれの処理には明確な責任があります。Canvas作成では、描画対象となる領域を準備します。コンテキスト初期化では、ライティングや射影設定を構成します。オブジェクト作成では、3Dシーン内に必要な要素を配置します。データ読み込みでは、シミュレーションされた二項分布サンプルを使用してヒストグラムを生成します。この統合処理が存在しなければ、これまで定義してきた個々の関数は正しい順序で実行されず、ビジュアライザー全体が動作することはありません。

//+------------------------------------------------------------------+
//| Dispatch rendering to the active 2D or 3D pipeline               |
//+------------------------------------------------------------------+
void renderVisualization()
  {
   //--- Render using the 2D canvas pipeline
   if(m_currentViewMode == VIEW_2D_MODE)
      render2DVisualization();
   else
      //--- Render using the 3D DirectX pipeline
      render3DVisualization();
  }
//+------------------------------------------------------------------+
//| Render the 3D scene and overlay 2D UI elements on top            |
//+------------------------------------------------------------------+
void render3DVisualization()
  {
   //--- Abort if data has not been loaded
   if(!m_isDataLoaded) return;

   //--- Recompute the view and light transforms for this frame
   updateCameraPosition();
   //--- Reposition all 3D histogram bars to match current data
   update3DHistogramBars();

   //--- Use the configured background colour for the clear pass
   color bgColor     = backgroundTopColor;
   uint  bgColorArgb = ColorToARGB(bgColor, 255);
   //--- Clear colour and depth buffers, then render the 3D scene
   m_mainCanvas.Render(DX_CLEAR_COLOR | DX_CLEAR_DEPTH, bgColorArgb);

   //--- Overlay the 2D border frame on top of the 3D render
   if(showBorderFrame)
      draw3DBorder();

   //--- Overlay the header bar on the rendered 3D image
   drawHeaderBarOn3D();
   //--- Overlay the stats panel and legend if enabled
   if(showStatistics)
     {
      drawStatisticsPanelOn3D();
      drawLegendOn3D();
     }
   //--- Project and draw the theoretical PMF curve into screen space
   draw3DTheoreticalCurve();

   //--- Draw the resize grip indicator when hovering
   if(m_isHoveringResizeZone && enableResizing)
      drawResizeIndicatorOn3D();

   //--- Flush the pixel buffer to the chart object
   m_mainCanvas.Update();
  }
//+------------------------------------------------------------------+
//| Create bitmap label, initialise 3D context and scene objects     |
//+------------------------------------------------------------------+
bool createCanvasAndObjects()
  {
   //--- Create the canvas bitmap label on the chart
   if(!m_mainCanvas.CreateBitmapLabel(m_canvasObjectName, 0, 0, m_currentWidth, m_currentHeight,
                                      COLOR_FORMAT_ARGB_NORMALIZE))
     {
      Print("ERROR: Failed to create canvas");
      return false;
     }

   //--- Position the canvas horizontally
   ObjectSetInteger(0, m_canvasObjectName, OBJPROP_XDISTANCE, m_currentPositionX);
   //--- Position the canvas vertically
   ObjectSetInteger(0, m_canvasObjectName, OBJPROP_YDISTANCE, m_currentPositionY);

   //--- Initialise the DirectX 3D rendering context
   if(!initialize3DContext())
     {
      Print("ERROR: Failed to initialize 3D context");
      return false;
     }

   //--- Build all 3D scene objects (bars, ground, axes)
   if(!create3DObjects())
     {
      Print("ERROR: Failed to create 3D objects");
      return false;
     }

   return true;
  }

//+------------------------------------------------------------------+
//| Set up projection, lighting and initial camera for 3D rendering  |
//+------------------------------------------------------------------+
bool initialize3DContext()
  {
   //--- Set perspective projection matrix with 30-degree FOV
   m_mainCanvas.ProjectionMatrixSet((float)(DX_PI / 6.0),
                                    (float)m_currentWidth / (float)m_currentHeight,
                                    0.1f, 1000.0f);

   //--- Point the camera at the scene origin
   m_mainCanvas.ViewTargetSet(DXVector3(0.0f, 0.0f, 0.0f));
   //--- Define world up direction as positive Y
   m_mainCanvas.ViewUpDirectionSet(DXVector3(0.0f, 1.0f, 0.0f));

   //--- Set directional light colour to near-white
   m_mainCanvas.LightColorSet(DXColor(1.0f, 1.0f, 1.0f, 0.9f));
   //--- Set ambient light colour for soft fill
   m_mainCanvas.AmbientColorSet(DXColor(0.6f, 0.6f, 0.6f, 0.5f));

   //--- Auto-fit camera if enabled and data is ready
   if(autoFitCamera && m_isDataLoaded)
      autoFitCameraPosition();

   //--- Recompute and apply the camera transform
   updateCameraPosition();

   Print("SUCCESS: 3D context initialized");
   return true;
  }
//+------------------------------------------------------------------+
//| Build all 3D scene objects: bars, ground plane, and axes         |
//+------------------------------------------------------------------+
bool create3DObjects()
  {
   //--- Create the histogram bar boxes
   if(!create3DHistogramBars())
     {
      Print("ERROR: Failed to create 3D histogram bars");
      return false;
     }

   //--- Create the flat ground reference plane
   if(!createGroundPlane())
     {
      Print("ERROR: Failed to create ground plane");
      return false;
     }

   //--- Create coordinate axes only when the option is enabled
   if(show3DAxes && !create3DAxes())
     {
      Print("ERROR: Failed to create 3D axes");
      return false;
     }

   //--- Flag that all 3D objects are ready
   m_are3DObjectsCreated = true;
   Print("SUCCESS: 3D objects created");
   return true;
  }

//+------------------------------------------------------------------+
//| Prepare the scene for 3D rendering after a mode switch           |
//+------------------------------------------------------------------+
bool setup3DMode()
  {
   //--- If objects already exist, simply refresh the camera
   if(m_are3DObjectsCreated)
     {
      //--- Auto-fit camera when enabled and data is available
      if(autoFitCamera && m_isDataLoaded)
         autoFitCameraPosition();

      //--- Apply updated camera transform
      updateCameraPosition();
      return true;
     }

   //--- Warn if this path is reached unexpectedly
   Print("WARNING: 3D objects not created - this shouldn't happen!");
   //--- Attempt to create objects as a fallback
   return create3DObjects();
  }

//+------------------------------------------------------------------+
//| Generate binomial sample, compute histogram and statistics       |
//+------------------------------------------------------------------+
bool loadDistributionData()
  {
   //--- Seed the random number generator with current tick count
   MathSrand(GetTickCount());

   //--- Allocate the sample data buffer
   ArrayResize(m_sampleData, sampleSize);
   //--- Fill the buffer with random binomial variates
   MathRandomBinomial(numTrials, successProbability, sampleSize, m_sampleData);

   //--- Compute the frequency histogram from the sample
   if(!computeHistogram(m_sampleData, m_histogramIntervals, m_histogramFrequencies,
                        m_maxDataValue, m_minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate histogram");
      return false;
     }

   //--- Allocate arrays for the theoretical PMF curve
   ArrayResize(m_theoreticalXValues, numTrials + 1);
   ArrayResize(m_theoreticalYValues, numTrials + 1);
   //--- Fill X values as integer sequence 0 .. numTrials
   MathSequence(0, numTrials, 1, m_theoreticalXValues);
   //--- Compute binomial PMF for each X value
   MathProbabilityDensityBinomial(m_theoreticalXValues, numTrials, successProbability,
                                  false, m_theoreticalYValues);

   //--- Find the tallest histogram bin
   m_maxFrequency = m_histogramFrequencies[ArrayMaximum(m_histogramFrequencies)];
   //--- Find the peak theoretical PMF value
   m_maxTheoreticalValue = m_theoreticalYValues[ArrayMaximum(m_theoreticalYValues)];

   //--- Compute scaling factor to align histogram with PMF curve
   double scaleFactor = m_maxFrequency / m_maxTheoreticalValue;
   //--- Scale every bin frequency so it matches PMF units
   for(int i = 0; i < histogramCells; i++)
      m_histogramFrequencies[i] /= scaleFactor;

   //--- Compute all descriptive statistics
   computeAdvancedStatistics();

   //--- Mark data as successfully loaded
   m_isDataLoaded = true;

   //--- Update 3D bar heights if the scene is already built
   if(m_currentViewMode == VIEW_3D_MODE && m_are3DObjectsCreated)
     {
      //--- Refit camera to new data extents
      if(autoFitCamera)
         autoFitCameraPosition();
      //--- Reposition every bar in 3D space
      update3DHistogramBars();
     }

   Print("SUCCESS: Loaded distribution data");
   return true;
  }

まず、現在の表示モードに応じて描画処理を制御するrenderVisualization関数を定義します。この関数では、m_currentViewModeを確認し、VIEW_2D_MODEの場合はrender2DVisualizationを呼び出し、それ以外の場合はrender3DVisualizationを呼び出します。これにより、2D表示と3D表示の両方に対する描画処理を一元管理できます。2D表示については、以前のバージョンで実装したロジックと同じであるため、今回は大きな変更は行いません。3D表示については、render3DVisualization関数を実装します。まず、データが存在しない場合は早期に処理を終了します。次に、カメラ位置とヒストグラムバーの状態を更新します。その後、backgroundTopColorからARGB形式へ変換した背景色を設定します。Renderを使用してシーンをクリアします。この際、DX_CLEAR_COLOR | DX_CLEAR_DEPTHフラグを指定し、カラー情報と深度バッファの両方を初期化します。showBorderFrameが有効な場合はdraw3DBorderを呼び出して枠線を描画します。続いてdrawHeaderBarOn3Dを使用してヘッダーを追加します。最後にUpdateを呼び出して、完成した3Dコンテンツを画面へ表示します。Canvasを準備するためcreateCanvasAndObject関数を作成します。この関数では、まずm_mainCanvaに対して正規化されたARGBフォーマットでCreateBitmapLabelを呼び出します。続いて、 ObjectSetIntegerを使用してOBJPROP_XDISTANCEおよびOBJPROP_YDISTANCEを設定し、オブジェクトの表示位置を指定します。その後、initialize3DContextとcreate3DObjectsを順番に呼び出します。いずれかの処理が失敗した場合は、エラーメッセージを出力してfalseを返します。

次に、3D環境を構成するinitialize3DContext関数を定義します。まず、ProjectionMatrixSetを使用して射影行列を設定します。この際、視野角は30度に設定し、アスペクト比にはCanvasサイズから計算した値を使用します。また、近距離クリッピング面と遠距離クリッピング面も指定します。次に、ViewTargetSetとViewUpDirectionSetを使用して、ビューのターゲット位置と上方向ベクトルを設定します。どちらも原点を基準として構成します。その後、LightColorSetとAmbientColorSetを使用してライトカラーと環境光カラーを適用します。自動カメラ調整が有効で、かつデータがロード済みの場合は、autoFitCameraPositionを呼び出してカメラ位置を自動調整します。続いてupdateCameraPositionを実行して現在のカメラ位置を反映し、最後に初期化成功メッセージを出力します。

次に、create3DObjectsは、create3DHistogramBars、createGroundPlaneを順番に呼び出し、さらにshow3DAxesがtrueの場合は条件付きでcreate3DAxesを呼び出すことで、3D要素の生成全体を統括します。すべての処理が成功すると、m_are3DObjectsCreated をtrueに設定し、成功メッセージを出力してtrueを返します。一方、途中でいずれかの処理が失敗した場合は、その時点でfalseを返します。モード切り替え時の処理として、setup3DMode関数を実装します。まず、3Dオブジェクトがすでに作成済みか確認します。作成済みの場合はautoFitCameraPositionを呼び出してカメラを自動調整し、updateCameraPositionで位置を更新します。まだ作成されていない場合は警告を出力し、create3DObjectsを呼び出してオブジェクト生成を試みます。最後に、分布データを準備するloadDistributionData関数を定義します。まず、GetTickCountを使用してMathSrandで乱数シードを設定します。次に、m_sampleData配列のサイズを変更し、MathRandomBinomialを使用して二項分布サンプルを生成します。その後、computeHistogramを使用してヒストグラムを計算します。さらに、MathSequenceを使用して理論値用の配列を設定し、MathProbabilityDensityBinomialによって二項分布の確率質量関数(PMF)を計算します。ArrayMaximumを使用して最大値を取得し、理論値の最大値に合わせて頻度値をスケーリングします。続いて統計情報を計算し、m_isDataLoadedをtrueに設定します。もし現在の表示モードがVIEW_3D_MODEであり、かつ3Dオブジェクトが作成済みの場合は、autoFitCameraPositionを呼び出してカメラを再調整し、update3DHistogramBarsによってバー位置を更新します。最後に、データ読み込み成功メッセージを出力します。この初期化処理を実行するため、初期化イベントハンドラ内で以下のように関数を呼び出します。

初期化イベントハンドラの接続

すべてのクラスロジックが定義できたため、次はプログラムのエントリーポイントへ接続する必要があります。OnInitイベントハンドラでは、ビジュアライザーのインスタンスを作成し、Canvasと3Dオブジェクトを初期化し、分布データを読み込み、最初の描画処理を実行します。この段階でいずれかの処理が失敗した場合は、インスタンスを安全に削除し、初期化失敗コードを返します。これにより、不完全な状態のままプログラムが実行されることを防ぎ、安定した起動処理を実現します。

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
DistributionVisualizer *distributionVisualizer = NULL; // Pointer to the active visualizer instance

//+------------------------------------------------------------------+
//| Initialise the EA, create the canvas and load distribution data  |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Enable mouse movement events on the chart
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE,  true);
   //--- Enable mouse wheel events on the chart
   ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true);

   //--- Allocate and construct the visualizer object
   distributionVisualizer = new DistributionVisualizer();
   if(distributionVisualizer == NULL)
     {
      Print("ERROR: Failed to create window object");
      return INIT_FAILED;
     }

   //--- Create the canvas bitmap label and initialise the 3D scene
   if(!distributionVisualizer.createCanvasAndObjects())
     {
      Print("ERROR: Failed to create canvas");
      delete distributionVisualizer;
      distributionVisualizer = NULL;
      return INIT_FAILED;
     }

   //--- Generate the binomial sample and compute all statistics
   if(!distributionVisualizer.loadDistributionData())
     {
      Print("ERROR: Failed to load distribution data");
      delete distributionVisualizer;
      distributionVisualizer = NULL;
      return INIT_FAILED;
     }

   //--- Render the initial frame
   distributionVisualizer.renderVisualization();
   ChartRedraw();

   Print("SUCCESS: Distribution window initialized");
   return INIT_SUCCEEDED;
  }

まず、プログラム全体でツールのインスタンスを管理するために、DistributionVisualizerクラスへのグローバルポインタdistributionVisualizerを宣言します。初期値はNULLに設定します。OnInitイベントハンドラでは、操作対応のためにマウスイベントを有効化します。ChartSetIntegerを使用してCHART_EVENT_MOUSE_MOVEとCHART_EVENT_MOUSE_WHEELをtrueに設定します。次に、newを使用してビジュアライザーのインスタンスを生成します。生成結果がNULLか確認し、失敗した場合はエラーを出力してINIT_FAILEDを返します。続いて、createCanvasAndObjectsを呼び出してCanvasと3D要素を準備します。この処理が失敗した場合は、作成済みのインスタンスを削除し、初期化失敗として終了します。その後、loadDistributionDataを使用してデータを読み込みます。ここでもエラーが発生した場合は、同様にクリーンアップ処理を行って失敗を返します。最後に、renderVisualizationを呼び出してビジュアルを描画し、ChartRedrawでチャートを再描画します。成功メッセージを出力した後、INIT_SUCCEEDEDを返して初期化が正常に完了したことを示します。コンパイルすると、次の結果が得られます。

3Dバーが初期化された

次に、統計情報と凡例パネルを追加する必要があります。そのためのロジックを以下に実装します。

3Dモードでの統計パネル、凡例、サイズ変更インジケータの描画

統計パネルと凡例は、すでに2Dバージョンで実装されています。これらは分析面で重要な役割を持ち、平均値、標準偏差、信頼区間などの主要な統計指標や、ヒストグラムと理論曲線を読み取るための凡例をユーザーへ提供します。3Dモードでは、これらの要素を維持する必要があります。そのため、各レンダリング処理の後に、3Dシーン上へ重ねて描画する2Dオーバーレイとして実装します。同様に、サイズ変更インジケータも現在の表示モードに関係なく、ユーザーがサイズ変更領域上にカーソルを移動した際には表示される必要があります。この処理ブロックでは、これらのオーバーレイ要素を3Dレンダリングパイプラインへ統合します。

//+------------------------------------------------------------------+
//| Draw the statistics panel overlay on the 3D render               |
//+------------------------------------------------------------------+
void drawStatisticsPanelOn3D()
  {
   //--- Reuse the same 2D statistics panel for the 3D overlay
   drawStatisticsPanel();
  }
//+------------------------------------------------------------------+
//| Draw the legend panel overlay on the 3D render                   |
//+------------------------------------------------------------------+
void drawLegendOn3D()
  {
   //--- Compute legend panel absolute position (same layout as 2D)
   int legendX          = statsPanelX;
   int legendY          = HEADER_BAR_HEIGHT + statsPanelY + statsPanelHeight;
   int legendWidth      = statsPanelWidth;
   int legendHeightThis = legendHeight;

   //--- Compute legend background colour as a very light theme tint
   color legendBgColor = LightenColor(themeColor, 0.9);
   uchar bgAlpha       = 153;
   uint  argbLegendBg  = ColorToARGB(legendBgColor, bgAlpha);
   uint  argbBorder    = ColorToARGB(themeColor, 255);
   uint  argbText      = ColorToARGB(clrBlack, 255);

   //--- Flood-fill the legend background with alpha blending
   for(int y = legendY; y <= legendY + legendHeightThis; y++)
      for(int x = legendX; x <= legendX + legendWidth; x++)
         blendPixelSet(m_mainCanvas, x, y, argbLegendBg);

   //--- Draw all four border lines of the legend panel
   for(int x = legendX; x <= legendX + legendWidth; x++)
      blendPixelSet(m_mainCanvas, x, legendY, argbBorder); // Top border
   for(int y = legendY; y <= legendY + legendHeightThis; y++)
      blendPixelSet(m_mainCanvas, legendX + legendWidth, y, argbBorder); // Right border
   for(int x = legendX; x <= legendX + legendWidth; x++)
      blendPixelSet(m_mainCanvas, x, legendY + legendHeightThis, argbBorder); // Bottom border
   for(int y = legendY; y <= legendY + legendHeightThis; y++)
      blendPixelSet(m_mainCanvas, legendX, y, argbBorder); // Left border

   //--- Set the legend text font
   m_mainCanvas.FontSet("Arial", panelFontSize);

   //--- Initialise vertical text cursor inside the legend
   int itemY      = legendY + 10;
   int lineSpacing = panelFontSize;

   //--- Draw 3D histogram colour swatch and its label
   uint argbHist = ColorToARGB(histogramColor, 255);
   m_mainCanvas.FillRectangle(legendX + 7, itemY - 4, legendX + 22, itemY + 4, argbHist);
   m_mainCanvas.TextOut(legendX + 27, itemY - 4, "3D Histogram", argbText, TA_LEFT);
   itemY += lineSpacing;

   //--- Draw a short horizontal line as the curve colour swatch
   uint argbCurve = ColorToARGB(theoreticalCurveColor, 255);
   for(int i = 0; i < 15; i++)
     {
      blendPixelSet(m_mainCanvas, legendX + 7 + i, itemY,     argbCurve); // Upper swatch pixel row
      blendPixelSet(m_mainCanvas, legendX + 7 + i, itemY + 1, argbCurve); // Lower swatch pixel row
     }
   //--- Draw the PMF curve label beside the swatch
   m_mainCanvas.TextOut(legendX + 27, itemY - 4, "Theoretical PMF", argbText, TA_LEFT);
  }
//+------------------------------------------------------------------+
//| Draw resize grip indicators overlaid on the 3D render            |
//+------------------------------------------------------------------+
void drawResizeIndicatorOn3D()
  {
   //--- Reuse the same 2D resize indicator for the 3D overlay
   drawResizeIndicator();
  }

//--- Call these in the 3D visual function

if(showStatistics)
  {
   drawStatisticsPanelOn3D();
   drawLegendOn3D();
  }
//--- Project and draw the theoretical PMF curve into screen space
draw3DTheoreticalCurve();

//--- Draw the resize grip indicator when hovering
if(m_isHoveringResizeZone && enableResizing)
   drawResizeIndicatorOn3D();

ここでは、3Dモードで統計情報を描画するdrawStatisticsPanelOn3D関数を定義します。この関数では、単純にdrawStatisticsPanelを呼び出し、2D用のロジックを再利用することで、オーバーレイ表示の一貫性を維持します。3D用の凡例については、2D版と同様のdrawLegendOn3D関数を実装しますが、「3D Histogram」というラベルを使用します。入力値から位置を設定し、LightenColorを使用してthemeColorを明るくした背景色を作成します。次に、ColorToARGBを使用してアルファ値を含むARGBカラーを準備します。fillと枠線のためにblendPixelSetを使用してピクセルのブレンド処理をループで実行します。その後、FontSetでフォントを設定し、FillRectangleを使用してヒストグラムのサンプル矩形を描画し、TextOutでラベルを描画します。続いて、曲線サンプルラインをブレンドしてそのラベルを追加し、3D表示に適応した視覚的な凡例を提供します。3D表示でサイズ変更領域を示すために、drawResizeIndicatorOn3Dを作成します。この関数ではdrawResizeIndicatorを呼び出し、2D表示と同じフィードバックを維持します。

3Dレンダリングフローでは、showStatisticsがtrueの場合、drawStatisticsPanelOn3DとdrawLegendOn3Dを呼び出して情報パネルをオーバーレイ表示します。確率的なオーバーレイとしてdraw3DTheoreticalCurveで曲線を描画します。また、サイズ変更が有効で、かつサイズ変更領域上にホバーしている場合は、drawResizeIndicatorOn3Dを追加します。これにより、3Dレンダリング後に2D要素を統合したハイブリッド表示を実現します。コンパイルすると、次のようになります。

統計情報と凡例パネル

パネルが完成したので、次にチャート操作を処理する必要があります。

マウス操作と表示モード切り替えの処理

3D可視化は、インタラクティブ性があって初めて有用になります。ユーザーは、異なる角度からバーを確認するためにシーンを回転できる必要があります。また、分布の特定の領域に注目するためにズームイン・ズームアウトできる必要があります。さらに、Canvasをドラッグしてチャート上で位置を変更したり、表示領域を調整するためにサイズ変更したり、1回のクリックで2Dモードと3Dモードを切り替えたりできる必要があります。これらすべての操作はマウスイベントを通じて実行されます。そして、それぞれの操作は競合を避けるために慎重に処理する必要があります。例えば、3Dシーンを回転するためのドラッグ操作は、同時にチャートのスクロールを発生させてはいけません。また、切り替えアイコンのクリックは、同時にドラッグ操作を開始してはいけません。このブロックでは、ツールを完全に応答性のあるものにするために必要なすべてのインタラクションロジックを定義します。

//+------------------------------------------------------------------+
//| Process mouse move and button events for interaction             |
//+------------------------------------------------------------------+
void handleMouseEvent(int mouseX, int mouseY, int mouseState)
  {
   //--- Snapshot previous hover states to detect changes
   bool previousHoverState       = m_isHoveringCanvas;
   bool previousHeaderHoverState = m_isHoveringHeader;
   bool previousResizeHoverState = m_isHoveringResizeZone;
   bool previousSwitchHoverState = m_isHoveringSwitchIcon;

   //--- Update canvas hover flag based on cursor position
   m_isHoveringCanvas = (mouseX >= m_currentPositionX &&
                         mouseX <= m_currentPositionX + m_currentWidth &&
                         mouseY >= m_currentPositionY &&
                         mouseY <= m_currentPositionY + m_currentHeight);

   //--- Update individual zone hover flags
   m_isHoveringHeader      = isMouseOverHeaderBar(mouseX, mouseY);
   m_isHoveringSwitchIcon  = isMouseOverSwitchIcon(mouseX, mouseY);
   m_isHoveringResizeZone  = isMouseInResizeZone(mouseX, mouseY, m_hoverResizeMode);

   //--- Determine if a redraw is needed due to hover state changes
   bool needRedraw = (previousHoverState       != m_isHoveringCanvas  ||
                      previousHeaderHoverState != m_isHoveringHeader  ||
                      previousResizeHoverState != m_isHoveringResizeZone ||
                      previousSwitchHoverState != m_isHoveringSwitchIcon);

   //--- Handle 3D orbit drag when in 3D mode and not over the header
   if(m_currentViewMode == VIEW_3D_MODE && m_isHoveringCanvas && !m_isHoveringHeader)
     {
      //--- Begin rotation on fresh left-button press
      if(mouseState == 1 && m_previousMouseButtonState == 0)
        {
         m_isRotating3D  = true;
         m_mouse3DStartX = mouseX;
         m_mouse3DStartY = mouseY;
         //--- Prevent chart from consuming mouse scroll during rotation
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
        }
      //--- Continue rotation while button is held and dragging
      else if(mouseState == 1 && m_previousMouseButtonState == 1 && m_isRotating3D)
        {
         //--- Update azimuth angle proportional to horizontal mouse delta
         m_cameraAngleY += (mouseX - m_mouse3DStartX) / 300.0;
         //--- Update elevation angle proportional to vertical mouse delta
         m_cameraAngleX += (mouseY - m_mouse3DStartY) / 300.0;

         //--- Clamp elevation to avoid gimbal lock at poles
         if(m_cameraAngleX < -DX_PI * 0.49) m_cameraAngleX = -DX_PI * 0.49;
         if(m_cameraAngleX >  DX_PI * 0.49) m_cameraAngleX =  DX_PI * 0.49;

         //--- Update rotation anchor for the next delta computation
         m_mouse3DStartX = mouseX;
         m_mouse3DStartY = mouseY;
         needRedraw = true;
        }
      //--- End rotation on button release
      else if(mouseState == 0 && m_previousMouseButtonState == 1)
        {
         m_isRotating3D = false;
         //--- Restore chart scroll on release
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
        }
     }

   //--- Handle button-press interactions
   if(mouseState == 1 && m_previousMouseButtonState == 0)
     {
      //--- Switch view mode when the icon is clicked
      if(m_isHoveringSwitchIcon)
        {
         switchViewMode();
         m_previousMouseButtonState = mouseState;
         return;
        }
      //--- Begin canvas drag when clicking the header (not a resize zone)
      else if(enableDragging && m_isHoveringHeader && !m_isHoveringResizeZone)
        {
         m_isDragging   = true;
         m_dragStartX   = mouseX;
         m_dragStartY   = mouseY;
         m_canvasStartX = m_currentPositionX;
         m_canvasStartY = m_currentPositionY;
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
         needRedraw = true;
        }
      //--- Begin canvas resize when clicking a resize grip zone
      else if(m_isHoveringResizeZone)
        {
         m_isResizing          = true;
         m_activeResizeMode    = m_hoverResizeMode;
         m_resizeStartX        = mouseX;
         m_resizeStartY        = mouseY;
         m_resizeInitialWidth  = m_currentWidth;
         m_resizeInitialHeight = m_currentHeight;
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
         needRedraw = true;
        }
     }
   //--- Continue drag or resize while button stays pressed
   else if(mouseState == 1 && m_previousMouseButtonState == 1)
     {
      if(m_isDragging)
         handleCanvasDrag(mouseX, mouseY);
      else if(m_isResizing)
         handleCanvasResize(mouseX, mouseY);
     }
   //--- End drag or resize on button release
   else if(mouseState == 0 && m_previousMouseButtonState == 1)
     {
      if(m_isDragging || m_isResizing)
        {
         m_isDragging       = false;
         m_isResizing       = false;
         m_activeResizeMode = NO_RESIZE;
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
         needRedraw = true;
        }
     }

   //--- Redraw the visualization if any state changed
   if(needRedraw)
     {
      renderVisualization();
      ChartRedraw();
     }

   //--- Record current mouse position for next event
   m_lastMouseX = mouseX;
   m_lastMouseY = mouseY;
   //--- Record current button state for next event
   m_previousMouseButtonState = mouseState;
  }

//+------------------------------------------------------------------+
//| Handle mouse wheel zoom for the 3D scene                         |
//+------------------------------------------------------------------+
void handleMouseWheel(int mouseX, int mouseY, double delta)
  {
   //--- Determine if the wheel event occurred over the 3D canvas body
   bool isOverCanvas = (mouseX >= m_currentPositionX &&
                        mouseX <= m_currentPositionX + m_currentWidth &&
                        mouseY >= m_currentPositionY + HEADER_BAR_HEIGHT &&
                        mouseY <= m_currentPositionY + m_currentHeight);

   //--- Apply zoom only in 3D mode and when cursor is over the canvas
   if(m_currentViewMode == VIEW_3D_MODE && isOverCanvas)
     {
      //--- Suppress chart scroll so wheel is captured by the visualizer
      ChartSetInteger(0, CHART_MOUSE_SCROLL, false);

      //--- Adjust camera distance by a small fraction of the wheel delta
      m_cameraDistance *= 1.0 - delta * 0.001;

      //--- Clamp distance to prevent clipping through the scene
      if(m_cameraDistance <  20.0) m_cameraDistance =  20.0;
      if(m_cameraDistance > 200.0) m_cameraDistance = 200.0;

      //--- Re-render with updated camera distance
      renderVisualization();
      ChartRedraw();
     }
   else
     {
      //--- Restore chart scroll when wheel is outside the canvas
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
     }
  }

//+------------------------------------------------------------------+
//| Return true when the cursor is over the mode switch icon         |
//+------------------------------------------------------------------+
bool isMouseOverSwitchIcon(int mouseX, int mouseY)
  {
   //--- Compute the icon's left edge from the canvas right margin
   int iconX = m_currentPositionX + m_currentWidth - SWITCH_ICON_SIZE - SWITCH_ICON_MARGIN;
   //--- Vertically centre the icon within the header bar
   int iconY = m_currentPositionY + (HEADER_BAR_HEIGHT - SWITCH_ICON_SIZE) / 2;

   //--- Return true if the cursor falls within the icon bounding box
   return (mouseX >= iconX && mouseX <= iconX + SWITCH_ICON_SIZE &&
           mouseY >= iconY && mouseY <= iconY + SWITCH_ICON_SIZE);
  }

//+------------------------------------------------------------------+
//| Toggle between 2D and 3D view modes                              |
//+------------------------------------------------------------------+
void switchViewMode()
  {
   //--- Switch from 2D to 3D
   if(m_currentViewMode == VIEW_2D_MODE)
     {
      m_currentViewMode = VIEW_3D_MODE;
      Print("Switched to 3D mode");

      //--- Set up the 3D scene; revert to 2D on failure
      if(!setup3DMode())
        {
         Print("ERROR: Failed to setup 3D mode, reverting to 2D");
         m_currentViewMode = VIEW_2D_MODE;
        }
      else
        {
         //--- Auto-fit camera to the scene on mode entry
         if(autoFitCamera)
            autoFitCameraPosition();
        }
     }
   else
     {
      //--- Switch from 3D back to 2D
      m_currentViewMode = VIEW_2D_MODE;
      Print("Switched to 2D mode");
     }

   //--- Render the scene in the new mode immediately
   renderVisualization();
   ChartRedraw();
  }

handleMouseEvent関数を定義し、ビジュアライザー内のすべてのマウス操作を管理します。まず、以前のホバー状態を保存します。マウス位置をCanvasの範囲と比較してm_isHoveringCanvasを更新し、isMouseOverHeaderBarを使用してm_isHoveringHeaderを設定します。isMouseOverSwitchIconによってm_isHoveringSwitchIconを設定し、isMouseInResizeZoneを使用してm_isHoveringResizeZoneを設定します。

いずれかのホバー状態が変更された場合、再描画フラグを設定します。VIEW_3D_MODEで、Canvas上かつヘッダー上ではない場合、回転処理を行います。ボタン押下時には、m_isRotating3Dをtrueに設定し、開始位置を記録します。また、ChartSetIntegerを使用してCHART_MOUSE_SCROLLを無効化し、スクロールを停止します。ドラッグ中は、移動量を300.0でスケーリングし、その差分に基づいてm_cameraAngleYとm_cameraAngleXを調整します。反転を防ぐため、X角度を-0.49PIから0.49PIの範囲に制限します。その後、開始位置を更新し、再描画フラグを設定します。ボタンを離した場合は、回転状態をリセットし、スクロールを有効化します。次に、押下状態を確認します。スイッチアイコン上にある場合はswitchViewModeを呼び出し、状態を更新した後に処理を終了します。ヘッダー上でドラッグ可能かつサイズ変更ではない場合は、ドラッグ状態を有効化し、開始位置を記録してスクロールを無効化します。サイズ変更領域上にある場合は、サイズ変更を有効化し、モードを設定し、初期値を取得してスクロールを無効化します。その後、再描画フラグを設定します。ボタンが押された状態では、handleCanvasDragまたはhandleCanvasResizeを呼び出します。ボタンを離した場合は、フラグとモードをリセットし、スクロールを有効化して再描画フラグを設定します必要に応じてrenderVisualizationとChartRedrawを呼び出します。その後、最後のマウス位置と状態を更新します。

次に、3Dズーム処理を行うhandleMouseWheel関数を実装します。VIEW_3D_MODEで、マウスがヘッダーより下のプロット領域上にあるか確認します。条件を満たす場合はスクロールを無効化します。スムーズな調整を行うため、1.0からスケーリングしたデルタ値を引いた値を使用してm_cameraDistanceを乗算します。その後、値を20.0から200.0の範囲に制限し、再描画付きで再レンダリングします。条件を満たさない場合は、チャート操作を可能にするためスクロールを有効化します。

トグルボタン上のホバー状態を検出するため、isMouseOverSwitchIcon関数を作成します。現在のサイズと定数からアイコン座標を計算し、マウス位置が正方形の範囲内にある場合はtrueを返します。最後に、表示モードを切り替えるswitchViewMode関数を定義します。現在が2Dの場合は、VIEW_3D_MODEに設定します。切り替えメッセージを出力し、setup3DModeを実行します。失敗した場合は、元のモードへ戻し、エラーを出力します。成功した場合、必要に応じてカメラを自動調整します。現在が3Dの場合は、2Dへ切り替え、切り替えメッセージを出力します。その後、新しいモードをrenderVisualizationで描画し、ChartRedrawを呼び出して即座に更新します。これで、チャートイベントハンドラ内でこれらの関数を呼び出すことができます。

//+------------------------------------------------------------------+
//| Route chart mouse and wheel events to the visualizer             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   //--- Abort if the visualizer has not been initialised
   if(distributionVisualizer == NULL) return;

   //--- Handle mouse move and button events
   if(id == CHARTEVENT_MOUSE_MOVE)
     {
      int mouseX     = (int)lparam;                        // Horizontal cursor position in pixels
      int mouseY     = (int)dparam;                        // Vertical cursor position in pixels
      int mouseState = (int)StringToInteger(sparam);       // Bitmask of pressed mouse buttons

      distributionVisualizer.handleMouseEvent(mouseX, mouseY, mouseState);
     }

   //--- Handle mouse wheel scroll events
   if(id == CHARTEVENT_MOUSE_WHEEL)
     {
      //--- Unpack the cursor X from the low 16 bits of lparam
      int mouseX = (int)(short) lparam;
      //--- Unpack the cursor Y from the high 16 bits of lparam
      int mouseY = (int)(short)(lparam >> 16);

      distributionVisualizer.handleMouseWheel(mouseX, mouseY, dparam);
     }
  }

OnChartEventイベントハンドラでは、チャート操作を全体的に処理します。エラーを防ぐため、distributionVisualizerがNULLの場合は早期に処理を終了します。IDがCHARTEVENT_MOUSE_MOVEの場合、パラメータをキャストしてマウス座標と状態を取得し、その後、ビジュアライザーのインスタンス上でhandleMouseEventへ処理を委譲します。CHARTEVENT_MOUSE_WHEELの場合は、lparamのビットから調整されたマウス位置を取得し、deltaとともにhandleMouseWheelを呼び出します。これにより、3D表示でホイールによるズーム操作が可能になります。次に、以下と同じ形式を使用して、変更内容を反映させるために初期化解除イベントハンドラとティックイベントハンドラを更新します。

//+------------------------------------------------------------------+
//| Release all resources when the EA is removed                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   //--- Destroy the visualizer object if it still exists
   if(distributionVisualizer != NULL)
     {
      delete distributionVisualizer;
      distributionVisualizer = NULL;
     }

   //--- Redraw the chart to remove the canvas bitmap object
   ChartRedraw();
   Print("Distribution window deinitialized");
  }

//+------------------------------------------------------------------+
//| Reload distribution data on each new bar                         |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Track the last processed bar open time across calls
   static datetime lastBarTimestamp = 0;
   //--- Read the current bar open time on the configured timeframe
   datetime currentBarTimestamp = iTime(_Symbol, chartTimeframe, 0);

   //--- Reload and redraw only when a new bar has formed
   if(currentBarTimestamp > lastBarTimestamp)
     {
      if(distributionVisualizer != NULL)
        {
         //--- Regenerate the binomial sample and statistics
         if(distributionVisualizer.loadDistributionData())
           {
            //--- Redraw the updated visualization
            distributionVisualizer.renderVisualization();
            ChartRedraw();
           }
        }
      //--- Update the bar timestamp to prevent repeated processing
      lastBarTimestamp = currentBarTimestamp;
     }
  }

OnDeinitイベントハンドラでは、distributionVisualizerがNULLではないか確認します。NULLではない場合、インスタンスを削除してリソースを解放し、ポインタをNULLに設定します。その後、ChartRedrawでチャートを再描画し、初期化解除メッセージを出力します。次に、OnTickイベントハンドラでは、static変数lastBarTimestampを使用して前回のバーの開始時刻を追跡します。また、iTimeを使用してsymbol、chartTimeframe、shift 0から現在のバーの開始時刻を取得します。新しいバーが検出された場合、ビジュアライザーが存在することを確認し、loadDistributionDataでデータを再読み込みします。成功した場合はrenderVisualizationによって再描画を行い、ChartRedrawでチャートを更新します。その後、タイムスタンプを更新します。完全なログサイクルは以下のようになります。

相互作用サイクル

これで、3D可視化の実装は完了です。残る作業はシステムの動作検証であり、これは次のセクションで扱います。


バックテスト

テストを実施しました。以下はビジュアルを単一のGraphics Interchange Format (GIF)画像としてまとめたものです。

バックテストGIF

テスト中、ヒストグラムは試行回数の変動に関わらず3D空間内で適切にスケーリングされ、カメラの自動調整機能により読み込み時に常に棒グラフ全体が表示される位置に調整され、2Dと3Dの描画モードの切り替えもデータ損失やレイアウトの乱れなく行われました。


結論

本記事では、MQL5の二項分布ビューアにDirectX 3Dを統合し、2D表示と3D表示を切り替えられる機能に加え、回転・ズーム・自動フィットに対応したカメラ操作を実装しました。また、グラウンドプレーンと色分けされた座標軸を備えた3Dヒストグラムバーを描画し、理論PMF曲線を透視空間へ投影するとともに、統計情報パネル、凡例、カスタマイズ可能なテーマなどの2D要素も維持しました。さらに、クラスベースのアーキテクチャ、マウス操作、新規バー発生時のリアルタイム更新、および試行回数・成功確率・サンプルサイズ・表示設定などを変更できる入力パラメータについて、実装の詳細を解説しました。この記事を読み終えると、次のことができるようになります。

  • チャートを再起動することなく、二項分布の2D表示と3D表示を切り替えられる
  • 3Dヒストグラムを回転・ズームし、さまざまな角度から確率質量関数の形状や度数の違いを詳しく確認する
  • 理論曲線を重ね合わせた3D可視化を利用して、シミュレーションによる標本分布と理論上の二項分布の確率をリアルタイムで比較する

次回以降の記事では、3Dビューをドラッグして移動できるパン操作の追加、2Dバー描画に対応する統計分布関数の拡張、さらに2D表示と3D表示をよりシームレスに切り替えられる仕組みについて解説していきます。次回の記事もぜひご期待ください。

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

EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
ラリー・ウィリアムズの『市場の秘密』(第14回):カスタムインジケータで隠れスマッシュデー反転を検出する ラリー・ウィリアムズの『市場の秘密』(第14回):カスタムインジケータで隠れスマッシュデー反転を検出する
厳密な数値条件と、オプションとして次のセッションによる確認を利用して、隠れスマッシュデー反転バーを検出する実用的なMQL5インジケータを開発します。検出ルーチン、バッファ登録、そして有効なバー上に矢印を表示するためのプロット設定について説明します。このアプローチにより、過去データのテストおよびリアルタイム監視において、安定した非リペイントシグナルを提供します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
PDアレイを使いこなす:PDアレイ内のインバランスを活用したトレードの最適化 PDアレイを使いこなす:PDアレイ内のインバランスを活用したトレードの最適化
PDアレイ(PD Arrays)内で発生するインバランスから生じるトレードセットアップを、どのように整理・定義し、活用すべきかを明確に解説することを目的とした、特化型のトレンドフォロー型EAに関する記事です。本記事では、PDアレイおよびインバランスを自身のトレードにおけるエントリー条件やトレード判断基準として最適化し、活用したいと考えるトレーダー向けに設計されたEAについて詳しく解説します。また、高値圏(Premium Array)および安値圏(Discount Array)を正しく判定し、それぞれが適切な相場環境で発生した際に、どのように検証し活用するべきかについても説明します。