トレードにおけるOLAPの適用(パート2):インタラクティブな多次元データ分析結果の可視化

Stanislav Korotky | 8 7月, 2019

トレードにおけるOLAP技術の使用に関する最初の記事では、一般的な多次元データ処理原理を考慮し、アカウントヒストリーにOLAPを実用的に適用することを可能にするすぐに使用できるMQLクラスを提供しました。トレードレポート処理。 ただし、エキスパート ログのテキストとして結果の簡略化された出力を実装しました。 より効率的な視覚的表現これを行うには、グラフィックスを使用して OLAP データを視覚化できる表示インターフェイスの子である新しいクラスを作成する必要があります。 このタスクは、多くの準備タスクを必要とし、OLAP に関連しない多くの異なる側面に関するものです。 では、データ処理を脇に置いて、MQLプログラムのグラフィカルインターフェイスに焦点を当ててみましょう。

GUI 実装には、コントロールの標準ライブラリ(インクルード/コントロール) など、 MQL ライブラリを使用できます。 ほぼすべてのライブラリで顕著な欠点の 1 つは、ウィンドウ内の要素のレイアウトを自動的に制御する手段がないということに関連しています。 つまり、要素のポジション決めとポジション合わせは、X 座標と Y 座標を持つハードコーディングされた定数を使用して静的に実行されます。 最初のものと密接に関連する別の問題があります: 画面フォームの視覚的なデザインはありません。 さらに難しいタスクですが、不可能ではありません。 インターフェイスはこのプロジェクト内の主要なトピックではないため、画面フォームエディタに焦点を当てるのではなく、よりシンプルなアダプティブインターフェイスアプローチを実装することにしました。 このインターフェイスの要素は、相関する配置とスケーリングルールを自動的にサポートできるグループに特別に配置する必要があります。

標準ライブラリの問題は、ダイアログ ウィンドウのサイズが固定されていることです。 ただし、大きな OLAP ハイパーキューブをレンダリングする場合は、ウィンドウを最大化したり、少なくとも重なり合わずに軸上のセル ラベルに収まるほどストレッチしたりする方が便利です。

mql5.comのウェブサイトでは、別々のオープンGUI関連の開発が利用可能です: 別々の問題に対処しますが、その複雑さ/能力比は最適ではありません。 関数が制限されている (たとえば、ソリューションにはレイアウト メカニズムが用意されているがスケーリング オプションが用意されていない) か、統合に多大な労力が必要です (広範なドキュメントを読んだり、標準以外の方法を学ぶ必要があります)。 さらに、他のすべてのものが等しい場合は、より一般的で一般的な標準要素に基づくソリューションを使用することをお考えめします(つまり、より多くのMQLアプリケーションで使用され、したがって、ユーティリティ係数が高い)。

その結果、GUIコントロールのレイアウトとコンテナの使用:CBox クラスと GUI コントロールのレイアウトとコンテナの使用: エンリコ ランビーノによる CGrid クラスの記事で説明されている、シンプルな技術的なソリューションと思われるものを選択しました。

最初の記事では、コントロールが水平レイアウトまたは垂直レイアウトのコンテナに追加されます。 ネストにすることができ、したがって、任意のインターフェイスレイアウトを提供します。 2 番目の記事では、コンテナに表形式のレイアウトを表示します。 どちらも、すべての標準コントロールと、CWnd クラスに基づいて適切に開発されたコントロールで動作します。

このソリューションには、動的なウィンドウとコンテナのサイズ変更のみが含まれます。 一般的な問題を解決するための最初のステップになります。

"ラバー" ウィンドウ

CBox クラスと CGrid クラスは、ヘッダ ファイル Box.mqh、Grid.mqh、GridTk.mqh などのプロジェクトに接続されています。 記事のアーカイブを使用している場合は、ファイルをインクルード/レイアウト ディレクトリの下にインストールします。

注意! 標準ライブラリには既に CGrid 構造体があります。 チャートグリッドを描画するために設計されています。 CGrid コンテナ クラスは関連していません。 名前の偶然の一致は不愉快ですが、重要ではありません。

GridTk.mqhファイルの小さなエラーを修正し、Box.mqhファイルに追加を行い、その後、標準のダイアログクラスCAppDialogの改善に直接進むことができます。 もちろん、既存のクラスを壊すわけではありません。 代わりに、CAppDialog から派生した新しいクラスを作成します。

主な変更点は、CBox::GetTotalControlsSize メソッドに関するもので、関連する行にはコメントが付いています)。 元のプロジェクトのファイルと、以下に添付されているファイルを比較できます。

  void CBox::GetTotalControlsSize(void)
  {
    m_total_x = 0;
    m_total_y = 0;
    m_controls_total = 0;
    m_min_size.cx = 0;
    m_min_size.cy = 0;
    int total = ControlsTotal();
    
    for(int i = 0; i < total; i++)
    {
      CWnd *control = Control(i);
      if(control == NULL) continue;
      if(control == &m_background) continue;
      CheckControlSize(control);
      
      //追加: ネストになったコンテナに対して再帰的に呼び出す
      if(control.Type() == CLASS_LAYOUT)
      {
        ((CBox *)control).GetTotalControlsSize();
      }
      
      CSize control_size = control.Size();
      if(m_min_size.cx < control_size.cx)
        m_min_size.cx = control_size.cx;
      if(m_min_size.cy < control_size.cy)
        m_min_size.cy = control_size.cy;
      
      //編集: m_total_x および m_total_y は、コンテナの向きに従って条件付きでインクリムされます。
      if(m_layout_style == LAYOUT_STYLE_HORIZONTAL) m_total_x += control_size.cx;
      else m_total_x = MathMax(m_min_size.cx, m_total_x);
      if(m_layout_style == LAYOUT_STYLE_VERTICAL) m_total_y += control_size.cy;
      else m_total_y = MathMax(m_min_size.cy, m_total_y);
      m_controls_total++;
    }
    
    //追加: 新しい合計に応じてコンテナのサイズを調整します。
    CSize size = Size();
    if(m_total_x > size.cx && m_layout_style == LAYOUT_STYLE_HORIZONTAL)
    {
      size.cx = m_total_x;
    }
    if(m_total_y > size.cy && m_layout_style == LAYOUT_STYLE_VERTICAL)
    {
      size.cy = m_total_y;
    }
    Size(size);
  }

要するに、変更されたバージョンでは、要素の動的なサイズ変更が可能な場合を考慮に入れます。

元の記事のテスト例には、Controls2EA(標準の MetaTrader 配信パッケージで利用可能な標準コントロールプロジェクトの類似体、エキスパートサンプルコントロールフォルダ)とSlidingPuzzle2が含まれていました。 どちらのコンテナの例も、デフォルトではエキスパート例Layout フォルダの下にあります。 コンテナをベースに、ラバーウィンドウの実装を試みます。

Include\Layouts\下のMaximizableAppDialog.mqhを生成します。 このウィンドウ クラスは CAppDialog から継承されます。

  #include <Controls\Dialog.mqh>
  #include <Controls\Button.mqh>
  
  class MaximizableAppDialog: public CAppDialog
  {

画像を含む 2 つの新しいボタンが必要です: ウィンドウを最大化するボタン (ヘッダ、最小化ボタンの横に配置されます) と、任意のサイズ変更用のボタン (右下隅) の 1 つです。

  protected:
    CBmpButton m_button_truemax;
    CBmpButton m_button_size;

現在の最大化状態またはサイズ変更プロセスの表示は、対応する論理変数に格納されます。

    bool m_maximized;
    bool m_sizing;

また、最大状態のチャート サイズを常に監視する4角形を追加し (チャート サイズも調整する必要があります)、ウィンドウを小さくすることはできません (ユーザーがこの制限値を設定できるようにする) 特定の最小サイズを設定します。SetSizeLimit パブリックメソッドを使用する場合)。

    CRect m_max_rect;
    CSize m_size_limit;

新しく追加された最大化モードとサイズ変更モードは、標準モード(デフォルトサイズとダイアログの最小化)と相互作用する必要があります。 したがって、ウィンドウが最大化されている場合は、標準サイズで許可されているタイトルバーを押したままにしてドラッグしないでください。 また、ウィンドウを最大化する場合は、最小化ボタンの状態をリセットする必要があります。 このために、CDialogクラスのCEdit m_captionとCAppDialogのCBmpButton m_button_minmaxの変数にアクセスする必要があります。 残念ながら、クラスの他の多くのメンバは、プライベートセクションで宣言されています。 これは一見かなり奇妙に見えますが、基本クラスは広く使用することを目的としたパブリックライブラリの一部です。 より良い解決策は、すべてのメンバを「保護」として宣言するか、少なくともメンバにアクセスするためのメソッドを提供することです。 しかし、今回のタイトルバー場合、プライベートです。 そのため、"patch"を追加して標準ライブラリを修正するだけです。 この更新プログラムの問題は、ライブラリの更新後に、更新プログラムを再度適用する必要がある点です。 しかし、CDialogとCAppDialogの重複クラスを作成する唯一の可能な代替ソリューションは、OOPイデオロギーの観点からは適切ではないようです。

クラス メンバのプライベート宣言によって派生クラスの関数の拡張が妨がらない場合の直近のケースではありません。 したがって、インクルード/コントロールフォルダのコピーを作成することをお勧めしますし、コンパイル中に"プライベートメンバアクセスエラー"が発生した場合は、適切な要素を「保護された」セクションに移動したり、置き換えたりするなど、適切なパーツを編集できます。

基本クラスの仮想メソッドの一部を書き直す必要があります。

    virtual bool CreateButtonMinMax(void) override;
    virtual void OnClickButtonMinMax(void) override;
    virtual void Minimize(void) override;
  
    virtual bool OnDialogDragStart(void) override;
    virtual bool OnDialogDragProcess(void) override;
    virtual bool OnDialogDragEnd(void) override;

最初の 3 つの方法は [最小化] ボタンに関連付けられ、他の 3 つの方法は drag'n'drop テクノロジに基づくサイズ変更プロセスに関連します。

ダイアログを作成するための仮想メソッドとイベントへの反応も説明します (後者は、イベント処理マップのマクロ定義で常に暗黙的に使用しますが、後で考慮されます)。

    virtual bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2) override;
    virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) override;

[最大化] ボタンは、事前定義されたバージョンの CreateButtonMinMax の標準の最小化ボタンと共に作成されます。 まず、標準のヘッダ ボタンを取得するために、基本的な実装が呼び出されます。 その後、新しい最大化ボタンが追加で描画されます。 ソース コードには、初期座標と配置を設定し、イメージ リソースを接続する一般的なオーダーセットがあります。 したがって、このコードはここには表示されません。 ソースコード全体は以下に添付されています。 2 つのボタンのリソースは、"res" サブディレクトリの下にあります。

  #resource "res\\expand2.bmp"
  #resource "res\\size6.bmp"
  #resource "res\\size10.bmp"

次の方法は、ボタンのクリックの最大化の処理を行います。

    virtual void OnClickButtonTrueMax(void);

さらに、チャート全体にウィンドウを最大化し、元のサイズを復元するヘルパー メソッドを追加します: メソッドは、OnClickButtonTrueMax から呼び出し、ウィンドウが最大化されているかどうかに応じて、すべてのタスクを実行できます。

    virtual void Expand(void);
    virtual void Restore(void);

スケーリング プロセスのサイズ変更ボタンと昼食の作成は、次の方法で実装されます。

    bool CreateButtonSize(void);
    bool OnDialogSizeStart(void);

イベント処理は、使い慣れたマクロによって決まります。

  EVENT_MAP_BEGIN(MaximizableAppDialog)
    ON_EVENT(ON_CLICK, m_button_truemax, OnClickButtonTrueMax)
    ON_EVENT(ON_DRAG_START, m_button_size, OnDialogSizeStart)
    ON_EVENT_PTR(ON_DRAG_PROCESS, m_drag_object, OnDialogDragProcess)
    ON_EVENT_PTR (ON_DRAG_END, m_drag_object, OnDialogDragEnd)
  EVENT_MAP_END(CAppDialog)

m_button_truemax オブジェクトと m_button_size オブジェクトは自分たちで作成され、m_drag_object は CWnd クラスから継承されます。 オブジェクトは、タイトルバーを使用してウィンドウのドラッグを有効にするために、そのクラスで使用します。 このクラスでは、このオブジェクトのサイズ変更に関与します。

しかし、イベントで必要なすべてのタスクではありません。 チャートのサイズ変更をインターセプトするには、CHARTEVENT_CHART_CHANGE イベントを処理する必要があります。 このために、クラスの ChartEvent メソッドについて説明します: CAppDialog で同様のメソッドが重複します。 したがって、基本的な実装を呼び出す必要があります。 さらに、イベントコードをチェックし、CHARTEVENT_CHART_CHANGEの特定の処理を実行します。

  void MaximizableAppDialog::ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
    if(id == CHARTEVENT_CHART_CHANGE)
    {
      if(OnChartChange(lparam, dparam, sparam)) return;
    }
    CAppDialog::ChartEvent(id, lparam, dparam, sparam);
  }

OnChartChange メソッドはチャートサイズを追跡し、アクティブな最大化モード中にチャートサイズを変更すると、要素の新しいレイアウトが開始されます。 自己調整メソッドによって実行されます。

  bool MaximizableAppDialog::OnChartChange(const long &lparam, const double &dparam, const string &sparam)
  {
    m_max_rect.SetBound(0, 0,
                        (int)ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) - 0 * CONTROLS_BORDER_WIDTH,
                        (int)ChartGetInteger(ChartID(), CHART_HEIGHT_IN_PIXELS) - 1 * CONTROLS_BORDER_WIDTH);
    if(m_maximized)
    {
      if(m_rect.Width() != m_max_rect.Width() || m_rect.Height() != m_max_rect.Height())
      {
        Rebound(m_max_rect);
        SelfAdjustment();
        m_chart.Redraw();
      }
      return true;
    }
    return false;
  }

このメソッドは、MaximizableAppDialog クラスで抽象および仮想として宣言されます。

    virtual void SelfAdjustment(const bool minimized = false) = 0;

同じメソッドは、サイズ変更が実行される "rubber" ウィンドウ クラスの他の場所から呼び出されます。 たとえば、OnDialogDragProcess (ユーザーが直角の下をドラッグしたとき) と OnDialogDragEnd (ユーザーがスケーリングを完了した) から。

高度なダイアログの動作は次のとおりです: チャートに標準サイズで表示された後、ユーザーはタイトルバー (標準動作) を使用してドラッグし、を最小化 (標準動作) と最大化 (追加された動作) します。 チャートのサイズを変更すると、最大化状態が保存されます。 同じボタンを最大化状態で使用して、ウィンドウを元のサイズにリセットしたり、最小化したりできます。 ウィンドウはまた、最小化された状態から即座に最大化することができます。 ウィンドウが最小化も最大化もされていない場合は、任意のスケーリング(三角形ボタン)のアクティブ領域が右下隅に表示されます。 ウィンドウが最小化または最大化されている場合、この領域は非アクティブ化および非表示になります。

かなり奇妙に見えますが、これより、最大値AppDialogの実装が完了する可能性があります。 しかし、テスト中に別の側面が明らかになり、さらなる開発が必要でしました。

最小化された状態では、アクティブなサイズ変更領域がウィンドウの閉じるボタンと重なり、マウス イベントをインターセプトします。 サイズ変更ボタンは最小化された状態で非表示になり、非アクティブになるため、明らかなライブラリ エラーです。 この問題は、CWnd::OnMouseEvent メソッドに関するものです。 次のチェックが必要です。

  // if(!IS_ENABLED || !IS_VISIBLE) return false; - this line is missing

その結果、無効なコントロールや非表示のコントロールでもイベントをインターセプトします。 明らかに、コントロール要素に適切な Z オーダーを設定することで問題を解決できます。 ただし、ライブラリの問題は、コントロールの Z オーダーを考慮しないことです。 特に、CWndContainer::OnMouseEvent メソッドには、すべての従属要素を逆のオーダーでループするシンプルなループが含まれているため、Z オーダーで優先順位を決定しようとはしません。

したがって、ライブラリの新しいパッチが必要になるか、子クラスに "trick" のようなものが必要です。 ここでは、2 番目の亜種が使用します。 "trick" は次のとおりです: 最小化された状態では、[サイズ変更] ボタンのクリックは[閉じる] ボタンのクリックと解釈されます (重なり合っているボタンであるため)。 このために、次のメソッドが MaximizableAppDialog に追加されました。

  void MaximizableAppDialog::OnClickButtonSizeFixMe(void)
  {
    if(m_minimized)
    {
      Destroy();
    }
  }

メソッドがイベント マップに追加されました。

  EVENT_MAP_BEGIN(MaximizableAppDialog)
    ...
    ON_EVENT(ON_CLICK, m_button_size, OnClickButtonSizeFixMe)
    ...
  EVENT_MAP_END(CAppDialog)

で、MaximizableAppDialogクラスを使用する準備が整いました。 メインチャートエリアでの使用を目的としていますのでご注意ください。

まず、スライディングパズルゲームに追加してみましょう。 SlidingPuzzle2.mq5とSlidingPuzzle2.mqhをSlidingPuzzle3.mq5とSulidingPuzzle3.mqhとしてコピーして編集を開始します。 mq5 ファイルに変更を要するものはほとんどありません。インクルード ファイルへの参照をスライディング Puzzle3.mqh に変更するだけです。

SlidingPuzzle3.mqhファイルに、標準ダイアログクラスの代わりに新しく作成されたクラスをインクルードします。

  #include <Controls\Dialog.mqh>

вставим:

  #include <Layouts\MaximizableAppDialog.mqh>

このクラスの説明では、新しい親クラスを使用する必要があります。

  class CSlidingPuzzleDialog: public MaximizableAppDialog // CAppDialog

クラス名の同様の置き換えは、イベント マップで実行する必要があります。

  EVENT_MAP_END(MaximizableAppDialog) //CAppダイアログ

また、置換はCreateで実行する必要があります。

  bool CSlidingPuzzleDialog::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2)
  {
    if(!MaximizableAppDialog::Create(chart, name, subwin, x1, y1, x2, y2)) // CAppDialog
      return (false);
    ...

最後に、新しいダイアログでは、サイズ変更に応答する Self調整メソッドの実装が必要です。

  void CSlidingPuzzleDialog::SelfAdjustment(const bool minimized = false)
  {
    CSize size;
    size.cx = ClientAreaWidth();
    size.cy = ClientAreaHeight();
    m_main.Size(size);
    m_main.Pack();
  }

関連するタスクは m_main コンテナによって実行されます。その 'Pack' メソッドは、ウィンドウのクライアント領域の直近の既知のサイズに対して呼び出されます。

これでアダプティブレイアウトをゲームに提供するのに十分です。 しかし、コードの読みやすさと効率性を向上させるため、アプリケーションのボタン使用原理を少し変更しました。今では、すべて単一の配列CButton m_buttons[16]で収集され、'switch'オペレータの代わりにインデックスによってアクセスされ、イベント マップ内の (OnClickButton メソッドによる) シングルラインで次の操作を行います。

  ON_INDEXED_EVENT(ON_CLICK, m_buttons, OnClickButton)

元のゲームのソース コードと変更されたコードを比較できます。

アダプティブ ウィンドウの動作を次に示します。

スライディングパズルゲーム

スライディングパズルゲーム

同様に、Experts\Examples\Layouts\Controls2.mq5にあるデモEAを修正する必要があります。メインのmq5ファイルとダイアログ記述を含むインクルードヘッダファイルを修正する必要があります。そのメイン mq5 ファイルとダイアログの説明を含むインクルード ヘッダー ファイルは、新しい名前の Controls3.mq5 および ControlsDialog3.mqh の下にここに表示されます。 ゲームはグリッドタイプのコンテナを使用し、コントロールを含むダイアログは'box'タイプに基づいて構築されます。

変更されたプロジェクトに、ゲームで使用されているものと同様に、Self調整メソッドの同じ実装を残すと、以前は気付かなかった欠陥に気付きます。つまり、アダプティブ ウィンドウのサイズ変更はウィンドウ自体に対してのみ機能しますが、コントロールに影響はありません。 動的ウィンドウ サイズに合わせてコントロールのサイズを調整するものを実装する必要があります。

"ラバー" コントロール

異なる標準コントロールは、動的サイズ変更に異なる適応があります。 CButton ボタンなど、一部のメソッド呼び出しに適切に応答できるものがあります。 CListView リストなどの他のユーザーは、 「配置」を使用してポジション合わせを設定するだけで、システムは自動的にコントロールとウィンドウの境界線の間の距離を保存します。 ただし、一部のコントロールは、亜種のいずれもサポートしていません。 これは、CSpinEdit や CComboBox などが含まれます。 新しい関数を追加するには、サブクラスを作成する必要があります。

CSpinEdit の場合、仮想 OnResize メソッドをオーバーライドするだけで十分です。

  #include <Controls/SpinEdit.mqh> // patch required: private: -> protected:
  
  class SpinEditResizable: public CSpinEdit
  {
    public:
      virtual bool OnResize(void) override
      {
        m_edit.Width(Width());
        m_edit.Height(Height());
        
        int x1 = Width() - (CONTROLS_BUTTON_SIZE + CONTROLS_SPIN_BUTTON_X_OFF);
        int y1 = (Height() - 2 * CONTROLS_SPIN_BUTTON_SIZE) / 2;
        m_inc.Move(Left() + x1, Top() + y1);
        
        x1 = Width() - (CONTROLS_BUTTON_SIZE + CONTROLS_SPIN_BUTTON_X_OFF);
        y1 = (Height() - 2 * CONTROLS_SPIN_BUTTON_SIZE) / 2 + CONTROLS_SPIN_BUTTON_SIZE;
        m_dec.Move(Left() + x1, Top() + y1);
  
        return CWndContainer::OnResize();
      }
  };

CSpinEditは実際には3つの要素、インプットフィールドと2つのボタンで構成されているので、(OnResizeメソッドによって行われる)サイズ変更リクエストに応じて、新しいサイズに合わせてインプットフィールドを増減し、ボタンをフィールドの右端に近づける必要があります。 唯一の問題は、従属要素 m_edit、m_inc、m_dec がプライベート領域で記述されている点です。 したがって、標準ライブラリをもう一度修正する必要があります。 CSpinEdit は、この場合は実装できるアプローチを示すためにのみ使用されました。 実際の OLAP インターフェイスには、適応されたドロップダウン リストが必要です。

ただし、CComboBox クラスをカスタマイズする場合も同様の問題が発生する可能性があります。 派生クラスを実装する前に、CComboBox ベース クラスにパッチを適用する必要があります。 パッチはすべて、標準ライブラリを使用する他のプロジェクトとの互換性には影響しないことに注意してください。

"rubber" コンボ ボックスを実装するには、もう少し工夫が必要です。 OnResize だけでなく、OnClickButton、有効/無効、イベント マップの追加もオーバーライドする必要があります。 すべての下位オブジェクト m_edit、m_list、および m_drop、つまりコンボ ボックスが構成するすべてのオブジェクトを管理します。

  #include <Controls/ComboBox.mqh> // patch required: private: -> protected:
  
  class ComboBoxResizable: public CComboBox
  {
    public:
      virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) override;
  
      virtual bool OnResize(void) override
      {
        m_edit.Width(Width());
        
        int x1 = Width() - (CONTROLS_BUTTON_SIZE + CONTROLS_COMBO_BUTTON_X_OFF);
        int y1 = (Height() - CONTROLS_BUTTON_SIZE) / 2;
        m_drop.Move(Left() + x1, Top() + y1);
        
        m_list.Width(Width());
  
        return CWndContainer::OnResize();
      }
      
      virtual bool OnClickButton(void) override
      {
        //これが、リスト内の要素のサイズ変更をトリガするハックです
        //標準の ListView がこのような方法で誤ってコーディングされているため必要です
        //vscroll が存在する場合にのみ要素のサイズが変更される
        bool vs = m_list.VScrolled();
        if(m_drop.Pressed())
        {
          m_list.VScrolled(true);
        }
        bool b = CComboBox::OnClickButton();
        m_list.VScrolled(vs);
        return b;
      }
      
      virtual bool Enable(void) override
      {
        m_edit.Show();
        m_drop.Show();
        return CComboBox::Enable();
      }
      
      virtual bool Disable(void) override
      {
        m_edit.Hide();
        m_drop.Hide();
        return CComboBox::Disable();
      }
  };
  
  #define EXIT_ON_disabled 
        if(!IsEnabled())   \
        {                  
          return false;    \
        }
  
  EVENT_MAP_BEGIN(ComboBoxResizable)
    EXIT_ON_DISABLED
    ON_EVENT(ON_CLICK, m_drop, OnClickButton)
  EVENT_MAP_END(CComboBox)

デモ プロジェクト Controls3 を使用して、 "rubber" コントロールを確認できるようになりました。 CSpinEdit クラスと CComboBox クラスをそれぞれ SpinEditResizable クラスとコンボボックス再サイズ可能クラスに置き換えます。 SelfAdjustメソッドでコントロールのサイズを変更します。

  void CControlsDialog::SelfAdjustment(const bool minimized = false)
  {
    CSize min = m_main.GetMinSize();
    CSize size;
    size.cx = ClientAreaWidth();
    size.cy = ClientAreaHeight();
    if(minimized)
    {
      if(min.cx > size.cx) size.cx = min.cx;
      if(min.cy > size.cy) size.cy = min.cy;
    }
    m_main.Size(size);
    int w = (m_button_row.Width() - 2 * 2 * 2 * 3) / 3;
    m_button1.Width(w);
    m_button2.Width(w);
    m_button3.Width(w);
    m_edit.Width(w);
    m_spin_edit.Width(w);
    m_combo_box.Width(m_lists_row.Width() / 2);
    m_main.Pack();
  }

SelfAdjust メソッドは、ウィンドウのサイズ変更後に親 MaximizableAppDialog クラスによって自動的に呼び出されます。 さらに、このメソッドは、CreateMain メソッドから、ウィンドウの初期化時に一度、自分自身を呼び出します。

これが実際の見え方です。(単純にするために、コントロールはタスク領域を水平方向にのみ塗りつぶしますが、同じ効果を垂直方向に適用できます)。

コントロールのデモンストレーション

コントロールのデモンストレーション

赤いボックスはデバッグ用にここに表示され、LAYOUT_BOX_DEBUG マクロを使用して無効にすることができます。

上記の変更に加えて、制御初期化も少し変更しました。 ウィンドウのメイン クライアント領域から始めて、各ブロックは専用のメソッド (CreateMain、CreateEditRow、CreateButtonRow など) で完全に初期化され、成功した場合は作成されたコンテナ タイプ (CWnd *) への参照を返します。 親コンテナは、CWndContainer::Add を呼び出すことによって子を追加します。 これで、メイン ダイアログの初期化ダイアログは次のようになります。

  bool CControlsDialog::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2)
  {
      if(MaximizableAppDialog::Create(chart, name, subwin, x1, y1, x2, y2)
      && Add(CreateMain(chart, name, subwin)))
      {
          return true;
      }
      return false;
  }
  
  CWnd *CControlsDialog::CreateMain(const long chart, const string name, const int subwin)
  {
      m_main.LayoutStyle(LAYOUT_STYLE_VERTICAL);
      if(m_main.Create(chart, name + "main", subwin, 0, 0, ClientAreaWidth(), ClientAreaHeight())
      && m_main.Add(CreateEditRow(chart, name, subwin))
      && m_main.Add(CreateButtonRow(chart, name, subwin))
      && m_main.Add(CreateSpinDateRow(chart, name, subwin))
      && m_main.Add(CreateListsRow(chart, name, subwin))
      && m_main.Pack())
      {
          SelfAdjustment();
          return &m_main;
      }
      return NULL;
  }

ボタンを含むラインの初期化を次に表示します。

  CWnd *CControlsDialog::CreateButtonRow(const long chart, const string name, const int subwin)
  {
      if(m_button_row.Create(chart, name + "buttonrow", subwin, 0, 0, ClientAreaWidth(), BUTTON_HEIGHT * 1.5)
      && m_button_row.Add(CreateButton1())
      && m_button_row.Add(CreateButton2())
      && m_button_row.Add(CreateButton3()))
      {
        m_button_row.Alignment(WND_ALIGN_LEFT|WND_ALIGN_RIGHT, 2, 0, 2, 0);
        return &m_button_row;
      }
      return NULL;
  }

この構文は、以前に使用した構文よりも論理的でコンパクトなようです。 ただし、このような実装では、古いプロジェクトと新しいプロジェクトのコンテキストの比較が困難な場合があります。

コントロールに関してはまだやるべきことがたくさんあります。 プロジェクトの目的は、OLAP のグラフィカル インターフェイスを実装することです。 したがって、中央制御は "チャート"です。 問題は、標準ライブラリにそのような制御がないということです。 これを作成する必要があります。

"チャート" コントロール (CPlot)

MQL ライブラリには、グラフィック プリミティブがいくつか提供されます。 これには、キャンバス (CCanvas)、キャンバスベースのグラフィックス (CGraphic)、および既製のイメージを表示するためのグラフィック オブジェクト (CChartObjectBitmap、CPicture) が含まれますが、今回必要なグラフィックスとは関係ありません。 上記のプリミティブのいずれかをウィンドウ インターフェイスに挿入するには、プロットできる適切なコントロールの子クラスにラップする必要があります。 幸いなことに、このタスクを最初から解決する必要はありません。 このサイトの記事 データ配列間の相関関係を分析するためのCGraphicに基づくPairPlotチャートを参照してください。 そこでは、シンボル間の相関関係を分析するための一連のチャートを含む、すぐに使用可能なコントロールクラスを提供しています。 したがって、コントロール内の単一のチャートで機能するために変更するだけで、必要な結果が得られます。

この記事のファイルは、Include\PairPlot\ ディレクトリにインストールされます。 対象となるクラスが含まれているファイルは、PairPlot.mqh と呼ばれています。 このファイルに基づいて、Plot.mqh という名前で亜種を作成します。 主な違い:

CTimeserie クラスは今回必要ないので、削除しましょう。 CWndClient から派生した CPairPlot コントロール クラスは CPlot に変換され、クロスシンボル チャートを使用した操作は 1 つのチャートに置き換えられます。 上記のプロジェクトのチャートは、一般的な CPlotBase クラスから派生した特殊ヒストグラム クラス (CHistogram) と散布図クラス (CScatter) を使用してプロットされます ( CGraphic から派生します)。 CPlotBase を独自の CGraphicInPlot クラスに変換します。 特別な図や散布図は必要ありません。 代わりに、CGraphic クラス (つまり、隣接する CCurve クラス) によって提供される標準描画スタイル(CURVE_POINTS、CURVE_LINES、CURVE_POINTS_AND_LINES、CURVE_STEPS、CURVE_HISTOGRAM)を使用します。 クラス間の関係の簡略化図を以下に示します。

グラフィッククラス間の関係図

グラフィッククラス間の関係図

新しく追加されたクラスには灰色の色が使用され、他のすべてのクラスは標準です。

PlotDemo テスト エキスパート アドバイザーを作成して、新しいコントロールを確認してみましょう。 初期化、イベントへのバインド、起動は、PlotDemo.mq5 ファイルに実装され、ダイアログの説明は PlotDemo.mqh にあります。 (両方のファイルが添付されています)。

このEAは、描画スタイルの唯一のインプットパラメータを受け入れます。

  #include "PlotDemo.mqh"
  
  input ENUM_CURVE_TYPE PlotType = CURVE_POINTS;
  
  CPlotDemo *pPlotDemo;
  
  int OnInit()
  {
      pPlotDemo = new CPlotDemo;
      if(CheckPointer(pPlotDemo) == POINTER_INVALID) return INIT_FAILED;
  
      if(!pPlotDemo.Create(0, "Plot Demo", 0, 20, 20, 800, 600, PlotType)) return INIT_FAILED;
      if(!pPlotDemo.Run()) return INIT_FAILED;
      pPlotDemo.Refresh();
  
      return INIT_SUCCEEDED;
  }
  
  void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
      pPlotDemo.ChartEvent(id, lparam, dparam, sparam);
  }
  
  ...

ダイアログのヘッダ ファイルにコントロール オブジェクトを作成し、2 つのテスト カーブを追加します。

  #include <Controls\Dialog.mqh>
  #include <PairPlot/Plot.mqh>
  #include <Layouts/MaximizableAppDialog.mqh>
  
  class CPlotDemo: public MaximizableAppDialog // CAppDialog
  {
    private:
      CPlot m_plot;
  
    public:
      CPlotDemo() {}
      ~CPlotDemo() {}
  
      bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2, const ENUM_CURVE_TYPE curveType = CURVE_POINTS);
      virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
      bool Refresh(void);
  
      virtual void SelfAdjustment(const bool minimized = false) override
      {
        if(!minimized)
        {
          m_plot.Size(ClientAreaWidth(), ClientAreaHeight());
          m_plot.Resize(0, 0, ClientAreaWidth(), ClientAreaHeight());
        }
        m_plot.Refresh();
      }
  };
  
  EVENT_MAP_BEGIN(CPlotDemo)
  EVENT_MAP_END(MaximizableAppDialog)
  
  bool CPlotDemo::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2, const ENUM_CURVE_TYPE curveType = CURVE_POINTS)
  {
      const int maxw = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      const int maxh = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
      int _x1 = x1;
      int _y1 = y1;
      int _x2 = x2;
      int _y2 = y2;
      if(x2 - x1 > maxw || x2 > maxw)
      {
        _x1 = 0;
        _x2 = _x1 + maxw - 0;
      }
      if(y2 - y1 > maxh || y2 > maxh)
      {
        _y1 = 0;
        _y2 = _y1 + maxh - 1;
      }
      
      if(!MaximizableAppDialog::Create(chart, name, subwin, _x1, _y1, _x2, _y2))
          return false;
      if(!m_plot.Create(m_chart_id, m_name + "Plot", m_subwin, 0, 0, ClientAreaWidth(), ClientAreaHeight(), curveType))
          return false;
      if(!Add(m_plot))
          return false;
      double x[] = {-10, -4, -1, 2, 3, 4, 5, 6, 7, 8};
      double y[] = {-5, 4, -10, 23, 17, 18, -9, 13, 17, 4};
      m_plot.CurveAdd(x, y, "Example 1");
      m_plot.CurveAdd(y, x, "Example 2");
      return true;
  }
  
  bool CPlotDemo::Refresh(void)
  {
      return m_plot.Refresh();
  }

EA操作は以下に視覚化されます。

グラフィックスを使用したコントロールのデモンストレーション

グラフィックスを使用したコントロールのデモンストレーション

これで、タスクの大部分が完了し、グラフィックスサポートを備えたアダプティブインターフェイスを作成するOLAPプロジェクトにとっては十分です。 これまでを要約して、グラフィカルユーザーインターフェイスに関連するメインクラスの図を紹介します。

制御クラスの図

制御クラスの図

白色は標準クラスに使用します。黄色はコンテナクラスに使用します。ピンクは、サイズ変更をサポートするダイアログやカスタマイズされた要素のクラスに使用します。緑は組み込みのグラフィックスを持つコントロールに使用します。

OLAP の GUI

トレードヒストリーデータのインタラクティブな処理と可視化を実装する新しいEAを作成してみましょう: OLAPGUI. ウィンドウとコントロールの作成、ユーザー・アクションへの応答、および OLAP 関数呼び出しに関するすべての操作は、OLAPGUI.mqh ヘッダ・ファイルに含まれています。

HTML または CSV からのデータインポートに関連するEAインプットだけを残します。 まず、最初の OLAPDEMO プロジェクトから既に使い慣れているReportFile、プレフィックス、サフィックス変数に関するものです。 ReportFileが空の場合、EAは現在の口座のトレードヒストリーを分析します。

コントロール要素を使用して、セレクタ、アグリゲータ、チャートスタイルが選択されます。 ハイパーキューブの 3 次元、つまり条件付き軸 X、Y、Z の 3 つのセレクタを設定する可能性を保持します。このために、3つのドロップダウンリストが必要になります。 コントロールの上の行に配置します。 同じ行の右端に近い [プロセス] ボタンを追加し、クリックして解析を開始します。

アグリゲータ関数とフィールドの選択は、集計が実行されるに応じて、コントロールの 2 行目にある他の 2 つのドロップダウン リストを使用して実装されます。 並べ替えオーダーとチャート スタイルのドロップドローン リストを追加します。 UI を簡略化するためにフィルタリングが不要になります。

残りの領域はチャートで占有されます。

セレクタを含むドロップダウン リストには、同じオプションセットが含まれます。 セレクタのタイプと直接出力レコードを組み合わせます。 次の表は、コントロールの名前と対応するフィールドやセレクタの種類を示します。

* でマークされたセレクタの選択は、アグリゲータの種類によって決まります。それ以外の場合は、クオンタイズセレクタが使用します。

ドロップダウン リストのセレクタの名前 (ポイント 1 ~ 9) はクオートで囲まれています。

セレクタは、X から Z まで、左から右に順番に選択する必要があります。トレーリングの軸のコンボ ボックスは、前の測定セレクタを選択した後にのみ非表示になります。

サポートされる集計関数:

すべての関数 (直近の関数を除く) では、アグリゲータの右側にあるドロップダウン リストを使用して、集計レコードフィールドの指定が必要です。

"プログレッシブ合計" 関数は、"ordinal" が X 軸に沿ったセレクタとして選択されたことを意味します (つまり、レコードを通過する順次を意味します)。

並べ替え付きのコンボ ボックスは、セレクタ (X) のみを選択した場合に使用できます。

X 軸と Y 軸は、それぞれチャート上に水平方向と垂直方向に配置されます。 Z 軸に沿って異なる座標を持つ 3 次元ハイパーキューブの場合、最もプリミティブなアプローチを適用しました。つまり、Z 平面内の複数のセクションを [プロセス] ボタンを使用してスクロールできます。 Z座標がある場合、ボタン名が "i/ n title >>"に変わり、'i' は現在の Z 座標の数で、'n' は Z 軸に沿ったサンプルの総数で、'title' は軸に沿ってプロットされている内容を示します (たとえば、曜日やトレードの表記は Z 軸セレクタに依存します)。 ハイパーキューブの構築条件を変更すると、ボタンのタイトルが "プロセス" に再度設定され、通常モードで動作が開始されます。 "ID" アグリゲータでは処理が異なります。この場合、キューブには常に 2 つの次元があり、3 つのカーブ (X、Y、Z フィールドの場合) はすべてスクロールせずにチャートにプロットされます。

グラフィカル表示に加えて、各キューブもテキストとしてログに表示されます。 集計がセレクタではなくシンプルなフィールドによって実行される場合に特に重要です。 セレクタは軸に沿ってラベルの出力を提供しますが、シンプルなフィールドを量子化する場合、システムはセルインデックスのみを出力できます。 たとえば、ロットサイズ別に分割された利益を分析するには、X セレクタの "lot" フィールドと"profit 金額" フィールドの "sum" アグリゲータを選択します。 X 軸に沿って次の値を表示できます: 0、0.5、1.0、1.5 など、異なるトレードボリュームの数までです。 ただし、セル番号ですが、ロット値は含みませんが、後者の値はログに反映されます。 このログには、次のメッセージが含まれます。

	Selectors: 1
	SumAggregator<TRADE_RECORD_FIELDS> FIELD_PROFIT_AMOUNT [6]
	X: QuantizationSelector(FIELD_LOT) [6]
	===== QuantizationSelector(FIELD_LOT) =====
	      [value] [title]
	[0] 365.96000 "0.01"
	[1]   0.00000 "0.0"
	[2]   4.65000 "0.03"
	[3]  15.98000 "0.06"
	[4]  34.23000 "0.02"
	[5]   0.00000 "1.0"

ここでの「値」は総利益であり、「タイトル」はこの利益に対応する実際のロット値であり、左側の数字はX軸に沿った座標です。 小数点の値は軸に沿ってチャートに表示されますが、整数インデックスのみが意味を持ちます。 とりわけ、このラベル表示の側面は確かに改善することができます。

OLAPcube.mqh ヘッダ ファイルで GUI コントロールを OLAP コア (最初の記事で説明するアイデアは現在のまま使用) にリンクするには、OLAPLapper レイヤー クラスを実装する必要があります。 最初のデモプロジェクトOLAPDEMOの「プロセス」関数によって実行されたデータと同じ準備操作を備えています。 さて、クラスメソッドです。

  class OLAPWrapper
  {
    protected:
      Selector<TRADE_RECORD_FIELDS> *createSelector(const SELECTORS selector, const TRADE_RECORD_FIELDS field);
  
    public:
      void process(
          const SELECTORS &selectorArray[], const TRADE_RECORD_FIELDS &selectorField[],
          const AGGREGATORS AggregatorType, const TRADE_RECORD_FIELDS AggregatorField, Display &display,
          const SORT_BY SortBy = SORT_BY_NONE,
          const double Filter1value1 = 0, const double Filter1value2 = 0)
      {
        int selectorCount = 0;
        for(int i = 0; i < MathMin(ArraySize(selectorArray), 3); i++)
        {
          selectorCount += selectorArray[i] != SELECTOR_NONE;
        }
        ...
        HistoryDataAdapter<CustomTradeRecord> history;
        HTMLReportAdapter<CustomTradeRecord> report;
        CSVReportAdapter<CustomTradeRecord> external;
        
        DataAdapter *adapter = &history;
        
        if(ReportFile != "")
        {
          if(StringFind(ReportFile, ".htm") > 0 && report.load(ReportFile))
          {
            adapter = &report;
          }
          else
          if(StringFind(ReportFile, ".csv") > 0 && external.load(ReportFile))
          {
            adapter = &external;
          }
          else
          {
            Alert("Unknown file format: ", ReportFile);
            return;
          }
        }
        else
        {
          Print("Analyzing account history");
        }
        
        Selector<TRADE_RECORD_FIELDS> *selectors[];
        ArrayResize(selectors, selectorCount);
        
        for(int i = 0; i < selectorCount; i++)
        {
          selectors[i] = createSelector(selectorArray[i], selectorField[i]);
        }
  
        Aggregator<TRADE_RECORD_FIELDS> *aggregator;
        switch(AggregatorType)
        {
          case AGGREGATOR_SUM:
            aggregator = new SumAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
            break;
            ...
        }
        
        Analyst<TRADE_RECORD_FIELDS> *analyst;
        analyst = new Analyst<TRADE_RECORD_FIELDS>(adapter, aggregator, display);
        
        analyst.acquireData();
        ...
        analyst.build();
        analyst.display(SortBy, AggregatorType == AGGREGATOR_IDENTITY);
        ...
      }

ソースコード全体は以下に添付されています。 OLAPDEMO プロジェクトでインプット変数から受信されたすべての設定が 'process' メソッドのパラメータとして渡され、コントロールの状態に基づいて明らかにインプットされる必要があります。

特に興味深いのは「display(表示)」パラメータです。 OLAP コアは、データビジュアライゼーション用にこの特別な 「表示」インターフェイスを宣言します。 次に、プログラムのグラフィカルな部分に実装する必要があります。 このインターフェイスを使用してオブジェクトを作成することで、最初の記事で説明した "依存関係インジェクション"を実装します。 これより、OLAP コアを変更せずに新しい結果表示方法を接続できます。

OLAPGUI.mq5 ファイルでダイアログを作成し、OLAPWrapper サンプルを渡します。

  #include "OLAPGUI.mqh"
  
  OLAPWrapper olapcore;
  OLAPDialog dialog(olapcore);
  
  int OnInit()
  {
      if(!dialog.Create(0, "OLAPGUI" + (ReportFile != "" ? " : " + ReportFile : ""), 0,  0, 0, 584, 456)) return INIT_FAILED;
      if(!dialog.Run()) return INIT_FAILED;
      return INIT_SUCCEEDED;
  }
  ...

OLAPDialog ダイアログ クラスは、OLAPGUI.mqh で定義されています。

  class OLAPDialog;
  
  //MQL5 は複数の継承をサポートしていないため、このデリゲート オブジェクトが必要です。
  class OLAPDisplay: public Display
  {
    private:
      OLAPDialog *parent;
  
    public:
      OLAPDisplay(OLAPDialog *ptr): parent(ptr) {}
      virtual void display(MetaCube *metaData, const SORT_BY sortby = SORT_BY_NONE, const bool identity = false) override;
  };
  
  class OLAPDialog: public MaximizableAppDialog
  {
    private:
      CBox m_main;
  
      CBox m_row_1;
      ComboBoxResizable m_axis[AXES_NUMBER];
      CButton m_button_ok;
  
      CBox m_row_2;
      ComboBoxResizable m_algo[ALGO_NUMBER]; //アグリゲータ、フィールド、チャートタイプ、並べ替え
  
      CBox m_row_plot;
      CPlot m_plot;
      ...
      OLAPWrapper *olapcore;
      OLAPDisplay *olapdisplay;
      ...
  
    public:
      OLAPDialog(OLAPWrapper &olapimpl)
      {
        olapcore = &olapimpl;
        olapdisplay = new OLAPDisplay(&this);
      }
      
      ~OLAPDialog(void);
      ...

"Process" ボタンのクリックに応答して、OLAPWrapper::process メソッドに必要なパラメータをインプットし、olapdisplay オブジェクトを表示として渡しながら、このメソッドを呼び出します。

  void OLAPDialog::OnClickButton(void)
  {
    SELECTORS Selectors[4];
    TRADE_RECORD_FIELDS Fields[4];
    AGGREGATORS at = (AGGREGATORS)m_algo[0].Value();
    TRADE_RECORD_FIELDS af = (TRADE_RECORD_FIELDS)(AGGREGATORS)m_algo[1].Value();
    SORT_BY sb = (SORT_BY)m_algo[2].Value();
  
    ArrayInitialize(Selectors, SELECTOR_NONE);
    ArrayInitialize(Fields, FIELD_NONE);
    ...
    
    olapcore.process(Selectors, Fields, at, af, olapdisplay, sb);
  }

すべての設定の完全なコードは、以下に添付されています。

MQL は複数の継承をサポートしていないため、補助 OLAPDisplay クラスが必要です。 OLAPDialog クラスは MaximizableAppDialog から派生しているため、ダイアログ インターフェイスを直接実装することはできません。 代わりに、このタスクは OLAPDisplay クラスによって実行されます。

キューブを作成すると、OLAP コアは OLAPDisplay::display メソッドを呼び出します。

  void OLAPDisplay::display(MetaCube *metaData, const SORT_BY sortby = SORT_BY_NONE, const bool identity = false) override
  {
    int consts[];
    int selectorCount = metaData.getDimension();
    ArrayResize(consts, selectorCount);
    ArrayInitialize(consts, 0);
  
    Print(metaData.getMetaCubeTitle(), " [", metaData.getCubeSize(), "]");
    for(int i = 0; i < selectorCount; i++)
    {
      Print(CharToString((uchar)('X' + i)), ": ", metaData.getDimensionTitle(i), " [", metaData.getDimensionRange(i), "]");
    }
    
    if(selectorCount == 1)
    {
      PairArray *result;
      if(metaData.getVector(0, consts, result, sortby))
      {
        Print("===== " + metaData.getDimensionTitle(0) + " =====");
        ArrayPrint(result.array);
        parent.accept1D(result, metaData.getDimensionTitle(0));
      }
      parent.finalize();
      return;
    }
    ...

このため、metaData オブジェクトから表示するデータ (getDimension()、getDimensionTitle()、getVector()) を取得し、ウィンドウに渡すことを目的とします。 上記のフラグメントは、単一のセレクタを持つケースの処理を備えています。 ダイアログ・クラスでは、特別なデータ受信メソッドが予約されています。

  void OLAPDialog::accept1D(const PairArray *data, const string title)
  {
    m_plot.CurveAdd(data, title);
  }
  
  void OLAPDialog::accept2D(const double &x[], const double &y[], const string title)
  {
    m_plot.CurveAdd(x, y, title);
  }
  
  void OLAPDialog::finalize()
  {
    m_plot.Refresh();
    m_button_ok.Text("Process");
  }

OLAPGUI を使用してグラフィカルに表示できる分析プロファイルの例を次に示します。

シンボル別利益(降順)

シンボル別利益(降順)

シンボル別利益(アルファベット順に並べ替え)

シンボル別利益(アルファベット順に並べ替え)

シンボル別利益、ポジションがクローズされた曜日、トレードタイプ "買い"

シンボル別利益、ポジションがクローズされた曜日、トレードタイプ "買い"

シンボル別利益、ポジションがクローズされた曜日、トレードタイプ "売り"

シンボル別利益、ポジションがクローズされた曜日、トレードタイプ "売り"

ロットサイズ別利益(ロットはセルインデックスとして示され、値はログに表示されます)

ロットサイズ別利益(ロットはセルインデックスとして示され、値はログに表示されます)

総バランスカーブ

総バランスカーブ

売買操作による残高

売買操作による残高

各シンボルのカーブを個別にバランス

各シンボルのカーブを個別にバランス

個別のシンボルのスワップカーブ

個別のシンボルのスワップカーブ

各シンボルのトレード「期間」に対する利益依存性を個別に

各シンボルのトレード「期間」に対する利益依存性を個別に

シンボルとタイプ別のトレード数

シンボルとタイプ別のトレード数

各トレードの「利益」と「期間」(秒単位)フィールドの依存

各トレードの「利益」と「期間」(秒単位)フィールドの依存

MFE (%)と MAE (%)すべてのトレードの依存関係

MFE (%)と MAE (%)すべてのトレードの依存関係

残念ながら、標準のヒストグラム描画スタイルでは、同じインデックスを持つ異なる配列の列のオフセットを持つ複数の配列を表示できません。 つまり、同じ座標を持つ値は完全に重なり合います。 この問題は、カスタムヒストグラムビジュアライゼーションメソッド (CGraphic クラスを使用して実行できます) を実装することで解決できます。 しかし、この解決策はこの記事の範囲を超えています。

結論

この記事では、コントロールのサイズ変更とユニバーサル レイアウトをサポートする MQL プログラムの GUI 作成の一般的な原則を確認しました。 この技術に基づいて、OLAPシリーズの最初の記事から、トレードレポートの分析のインタラクティブなアプリケーションを作成しました。 様々なインジケータの任意の組み合わせの視覚化は、隠れたパターンを識別するのに役立ち、トレードシステムの最適化に使用できる複数の基準分析を簡素化します。

添付ファイルの説明については、次の表を参照してください。

OLAPGUIプロジェクト

SlidingPuzzle3プロジェクト

Проект Controls3

PlotDemoプロジェクト