- 总结
- 优化分析仪结构
- 图解
- 配合数据库操作
- 计算
- 伦德尔
- 结束语
总结
现代技术已经深深植根于金融交易领域,现在几乎不可能想象没有它我们能做什么。然而,不久前,交易是手工完成的,有一个复杂的手语系统(现在很快被遗忘)来描述购买或出售资产的份额。
个人电脑将在线交易带入我们的家庭,并迅速取代传统的商业方式。现在我们可以实时查看资产报价并做出相应的决策。甚至在线技术在市场行业中的出现也导致了手工交易者队伍的快速下降。今天,超过一半的交易都是通过算法交易完成的,值得一提的是,MetaTrader 5是最方便的终端之一。
但是尽管这个平台有很多优点,我还是想解释一下这个应用程序的一些缺点。本文介绍的easyandfastgui函数库是一个完全用MQL5编写的程序。该函数库的目的是改进交易算法的最优参数选择。它还为追踪交易分析和一般EA评估增加了新功能。
首先,EA优化需要很长时间。当然,这是因为测试器以更高质量的方式引用报价(即使选择OHLC,每条蜡烛四引号)和其他附加功能来更好地评估EA。然而,在功能较弱的家用电脑上,优化可能需要几天或几周时间。通常,在选择了EA参数之后,我们很快意识到它们是不正确的,除了优化递归统计和一些评估比率之外,没有其他可用的。
最好根据多个参数为每个优化步骤和过滤能力(包括条件过滤器)提供完整的统计数据。将交易统计数据与买入和持有策略进行比较,并将所有的统计数据强加给对方也是一件好事。此外,有时需要将所有事务历史数据存储在一个文件中,以便随后处理每个事务的结果。
有时,我们也可能想看看算法能承受什么样的滑动点,以及算法在特定时间段内的行为,因为有些策略取决于市场类型。基于横杆的策略可以作为一个例子。在趋势期亏损,在跨市场期收益。最好能够在特定的时间间隔(按日期)查看一组完整的比率和其他添加内容,而不是简单地在价格图上查看。
我们还应该关注前瞻性测试。它们包含很多信息,但是它们的图形显示在策略测试人员的标准报告中,作为前一阶段图形的延续。新进入者很容易得出这样的结论:他们的机器人正在迅速失去所有的利润,然后开始恢复(或者更糟的是,恢复到负数)。在这里描述的程序中,所有数据都是根据优化类型(前瞻性或历史记录)进行审计的。
另一件重要的事情是圣杯,这是许多EA开发人员热衷的。一些机器人每月产生1000%或更多的利润。他们似乎超越了市场(购买和持有策略),但实际上一切看起来都非常不同。正如所描述的程序所显示的,这些机器人确实可以做到1000%,但它们并不超出市场。
该程序的特点是分别分析机器人执行的事务:手的总数(增加/减少等)和限制为一只手的手的数量(可用于事务的最小手的数量)。在建立买入并持有交易图时,所述程序考虑了机器人管理的手的数量(即,当手的数量增加时购买更多的资产,当手的数量减少时减少购买的资产)。如果我们比较这两个图,结果表明,我的测试机器人在其最佳优化订单中显示出不切实际的结果,并且不能超越市场。因此,为了更客观地评估交易策略,我们应该看看单手交易图,其中机器人和带买入和持有策略的PLS都显示为交易量最小的交易(pl=损益时间利润图)。
现在让我们更详细地看一下程序是如何开发的。
优化分析仪结构
程序结构如下:
所得到的优化分析器不依赖于任何特定的机器人或其部分。然而,由于在mql5中构造图形接口的细节,mql5-ea开发模板被用作程序的基础。由于程序非常大(数千行代码),因此它被划分为多个模块(如上图所示),并进行分类以获得更具体和一致性。机器人模板只是启动应用程序的起点。每个模块将在下面进行更详细的检查。在这里,我们将描述它们之间的关系。要使用此应用程序,我们需要:
- 事务处理算法
- DLL SqLITE3
- 必须编辑上面提到的GUI库(在下面的图形模块中描述)
机器人本身可以以您喜欢的任何方式开发(使用oop,机器人模板中的函数,从dll导入…)。最重要的是,它应该应用由MQL5向导提供的机器人开发模板。在每个优化步骤之后,类将所需数据存储到数据库中,并将其从数据库模块连接到文件。该部分独立于应用程序本身,因为数据库是在策略测试仪中启动机器人时形成的。
计算模块是我上一篇文章“自定义事务历史表示和报表图表创建”的持续改进。
数据库和计算模块在所分析的机器人和所描述的应用中都被使用。因此,它们被放在include目录中。这些模块执行大部分工作,并通过广播类连接到图形界面。
广播类连接单独的节目模块。每个模块在图形界面中都有自己的功能。它处理按钮按下和其他事件,并重定向到其他逻辑模块。从中获得的数据返回给广播公司,在那里处理和映射数据,填写表格,并与其他图形部件交互。
程序的图形部分不执行任何概念逻辑。相反,它只构建一个具有所需接口的窗口,并在按钮事件期间调用相应的广播功能。
程序本身被编写为一个MQL5项目,使您能够以更结构化的方式开发它,并将所有必要的文件和代码放在一个地方。该项目还包含另一个将在计算模块中描述的类。这门课是专门为这个程序编写的。它使用我开发的方法来排序优化层次结构。实际上,它服务于整个优化选择选项卡,减少了基于特定标准的数据采样。
通用排序类是程序的独立补充。它不依赖于任何模块,但它仍然是程序的重要部分。因此,我们将在本文的这一部分中对其进行简要的研究。
顾名思义,这个类处理数据排序。该算法源自第三方网站,即选择排序(俄语)。
//+------------------------------------------------------------------+ //| E-num 排序风格 | //+------------------------------------------------------------------+ enum SortMethod { Sort_Ascending,// 升序 Sort_Descendingly// 降序 }; //+------------------------------------------------------------------+ //| 针对所传递数据类型进行排序的类 | //+------------------------------------------------------------------+ class CGenericSorter { public: // 默认构造函数 CGenericSorter(){method=Sort_Descendingly;} // 排序方法 template<typename T> void Sort(T &out[],ICustomComparer<T>*comparer); // 选择排序类型 void Method(SortMethod _method){method=_method;} // 获取排序方法 SortMethod Method(){return method;} private: // 排序方法 SortMethod method; };
此类包含对数据排序的模板排序方法。模板方法允许对传递的任何数据(包括类和结构)进行排序。数据比较方法应在实现iustomcomparer<;t>;接口的单独类中描述。我必须开发自己的iomparer类型的接口,因为在传统的比较方法的iomparer接口中,它所包含的数据不是通过引用来传输的,它的引用传输是mql5语言传递方法结构的条件之一。
CGenericSorter::方法类方法重载返回和接收的数据的排序类型(升序或降序)。此类用于需要对数据排序的所有程序模块。
图解
警告!在开发图形界面时,在Easy and Fast GUI(Easy and Fast GUI)中检测到一个错误。组合框图形元素在重新填充期间清除了一些不完整的变量。根据函数库开发人员(俄语)的建议,应该对该问题进行以下更改以解决该问题:m_item_index_focus=wrong_value; m_prev_selected_item=wrong_value; m_prev_item_index_focus=wrong_value;clistview::clear(const bool raw=false)方法中的代码。此方法位于ListView的600字符串中。MQH文件。文件路径: include/easyandfastgui/controls。 如果不添加这些编辑,有时打开组合框时会弹出“数组超出范围”错误,应用程序将异常关闭。 |
---|
要在基于easyandfastgui库的mql5中创建窗口,
需要一个类作为容器,以供所有后续窗口填充。此类应派生自CWindEvents类。这些方法应该在类中重新定义:
//--- 初始/逆初 void OnDeinitEvent(const int reason){CWndEvents::Destroy();}; //--- 图表事件处理程序 virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);//
用于创建窗口的模块应如下:
class CWindowManager : public CWndEvents { public: CWindowManager(void){presenter = NULL;}; ~CWindowManager(void){}; //=============================================================================== // 调用方法和事件 : //=============================================================================== //--- 初始/逆初 void OnDeinitEvent(const int reason){CWndEvents::Destroy();}; //--- 图表事件处理程序 virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- 创建程序的图形界面 bool CreateGUI(void); private: //--- 主窗口 CWindow m_window; }
窗口本身是由类中的C窗口类型创建的。但是,在窗口显示之前,应该定义一定数量的窗口属性。在这种特殊情况下,窗口创建方法如下:
bool CWindowManager::CreateWindow(const string text) { //--- 将窗口指针添加到窗口数组 CWndContainer::AddWindow(m_window); //--- 坐标 int x=(m_window.X()>0) ? m_window.X() : 1; int y=(m_window.Y()>0) ? m_window.Y() : 1; //--- 属性 m_window.XSize(WINDOW_X_SIZE+25); m_window.YSize(WINDOW_Y_SIZE); m_window.Alpha(200); m_window.IconXGap(3); m_window.IconYGap(2); m_window.IsMovable(true); m_window.ResizeMode(false); m_window.CloseButtonIsUsed(true); m_window.FullscreenButtonIsUsed(false); m_window.CollapseButtonIsUsed(true); m_window.TooltipsButtonIsUsed(false); m_window.RollUpSubwindowMode(true,true); m_window.TransparentOnlyCaption(true); //--- 设置工具提示 m_window.GetCloseButtonPointer().Tooltip("Close"); m_window.GetFullscreenButtonPointer().Tooltip("Fullscreen/Minimize"); m_window.GetCollapseButtonPointer().Tooltip("Collapse/Expand"); m_window.GetTooltipButtonPointer().Tooltip("Tooltips"); //--- 创建窗体 if(!m_window.CreateWindow(m_chart_id,m_subwin,text,x,y)) return(false); //--- return(true); }
此方法的先决条件是将窗口添加到应用程序的窗口数组中并创建一系列表单。稍后,当应用程序运行并触发OnEvent事件时,它将迭代窗口数组中列出的所有窗口并运行库方法之一。然后,它遍历窗口中的所有元素,查找与单击事件相关的所有突出显示的管理接口或表行,依此类推。因此,在创建每个新的应用程序窗口时,应该将对该窗口的引用添加到引用数组中。
开发的应用程序将接口划分为选项卡。有四个标签容器:
//--- 选项卡 CTabs main_tab; // 主选项卡 CTabs tab_up_1; // 设置和结果表格的选项卡 CTabs tab_up_2; // 统计数据和参数选择的选项卡,以及共用图形 CTabs tab_down; // 统计信息及上传到文件的选项卡
它们在窗体上看起来如下(屏幕截图中的红色符号):
- 主_选项卡将所有选定的优化层次结构(“优化数据”)与程序接口的其余部分分开。此表包含满足“设置”选项卡上的筛选条件的所有结果。然后按组合框中选择的比率对结果排序-排序依据。将得到的数据转换成以分类形式描述的表。程序界面中的其余选项卡包含三个额外的选项卡容器。
- tab_up_1包含程序初始设置和排序结果表的分区。除了上面提到的条件过滤器之外,“设置”选项卡还用于选择数据库并输入其他数据。例如,您可以选择是否将所有添加到表的优化数据选项卡中的数据输入到数据选择结果表中,或者只需选择一定数量的最佳参数(按所选比率按降序排序)就足够了。
- TabuUp2包含三个选项卡。它们中的每一个包含执行三种不同类型任务的接口。第一个选项卡包含关于所选择的优化层次结构的完整报告,并且允许考虑特定时间段的事务历史的滑动点的模拟。第二个被用作滤波器来优化层次结构,帮助定义策略对不同参数的敏感性,并通过选择感兴趣参数的最佳间隔来减少优化结果的数目。最后一个选项卡用作优化结果表的图形表示,并显示所选优化参数的总数。
- tab_down有五个选项卡,其中四个选项卡在优化所选参数时显示EA事务报告,最后一个选项卡将数据保存到文件中。第一个选项卡显示评估比率表。第二张卡按交易日提供损益分配。第三个选项卡显示了买入和持有策略的损益图(黑色图形),而第四个选项卡显示了一些选定比率随时间的变化,以及通过分析EA交易结果获得的一些其他有趣和信息丰富的图形类型。
创建选项卡的过程类似——唯一的区别是内容。例如,我将提供一种创建主选项卡的方法:
//+------------------------------------------------------------------+ //| 主选卡 | //+------------------------------------------------------------------+ bool CWindowManager::CreateTab_main(const int x_gap,const int y_gap) { //--- 将指针保存到主元素 main_tab.MainPointer(m_window); //--- 选卡宽度数组 int tabs_width[TAB_MAIN_TOTAL]; ::ArrayInitialize(tabs_width,45); tabs_width[0]=120; tabs_width[1]=120; //--- string tabs_names[TAB_UP_1_TOTAL]={"Analysis","Optimisation Data"}; //--- 属性 main_tab.XSize(WINDOW_X_SIZE-23); main_tab.YSize(WINDOW_Y_SIZE); main_tab.TabsYSize(TABS_Y_SIZE); main_tab.IsCenterText(true); main_tab.PositionMode(TABS_LEFT); main_tab.AutoXResizeMode(true); main_tab.AutoYResizeMode(true); main_tab.AutoXResizeRightOffset(3); main_tab.AutoYResizeBottomOffset(3); //--- main_tab.SelectedTab((main_tab.SelectedTab()==WRONG_VALUE)? 0 : main_tab.SelectedTab()); //--- 添加指定属性的选项卡 for(int i=0; i<TAB_MAIN_TOTAL; i++) main_tab.AddTab((tabs_names[i]!="")? tabs_names[i]: "Tab "+string(i+1),tabs_width[i]); //--- 创建一个控件元素 if(!main_tab.CreateTabs(x_gap,y_gap)) return(false); //--- 将对象添加到公共对象数组 CWndContainer::AddToElementsArray(0,main_tab); return(true); }
除了可能的更改外,主要代码如下:
- 添加指向主元素的指针-选项卡容器应该知道分配给它的元素
- 控制元素创建字符串
- 向常规控件列表中添加元素。
控制元素应符合层次结构。应用程序中使用了11种控制元素类型。它们都是以类似的方式创建的,因此已经编写了添加控制元素的方法来依次创建它们。我们将只检查其中一个的实现:
bool CWindowManager::CreateLable(const string text, const int x_gap, const int y_gap, CTabs &tab_link, CTextLabel &lable_link, int tabIndex, int lable_x_size) { //--- 将指针保存到主元素 lable_link.MainPointer(tab_link); //--- 分配到选卡 tab_link.AddToElementsArray(tabIndex,lable_link); //--- 设置 lable_link.XSize(lable_x_size); //--- 创建 if(!lable_link.CreateTextLabel(text,x_gap,y_gap)) return false; //--- 将对象添加到常规对象数组 CWndContainer::AddToElementsArray(0,lable_link); return true; }
传递的控制元素(ctextlabel)和选项卡应记住元素分配到的容器。反过来,选项卡容器会记住元素所在的选项卡。之后,元素将填充所需的设置和初始数据。最后,将对象添加到常规对象数组中。
与标签类似,需要添加类容器中定义的其他元素字段。我分离了一些元素,把它们放在类的保护区。这些元素不需要广播者访问。其他元素放在“public”中。这些元素定义了某些条件或广播公司需要检查的单选按钮。换句话说,所有不希望被访问的元素和方法都放在类的“受保护”或“私有”部分,以及对广播者的引用。添加广播引用是以公共方法的形式进行的,该方法首先检查添加的广播者是否存在,如果未添加广播者引用,则保存广播者。这样做是为了避免在节目执行期间动态替换广播电台。
窗口本身是在creategui方法中创建的:
bool CWindowManager::CreateGUI(void) { //--- 创建窗口 if(!CreateWindow("Optimisation Selection")) return(false); //--- 创建选项卡 if(!CreateTab_main(120,20)) return false; if(!CreateTab_up_1(3,44)) return(false); int indent=WINDOW_Y_SIZE-(TAB_UP_1_BOTTOM_OFFSET+TABS_Y_SIZE-TABS_Y_SIZE); if(!CreateTab_up_2(3,indent)) return(false); if(!CreateTab_down(3,33)) return false; //--- 创建控件 if(!Create_all_lables()) return false; if(!Create_all_buttons()) return false; if(!Create_all_comboBoxies()) return false; if(!Create_all_dropCalendars()) return false; if(!Create_all_textEdits()) return false; if(!Create_all_textBoxies()) return false; if(!Create_all_tables()) return false; if(!Create_all_radioButtons()) return false; if(!Create_all_SepLines()) return false; if(!Create_all_Charts()) return false; if(!Create_all_CheckBoxies()) return false; // 显示窗口 CWndEvents::CompletedGUI(); return(true); }
从它的实现中可以看到,它本身并不直接创建任何控制元素,而是只调用其他方法来创建这些元素。此方法中包含的最后一个主代码是cwndevents::completed gui();
此代码完成图形创建并将其绘制在用户屏幕上。implement the creation of each control element(which can be a separator,label or button)as a method with similar content,and apply the above method to create graphic control elements.方法头可以在类的“private”部分中找到:
//=============================================================================== // 创建控件: //=============================================================================== //--- 所有标签 bool Create_all_lables(); bool Create_all_buttons(); bool Create_all_comboBoxies(); bool Create_all_dropCalendars(); bool Create_all_textEdits(); bool Create_all_textBoxies(); bool Create_all_tables(); bool Create_all_radioButtons(); bool Create_all_SepLines(); bool Create_all_Charts(); bool Create_all_CheckBoxies();
当涉及到图形时,不可能跳过事件模型部分。要正确处理使用easyandfastgui开发的图形应用程序,需要执行以下步骤:
创建事件处理器方法(例如,按下按钮)。此方法应接收’id’和’lparam’作为参数。第一个参数表示图形事件的类型,而第二个参数表示与之交互的对象的ID。在所有情况下,这些方法的实现都是相似的:
//+------------------------------------------------------------------+ //| Btn_Update_Click | //+------------------------------------------------------------------+ void CWindowManager::Btn_Update_Click(const int id,const long &lparam) { if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==Btn_update.Id()) { presenter.Btn_Update_Click(); } }
首先,检查条件(按按钮或选择列表元素…)。接下来,检查lparam并将传递给方法的ID与所需列表元素的ID进行比较。
所有按钮事件声明都在类的“private”部分中。应该调用事件以获取对其的响应。在重载的OnEvent方法中调用声明的事件:
//+------------------------------------------------------------------+ //| OnEvent | //+------------------------------------------------------------------+ void CWindowManager::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { Btn_Update_Click(id,lparam); Btn_Load_Click(id,lparam); OptimisationData_inMainTable_selected(id,lparam); OptimisationData_inResults_selected(id,lparam); Update_PLByDays(id,lparam); RealPL_pressed(id,lparam); OneLotPL_pressed(id,lparam); CoverPL_pressed(id,lparam); RealPL_pressed_2(id,lparam); OneLotPL_pressed_2(id,lparam); RealPL_pressed_4(id,lparam); OneLotPL_pressed_4(id,lparam); SelectHistogrameType(id,lparam); SaveToFile_Click(id,lparam); Deals_passed(id,lparam); BuyAndHold_passed(id,lparam); Optimisation_passed(id,lparam); OptimisationParam_selected(id,lparam); isCover_clicked(id,lparam); ChartFlag(id,lparam); show_FriquencyChart(id,lparam); FriquencyChart_click(id,lparam); Filtre_click(id,lparam); Reset_click(id,lparam); RealPL_pressed_3(id,lparam); OneLotPL_pressed_3(id,lparam); ShowAll_Click(id,lparam); DaySelect(id,lparam); }
相反,该方法是从robot模板调用的。因此,事件模型从机器人模板(下面提供)扩展到图形界面。GUI执行所有处理、排序和重定向,以便在广播程序中进行后续处理。机器人模板本身就是程序的起点。如下所示:
#include "Presenter.mqh" CWindowManager _window; CPresenter Presenter(&_window); //+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- if(!_window.CreateGUI()) { Print(__FUNCTION__," > Failed to create the graphical interface!"); return(INIT_FAILED); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 智能系统逆初始化函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- _window.OnDeinitEvent(reason); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { _window.ChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
配合数据库操作
在研究项目的扩展之前,值得对已经做出的选择说几句话。该项目的初始目标之一是提供处理优化结果的能力,并在优化本身完成后的任何时间获得这些结果。将数据保存到文件中会立即被放弃,因为它不合适。它需要创建多个表(实际上是形成一个大表,但有不同的行)或文件。
两者都不方便。此外,这种方法很难实现。第二种方法是创建优化的帧。工具箱本身很好,但我们不会在优化过程中对其进行优化。此外,框架函数不如数据库函数好。此外,框架是为MetaTrader设计的,数据库可以用于任何第三方分析器(如果需要)。
选择正确的数据库很容易。我们需要一个快速流行的数据库,不需要任何其他软件就可以轻松连接。sqlite数据库符合所有标准。这些特点使它如此受欢迎。要使用它,提供程序将所需的数据库连接到DLL项目。DLL数据是用C语言编写的,可以很容易地链接到MQL5应用程序,这是一个很好的补充,因为您不必用第三方语言编写单独的代码来使项目复杂化。这种方法的缺点之一是,dll sqlite不提供方便的API来处理数据库,因此至少需要描述一个最小的操作数据库包装器。编写此函数的示例已显示在文章“SQL和MQL5:使用sqlite数据库”中。在这个项目中,使用winapi交互和一些从dll导入到mql5的函数代码。至于包装纸,我决定自己写。
因此,数据库处理模块由sqlite3文件夹组成,其中描述了用于处理数据库的方便包装器和为开发程序专门创建的优化选择器文件夹。这两个文件夹都位于mql5/include目录中。如前所述,Windows标准库的许多功能可用于操作数据库。应用程序这一部分中的所有函数都位于winapi目录中。除了上述借用之外,我还使用代码库中的代码来创建共享资源(互斥体)。当从两个源操作数据库时(顾名思义,如果优化分析器打开优化期间使用的数据库),程序获得的数据应该总是完整的。这就是资源需要共享的原因。结果表明,如果其中一方(优化过程或分析器)激活数据库,则第二方必须等待另一方完成其工作。sqlite数据库允许从多个线程读取它。仅限于本文的主题,我们将不详细研究从mql5操作sqlite3数据库的包装器。相反,我们将只描述其实现和应用方法的一些关键点。如前所述,使用数据库的包装器位于sqlite3文件夹中。里面有三个文件。我们按书面顺序检查它们。
- 我们需要做的第一件事是从dll导入必要的函数来处理数据库。因为目标仅仅是创建一个包含最低要求功能的包装器,所以我从数据库开发人员导入的函数总数不会超过1%。所有必要的函数都将导入sqlite amalgmation.mqh文件。这些功能在开发人员的网站上有很好的注释,并且在上面的文件中也有注释。如有必要,可以以相同的方式导入整个头文件。结果是所有函数的完整列表,您可以从中访问它们。导入功能列表如下:
#import "Sqlite3_32.dll" int sqlite3_open(const uchar &filename[],sqlite3_p32 &paDb);// 打开数据库 int sqlite3_close(sqlite3_p32 aDb); // 关闭数据库 int sqlite3_finalize(sqlite3_stmt_p32 pStmt);// 完成语句 int sqlite3_reset(sqlite3_stmt_p32 pStmt); // 重置语句 int sqlite3_step(sqlite3_stmt_p32 pStmt); // 读取语句时移至下一行 int sqlite3_column_count(sqlite3_stmt_p32 pStmt); // 计算列数 int sqlite3_column_type(sqlite3_stmt_p32 pStmt,int iCol); // 获取所选列的类型 int sqlite3_column_int(sqlite3_stmt_p32 pStmt,int iCol);// 将数值转换为 int long sqlite3_column_int64(sqlite3_stmt_p32 pStmt,int iCol); // 将数值转换为 int64 double sqlite3_column_double(sqlite3_stmt_p32 pStmt,int iCol); // 将数值转换为 double const PTR32 sqlite3_column_text(sqlite3_stmt_p32 pStmt,int iCol);// 获取文本值 int sqlite3_column_bytes(sqlite3_stmt_p32 apstmt,int iCol); // 从传递的单元格中获取该行占用的字节数 int sqlite3_bind_int64(sqlite3_stmt_p32 apstmt,int icol,long a);// 合成请求值(int64 类型) int sqlite3_bind_double(sqlite3_stmt_p32 apstmt,int icol,double a);// 合成请求值(double 类型) int sqlite3_bind_text(sqlite3_stmt_p32 apstmt,int icol,char &a[],int len,PTRPTR32 destr);// 合成请求值 (字符串类型 (C++ 中的 char*)) int sqlite3_prepare_v2(sqlite3_p32 db,const uchar &zSql[],int nByte,PTRPTR32 &ppStmt,PTRPTR32 &pzTail);// 准备一个请求 int sqlite3_exec(sqlite3_p32 aDb,const char &sql[],PTR32 acallback,PTR32 avoid,PTRPTR32 &errmsg);// 执行 Sql int sqlite3_open_v2(const uchar &filename[],sqlite3_p32 &ppDb,int flags,const char &zVfs[]); // 依据参数打开数据库 #import
开发人员提供的数据库应放在libraries文件夹中,并根据其dll数据库包装器的数量命名为sqlite3_32.dll和sqlite3_64.dll。您可以从文章的附件获取DLL数据,从sqlite amalgmation编译它们,或者从sqlite开发人员的网站获取它们。它们的存在是程序的前提。您还需要允许EA导入Dll。nbsp;nbsp;
- 第二件事是编写一个函数包装器来连接到数据库。这应该是一个创建到数据库的连接并在析构函数中释放它(从数据库断开)的类。此外,它应该能够执行简单的字符串SQL命令、管理事务和创建查询(语句)。所有描述的函数都在csqlitemanager类中实现——从创建开始就与数据库交互的过程。
//+------------------------------------------------------------------+ //| 数据库连接和管理类 | //+------------------------------------------------------------------+ class CSqliteManager { public: CSqliteManager(){db=NULL;} // 空构造函数 CSqliteManager(string dbName); // 传递名字 CSqliteManager(string dbName,int flags,string zVfs); // 传递名称和连接标志 CSqliteManager(CSqliteManager &other) { db=other.db; } // 复制构造函数 ~CSqliteManager(){Disconnect();};// 析构函数 void Disconnect(); // 断开与数据库的连接 bool Connect(string dbName,int flags,string zVfs); // 与数据库连接的参数 bool Connect(string dbName); // 按名称连接到数据库 void operator=(CSqliteManager &other){db=other.db;}// 分配操作符 sqlite3_p64 DB() { return db; }; // 获取指向数据库的指针 sqlite3_stmt_p64 Create_statement(const string sql); // 创建语句 bool Execute(string sql); // 执行命令 void Execute(string sql,int &result_code,string &errMsg); // 执行命令,并提供错误代码和消息 void BeginTransaction(); // 事务开始 void RollbackTransaction(); // 事务回滚 void CommitTransaction(); // 确认事务 private: sqlite3_p64 db; // 数据库 void stringToUtf8(const string strToConvert,// 要转换为 utf-8 编码的字符串数组 uchar &utf8[],// 将 utf-8 编码的数组放入转换后的 strToConvert 字符串中 const bool untilTerminator=true) { // 转换为 utf-8 编码的字符数量,并复制到 utf-8 数组 //--- int count=untilTerminator ? -1 : StringLen(strToConvert); StringToCharArray(strToConvert,utf8,0,count,CP_UTF8); } };
从代码中可以看出,生成的类可以在数据库中创建两种类型的连接(文本和指定参数)。CealTySttEtMeST方法向数据库形成请求并返回指向它的指针。exequte方法重载简单字符串查询的执行,而transaction方法创建和接收/取消事务。与数据库本身的连接存储在数据库变量中。如果我们应用disconnect方法或只使用默认构造函数(没有时间连接到数据库)创建类,那么变量为空。当我们重复调用connect方法时,我们将断开与前一个数据库的连接,并连接到新的数据库。因为连接到数据库需要以UTF-8格式传递字符串,所以这个类有一个特殊的“private”方法,可以将字符串转换为所需的数据格式。
- 下一个任务是创建一个包装器以方便查询(语句)。应创建并销毁对数据库的请求。请求由csqlitemanager创建,内存不由任何东西管理。换句话说,在创建请求之后,当不再需要它时,它将被销毁,否则它将不被允许断开与数据库的连接,当我们尝试完成数据库的操作时,我们将得到一个异常,指示数据库正忙。此外,语句包装类应该能够用传递的参数来填充请求(当它采用“insert in to table_1 values(@id,@param_1,@param_2);”的形式时)。此外,给定的类应该能够执行放置在其中的查询(exequte方法)。
typedef bool(*statement_callback)(sqlite3_stmt_p64); // 执行查询时要执行的回调。 如果成功,则执行“true” //+------------------------------------------------------------------+ //| 对数据库的查询类 | //+------------------------------------------------------------------+ class CStatement { public: CStatement(){stmt=NULL;} // 空构造函数 CStatement(sqlite3_stmt_p64 _stmt){this.stmt=_stmt;} // 含参数的构造函数 - 指向语句的指针 ~CStatement(void){if(stmt!=NULL)Sqlite3_finalize(stmt);} // 析构函数 sqlite3_stmt_p64 get(){return stmt;} // 获取指向语句的指针 void set(sqlite3_stmt_p64 _stmt); // 设置指向语句的指针 bool Execute(statement_callback callback=NULL); // 执行语句 bool Parameter(int index,const long value); // 添加参数 bool Parameter(int index,const double value); // 添加参数 bool Parameter(int index,const string value); // 添加参数 private: sqlite3_stmt_p64 stmt; };
参数方法重载填充请求参数。“set”方法将传递的语句保存到“stmt”变量中:如果在保存新请求之前在类中找到旧请求,则会为以前保存的请求调用sqlite3_finalize方法。
- 数据库处理包装器中的结束类是CSQliteReader,它可以从数据库中读取响应。与前面的类类似,这个类在其析构函数中调用sqlite3_reset方法——它删除请求并允许您再次使用它。在新版本的数据库中,不需要调用这个函数,但开发人员仍然保留它。我用它来包装以防万一。该类还应执行其主要职责,即通过字符串从数据库字符串中读取响应,并将读取的数据转换为相应的格式。
//+------------------------------------------------------------------+ //| 从数据库中读取响应的类 | //+------------------------------------------------------------------+ class CSqliteReader { public: CSqliteReader(){statement=NULL;} // 空构造函数 CSqliteReader(sqlite3_stmt_p64 _statement) { this.statement=_statement; }; // 构造函数接收指向语句的指针 CSqliteReader(CSqliteReader &other) : statement(other.statement) {} // 复制构造函数 ~CSqliteReader() { Sqlite3_reset(statement); } // 析构函数 void set(sqlite3_stmt_p64 _statement); // 添加语句的引用 void operator=(CSqliteReader &other){statement=other.statement;}// 读取器分配运算符 void operator=(sqlite3_stmt_p64 _statement) {set(_statement);}// 语句分配运算符 bool Read(); // 读取字符串 int FieldsCount(); // 计算列数 int ColumnType(int col); // 获取列类型 bool IsNull(int col); // 检查数值是否 == SQLITE_NULL long GetInt64(int col); // 转换为 'int' double GetDouble(int col);// 转换为 'double' string GetText(int col);// 转换为 'string' private: sqlite3_stmt_p64 statement; // 指向语句的指针 };
现在我们已经实现了操作sqlite3.dll数据库所描述的函数类,现在是时候展示如何使用程序中的类来操作数据库了。
创建的数据库结构如下:
购买并持有表格:
- 时间X轴(时间段标签)
- pl_total——如果我们按比例增加机器人手的数量,则损益
- 损益单——如果交易是基于单手损益
- dd_total-如果EA以相同方式交易一只手,则提取
- DD onelot-如果交易是第一手交易,则提取
- isforvard-前瞻性图形属性
优化参数表:
- ID-数据库中唯一的自动填充项索引
- 历史边界-历史优化完成日期
- 时间帧
- PARAM1…参数参数
- 初始余额
参数定义表:
- ID-外键参考优化参数(ID)
- isforvard-前瞻性优化属性
- Isonelot-比率所基于的图表属性
- 撤退
- 平均损益图的平均损益
- 平均日平均提款
- 平均利润-平均利润
- 利润因素
- 回收因子回收因子
- 夏普比率-夏普比率
- Altman_z_得分-Altman z得分
- var_绝对值_90-var 90
- Var_绝对值
- var_绝对值
- var_增长_90-var 90
- var_增长_95-var 95
- var_增长_99-var 99
- Wincoef获胜率
- 自定义比率
参数类型表:
- 参数名-机器人参数名
- 参数类型-机器人参数类型(int/double/string)
交易历史表
- ID-外键引用优化参数(ID)
- Isforvard-前瞻性测试标记
- 符号多样性
- 打开日期-打开日期
- 开放日
- 结束日期
- 日关闭日期
- 体积-手数
- Islong多/短属性
- 价格-门票价格
- 价格
- 单手交易利润
- 交易损益-交易收益,如前所述
- opencomment-进场指令
- 关闭注释-场外说明
根据所提供的数据库结构,我们可以看到一些表使用外键引用优化参数表,我们在其中存储EA参数。输入参数的每一列都有其名称(例如,快速/慢速快速/慢速移动平均值)。此外,每列都应该有一个特定的数据格式。已经创建了许多SQLite数据库,但未定义表和列数据格式。在这种情况下,所有数据都存储在行中。但是,我们需要知道确切的数据格式,因为我们应该通过一个属性来组织比率,这意味着将从数据库中获得的数据转换为其原始格式。
为此,在进入数据库之前,我们应该知道数据的格式。有几个可能的选项:创建模板方法、将转换器转换为模板方法或创建类。实际上,它是几个数据类型(任何数据类型都可以转换)的通用存储,并与EA名称变量组合在一起。我选择了第二个选项并创建了cdatakeeper类。所描述的类可以存储三种数据类型[int、double、string],所有其他可以用作EA输入格式的数据类型都可以以某种方式转换为它们。
//+------------------------------------------------------------------+ //| EA 参数的输入数据类型 | //+------------------------------------------------------------------+ enum DataTypes { Type_INTEGER,// int Type_REAL,// double, float Type_Text // string }; //+------------------------------------------------------------------+ //| 比较两个 CDataKeeper 的结果 | //+------------------------------------------------------------------+ enum CoefCompareResult { Coef_Different,// 不同的数据类型或变量名称 Coef_Equal,// 变量相等 Coef_Less, // 当前变量小于所传递的变量 Coef_More // 当前变量超过所传递的变量 }; //+---------------------------------------------------------------------+ //| 存储一个特定机器人输入的类。 | //| 它可以存储以下类型的数据: [int, double, string] | //+---------------------------------------------------------------------+ class CDataKeeper { public: CDataKeeper(); // 构造函数 CDataKeeper(const CDataKeeper&other); // 复制构造函数 CDataKeeper(string _variable_name,int _value); // 参数化构造函数 CDataKeeper(string _variable_name,double _value); // 参数化构造函数 CDataKeeper(string _variable_name,string _value); // 参数化构造函数 CoefCompareResult Compare(CDataKeeper &data); // 比较方法 DataTypes getType(){return variable_type;}; // 获取数据类型 string getName(){return variable_name;}; // 获取参数名 string valueString(){return value_string;}; // 获取参数 int valueInteger(){return value_int;}; // 获取参数 double valueDouble(){return value_double;}; // 获取参数 string ToString(); // 将任何参数转换为字符串。 如果是一个字符串参数,则从两端给字符串添加单引号 <<'>> private: string variable_name,value_string; // 变量名和字符串变量 int value_int; // Int 变量 double value_double; // Double 变量 DataTypes variable_type; // 变量类型 int compareDouble(double x,double y) // 比较 Double 类型,精确到 10 位小数 { double diff=NormalizeDouble(x-y,10); if(diff>0) return 1; else if(diff<0) return -1; else return 0; } };
三个重载的构造函数接收变量名作为第一个参数,并接收转换为上述类型之一的值作为第二个参数。这些值保存在类全局变量中,以’value’开头,后跟类型指示。gettype()method返回上述枚举中的类型,而getname()method返回变量名。以“value”开头的方法返回所需类型的变量,但如果调用valuedouble()方法并且类中存储的变量为“int”类型,则返回空值。ToString()方法将任何变量的值转换为字符串格式。但是,如果变量最初是一个字符串,则会向其添加一个单引号(以形成SQL请求)。比较(cdatakeepe&ther)方法允许比较两种cdatakeeper类型的对象:
- EA变量名
- 变量类型
- 变量值
如果前两个比较失败,那么我们尝试比较两个不同的参数(例如快速移动平均值的周期和缓慢移动平均值的周期),但是我们不能这样做,因为我们只需要比较相同类型的数据。因此,我们返回coefCompareResult类型的coef_different值。在其他情况下,进行比较并返回所需的结果。比较方法本身实现如下:
//+------------------------------------------------------------------+ //| 将当前参数与传递的参数进行比较 | //+------------------------------------------------------------------+ CoefCompareResult CDataKeeper::Compare(CDataKeeper &data) { CoefCompareResult ans=Coef_Different; if(StringCompare(this. variable_name,data.getName())==0 && this.variable_type==data.getType()) // 比较名称和类型 { switch(this.variable_type) // 比较数值 { case Type_INTEGER : ans=(this.value_int==data.valueInteger() ? Coef_Equal 🙁this.value_int>data.valueInteger() ? Coef_More : Coef_Less)); break; case Type_REAL : ans=(compareDouble(this.value_double,data.valueDouble())==0 ? Coef_Equal :(compareDouble(this.value_double,data.valueDouble())>0 ? Coef_More : Coef_Less)); break; case Type_Text : ans=(StringCompare(this.value_string,data.valueString())==0 ? Coef_Equal 🙁StringCompare(this.value_string,data.valueString())>0 ? Coef_More : Coef_Less)); break; } } return ans; }
变量的类型无关表示允许以更方便的形式使用它们,同时考虑到变量的名称、数据类型和值。
下一个任务是创建上述数据库。CDatabaseWriter类用于此目的。
//+---------------------------------------------------------------------------------+ //| 回调计算用户比率 | //| 计算比率所需的历史数据和历史类型的标志 | //| 作为输入参数传递 | //+---------------------------------------------------------------------------------+ typedef double(*customScoring_1)(const DealDetales &history[],bool isOneLot); //+---------------------------------------------------------------------------------+ //| 回调计算用户比率 | //| 连接到数据库(只读),历史记录和所请求的比率类型标志 | //| 作为输入参数传递 | //+---------------------------------------------------------------------------------+ typedef double(*customScoring_2)(CSqliteManager *dbManager,const DealDetales &history[],bool isOneLot); //+---------------------------------------------------------------------------------+ //| 保存数据库中的数据并在此之前创建数据库的类 | //+---------------------------------------------------------------------------------+ class CDBWriter { public: // 为 OnInit 调用重置之一 void OnInitEvent(const string DBPath,const CDataKeeper &inputData_array[],customScoring_1 scoringFunction,double r,ENUM_TIMEFRAMES TF=PERIOD_CURRENT); // 回调 1 void OnInitEvent(const string DBPath,const CDataKeeper &inputData_array[],customScoring_2 scoringFunction,double r,ENUM_TIMEFRAMES TF=PERIOD_CURRENT); // 回调 2 void OnInitEvent(const string DBPath,const CDataKeeper &inputData_array[],double r,ENUM_TIMEFRAMES TF=PERIOD_CURRENT);// 没有回调且没有用户比率(等于零) double OnTesterEvent();// 在 OnTester 中调用 void OnTickEvent();// 在 OnTick 中调用 private: CSqliteManager dbManager; // 连接到数据库 CDataKeeper coef_array[]; // 输入参数 datetime DT_Border; // 最后一根蜡烛的日期(在 OnTickEvent 中计算) double r; // 无风险比率 customScoring_1 scoring_1; // 回调 customScoring_2 scoring_2; // 回调 int scoring_type; // 回调类型 [1,2] string DBPath; // 指向数据库的路径 double balance; // 余额 ENUM_TIMEFRAMES TF; // 时间帧 void CreateDB(const string DBPath,const CDataKeeper &inputData_array[],double r,ENUM_TIMEFRAMES TF);// 创建数据库及其随附的所有内容 bool isForvard();// 定义当前优化类型(历史/前瞻) void WriteLog(string s,string where);// 文件日志条目 int setParams(bool IsForvard,CReportCreator *reportCreator,DealDetales &history[],double &customCoef);// 填写输入表 void setBuyAndHold(bool IsForvard,CReportCreator *reportCreator);// 填写买入并持有历史 bool setTraidingHistory(bool IsForvard,DealDetales &history[],int ID);// 填写交易历史 bool setTotalResult(TotalResult &coefData,bool isOneLot,long ID,bool IsForvard,double customCoef);// 用比率填写表格 bool isHistoryItem(bool IsForvard,DealDetales &item,int ID); // 检查交易历史记录表中是否已存在这些参数 };
此类仅用于自定义机器人本身。其目的是为所描述的程序(即具有所需结构和内容的数据库)创建输入参数。如我们所见,它有三种公共方法(重载方法也被认为是一种方法):
- 单事件
- 测试器事件
- 意外事件
在robot模板的相应回调中调用它们中的每一个,并将所需的参数传递给它们。OnInitEvent方法旨在为处理数据库准备类。其过载实现如下:
//+------------------------------------------------------------------+ //| 创建数据库并连接 | //+------------------------------------------------------------------+ void CDBWriter::OnInitEvent(const string _DBPath,const CDataKeeper &inputData_array[],customScoring_2 scoringFunction,double _r,ENUM_TIMEFRAMES _TF) { CreateDB(_DBPath,inputData_array,_r,_TF); scoring_2=scoringFunction; scoring_type=2; } //+------------------------------------------------------------------+ //| 创建数据库并连接 | //+------------------------------------------------------------------+ void CDBWriter::OnInitEvent(const string _DBPath,const CDataKeeper &inputData_array[],customScoring_1 scoringFunction,double _r,ENUM_TIMEFRAMES _TF) { CreateDB(_DBPath,inputData_array,_r,_TF); scoring_1=scoringFunction; scoring_type=1; } //+------------------------------------------------------------------+ //| 创建数据库并连接 | //+------------------------------------------------------------------+ void CDBWriter::OnInitEvent(const string _DBPath,const CDataKeeper &inputData_array[],double _r,ENUM_TIMEFRAMES _TF) { CreateDB(_DBPath,inputData_array,_r,_TF); scoring_type=0; }
正如我们在方法实现中看到的,它将所需的值分配给类字段并创建数据库。回调方法应该由用户自己实现(如果应该计算自定义比率),或者使用不带回调的重载实现——在这种情况下,自定义比率等于零。用户比率是评估EA优化层次结构的一种自定义方法。为了实现它,创建了指向两个函数的指针,对应于两个可能的必需数据。
- 第一个(customscording_1)接收计算所需的事务历史记录和定义最佳顺序的标志(实际事务数或单手事务数-用于计算的所有数据都存储在传递的数组中)。
- 第二个回调类型(customscording_2)可以访问需要执行语句的数据库,但只具有只读权限,以避免用户意外编辑。
CreateDB 方法是类的主要方法之一。 它为操作做了充分的准备:
- 分配余额、时间框架和无风险比率。
- 建立与数据库的连接并占用共享资源(mutex)
- 否则,创建表数据库。
OnTickevent的公开引述在每个引述中保持蜡烛日期分钟。在测试策略时,无法定义当前订单是否是前瞻性的,并且数据库具有类似的参数。但是我们知道测试人员在历史测试之后运行前瞻性的迭代。因此,当每个引用的游泳日期包含变量时,我们会在优化过程结束时找到最终日期。优化参数表具有历史边界参数。它等于保存日期。仅在历史优化期间将行添加到此表中。当使用此参数的第一次迭代(与历史优化迭代相同)时,将日期添加到数据库中所需的字段中。如果在下一次传递中,我们看到数据库中已经存在这些参数的条目,则有两种选择:
- 用户出于某种原因停止历史优化并重新启动它。
- 或者这是一个前瞻性的优化。
为了相互过滤,我们将当前层次结构中存储的最后一个日期与数据库中的日期进行比较。如果当前日期大于数据库中的日期,则这是一个前瞻性的顺序,如果它小于或等于,则您的事务包含历史日期。考虑到优化应该以相同的速度启动两次,我们只将新数据输入数据库或取消当前迭代期间所做的所有更改。ontesterEvent()方法将数据存储在数据库中。其实施方式如下:
//+------------------------------------------------------------------+ //| 保存所有数据至数据库并返回 | //| 一个自定义比率 | //+------------------------------------------------------------------+ double CDBWriter::OnTesterEvent() { DealDetales history[]; CDealHistoryGetter historyGetter; historyGetter.getDealsDetales(history,0,TimeCurrent()); // 获取交易历史 CMutexSync sync; // 同步对象 if(!sync.Create(getMutexName(DBPath))) { Print(Symbol()+" MutexSync create ERROR!"); return 0; } CMutexLock lock(sync,(DWORD)INFINITE); // 将片段锁定在括号内 bool IsForvard=isForvard(); // 找出当前测试器迭代是否为前瞻测试 CReportCreator rc; string Symb[]; rc.Get_Symb(history,Symb); // 获取品种列表 rc.Create(history,Symb,balance,r); // 创建报告(自动创建“购买并持有”报告) double ans=0; dbManager.BeginTransaction(); // 事务开始 CStatement stmt(dbManager.Create_statement("INSERT OR IGNORE INTO ParamsType VALUES(@ParamName,@ParamType);")); // 请求保存 EA 参数类型列表 if(stmt.get()!=NULL) { for(int i=0;i<ArraySize(coef_array);i++) { stmt.Parameter(1,coef_array[i].getName()); stmt.Parameter(2,(int)coef_array[i].getType()); stmt.Execute(); // 保存参数类型及其名称 } } int ID=setParams(IsForvard,&rc,history,ans); // 保存 EA 参数以及评估比率并获取ID if(ID>0)// 如果 ID > 0, 参数保存成功 { if(setTraidingHistory(IsForvard,history,ID)) // 保存交易历史记录并检查是否已保存 { setBuyAndHold(IsForvard,&rc); // 保存买入并持有历史记录(仅保存一次 - 在第一次保存期间) dbManager.CommitTransaction(); // 确认一笔事务结束 } else dbManager.RollbackTransaction(); // 否则,取消事务 } else dbManager.RollbackTransaction(); // 否则,取消事务 return ans; }
这个方法的第一件事是使用我在上一篇文章中描述的类来形成事务历史。然后得到共享资源(互斥)并保存数据。为了实现这一目标,我们首先定义当前的优化层次是否是前瞻性的(根据上面的方法),然后获得一个品种列表(所有交易品种)。
鉴于此,如果实例是测试扩展事务EA,则将加载处理的两个品种的事务历史。之后,生成报告(使用下面评估的类)并将其写入数据库。为正确的记录创建了一个事务。如果在填写任何表格时发生错误或获得不正确的数据,则取消交易。首先,保持这个比例。然后,如果一切顺利,我们将保留事务历史记录。然后我们购买并保存历史。后者在第一次数据输入期间只保存一次。如果发生数据保存错误,将在common/files文件夹中生成日志文件。
创建数据库之后,应该读取它。数据库读取类已在所述程序中使用。它更简单,看起来如下:
//+------------------------------------------------------------------+ //| 从数据库中读取数据的类 | //+------------------------------------------------------------------+ class CDBReader { public: void Connect(string DBPath);// 连接数据库的方法 bool getBuyAndHold(BuyAndHoldChart_item &data[],bool isForvard);// 计算买入并持有历史的方法 bool getTraidingHistory(DealDetales &data[],long ID,bool isForvard);// 计算 EA 交易历史的方法 bool getRobotParams(CoefData_item &data[],bool isForvard);// 计算 EA 参数和比率的方法 private: CSqliteManager dbManager; // 数据库管理器 string DBPath; // 数据库的路径 bool getParamTypes(ParamType_item &data[]);// 计算输入类型及其名称。 };
它实现三个公共方法,读取我们感兴趣的四个表,并使用这些表中的数据创建结构化数组。
- 第一个方法(getbuyandhold)返回buyandhold历史参考,用于基于已通过标志的前瞻性和历史期间测试。如果上载成功,该方法将返回“true”或“false”。从购买和保留表执行上载。
- getTradingHistory方法还返回传递的ID和isForVard标志的事务历史记录。从交易历史表执行上传。
- getrobot params方法结合了两个表上载:从中获取机器人参数的params coefitients和优化参数,其中包括计算出的估值比率。
因此,不是直接访问数据库,而是编写使用隐藏整个算法的类来操作数据库并提供所需数据的类。相反,这些类使用一个写入的包装器来访问数据库,这也简化了工作。提到包装器通过数据库开发人员提供的DLL与数据库一起工作。数据库本身满足所有必要的条件。实际上,它是一个文件,可以很容易地在这个程序和其他分析应用程序中传输和处理。这种方法的另一个优点是,单个算法的长期运行允许您从每个优化中收集数据库,从而积累历史记录并跟踪参数更改模式。
计算
该模块由两个类组成。第一个用于生成事务报告,它是前一篇文章中描述的事务报告类生成的改进版本。
第二个是过滤器类。它在层次范围内对优化样本进行排序,并可以创建一个图表,显示每个优化比率值的盈亏平衡交易频率。该类的另一个目的是为优化结束时的实际交易损益创建正态分布图(即整个优化区间的损益)。换句话说,如果有1000个优化订单,我们有1000个优化结果(损益就是优化的结束)。我们感兴趣的正是基于它们的分布。
分布表明获得的值在哪个方向上不对称偏移。如果机尾大,配送中心位于利润区,机器人产生的优化订单大多是有利可图的,当然是好的,否则会产生最无利可图的订单。如果将不对称性转移到损失区域,则意味着所选参数将导致损失而不是利润。
让我们从生成事务报告的类开始,看看这个模块。描述的类位于History Manager文件夹的include目录中,并具有以下标题:
//+------------------------------------------------------------------+ //| 用于生成交易历史统计的类 | //+------------------------------------------------------------------+ class CReportCreator { public: //============================================================================================================================================= // 计算/重计算: //============================================================================================================================================= void Create(DealDetales &history[],DealDetales &BH_history[],const double balance,const string &Symb[],double r); void Create(DealDetales &history[],DealDetales &BH_history[],const string &Symb[],double r); void Create(DealDetales &history[],const string &Symb[],const double balance,double r); void Create(DealDetales &history[],double r); void Create(const string &Symb[],double r); void Create(double r=0); //============================================================================================================================================= // Getters: //============================================================================================================================================= bool GetChart(ChartType chart_type,CalcType calc_type,PLChart_item &out[]); // 获取盈亏图 bool GetDistributionChart(bool isOneLot,DistributionChart &out); // 获取分布图 bool GetCoefChart(bool isOneLot,CoefChartType type,CoefChart_item &out[]); // 获取比率图 bool GetDailyPL(DailyPL_calcBy calcBy,DailyPL_calcType calcType,DailyPL &out); // 按日获取盈亏图 bool GetRatioTable(bool isOneLot,ProfitDrawdownType type,ProfitDrawdown &out); // 获得极值点表 bool GetTotalResult(TotalResult &out); // 获取 TotalResult 表 bool GetPL_detales(PL_detales &out); // 获取 PL_detales 表 void Get_Symb(const DealDetales &history[],string &Symb[]); // 获取所交易品种的数组 void Clear(); // 清楚统计 private: //============================================================================================================================================= // 私有数据类型: //============================================================================================================================================= // 盈亏图类型的结构 struct PL_keeper { PLChart_item PL_total[]; PLChart_item PL_oneLot[]; PLChart_item PL_Indicative[]; }; // 每日盈亏图的类型结构 struct DailyPL_keeper { DailyPL avarage_open,avarage_close,absolute_open,absolute_close; }; // 极值点表的结构 struct RatioTable_keeper { ProfitDrawdown Total_max,Total_absolute,Total_percent; ProfitDrawdown OneLot_max,OneLot_absolute,OneLot_percent; }; // 用于连续计算利润和损失金额的结构 struct S_dealsCounter { int Profit,DD; }; struct S_dealsInARow : public S_dealsCounter { S_dealsCounter Counter; }; // 用于计算辅助数据的结构 struct CalculationData_item { S_dealsInARow dealsCounter; int R_arr[]; double DD_percent; double Accomulated_DD,Accomulated_Profit; double PL; double Max_DD_forDeal,Max_Profit_forDeal; double Max_DD_byPL,Max_Profit_byPL; datetime DT_Max_DD_byPL,DT_Max_Profit_byPL; datetime DT_Max_DD_forDeal,DT_Max_Profit_forDeal; int Total_DD_numDeals,Total_Profit_numDeals; }; struct CalculationData { CalculationData_item total,oneLot; int num_deals; bool isNot_firstDeal; }; // 用于创建比率图形的结构 struct CoefChart_keeper { CoefChart_item OneLot_ShartRatio_chart[],Total_ShartRatio_chart[]; CoefChart_item OneLot_WinCoef_chart[],Total_WinCoef_chart[]; CoefChart_item OneLot_RecoveryFactor_chart[],Total_RecoveryFactor_chart[]; CoefChart_item OneLot_ProfitFactor_chart[],Total_ProfitFactor_chart[]; CoefChart_item OneLot_AltmanZScore_chart[],Total_AltmanZScore_chart[]; }; // 参与按截止日期排序交易历史的类。 class CHistoryComparer : public ICustomComparer<DealDetales> { public: int Compare(DealDetales &x,DealDetales &y); }; //============================================================================================================================================= // Keepers: //============================================================================================================================================= CHistoryComparer historyComparer; // 比较类 CChartComparer chartComparer; // 比较类 // 辅助结构 PL_keeper PL,PL_hist,BH,BH_hist; DailyPL_keeper DailyPL_data; RatioTable_keeper RatioTable_data; TotalResult TotalResult_data; PL_detales PL_detales_data; DistributionChart OneLot_PDF_chart,Total_PDF_chart; CoefChart_keeper CoefChart_data; double balance,r; // 初始存款和无风险率 // 排序类 CGenericSorter sorter; //============================================================================================================================================= // 计算: //============================================================================================================================================= // 计算盈亏 void CalcPL(const DealDetales &deal,CalculationData &data,PLChart_item &pl_out[],CalcType type); // 计算盈亏直方图 void CalcPLHist(const DealDetales &deal,CalculationData &data,PLChart_item &pl_out[],CalcType type); // 计算用于绘图的辅助结构 void CalcData(const DealDetales &deal,CalculationData &out,bool isBH); void CalcData_item(const DealDetales &deal,CalculationData_item &out,bool isOneLot); // 计算每日盈利/亏损 void CalcDailyPL(DailyPL &out,DailyPL_calcBy calcBy,const DealDetales &deal); void cmpDay(const DealDetales &deal,ENUM_DAY_OF_WEEK etalone,PLDrawdown &ans,DailyPL_calcBy calcBy); void avarageDay(PLDrawdown &day); // 比较品种 bool isSymb(const string &Symb[],string symbol); // 计算盈利因子 void ProfitFactor_chart_calc(CoefChart_item &out[],CalculationData &data,const DealDetales &deal,bool isOneLot); // 计算恢复因子 void RecoveryFactor_chart_calc(CoefChart_item &out[],CalculationData &data,const DealDetales &deal,bool isOneLot); // 计算胜率 void WinCoef_chart_calc(CoefChart_item &out[],CalculationData &data,const DealDetales &deal,bool isOneLot); // 计算锋锐比率 double ShartRatio_calc(PLChart_item &data[]); void ShartRatio_chart_calc(CoefChart_item &out[],PLChart_item &data[],const DealDetales &deal); // 计算分布 void NormalPDF_chart_calc(DistributionChart &out,PLChart_item &data[]); double PDF_calc(double Mx,double Std,double x); // 计算 VaR double VaR(double quantile,double Mx,double Std); // 计算 Z 分数 void AltmanZScore_chart_calc(CoefChart_item &out[],double N,double R,double W,double L,const DealDetales &deal); // 计算 TotalResult_item 结构 void CalcTotalResult(CalculationData &data,bool isOneLot,TotalResult_item &out); // 计算 PL_detales_item 结构 void CalcPL_detales(CalculationData_item &data,int deals_num,PL_detales_item &out); // 从日期中获取天 ENUM_DAY_OF_WEEK getDay(datetime DT); // 清除数据 void Clear_PL_keeper(PL_keeper &data); void Clear_DailyPL(DailyPL &data); void Clear_RatioTable(RatioTable_keeper &data); void Clear_TotalResult_item(TotalResult_item &data); void Clear_PL_detales(PL_detales &data); void Clear_DistributionChart(DistributionChart &data); void Clear_CoefChart_keeper(CoefChart_keeper &data); //============================================================================================================================================= // 复制: //============================================================================================================================================= void CopyPL(const PLChart_item &src[],PLChart_item &out[]); // 复制盈亏图形 void CopyCoefChart(const CoefChart_item &src[],CoefChart_item &out[]); // 复制比率图形 };
与以前的版本不同,这个类计算的数据是以前的两倍,并生成更多类型的图形。重载的“create”方法也计算报表。
实际上,当调用create方法时,报告只生成一次。之后,只在以get word开头的方法中获取先前计算的数据。create方法的参数最多,其主循环只迭代一次输入参数。此方法迭代参数并立即计算一系列数据,根据这些数据,所有必需的数据都在同一迭代中构造。
这允许我们在一次迭代中构建我们感兴趣的所有内容,并且该类的早期版本用于根据初始数据再次迭代图形。因此,所有比率都是以毫秒为单位计算的,获得所需数据所需的时间更少。在类的“私有”区域中,有一系列的结构仅在该类中使用,以方便用作数据容器。使用上面描述的通用排序方法来执行排序事务历史记录。
我们描述了调用每个getter时获得的数据:
方法 | 参数 | 图表类型 |
---|---|---|
获得图表 | 图表类型=pl,计算类型=total | 损益图-基于实际交易历史 |
获得图表 | 图表类型=pl,计算类型=一个批次 | 损益图-单手交易时 |
获得图表 | 图表类型=pl,计算类型=指示性 | 损益图-指示器 |
获得图表 | 图表类型=bh,计算类型=总计 | BH图-如果机器人控制手的数量 |
获得图表 | 图表类型=bh,计算类型=一个批次 | BH图-如果是单手交易 |
获得图表 | 图表类型=bh,计算类型=指示性 | BH图-指示器 |
获得图表 | chart_type=hist_pl,calc_type=total | 损益表-基于实际交易历史 |
获得图表 | chart_type=hist_pl,calc_type=onelot | 损益表-如果单手交易 |
获得图表 | CaltTyType = CythyPL,CalcCyType =指示性 | 光谱学指示器 |
获得图表 | CytTyType = CythyBH,CalcYyType = | BH直方图-如果机器人管理手的数量 |
获得图表 | CaltyType = CythyBH,CalcCyType = OneloT | BH直方图-单手交易 |
获得图表 | CytTyType = CythyBH,CalcCyType =指示性 | BH柱状图-说明 |
获取分布图 | 伊索诺特=真 | 单手交易中的分布和VaR |
获取分布图 | isonelot=错误 | 与以前一样,交易中的分布和VaR |
GETCOFECTRICH | isonelot=true,type=sharatio图表 | 单手交易中的夏普比率 |
GETCOFECTRICH | isonelot=true,type=_Wincoef_图表 | 单手交易中奖率 |
GETCOFECTRICH | isonelot=真,type=恢复系数图 | 单手交易中的恢复系数 |
GETCOFECTRICH | isonelot=true,type=profitfactoru图表 | 单手交易中的利润因素 |
GETCOFECTRICH | isonelot=true,type=_Altmanzscore_图表 | Z-单手交易中的阿尔特曼得分 |
GETCOFECTRICH | isonelot=false,type=sharatio图表 | 交易中的时间尖利比率和我们以前一样 |
GETCOFECTRICH | isonelot=false,type=_Wincoef_图表 | 与我们以前的交易同时的获胜率 |
GETCOFECTRICH | isonelot=false,type=_recoveryFactor_图表 | 时间分割恢复系数与我们以前的交易同时 |
GETCOFECTRICH | isonelot=false,type=profitfactoru图表 | 使用时间盈利因素,与我们以前的交易相同 |
GETCOFECTRICH | isonelot=false,type=AltmanzScore_图表 | Z-Time Altman与我们之前的交易同时得分 |
GETDAYLYPL | calby=calc_表示关闭,calcttype=average_数据 | 收盘时间日平均盈亏 |
GETDAYLYPL | calcBy=calc_表示关闭,calcType=absolute_数据 | 截止日利润总额 |
GETDAYLYPL | calcBy=calc_表示打开,calcType=average_数据 | 开业时间平均日损益 |
GETDAYLYPL | calcBy=calc_表示打开,calcType=absolute_数据 | 开业时每日总损益 |
可测量的 | isonelot=真,type=最大 | 如果是单手交易-每笔交易的最大损益 |
可测量的 | isonelot=真,type=绝对 | 如果是单手交易-总损益 |
可测量的 | isonelot=true,type=percent | 如果是单手交易-损益百分比 |
可测量的 | isonelot=false,type=max | 如果你像以前那样交易-每笔交易的最大利润和损失 |
可测量的 | isonelot=false,type=absolute | 如果你像以前那样交易-总损益 |
可测量的 | isonelot=false,type=percent | 如果你像以前那样交易,那就按损益金额的百分比。 |
获得全部结果 | 比率表 | |
格特普尔 | 损益曲线小结 | |
GETY-SIMB | 交易历史中的品种数组 |
损益图-基于实际交易历史:
这个数字等于通常的损益数字。在所有的测试运行之后,我们可以在终端中看到这一点。
损益图-单手交易时:
这个数字与前面描述的不同交易量的数字类似。计算起来就好像我们是一手交易。进出口价格根据EA市场价格的平均价格和进出口总数计算。交易收益也是根据EA交易的收益计算的,但是它们被转换成收益,就像单手交易通过这个比率一样。
pl图-表示:
正常损益表。如果pl>;0,pl除以此时交易的最大亏损,否则pl除以迄今为止交易的最大利润。
柱状图以类似的方式构造。
分布和VaR
参数var是使用绝对数据和增长来构建的。
分布图也是如此。
比值图。
这个特殊的迭代根据相应的方程遍历整个历史,并在每次迭代中构造。
每日利润图:
表中提到的四种可能的投资组合利润结构。它看起来像一个柱状图。
创建上述所有数据的方法如下:
//+------------------------------------------------------------------+ //| 计算/重新计算比率 | //+------------------------------------------------------------------+ void CReportCreator::Create(DealDetales &history[],DealDetales &BH_history[],const double _balance,const string &Symb[],double _r) { Clear(); // 清除数据 // 保存余额 this.balance=_balance; if(this.balance<=0) { CDealHistoryGetter dealGetter; this.balance=dealGetter.getBalance(history[ArraySize(history)-1].DT_open); } if(this.balance<0) this.balance=0; // 保存无风险比率 if(_r<0) _r=0; this.r=r; // 辅助结构 CalculationData data_H,data_BH; ZeroMemory(data_H); ZeroMemory(data_BH); // 排序交易历史 sorter.Method(Sort_Ascending); sorter.Sort<DealDetales>(history,&historyComparer); // 通过交易历史循环 for(int i=0;i<ArraySize(history);i++) { if(isSymb(Symb,history[i].symbol)) CalcData(history[i],data_H,false); } // 排序购买并持有历史记录和相应的循环 sorter.Sort<DealDetales>(BH_history,&historyComparer); for(int i=0;i<ArraySize(BH_history);i++) { if(isSymb(Symb,BH_history[i].symbol)) CalcData(BH_history[i],data_BH,true); } // 平均每日盈亏(平均类型) avarageDay(DailyPL_data.avarage_close.Mn); avarageDay(DailyPL_data.avarage_close.Tu); avarageDay(DailyPL_data.avarage_close.We); avarageDay(DailyPL_data.avarage_close.Th); avarageDay(DailyPL_data.avarage_close.Fr); avarageDay(DailyPL_data.avarage_open.Mn); avarageDay(DailyPL_data.avarage_open.Tu); avarageDay(DailyPL_data.avarage_open.We); avarageDay(DailyPL_data.avarage_open.Th); avarageDay(DailyPL_data.avarage_open.Fr); // 填写损益表 RatioTable_data.data_H.oneLot.Accomulated_Profit; RatioTable_data.data_H.oneLot.Accomulated_DD; RatioTable_data.data_H.oneLot.Max_Profit_forDeal; RatioTable_data.data_H.oneLot.Max_DD_forDeal; RatioTable_data.data_H.oneLot.Total_Profit_numDeals/data_H.num_deals; RatioTable_data.data_H.oneLot.Total_DD_numDeals/data_H.num_deals; RatioTable_data.Total_absolute.Profit=data_H.total.Accomulated_Profit; RatioTable_data.Total_absolute.Drawdown=data_H.total.Accomulated_DD; RatioTable_data.Total_max.Profit=data_H.total.Max_Profit_forDeal; RatioTable_data.Total_max.Drawdown=data_H.total.Max_DD_forDeal; RatioTable_data.Total_percent.Profit=data_H.total.Total_Profit_numDeals/data_H.num_deals; RatioTable_data.Total_percent.Drawdown=data_H.total.Total_DD_numDeals/data_H.num_deals; // 计算正态分布 NormalPDF_chart_calc(OneLot_PDF_chart,PL.PL_oneLot); NormalPDF_chart_calc(Total_PDF_chart,PL.PL_total); // TotalResult CalcTotalResult(data_H,true,TotalResult_data.oneLot); CalcTotalResult(data_H,false,TotalResult_data.total); // PL_detales CalcPL_detales(data_H.oneLot,data_H.num_deals,PL_detales_data.oneLot); CalcPL_detales(data_H.total,data_H.num_deals,PL_detales_data.total); }
从它的实现中,我们可以看到,部分数据是在迭代历史数据时计算的,而一些数据是根据结构中的数据和所有周期后的数据计算的:计算数据_h,数据_bh。
CalcDATA方法以类似于CREATE方法的方式实现。这是在每次迭代中应该调用的唯一方法来执行计算。最终数据的所有方法计算都基于上述结构中包含的信息。结构的填充/再填充是通过以下方法进行的:
//+------------------------------------------------------------------+ //| 计算辅助数据 | //+------------------------------------------------------------------+ void CReportCreator::CalcData_item(const DealDetales &deal,CalculationData_item &out, bool isOneLot) { double pl=(isOneLot ? deal.pl_oneLot : deal.pl_forDeal); // PL int n=0; // 损益金额 if(pl>=0) { out.Total_Profit_numDeals++; n=1; out.dealsCounter.Counter.DD=0; out.dealsCounter.Counter.Profit++; } else { out.Total_DD_numDeals++; out.dealsCounter.Counter.DD++; out.dealsCounter.Counter.Profit=0; } out.dealsCounter.DD=MathMax(out.dealsCounter.DD,out.dealsCounter.Counter.DD); out.dealsCounter.Profit=MathMax(out.dealsCounter.Profit,out.dealsCounter.Counter.Profit); // 损益序列 int s=ArraySize(out.R_arr); if(!(s>0 && out.R_arr[s-1]==n)) { ArrayResize(out.R_arr,s+1,s+1); out.R_arr[s]=n; } out.PL+=pl; // Total PL // 最大盈利 / 回撤 if(out.Max_DD_forDeal>pl) { out.Max_DD_forDeal=pl; out.DT_Max_DD_forDeal=deal.DT_close; } if(out.Max_Profit_forDeal<pl) { out.Max_Profit_forDeal=pl; out.DT_Max_Profit_forDeal=deal.DT_close; } // 累计利润/ 回撤 out.Accomulated_DD+=(pl>0 ? 0 : pl); out.Accomulated_Profit+=(pl>0 ? pl : 0); // 利润极值点 double maxPL=MathMax(out.Max_Profit_byPL,out.PL); if(compareDouble(maxPL,out.Max_Profit_byPL)==1/* || !isNot_firstDeal*/)// yet another check is required for saving the date { out.DT_Max_Profit_byPL=deal.DT_close; out.Max_Profit_byPL=maxPL; } double maxDD=out.Max_DD_byPL; double DD=0; if(out.PL>0)DD=out.PL-maxPL; else DD=-(MathAbs(out.PL)+maxPL); maxDD=MathMin(maxDD,DD); if(compareDouble(maxDD,out.Max_DD_byPL)==-1/* || !isNot_firstDeal*/)// 另外的检查需要保存日期 { out.Max_DD_byPL=maxDD; out.DT_Max_DD_byPL=deal.DT_close; } out.DD_percent=(balance>0 ?(MathAbs(DD)/(maxPL>0 ? maxPL : balance)) :(maxPL>0 ?(MathAbs(DD)/maxPL) : 0)); }
这是计算每个计算方法的所有输入数据的基本方法。这种方法(将输入数据的计算移动到此方法中)可以避免在事务报告类创建的早期版本中发生的历史循环中过度传递。在CalcData方法中调用此方法。
优化递阶结果过滤器的类具有以下头部:
//+--------------------------------------------------------------------------+ //| 从数据库中卸载它们之后排序优化递次的类 | //+--------------------------------------------------------------------------+ class CParamsFiltre { public: CParamsFiltre(){sorter.Method(Sort_Ascending);} // 默认构造函数 int Total(){return ArraySize(arr_main);}; // 卸载参数总数(根据优化数据表) void Clear(){ArrayFree(arr_main);ArrayFree(arr_result);}; // 清除所有数组 void Add(LotDependency_item &customCoef,CDataKeeper ¶ms[],long ID,double total_PL,bool addToResult); // 向数组添加新值 double GetCustomCoef(long ID,bool isOneLot);// 按 ID 获取自定义比率 void GetParamNames(CArrayString &out);// 获取 EA 参数名称 void Get_UniqueCoef(UniqCoefData_item &data[],string paramName,CArrayString &coefValue); // 获得独有的比率 void Filtre(string Name,string from,string till,long &ID_Arr[]);// 对 arr_result 数组进行排序 void ResetFiltre(long &ID_arr[]);// 重置过滤器 bool Get_Distribution(Chart_item &out[],bool isMainTable);// 通过两个数组创建分布 bool Get_Distribution(Chart_item &out[],string Name,string value);// 按选定数据创建分布 private: CGenericSorter sorter; // 排序器 CCoefComparer cmp_coef;// 比较比率 CChartComparer cmp_chart;// 比较图形 bool selectCoefByName(CDataKeeper &_input[],CDataKeeper &out,string Name);// 按名称选择比率 double Mx(CoefStruct &_arr[]);// 算术平均值 double Std(CoefStruct &_arr[],double _Mx);// 标准偏差 CoefStruct arr_main[]; // 优化数据表等效衡 CoefStruct arr_result[];// 结果表等效 };
分析了类的结构,并详细描述了一些方法。如您所见,这个类有两个全局数组:arr_main和arr_result。数组存储优化的数据。从数据库中卸载包含优化顺序的表后,将其拆分为两个表:
- 主表-除条件排序过程中丢弃的数据外,所有获取的卸载数据
- 结构-为初始选择获得的n个最佳数据。然后,所描述的类对特定表进行排序,并相应地减少或重置条目数。
所描述的数组根据数组名存储EA的ID和参数,以及上表中的一些其他数据。本质上,这个类执行两个函数:一个是方便地操作表来存储数据,另一个是为选定的优化层次结构对结果表进行排序。排序类和两个比较器类涉及到所提出的数组的排序过程以及基于所描述的表的分布式排序的构造。
由于此类类以EA比率运行,即它们以cdatakeeper类的形式表示,因此会创建一个私有方法selectcoefbyname。它选择一个必要的比率,并通过按特定优化顺序传递给EA的比率数组引用返回结果。
考虑到addtoresult=true,add方法向数据库添加一行(两个数组),或者如果addtoresult=false,则仅添加arr_主数组。ID是每个优化顺序的唯一参数,因此所有操作都基于它选择的特定顺序的定义。我们从提供的数组中得到这个参数的用户计算比率。程序本身不知道计算自定义估价的公式,因为估价是在EA优化过程中计算的,没有程序参与。这就是为什么我们需要在这些数组中保存自定义估计。请求时,我们使用getCustomCoef方法获取传递的ID。
最重要的类方法如下:
- filtre-对结果表进行排序,使其包含层次范围(从/到)中选定的比率值。
- resetfiltre-重置整个排序信息。
- get_distribution(chart_item&out[],bool ismaintable)-使用ismaintable参数选择指定的表,根据实际交易损益构造分布。
- GETION分发(CARTTIONESTANDUMENT,String name,String值)创建一个新数组,其中所选参数(name)等于传递的值。换句话说,ARRESREST数组值在循环中顺序传递。在循环的每次迭代中,我们通过它们的名称(使用SELECTCOFEFBYNAMEY函数)从所有EA参数中选择感兴趣的参数。此外,检查该值是否等于所需的值。如果是,则将ARRESREST数组值添加到临时数组中。然后,创建并返回临时数组的分布。换句话说,这就是我们如何选择所有优化级别,其中通过名称选择的参数的值被检测为等于传递的值。这是必要的,以评估这一特定参数对整体EA的影响。代码中已经充分讨论了所描述类的实现,因此我不需要在这里提供这些方法的实现。
伦德尔
广播机用作连接器。这是应用程序的图形层与其上面描述的逻辑之间的连接。在这个应用程序中,广播者使用抽象实现-ipresenter接口。此接口包含所需回调方法的名称;反过来,它们在Broadcaster类中实现,Broadcaster类应继承所需的接口。创建此部分是为了完成应用程序。如果你需要重写播放器模块,你可以很容易地做到,而不影响图形模块或应用程序逻辑。接口描述如下:
//+------------------------------------------------------------------+ //| 演播器接口 | //+------------------------------------------------------------------+ interface IPresenter { void Btn_Update_Click(); // 下载数据并构建整个窗体 void Btn_Load_Click(); // 创建报告 void OptimisationData(bool isMainTable);// 在表中选择优化行 void Update_PLByDays(); // 按天上传损益 void DaySelect();// 从盈亏表中按周内天数选择一天 void PL_pressed(PLSelected_type type);// 按选定历史记录构建盈亏图 void PL_pressed_2(bool isRealPL);// 构建 "其它图表" 图形 void SaveToFile_Click();// 保存数据文件(到沙箱) void SaveParam_passed(SaveParam_type type);// 选择要写入文件的数据 void OptimisationParam_selected(); // 选择优化参数并填写“优化选择”选项卡 void CompareTables(bool isChecked);// 通过结果表构建分布(用于与共用(主要)表的关联) void show_FriquencyChart(bool isChecked);// 显示盈亏频率图 void FriquencyChart_click();// 在比率表中选择一行并构建分布 void Filtre_click();// 按选定条件排序 void Reset_click();// 重置过滤器 void PL_pressed_3(bool isRealPL);// 按结果表中的所有数据构建盈亏图 void PL_pressed_4(bool isRealPL);// 构建统计表 void setChartFlag(bool isPlot);// 从 PL_pressed_3(bool isRealPL) 方法构建(或不构建)图形的条件; };
广播类实现所需的接口,如下所示:
class CPresenter : public IPresenter { public: CPresenter(CWindowManager *_windowManager); // 构造函数 void Btn_Update_Click();// 下载数据并构建整个窗体 void Btn_Load_Click();// 创建报告 void OptimisationData(bool isMainTable);// 在表中选择优化行 void Update_PLByDays();// 按天上传损益 void PL_pressed(PLSelected_type type);// 按选定历史记录构建盈亏图 void PL_pressed_2(bool isRealPL);// 构建 "其它图表" 图形 void SaveToFile_Click();// 保存数据文件(到沙箱) void SaveParam_passed(SaveParam_type type);// 选择要写入文件的数据 void OptimisationParam_selected();// 选择优化参数并填写“优化选择”选项卡 void CompareTables(bool isChecked);// 通过结果表构建分布(用于与共用(主要)表的关联) void show_FriquencyChart(bool isChecked);// 显示盈亏频率图 void FriquencyChart_click();// 在比率表中选择一行并构建分布 void Filtre_click();// 按选定条件排序 void PL_pressed_3(bool isRealPL);// 按结果表中的所有数据构建盈亏图 void PL_pressed_4(bool isRealPL);// 构建统计表 void DaySelect();// 从盈亏表中按周内天数选择一天 void Reset_click();// 重置过滤器 void setChartFlag(bool isPlot);// 从 PL_pressed_3(bool isRealPL) 方法构建(或不构建)图形的条件; private: CWindowManager *windowManager;// 窗口类的引用 CDBReader dbReader;// 操纵数据库的类 CReportCreator reportCreator; // 处理数据的类 CGenericSorter sorter; // 排序类 CoefData_comparer coefComparer; // 数据比较类 void loadData();// 从数据库上传数据并填写表 void insertDataTo_main_Table(bool isResult,const CoefData_item &data[]); // 将数据插入结果表和“Main”表(表内为优化递次比率) void insertRowTo_main_Table(CTable *tb,int n,const CoefData_item &data); // 直接将数据插入优化递次表 void selectChartByID(long ID,bool recalc=true);// 按 ID 选择图形 void createReport();// 创建报告 string getCorrectPath(string path,string name);// 获取文件的正确路径 bool getPLChart(PLChart_item &data[],bool isOneLot,long ID); bool curveAdd(CGraphic *chart_ptr,const PLChart_item &data[],bool isHist);// 将图形添加到其它图表 bool curveAdd(CGraphic *chart_ptr,const CoefChart_item &data[],double borderPoint);// 将图形添加到其它图表 bool curveAdd(CGraphic *chart_ptr,const Distribution_item &data);// 将图形添加到其它图表 void setCombobox(CComboBox *cb_ptr,CArrayString &arr,bool isFirstIndex=true);// 设置组合框参数 void addPDF_line(CGraphic *chart_ptr,double &x[],color clr,int width,string _name=NULL);// 添加分布图的平滑线 void plotMainPDF();// 通过“Main”表构建分布(优化数据) void updateDT(CDropCalendar *dt_ptr,datetime DT);// 更新下拉日历 CParamsFiltre coefKeeper;// 排序优化递次(按分布) CArrayString headder; // 比率表标题 bool _isUpbateClick; // 按下按钮的更新,以及从数据库加载数据的标志 long _selectedID; // 所有选定盈亏图系列的 ID(如果亏损则为红色,如果盈利则为绿色) long _ID,_ID_Arr[];// 上载数据后为结果表所选择的 ID 数组 bool _IsForvard_inTables,_IsForvard_inReport; // 优化递次表中优化数据类型的标志 datetime _DT_from,_DT_till; double _Gap; // 已保存的添加间隙类型(点差/或滑点模拟...) };
每一个回调都经过了很好的讨论,所以这里不需要详细说明。应用程序中唯一需要解释的部分是实现所有表单行为的部分。它包括构建图形、填充组合框、调用从数据库上传和处理数据的方法,以及通过各种类运行的其他操作。
结束语
我们开发了一个处理表的应用程序,可以处理来自测试人员的所有可能的优化参数,并将所有优化添加到EA中的数据库中。除了获得我们所选择的相关参数的详细交易报告外,该程序还允许我们在整个优化历史中的选定时间和给定期间查看所有比率。还可以通过添加间隙参数来模拟滑动点,并查看它们如何影响图和比率的行为。另一个额外的能力是能够在特定的比率区间对优化结果进行排序。
获得100个最佳级别的最简单方法是将CDBWriter类连接到Robot,就像示例EA(在附加文件中)一样,设置条件过滤器(例如,利润系数>;=1立即排除所有损失等),然后单击“更新”将“显示n参数”参数保留为100。在这种情况下,结果表显示100个优化订单(根据您的过滤器)。下一篇文章将详细讨论结果应用程序的每个选项,以及选择比率的更精确方法。
所附文件如下:
专家/ 2Mai-Mald-测试EA项目
- 2ma_martin.mq5-ea模板代码。mqh包含将优化数据保存到数据库的文件。
- robot.mq5-ea逻辑
- 机器人。mqh-在robot中实现的头文件。MQ5文件
- trade.mq5-ea事务逻辑
- trade.mqh-在trade.mq5文件中实现的头文件
专家/优化选择器描述应用程序
- optimizationselector.mq5——调用整个项目代码的EA模板
- 副过滤器。MQ5-按结果表筛选和分发
- 副过滤器。mqh-在paramsfiltre中实现的头文件。MQ5文件
- 演示者.mq5-广播者
- 节目主持人。mqh-演示者中实现的头文件。MQ5文件
- Presenter_interface.mqh-广播接口
- window_1.mq5-图形
- windows_1.mqh-在windows_1.mq5文件中实现的头文件
包含/customgeneric
- 通用分拣机MQH-数据排序
- icustomcomparer.mqh-icustomorter接口
包括/历史记录管理器
- 经销商历史记录。mqh-从终端卸载事务历史记录并将其转换为所需视图
- 报告创建程序mqh-用于创建事务历史记录的类
包括/优化选择器
- 数据管理员。mqh-一个类,它存储具有关联比率名称的EA比率
- DBReader。mqh-从数据库中读取所需表的类
- DBWriter。mqh-写入数据库的类
包含/SqLITE3
- sqlite_amalgmation.mqh–导入用于处理数据库的函数
- sqlitemanager.mqh——用于连接数据库和语句的类
- SqliteReader。mqh——从数据库中读取响应的类
Include/WinApi
- MimcPy。mqh-导入memcpy函数
- Mutex。mqh-导入mutex以创建函数
- Strcpy。mqh-导入strcpy函数
- strlen.mqh-导入strlen函数
图书馆
- sqlite 3_32.dll-用于32位终端的sqlite dll
- sqlite 3_64.dll-用于64位终端的sqlite dll
附带条件
- 2ma_Martin优化数据sqlite数据库
本文由MetaQuotes Software Corp.翻译自俄语原文
,网址为https://www.mql5.com/ru/articles/5214。
MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。