選択した基準による最適化結果の可視化
Anatoli Kazharski | 18 6月, 2018
目次
概論
以前の記事で開発を開始した最適化の結果を扱うアプリケーションの開発をさらに進めていきます。グラフィカルインターフェイスを介して別の基準を指定し、パラメータを最適化した後に最良の結果の表を作成する例を見ていきましょう。以前の記事で、フレームの配列にデータのカテゴリを保存し、その後そこから取り出すことで、多くのデータカテゴリを扱う方法をご紹介しました。そこでは、統計的指標、シンボル残高の配列、またデポジットのドローダウンを扱いました。
最適化後、表内の対応する行を選択するだけで、このデータを別々のチャートで見ることができます。しかし、完璧には際限がありません。様々な基準で選択した結果を見る為にも、それぞれの残高を別々のチャートで一度に表示したいものです。表内の行を選択すると、この総合チャートに選択した残高曲線が形成されます。このようにして、私達は最適化の結果をよりよく評価することができるようになります。
また、皆さんからの要望に応じて、キーボードから表内の行の選択を行う方法を説明します。これを行うには、私達のライブラリのCTableクラスに手を加える必要がありました。
グラフィカルインタフェースの開発
グラフィックインターフェースの前のバージョンには、 Frames、Results、Balanceの3つのタブがありました。
Framesタブには、最適化の過程とその後のすべての結果を扱う為の要素が置かれています。
そして、2番目と(Results)と3番目(Balance)のタブをここで一緒にします。こうすることで、表内の行を選択すると、すぐに結果がチャートに表示されるため、別のタブに切り替える必要がなくなります。
Resultsタブに、BalancesとFavoritesを含むもう一つのグループを加え、Balancesタブには、複数のシンボルの残高、デポジットのドローダウン、テストの対象になっているシンボルのリストを一覧できるチャートが入ります。Favoritesタブには、表内のすべての最良の結果のチャートを表示します。さらに、CComboBox(ドロップダウンリスト)型の要素を追加します。これは、フレームの総リストから最良の結果を選択する基準を選択するのに役立ちます。
GUI要素のフル階層は次のようになります。
- コントロール要素のフォーム
- 追加の要約情報を表示するためのステータスバー
- タブの最初のグループ:
- Frames:
- 最適化後の再スクロール時に表示される残高結果の数を入力するフィールド
- 結果をスクロールする時の遅延時間(ミリ秒単位)
- 結果の再スクロールの開始ボタン
- 指定した残高結果数を表示するチャート
- すべての結果を表示するチャート
- Results:
- 最良の結果の表
- 表を形成する基準を選択する為のドロップダウンリストをもつコンボボックス
- 第2のタブのグループ:
- Balance:
- 選択した結果のマルチシンボルの残高を表示するチャート
- 選択した結果のドローダウンを表示するチャート
- テスト対象のシンボルの残高一覧
- Favorites:
- 表内の最良の結果を表示するチャート
- フレームを再生するためのインジケータ
これらの要素を作成するためのメソッドコードは別のファイルに格納されており、MQLプログラムクラスを持つファイルに接続します。
//+------------------------------------------------------------------+ // |アプリケーションを作成するクラス | //+------------------------------------------------------------------+ class CProgram : public CWndEvents { private: // --- ウィンドウ CWindow m_window1; // --- ステータスバー CStatusBar m_status_bar; // --- タブ CTabs m_tabs1; CTabs m_tabs2; // --- 入力フィールド CTextEdit m_curves_total; CTextEdit m_sleep_ms; // --- ボタン CButton m_reply_frames; //--- コンボボックス CComboBox m_criterion; // --- グラフ CGraph m_graph1; CGraph m_graph2; CGraph m_graph3; CGraph m_graph4; CGraph m_graph5; // --- 表 CTable m_table_main; CTable m_table_symbols; // --- 実行インジケータ CProgressBar m_progress_bar; //--- public: // --- グラフィカルインタフェースを作成する bool CreateGUI(void); //--- private: // --- フォーム bool CreateWindow(const string text); // --- ステータスバー bool CreateStatusBar(const int x_gap,const int y_gap); // --- タブ bool CreateTabs1(const int x_gap,const int y_gap); bool CreateTabs2(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 CreateCriterion(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 CreateGraph3(const int x_gap,const int y_gap); bool CreateGraph4(const int x_gap,const int y_gap); bool CreateGraph5(const int x_gap,const int y_gap); // --- ボタン bool CreateUpdateGraph(const int x_gap,const int y_gap,const string text); // --- 表 bool CreateMainTable(const int x_gap,const int y_gap); bool CreateSymbolsTable(const int x_gap,const int y_gap); // --- 実行インジケータ bool CreateProgressBar(const int x_gap,const int y_gap,const string text); }; //+------------------------------------------------------------------+ // |コントロール要素を作成するメソッド | //+------------------------------------------------------------------+ #include "CreateGUI.mqh" //+------------------------------------------------------------------+
最適化結果の選択
最良の最適化結果を1つのグラフですべて表示するには、パラメータで設定した結果の数を見つけるまで、trueを返すメソッドが必要になります。見つけるとメソッドはすぐにfalseを返します。そのメソッドとは以下に紹介するCFrameGenerator :: UpdateBestResultsGraph()です。デフォルトでは最適化結果上位100がレンダリングされます。
このメソッドはダブルループを使用します。最初のサイクルは、データ表配列の構造内の範囲外のものを除外するために、表示された最良の結果の数と表の行数によって制限されます。このサイクルの各反復において、フレームポインタはフレームリストの先頭に移動されます。
第2のサイクルでは、フレームを移動させながら配列の構造内に以前に保存されたパス番号を探します。配列構造CFrameGenerator :: UpdateBestResultsGraph()は、メソッドを呼び出す前に、指定された条件でソートされていなければなりません。さらに、パス番号が見つかると、このパス上のEAのパラメータとその数を取得します。その後、データ配列からの現在のパスの結果の残高を取得します(m_data[])。総残高のデータは、統計指標の後のフレーム配列に含まれており、配列のサイズはフレームのdoubleパラメータの値と等しくなることに注意をする必要があります。この配列は、一連のデータのように、最良結果の残高グラフに表示されます。このテストの最終結果が最初のデポジットよりも高い場合は、その行は緑色になり、それ以外の場合には赤色になります。サイクルの終了後に、X軸の境界を設定する要素の最大数を持つ系列を定義することができるように、系列のサイズは別々の配列に格納します。ここで次の時にこのパスを省いてサイクルを継続できるようにするために、フレームカウンタを1つ増やすひつようがあります。
サイクルが完全に完了すると、
- フレームカウンタがリセットされ、
- 最大要素数を有する一連のデータが決定され、
- 最良の結果のグラフが設定され、更新されます。
その後、CFrameGenerator :: UpdateBestResultsGraph()メソッドがfalseを返しますが、これは結果の選択が完了したことを意味しています。
//+------------------------------------------------------------------+ // | 最適化結果を扱うクラス | //+------------------------------------------------------------------+ class CFrameGenerator { private: // ---最良の結果の数 int m_best_results_total; //--- public: // ---最良の結果のグラフを更新する bool UpdateBestResultsGraph(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CFrameGenerator::CFrameGenerator(void) : m_best_results_total(100) { } //+------------------------------------------------------------------+ // |最良の結果のグラフを更新する | //+------------------------------------------------------------------+ bool CFrameGenerator::UpdateBestResultsGraph(void) { for(int i=(int)m_frames_counter; i<m_best_results_total && i<m_rows_total; i++) { // --- フレームポインタを先頭に移動する ::FrameFirst(); //--- データの抽出 while(::FrameNext(m_pass,m_name,m_id,m_value,m_data)) { //--- パス番号が一致しない場合、次へ進む if(m_pass!=(ulong)m_columns[0].m_rows[i]) continue; // ---パラメータとその数を取得する GetParametersTotal(); // ---現在の結果の残高を取得する double serie[]; ::ArrayCopy(serie,m_data,0,STAT_TOTAL,(int)m_value); // ---残高グラフに出力する為の配列を送信する CCurve *curve=m_graph_best.CurveGetByIndex(i); curve.Name((string)m_pass); curve.Color((m_data[m_profit_index]>=0)? ::ColorToARGB(clrLimeGreen) : ::ColorToARGB(clrRed)); curve.Update(serie); // ---シリーズのサイズを取得する m_curve_max[i]=::ArraySize(serie); // ---フレームカウンタを増やす m_frames_counter++; return(true); } } // ---フレームカウンタをリセットする m_frames_counter=0; // ---要素の最大数を持つ系列を定義する double x_max=m_curve_max[::ArrayMaximum(m_curve_max)]; // --- 水平軸のプロパティ CAxis *x_axis=m_graph_best.XAxis(); x_axis.Min(0); x_axis.Max(x_max); x_axis.DefaultStep((int)(x_max/8.0)); // --- グラフを更新する m_graph_best.CalculateMaxMinValues(); m_graph_best.CurvePlotAll(); m_graph_best.Update(); return(false); }
結果を見つけるには、一般リストのすべてのフレームを調べる必要があるため、これには時間がかかります。したがって、検索ステージを追跡するために、『実行インジケータ』(CProgressBar)要素を使用します。このために、アプリケーションクラス(CProgram)でCProgram :: GetBestOptimizationResults()メソッドが実装されました。ここではwhileサイクルで、条件としてCFrameGenerator :: UpdateBestResultsGraphCFrameGenerator :: UpdateBestResultsGraph()メソッドが呼び出されます。サイクルを開始する前に、実行インジケータを表示します(進行状況バー)。CFrameGenerator::UpdateBestResultsGraph()メソッドでフレームカウンタを使用する為、現在の値を取得することができます。サイクルが完了したら、実行インジケータを非表示にする必要があります。
class CProgram : public CWndEvents { private: // ---最良の最適化結果を取得する void GetBestOptimizationResults(void); }; //+------------------------------------------------------------------+ // |最良の最適化結果を取得する | //+------------------------------------------------------------------+ void CProgram::GetBestOptimizationResults(void) { //--- 進行状況バーを表示する m_progress_bar.Show(); // ---最良の結果を取得するプロセスを視覚化する int best_results_total=m_frame_gen.BestResultsTotal(); while(m_frame_gen.UpdateBestResultsGraph() && !::IsStopped()) { // ---進行状況バーを更新する m_progress_bar.LabelText("Selection of results: "+string(m_frame_gen.CurrentFrame())+"/"+string(best_results_total)); m_progress_bar.Update((int)m_frame_gen.CurrentFrame(),best_results_total); } // ---進行状況バーを隠す m_progress_bar.Hide(); }
CProgram :: GetBestOptimizationResults()メソッドは、最適化完了メソッドで呼び出す必要があります。したがって、ユーザは、プログラムが実行中であり、フリーズしていないことを確認する必要があります。残りのメソッドは以前の記事で紹介しているので、ここではこれ以上ふれません。
//+------------------------------------------------------------------+ // |最適化プロセスの終了のイベント | //+------------------------------------------------------------------+ void CProgram::OnTesterDeinitEvent(void) { // ---最適化の完了 m_frame_gen.OnTesterDeinitEvent(); // ---最良の結果を取得するプロセスを視覚化する GetBestOptimizationResults(); // ---インターフェイスを利用可能にする IsLockedGUI(true); // ---正の結果と負の結果の比を計算する CalculateProfitsAndLosses(); // ---最適化結果表へデータを取得する GetFrameDataToTable(); // --- GUIカーネルを初期化する CWndEvents::InitializeCore(); }
最適化が完了した直後または強制的に停止した後、ステータスバー領域に進行状況バーが表示されます。これは選択プロセスが進行中であることをユーザーに示しています。
図1.結果の選択プロセスの可視化。
選択したすべての結果の残高を視覚的に見るには、Resultsタブに移動し、その次にFavoritesタブへ移動します。デフォルトでは、表はProfitの基準に従い、上位100の結果を表に追加します。いつでもドロップダウンリストのCriterionから、上位100の結果を選ぶための基準を別のものに設定することができます。後でこの話題に戻りますが、今はこのプロセスを編成する方法を考えてみましょう。
図2. 最良の最適化結果のグラフ。
プログラムによる表内の行の選択
これまでは、左クリックでのみ表内の行を選択することができました。しかし、プログラムで行う必要がある場合もあります。たとえば、Up、Down、HomeやEndキーなどで行を選択する場合などです。プログラムで行を選択するために、CTableクラスにCTable :: SelectRow()メソッドが追加されました。そのコードはCTable :: RedrawRow()プライベートメソッドに似ています。これは、マウスクリックイベントによって行を再描画したり、行の強調表示モードがオンのときにカーソルを表の上に移動したりするために使用されます。
コードの多くは両方のメソッドで再利用できます。したがって、私は別のCTable :: DrawRow()メソッドにこれを移しました。ここには以下のものを渡す必要があります。
- 再描画のシーケンスを決定するインデックスの配列、
- 選択した行の現在のインデックス、
- 前に選択した行のインデックス、
- メソッドを使用するモードは、プログラム(false)またはカスタム(true)になります。
//+------------------------------------------------------------------+ // |指定されたモードに従って指定されたテーブル行を描画する | //+------------------------------------------------------------------+ void CTable::DrawRow(int &indexes[],const int item_index,const int prev_item_index,const bool is_user=true) { int x1=0,x2=m_table_x_size-2; int y1[2]={0},y2[2]={0}; // ---描画する行と列の数 uint rows_total =0; uint columns_total =m_columns_total-1; // ---これが文字列を割り当てるためのプログラムメソッドの場合 if(!is_user) rows_total=(prev_item_index!=WRONG_VALUE && item_index!=prev_item_index)? 2 : 1; else rows_total=(item_index!=WRONG_VALUE && prev_item_index!=WRONG_VALUE && item_index!=prev_item_index)? 2 : 1; // ---線の背景を描画する for(uint r=0; r<rows_total; r++) { // ---上下の境界線の座標を計算する y1[r] =m_rows[indexes[r]].m_y+1; y2[r] =m_rows[indexes[r]].m_y2-1; // ---バックライトモードに関連した行にフォーカスを定義する bool is_item_focus=false; if(!m_lights_hover) is_item_focus=(indexes[r]==item_index && item_index!=WRONG_VALUE); else is_item_focus=(item_index==WRONG_VALUE)?(indexes[r]==prev_item_index) :(indexes[r]==item_index); // ---背景を描画する m_table.FillRectangle(x1,y1[r],x2,y2[r],RowColorCurrent(indexes[r],(is_user)? is_item_focus : false)); } // ---境界を描画する for(uint r=0; r<rows_total; r++) { for(uint c=0; c<columns_total; c++) m_table.Line(m_columns[c].m_x2,y1[r],m_columns[c].m_x2,y2[r],::ColorToARGB(m_grid_color)); } // ---画像を描く for(uint r=0; r<rows_total; r++) { for(uint c=0; c<m_columns_total; c++) { // ---もし(1)画像がこのセルにあり、(2)この列内にテキストが左に揃えられている場合は、画像を描画する if(ImagesTotal(c,indexes[r])>0 && m_columns[c].m_text_align==ALIGN_LEFT) CTable::DrawImage(c,indexes[r]); } } // --- 座標を計算するため int x=0,y=0; // --- テキスト整形メソッド uint text_align=0; // ---テキストを書く for(uint c=0; c<m_columns_total; c++) { // ---(1)テキストのX座標と(2)テキストの配置方法を取得する x =TextX(c); text_align =TextAlign(c,TA_TOP); //--- for(uint r=0; r<rows_total; r++) { // ---(1)座標を計算し、(2)テキストを書く y=m_rows[indexes[r]].m_y+m_label_y_gap; m_table.TextOut(x,y,m_columns[c].m_rows[indexes[r]].m_short_text,TextColor(c,indexes[r]),text_align); } } }
CTable :: SelectRow()メソッドを使用するには、選択する文字列のインデックスである引数を1つだけ渡す必要があります。ここではまず、表の範囲外とこのインデックスの行がすでに選択されているかどうかを確認します。次に、選択した行の現在および前のインデックスと再描画シーケンスを定義します。取得値をCTable :: DrawRow()メソッドに引き渡します。表の目に見える領域の境界にインデックスを付けて、どのポジションにスクロールバーのスライダを移動する必要があるかを定義することができます。
//+------------------------------------------------------------------+ // | 表内の指定された行を選択する | //+------------------------------------------------------------------+ void CTable::SelectRow(const int row_index) { // --- 範囲からの超越をチェックする if(!CheckOutOfRange(0,(uint)row_index)) return; // ---そのような行が既に選択されている場合 if(m_selected_item==row_index) return; // ---現在と前の行のインデックス m_prev_selected_item =(m_selected_item==WRONG_VALUE)? row_index : m_selected_item; m_selected_item =row_index; // --- 特定のシーケンス内の値の配列 int indexes[2]; // ---これが初回の場合 if(m_prev_selected_item==WRONG_VALUE) indexes[0]=m_selected_item; else { indexes[0] =(m_selected_item>m_prev_selected_item)? m_prev_selected_item : m_selected_item; indexes[1] =(m_selected_item>m_prev_selected_item)? m_selected_item : m_prev_selected_item; } // --- 指定されたモードに従って、指定されたテーブル行を描画する DrawRow(indexes,m_selected_item,m_prev_selected_item,false); // --- 可視領域の境界のインデックスを取得する VisibleTableIndexes(); // --- スクロールバーを指定された行に移動する if(row_index==0) { VerticalScrolling(0); } else if((uint)row_index>=m_rows_total-1) { VerticalScrolling(WRONG_VALUE); } else if(row_index<(int)m_visible_table_from_index) { VerticalScrolling(m_scrollv.CurrentPos()-1); } else if(row_index>=(int)m_visible_table_to_index-1) { VerticalScrolling(m_scrollv.CurrentPos()+1); } }
CTableクラスの更新版は記事の最後でダウンロードすることができます。EasyAndFastライブラリの最新バージョンはコードベースにあります。
フレームデータを処理するための補助メソッド
前の記事で紹介したアプリケーションのバージョンでは、結果表の行を選択すると、グラフには複数のシンボルの残高とデポジットのドローダウンが表示されました。マルチシンボルグラフでは特定の曲線がどのシンボルに属するかを理解するために、グラフの右側に曲線の名前が別々に表示されました。このバージョンでは、このグラフのサイズはY軸に沿って確定します。テストに複数のシンボルがある場合、それらのすべては選択された領域に収まりません。したがって、グラフの右側に残高のすべての名前を含むスクロールバーが表示されるCTableタイプのリストを配置します。
シンボルを取得する為に、CProgram :: GetFrameSymbolsToTable()メソッドを使用します。フレームデータが受信されると、結果のシンボルをStringパラメータから結果のシンボルを取得する機能ができます。文字列配列を引き渡すことで、文字のリストを取得します。シンボルの結果が複数の場合、1つの要素で残高の数を大きくし、最初の残高を総残高用に確保する必要があります。
次に、表のディメンションを設定します。ここでは1つの列のみが必要であり、行の数はグラフ上の曲線の数に等しくなります。カラムの幅を確認し、表のヘッダのタイトルを設定します。このサイクルでは、残高名を表に記入します。どの曲線がどの名前に関連しているかを理解するには、これらを色で関連付けさせます。変更内容を表示するには、要素を更新する必要があります。
//+------------------------------------------------------------------+ // | 表のフレームのシンボルを取得します。 | //+------------------------------------------------------------------+ void CProgram::GetFrameSymbolsToTable(void) { // --- シンボルのリストと曲線の数を取得する string symbols[]; int symbols_total =m_frame_gen.CopySymbols(symbols); int balances_total =(symbols_total>1)? symbols_total+1 : symbols_total; //--- 表のサイズを設定する m_table_symbols.Rebuilding(1,balances_total,true); // ---リストのカラムの幅 int width[]={111}; m_table_symbols.ColumnsWidth(width); // --- タイトルを設定する m_table_symbols.SetHeaderText(0,"Balances"); // --- フレームのデータで表を埋める for(uint r=0; r<m_table_symbols.RowsTotal(); r++) { uint clr=m_graph3.GetGraphicPointer().CurveGetByIndex(r).Color(); m_table_symbols.TextColor(0,r,::ColorToARGB(clr)); m_table_symbols.SetValue(0,r,(symbols_total>1)?(r<1)? "BALANCE" : symbols[r-1]: symbols[r],0); } // --- 表を更新する m_table_symbols.Update(true); m_table_symbols.GetScrollHPointer().Update(true); m_table_symbols.GetScrollVPointer().Update(true); }
テーブル内の結果を選択した時に、グラフ上の曲線も強調表示されると非常に便利です。このために、CProgram :: SelectCurve()メソッドを記述します。ここにパス番号を渡して、グラフ上の必要なの曲線を見つけます。曲線の名前は、それらが属するパスの番号に対応しています。このため、 曲線の名前に含まれるものと一緒に送信されたパス番号をループ内で比較するだけで見つけることができます。目的の曲線が見つかったら、そのインデックスを覚えて、ループを停止します。
ここで、見つけた曲線を最上位層に転送する必要があります。単に色を変えるだけで曲線に印を付けると、他のものに埋もれてしまう可能性があるからです。したがって、最後に描いた曲線と見つけた曲線を入れ替える必要があります。
このために、インデックスによってこれら2つの曲線のポインタを取得します。次に、これらの名前とデータ配列をコピーします。その後、これらを入れ替えます。最後の曲線に、線の太さと色を設定します。他のものに比べてわかりやすくする為に、これの色を黒にします。変更を有効にするには、グラフを更新する必要があります。
//+------------------------------------------------------------------+ // |最適化プロセスの終了のイベント | //+------------------------------------------------------------------+ void CProgram::SelectCurve(const ulong pass) { CGraphic *graph=m_graph5.GetGraphicPointer(); // --- パス番号で曲線を探す ulong curve_index =0; int curves_total =graph.CurvesTotal(); for(int i=0; i<curves_total; i++) { if(pass==(ulong)graph.CurveGetByIndex(i).Name()) { curve_index=i; break; } } // --- グラフで選択した曲線と最後の曲線 CCurve *selected_curve =graph.CurveGetByIndex((int)curve_index); CCurve *last_curve =graph.CurveGetByIndex((int)curves_total-1); // --- 選択したデータ配列と最後のデータ配列をコピーする double y1[],y2[]; string name1=selected_curve.Name(); string name2=last_curve.Name(); selected_curve.GetY(y1); last_curve.GetY(y2); //--- last_curve.Name(name1); selected_curve.Name(name2); last_curve.Update(y1); selected_curve.Update(y2); //--- last_curve.LinesWidth(2); last_curve.Color(clrBlack); // --- グラフを更新する graph.CurvePlotAll(); graph.Update(); }
これで表内の行を選択すると、グラフ上の対応する残高曲線を見ることができるようになります。
図3.グラフ上の曲線を強調表示する。
グラフィカルインタフェースと相互作用する時のイベント処理
私達のアプリケーションのグラフィカルインタフェースと相互作用する際に生成されるイベントを処理する方法を検討する必要があります。これがそのメソッドです。
- マウスの左クリックでの表内の行の選択。
- キーボードからの表内の結果の選択。
- ドロップダウンリストでの結果の選択基準の選択。
マウスでクリックして行を選択すると、カスタムイベントON_CLICK_LIST_ITEMが生成されます。これを処理するために、CProgram :: TableRowSelection()メソッドが選択されます。ここにlongイベントパラメータが引き渡されます。このパラメータは、このイベントが生成された要素の識別子です。識別子が要素に適用されない場合、プログラムはメソッドを終了し、アプリケーション要素のイベントハンドラの次の要素をチェックします。識別子が結果表と同じであれば、表の最初の列からパス番号を取得します。したがって、パス番号を取得するには、CTable :: GetValue()メソッドに値を引き渡すことで列と新しく選択した行のインデックスを指定する必要があります。
ということで、パス番号を取得しました。これで、このフレームからデータを取得し、次にこの結果に含まれるシンボルを取得できます。これを2番目のグループの最初のタブの表に追加します。最後に、全結果のグラフ上で残高曲線を選択します。
//+------------------------------------------------------------------+ // | 左クリックで表の行を選択する | //+------------------------------------------------------------------+ bool CProgram::TableRowSelection(const long element_id) { // --- 表内の行の選択 if(element_id!=m_table_main.Id()) return(false); // --- 表からパス番号を取得する ulong pass=(ulong)m_table_main.GetValue(0,m_table_main.SelectedItem()); // --- パス番号のデータを取得する m_frame_gen.GetFrameData(pass); // --- 表にシンボルを追加する GetFrameSymbolsToTable(); // --- グラフ上の曲線をパスの数で選択する SelectCurve(pass); return(true); }
カスタムイベントON_CLICK_LIST_ITEMが発生すると、コンボボックスのドロップダウンリストで結果を選択する基準を選ぶアクションも処理されます(CComboBox)。これを行うのがCProgram :: ShowResultsBySelectedCriteria()になります。要素の識別子の検証に成功すると、選択したアイテムのインデックスがドロップダウンリストに表示されます。このバージョンのアプリケーションでは、ドロップダウンリストに3つの基準が表示されます。
- Resultー OnTester()を返す、カスタム結果。
- Profitー テスト結果の総利益。
- Recovery factor— 回復要素。
次に、選択した基準に関連するデータで列のインデックスを決定します。最初の項目は、1のインデックスを持つ列に対応し、2番目は2のインデックスを持つ列、そして3番目は5のインデックスを持つ列に対応しています。次に、選択した基準に基づく最良の結果があるフレームを取得します。このために列のインデックスを引き渡してから、CFrameGenerator :: OnChangedSelectionCriteria()メソッドを呼び出す必要があります。これで、チャート上で最良の結果の残高を取得する準備が完了しました。このプロセスは進行状況バーで視覚化されます。ここでの最後の課題は、取得した全データを最良結果表に収めることです。
//+------------------------------------------------------------------+ // | 結果を指定された基準で表示する | //+------------------------------------------------------------------+ bool CProgram::ShowResultsBySelectedCriteria(const long element_id) { // --- 要素の識別子を確認する if(element_id!=m_criterion.Id()) return(false); // --- 最良の結果を取得する基準のインデックスを定義する int index=m_criterion.GetListViewPointer().SelectedItemIndex(); int column_index=(index<1)? 1 : (index==1)? 2 : 5; m_frame_gen.OnChangedSelectionCriteria(column_index); // ---最良の結果を取得するプロセスを視覚化する GetBestOptimizationResults(); // ---最適化結果表へデータを取得する GetFrameDataToTable(); return(true); }
プログラムイベントハンドラでは、ON_CLICK_LIST_ITEMイベントの発生時に順次に上記のメソッドが呼び出され、それらのうちの1つがtrueを返すまで呼び出されます。
//+------------------------------------------------------------------+ // |イベントハンドラ | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... // --- 表の行を押すイベント if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { // --- 表内の行の選択 if(TableRowSelection(lparam)) return; // --- 結果の選択基準を選ぶ if(ShowResultsBySelectedCriteria(lparam)) return; //--- return; } ... }
ドロップダウンリストからの選択プロセスがどのように表示され、取得データとグラフ上で描画された様子は以下のようになります。
図4 - 指定された基準による結果の選択。
キーボードから列を選択するには、CProgram :: UsingResultsUsingKeys()メソッドが必要になります。ここに押したキーのコードを渡す必要があります。これはCHARTEVENT_KEYDOWNイベントのlongパラメータに入ります。メソッドの始めに、表内で現在選択している行のインデックスを取得します。次に、switchオペレータースイッチで、どのキーが押されたかを判別します。4つのキーストロークを処理する方法の例を以下に書きます。
- KEY_UP — Upキーを押下した場合、選択した行の現在のインデックスが1減り、上の行が強調表示されます。
- KEY_DOWN — Downキーを押下した場合、選択した行の現在のインデックスが1増え、下の次の行が強調表示されます。
- KEY_HOME — Homeキーを押下した場合、表内の最初の行のインデックスを設定します。
- KEY_END — Endキーを押下した場合、表内の最後の行のインデックスを設定します。
次に、チェックをします。プログラムは以下の場合にメソッドを終了します。
- 行が選択されていない場合、
- 同じ行が選択されている場合、
- リストの範囲外に出た場合。
チェックが完了すると、指定された行が表で強調表示され、必要に応じて垂直のスクロールバーが移動します。
行が選択されると、次のようになります。
- 表内のの最初の行からパス番号を取得する。
- パス番号に関するデータを取得する。
- マルチシンボルグラフの隣にあるシンボルをリストに追加します。
- 選択したすべての結果のグラフ上のバランス曲線を選択します。
CProgram::SelectingResultsUsingKeys()メソッドのコード:
//+------------------------------------------------------------------+ // | キーを使用して結果を選択する | //+------------------------------------------------------------------+ bool CProgram::SelectingResultsUsingKeys(const long key) { // --- 選択した行のインデックスを取得する int selected_row=m_table_main.SelectedItem(); // --- スクロールバーを移動する方向と行を定義する switch((int)key) { case KEY_UP : selected_row--; break; case KEY_DOWN : selected_row++; break; case KEY_HOME : selected_row=0; break; case KEY_END : selected_row=(int)m_table_main.RowsTotal()-1; break; } // --- (1)行が強調表示されていないか、または(2)同じ行が選択されているか、(3)リストの外に出た場合に終了する if(selected_row==WRONG_VALUE || selected_row==m_table_main.SelectedItem() || selected_row<0 || selected_row>=(int)m_table_main.RowsTotal()) return(false); // --- 行を選択してスクロールバーを移動する m_table_main.SelectRow(selected_row); m_table_main.Update(); m_table_main.GetScrollVPointer().Update(true); // --- 選択した表内の行からパス番号を取得する ulong pass=(ulong)m_table_main.GetValue(0,m_table_main.SelectedItem()); // --- パス番号のデータを取得する m_frame_gen.GetFrameData(pass); // --- 表にシンボルを追加する GetFrameSymbolsToTable(); // --- グラフ上の曲線をパスの数で選択する SelectCurve(pass); return(true); }
CProgram :: UsingResultsUsingKeys()メソッドは、イベントハンドラ内でキーボードイベント(CHARTEVENT_KEYDOWN)が起こると呼び出されます。
//+------------------------------------------------------------------+ // |イベントハンドラ | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { // --- キーを押す if(id==CHARTEVENT_KEYDOWN) { // --- キーを使用して結果を選択する if(SelectingResultsUsingKeys(lparam)) return; //--- return; } ... }
動作の様子は以下のようになります。
図5 - キーボードを使用して表内の行を選択する。
まとめ
この記事では、EasyAndFastグラフィカルインターフェイスのライブラリが役立つもう1つのケースを紹介しました。視覚的要素がテスト結果の解析にとって非常に重要なことは明らかです。これらのテスト結果の配列の深く広範な見解は、新しいアイデアにつながる可能性があります。そういったもののうちのいくつかは既にMQLコミュニティの参加者によって提案されています。
たとえば、フレーム配列では、統計指標や残高データだけでなく、完全なテストレポートを保存することができます。またフォーラムで議論された別のアイデアは、結果の選択にカスタム基準を使用するというものでした。たとえば、複数のドロップダウンリストやチェックボックスを使用してカスタム条件を作成し、設定で指定された数式を使用して計算することができます。グラフィカルインターフェースなしでこれをどのように実装できるか想像するのは難しいです。
記事へのコメントで提案してもらったアイデアは、次のバージョンで実装されるかもしれません。あなた自身の最適化の結果を扱うアプリケーションをさらに開発する方法を提案することを躊躇しないでください。
以下から記事で紹介したコードをより詳しく知ったりテストを行うために、自分のPCにファイルをダウンロードすることができます。
ファイル名 | コメント |
---|---|
MacdSampleCFrames.mq5 | 標準配布から変更されたEA - MACD Sample |
Program.mqh | プログラムクラスファイル |
CreateGUI.mqh | Program.mqhファイルのプログラムクラスのメソッドが実装されたファイル |
Strategy.mqh | MACD Sampleのストラテジーが変更されたクラスのファイル(マルチシンボルバージョン) |
FormatString.mqh | 文字列を整えるための補助関数ファイル |
FrameGenerator.mqh | 最適化結果を処理するクラスのファイル |