MetaTrader 5をダウンロードする

MetaTrader 5における取引戦略最適化の可視化

8 5月 2018, 11:05
Anatoli Kazharski
0
193

内容

はじめに

取引アルゴリズムを開発する際に、パラメータを最適化しながらテスト結果を表示すると便利です。しかし、最適化グラフタブにある単一のグラフは取り引き戦略の効果を評価するには十分でありません。複数のテストのバランス曲線を同時に見て、最適化後でもそれらを解析出来らならばずっと良いでしょう。MetaTrader 5テスター戦略のビジュアル化稿では、このようなアプリケーションを既に検討しました。しかし、それ以来多くの新しい機会が登場しました。したがって、類似してはるかに強力なアプリケーションを実装することが可能になりました。

本稿では、最適化プロセスの可視化を拡張するためのグラフィカルインターフェイスを備えたMQLアプリケーションが実装されます。グラフィカルインターフェイスには、tEasyAndFastライブラリの最新バージョンが適用されます。多くのMQLコミュニティユーザは、なぜMQLアプリケーションでグラフィカルインタフェースが必要かを尋ねるかもしれません。本稿では、その潜在的な用途を示します。これは、ライブラリを作業に適用する人にとっても役に立つかもしれません。

グラフィカルインタフェイスの開発

ここでは、グラフィカルインターフェイスの開発について簡単に説明します。既にEasyAndFast ライブラリをマスターしていれば、使い方を素早く理解し、MQLアプリケーション用のグラフィカルインターフェイスを開発するのがどれだけ簡単かを評価することができるでしょう。

まず、開発されたアプリケーションの一般的な構造について説明します。Program.mqhファイルにはCProgramアプリケーションクラスが含まれます。この基本クラスはグラフィカルライブラリエンジンに接続されるべきです。

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- グラフィカルインターフェイスを作成するためのライブラリクラス
#include <EasyAndFastGUI\WndEvents.mqh>
//+------------------------------------------------------------------+
//| アプリケーション開発クラス                                          |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
  };

EasyAndFastライブラリは、画像を乱雑にしないために単一のブロック(ライブラリGUI)に表示されます。これはライブラリページで完全に見ることができます。 

 図1 GUI作成ライブラリのインクルード

図1 GUI作成ライブラリのインクルード

MQLプログラムのメイン関数と接続するにはCProgramクラスには同じようなメソッドが作成されるべきです。フレームの作業にはOnTesterXXX()カテゴリーからのメソッドが必要です。

class CProgram : public CWndEvents
  {
public:
   //--- 初期化/初期化会場
   bool              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- 「新ティック」イベントハンドラ
   void              OnTickEvent(void);
   //--- 取引イベントハンドラ
   void              OnTradeEvent(void);
   //--- タイマー
   void              OnTimerEvent(void);
   //--- テスター
   double            OnTesterEvent(void);
   void              OnTesterPassEvent(void);
   void              OnTesterInitEvent(void);
   void              OnTesterDeinitEvent(void);
  };

子の場合メソッドはアプリケーションのメインファイルで次のように呼び出されるべきです

//--- アプリケーションクラスをインクルードする
#include "Program.mqh"
CProgram program;
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                              |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- プログラムを初期化する
   if(!program.OnInitEvent())
     {
      ::Print(__FUNCTION__," > Failed to initialize!");
      return(INIT_FAILED);
     }  
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) { program.OnDeinitEvent(reason); }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick(void) { program.OnTickEvent(); }
//+------------------------------------------------------------------+
//| タイマー関数                                                       |
//+------------------------------------------------------------------+
void OnTimer(void) { program.OnTimerEvent(); }
//+------------------------------------------------------------------+
//| ChartEvent関数                                                    |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
   { program.ChartEvent(id,lparam,dparam,sparam); }
//+------------------------------------------------------------------+
//| テスター関数                                                       |
//+------------------------------------------------------------------+
double OnTester(void) { return(program.OnTesterEvent()); }
//+------------------------------------------------------------------+
//| TesterInit関数                                                    |
//+------------------------------------------------------------------+
void OnTesterInit(void) { program.OnTesterInitEvent(); }
//+------------------------------------------------------------------+
//| TesterPass関数                                                    |
//+------------------------------------------------------------------+
void OnTesterPass(void) { program.OnTesterPassEvent(); }
//+------------------------------------------------------------------+
//| TesterDeinit関数                                                  |
//+------------------------------------------------------------------+
void OnTesterDeinit(void) { program.OnTesterDeinitEvent(); }
//+------------------------------------------------------------------+

これにより、アプリケーションワークピースのグラフィカルインタフェースを開発する準備が整います。主な作業はCProgramクラスで行われます。必要なファイルはすべてProgram.mqh.にインクルードされています。

ここでグラフィカルインターフェイスのコンテンツを定義しましょう。作成するすべての要素を一覧表示します。

  • コントロールのフォーム。
  • グラフに表示される天びんの量を指定するフィールド。
  • 最適化結果の繰り返し表示の速度を調整するフィールド。
  • 反復表示を開始するためのボタン。
  • 結果統計表。
  • EAの外部パラメータを表示するための表。
  • バランスカーブグラフ。
  • 最適化結果グラフ。
  • 追加の要約情報を表示するためのステータスバー。
  • 再スクロール時の合計量からの表示結果のパーセンテージを示すプログレスバー。

以下に、コントロール要素クラスのインスタンスとその作成メソッドの宣言を示します(下記のコードを参照してください)。メソッドのコードは、 CProgramクラスファイルに関連付けられた別のファイル( CreateFrameModeGUI.mqh )に入れられます。開発されたアプリケーションのコードが大きくなるにつれ、個々のファイルによる配布方法の関連性が高まり、プロジェクトのナビゲートが容易になります。

class CProgram : public CWndEvents
  {
private:
   //--- ウィンドウ
   CWindow           m_window1;
   //--- ステータスバー
   CStatusBar        m_status_bar;
   //--- 入力フィールド
   CTextEdit         m_curves_total;
   CTextEdit         m_sleep_ms;
   //--- ボタン
   CButton           m_reply_frames;
   //--- 表
   CTable            m_table_stat;
   CTable            m_table_param;
   //--- グラフ
   CGraph            m_graph1;
   CGraph            m_graph2;
   //--- プログレスバー
   CProgressBar      m_progress_bar;
   //---
public:
   //--- 最適化モードでフレームを操作するためのグラフィカルインターフェイスを作成する
   bool              CreateFrameModeGUI(void);
   //---
private:
   //--- フォーム
   bool              CreateWindow(const string text);
   //--- ステータスバー
   bool              CreateStatusBar(const int x_gap,const int y_gap);
   //--- 表
   bool              CreateTableStat(const int x_gap,const int y_gap);
   bool              CreateTableParam(const int x_gap,const int y_gap);
   //--- 入力フィールド
   bool              CreateCurvesTotal(const int x_gap,const int y_gap,const string text);
   bool              CreateSleep(const int x_gap,const int y_gap,const string text);
   //--- ボタン
   bool              CreateReplyFrames(const int x_gap,const int y_gap,const string text);
   //--- グラフ
   bool              CreateGraph1(const int x_gap,const int y_gap);
   bool              CreateGraph2(const int x_gap,const int y_gap);
   //--- プログレスバー
   bool              CreateProgressBar(const int x_gap,const int y_gap,const string text);
  };
//+------------------------------------------------------------------+
//| コントロール要素作成メソッド                                         |
//+------------------------------------------------------------------+
#include "CreateFrameModeGUI.mqh"
//+------------------------------------------------------------------+

CreateFrameModeGUI.mqh で接続されるファイルのインクルードも有効にしましょう。ここでは例としてアプリのグラフィカルインターフェイス作成のメインメソッドだけを示します。

//+------------------------------------------------------------------+
//|                                           CreateFrameModeGUI.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Program.mqh"
//+------------------------------------------------------------------+
//| 最適化結果の分析とフレームの操作のための                              |
//| グラフィカルインターフェイスを作成する                                |
//+------------------------------------------------------------------+
bool CProgram::CreateFrameModeGUI(void)
  {
//--- フレームを最適化するためのモードでのみインタフェースを作成する
   if(!::MQLInfoInteger(MQL_FRAME_MODE))
      return(false);
//--- コントロール要素のフォームを作成する
   if(!CreateWindow("Frame mode"))
      return(false);
//--- コントロール要素を作成する
   if(!CreateStatusBar(1,23))
      return(false);
   if(!CreateCurvesTotal(7,25,"Curves total:"))
      return(false);
   if(!CreateSleep(145,25,"Sleep:"))
      return(false);
   if(!CreateReplyFrames(255,25,"Replay frames"))
      return(false);
   if(!CreateTableStat(2,50))
      return(false);
   if(!CreateTableParam(2,212))
      return(false);
   if(!CreateGraph1(200,50))
      return(false);
   if(!CreateGraph2(200,159))
      return(false);
//--- プログレスバー
   if(!CreateProgressBar(2,3,"Processing..."))
      return(false);
//--- GUI 作成を完成する
   CWndEvents::CompletedGUI();
   return(true);
  }
...

1つのクラスに属するファイル間の接続は、両面の黄色の矢印で示されます。

 図2 プロジェクトのいくつかのファイルへの分割

図2 プロジェクトのいくつかのファイルへの分割



フレームデータ処理クラスの開発

フレームを操作するために別のCFrameGeneratorクラスを書きましょう。クラスはFrameGenerator.mqhに含まれ、これはProgram.mqhにインクルードされるべきです。例として、グラフィカルインターフェイス要素で表示するためにこれらのフレームを受信する2つのオプションを示します。 

  • 最初のケースでは、グラフオブジェクトにフレームを表示するために、これらのオブジェクトへのポインタがクラスメソッドに渡されます。
  • 2番目のケースでは、特別なメソッドを使用して、他のカテゴリの表を埋めるためのフレームデータを受け取ります。 

これらのオプションのどれを主なものとして残すかはあなた次第です。

EasyAndFastライブラリはデータの可視化に、標準ライブラリのCGraphicクラスを適用します。 そのメソッドにアクセスするためにFrameGenerator.mqhにインクルードしましょう。

//+------------------------------------------------------------------+
//|                                               FrameGenerator.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Graphics\Graphic.mqh>
//+------------------------------------------------------------------+
//| 最適化の結果を受信するためのクラス                                   |
//+------------------------------------------------------------------+
class CFrameGenerator
  {
  };

プログラムの構成は次のようになります。

 図3 作業のためのクラスプロジェクトへの接続

図3 作業のためのクラスプロジェクトへの接続

ここでCFrameGeneratorクラスがどのように構成されているかを見ていきましょう。また、ストラテジーテスターイベントの処理方法も必要です(下記のコードリストを参照)。それらは、我々が開発するプログラムの類似のクラスメソッド( CProgram )で呼び出されます。現在の最適化プロセスが表示されているグラフオブジェクトへのポインタはCFrameGenerator::OnTesterInitEvent()メソッドに渡されます。 

  • 最初のグラフ(graph_balance)には、最適化結果残高の最後の一連の指定された数が表示されます。
  • 2番目のグラフ(graph_result)には全体的な最適化結果が表示されます。
class CFrameGenerator
  {
private:
   //--- データの可視化のためのグラフポインタ
   CGraphic         *m_graph_balance;
   CGraphic         *m_graph_results;
   //---
public:
   //--- ストラテジーテスターイベントハンドラ
   void              OnTesterEvent(const double on_tester_value);
   void              OnTesterInitEvent(CGraphic *graph_balance,CGraphic *graph_result);
   void              OnTesterDeinitEvent(void);
   bool              OnTesterPassEvent(void);
  };
//+------------------------------------------------------------------+
//| OnTesterInit()ハンドラで呼び出されるべき                            |
//+------------------------------------------------------------------+
void CFrameGenerator::OnTesterInitEvent(CGraphic *graph_balance,CGraphic *graph_results)
  {
   m_graph_balance =graph_balance;
   m_graph_results =graph_results;
  }

両方のグラフで、肯定的な結果は緑色で表示され、否定的な結果は赤色で表示されます。

CFrameGenerator::OnTesterEvent()メソッドでは、テスト結果のバランスと統計パラメータを受け取ります。これらのデータはCFrameGenerator::GetBalanceData()メソッドとCFrameGenerator::GetStatData()メソッドを使ってフレームに渡されます。CFrameGenerator::GetBalanceData() メソッドはテスト履歴全体を受け取り、in-/inoutのすべての取引をまとめます。得られた結果はステップごとに m_balance[]配列に保存されます。次に、この配列はCFrameGeneratorクラスのメンバーです。

フレームに送信される動的配列はCFrameGenerator::GetStatData()メソッドに渡されます。そのサイズは、以前に受信した結果のバランスの配列のサイズと一致するものです。さらに、統計パラメータを受け取るいくつかの要素が追加されています。

//--- 統計パラメータの数
#define STAT_TOTAL 7
//+------------------------------------------------------------------+
//| 最適化結果を処理するためのクラス                                     |
//+------------------------------------------------------------------+
class CFrameGenerator
  {
private:
   //--- 結果バランス
   double            m_balance[];
   //---
private:
   //--- バランスデータを受け取る
   int               GetBalanceData(void);
   //--- 統計データを受け取る
   void              GetStatData(double &dst_array[],double on_tester_value);
  };
//+------------------------------------------------------------------+
//| バランスデータを取得する                                            |
//+------------------------------------------------------------------+
int CFrameGenerator::GetBalanceData(void)
  {
   int    data_count      =0;
   double balance_current =0;
//--- すべての取引履歴をリクエストする
   ::HistorySelect(0,LONG_MAX);
   uint deals_total=::HistoryDealsTotal();
//--- 取引のデータを集める
   for(uint i=0; i<deals_total; i++)
     {
      //--- ティケットを受け取る
      ulong ticket=::HistoryDealGetTicket(i);
      if(ticket<1)
         continue;
      //--- 初期バランスまたはout-/inout取引の場合
      long entry=::HistoryDealGetInteger(ticket,DEAL_ENTRY);
      if(i==0 || entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT)
        {
         double swap      =::HistoryDealGetDouble(ticket,DEAL_SWAP);
         double profit    =::HistoryDealGetDouble(ticket,DEAL_PROFIT);
         double commision =::HistoryDealGetDouble(ticket,DEAL_COMMISSION);
         //--- バランスを計算する
         balance_current+=(profit+swap+commision);
         //--- 配列に保存する
         data_count++;
         ::ArrayResize(m_balance,data_count,100000);
         m_balance[data_count-1]=balance_current;
        }
     }
//--- データの量を取得する
   return(data_count);
  }
//+------------------------------------------------------------------+
//| 統計データを受け取る                                                |
//+------------------------------------------------------------------+
void CFrameGenerator::GetStatData(double &dst_array[],double on_tester_value)
  {
   ::ArrayResize(dst_array,::ArraySize(m_balance)+STAT_TOTAL);
   ::ArrayCopy(dst_array,m_balance,STAT_TOTAL,0);
//--- 最初の配列値(STAT_TOTAL)をテスト結果で入力する
   dst_array[0] =::TesterStatistics(STAT_PROFIT);               // 純利益
   dst_array[1] =::TesterStatistics(STAT_PROFIT_FACTOR);        // 利益率
   dst_array[2] =::TesterStatistics(STAT_RECOVERY_FACTOR);      // 回復率
   dst_array[3] =::TesterStatistics(STAT_TRADES);               // 取引数
   dst_array[4] =::TesterStatistics(STAT_DEALS);                // 約定数
   dst_array[5] =::TesterStatistics(STAT_EQUITY_DDREL_PERCENT); // 最大資金ドローダウン%
   dst_array[6] =on_tester_value;                               // カスタム最適化基準値
  }

CFrameGenerator::GetBalanceData()及びCFrameGenerator::GetStatData()メソッドはテスト完了イベントハンドラ(CFrameGenerator::OnTesterEvent())で呼ばれます。データが受信されます。それらをフレームでターミナルに送信します。 

//+------------------------------------------------------------------+
//| バランス値の配列を準備し、フレームに送る
//| 関数はEAのOnTester()ハンドラで呼ばれるべきである                      |
//+------------------------------------------------------------------+
void CFrameGenerator::OnTesterEvent(const double on_tester_value)
  {
//--- バランスデータを取得する
   int data_count=GetBalanceData();
//--- フレームにデータを送信するための配列
   double stat_data[];
   GetStatData(stat_data,on_tester_value);
//--- データを含むフレームを作成し、ターミナルに送信する
   if(!::FrameAdd(::MQLInfoString(MQL_PROGRAM_NAME),1,data_count,stat_data))
      ::Print(__FUNCTION__," > Frame add error: ",::GetLastError());
   else
      ::Print(__FUNCTION__," > Frame added, Ok");
  }

N次に、最適化中にフレーム到着イベントハンドラで使用されるメソッド(CFrameGenerator::OnTesterPassEvent())を考えてみましょう。名前、ID、パス番号、受け入れられた値、受け入れられたデータ配列など、フレームを扱うための変数が必要です。これらのデータはすべて、上記のFrameAdd()関数を使用してフレームに送信されます。

class CFrameGenerator
  {
private:
   //--- フレーム操作のための変数
   string            m_name;
   ulong             m_pass;
   long              m_id;
   double            m_value;
   double            m_data[];
  };

フレームで受け入れた配列の CFrameGenerator :: SaveStatData ()メソッドは、統計パラメータを取得し、それらを別の文字列配列に保存するために使用されます。データには、指標名とその値が含まれている必要があります。'=' 記号は区切りとして使用されます。

class CFrameGenerator
  {
private:
   //--- 統計パラメータの配列
   string            m_stat_data[];
   //---
private:
   //--- 統計データを保存する 
   void              SaveStatData(void);
  };
//+------------------------------------------------------------------+
//| 結果統計パラメータを配列に保存する                                   |
//+------------------------------------------------------------------+
void CFrameGenerator::SaveStatData(void)
  {
//--- フレーム統計パラメータを受け入れるための配列
   double stat[];
   ::ArrayCopy(stat,m_data,0,0,STAT_TOTAL);
   ::ArrayResize(m_stat_data,STAT_TOTAL);
//--- 配列にテスト結果を書き入れる
   m_stat_data[0] ="Net profit="+::StringFormat("%.2f",stat[0]);
   m_stat_data[1] ="Profit Factor="+::StringFormat("%.2f",stat[1]);
   m_stat_data[2] ="Factor Recovery="+::StringFormat("%.2f",stat[2]);
   m_stat_data[3] ="Trades="+::StringFormat("%G",stat[3]);
   m_stat_data[4] ="Deals="+::StringFormat("%G",stat[4]);
   m_stat_data[5] ="Equity DD="+::StringFormat("%.2f%%",stat[5]);
   m_stat_data[6] ="OnTester()="+::StringFormat("%G",stat[6]);
  }

統計データは別の配列に保存する必要があります。そのため、テーブルに書き込むためのアプリケーション(CProgram))クラスで検索できます。CFrameGenerator::CopyStatData() メソッドは、コピーのために配列を渡した後に受け取るために呼び出されます。

class CFrameGenerator
  {
public:
   //--- 渡された配列に統計的パラメータを取得する
   int               CopyStatData(string &dst_array[]) { return(::ArrayCopy(dst_array,m_stat_data)); }
  };

最適化中に結果グラフを更新するには、配列に正の結果と負の結果を追加する補助的なメソッドが必要です。結果は現在のフレームカウンタ値にX軸で加算されることにご注意ください。その結果、形成された空隙はゼロ値としてグラフに反映されません。

//--- 配列のスタンドバイサイズ
#define RESERVE_FRAMES 1000000
//+------------------------------------------------------------------+
//| 最適化結果を処理するためのクラス                                     |
//+------------------------------------------------------------------+
class CFrameGenerator
  {
private:
   //--- フレームカウンタ
   ulong             m_frames_counter;
   //--- 正と負の結果のデータ
   double            m_loss_x[];
   double            m_loss_y[];
   double            m_profit_x[];
   double            m_profit_y[];
   //---
private:
   //--- (1) 負と (2) 正の結果を配列に追加する
   void              AddLoss(const double loss);
   void              AddProfit(const double profit);
  };
//+------------------------------------------------------------------+
//| 負の結果を配列に追加する                                            |
//+------------------------------------------------------------------+
void CFrameGenerator::AddLoss(const double loss)
  {
   int size=::ArraySize(m_loss_y);
   ::ArrayResize(m_loss_y,size+1,RESERVE_FRAMES);
   ::ArrayResize(m_loss_x,size+1,RESERVE_FRAMES);
   m_loss_y[size] =loss;
   m_loss_x[size] =(double)m_frames_counter;
  }
//+------------------------------------------------------------------+
//| 正の結果を配列に追加する                                            |
//+------------------------------------------------------------------+
void CFrameGenerator::AddProfit(const double profit)
  {
   int size=::ArraySize(m_profit_y);
   ::ArrayResize(m_profit_y,size+1,RESERVE_FRAMES);
   ::ArrayResize(m_profit_x,size+1,RESERVE_FRAMES);
   m_profit_y[size] =profit;
   m_profit_x[size] =(double)m_frames_counter;
  }

ここでグラフを更新する主なメソッドはCFrameGenerator::UpdateResultsGraph()とCFrameGenerator::UpdateBalanceGraph()です。

class CFrameGenerator
  {
private:
   //--- 結果グラフを更新する
   void              UpdateResultsGraph(void);
   //--- バランスグラフを更新する
   void              UpdateBalanceGraph(void);
  };

CFrameGenerator::UpdateResultsGraph()メソッドでは、テスト結果(正/負利益)は配列に追加されます。そして、 これらのデータは適切なグラフに表示されます。グラフシリーズの名前は、現在の正/負の結果を表示します。 

//+------------------------------------------------------------------+
//| 結果グラフを更新する                                                |
//+------------------------------------------------------------------+
void CFrameGenerator::UpdateResultsGraph(void)
  {
//--- 負の結果
   if(m_data[0]<0)
      AddLoss(m_data[0]);
//--- 正の結果
   else
      AddProfit(m_data[0]);
//--- Update series on the optimization results graph
   CCurve *curve=m_graph_results.CurveGetByIndex(0);
   curve.Name("P: "+(string)ProfitsTotal());
   curve.Update(m_profit_x,m_profit_y);
//---
   curve=m_graph_results.CurveGetByIndex(1);
   curve.Name("L: "+(string)LossesTotal());
   curve.Update(m_loss_x,m_loss_y);
//--- x軸プロパティ
   CAxis *x_axis=m_graph_results.XAxis();
   x_axis.Min(0);
   x_axis.Max(m_frames_counter);
   x_axis.DefaultStep((int)(m_frames_counter/8.0));
//--- Update graph
   m_graph_results.CalculateMaxMinValues();
   m_graph_results.CurvePlotAll();
   m_graph_results.Update();
  }

At the very start of CFrameGenerator::UpdateBalanceGraph() method, the data related to the balance is retrieved from the array of data passed in the frame. Since several series can be displayed on the graph, we should make the series update consistent. To achieve this, we will use a separate series counter. To configure the number of simultaneously displayed balance series on the graph, we need CFrameGenerator::SetCurvesTotal() public method. As soon as the series counter in it reaches the established limit, the count starts from the beginning. The frame counter acts as the series names. The series color also depends on the result: green stands for a positive result, red — for a negative one.

Since the number of trades in each result is different, we should define the largest series and set the maximum by X axis to fit all necessary series on the graph.

class CFrameGenerator
  {
private:
   //--- Number of series
   uint              m_curves_total;
   //--- Index of the current series on the graph
   uint              m_last_serie_index;
   //--- To define the maximum series
   double            m_curve_max[];
   //---
public:
   //--- Set the number of series to display on the graph
   void              SetCurvesTotal(const uint total);
  };
//+------------------------------------------------------------------+
//| Set the number of series for display on the graph                |
//+------------------------------------------------------------------+
void CFrameGenerator::SetCurvesTotal(const uint total)
  {
   m_curves_total=total;
   ::ArrayResize(m_curve_max,total);
   ::ArrayInitialize(m_curve_max,0);
  }
//+------------------------------------------------------------------+
//| Update the balance graph                                         |
//+------------------------------------------------------------------+
void CFrameGenerator::UpdateBalanceGraph(void)
  {
//--- Array for accepting balance values of the current frame
   double serie[];
   ::ArrayCopy(serie,m_data,0,STAT_TOTAL,::ArraySize(m_data)-STAT_TOTAL);
//--- Send the array for displaying on the balance graph
   CCurve *curve=m_graph_balance.CurveGetByIndex(m_last_serie_index);
   curve.Name((string)m_frames_counter);
   curve.Color((m_data[0]>=0)? ::ColorToARGB(clrLimeGreen) : ::ColorToARGB(clrRed));
   curve.Update(serie);
//--- Get the series size
   int serie_size=::ArraySize(serie);
   m_curve_max[m_last_serie_index]=serie_size;
//--- Define the series with the maximum number of elements
   double x_max=0;
   for(uint i=0; i<m_curves_total; i++)
      x_max=::fmax(x_max,m_curve_max[i]);
//--- x軸プロパティ
   CAxis *x_axis=m_graph_balance.XAxis();
   x_axis.Min(0);
   x_axis.Max(x_max);
   x_axis.DefaultStep((int)(x_max/8.0));
//--- グラフを更新する
   m_graph_balance.CalculateMaxMinValues();
   m_graph_balance.CurvePlotAll();
   m_graph_balance.Update();
//--- Increase the series counter
   m_last_serie_index++;
//--- If the limit is reached, set the series counter to zero
   if(m_last_serie_index>=m_curves_total)
      m_last_serie_index=0;
  }

We considered the methods needed to organize the work in the frame handler. Now let's have a closer look at CFrameGenerator::OnTesterPassEvent() method handler itself. It returns true, while optimization is underway and FrameNext() function gets frame data. After completing the optimization, the method returns false.

In the EA list of parameters that can be obtained using FrameInputs() function, the parameters set for optimization go first followed by the ones that do not participate in optimization. 

If frame data is obtained, FrameInputs() function allows us to obtain EA parameters during the current optimization pass. Then we save the statistics, update the graphs and increase the frame counter. After that, CFrameGenerator::OnTesterPassEvent() method returns true till the next call.

class CFrameGenerator
  {
private:
   //--- EA parameters
   string            m_param_data[];
   uint              m_par_count;
  };
//+------------------------------------------------------------------+
//| Receive frame with data during optimization and display the graph|
//+------------------------------------------------------------------+
bool CFrameGenerator::OnTesterPassEvent(void)
  {
//--- After getting a new frame, try to retrieve data from it
   if(::FrameNext(m_pass,m_name,m_id,m_value,m_data))
     {
      //--- Get input parameters of the EA the frame is formed for
      ::FrameInputs(m_pass,m_param_data,m_par_count);
      //--- Save result statistical parameters to the array
      SaveStatData();
      //--- 結果とバランスグラフを更新する
      UpdateResultsGraph();
      UpdateBalanceGraph();
      //--- Increase the processed frames counter
      m_frames_counter++;
      return(true);
     }
//---
   return(false);
  }

After optimization is complete, TesterDeinit event is generated and CFrameGenerator::OnTesterDeinitEvent() method is called in the frame processing mode. At the moment, not all frames can be processed during the optimization, therefore the results visualization graph will be incomplete. To see the full picture, you need to cycle through all the frames using CFrameGenerator::FinalRecalculateFrames() method and reload the graph right after the optimization.

To do this, relocate the pointer to the start of the frame list, then set result arrays and frame counter to zero. Then, cycle through the full list of frames, fill in the arrays by positive and negative results and eventually update the graph.

class CFrameGenerator
  {
private:
   //--- Free the arrays
   void              ArraysFree(void);
   //--- Final data re-calculation from all frames after optimization
   void              FinalRecalculateFrames(void);
  };
//+------------------------------------------------------------------+
//| Free the arrays                                                  |
//+------------------------------------------------------------------+
void CFrameGenerator::ArraysFree(void)
  {
   ::ArrayFree(m_loss_y);
   ::ArrayFree(m_loss_x);
   ::ArrayFree(m_profit_y);
   ::ArrayFree(m_profit_x);
  }
//+------------------------------------------------------------------+
//| Final data re-calculation from all frames after optimization     |
//+------------------------------------------------------------------+
void CFrameGenerator::FinalRecalculateFrames(void)
  {
//--- Set the frame pointer to the start
   ::FrameFirst();
//--- Reset the counter and the arrays
   ArraysFree();
   m_frames_counter=0;
//--- フレームの反復処理を始める
   while(::FrameNext(m_pass,m_name,m_id,m_value,m_data))
     {
      //--- 負の結果
      if(m_data[0]<0)
         AddLoss(m_data[0]);
      //--- 正の結果
      else
         AddProfit(m_data[0]);
      //--- 処理済みのフレームのカウンタを増加する
      m_frames_counter++;
     }
//--- グラフのシリーズを更新する
   CCurve *curve=m_graph_results.CurveGetByIndex(0);
   curve.Name("P: "+(string)ProfitsTotal());
   curve.Update(m_profit_x,m_profit_y);
//---
   curve=m_graph_results.CurveGetByIndex(1);
   curve.Name("L: "+(string)LossesTotal());
   curve.Update(m_loss_x,m_loss_y);
//--- x軸プロパティ
   CAxis *x_axis=m_graph_results.XAxis();
   x_axis.Min(0);
   x_axis.Max(m_frames_counter);
   x_axis.DefaultStep((int)(m_frames_counter/8.0));
//--- グラフを更新する
   m_graph_results.CalculateMaxMinValues();
   m_graph_results.CurvePlotAll();
   m_graph_results.Update();
  }

子の場合CFrameGenerator::OnTesterDeinitEvent()メソッドコードは下記のようになります。ここでは、フレームの総数を覚えて、カウンタをゼロに設定します。

//+------------------------------------------------------------------+
//| OnTesterDeinit()ハンドラで呼び出されるべき                          |
//+------------------------------------------------------------------+
void CFrameGenerator::OnTesterDeinitEvent(void)
  {
//--- 最適化後の全フレームからのデータの最終的な再計算
   FinalRecalculateFrames();
//--- フレームの総数を覚えておき、カウンターをゼロに設定する
   m_frames_total     =m_frames_counter;
   m_frames_counter   =0;
   m_last_serie_index =0;
  }

次にアプリケーションクラスのCFrameGeneratorクラスメソッドの使用についてみてみましょう。 


アプリケーションクラス内の最適化データの処理

グラフィカルインターフェイスはCProgram::OnTesterInitEvent()テスト初期化メソッドで作成されます。その後、グラフィカルインターフェイスはアクセス不能にする必要があります。これを行うには、他のCProgramクラスメソッドで使用される別のCProgram::IsAvailableGUI()及びCProgram::IsLockedGUI()メソッドが必要です。

フレームジェネレータを初期化します。最適化結果を可視化するために使用するグラフへのポインタを渡します。

class CProgram : public CWndEvents
  {
private:
   //--- インターフェイスの可用性
   void              IsAvailableGUI(const bool state);
   void              IsLockedGUI(const bool state);
  }
//+------------------------------------------------------------------+
//| 最適化開始イベント                                                 |
//+------------------------------------------------------------------+
void CProgram::OnTesterInitEvent(void)
  {
//--- グラフィカルインターフェイスを作成する
   if(!CreateFrameModeGUI())
     {
      ::Print(__FUNCTION__," > Could not create the GUI!");
      return;
     }
//--- インターフェイスをアクセス不能にする
   IsLockedGUI(false);
//--- フレームジェネレータを初期化する
   m_frame_gen.OnTesterInitEvent(m_graph1.GetGraphicPointer(),m_graph2.GetGraphicPointer());
  }
//+------------------------------------------------------------------+
//| インターフェイスの可用性                                            |
//+------------------------------------------------------------------+
void CProgram::IsAvailableGUI(const bool state)
  {
   m_window1.IsAvailable(state);
   m_sleep_ms.IsAvailable(state);
   m_curves_total.IsAvailable(state);
   m_reply_frames.IsAvailable(state);
  }
//+------------------------------------------------------------------+
//| インターフェイスをブロックする                                       |
//+------------------------------------------------------------------+
void CProgram::IsLockedGUI(const bool state)
  {
   m_window1.IsAvailable(state);
   m_sleep_ms.IsLocked(!state);
   m_curves_total.IsLocked(!state);
   m_reply_frames.IsLocked(!state);
  }

CProgram::UpdateStatTable()及びCProgram::UpdateParamTable()メソッドを使用して、テーブルのデータをアプリケーションクラス内で更新することは既に述べました。両方のテーブルのコードは同じですので、それらのうちの1つのみの例を示します。同じ行のパラメータ名と値は、セパレータとして '='を使用して表示されます。したがって、ループで反復処理し、別々の配列に分割して2つの要素に分割します。その後、値をテーブルセルに入力します。

class CProgram : public CWndEvents
  {
private:
   //--- 統計表を更新する
   void              UpdateStatTable(void);
   //--- パラメータ表を更新する
   void              UpdateParamTable(void);
  }
//+------------------------------------------------------------------+
//| 統計表を更新する                                                   |
//+------------------------------------------------------------------+
void CProgram::UpdateStatTable(void)
  {
//--- 統計表のデータ配列を取得する
   string stat_data[];
   int total=m_frame_gen.CopyStatData(stat_data);
   for(int i=0; i<total; i++)
     {
      //--- 2つの線に分けて表に入力する
      string array[];
      if(::StringSplit(stat_data[i],'=',array)==2)
        {
         if(m_frame_gen.CurrentFrame()>1)
            m_table_stat.SetValue(1,i,array[1],0,true);
         else
           {
            m_table_stat.SetValue(0,i,array[0],0,true);
            m_table_stat.SetValue(1,i,array[1],0,true);
           }
        }
     }
//--- 表を更新する
   m_table_stat.Update();
  }

テーブル内のデータを更新する両方のメソッドCProgram::OnTesterPassEvent()メソッドで、同名のCFrameGenerator::OnTesterPassEvent()メソッドからの肯定的答えによって呼ばれます。

//+------------------------------------------------------------------+
//| 最適化パス処理イベント                                              |
//+------------------------------------------------------------------+
void CProgram::OnTesterPassEvent(void)
  {
//--- テスト結果を処理してグラフを表示する
   if(m_frame_gen.OnTesterPassEvent())
     {
      UpdateStatTable();
      UpdateParamTable();
     }
  }

最適化が完了すると、CProgram::CalculateProfitsAndLosses() メソッドは正と負の結果のパーセンテージ比を計算し、ステータスバーにデータを表示します。

class CProgram : public CWndEvents
  {
private:
   //--- 正と負の結果のパーセンテージ比を計算する
   void              CalculateProfitsAndLosses(void);
  }
//+------------------------------------------------------------------+
//| 正と負の結果の比を計算する                                          |
//+------------------------------------------------------------------+
void CProgram::CalculateProfitsAndLosses(void)
  {
//--- フレームがない場合は終了する
   if(m_frame_gen.FramesTotal()<1)
      return;
//--- 負と正の結果の数
   int losses  =m_frame_gen.LossesTotal();
   int profits =m_frame_gen.ProfitsTotal();
//--- パーセント比
   string pl =::DoubleToString(((double)losses/(double)m_frame_gen.FramesTotal())*100,2);
   string pp =::DoubleToString(((double)profits/(double)m_frame_gen.FramesTotal())*100,2);;
//--- ステータスバーで表示する
   m_status_bar.SetValue(1,"Profits: "+(string)profits+" ("+pp+"%)"+" / Losses: "+(string)losses+" ("+pl+"%)");
   m_status_bar.GetItemPointer(1).Update(true);
  }

TesterDeinitイベントを処理するコードは下に見られます。グラフィックコアの初期化とはマウスカーソルの動きを追跡し、タイマーをオンにすることを意味します。残念ながら、現在のMetaTrader 5 バージョンでは、最適化が完了してもタイマーはオンになりません。将来的にこれが可能になることを祈りましょう。

//+------------------------------------------------------------------+
//| 最適化完了イベント                                                 |
//+------------------------------------------------------------------+
void CProgram::OnTesterDeinitEvent(void)
  {
//--- 最適化の完了
   m_frame_gen.OnTesterDeinitEvent();
//--- インターフェイスをアクセス可能にする
   IsLockedGUI(true);
//--- 正と負の結果のパーセンテージ比を計算する
   CalculateProfitsAndLosses();
//--- GUIコアを初期化する
   CWndEvents::InitializeCore();
  }

これで、最適化が完了した後にもフレームデータでの作業ができます。EAはターミナルチャートに配置され、フレームにアクセスして結果を分析することができます。グラフィカルインターフェイスによってこれはすべて直感的になります。CProgram::OnEvent()イベントハンドラメソッドでは、下記を追跡します。

  • グラフ上に表示されたバランスシリーズの数を設定するための入力フィールドの変更
  • 最適化結果の表示を開始します。

CProgram::UpdateBalanceGraph()メソッドは、系列の数を変更した後にグラフを更新するために使用されます。ここでは、フレームジェネレータで作業するためのシリーズの数を設定し、グラフ上にこの数を予約します。

class CProgram : public CWndEvents
  {
private:
   //--- グラフを更新する
   void              UpdateBalanceGraph(void);
  };
//+------------------------------------------------------------------+
//| グラフを更新する                                                   |
//+------------------------------------------------------------------+
void CProgram::UpdateBalanceGraph(void)
  {
//--- 作業のシリーズの数を設定する
   int curves_total=(int)m_curves_total.GetValue();
   m_frame_gen.SetCurvesTotal(curves_total);
//--- シリーズを削除する
   CGraphic *graph=m_graph1.GetGraphicPointer();
   int total=graph.CurvesTotal();
   for(int i=total-1; i>=0; i--)
      graph.CurveRemoveByIndex(i);
//--- シリーズを追加する
   double data[];
   for(int i=0; i<curves_total; i++)
      graph.CurveAdd(data,CURVE_LINES,"");
//--- グラフを更新する
   graph.CurvePlotAll();
   graph.Update();
  }

イベントハンドラでは、CProgram::UpdateBalanceGraph()メソッドは入力フィールドのボタンを取るぐするtoggling the buttons in the input field (ON_CLICK_BUTTON) 時やキーボードから値が入力された (ON_END_EDIT)時に呼ばれます。

//+------------------------------------------------------------------+
//| イベントハンドラ                                                   |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- ボタン押下イベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- グラフでのシリーズの数を変える
      if(lparam==m_curves_total.Id())
        {
         UpdateBalanceGraph();
         return;
        }
      return;
     }
//--- 入力フィールドに値を入力するイベント
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      //--- グラフでのシリーズの数を変える
      if(lparam==m_curves_total.Id())
        {
         UpdateBalanceGraph();
         return;
        }
      return;
     }
  }

CFrameGeneratorクラスで最適化後の結果を表示するには、CFrameGenerator::ReplayFrames() publicメソッドが実装されています。最初の段階では、フレームカウンタで次のように定義します。プロセスが開始されたばかりの場合、配列はゼロに設定され、フレームポインタはリストの先頭に移動されます。その後、フレームが循環され、前述のCFrameGenerator::OnTesterPassEvent()メソッドと同じアクションが実行されます。フレームが受信された場合、このメソッドはtrueを返します。完了すると、フレームカウンタとシリーズカウンタはゼロに設定され、このメソッドは falseを返します。 

class CFrameGenerator
  {
public:
   //--- フレームを反復処理する
   bool              ReplayFrames(void);
  };
//+------------------------------------------------------------------+
//| 最適化の完了後でフレームを再生する                                   |
//+------------------------------------------------------------------+
bool CFrameGenerator::ReplayFrames(void)
  {
//--- フレームポインタを先頭に設定する
   if(m_frames_counter<1)
     {
      ArraysFree();
      ::FrameFirst();
     }
//--- フレームの反復処理を始める
   if(::FrameNext(m_pass,m_name,m_id,m_value,m_data))
     {
      //--- フレームが形成したEA入力を取得する
      ::FrameInputs(m_pass,m_param_data,m_par_count);
      //--- 統計結果パラメータを配列に保存する
      SaveStatData();
      //--- 結果とバランスグラフを更新する
      UpdateResultsGraph();
      UpdateBalanceGraph();
      //--- 処理済みのフレームのカウンタを増加する
      m_frames_counter++;
      return(true);
     }
//--- 反復処理を完了する
   m_frames_counter   =0;
   m_last_serie_index =0;
   return(false);
  }

CFrameGenerator::ReplayFrames() method is called in CProgram class from ViewOptimizationResults() method. フレームを起動する前には、グラフィカルインターフェイスは利用できなくなります。スクロール速度はSleep入力フィールドで一時停止を指定することで調整できます。一方、ステータスバーには、処理が終了するまでの時間を示す進行状況バーが表示されます。

class CFrameGenerator
  {
private:
   //--- 最適化結果を表示する
   void              ViewOptimizationResults(void);
  };
//+------------------------------------------------------------------+
//| 最適化結果を表示する                                                |
//+------------------------------------------------------------------+
void CProgram::ViewOptimizationResults(void)
  {
//--- インターフェイスをアクセス不能にする
   IsAvailableGUI(false);
//--- 一時停止
   int pause=(int)m_sleep_ms.GetValue();
//--- フレームを再生する
   while(m_frame_gen.ReplayFrames() && !::IsStopped())
     {
      //--- 表を更新する
      UpdateStatTable();
      UpdateParamTable();
      //--- プログレスバーを更新する
      m_progress_bar.Show();
      m_progress_bar.LabelText("Replay frames: "+string(m_frame_gen.CurrentFrame())+"/"+string(m_frame_gen.FramesTotal()));
      m_progress_bar.Update((int)m_frame_gen.CurrentFrame(),(int)m_frame_gen.FramesTotal());
      //--- 一時停止
      ::Sleep(pause);
     }
//--- 正と負の結果のパーセンテージ比を計算する
   CalculateProfitsAndLosses();
//--- プログレスバーを非表示にする
   m_progress_bar.Hide();
//--- インターフェイスをアクセス可能にする
   IsAvailableGUI(true);
   m_reply_frames.MouseFocus(false);
   m_reply_frames.Update(true);
  }

CProgram::ViewOptimizationResults()メソッドは、アプリケーションのグラフィカルインターフェイスでフレーム再生ボタンが押されると呼ばれます。ON_CLICK_BUTTONイベントが生成されます。

//+------------------------------------------------------------------+
//| イベントハンドラ                                                   |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- ボタン押下イベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- 最適化結果を表示する 
      if(lparam==m_reply_frames.Id())
        {
         ViewOptimizationResults();
         return;
        }
      //--- 
      ...
      return;
     }
  }

ここで、結果を見て、フレームで作業するときに最適化中にグラフ上に実際に表示されるものを定義します。


結果の表示

テストの場合、標準パッケージの取引アルゴリズム(移動平均)を使用します。ここではクラスを追加したり訂正したりすることなく、「現状のまま」実装します。開発されたアプリケーションのすべてのファイルは、同じフォルダーに配置されます。戦略ファイルはProgram.mqhファイルにインクルードされます。

FormatString.mqh は、線の書式設定のための関数としての追加としてここに含まれています。これらはまだどのクラスにも属していないので、矢印を黒色にします。結果的に、アプリケーション構造は次のようになります。

図4 取引戦略クラスと追加機能を含むファイルのインクルード 

図4 取引戦略クラスと追加機能を含むファイルのインクルード

パラメータを最適化して、ターミナルチャートでどのように見えるかを見てみましょう。テスター設定: EURUSD H1、時間範囲2017.01.01 – 2018.01.01.

図5 標準パッケージの移動平均EAの結果

図5 標準パッケージの移動平均EAの結果

ここで見る通り、それはかなり有益です。この取引アルゴリズムのほとんどすべての結果は負(95.23%)です。時間範囲を広げると、それはさらに悪化します。しかし、取引システムを開発する際には、ほとんどの結果が正であることを確認する必要があります。それ以外の場合は、アルゴリズムは損失を引き起こすので、使用されてはなりません。より多くのデータのパラメータを最適化し、できるだけ多くの取引が存在することを確認する必要があります。  

標準パッケージから別の取引アルゴリズムを試してみましょう。 MACD Sample.mq5です。これはすでにクラスとして実装されています。わずかな改良を加えれば、以前のようにアプリケーションに接続するだけで済みます。テストは同じ銘柄と時間枠で行うべきです。テストではより多くの取引があるように時間範囲を広げる必要があります (2010.01.01 – 2018.01.01)。以下は、取引EAの最適化結果です。

 図6 標準パッケージの MACDサンプルの結果

図6  MACDサンプル最適化の結果

ここでは、肯定的結果が90.89%という非常に異なる結果が得られます。

使用されるデータの量によって、パラメータの最適化には非常に時間がかかることがあります。プロセスの最初から最後までPCの前に座っている必要はありません。最適化後にフレーム再生を押すと、加速モードで結果の繰り返し表示を開始できます。表示が25シリーズに制限されたフレームを再生しましょう。それはこのように見えます。

図7 最適化の後のMACDサンプルEA結果

図7 最適化の後のMACDサンプルEA結果


終わりに

本稿では、最適化フレームを受け取って分析するための最新版のプログラムを紹介しました。データはEasyAndFastライブラリに基づいて開発されたグラフィカルインタフェース環境で可視化されます。 

この解決策の欠点は、フレーム処理モードで最適化を完了すると、タイマーを起動できないことです。これは、同じグラフィカルインタフェースで作業する場合にいくつかの制限を課します。2番目の問題は、チャートからEAを削除するときにOnDeinit()関数での初期化解除がトリガされないことです。これにより、正しいイベント処理が妨げられます。おそらく、これらの問題は将来のMetaTrader 5 ビルドのどれかで解決されるでしょう。

MetaQuotes Software Corp.によりロシア語から翻訳された
元の記事: https://www.mql5.com/ru/articles/4395

添付されたファイル |
MQL5.zip (33.34 KB)
インジケーターの開発を依頼するための要件定義を作成する方法 インジケーターの開発を依頼するための要件定義を作成する方法

最もよくあるトレードシステムの開発の第一歩は、相場行動パターンを識別できるテクニカルインジケーターの作成です。 専門的に開発されたインジケーターを、フリーランスのサービスからオーダーすることができます。 この記事からは、適切な要件定義を作成する方法を学習します。より速く、希望のインジケーターを取得するのに役立ちます.

ビンスによる資金管理 MQL5 ウィザードのモジュールとしての実装 ビンスによる資金管理 MQL5 ウィザードのモジュールとしての実装

この記事は、ラルフ·ビンスによる "The Mathematics of Money Management" に基づいています。 トレードロットの最適なサイズを見つけるために使用される経験的およびパラメトリックメソッドの説明をします。 また、それらのメソッドに基づいて MQL5 ウィザードのトレーディングモジュールの実装を行います。

トレーダーのハック: 定義と ForEach のブレンド (#define) トレーダーのハック: 定義と ForEach のブレンド (#define)

この記事は、現在MQL4でコーディングしていて、MQL5に切り替えたいとは思っていない人のためのものです。 今回はMQL4のスタイルでコードを書く方法を模索していきます。 #define プリプロセッサのマクロ置換を見ていきます。

ディープニューラルネットワーク(その5)DNNハイパーパラメータのベイズ最適化 ディープニューラルネットワーク(その5)DNNハイパーパラメータのベイズ最適化

本稿では、様々な訓練の変形によって得られたディープニューラルネットワークのハイパーパラメータにベイズ最適化を適用する可能性について検討します。様々な訓練の変形における最適なハイパーパラメータを有するDNNの分類の質が比較されます。DNN最適ハイパーパラメータの有効性の深さは、フォワードテストで確認されています。分類の質を向上させるための方向性が特定されています。