在本文中,我們將研究程序的邏輯部分如何與其圖形呈現形式聯系在一起。 我們將研究整個優化運行過程,從其開始逐層分析,直至自動優化器類的所有階段。 我們還將看到邏輯程序部分與顯示部分如何連接,並研究從應用程序代碼管理圖形的方法。 本系列的前幾篇文章:
- 連續前行優化 (第一部分): 操控優化報告
- 連續前行優化 (第二部分): 創建優化報告機器人的機理
- 連續前行優化 (第三部分): 將機器人適配為自動優化器
- 連續前行優化 (第四部分): 優化管理器(自動優化器)
- 連續前行優化 (第五部分): 自動優化器項目概述和 GUI 的創建
- 連續前行優化 (第六部分): 自動優化器的邏輯部分和結構
ViewModel 類與圖形層的交互
如前所述,ViewModel 是應用程序的圖形部分與軟件邏輯實現之間的連接器。 它是程序圖形表述,其實現應用程序邏輯調用,並針對應用程序邏輯部分的回調在圖形上做出反應。 相應地,來自 ViewModel 部分的公開屬性對應於應用程序圖形部分中的每個可編輯字段。 這些屬性可以是 getter(只讀),在這種情況下不能在圖形中更改;也可以是 setter,如此即可覆蓋隱藏在此屬性後面的對象。 在前面的部分里,我們已經詳細研究過數據綁定技術。 故此,我在這里僅提供一些示例。
文本字段是通過可讀寫權限的屬性進行連接。 舉例,考慮一個字段,該字段指示正在執行優化的資產名稱。 該字段的 XAML 標記極其簡單。
<TextBox Width="100" IsEnabled="{Binding EnableMainTogles, UpdateSourceTrigger=PropertyChanged}" Text="{Binding AssetName}"/>
除了設置文本窗口的寬度外,它還含有字段 IsEnabled 和 Text。 第一個設置該字段是否可編輯。 如果將其設置為 true,則該字段可編輯。 如果為 false,則該字段被鎖定。 “Text” 字段包含在此字段中輸入的文本。 然後,每個結構都有一對花括號。 其內容設置對象與特定公共屬性的連接,而屬性來自在 “Binding” 參數之後指定的 ViewModel 類。
後還可以跟一定數量的參數。 例如,UpdateSourceTrigger 參數指示此應用程序的圖形部分的更新方法。 在我們的示例中使用的值(PropertyChanged)表示,僅當觸發 ViewModel 類中的 OnPropertyChanged 事件時,圖形部分才會更新,並且在 “Binding” 參數之後指定傳遞的名稱(在本例中為 “EnableMainTogles”) 。
如果 “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;
以及 Text 參數:
/// <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 類關聯的所有字段都是這些類的屬性。 在上面的表格示例中,我們處理了三個類:
- ListView — System.Windows.Controls.ListView.
- GridView — System.Windows.Controls.GridView,它是從 System.Windows.Controls.ViewBase 派生的,因此可用作 ListView 類 View 屬性的初始化類。
- GridViewColumn — System.Windows.Controls.GridViewColumn.
ListView 類的 ItemsSource屬性代表由表格構成的元素集合。 將此屬性與 ViewModel 的集合連接後,我們為 Window 類提供了一種 DataContext,該 DataContext 要在表格中操作。 由於我們正在談論一個表格,因此代表集合的表格必須由含有每個表格公開屬性的類構成。 將 ItemsSource 屬性與 ViewModel 的屬性綁定在一起之後,該屬性表示一個帶有數據的表格,我們可以將每列與給定表格中的所需列值進行綁定。 此外,該表格還含有 SelectedIndex 屬性和 ViewModel 中的 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]); } } }
由於該屬性用作回調,因此(在我們的示例中)期望針對所設置的數值做出特別反應,故此 setter 必須包含此反應的實現,並作為函數。 由此,屬性值存儲在私密變量中。 若要從該變量接收數值,我們可從取值器(getter)直接訪問它。 若要設置一個值,在賦值器(setter)中將數值存儲在 "value" 變量里。'value' 變量沒有題標,在 C# 語言里作為設置數值的特定別名。 如果 “value” 大於-1,則在“結果”選項卡中填充其他相關表格,這些表格會根據所選行進行更新。 這些表格包含交易機器人參數,平均利潤,交易日的虧損,以及盈虧的最高/最低值。 需要在 “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 結構的包裝。
表格行上的所有按鈕,或在表格行上雙擊都與 ViewModel 的 Command 屬性連接,其基本類型為 ICommand。 在前面有關圖形界面創建的文章里,我們已經研究過該技術。
ViewModel 類以及與數據模型的交互
本章開始,我們先從優化開始和停止回調說起,這兩個回調在同一按鈕中合並。
單擊 StartStop 按鈕從 AutoOptimiserVM 類調用 _StartStopOptimisation 方法。 將來,有兩種備選方案:停止優化,和開始優化。 正如您從圖中可以看出,當優化器類的 IsOptimisationInProcess 屬性返回 true 時,我們將執行邏輯的第一部分,並從數據模型類中請求 StopOptimisation 方法。 然後,該方法將此調用重定向到優化器。 如果尚未啟動優化,則將調用數據模型類中的 StartOptimisation 方法。 該方法是異步的,因此即使 _StartStopOptimisation 操作完成,所調用的 Start 方法仍將繼續操作。
我們已經順着執行的方法調用研究了調用鏈條。 現在,讓我們查看描述這些方法調用與圖形部件以及Model with 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” 條件語句切分為兩條分支。 如果正在運行,則第一條停止優化過程。 否則,第二條會啟動該進程。
在啟動優化的同時,我們通過設置 EnableMainTogles = false 鎖定圖形界面的主要區域,然後繼續處理形成的輸入參數。 若要開始優化,我們需要創建一個 OptimistionInputData 結構,該結構由 OptimiserSettings、BotParams、FilterItems、SorterItems 和 DateBorders 集合填充。 利用上述的數據綁定機制,值直接從圖形界面填充入這些結構。 直至此結構成形後,我們為數據模型類的實例運行前面討論的 StartOptimisation 方法。 構造函數中的 StartStopOptimisation 屬性。
// Callback of optimization start/stop buttons StartStopOptimisation = new RelayCommand(_StartStopOptimisation);
其實例化經由實現了 ICommand 接口的 RelayCommand 類實例,該 ICommand 接口是將 ViewModel 命令與應用程序圖形部分中的 Command 屬性綁定所需的。
一旦所有優化執行完畢,並形成了“結果”選項卡中的表格(或一旦從表格里選擇一個優化結果,並利用 “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 中顯示錯誤消息。 該方法的實現已經討論過了。 不過,我們再次查看包含此回調的實例化屬性。 我們有三個不同的表格:
- 前向驗證測試,
- 曆史複盤測試,
- 所選日期範圍的優化列表。
因此,需創建三個回調。 這是正確處理每個表格數據所必需的。
/// <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; }
它們的實現是通過設置 lambda 函數來執行的:
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; }
這兩種方法具有類似的實現。 它們要檢查數據過濾參數是否存在(即表格不為空),並將它們的執行重定向到數據模型類。 數據模型類的兩種方法均將執行重定向到第一篇文章里講述的相應擴展方法。
排序方法具有以下原型簽名:
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)
這是在異步模式下執行的,以避免在執行排序時鎖定圖形(取決於數據量,這可能花費一秒鍾以上的時間)。
說到數據排序,我們查看兩個數據排序表之間的連接,以及數據過濾的實現。 在自動優化器中,“結果”選項卡和“設置”選項卡(主)都含有一個區域,該區域含有數據排序和過濾表格數據 — 這就是我們所討論的。
在上面的屏幕截圖中,該區域在優化結果選項卡中已被標記。 該思路是,如果我們在此區域添加任何排序參數,然後切換到另一個選項卡(本示例中的設置選項卡),則所添加的相同值即會出現在同一區域。 現在,如果我們從“設置”選項卡上的該區域中刪除該值,然後切換回含有優化結果的選項卡,則將看到該值也已從該選項卡中刪除。 這是因為兩個表都鏈接到同一屬性。
排序表格被鏈接到以下屬性:
/// <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 類是一個對象,用於顯示所選排序參數的表格行。 除了排序參數之外,它還包含指向列表中該特定參數刪除回調的屬性。 請注意,該回調是通過委派在外部設置的。 數據過濾器類是從 sort 類繼承的:無需重複編寫已實現的代碼,我們可簡單地從基類繼承它。 除了早先研究的參數集外,它還有帶閾值的數據比較類型和閾值本身。
如同當前實現那樣,在顯示每行的類中還有刪除方法,能夠在每行旁邊添加一個 Delete 按鈕。 它對用戶來說很方便,並且實現很有趣。 刪除方法在類之外實現。 之所以將它們設置為委派,是因為它們需要訪問位於表達 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,則滿足第一個條件。 當該情況觸發時,我們解鎖 GUI,將進度條狀態重置,並將進度條重置為初始值。 請注意,可以在輔助線程關聯中調用此事件,且圖形通知(OnPropertyChanged 事件調用)必須始終在主線程中完成,即在與 GUI 相同的線程中執行。 所以,為了避免錯誤,要從派發類調用此事件。 派發器允許從此窗口的線程里訪問 GUI。
一旦數據模型更新了所有已執行優化的列表,就會調用下一個條件。 若要通過組合框選擇優化列表,我們需要在其中填入相應的優化日期。 這是通過這部分代碼完成的。 它還為測試器填充了固定的參數:
- 智能交易系統名稱
- 存款
- 存款貨幣
- 杠杆
之後,它顯示一個 MessageBox,通知優化進程報告的參數和表格已完成更新。
一旦過濾或排序完成,就會觸發相應條件。 然而,為了理解其實現,我們來研究 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
賦值器(setter)部分更新 ViewModel 類中的 AllOptimisations 集合,故令該條件的代碼具有了意義。 換言之,把 SelectedReportDateBorder 參數設置為其自身,我們可以簡單地避免重複循環。
更新 Forward 和 Historical 表格相關的條件,與先前條件有相同的作用,即 ViewModel 和 Model 之間的數據同步。 之所以需要這種同步,是因為我們不能直接引用數據模型操作的結構,鑒於需要相應的類來描述表格行,其中每一列均由一個屬性表示。 創建這些類在數據模型中用作結構包裝器。 ReportItem 類用於優化結果表格,上一章已對此進行了討論。
結束語
本文是專門介紹前行優化和實現該過程自動優化器系列文章中的倒數第二篇。 我們已經研究了所創建應用程序最重要部分的結構。 第一篇文章介紹了應用程序中負責處理報告,並將其保存在 xml 文件里的部分。 第二部分和第三部分包含如何生成自動優化器報告,以及如何將 EA 與報告加載程序接口相連接的講述,這在第一篇文章中已有介紹。 第四部分包含程序使用說明:在那時,我們已經研究了將任何機器人連接到自動優化器的必需步驟。
在第五、第六和第七部分里,我們研究了自動優化器程序的控制過程。 我們從其圖形部分(第五篇)開始,然後研究其操作邏輯(第六篇),及其之間的聯系(本文)。 在第五篇文章的評論中,用戶添加了有關應用程序 UI 的一些建議。 它們當中最有趣的已被實現。
當前部分不包含這些改進,因為主要思想是講述之前的工作。 下一篇文章(將是最後一篇文章)將包含所示意的改進,並將提供有關如何自行創建優化器的講述。 通過優化器,我的意思是運行優化的邏輯。 當前的優化器邏輯已在前面進行了討論(主要在第四篇文章當中)。 因此,最後一篇文章將提供有關如何創建類似邏輯的說明。 我們將利用現有的優化邏輯為基礎,並研究如何逐步自行創建優化器。
附件包含第四篇文章中所分析的擁有交易機器人的自動優化器項目。 若要使用該項目,請編譯自動優化器項目文件,和測試機器人文件。 然後將 ReportManager.dll(在第一篇文章中講述)複制到 MQL5/Libraries 目錄,您便可以開始測試 EA。 有關如何將自動優化器與您的智能交易系統相鏈接的詳細信息,請參閱本系列文章的第三、四篇。
這是針對所有未曾用過 Visual Studio 的人員提供的編譯過程說明。 可以在 Visual Studio 中以不同的方式編譯項目,以下是其中三種:
- 最簡單的是按 CTRL+SHIFT+B 組合鍵。
- 一種更直觀的方法是在編輯器中單擊綠色箭頭 — 這將以代碼調試模式啟動應用程序,並執行編譯(如果選擇了調試編譯模式)。
- 另一個選擇是利用菜單中的 Build 命令。
然後,取決於所選的編譯方法,已編譯程序將保存在文件夾 MetaTrader Auto Optimiser/bin/Debug,或 MetaTrader Auto Optimiser/bin/Release 之內。