股票量化交易軟件:繼續(xù)漫步優(yōu)化2為任意機(jī)器人創(chuàng)建優(yōu)化報告的機(jī)制

這是致力于創(chuàng)建自動優(yōu)化器的系列文章中的下一篇,該優(yōu)化器可以執(zhí)行交易策略的漫步優(yōu)化。 上一篇文章描述過如何創(chuàng)建 DLL,并運(yùn)用在赫茲股票量化的自動優(yōu)化器和 EA 之中。 這部分新內(nèi)容則完全致力于 赫茲股票量化語言。 我們將研究優(yōu)化報告的生成方法,以及在您的算法中該功能的應(yīng)用。

策略測試器不允許從智能交易系統(tǒng)中訪問其數(shù)據(jù),而其提供的結(jié)果缺乏細(xì)節(jié),所以,赫茲股票量化將利用我在之前文章中實(shí)現(xiàn)的優(yōu)化報告下載功能。 由于此功能的各個獨(dú)立部分均被修改,而其他內(nèi)容在先前的文章中并未完全涵蓋,因此我們再次研究這些功能,因?yàn)樵谖覀兊某绦蚶锼鼈兪顷P(guān)鍵的構(gòu)成部分。 我們從新功能之一開始:添加自定義傭金。 本文中描述的所有類和函數(shù)都位于 Include/History manager 目錄之下。
自定義傭金和滑點(diǎn)的實(shí)現(xiàn)
赫茲股票量化平臺測試器提供了許多令人興奮的可能性。 然而,某些經(jīng)紀(jì)商未將交易傭金添加到歷史記錄中。 進(jìn)而,有時您也許要附加傭金以便進(jìn)行額外的策略測試。 為此目的,我已新加了一個類,可為每個單獨(dú)的品種保存?zhèn)蚪稹?調(diào)用相應(yīng)的方法后,該類將返回傭金和指定的滑點(diǎn)。 該類本身清單如下:
class CCCM ?{ private: ? struct Keeper ? ? { ? ? ?string ? ? ? ? ? ?symbol; ? ? ?double ? ? ? ? ? ?comission; ? ? ?double ? ? ? ? ? ?shift; ? ? }; ? Keeper ? ? ? ? ? ?comission_data[]; public: ? void ? ? ? ? ? ? ?add(string symbol,double comission,double shift); ? double ? ? ? ? ? ?get(string symbol,double price,double volume); ? void ? ? ? ? ? ? ?remove(string symbol); ?};
已為該類創(chuàng)建了 Keeper 結(jié)構(gòu),該結(jié)構(gòu)存儲指定資產(chǎn)的傭金和滑點(diǎn)。 已創(chuàng)建一個數(shù)組來存儲所有傳入的傭金和滑點(diǎn)值。 聲明三個方法:添加、接收和刪除數(shù)據(jù)。 資產(chǎn)加入方法實(shí)現(xiàn)如下:
void CCCM::add(string symbol,double comission,double shift) { int s=ArraySize(comission_data); for(int i=0;i<s;i++) ? { ? ?if(comission_data[i].symbol==symbol) ? ? ? ?return; ? } ArrayResize(comission_data,s+1,s+1); Keeper keeper; keeper.symbol=symbol; keeper.comission=MathAbs(comission); keeper.shift=MathAbs(shift); comission_data[s]=keeper; }
此方法實(shí)現(xiàn)了初步檢查是否早前已添加同一資產(chǎn),之后會根據(jù)結(jié)果向集合中添加新資產(chǎn)。 請注意,滑點(diǎn)和傭金已添加模量化。 因此,當(dāng)所有成本累加時,數(shù)值符號將不會影響計(jì)算。 另一點(diǎn)要注意的是計(jì)量單位。
傭金:根據(jù)資產(chǎn)類型,傭金可以按獲利貨幣、或交易量的百分比加收。
滑點(diǎn):始終按點(diǎn)數(shù)為單位指定。
還請注意,加收數(shù)值并非覆蓋每筆完整倉位(即開倉+平倉),而是每次交易(每次開倉或平倉)都要加收。 因此,倉位將含有以下數(shù)值:n*傭金 + n*滑點(diǎn),其中 n 是一筆倉位之中所有成交的次數(shù)。
remove 方法刪除所選資產(chǎn)。 品種名作為關(guān)鍵字。
void CCCM::remove(string symbol) { int total=ArraySize(comission_data); int ind=-1; for(int i=0;i<total;i++) ? { ? ?if(comission_data[i].symbol==symbol) ? ? ?{ ? ? ? ind=i; ? ? ? break; ? ? ?} ? } if(ind!=-1) ? ?ArrayRemove(comission_data,ind,1); }
如果找不到相應(yīng)的品種,則該方法不會刪除任何資產(chǎn),并終止。
get 方法用于獲取所選的偏移值和傭金。 針對不同的資產(chǎn)類型,該方法的實(shí)現(xiàn)方式是不同的。
double CCCM::get(string symbol,double price,double volume) { int total=ArraySize(comission_data); for(int i=0;i<total;i++) ? { ? ?if(comission_data[i].symbol==symbol) ? ? ?{ ? ? ? ENUM_SYMBOL_CALC_MODE mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_CALC_MODE); ? ? ? double shift=comission_data[i].shift*SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE); ? ? ? double ans; ? ? ? switch(mode) ? ? ? ? { ? ? ? ? ?case SYMBOL_CALC_MODE_FOREX : ? ? ? ? ? ? ans=(comission_data[i].comission+shift)*volume; ? ? ? ? ? ? break; ? ? ? ? ?case SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE : ? ? ? ? ? ? ans=(comission_data[i].comission+shift)*volume; ? ? ? ? ? ? break; ? ? ? ? ?case SYMBOL_CALC_MODE_FUTURES : ? ? ? ? ? ? ans=(comission_data[i].comission+shift)*volume; ? ? ? ? ? ? break; ? ? ? ? ?case SYMBOL_CALC_MODE_CFD : ? ? ? ? ? ? ans=(comission_data[i].comission+shift)*volume; ? ? ? ? ? ? break; ? ? ? ? ?case SYMBOL_CALC_MODE_CFDINDEX : ? ? ? ? ? ? ans=(comission_data[i].comission+shift)*volume; ? ? ? ? ? ? break; ? ? ? ? ?case SYMBOL_CALC_MODE_CFDLEVERAGE : ? ? ? ? ? ? ans=(comission_data[i].comission+shift)*volume; ? ? ? ? ? ? break; ? ? ? ? ?case SYMBOL_CALC_MODE_EXCH_STOCKS : ? ? ? ? ? ?{ ? ? ? ? ? ? double trading_volume=price*volume*SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE); ? ? ? ? ? ? ans=trading_volume*comission_data[i].comission/100+shift*volume; ? ? ? ? ? ?} ? ? ? ? ?break; ? ? ? ? ?case SYMBOL_CALC_MODE_EXCH_FUTURES : ? ? ? ? ? ? ans=(comission_data[i].comission+shift)*volume; ? ? ? ? ? ? break; ? ? ? ? ?case SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS : ? ? ? ? ? ? ans=(comission_data[i].comission+shift)*volume; ? ? ? ? ? ? break; ? ? ? ? ?case SYMBOL_CALC_MODE_EXCH_BONDS : ? ? ? ? ? ?{ ? ? ? ? ? ? double trading_volume=price*volume*SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE); ? ? ? ? ? ? ans=trading_volume*comission_data[i].comission/100+shift*volume; ? ? ? ? ? ?} ? ? ? ? ?break; ? ? ? ? ?case SYMBOL_CALC_MODE_EXCH_STOCKS_MOEX : ? ? ? ? ? ?{ ? ? ? ? ? ? double trading_volume=price*volume*SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE); ? ? ? ? ? ? ans=trading_volume*comission_data[i].comission/100+shift*volume; ? ? ? ? ? ?} ? ? ? ? ?break; ? ? ? ? ?case SYMBOL_CALC_MODE_EXCH_BONDS_MOEX : ? ? ? ? ? ?{ ? ? ? ? ? ? double trading_volume=price*volume*SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE); ? ? ? ? ? ? ans=trading_volume*comission_data[i].comission/100+shift*volume; ? ? ? ? ? ?} ? ? ? ? ?break; ? ? ? ? ?case SYMBOL_CALC_MODE_SERV_COLLATERAL : ? ? ? ? ? ? ans=(comission_data[i].comission+shift)*volume; ? ? ? ? ? ? break; ? ? ? ? ?default: ans=0; break; ? ? ? ? } ? ? ? if(ans!=0) ? ? ? ? ?return -ans; ? ? ?} ? } return 0; }
在數(shù)組中搜索指定的品種。 鑒于針對不同的品種類型要運(yùn)用不同的傭金計(jì)算類型,因此為傭金設(shè)置的類型也有所區(qū)別。 例如,將股票和債券傭金設(shè)置為營業(yè)額的百分比,而將計(jì)算出的營業(yè)額設(shè)為手?jǐn)?shù)乘以每手的合約數(shù)量和成交價格。
結(jié)果就是,赫茲股票量化得到了所執(zhí)行操作的貨幣等價值。 該方法的執(zhí)行結(jié)果始終是傭金和滑點(diǎn)的總和(以貨幣計(jì))。 滑點(diǎn)是根據(jù)即時報價值計(jì)算的。 進(jìn)而,所描述的類會在接下來的下載報告類中用到。 每種資產(chǎn)的傭金參數(shù)可以是硬編碼的,也可以從數(shù)據(jù)庫中自動請求; 亦或,可以將其作為輸入傳遞給 EA。 在我的算法中,我首選后一種方法。
在 CDealHistoryGetter 類里的創(chuàng)新
在本部分里進(jìn)一步討論的類,曾在之前的文章中提到過。 這就是為什么我不會深入討論早前所討論的類。 然而,我嘗試全面論述新類,因?yàn)榻灰讏蟾嫦螺d算法中的關(guān)鍵算法是創(chuàng)建所下載的報告。 ?
我們從 CDealHistoryGetter 類開始,自第一篇 文章以來,該類的運(yùn)用有了一些修改。 第一篇文章主要致力于闡述這個類。 最新版本隨附于后。 它包括一些新功能,意即次要的修復(fù)。 在第一篇文章中,曾以易于閱讀的形式詳細(xì)論述過下載報告的機(jī)制。 在本文中,我們將更詳細(xì)地研究向報告里添加傭金和滑點(diǎn)。 根據(jù) OOP(面向?qū)ο缶幊蹋┰瓌t,這意味著一個對象必須執(zhí)行一個特定的指定用途,創(chuàng)建該對象是為了接收所有類型的交易報告結(jié)果。 它包含以下公開方法,每個方法都扮演其特定角色:
getHistory — 此方法允許下載按倉位分組的交易歷史記錄。 如果我們在循環(huán)里用標(biāo)準(zhǔn)方法下載交易歷史記錄,且不用任何過濾器,則我們會收到按照 DealData 結(jié)構(gòu)呈現(xiàn)的交易描述:
struct DealData ?{ ? long ? ? ? ? ? ? ?ticket; ? ? ? ?// Deal ticket ? long ? ? ? ? ? ? ?order; ? ? ? ? // The number of the order that opened the position ? datetime ? ? ? ? ?DT; ? ? ? ? ? ?// Position open date ? long ? ? ? ? ? ? ?DT_msc; ? ? ? ?// Position open date in milliseconds ? ENUM_DEAL_TYPE ? ?type; ? ? ? ? ?// Open position type ? ENUM_DEAL_ENTRY ? entry; ? ? ? ? // Position entry type ? long ? ? ? ? ? ? ?magic; ? ? ? ? // Unique position number ? ENUM_DEAL_REASON ?reason; ? ? ? ?// Order placing reason ? long ? ? ? ? ? ? ?ID; ? ? ? ? ? ?// Position ID ? double ? ? ? ? ? ?volume; ? ? ? ?// Position volume (lots) ? double ? ? ? ? ? ?price; ? ? ? ? // Position entry price ? double ? ? ? ? ? ?comission; ? ? // Commission paid ? double ? ? ? ? ? ?swap; ? ? ? ? ?// Swap ? double ? ? ? ? ? ?profit; ? ? ? ?// Profit / loss ? string ? ? ? ? ? ?symbol; ? ? ? ?// Symbol ? string ? ? ? ? ? ?comment; ? ? ? // Comment specified when at opening ? string ? ? ? ? ? ?ID_external; ? // External ID ?};
收到的數(shù)據(jù)將按開倉時間排序,且不會以其他任何方式分組。 本篇文章包含一些示例,展示出以這種形式讀取報告的困難之處,因?yàn)槿糇裾斩喾N算法交易時,交易之間可能會發(fā)生混淆。 尤其是當(dāng)您運(yùn)用持倉遞增技術(shù)時,會根據(jù)底層算法為資產(chǎn)追加多頭或空頭倉位。 結(jié)果則是,赫茲股票量化得到了大量的入場和出場成交,而這些并不能反映出全面情況。
赫茲股票量化的方法是按倉位對這些成交進(jìn)行分組。 盡管訂單十分混亂,但我們會把不涉及所分析倉位的不必要成交剔除。 結(jié)果會按照如上所示的成交結(jié)構(gòu)保存在結(jié)構(gòu)數(shù)組當(dāng)中。 ?
struct DealKeeper ?{ ? DealData ? ? ? ? ?deals[]; /* List of all deals for this position ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(or several positions in case of position reversal)*/ ? string ? ? ? ? ? ?symbol; ?// Symbol ? long ? ? ? ? ? ? ?ID; ? ? ?// ID of the position (s) ? datetime ? ? ? ? ?DT_min; ?// Open date (or the date of the very first position) ? datetime ? ? ? ? ?DT_max; ?// Close date ?};
請注意,該類不會在分組中考慮魔幻數(shù)字,因?yàn)楫?dāng)一筆持倉由兩個或多個算法進(jìn)行交易時,通常會交錯出現(xiàn)不同數(shù)字。 至少在莫斯科交易所,完全分離從技術(shù)層面是不可能的,故而我主要為此編寫算法。 此外,該工具設(shè)計(jì)用于下載的交易結(jié)果,或測試/優(yōu)化結(jié)果。 在第一種情況下,所選品種的統(tǒng)計(jì)信息就足夠了;而在第二種情況下,魔幻數(shù)字并不重要,因?yàn)椴呗詼y試器每次運(yùn)行只采用一種算法。
自第一篇文章以來,方法核心的實(shí)現(xiàn)未發(fā)生變化。 如今,赫茲股票量化將自定義傭金加入其內(nèi)。 為此任務(wù),上面討論的 CCCM 類會通過引用傳遞給類構(gòu)造函數(shù),并將其保存在相應(yīng)的字段中。 然后,在填充 DealData 結(jié)構(gòu)時,即在填充傭金時,在所傳遞 CCCM 類中的自定義傭金會被保存。
#ifndef ONLY_CUSTOM_COMISSION ? ? ? ? ? ? ? if(data.comission==0 && comission_manager != NULL) ? ? ? ? ? ? ? ? { ? ? ? ? ? ? ? ? ?data.comission=comission_manager.get(data.symbol,data.price,data.volume); ? ? ? ? ? ? ? ? } #else ? ? ? ? ? ? ? data.comission=comission_manager.get(data.symbol,data.price,data.volume); #endif
所添加的傭金是有針對性的和有條件的。 在機(jī)器人中,如果該類之前讀取過我們在文件里定義的 ONLY_CUSTOM_COMISSION 參數(shù),則傭金字段將始終包含所傳遞的傭金,取代經(jīng)紀(jì)商提供的數(shù)值。 如果未定義此參數(shù),則將有條件地添加所傳遞傭金:僅在經(jīng)紀(jì)商未提供該值報價的情況下。 在所有其他情況下,用戶傭金值將被忽略。
getIDArr — 返回在請求的時間范圍內(nèi)為所有交易品種的開倉 ID 數(shù)組。 倉位 ID 可以將所有成交合并到我們方法中的倉位。 實(shí)際上,這是 DealData.ID 字段的唯一列表。
getDealsDetales — 該方法類似于 getHistory,但是它提供的細(xì)節(jié)較少。 該方法的思路是提供一種易于閱讀的倉位列表,其中每一行對應(yīng)一筆特定的成交。 每筆倉位由以下結(jié)構(gòu)描述: struct DealDetales ? { ? ?string ? ? ? ? ? ?symbol; ? ? ? ?// Symbol datetime ? ? ? ? ?DT_open; ? ? ? // Open date ENUM_DAY_OF_WEEK ?day_open; ? ? ?// Open day datetime ? ? ? ? ?DT_close; ? ? ?// Cloe date ENUM_DAY_OF_WEEK ?day_close; ? ? // Close day double ? ? ? ? ? ?volume; ? ? ? ?// Volume (lots) bool ? ? ? ? ? ? ?isLong; ? ? ? ?// Long/Short double ? ? ? ? ? ?price_in; ? ? ?// Position entry price double ? ? ? ? ? ?price_out; ? ? // Position exit price double ? ? ? ? ? ?pl_oneLot; ? ? // Profit / loss is trading one lot double ? ? ? ? ? ?pl_forDeal; ? ?// Real profit/loss taking into account commission string ? ? ? ? ? ?open_comment; ?// Comment at the time of opening string ? ? ? ? ? ?close_comment; // Comment at the time of closing ? }; ? 它們代表按平倉日期排序的倉位表。 這些數(shù)值的數(shù)組將在接下來的類中用于計(jì)算系數(shù)。 另外,赫茲股票量化還將得到基于所示數(shù)據(jù)得最終測試報告。 甚而,基于此類數(shù)據(jù),測試器可以在交易后創(chuàng)建盈虧曲線圖。 對于測試器,請注意,在進(jìn)一步的計(jì)算中,終端計(jì)算出的恢復(fù)因子會與根據(jù)接收數(shù)據(jù)計(jì)算出的恢復(fù)因子有所不同。 這一事實(shí)出于盡管數(shù)據(jù)下載正確,且計(jì)算公式相同,但源數(shù)據(jù)卻不同。 測試器使用綠線(即詳細(xì)報告)計(jì)算恢復(fù)因子,而我們將使用藍(lán)線(即忽略開倉和平倉之間發(fā)生的價格波動數(shù)據(jù))進(jìn)行計(jì)算。 ?
getBalance — 此方法旨在獲取余額數(shù)據(jù),且不考慮指定日期的交易操作。 double CDealHistoryGetter::getBalance(datetime toDate) ? { ? ?if(HistorySelect(0,(toDate>0 ? toDate : TimeCurrent()))) ? ? ?{ ? ? ? int total=HistoryDealsTotal(); // Get the total number of positions double balance=0; ? ? ? for(int i=0; i<total; i++) ? ? ? ? { ? ? ? ? ?long ticket=(long)HistoryDealGetTicket(i); ? ? ? ? ? ENUM_DEAL_TYPE dealType=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE); ? ? ? ? ?if(dealType==DEAL_TYPE_BALANCE || ? ? ? ? ? ? dealType == DEAL_TYPE_CORRECTION || ? ? ? ? ? ? dealType == DEAL_TYPE_COMMISSION) ? ? ? ? ? ?{ ? ? ? ? ? ? balance+=HistoryDealGetDouble(ticket,DEAL_PROFIT); ? ? ? ? ? ? ?if(toDate<=0) ? ? ? ? ? ? ? ?break; ? ? ? ? ? ?} ? ? ? ? } ? ? ? return balance; ? ? ?} ? ?else return 0; ? } ?
為了達(dá)成任務(wù),首先從最開始的時間段調(diào)取至指定時間段的所有成交的歷史記錄。 之后,在循環(huán)里保存余額,同時將所有入金和出金增減到初始余額中,并考慮經(jīng)紀(jì)商提供的傭金和調(diào)整。 如果日期傳遞零值作為輸入,則僅從第一個日期開始調(diào)取余額。
getBalanceWithPL — 該方法與前一種方法類似,但它另行考慮了所執(zhí)行操作的盈虧造成的對余額變化,包括根據(jù)上述原則的傭金。
創(chuàng)建優(yōu)化報告的類 — 計(jì)算中使用的結(jié)構(gòu)
先前文章中曾提到的另一個類是 CReportCreator。 在文章100 個最佳優(yōu)化通測里的“計(jì)算部分”章節(jié)中對此進(jìn)行了簡要說明。 現(xiàn)在是時候提供更詳細(xì)的論述了,因?yàn)樵擃愑?jì)算所有系數(shù),自動優(yōu)化器將基于這些系數(shù)來確定算法參數(shù)的組合是否與所請求的標(biāo)準(zhǔn)相符。
首先讓我們描述在類實(shí)現(xiàn)中使用的方法的基本思想。 我在第一篇文章中曾實(shí)現(xiàn)了一個相似的類,只是功能較少。 但它非常慢,因?yàn)橐?jì)算下一組要求的參數(shù)或下一張圖表,它必須重新下載所有交易歷史,并循環(huán)遍歷。 這是在每次參數(shù)請求時完成的。
有時,如果數(shù)據(jù)太多,該方法可能需要花費(fèi)幾秒鐘。 為了提升計(jì)算速度。 我用另一個類來實(shí)現(xiàn),它另外提供了更多數(shù)據(jù)(包括標(biāo)準(zhǔn)優(yōu)化結(jié)果中未提供的一些數(shù)據(jù))。 您可能會注意到,許多系數(shù)的計(jì)算都需要類似的數(shù)據(jù),例如,最大利潤/虧損,或累計(jì)利潤/虧損等。
所以,通過在一個循環(huán)中計(jì)算系數(shù)并將其保存在類的字段中,我們可以將該數(shù)據(jù)進(jìn)一步用于需要依賴這些數(shù)據(jù)進(jìn)行計(jì)算的所有其他參數(shù)。 因此,赫茲股票量化得到一個類,該類循環(huán)一次遍歷下載的歷史記錄,計(jì)算所有必需的參數(shù),并存儲數(shù)據(jù)直至一次計(jì)算。 然后,當(dāng)我們需要獲取所需的參數(shù)時,該類將復(fù)制已保存的數(shù)據(jù),而不必重新計(jì)算它,從而大大加快了操作速度。
現(xiàn)在我們看看參數(shù)如何計(jì)算。 我們從存儲計(jì)算數(shù)據(jù)的對象開始。 這些被創(chuàng)建的對象,是為私密作用域中聲明的嵌套類對象。 這樣做出于兩個原因。 首先,防止使用此功能的其他類調(diào)用它們。 大量已聲明的結(jié)構(gòu)和類令人混淆:其中一些是外部計(jì)算所必需的,而另一些是技術(shù)性的,即用于內(nèi)部計(jì)算。 因此,第二個原因是強(qiáng)調(diào)其純粹的技術(shù)目的。
PL_Keeper 結(jié)構(gòu):
struct PL_keeper { PLChart_item ? ? ?PL_total[]; PLChart_item ? ? ?PL_oneLot[]; PLChart_item ? ? ?PL_Indicative[]; };