English Русский 中文 Español Deutsch Português
preview
連続的なウォークフォワード最適化(その7)。オートオプティマイザの論理部分をグラフィックスでバインドし、プログラムからグラフィックスを制御する

連続的なウォークフォワード最適化(その7)。オートオプティマイザの論理部分をグラフィックスでバインドし、プログラムからグラフィックスを制御する

MetaTrader 5 | 11 8月 2020, 10:06
1 391 0
Andrey Azatskiy
Andrey Azatskiy

今回は、プログラムの論理的な部分とグラフィカルな表現がどのようにつながっているかを考えてみます。 最適化の実行プロセス全体を最初から見て、オートオプティマイザクラスに至るまでの全段階を分析します。 また、論理プログラムパートと表示パートがどのようにつながっているのかを確認し、アプリケーションコードからグラフィックスを管理する方法を検討します。 このシリーズの過去の記事です。

  1. 連続的なウォークフォワード最適化(その1)。最適化レポートを使ったタスク
  2. 連続的なウォークフォワード最適化(その2)。任意のロボットの最適化レポートを作成する仕組み
  3. 連続的なウォークフォワード最適化(その3)。ロボットの自動最適化への適応
  4. 連続的なウォークフォワード最適化(その4)。最適化マネージャ(オートオプティマイザ)
  5. 連続的なウォークフォワード最適化(その5)。オートオプティマイザプロジェクトの概要とGUIの作成
  6. 連続的なウォークフォワード最適化(その6)。オートオプティマイザの論理部分と構造

ViewModel クラスとグラフィックレイヤーとの相互作用

既に述べたように、ViewModel はアプリケーションのグラフィカルな部分とロジックのソフトウェア実装の間のコネクタです。 アプリケーションの論理部分のコールバックに対するアプリケーションロジックの呼び出しとグラフィックスの反応を実装したプログラムのグラフィックス表現です。 したがって、ViewModel部分のパブリックプロパティは、アプリケーションのグラフィカル部分の各編集可能フィールドに対応します。 プロパティは、ゲッター(この場合はグラフィックから変更できません)か、セッター(このプロパティの背後に隠されたオブジェクトを上書きすることができます)のどちらかになります。 これまでのパートでは、すでにデータバインディング技術について詳細に検討してきました。 そこで、ここでは例を挙げてみます。 

テキストフィールドは、書き込みと読み取りの両方のアクセス権を持つプロパティを使用して接続されています。 例として、最適化を実行するアセットの名前を示すフィールドを考えてみましょう。 このフィールドのXAMLマークアップはシンプルです。

<TextBox Width="100"          IsEnabled="{Binding EnableMainTogles, UpdateSourceTrigger=PropertyChanged}"          Text="{Binding AssetName}"/>

テキストウィンドウの幅を設定するだけでなく、IsEnabledとTextというフィールドも持っています。 最初のは、そのフィールドが編集可能かどうかを設定します。 trueに設定すると、編集可能なフィールドになります。 falseの場合、フィールドはロックされています。 "Text"フィールドには、このフィールドにインプットされたテキストがあります。 そして、それぞれの反対側に巻き括弧での構築があります。 その内容は、"Binding "パラメータの後に指定されたViewModelクラスの特定のパブリックプロパティとオブジェクトの接続を設定します。

続いて、パラメータを指定することができます。 例えば、UpdateSourceTriggerパラメータは、このアプリケーションのグラフィカル部分の更新方法を示します。 この例で使用した値 (PropertyChanged) は、"Binding" パラメータの後に渡された名前 (この例では "EnableMainTogles" です) が指定された ViewModel クラスから OnPropertyChanged イベントがトリガーされた時にのみグラフィカルな部分が更新されることを示します。

"Text" パラメータが文字列ではなく double パラメータにバインドされている場合、このフィールドには数字のみが許可されます。 int型にバインドされている場合は、整数のみが許可されます。 すなわち、実施形態では、インプットされた値の種類に応じた要件を設定することができます。

ViewModel部分では、以下のようにフィールドが提示されています。

IsEnabled パラメータ。

/// <summary> /// If the switch = false, then the most important fields are not available /// </summary> public bool EnableMainTogles { get; private set; } = true;

とテキストパラメータを指定します。

/// <summary> /// Name of the asset selected for tests / optimization /// </summary> public string AssetName { get; set; }

ご覧の通り、どちらも書き込みデータと読み込みデータの両方にアクセスできるようになっています。 唯一の差は、EnableMainToglesプロパティは、AutoOptimiserVMクラスから(つまり自分自身から)のみ書き込みアクセスを提供するため、外部から編集することができないということです。

例えば、フォワード最適化結果のリストなど、任意のデータの集合を考えると、値のリストを含むプロパティに対応します。 フォワードパスの結果を表にして考えてみましょう。

<ListView ItemsSource="{Binding ForwardOptimisations}"           SelectedIndex="{Binding SelectedForwardItem}"           v:ListViewExtention.DoubleClickCommand="{Binding StartTestForward}">     <ListView.View>         <GridView>             <GridViewColumn Header="Date From"                             DisplayMemberBinding="{Binding From}"/>             <GridViewColumn Header="Date Till"                             DisplayMemberBinding="{Binding Till}"/>             <GridViewColumn Header="Payoff"                             DisplayMemberBinding="{Binding Payoff}"/>             <GridViewColumn Header="Profit pactor"                             DisplayMemberBinding="{Binding ProfitFactor}"/>             <GridViewColumn Header="Average Profit Factor"                             DisplayMemberBinding="{Binding AverageProfitFactor}"/>             <GridViewColumn Header="Recovery factor"                             DisplayMemberBinding="{Binding RecoveryFactor}"/>             <GridViewColumn Header="Average Recovery Factor"                             DisplayMemberBinding="{Binding AverageRecoveryFactor}"/>             <GridViewColumn Header="PL"                             DisplayMemberBinding="{Binding PL}"/>             <GridViewColumn Header="DD"                             DisplayMemberBinding="{Binding DD}"/>             <GridViewColumn Header="Altman Z score"                             DisplayMemberBinding="{Binding AltmanZScore}"/>             <GridViewColumn Header="Total trades"                             DisplayMemberBinding="{Binding TotalTrades}"/>             <GridViewColumn Header="VaR 90"                             DisplayMemberBinding="{Binding VaR90}"/>             <GridViewColumn Header="VaR 95"                             DisplayMemberBinding="{Binding VaR95}"/>             <GridViewColumn Header="VaR 99"                             DisplayMemberBinding="{Binding VaR99}"/>             <GridViewColumn Header="Mx"                             DisplayMemberBinding="{Binding Mx}"/>             <GridViewColumn Header="Std"                             DisplayMemberBinding="{Binding Std}"/>         </GridView>     </ListView.View> </ListView>

マークアップからわかるように、ListView型のテーブルは、テーブルクラス自体の参照です。 続いて、データが格納されるグリッドを作成し、データが格納されるを作成します。 クラス参照の作成に言及することで、ListViewクラスを参照していました。 この一見シンプルなXAMLマークアップは、かなり複雑でよく考えられたメカニズムで、マークアップ言語を使ってクラスを記述したり、クラスオブジェクトを使ってタスクしたりすることができます。 AutoOptimiserVMクラスに関連付けられたすべてのフィールドは、まさにクラスのプロパティです。 上記の表を用いた例では、3つのクラスを扱います。

  • ListView - System.Windows.Control.ListView.
  • GridView - System.Windows.Controls.GridViewは、System.Windows.Controls.ViewBaseから派生したもので、ListViewクラスからViewプロパティを初期化するクラスとして使用することができます。
  • GridViewColumn - System.Windows.Control.GridViewColumn.

ListViewクラスのItemsSourceプロパティは、テーブルが構成する要素の集合を示します。 このプロパティを ViewModel のコレクションに接続した後、テーブル内で動作する Window クラスの DataContext のようなものができました。 ここではテーブルの話をしているので、コレクションを表すテーブルは、それぞれのテーブルの公開プロパティを持つクラスで構成されていなければなりません。 データを持つテーブルを表すViewModelからのプロパティでItemsSourceプロパティをバインドした後、与えられたテーブルから所望のカラム値を持つ各カラムをバインドすることができます。 また、テーブルではViewModelからSelectedIndexプロパティとSelectedForwardItemプロパティを接続します。 ViewModel がユーザがこのテーブルでどの行を選択したかを知るために必要です。

ViewModelでは、提示されたテーブルがバインドされるプロパティを以下のように実装します。

/// <summary> /// Selected forward tests /// </summary> public ObservableCollection<ReportItem> ForwardOptimisations { get; } = new ObservableCollection<ReportItem>();

C#標準ライブラリにあるObservableCollectionクラスは、グラフィックに変更を通知するオブジェクトです。 これは、クラスがすでに前述のイベントを持っていて、その要素のリストを更新するときに毎回を呼び出すためです。 あとは、定番のデータ集です。

SelectedForwardItemプロパティは、いくつかの役割を果たします:選択されたテーブル行のデータを格納し、行選択コールバックとして機能します。

/// <summary> /// Selected forward pass /// </summary> private int _selectedForwardItem; public int SelectedForwardItem {     get => _selectedForwardItem;     set     {         _selectedForwardItem = value;         if (value > -1)         {             FillInBotParams(model.ForwardOptimisations[value]);             FillInDailyPL(model.ForwardOptimisations[value]);             FillInMaxPLDD(model.ForwardOptimisations[value]);         }     } } 

プロパティはコールバックとして利用されており、値の設定に対する反応の指定(この例では)が期待されるため、セッターはこの反応の実装を含み、関数として機能しなければなりません。 このため、プロパティの値はプライベート変数に格納されます。 この変数から値を受け取るために、ゲッターから直接アクセスします。 値を設定するには、セッター で 'value' として格納されている値を設定します。 value' が -1 よりも大きい場合は、結果タブの他の関連テーブルを埋め、選択した行に従って更新されます。 トレードロボットのパラメータ、平均利益、週の日の損失と最高/最低PL値を持つテーブルです。 if条件で行われるチェックは、選択されたテーブル要素のインデックスが-1であれば、そのテーブルが空であることを意味するので、関連するテーブルをインプットする必要がないため、必要です。 呼び出されたメソッドの実装は、AutoOptimiserVMクラスのコードにあります。

ここでは、最適化結果のラインを記述したクラスの実装です。

/// <summary> /// Class - a wrapper for a report item (for a graphical interval) /// </summary> class ReportItem {     /// <summary>     /// Constructor     /// </summary>     /// <param name="item">Item</param>     public ReportItem(OptimisationResult item)     {         result = item;     }     /// <summary>     /// Report item     /// </summary>     private readonly OptimisationResult result;     public DateTime From => result.report.DateBorders.From;     public DateTime Till => result.report.DateBorders.Till;     public double SortBy => result.SortBy;     public double Payoff => result.report.OptimisationCoefficients.Payoff;     public double ProfitFactor => result.report.OptimisationCoefficients.ProfitFactor;     public double AverageProfitFactor => result.report.OptimisationCoefficients.AverageProfitFactor;     public double RecoveryFactor => result.report.OptimisationCoefficients.RecoveryFactor;     public double AverageRecoveryFactor => result.report.OptimisationCoefficients.AverageRecoveryFactor;     public double PL => result.report.OptimisationCoefficients.PL;     public double DD => result.report.OptimisationCoefficients.DD;     public double AltmanZScore => result.report.OptimisationCoefficients.AltmanZScore;     public int TotalTrades => result.report.OptimisationCoefficients.TotalTrades;     public double VaR90 => result.report.OptimisationCoefficients.VaR.Q_90;     public double VaR95 => result.report.OptimisationCoefficients.VaR.Q_95;     public double VaR99 => result.report.OptimisationCoefficients.VaR.Q_99;     public double Mx => result.report.OptimisationCoefficients.VaR.Mx;     public double Std => result.report.OptimisationCoefficients.VaR.Std; }

このクラスは、コード内の最適化パスの文字列表現を実演するために提供されています。 各テーブル・カラムは、特定のクラス・インスタンスの適切なプロパティに関連付けられています。 クラス自体は、最初の記事で検討したOptimisationResult構造体のラッパです。

テーブル行のすべてのボタンやダブルクリックは、基本型が ICommand である ViewModel の Command プロパティで接続されています。 この技術については、グラフィカル・インターフェースの作成に関する以前の記事ですでに検討しました。 

ViewModel クラスとデータモデルとの相互作用

この章では、同じボタンに結合された最適化の開始とストップのコールバックについて説明します。 


StartStopボタンをクリックすると、AutoOptimiserVMクラスの_StartStopOptimisationメソッドが呼び出されます。 さらに、最適化をストップして最適化を開始するという2つの選択肢があります。 図からわかるように、オプティマイザクラスのIsOptimisationInProcessプロパティがtrueを返すと、ロジックの最初の部分を実行し、データモデルクラスにStopOptimisationメソッドをリクエストします。 その後、このメソッドはこの呼び出しをオプティマイザにリダイレクトします。 最適化が開始されていない場合は、データモデルクラスからStartOptimisationメソッドが呼び出されます。 このメソッドは非同期であるため、呼び出されたStartメソッドは_StartStopOptimisation操作が完了した後も操作を継続します。 

メソッドの呼び出しで実行される呼び出しの連鎖を考察しました。 では、メソッド呼び出しとグラフィカルな部分とModelとの接続をViewModelで記述したコードブロックを見てみましょう。 XAMLのグラフィカルなマークアップは難しくないので、ここでは紹介しません。 ViewModel部分については、プロパティと最適化の起動を担当するメソッドは以下の通りです。

private void _StartStopOptimisation(object o) {     if (model.Optimiser.IsOptimisationInProcess)     {         model.StopOptimisation();     }     else     {         EnableMainTogles = false;         OnPropertyChanged("EnableMainTogles");         Model.OptimisationManagers.OptimiserInputData optimiserInputData = new Model.OptimisationManagers.OptimiserInputData         {             Balance = Convert.ToDouble(OptimiserSettings.Find(x => x.Name == "Deposit").SelectedParam),             BotParams = BotParams?.Select(x => x.Param).ToList(),             CompareData = FilterItems.ToDictionary(x => x.Sorter, x => new KeyValuePair<CompareType, double>(x.CompareType, x.Border)),             Currency = OptimiserSettings.Find(x => x.Name == "Currency").SelectedParam,             ExecutionDelay = GetEnum<ENUM_ExecutionDelay>(OptimiserSettings.Find(x => x.Name == "Execution Mode").SelectedParam),             Laverage = Convert.ToInt32(OptimiserSettings.Find(x => x.Name == "Laverage").SelectedParam),             Model = GetEnum<ENUM_Model>(OptimiserSettings.Find(x => x.Name == "Optimisation model").SelectedParam),             OptimisationMode = GetEnum<ENUM_OptimisationMode>(OptimiserSettings.Find(x => x.Name == "Optimisation mode").SelectedParam),             RelativePathToBot = OptimiserSettings.Find(x => x.Name == "Available experts").SelectedParam,             Symb = AssetName,             TF = GetEnum<ENUM_Timeframes>(OptimiserSettings.Find(x => x.Name == "TF").SelectedParam),             HistoryBorders = (DateBorders.Any(x => x.BorderType == OptimisationType.History) ?                             DateBorders.Where(x => x.BorderType == OptimisationType.History)                             .Select(x => x.DateBorders).ToList() :                             new List<DateBorders>()),             ForwardBorders = (DateBorders.Any(x => x.BorderType == OptimisationType.Forward) ?                             DateBorders.Where(x => x.BorderType == OptimisationType.Forward)                             .Select(x => x.DateBorders).ToList() :                             new List<DateBorders>()),             SortingFlags = SorterItems.Select(x => x.Sorter)         };         model.StartOptimisation(optimiserInputData, FileWritingMode == "Append", DirPrefix);     } } /// <summary> /// Callback for the graphical interface - run optimization / test /// </summary> public ICommand StartStopOptimisation { get; }

コードを見ても図を見てもわかるように、メソッドは「If Else」条件の2つの分岐に分かれています。 一つ目のは、実行中であれば最適化処理をストップします。 2つ目のは、それ以外の処理を実行します。

最適化起動時には、EnableMainTogles = falseとすることでグラフィカル・インターフェースのメインフィールドをロックし、インプットパラメータの形成を進めます。 最適化を開始するには、OptimistionInputData構造体を作成し、OptimiserSettings、BotParams、FilterItems、SorterItems、DateBordersコレクションを埋めます。 値は、すでに述べたデータ・バインディング・メカニズムを使用して、グラフィカル・インターフェースから直接構造に到着します。 この構造体の形成が完了したら、データモデルクラスのインスタンスに対して、前述のStartOptimisationメソッドを実行します。 コンストラクタのStartStopOptimisationプロパティ。

// Callback of optimization start/stop buttons StartStopOptimisation = new RelayCommand(_StartStopOptimisation);

アプリケーションのグラフィカルな部分から、ViewModel のコマンドを Command プロパティにバインドするために必要な ICommand インターフェースを実装した RelayCommand クラスインスタンスによってインスタンス化されます。

すべての最適化が実行され、結果タブのテーブルが作成されたら(または Load ボタンを使用して、リストから最適化を選択してアップロードしたら)、選択した最適化パスをダブルクリックすることで、必要な時間間隔で選択した最適化パスのテストを開始できます。 

private void _StartTest(List<OptimisationResult> results, int ind) {     try     {         Model.OptimisationManagers.OptimiserInputData optimiserInputData = new Model.OptimisationManagers.OptimiserInputData         {             Balance = Convert.ToDouble(OptimiserSettingsForResults_fixed.First(x => x.Key == "Deposit").Value),             Currency = OptimiserSettingsForResults_fixed.First(x => x.Key == "Currency").Value,             ExecutionDelay = GetEnum<ENUM_ExecutionDelay>(OptimiserSettingsForResults_changing.First(x => x.Name == "Execution Mode").SelectedParam),             Laverage = Convert.ToInt32(OptimiserSettingsForResults_fixed.First(x => x.Key == "Laverage").Value),             Model = GetEnum<ENUM_Model>(OptimiserSettingsForResults_changing.First(x => x.Name == "Optimisation model").SelectedParam),             OptimisationMode = ENUM_OptimisationMode.Disabled,             RelativePathToBot = OptimiserSettingsForResults_fixed.First(x => x.Key == "Expert").Value,             ForwardBorders = new List<DateBorders>(),             HistoryBorders = new List<DateBorders> { new DateBorders(TestFrom, TestTill) },             Symb = OptimiserSettingsForResults_fixed.First(x => x.Key == "Symbol").Value,             TF = (ENUM_Timeframes)Enum.Parse(typeof(ENUM_Timeframes), OptimiserSettingsForResults_fixed.First(x => x.Key == "TF").Value),             SortingFlags = null,             CompareData = null,             BotParams = results[ind].report.BotParams.Select(x => new ParamsItem { Variable = x.Key, Value = x.Value }).ToList()         };         model.StartTest(optimiserInputData);     }     catch (Exception e)     {         System.Windows.MessageBox.Show(e.Message);     } }

そして、インプットパラメータを持つ構造体を作成し、テストを起動します。 メソッド実行処理中にエラーが発生した場合、エラーメッセージをMessageBoxに表示します。 メソッドの実装はすでに考察されています。 しかし、このコールバックを含むプロパティのインスタンス化をもう一度見てみましょう。 3種類のテーブルを用意します。

  • フォワードテスト。
  • ヒストリー的なテスト。
  • 選択した日付範囲の最適化のリスト。

そのため、3つのコールバックが作成されています。 各テーブルデータを正しく処理するために必要です。 

/// <summary>
/// Run a test from a table with forward tests
/// </summary>
public ICommand StartTestForward { get; }
/// <summary>
/// Run a test from a table with historical tests
/// </summary>
public ICommand StartTestHistory { get; }
/// <summary>
/// Run a test from a table with optimization results
/// </summary>
public ICommand StartTestReport { get; }

実装はラムダ関数を設定することで行われます。

StartTestReport = new RelayCommand((object o) => {     _StartTest(model.AllOptimisationResults.AllOptimisationResults[ReportDateBorders[SelectedReportDateBorder]], SelecterReportItem); }); // Callback for the test start upon the event of double-clicking on the table with historical tests StartTestHistory = new RelayCommand((object o) => {     _StartTest(model.HistoryOptimisations, SelectedHistoryItem); }); // Callback for the test start upon the event of double-clicking on the table with historical tests StartTestForward = new RelayCommand((object o) => {     _StartTest(model.ForwardOptimisations, SelectedForwardItem); });

このアプローチにより、最適化結果を持つリクエストリストを作成することが可能になります。 

最適化プロセスが決済し、最適な結果が選択され、ヒストリカルデータとフォワードデータを使用してテストされた後、すべての最適化パスを含むリストが保存されます。 このプロセスにより、ユーザは、選択されたオプティマイザの動作ロジックを確認することができるだけでなく、フィルタリングおよびソート要因を変更することにより、他のパスを手動で選択することができます。 そのため、最適化結果をフィルタリングしたり、複数の条件に応じて同時にソートしたりするために、内蔵のメカニズムを使用する可能性があります。 この機構はデータモデルに実装されていますが、機構のインプットパラメータはViewModelクラスで生成されます。

/// <summary> /// Sort reports /// </summary> /// <param name="o"></param> private void _SortResults(object o) {     if (ReportDateBorders.Count == 0)         return;     IEnumerable<SortBy> sortFlags = SorterItems.Select(x => x.Sorter);     if (sortFlags.Count() == 0)         return;     if (AllOptimisations.Count == 0)         return;     model.SortResults(ReportDateBorders[SelectedReportDateBorder], sortFlags); } public ICommand SortResults { get; } /// <summary> /// Filtering reports /// </summary> /// <param name="o"></param> private void _FilterResults(object o) {     if (ReportDateBorders.Count == 0)         return;     IDictionary<SortBy, KeyValuePair<CompareType, double>> compareData =         FilterItems.ToDictionary(x => x.Sorter, x => new KeyValuePair<CompareType, double>(x.CompareType, x.Border));     if (compareData.Count() == 0)         return;     if (AllOptimisations.Count == 0)         return;     model.FilterResults(ReportDateBorders[SelectedReportDateBorder], compareData); } public ICommand FilterResults { get; }

2つのメソッドは、似たような実装があります。 データフィルタリングパラメータの存在をチェックし(すなわち、テーブルが空ではないこと)、その実行をデータモデルクラスにリダイレクトします。 データモデルクラスの両方のメソッドは、最初の記事で説明した適切な拡張メソッドに実行をリダイレクトします。

ソート方法には以下のようなシグニチャがあります。

public static IEnumerable<OptimisationResult> SortOptimisations(this IEnumerable<OptimisationResult> results,                                                                         OrderBy order, IEnumerable<SortBy> sortingFlags,                                                                         Func<SortBy, SortMethod> sortMethod = null)

フィルタリングメソッド:

public static IEnumerable<OptimisationResult> FiltreOptimisations(this IEnumerable<OptimisationResult> results,                                                                   IDictionary<SortBy, KeyValuePair<CompareType, double>> compareData)

非同期モードで実行され、ソート(データ量に応じて1秒以上かかる)を実行している間にグラフィックがロックするのを避けるためです。

データソートといえば、2つのデータソートテーブルとデータフィルタリングの接続の実装を見てみましょう。 オートオプティマイザーでは、結果タブと設定タブ(メイン)の両方に、テーブルデータのソートとフィルタリングを行う領域があります、これが今回の話です。

   

上のスクリーンショットでは、最適化結果タブでこの領域がマークされています。 この領域にソートパラメータを追加してから別のタブ(この例では設定タブ)に切り替えると、追加された同じ値が同じ領域に表示されるという考え方です。 さて、この値をwithの設定タブのこの領域から削除してから、最適化結果のあるタブに切り替えると、このタブからもこの値が削除されていることがわかります。 これは、両方のテーブルが同じプロパティにリンクされているためです。

ソートテーブルは、以下のプロパティにリンクされています。

/// <summary> /// Selected sorting options /// </summary> public ObservableCollection<SorterItem> SorterItems { get; } = new ObservableCollection<SorterItem>();

フィルタテーブルはリンクされています。   

/// <summary> /// Selected filters /// </summary> public ObservableCollection<FilterItem> FilterItems { get; } = new ObservableCollection<FilterItem>();

テーブルの行を記述するクラスには繰り返しフィールドがあり、同じファイル内の ViewModel.

/// <summary> /// Wrapper class for enum SortBy (for graphical interval) /// </summary> class SorterItem {     /// <summary>     /// Constructor     /// </summary>     /// <param name="sorter">Sort parameter</param>     /// <param name="deleteItem">Delete from list callback</param>     public SorterItem(SortBy sorter, Action<object> deleteItem)     {         Sorter = sorter;         Delete = new RelayCommand((object o) => deleteItem(this));      }      /// <summary>      /// Sort element      /// </summary>      public SortBy Sorter { get; }      /// <summary>      /// Item delete callback      /// </summary>      public ICommand Delete { get; } } /// <summary> /// Wrapper class for enum SortBy and CompareType flags (for GUI) /// </summary> class FilterItem : SorterItem {     /// <summary>     /// Constructor     /// </summary>     /// <param name="sorter">Sort element</param>     /// <param name="deleteItem">Deletion callback</param>     /// <param name="compareType">Comparison method</param>     /// <param name="border">Comparable value</param>     public FilterItem(SortBy sorter, Action<object> deleteItem,                       CompareType compareType, double border) : base(sorter, deleteItem)     {         CompareType = compareType;         Border = border;     }     /// <summary>     /// Comparison type     /// </summary>     public CompareType CompareType { get; }     /// <summary>     /// Comparable value     /// </summary>     public double Border { get; } }

SorterItemクラスは、ソートに選択されたパラメータのテーブル行を提示するオブジェクトです。 ソート・パラメータに加えて、リストからこの特定のパラメータの削除のコールバックを指すプロパティがあります。 このコールバックは、デリゲートを介して外部から設定されることに注意してください。 データフィルタクラスはソートクラスから継承されています: ベースクラスから継承することができるので、すでに実装されているフィールドを2度書きする必要はありません。 先に検討したパラメータセットに加えて、閾値を持つデータ比較型と、この閾値自体を持ちます。

ラインを提示するクラスに削除メソッドが存在することで、現在の実装で行われているように、各ラインの横に削除ボタンを追加することができます。 これは、ユーザーにとっては便利で、面白い実装です。 削除メソッドはクラスの外に実装されています。 ViewModel を表すクラスにあるデータコレクションにアクセスする必要があるため、デリゲートとして設定されます。 実装はシンプルなので、ここでは提供しません。 このメソッドは、目的のデータ収集インスタンスのDeleteメソッドを呼び出すだけです。

グラフィカルレイヤーからの反応を必要とするイベントのいくつかが完了した後、OnPropertyChanged イベントが呼び出されます。 ViewModelを表すクラスのイベントコールバックは以下のように実装されています。

private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e) {     // The test has completed, or you need to resume the availability of the buttons locked at the optimization or test start     if (e.PropertyName == "StopTest" ||         e.PropertyName == "ResumeEnablingTogle")     {         // button accessibility switch = true         EnableMainTogles = true;         // Reset status and progress         Status = "";         Progress = 0;         // Notify the GUI of changes         dispatcher.Invoke(() =>         {             OnPropertyChanged("EnableMainTogles");             OnPropertyChanged("Status");             OnPropertyChanged("Progress");         });     }     // Changed the list of passed optimization passes     if (e.PropertyName == "AllOptimisationResults")     {         dispatcher.Invoke(() =>         {             // Clear the previously saved optimization passes and add new ones             ReportDateBorders.Clear();             foreach (var item in model.AllOptimisationResults.AllOptimisationResults.Keys)             {                 ReportDateBorders.Add(item);             }             // Select the very first date             SelectedReportDateBorder = 0;             // Fill in the fixed settings of the tester in accordance with the settings of the uploaded results             ReplaceBotFixedParam("Expert", model.AllOptimisationResults.Expert);             ReplaceBotFixedParam("Deposit", model.AllOptimisationResults.Deposit.ToString());             ReplaceBotFixedParam("Currency", model.AllOptimisationResults.Currency);             ReplaceBotFixedParam("Laverage", model.AllOptimisationResults.Laverage.ToString());             OnPropertyChanged("OptimiserSettingsForResults_fixed");         });         // Notify when data loading is complete         System.Windows.MessageBox.Show("Report params where updated");     }     // Filter or sort optimization passes     if (e.PropertyName == "SortedResults" ||         e.PropertyName == "FilteredResults")     {         dispatcher.Invoke(() =>         {             SelectedReportDateBorder = SelectedReportDateBorder;         });     }     // Updated forward optimization data     if (e.PropertyName == "ForwardOptimisations")     {         dispatcher.Invoke(() =>         {             ForwardOptimisations.Clear();             foreach (var item in model.ForwardOptimisations)             {                 ForwardOptimisations.Add(new ReportItem(item));             }         });     }     // Updated historical optimization data     if (e.PropertyName == "HistoryOptimisations")     {         dispatcher.Invoke(() =>         {             HistoryOptimisations.Clear();             foreach (var item in model.HistoryOptimisations)             {                 HistoryOptimisations.Add(new ReportItem(item));             }         });     }     // Save (*.csv) file with optimization/test results     if (e.PropertyName == "CSV")     {         System.Windows.MessageBox.Show("(*.csv) File saved");     } }

このコールバックのすべての条件は、インプットパラメータ "e "からのPropertyNameプロパティをチェックします。 テストが完了し、データモデルがGUIのロックを解除するようにリクエストされた場合、第1条件が満たされます。 この状態がトリガーになると、GUIのロックを解除し、プログレスバーの状態をリセットし、プログレス足の状態を初期値に戻します。 このイベントはセカンダリスレッドコンテキストで呼び出すことができ、グラフィック通知(OnPropertyChangedイベントコール)は常にプライマリスレッドコンテキスト、つまりGUIと同じスレッドで行われなければならないことに注意してください。 そのため、エラーを避けるためには、ディスパッチャクラスからこのイベントを呼び出すようにします。 ディスパッチャーでは、このウィンドウのスレッドコンテキストからGUIにアクセスすることができます。

次の条件は、データ・モデルが実行されたすべての最適化のリストを更新すると呼び出されます。 コンボボックスで最適化リストを選択できるようにするには、適切な最適化日を記入する必要があります。 このコード部分で行います。 また、固定テスターのパラメータを埋めます。

  • EA名
  • デポジット
  • 口座通貨
  • レバレッジ

その後、最適化パスレポートのパラメータとテーブルの更新が完了したことを通知するメッセージボックスを表示します。

フィルタリングまたはソートが完了すると、対応する条件がトリガされます。 しかし、その実装を理解するために、SelectedReportDateBorderプロパティの実装を考えてみましょう。

#region Selected optimisation date border index keeper private int _selectedReportDateBorder; public int SelectedReportDateBorder {     get => _selectedReportDateBorder;     set     {         AllOptimisations.Clear();         if (value == -1)         {             _selectedReportDateBorder = 0;             return;         }         _selectedReportDateBorder = value;         if (ReportDateBorders.Count == 0)             return;         List<OptimisationResult> collection = model.AllOptimisationResults.AllOptimisationResults[ReportDateBorders[value]];         foreach (var item in collection)         {             AllOptimisations.Add(new ReportItem(item));         }     } } #endregion

セッター部分はViewModelクラスのAllOptimisationsコレクションを更新するので、条件のコードが意味を持つようになりました。 つまり、SelectedReportDateBorderパラメータを自身に設定することで、このループの重複を回避することができます。 

Forward および Historical テーブルの更新に関連する条件は、先ほどの条件と同じ役割、つまり ViewModel と Model 間のデータ同期を果たします。 各列がプロパティで表されるテーブル行を記述するためには、対応するクラスが必要となるため、データモデルが動作する構造を直接参照することができないため、この同期化が必要となります。 このクラスは、データ・モデルで使用する構造体のラッパとして作成されます。 ReportItemクラスは、前章で検討した最適化結果を持つテーブルに使用します。

結論

この記事は、ウォークフォワード最適化とこのプロセスを実装した自動オプティマイザに焦点を当てた一連の記事の最新版です。 作成されたアプリケーションの中で最も重要な部分の構造を考察しました。 第1回目の記事では、レポートのタスクやxmlファイルへの保存を担当するアプリケーションの部分について説明しました。 第2及び第3の部分には、自動最適化装置のレポートがどのように生成されるか、及び第1の記事で説明したレポートロードプログラムのインターフェースにEAがどのように接続されるかについての説明をしました。 第4部には、プログラムの使用方法があります。これまでに、任意のロボットを自動最適化装置に接続するために必要なステップを検討しました。

第5部、第6部、第7部では、プロセスを制御するオートオプティマイザプログラムを考察しました。 グラフィカルな部分(第5回)から始めて、その操作ロジック(第6回)と関連性を検討しました(今回)。 第5回目の記事へのコメントでは、ユーザーがアプリケーションのUIについて提案を追加しました。 その中でも最も興味深いのは、すでに実装されています。

前作の記述が第一の考えであったため、現在の部分には改善点は含まれていません。 次回(直近の記事になりますが)は、指摘された改善点を記載し、自分でオプティマイザーを作成する方法を説明します。 オプティマイザーというのは、最適化を実行するロジックのことです。 現在のオプティマイザロジックについては、すでに先に検討済みです(主に第4回の記事)。 そこで、直近の記事では、似たようなロジックの作り方を解説します。 既存の最適化ロジックをベースに、自作オプティマイザーの作り方を段階的に考えていきます。

添付ファイルには、記事4で分析したトレーディングロボットを使ったオートオプティマイザープロジェクトがあります。 プロジェクトを利用するには、オートオプティマイザのプロジェクトファイルとテスト用ロボットファイルをコンパイルしてください。 その後、ReportManager.dll(最初の記事で説明しました)をMQL5/Librariesディレクトリにコピーして、EAのテストを開始します。 オートオプティマイザーとEAの接続方法については、このシリーズの中の記事3と記事4を参考にしてください。

ここでは、Visual Studioを使ったことがない方に、コンパイルのステップを説明しています。 プロジェクトはVisualStudioで様々な方法でコンパイルすることができますが、ここではそのうちの3つを紹介しています。

  1. 一番簡単なのは、CTRL+SHIFT+Bを押すことです。
  2. エディタで緑の配列をクリックすると、コードデバッグモードでアプリケーションが起動し、コンパイルが実行されます(デバッグコンパイルモードが選択されている場合)。
  3. もう一つのオプションは、メニューからBuildコマンドを使用することです。

コンパイルされたプログラムは、MetaTrader Auto Optimiser/bin/Debug(またはMetaTrader Auto Optimiser/bin/Release - 選択されたコンパイル方法に応じて)フォルダに依存します。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/7747

添付されたファイル |
Auto_Optimiser.zip (125.7 KB)
DoEasyライブラリの時系列(第39部): ライブラリに基づいた指標 - データイベントと時系列イベントの準備 DoEasyライブラリの時系列(第39部): ライブラリに基づいた指標 - データイベントと時系列イベントの準備
本稿では、DoEasyライブラリを適用して複数の銘柄の複数期間の指標を作成する方法について説明します。指標内で機能するライブラリクラスを準備し、指標のデータソースとして使用される時系列の作成をテストします。時系列イベントの作成と送信も実装します。
クロスプラットフォームグリッドEAの開発:マルチカレンシーEAのテスト クロスプラットフォームグリッドEAの開発:マルチカレンシーEAのテスト
この1か月で相場は30%以上も下落しました。(コロナショック後です。) グリッド系とマーチンゲール系のEAのテストには最適な時期のようです。 本記事は、「クロスプラットフォームのグリッドEAを作る」シリーズの無計画な続編です。 現在の相場では、グリッドEAのストレスレストを整えるチャンスとなっています。 ということで、この機会にEAのテストをしてみましょう。
DoEasyライブラリの時系列(第40部): ライブラリに基づいた指標 - 実時間でのデータ更新 DoEasyライブラリの時系列(第40部): ライブラリに基づいた指標 - 実時間でのデータ更新
本稿では、DoEasyライブラリに基づく単純な複数期間指標の開発について検討します。時系列クラスを改善して、任意の時間枠からデータを受け取り、現在のチャート期間に表示します。
連続的なウォークフォワード最適化(その6):オートオプティマイザの論理部分と構造 連続的なウォークフォワード最適化(その6):オートオプティマイザの論理部分と構造
記事3と4以前、我々は自動ウォークフォワード最適化の作成を検討しました。 今回は、オートオプティマイザツールの内部構造について進めていきます。 この記事は、作成したプロジェクトをさらに稼働したい方、修正したい方はもちろん、プログラムのロジックを理解したい方にも役立つ内容となっています。 今回の記事では、プロジェクトの内部構造とオブジェクト間の関係を示すUML図を掲載します。 また、最適化開始までの過程が記述されていますが、オプティマイザの実装過程が記述されていない状態です。