打开和关闭图表

MQL 程序不仅可以分析图表列表,还可以对其进行修改:打开新图表或关闭现有图表。针对这些目的,分配了两个函数:ChartOpenChartClose

long ChartOpen(const string symbol, ENUM_TIMEFRAMES timeframe)

该函数会打开一个含指定交易品种和时间范围的新图表,并返回新图表的 ID。如果执行期间发生错误,结果为 0,并且可以在内置变量 _LastError中读取错误代码。

如果symbol参数为 NULL,则表示当前图表(正在执行 MQL 程序的图表)的交易品种。timeframe参数中的 0 值对应 PERIOD_CURRENT。

终端中同时打开的图表最大数量不能超过 CHARTS_MAX(100 个)。

在下一节中,在学完用于处理 tpl 模板的函数后,我们将看到一个使用 ChartOpen函数的示例。

请注意,终端不仅允许创建带有图表的完整窗口,还允许创建 图表对象。它们和其他图形对象(如趋势线、通道、价格标签等)一样,放置在普通图表内部。图表对象允许在一个标准图表中同时显示多个不同交易品种和时间范围的价格序列小片段。

bool ChartClose(long chartId = 0)

该函数用于关闭指定 ID 的图表(默认值 0 表示当前图表)。该函数返回一个成功状态指示值。

作为示例,我们将实现 ChartCloseIdle.mq5脚本,该脚本将关闭那些包含重复交易品种和时间周期组合但未包含 MQL 程序和图形对象的重复图表。

首先,我们需要创建一个列表来统计每个交易品种/时间范围对的图表数量。该任务由 ChartIdleList函数实现,它与我们在ChartList.mq5 脚本中看到的非常相似。该列表本身是在 MapArray<string,int> chartCounts映射数组中形成的。

#include <MQL5Book/Periods.mqh>
#include <MQL5Book/MapArray.mqh>
   
#define PUSH(A,V) (A[ArrayResize(AArraySize(A) + 1) - 1] = V)
   
void OnStart()
{
   MapArray<string,intchartCounts;
   ulong duplicateChartIDs[];
   // collect duplicate empty charts
   if(ChartIdleList(chartCountsduplicateChartIDs))
   {
      ...
   }
   else
   {
      Print("No idle charts.");
   }
}

与此同时,ChartIdleList函数会将符合关闭条件的空闲图表标识符填充到 duplicateChartIDs 数组中。

int ChartIdleList(MapArray<string,int> &mapulong &duplicateChartIDs[])
{
   // list charts until their list ends
   for(long id = ChartFirst(); id != -1id = ChartNext(id))
   {
      // skip objects
      if(ChartGetInteger(idCHART_IS_OBJECT)) continue;
   
      // getting the main properties of the chart
      const int win = (int)ChartGetInteger(idCHART_WINDOWS_TOTAL);
      const string expert = ChartGetString(idCHART_EXPERT_NAME);
      const string script = ChartGetString(idCHART_SCRIPT_NAME);
      const int objectCount = ObjectsTotal(id);
   
      // count the number of indicators
      int indicators = 0;
      for(int i = 0i < win; ++i)      
      {
         indicators += ChartIndicatorsTotal(idi);
      }
      
      const string key = ChartSymbol(id) + "/" + PeriodToString(ChartPeriod(id));
      
      if(map[key] == 0     // the first time we always read a new symbol/TF combination
                           // otherwise, only empty charts are counted:
         || (indicators == 0           // no indicators
            && StringLen(expert) == 0  // no Expert Advisor
            && StringLen(script) == 0  // no script
            && objectCount == 0))      // no objects
      {
         const int i = map.inc(key);
         if(map[i] > 1)                // duplicate
         {
            PUSH(duplicateChartIDsid);
         }
      }
   }
   return map.getSize();
}

当待删除列表形成后,我们在 OnStart中循环调用 ChartClose 函数以遍历列表。

void OnStart()
{
   ...
   if(ChartIdleList(chartCountsduplicateChartIDs))
   {
      for(int i = 0i < ArraySize(duplicateChartIDs); ++i)
      {
         const ulong id = duplicateChartIDs[i];
         // request to bring the chart to the front
         ChartSetInteger(idCHART_BRING_TO_TOPtrue);
         // update the state of the windows, pumping the queue with the request
         ChartRedraw(id);
         // ask user for confirmation
         const int button = MessageBox(
            "Remove idle chart: "
            + ChartSymbol(id) + "/" + PeriodToString(ChartPeriod(id)) + "?",
            __FILE__MB_YESNOCANCEL);
         if(button == IDCANCELbreak;   
         if(button == IDYES)
         {
            ChartClose(id);
         }
      }
      ...

对于每个图表,将先调用ChartSetInteger(id, CHART_BRING_TO_TOP, true)函数,向用户显示应关闭的窗口。由于该函数是异步的(仅将激活窗口命令加入事件队列),因此需要额外调用ChartRedraw以处理所有累积的消息。随后会提示用户确认该操作。只有在用户点击Yes后,图表才会关闭。若用户选择No时,将跳过当前图表(保持其开启状态),并继续处理后续图表。若点击 Cancel,可以提前中断整个关闭循环。