English Русский Español Deutsch 日本語 Português
preview
帧分析器(Frames Analyzer)工具带来的时间片交易魔法

帧分析器(Frames Analyzer)工具带来的时间片交易魔法

MetaTrader 5测试者 | 13 二月 2023, 15:23
1 190 0
Anatoli Kazharski
Anatoli Kazharski

内容


概述

在本文中,我想向您展示一个相当有趣的工具,它可以让您更深入地查看任何交易算法的优化结果。 甚至能够以最少的工作量和成本改善您的真实自动交易的结果。

什么是帧分析器(Frames Analyzer)? 这是适用于任意智能系统的一个插件库,可在测试器内部、以及测试器外部,参数优化之后立即读取测试创建的 MQD 文件、或数据库,并分析优化帧数据。

这是一个独家解决方案,这种形式在任何地方都前所未见,尽管它已在三年多前就实现了。 这个思路由 MQL 社区的活跃成员 fxsaber 首先详细定式,并在代码中实现。

帧分析优化应用程序的一些过度实现在我的一些文章中已经有所涵盖:

结合所有这些思路,获得了一个相当有趣、且信息丰富的工具。 在本文中将详细涵盖。


工具说明

该模块包含一个由 EasyAndFastGUI v2.0 图形库创建的嵌入式图形界面,不需要额外的编码。 我们需要连接到 MQL 应用程序的几个主要功能(如下所示)。

图形界面由若干个板块组成。 我们来看看它们。


"帧"选卡

元素:

  • Open DB 按钮打开包含优化帧数据的数据库。 此按钮仅显示在帧分析器当中。 故此,在测试器之外,EA 以读取模式基于由其创建的参数优化数据库文件工作。 
  • Open MQD-file 按钮,打开包含优化帧数据的 MQD 文件。 此按钮仅显示在帧分析器当中。 故此,在测试器之外,EA 以读取模式基于由其测试器创建的 MOD 文件工作(在付费分发的版本中这一项会被剔除)。
  • Curves total 输入字段,指定图表上同时显示的余额曲线数量。
  • Replay frames 按钮开始重播帧数据。
  • 余额图(优化结果)。
  • 所有结果的图表(盈利/亏损)。

如果帧分析器模块作为函数库连接到 EA,则在参数优化期间,帧分析器 EA 的图形会在测试器中打开,并可立即观察所有中间结果(余额)。 

在参数优化期间可视化结果

在参数优化期间可视化结果


"结果"选卡

顶部有两个表格:

  • Add to favorites 按钮,将结果加入收藏。
  • Removed intervals 输入字段,指定无盈利时间片序列的排除数量。
  • 含复选框的 Criterion 下拉列表,根据指定准测选择结果:
    • Profit - 按最大余额从总数中选择 100 个结果(其它准则同样如此)。
    • Trades - 按最大交易次数选择 100 个结果。
    • DD (drawdown) - 按最小回撤选择 100 个结果。
    • RF (recovery factor) - 按最大恢复率选择 100 个结果。
    • 带有 BI_ 前缀的相同准则,显示排除无盈利交易序列后的相同值。
  • 取最大余额的前 100 个最佳结果的表格。 与改进的结果相关的列单元格(具有 BI_ 前缀)拥有不同的颜色,以便能快速、直观地区分它们(参见下面的屏幕截图)。 含有外部参数的列单元格也会以其自己的颜色高亮显示,以便能快速识别它们。
  • 表格包含从交易历史的所有中间结果删除亏损周期后的结果。

当鼠标光标悬停在特定表格之上时,您可用以下按键切换行:PgUp(上翻页)PgDn(下翻页)Home(首页)、和End(尾页) 方向键允许您从头到尾快速水平导航表格。

在底部,有一个区域有两个选项卡:余额前 100 个结果。 我们来看看它们。


"余额"选卡

这里有两个图形:

  • 在左侧,图形内含初始余额和删除了亏损系列交易之后的所有中间改进结果。 我们能看出排除无盈利序列后余额图如何变化。
  • 在右侧,图形内含改进的最终余额(左侧图表中最好的),以及每个交易周期的所有余额。

图形还会依据表中高亮显示的行来更新。

最终的结果余额和去除无盈利时间片后的改进余额

最终的结果余额和去除无盈利时间片后的改进余额


"前 100 个结果"选卡

这里还有两个图形:

  • 在左侧,图形是前 100 个盈利结果。
  • 在右侧,图形是按指定准则的前 100 个结果:Profit, Trades, DD, RF, BI_Profit, BI_Trades, BI_DDBI_RF

表中高亮显示的行将更新图形中的结果。 例如,下面我们可以看到,左侧高亮显示的结果(黑色余额曲线),以及右侧图形是排除一系列亏损交易后的结果。

去除无盈利时间片后的 100 个最佳最终和改进余额

去除无盈利时间片后的 100 个最佳最终和改进余额

我们还能指定从历史记录中删除的无盈利时间片的数量,从而改善结果。

假设继续采用这些 EA 参数进行交易,同时排除检测到的无利可图时间片的一系列交易,交易者可以显著改善他们的交易结果。 事实上,即使是看似无盈利的交易算法有时也可能经由这种方法获利。

如上所述,您可自行把喜欢的结果保存到相应的表中。 为此,点击 Add to favorites 按钮。 添加的结果在整个结果表中以不同的颜色高亮显示。

添加到收藏夹的结果以不同的颜色高亮显示

添加到收藏夹的结果以不同的颜色高亮显示


"收藏"选卡

此选项卡还有两个表和两个图形。 所有参数背后的思路与  Results 选项卡中的相同。 唯一的区别是这些选定的结果可以保存到文件(EA 集),和数据库当中。 此处,除了表格和图形之外,以下按钮位于窗口的上部:

  • Delete selected - 从左侧表格的收藏中删除所选结果,同时一并从数据库中删除。
  • Delete all - 从左侧表格中的收藏中,以及数据库中删除所有结果。
  • Save parameters - 将参数保存到文件和数据库。

所选结果的数据和图形

所选结果的数据和图形

单击  Save parameters 将所有选定结果存到收藏:

  • 存至数据库的 FAVORITE_RESULTS 表中
  • 若 MQD 文件处于读取模式时,存至位于  MQL5/Files/Reports/FramesA/[CURRENT_TIME] 的文件
  • 若为  FRAME_MODE  模式(优化后立即),MQL5/Files/Reports/[CURRENT_TIME] [EXPERT_NAME]

所有结果都保存到单独的文件夹当中,其中验算编号用作文件夹名称。 它是一个包含外部 EA 参数 set 文件,和上面讨论的表格和图形的几个屏幕截图。 验算编号用作所有文件名中的前缀:

  • 422462.set -  set 文件含有外部 EA 参数。
  • 422462_balance_sub_bi.png - 排除所有无盈利交易时间片后的余额屏幕截图,以及其余每个时间片的所有余额。
  • 422462_balances_bi.png - 排除所有无盈利交易时间片后的所有余额的屏幕截图。
  • 422462_bi_table.png - 表格的屏幕截图,其中包含排除所有无盈利交易序列的时间片后的所有最终结果。
  • 422462_gui.png - 完整的 GUI 屏幕截图。

包含 EA 参数的 set 文件示例:

; this file contains last used input parameters for testing/optimizing FA expert advisor
; Experts\Advisors\ExpertMACD.ex5
;
Inp_Expert_Title=ExpertMACD||0.0||0.0||0.0||N
Inp_Signal_MACD_PeriodFast=15||5.0||5.0||30.0||Y
Inp_Signal_MACD_PeriodSlow=25||5.0||5.0||30.0||Y
Inp_Signal_MACD_PeriodSignal=25||5.0||5.0||30.0||Y
Inp_Signal_MACD_TakeProfit=270||30.0||5.0||300.0||Y
Inp_Signal_MACD_StopLoss=115||20.0||5.0||200.0||Y


优化结果数据库

如上所述,在测试器中优化参数完毕后,帧分析器立即将所有优化结果保存到数据库之中。 但这还不是全部! 每次参数优化后,都会在终端 MQL5/Files/DB 的本地目录中创建一个新的数据库。 数据库的名称由创建时的当前时间和 EA 的名称组成:[CURRENT_TIME] [EXPERT_NAME].db

总共有三个数据表:

OPTIMIZATION_RESULTS 表存储以下数据(列):

  • Pass - 验算索引。
  • Profit - 所获盈利。
  • Trades - 交易数量。
  • PF - 盈利因子。
  • DD - 回撤。
  • RF - 恢复因子。
  • 包含所有 EA 外部参数的列:
    • 参数 1
    • 参数 2
    • 与此类推
  • Deals - 计算每次验算余额的交易历史。

包含所有优化验算数据的表

包含所有优化验算数据的表

EXPERT_PARAMETERS 表。 该表包含 EA 外部参数,以及其名称和参数优化期间所在目录的路径。 该表有两列:

  • Parameter - 参数名
  • Value - 参数值。

下面是终端标准发行版的 ExpertMACD EA 示例。 对于外部参数(INPUT_n),Value 列保存此次优化的外部参数、和优化参数的名称(用 || 分隔)。

EA 数据表

EA 数据表

EA 操作所需的一些其它参数稍后可以添加到表中。

FAVORITE_RESULTS 表。 被选为收藏的优化结果都会保存在此处。 这里只有三列:

  • FavoriteID - 位置 ID.
  • Pass - 验算索引,我们希望获取 OPTIMIZATION_RESULTS 表中的所有数据。
  • RemovedIntervals - 若真实交易的时间片被禁止,则删除。

删除时间片后的选定优化结果表

删除时间片后的选定优化结果表

因此,我们可以在优化和分析所获结果后,从数据库中得到未来 EA 操作所需的一切。


如何使用帧分析器工具?

首先,从本页下载帧分析器。 如果您现在在终端中运行此 EA,您将看到所有帧分析器图形和表格都是空的,因为此工具需要优化结果。 如果您想尝试一下,请下载文后附带的优化结果数据库文件。 将文件放置于 MQL5/Files/DB。 现在您就可用帧分析器 EA 的图形界面加载它。 在 Frames 选卡上,单机 Open DB。 对话框立即打开,允许您选择如下所示的数据库文件。 我在这个目录中已经放好若干个数据库文件。

在对话框中选择数据库文件

在对话框中选择数据库文件

为了让帧分析器保存 EA 的优化结果,您需要将其作为函数库包含到 EA 代码之中。 下面是一个详细示例。

帧分析器下载到您的计算机后,您可以在 MQL5/Experts/Market 中找到它。 然后,您可以以如下方式将其包含在 EA 的主文件(*.mq5)当中:

#import "..\Experts\Market\FramesA.ex5"
  void OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
  void OnTesterEvent(void);
  void OnTesterInitEvent(void);
  void OnTesterPassEvent(void);
  void OnTesterDeinitEvent(void);
#import

正如我们在上面看到的,我们需要从帧分析器 EA 导入若干个函数。 它们需要处理 GUI 操作中的事件,并在测试器中优化 EA 参数期间收集数据。 您所要做的就是在类似的 EA 函数中调用这些函数,如下所示:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
  FramesA::OnEvent(id, lparam, dparam, sparam);
}
//+------------------------------------------------------------------+
//| Test completion event handler                                    |
//+------------------------------------------------------------------+
double OnTester(void) {
  FramesA::OnTesterEvent();
  return(0.0);
}
//+------------------------------------------------------------------+
//| TesterInit function                                              |
//+------------------------------------------------------------------+
void OnTesterInit(void) {
  FramesA::OnTesterInitEvent();
}
//+------------------------------------------------------------------+
//| TesterPass function                                              |
//+------------------------------------------------------------------+
void OnTesterPass(void) {
  FramesA::OnTesterPassEvent();
}
//+------------------------------------------------------------------+
//| TesterDeinit function                                            |
//+------------------------------------------------------------------+
void OnTesterDeinit(void) {
  FramesA::OnTesterDeinitEvent();
}
//+------------------------------------------------------------------+

如您所见,一切都非常简单。

顺便说一下,如果您在项目中使用 EasyAndFastGUI v2.0 图形库来创建图形界面(该图形库是帧分析器的一部分),那么无需执行任何其它操作,即可让它们在同一个 MQL 应用程序中协同工作。 内置于 EA 中的帧分析器在帧模式 (FRAME_MODE) 下单独工作,不会干扰应用程序图形界面的操作。

即使您没有 EasyAndFastGUI v2.0 图形库,帧分析器仍然可以工作。

文后附带来自标准发行版的 ExpertMACD EA 示例,并内置帧分析器模块。


在智能系统中应用被移除的时间片

好了,您已拥有了帧分析器工具。 您已将其连接到您的交易 EA,执行参数优化,现在您得到一个数据库,其中包含所有验算的结果,以及所有外部参数的值。 在单独的数据表中,您保存了您最喜欢的优化结果,并删除了时间片,现在可将其应用于您的交易策略。

我们来看看如何实现这一点。

就是说您打算通过图形用户界面实现时间交易范围的选择。 最好图形界面中能有一个元素直观示意被删除的时间片,意即不建议在此期间进行交易。 如果您在创建高级 GUI 时用到 EasyAndFastGUI 2.0 图形库,那么恭喜您有这样的机会。 在最新的更新之一中,添加了另一个独特的元素 — CTimeRanges。 它允许您依据时间片操作。 接下来,我们将利用此元素创建一个 GUI,并对其进行更详细的研究。

GUI 将包含以下元素:

  • 控件窗体 - CWindow
  • 打开数据库文件的按钮 - CButton
  • 来自所选优化结果的验算下拉列表 - CComboBox
  • 时间标尺显示不建议交易的时片 - CTimeRanges

若要创建这样的 GUI,您只需要几行代码:

void CApp::CreateGUI(void) {

//--- Form
  m_window1.ResizeMode(true);
  m_window1.ThemeButtonIsUsed(true);
  CCoreCreate::CreateWindow(m_window1, "TIME TRADE RANGES", 1, 1, 350, 100, true, true, true, true);
  
//--- Button
  CCoreCreate::CreateButton(m_button1, m_window1, 0, "Open DB...", 10, 30, 100);
  
//--- Combobox
  string items1[] = {"12345", "19876", "45678", "23456", "67890"};
  CCoreCreate::CreateCombobox(m_combobox1, m_window1, 0, "Passes: ", 120, 30, 135, 90, items1, 103, 0);
  
//--- Time ranges
  string time_ranges[] = {
    "00:45:00 - 01:20:01",
    "08:55:00 - 09:25:01",
    "12:55:00 - 13:50:01",
    "15:30:00 - 17:39:59",
    "20:10:00 - 21:05:00"
  };
  m_time_ranges1.SetTimeRanges(time_ranges);
  m_time_ranges1.AutoXResizeMode(true);
  m_time_ranges1.AutoXResizeRightOffset(5);
  CCoreCreate::CreateTimeRanges(m_time_ranges1, m_window1, 0, 5, 60, 390);
}

上面的示例演示您如何将 设置时间片 转至 TimeRanges 元素。

启动具有此类图形界面的 MQL 应用程序,您将看到以下内容:

来自 EasyAndFastGUI v2.0 图形库的 TimeRanges 元素

来自 EasyAndFastGUI v2.0 图形库的 TimeRanges 元素

上面的屏幕截图展示了一个 GUI 元素,该元素是日内时间标尺。 这是一个动态元素,可以自动调整到父元素的宽度(在本例中,它是窗体)。 虚线标记当前时间,附近有显示当前时间的文本标签。 如果鼠标光标悬停在时间标尺上,则会在鼠标光标下方绘制一条垂直实线,且在光标指向处有含时间的文本标签。 

它揭示了很多信息,但这还不是全部! 如果我们点击标尺,该元素将生成一个带有 ON_CLICK_TIME_RANGE ID 的自定义事件,该事件可以在自定义 MQL 应用程序中进行处理。 dparam 参数将包含自一天开始以来流逝的秒数,而 sparam 参数将包含已删除的时间范围,或在单期间,光标下方未设置任何范围,则为空字符串。 此外,您可启用只需单击一下即能将 TimeRanges 元素扩展到整个图表的模式。

此处是允许您拦截和处理此事件的代码:

void CApp::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {

  if(id == CHARTEVENT_CUSTOM + ON_CLICK_TIME_RANGE) {
    
    if(m_time_ranges1.Id() == lparam) {
      Print(__FUNCTION__, " > pressed time: ", ::TimeToString((datetime)dparam, TIME_MINUTES|TIME_SECONDS), "; pressed time range: ", sparam);
      
      string time_ranges[];
      m_time_ranges1.GetTimeRanges(time_ranges);
      
      ArrayPrint(time_ranges);
      return;
    }
    return;
  }
}

正如您在上面的代码清单中所见,我们首先检查事件的 id,如果这是 ON_CLICK_TIME_RANGE 用户事件,我们检查在事件 lparam 参数中传递的元素 ID。 接下来,将获得的数据发送到日志之中。 该示例还演示如何使用 CTimeRanges::GetTimeRanges() 方法获取元素中设置的时间片,并将获得的数组显示到日志。

在 EA 日志 (Experts 选卡) 中,您将看到如下所示的内容:

CApp::OnEvent > pressed time: 16:34:54; pressed time range: 15:30:00 - 17:39:59
"00:45:00-01:20:01" "08:55:00-09:25:01" "12:55:00-13:50:01" "15:30:00-17:39:59" "20:10:00-21:05:00"

下面的示例演示如何从数据库中的收藏夹结果表 (FAVORITE_RESULTS) 中提取验算索引数组。

void CApp::GetPassNumbersOfFavoriteResults(const string db_filename, ulong &passes[]) {

  ::ResetLastError();
  uint flags = DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE;
  int db_handle = ::DatabaseOpen(db_filename, flags);
  if(db_handle == INVALID_HANDLE) {
    ::Print(__FUNCTION__, " > DB: ", db_filename, " open failed with code: ", ::GetLastError());
    return;
  }
  
  string command = "SELECT (Pass) FROM FAVORITE_RESULTS;";
  
  int db_query = ::DatabasePrepare(db_handle, command);
  if(db_query == INVALID_HANDLE) {
    ::Print(__FUNCTION__, " > DB: ", db_filename, " request failed with code ", ::GetLastError());
    ::DatabaseClose(db_handle);
    return;
  }

  for(int i = 0; ::DatabaseRead(db_query); i++) {
    int pass_number;
    ::ResetLastError();
    if(::DatabaseColumnInteger(db_query, 0, pass_number)) {
      int prev_size = ::ArraySize(passes);
      ::ArrayResize(passes, prev_size + 1);
      passes[prev_size] = (ulong)pass_number;
    } 
    else {
      ::Print(__FUNCTION__, " > DatabaseColumnInteger() error: ", ::GetLastError());
    }
  }
//--- Finish working with the database
  ::DatabaseFinalize(db_query);
  ::DatabaseClose(db_handle);
}

收到验算索引后,我们可以调用 GetFavoriteRemovedIntervalsByPassNumber() 方法依据指定验算索引获取删除的时间片:

string CApp::GetFavoriteRemovedIntervalsByPassNumber(const ulong pass_number) {

  ::ResetLastError();
  uint flags = DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE;
  int db_handle = ::DatabaseOpen(m_db_filename, flags);
  if(db_handle == INVALID_HANDLE) {
    ::Print(__FUNCTION__, " > DB: ", m_db_filename, " open failed with code: ", ::GetLastError());
    return(NULL);
  }
  
  string command = "SELECT (RemovedIntervals) FROM FAVORITE_RESULTS WHERE Pass=" + (string)pass_number + ";";
  
  int db_query = ::DatabasePrepare(db_handle, command);
  if(db_query == INVALID_HANDLE) {
    ::Print(__FUNCTION__, " > DB: ", m_db_filename, " request failed with code ", ::GetLastError());
    ::DatabaseClose(db_handle);
    return(NULL);
  }
  
  string time_ranges = "";
  
  if(::DatabaseRead(db_query)) {
    ::ResetLastError();
    if(!::DatabaseColumnText(db_query, 0, time_ranges)) {
      ::Print(__FUNCTION__, " > DatabaseColumnText() error: ", ::GetLastError());
    }
  }
//--- Finish working with the database
  ::DatabaseFinalize(db_query);
  ::DatabaseClose(db_handle);
  
  return(time_ranges);
}

当单击 Open DB... 打开包含数据库的文件时,将调用 OnClickOpenDB()。 该方法打开对话框窗口,以便选择 MQL5/Files/DB 中的文件。 如果选择了文件,则会保存该文件,以供将来在其它方法中所有。 接下来,我们调用 GetPassNumbersOfFavoriteResults() 方法,其代码显示在前面的清单中,从收藏夹结果表中获取验算索引。 将此数据添加到下拉列表中,然后选择第一项。

bool CApp::OnClickOpenDB(const int id) {

  if(m_button1.Id() != id) {
    return(false);
  }

  string filenames[];
  string folder = "DB";
  string filter = "DB files (*.db)|*.db|SQLite files (*.sqlite)|*.sqlite|All files (*.*)|*.*";
  if(::FileSelectDialog("Select database file to upload", folder, filter,
                        FSD_FILE_MUST_EXIST, filenames) > 0) {
    
    int filenames_total = ::ArraySize(filenames);
    if(filenames_total < 1) {
      return(false);
    }
    
    m_db_filename = filenames[0];
    
    ::Print(__FUNCTION__, " > m_db_filename: ", m_db_filename);
    
    ulong passes[];
    GetPassNumbersOfFavoriteResults(m_db_filename, passes);
    
    ::ArrayPrint(passes);
    
    CListView *list_view = m_combobox1.GetListViewPointer();
    
    int passes_total = ::ArraySize(passes);
    if(passes_total > 0) {
      list_view.Clear();
      for(int i = 0; i < passes_total; i++) {
        list_view.AddItem(i, (string)passes[i]);
      }
      m_combobox1.SelectItem(0);
      m_combobox1.GetButtonPointer().Update(true);
      list_view.Update(true);
    }
  }
  return(true);
}

在下拉列表中选择验算索引,我们将在 SetTimeRange() 方法中获得删除的时间片,将它们保存在全局数组中,并在 CTimeRange 类型的图形元素中设置它们。

void CApp::SetTimeRange(void) {

  CListView *list_view = m_combobox1.GetListViewPointer();
  
  int   index       = list_view.SelectedItemIndex();
  ulong pass_number = (int)list_view.GetValue(index);
  
  string removed_intervals = GetFavoriteRemovedIntervalsByPassNumber(pass_number);
  
  ::ArrayFree(m_time_ranges_str);
  ::StringSplit(removed_intervals, ::StringGetCharacter("|", 0), m_time_ranges_str);
  
  int ranges_total = ::ArraySize(m_time_ranges_str);
  for(int i = 0; i < ranges_total; i++) {
    ::StringReplace(m_time_ranges_str[i], " - ", "-");
  }
  
  ::ArrayPrint(m_time_ranges_str);
  
  m_time_ranges1.SetTimeRanges(m_time_ranges_str);
  m_time_ranges1.Update();
}

接下来,我们需要一个结构来存储时间范围。 如果您正在使用 EasyAndFastGUI 2.0 图形库,则 CTimeRanges 元素文件中已存在这样的结构(TimeRange):

struct TimeRange {
  MqlDateTime start;
  MqlDateTime end;
};

若您未使用此图形库,则可以在代码中单独声明该结构。

由于删除的时间范围已经在 SetTimeRange() 方法中以字符串形式保存,现在我们需要将它们设置到结构数组,从而更方便处理。 为此,调用 GetTimeRanges() 方法:

void CApp::GetTimeRanges(TimeRange &ranges[]) {
  
  ::ArrayFree(ranges);
  
  int ranges_total = ::ArraySize(m_time_ranges_str);
  for(int i = 0; i < ranges_total; i++) {
    
    string tr[];
    ::StringSplit(m_time_ranges_str[i], ::StringGetCharacter("-", 0), tr);
  
    int size = ::ArraySize(ranges);
    ::ArrayResize(ranges, size + 1);
    
    MqlDateTime start, end;
    ::TimeToStruct(::StringToTime(tr[0]), start);
    ::TimeToStruct(::StringToTime(tr[1]), end);
    
    datetime time1 = HourSeconds(start.hour) + MinuteSeconds(start.min) + start.sec;
    datetime time2 = HourSeconds(end.hour) + MinuteSeconds(end.min) + end.sec;
    ::TimeToStruct(time1, ranges[size].start);
    ::TimeToStruct(time2, ranges[size].end);
  }
}

例如,在处理相应的事件(应用程序类的缩短版本)时,可以按以下方式调用这些方法:

class CApp : public CCoreCreate {
 private:
  TimeRange         m_time_ranges[];
  string            m_time_ranges_str[];
  
...

  virtual void      OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

  void              SetTimeRange(void);
  void              GetTimeRanges(TimeRange &ranges[]);
  
  int               HourSeconds(const int hour)     { return(hour * 60 * 60); }
  int               MinuteSeconds(const int minute) { return(minute * 60);    }
};

void CApp::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {

  if(id == CHARTEVENT_CUSTOM + ON_CLICK_BUTTON) {
    if(OnClickOpenDB((int)lparam)) {
      SetTimeRange();
      GetTimeRanges(m_time_ranges);
      return;
    }
    return;
  }
  
  if(id == CHARTEVENT_CUSTOM + ON_CLICK_COMBOBOX_ITEM) {
    if(OnSelectPass((int)lparam)) {
      SetTimeRange();
      GetTimeRanges(m_time_ranges);
      return;
    }
    return;
  }
}

现在我们可以实现 CheckTradeTime() 方法来检查交易是否在允许的时间执行:

bool CApp::CheckTradeTime(void) {

  bool     is_trade_time = true;
  datetime current_time  = ::TimeCurrent();
  
  MqlDateTime time;
  ::TimeToStruct(current_time, time);
  
  int ranges_total = ::ArraySize(m_time_ranges);
  for(int i = 0; i < ranges_total; i++) {
  
    MqlDateTime start = m_time_ranges[i].start;
    MqlDateTime end   = m_time_ranges[i].end;
    
    datetime time_c = HourSeconds(time.hour) + MinuteSeconds(time.min) + time.sec;
    datetime time_s = HourSeconds(start.hour) + MinuteSeconds(start.min) + start.sec;
    datetime time_e = HourSeconds(end.hour) + MinuteSeconds(end.min) + end.sec;
    
    if(time_c >= time_s && time_c <= time_e) {
      is_trade_time = false;
      break;
    }
  }
  return(is_trade_time);
}

您现在所有要做的就是设置 CheckTradeTime() 方法的调用,例如,在 OnTick() 函数当中:

  if(CheckTradeTime()) {
    Print(__FUNCTION__, " > trade time!");
  }

现成的应用程序附在文章末尾。

接收从交易中删除的时间片

接收从交易中删除的时间片


结束语

在策略测试器中优化参数后,我们必须运行单个测试来查验每个结果。 这非常耗时,且效率低下。

什么是帧分析器(Frames Analyzer)? 这是适用于任意智能系统的一个插件模块,在策略测试器中、以及测试器之外进行参数优化期间,该工具在参数优化完成后立即读取测试创建的 MQD 文件、或数据库,并分析优化帧数据。 您将能够与拥有帧分析器的其他用户共享这些优化结果,从而讨论结果。

帧分析器允许您以图形的形式同时查看前 100 个优化结果。 这些前 100 个结果可依据各种准则获得:盈利交易次数回撤盈利因子、和恢复因子。 甚至,帧分析器工具拥有内置的最佳片段模块来判定无盈利的时间片,从而将它们从交易历史记录中删除,可令您查看若在这些不会盈利的时间片内进行交易会是什么结果。

您可将自己喜欢的结果保存到数据库当中,也能以现成的,含有 EA 外部参数的 set 文件的形式保存。 删除的时间片也会保存在数据库当中。 故此,您可在交易中应用它们,以便令您的盈利最大化。 Frames Analyzer 只保留统计证明最安全的时间片!


本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/11667

附加的文件 |
MQL5.zip (5545.43 KB)
种群优化算法:蚁群优化(ACO) 种群优化算法:蚁群优化(ACO)
这次我将分析蚁群优化算法。 该算法非常有趣且复杂。 在本文中,我尝试创建一种新型的 ACO。
DoEasy. 控件 (第 24 部分): 提示(Hint)辅助 WinForms 对象 DoEasy. 控件 (第 24 部分): 提示(Hint)辅助 WinForms 对象
在本文中,我将修改为所有 WinForms 函数库对象指定基准对象和主对象的逻辑,并开发一个新的提示(Hint)基准对象,及其若干个派生类,用以示意移动隔板的可能方向。
创建一个行情卷播面板:基本版 创建一个行情卷播面板:基本版
在此,我将展示如何创建价格播报屏幕,它通常在交易所滚动显示报价。 我会只用 MQL5 来做到这一点,无需复杂的外部编程。
自适应指标 自适应指标
在本文中,我将研究创建自适应指标的若干种可能方式。 自适应指标的区别在于输入值和输出信号之间存在反馈。 这种反馈令指标能够独自调整到处理金融时序数据的最优状态。