股票量化交易軟件:連續(xù)前行優(yōu)化第八部分程序改進(jìn)和修復(fù)
添加日期自動(dòng)完成
以前的程序版本分階段輸入日期,從而進(jìn)行前行和歷史優(yōu)化,這很不方便。 而這一回,我實(shí)現(xiàn)了所需時(shí)間范圍的自動(dòng)輸入。 功能的細(xì)節(jié)可以描述如下。 所選時(shí)間間隔應(yīng)自動(dòng)分為前行優(yōu)化和歷史優(yōu)化。 兩種優(yōu)化類型的步驟都是固定的,并在間隔拆分之前已設(shè)置完畢。 每個(gè)新的前行范圍必須在上一個(gè)范圍之后的第二天開始。 歷史間隔的偏移(重疊)等于前行窗口的步長(zhǎng)。 與歷史優(yōu)化不同,前行優(yōu)化不會(huì)重疊,它們實(shí)現(xiàn)了連續(xù)的交易歷史。
為了實(shí)現(xiàn)該任務(wù),我決定將此功能轉(zhuǎn)移到一個(gè)單獨(dú)的圖形窗口之中,并令其獨(dú)立于主界面,彼此不直接相關(guān)。 結(jié)果就是,赫茲股票量化得到以下對(duì)象層次結(jié)構(gòu)。

赫茲股票量化來研究一下此功能如何連接,并查看其實(shí)現(xiàn)示例。 赫茲股票量化從創(chuàng)建擴(kuò)展的圖形界面開始,即,圖表上的所有內(nèi)容來自 AutoFillInDateBorders 對(duì)象,該對(duì)象代表圖形窗口,及以下。 該圖片示意 GUI 元素,XAML 標(biāo)記,以及由 AutoFillInDateBordersVM 類呈現(xiàn)的 ViewModel 部分中的字段。

如您所見,GUI 包括三個(gè)主要部分。 其中包括兩個(gè)日歷,用來輸入優(yōu)化期開始和結(jié)束日期;指定前行和歷史間隔邊界的表格;以及 “Set” 按鈕,單擊該按鈕會(huì)將指定范圍劃分為相應(yīng)的歷史和前行窗口。 屏幕截圖中的表格包含重復(fù)的三行,實(shí)際上只有兩行:第一行負(fù)責(zé)歷史日期范圍,第二行設(shè)置前行范圍。
表格中的 “Value” 是相應(yīng)優(yōu)化類型的步數(shù),以天為單位的。 例如,如果歷史間隔的值是 360 天,而前行值是 90,則意味著日歷中指定的時(shí)間間隔將分為 360 天的歷史優(yōu)化間隔,和 90 天的前行間隔。 每個(gè)下一個(gè)歷史優(yōu)化窗口的開始將依據(jù)前行間隔步數(shù)平移。
class AutoFillInDateBordersM : IAutoFillInDateBordersM
{
? ?private AutoFillInDateBordersM() { }
? ?private static AutoFillInDateBordersM instance;
? ?public static AutoFillInDateBordersM Instance()
? ?{
? ? ? ?if (instance == null)
? ? ? ? ? ?instance = new AutoFillInDateBordersM();
? ? ? ?return instance;
? ?}
? ?public event Action<List<KeyValuePair<OptimisationType, DateTime[]>>> DateBorders;
? ?public void Calculate(DateTime From, DateTime Till, uint history, uint forward)
? ?{
? ? ? ?if (From >= Till)
? ? ? ? ? ?throw new ArgumentException("Date From must be less then date Till");
? ? ? ?List<KeyValuePair<OptimisationType, DateTime[]>> data = new List<KeyValuePair<OptimisationType, DateTime[]>>();
? ? ? ?OptimisationType type = OptimisationType.History;
? ? ? ?DateTime _history = From;
? ? ? ?DateTime _forward = From.AddDays(history + 1);
? ? ? ?DateTime CalcEndDate()
? ? ? ?{
? ? ? ? ? ?return type == OptimisationType.History ? _history.AddDays(history) : _forward.AddDays(forward);
? ? ? ?}
?
? ? ? ?while (CalcEndDate() <= Till)
? ? ? ?{
? ? ? ? ? ?DateTime from = type == OptimisationType.History ? _history : _forward;
? ? ? ? ? ?data.Add(new KeyValuePair<OptimisationType, DateTime[]>(type, new DateTime[2] { from, CalcEndDate() }));
? ? ? ? ? ?if (type == OptimisationType.History)
? ? ? ? ? ? ? ?_history = _history.AddDays(forward + 1);
? ? ? ? ? ?else
? ? ? ? ? ? ? ?_forward = _forward.AddDays(forward + 1);
? ? ? ? ? ?type = type == OptimisationType.History ? OptimisationType.Forward : OptimisationType.History;
? ? ? ?}
? ? ? ?if (data.Count == 0)
? ? ? ? ? ?throw new ArgumentException("Can`t create any date borders with set In sample (History) step");
? ? ? ?DateBorders?.Invoke(data);
? ?}
}
窗口數(shù)據(jù)的模型類是運(yùn)用單例范式(Singletone pattern)編寫的對(duì)象。 這樣可以繞開擴(kuò)展的圖形窗口,令主窗口的 ViewModel 部分與數(shù)據(jù)模型進(jìn)行交互。 在有趣的方法當(dāng)中,對(duì)象僅包含“Calculate” ,用來計(jì)算日期范圍,并在完成上述過程后調(diào)用 事件。 事件接收一對(duì)數(shù)值集合作為參數(shù),其中鍵值是所分析間隔的類型(前行或歷史優(yōu)化),而其值是一個(gè)包含兩個(gè) DateTime 值的數(shù)組。 第一個(gè)表示所選間隔的開始日期,而第二個(gè)表示結(jié)束日期。
該方法會(huì)在一個(gè)循環(huán)中計(jì)算日期范圍,備選是更改計(jì)算窗口的類型(前行或歷史)。 首先,歷史窗口類型設(shè)置為所有計(jì)算的起點(diǎn)。 在循環(huán)開始之前還設(shè)置了每種窗口類型的初始日期值。 在循環(huán)的每次迭代中,使用嵌套函數(shù)計(jì)算所選窗口類型的邊界極值,然后依據(jù)極值范圍日期驗(yàn)證該值。 如果日期超界,那么此為循環(huán)退出條件。 優(yōu)化窗口范圍是在循環(huán)里形成的。 然后,更新下一個(gè)窗口開始日期和窗口類型切換器。
所有操作之后,如果未發(fā)生任何錯(cuò)誤,則利用所傳遞日期范圍調(diào)用事件。 所有進(jìn)一步的動(dòng)作均由類來執(zhí)行。 按下 “Set” 按鈕回調(diào)可啟動(dòng)上述方法的執(zhí)行。
為赫茲股票量化的擴(kuò)展而建立的數(shù)據(jù)模型工廠以最簡(jiǎn)單的方式實(shí)現(xiàn):
class AutoFillInDateBordersCreator
{
? ?public static IAutoFillInDateBordersM Model => AutoFillInDateBordersM.Instance();
}
基本上,當(dāng)我們調(diào)用 “Model” 靜態(tài)屬性時(shí),我們持續(xù)引用數(shù)據(jù)模型對(duì)象的同一實(shí)例,然后將其強(qiáng)制轉(zhuǎn)換為接口類型。 我們?cè)谥鞔翱诘?ViewModel 部分中用到此事實(shí)。
public AutoOptimiserVM()
{
? ?...
? ?AutoFillInDateBordersCreator.Model.DateBorders += Model_DateBorders;
? ?....
}
~AutoOptimiserVM()
{
? ?...
? ?AutoFillInDateBordersCreator.Model.DateBorders -= Model_DateBorders;
? ?....
}
在主窗口 ViewModel 對(duì)象的構(gòu)造函數(shù)和析構(gòu)函數(shù)之中,赫茲股票量化都可不用存儲(chǔ)指向該類實(shí)例的指針,但調(diào)用它則要通過靜態(tài)數(shù)據(jù)模型工廠。 請(qǐng)注意,主窗口的 ViewModel 部分實(shí)際上配合所研究的類一起操作,但無需知道該類是這樣操作的。 因?yàn)樵陬悩?gòu)造函數(shù)和析構(gòu)函數(shù)中之外,其他任何地方都未提及引用了該對(duì)象。 訂閱所提到的事件后,在回調(diào)時(shí),首先清空所有先前輸入的日期范圍,然后在循環(huán)中添加經(jīng)事件傳遞來的新日期范圍,一次一個(gè)。 在集合中添加日期范圍的方法也已在主圖形界面的 ViewModel 端實(shí)現(xiàn)。 看起來像這樣:
void _AddDateBorder(DateTime From, DateTime Till, OptimisationType DateBorderType)
{ ? ?
? ?try
? ?{
? ? ? ?DateBorders border = new DateBorders(From, Till);
? ? ? ?if (!DateBorders.Where(x => x.BorderType == DateBorderType).Any(y => y.DateBorders == border))
? ? ? ?{
? ? ? ? ? ?DateBorders.Add(new DateBordersItem(border, _DeleteDateBorder, DateBorderType));
? ? ? ?}
? ?}
? ?catch (Exception e)
? ?{
? ? ? ?System.Windows.MessageBox.Show(e.Message);
? ?}
}
DateBorder 對(duì)象的創(chuàng)建包裝在 “try-catch” 構(gòu)造當(dāng)中。 這樣做是因?yàn)閷?duì)象構(gòu)造函數(shù)里可能會(huì)發(fā)生異常,且必須以某種方式處理它。 我還添加了 ClearDateBorders 方法:
ClearDateBorders = new RelayCommand((object o) =>
{
? ?DateBorders.Clear();
});
它可以快速刪除所有輸入的日期范圍。 在以前的版本中,每個(gè)日期都需要分別刪除,這對(duì)于大量日期而言是不便的。 在之前存在的日期范圍控制的相同代碼行中添加了 GUI 主窗口按鈕調(diào)用所講述的新創(chuàng)內(nèi)容。

單擊 “Autoset” 將觸發(fā)一次回調(diào),它調(diào)用 SubFormKeeper 類實(shí)例之中的 Open 方法。 該類被編寫為包裝器,其中封裝嵌套的窗口創(chuàng)建過程。 這消除了主窗口 ViewModel 中不必要的屬性和字段,并防止赫茲股票量化直接訪問已創(chuàng)建的輔助窗口,因?yàn)楸静辉撝苯舆M(jìn)行交互。
class SubFormKeeper
{
? ?public SubFormKeeper(Func<Window> createWindow, Action<Window> subscribe_events = null, Action<Window> unSubscribe_events = null);
? ?public void Open();
? ?public void Close();
}
如果您查看類代碼,則可從公開方法中看到它提供了確切的可能性集合。 進(jìn)而,所有輔助自動(dòng)優(yōu)化器窗口都將包裝在此特定類當(dāng)中。
函數(shù)庫(kù)中操控優(yōu)化結(jié)果的新功能和錯(cuò)誤修復(fù)
本文的此部分講述處理優(yōu)化報(bào)告函數(shù)庫(kù)中的修改 - “ReportManager.dll”。 除了引入自定義系數(shù)外,新功能還可以更快地從終端卸載優(yōu)化報(bào)告。 它還修復(fù)了數(shù)據(jù)排序中的錯(cuò)誤。
引入一個(gè)自定義優(yōu)化系數(shù)
前幾篇文章的評(píng)論中有一項(xiàng)改進(jìn)建議,就是能夠采用自定義系數(shù)來過濾優(yōu)化結(jié)果。 為了實(shí)現(xiàn)這個(gè)選項(xiàng),我必須對(duì)現(xiàn)有對(duì)象進(jìn)行一些修改。 無論如何,為了支持舊報(bào)表,讀取優(yōu)化數(shù)據(jù)的類既可與含有自定義系數(shù)的報(bào)表一起操作,也可與程序的早期版本中生成的報(bào)表一起操作。 因此,報(bào)告格式保持不變。 它有一個(gè)附加參數(shù) - 一個(gè)用于指定自定義系數(shù)的字段。
現(xiàn)在,“ SortBy” 枚舉含有新參數(shù) “Custom”,并已將相應(yīng)的字段添加到 “Coefficients” 結(jié)構(gòu)之中。 這會(huì)將系數(shù)添加到負(fù)責(zé)存儲(chǔ)數(shù)據(jù)的對(duì)象當(dāng)中,但不會(huì)將其添加到卸載和讀取數(shù)據(jù)的對(duì)象之中。 數(shù)據(jù)寫入是通過兩種方法執(zhí)行的,和一個(gè)擁有靜態(tài)方法的類,它是為了從 MQL5 中保存報(bào)告。
public static void AppendMainCoef(double customCoef,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?double payoff,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?double profitFactor,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?double averageProfitFactor,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?double recoveryFactor,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?double averageRecoveryFactor,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?int totalTrades,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?double pl,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?double dd,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?double altmanZScore)
{
? ?ReportItem.OptimisationCoefficients.Custom = customCoef;
? ?...
}
首先,將標(biāo)識(shí)自定義系數(shù)的新參數(shù)添加到 AppendMainCoef 方法當(dāng)中。 然后,像其他傳遞的系數(shù)一樣,將其添加到 ReportWriter.ReportItem 結(jié)構(gòu)之中。 現(xiàn)在,如果您嘗試?yán)眯碌?“ReportManager.dll” 函數(shù)庫(kù)編譯舊項(xiàng)目,則會(huì)出現(xiàn)異常,因?yàn)?AppendMainCoef 方法代碼已有變化。 可稍微編輯卸載數(shù)據(jù)的對(duì)象來解決此錯(cuò)誤 - 赫茲股票量化稍后將繼續(xù)討論 MQL5 代碼。
為了能夠正確編譯當(dāng)前的 dll 版本,請(qǐng)用本文下面附帶的新代碼替換 Include 目錄中的 “History Manager”,如此足以在編譯機(jī)器人時(shí)兼容新、舊方法。
另外,我還修改了 Write 方法的代碼,該方法現(xiàn)在不會(huì)引發(fā)異常,但會(huì)返回錯(cuò)誤消息。 這樣做是因?yàn)樵摮绦虿辉偈褂妹コ怏w,該互斥體明顯減慢了數(shù)據(jù)卸載過程,但是在舊版本的卸載類中必需用其生成報(bào)告。 不過,我尚未刪除使用互斥體寫入數(shù)據(jù)的方法,以便保持與先前實(shí)現(xiàn)的數(shù)據(jù)導(dǎo)出格式的兼容性。
為了讓新記錄出現(xiàn)在報(bào)告文件中,我們需要?jiǎng)?chuàng)建一個(gè)新的 <Item/> 標(biāo)記,其 Name 屬性等于 “Custom”。
WriteItem(xmlDoc, xpath, "Item", ReportItem.OptimisationCoefficients.Custom.ToString(), new Dictionary<string, string> { { "Name", "Custom" } });
另一種修改的方法是 OptimisationResultsExtentions.ReportWriter:在此處添加了類似的代碼行,該代碼行加入了帶有自定義系數(shù)參數(shù)的 <Item/> 標(biāo)簽。
現(xiàn)在,赫茲股票量化研究將自定義系數(shù)添加到數(shù)據(jù)和 MQL 機(jī)器人代碼當(dāng)中。 首先,我們研究舊版本的數(shù)據(jù)下載功能,其中與 ReportWriter 類一起操作的代碼位于 XmlHistoryWriter.mqh 文件的 CXmlHistoryWriter 類當(dāng)中。 創(chuàng)建了以下代碼的引用,以便支持自定義系數(shù):
typedef double(*TCustomFilter)();
上述類中的 “private” 字段存儲(chǔ)此函數(shù)。
class CXmlHistoryWriter
?{
private:
? const string ? ? ?_path_to_file,_mutex_name;
? CReportCreator ? ?_report_manager;
? TCustomFilter ? ? custom_filter;
? void ? ? ? ? ? ? ?append_bot_params(const BotParams ?¶ms[]);//
? void ? ? ? ? ? ? ?append_main_coef(PL_detales &pl_detales,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?TotalResult &totalResult);//
? //double ? ? ? ? ? ?get_average_coef(CoefChartType type);
? void ? ? ? ? ? ? ?insert_day(PLDrawdown &day,ENUM_DAY_OF_WEEK day);//
? void ? ? ? ? ? ? ?append_days_pl();//
public:
? ? ? ? ? ? ? ? ? ? CXmlHistoryWriter(string file_name,string mutex_name,
? ? ? ? ? ? ? ? ? ? CCCM *_comission_manager, TCustomFilter filter);//
? ? ? ? ? ? ? ? ? ? CXmlHistoryWriter(string mutex_name,CCCM *_comission_manager, TCustomFilter filter);
? ? ? ? ? ? ? ? ? ?~CXmlHistoryWriter(void) {_report_manager.Clear();} //
? void ? ? ? ? ? ? ?Write(const BotParams ¶ms[],datetime start_test,datetime end_test);//
?};
該“private” 字段的值是從類的構(gòu)造函數(shù)中填充的。 進(jìn)而,在 append_main_coef 方法中,當(dāng)從 dll 庫(kù)調(diào)用 “ReportWriter::AppendMainCoef” 靜態(tài)方法時(shí),通過其指針調(diào)用所傳遞的函數(shù),并接收自定義系數(shù)值。