图表显示模式

ENUM_CHART_PROPERTY_INTEGER 枚举中的四个特性描述了图表显示模式。所有这些特性均可通过 ChartGetInteger读取,并通过 ChartSetInteger 记录,从而允许你更改图表的外观。

标识符

说明

值类型

CHART_MODE

图表类型(蜡烛图、柱线或线图)

ENUM_CHART_MODE

CHART_FOREGROUND

价格图表置于前景

bool

CHART_SHIFT

价格图表距右侧边距缩进模式

bool

CHART_AUTOSCROLL

自动滚动至图表右侧边缘

bool

在 MQL5 中,CHART_MODE 模式有一个特殊枚举 ENUM_CHART_MODE。其元素如下表所示。

标识符

说明

CHART_BARS

以柱线显示

0

CHART_CANDLES

以日式蜡烛图显示

1

CHART_LINE

以收盘价连线显示

2

我们来实现一个名为ChartMode.mq5的脚本,该脚本将监控各种模式的状态,并在检测到更改时将消息输出到日志中。由于特性处理算法具有通用性,我们将它们放在一个单独的头文件 ChartModeMonitor.mqh中,然后将其连接到不同的测试中。

让我们在抽象类ChartModeMonitorInterface中奠定基础:它为所有类型提供重载的 get 和 set 方法。派生类将需要通过重写虚方法 snapshot直接检查特性,以达到所需的检查程度。

class ChartModeMonitorInterface
{
public:
   long get(const ENUM_CHART_PROPERTY_INTEGER propertyconst int window = 0)
   {
      return ChartGetInteger(0propertywindow);
   }
   double get(const ENUM_CHART_PROPERTY_DOUBLE propertyconst int window = 0)
   {
      return ChartGetDouble(0propertywindow);
   }
   string get(const ENUM_CHART_PROPERTY_STRING property)
   {
      return ChartGetString(0property);
   }
   bool set(const ENUM_CHART_PROPERTY_INTEGER propertyconst long valueconst int window = 0)
   {
      return ChartSetInteger(0propertywindowvalue);
   }
   bool set(const ENUM_CHART_PROPERTY_DOUBLE propertyconst double value)
   {
      return ChartSetDouble(0propertyvalue);
   }
   bool set(const ENUM_CHART_PROPERTY_STRING propertyconst string value)
   {
      return ChartSetString(0propertyvalue);
   }
   
   virtual void snapshot() = 0;
   virtual void print() { };
   virtual void backup() { }
   virtual void restore() { }
};

此类还包含预留方法:print(例如用于输出到日志)、backup(用于保存当前状态)以及restore(用于恢复状态)。这些方法被声明为非抽象方法,但实现为空,因为它们是可选的。

将不同类型的特性定义为特定类是合理的,这些类可以作为从ChartModeMonitorInterface继承的单一模板,并接受参数化值 (T) 和枚举 (E) 类型。例如,对于整数类型的特性,你需要设置 T=longE=ENUM_CHART_PROPERTY_INTEGER

该对象包含data数组,用于存储所有请求特性的 [键,值] 对。它具有通用类型 MapArray<K,V>,我们在 多货币和多时间范围指标一章中针对指标IndUnityPercent介绍过该类型。其独特之处在于,除了通常按数字访问数组元素外,还可以使用按键寻址。

为了填充该数组,需要将一个整数数组传递给构造函数,同时将先使用detect方法检查这些整数是否符合给定枚举类型 E 的标识符规范。所有正确的特性将通过 get调用立即读取,得到的值会与其标识符一起存储在映射中。

#include <MQL5Book/MapArray.mqh>
   
template<typename T,typename E>
class ChartModeMonitorBasepublic ChartModeMonitorInterface
{
protected:
   MapArray<E,Tdata// array-map of pairs [property, value]
   
   // the method checks if the passed constant is an enumeration element,
   // and if it is, then add it to the map array
   bool detect(const int v)
   {
      ResetLastError();
      EnumToString((E)v); // resulting string is not used
      if(_LastError == 0// it only matters if there is an error or not
      {
         data.put((E)vget((E)v));
         return true;
      }
      return false;
   }
 
public:
   ChartModeMonitorBase(int &flags[])
   {
      for(int i = 0i < ArraySize(flags); ++i)
      {
         detect(flags[i]);
      }
   }
   
   virtual void snapshot() override
   {
      MapArray<E,Ttemp;
      // collect the current state of all properties
      for(int i = 0i < data.getSize(); ++i)
      {
         temp.put(data.getKey(i), get(data.getKey(i)));
      }
      
      // compare with previous state, display differences
      for(int i = 0i < data.getSize(); ++i)
      {
         if(data[i] != temp[i])
         {
            Print(EnumToString(data.getKey(i)), " "data[i], " -> "temp[i]);
         }
      }
      
      // save for next comparison
      data = temp;
   }
   ...
};

snapshot方法会遍历数组的所有元素,并为每个特性请求值。由于我们希望检测变更,新数据会先存储在一个临时映射数组temp中。然后,将数组 datatemp 进行逐元素对比,针对每个差异项,显示一条包含特性名称、旧值和新值的消息。此简化示例仅使用日志。然而,如果有必要,程序可以调用一些使行为适应环境的应用函数。

printbackuprestore 方法以尽可能简单的方式实现。

template<typename T,typename E>
class ChartModeMonitorBasepublic ChartModeMonitorInterface
{
protected:
   ...
   MapArray<E,Tstore// backup
public:
   ...
   virtual void print() override
   {
      data.print();
   }
   virtual void backup() override
   {
      store = data;
   }
   
   virtual void restore() override
   {
      data = store;
      // restore chart properties
      for(int i = 0i < data.getSize(); ++i)
      {
         set(data.getKey(i), data[i]);
      }
   }

通过结合使用 backup/restore方法,可以在开始对图表进行实验之前保存其状态,并在测试脚本完成后将一切恢复为初始状态。

最后,ChartModeMonitor.mqh文件中的最后一个类是ChartModeMonitor。它组合了三个ChartModeMonitorBase实例,这些实例是为特性类型的可用组合创建的。它们有一个指向基础接口 ChartModeMonitorInterfacem 指针数组。该类本身也从该基础接口继承而来。

#include <MQL5Book/AutoPtr.mqh>
   
#define CALL_ALL(A,Mfor(int i = 0size = ArraySize(A); i < size; ++iA[i][].M
   
class ChartModeMonitorpublic ChartModeMonitorInterface
{
   AutoPtr<ChartModeMonitorInterfacem[3];
   
public:
   ChartModeMonitor(int &flags[])
   {
      m[0] = new ChartModeMonitorBase<long,ENUM_CHART_PROPERTY_INTEGER>(flags);
      m[1] = new ChartModeMonitorBase<double,ENUM_CHART_PROPERTY_DOUBLE>(flags);
      m[2] = new ChartModeMonitorBase<string,ENUM_CHART_PROPERTY_STRING>(flags);
   }
   
   virtual void snapshot() override
   {
      CALL_ALL(msnapshot());
   }
   
   virtual void print() override
   {
      CALL_ALL(mprint());
   }
   
   virtual void backup() override
   {
      CALL_ALL(mbackup());
   }
   
   virtual void restore() override
   {
      CALL_ALL(mrestore());
   }
};

为了简化代码,此处使用 CALL_ALL 宏,该宏会为数组中的所有对象调用指定方法,并且在调用时会考虑 AutoPtr类中重载的 [] 运算符(该运算符用于解引用智能指针并获取指向“受保护”对象的直接指针)。

析构函数通常负责释放对象,但在这种情况下,决定使用 AutoPtr数组(之前在 对象类型模板一节中讨论过这个类)。这确保了在正常释放 m数组时,动态对象会被自动删除。

在文件 ChartModeMonitorFull.mqh中提供了一个支持子窗口编号的更完整版本的监控器。

基于 ChartModeMonitor类,可以轻松实现预期的脚本ChartMode.mq5。其任务是每半秒检查一次给定特性集合的状态。目前,我们在此处使用无限循环和 Sleep,但很快我们将学习如何以不同的方式响应图表上的事件:通过终端发送的通知。

#include <MQL5Book/ChartModeMonitor.mqh>
   
void OnStart()
{
   int flags[] =
   {
      CHART_MODECHART_FOREGROUNDCHART_SHIFTCHART_AUTOSCROLL
   };
   ChartModeMonitor m(flags);
   Print("Initial state:");
   m.print();
   m.backup();
   
   while(!IsStopped())
   {
      m.snapshot();
      Sleep(500);
   }
   m.restore();
}

在任意图表上运行该脚本,并尝试使用工具按钮切换模式。这样,除了 CHART_FOREGROUND 外,你可以访问所有元素,该特性可从“特性”对话框(Common选项卡,Chart on top 标志)中切换。

图表模式切换工具栏按钮

图表模式切换工具栏按钮

例如,以下日志是在进行以下操作之后生成的:将显示从蜡烛图切换为柱线,再从柱线切换为折线图,然后切换回蜡烛图,随后启用缩进和自动滚动到起始位置。

Initial state:
    [key] [value]
[0]     0       1
[1]     1       0
[2]     2       0
[3]     4       0
CHART_MODE 1 -> 0
CHART_MODE 0 -> 2
CHART_MODE 2 -> 1
CHART_SHIFT 0 -> 1
CHART_AUTOSCROLL 0 -> 1

使用 CHART_MODE 特性的一个更实用的示例是改进版 IndSubChart.mq5指标(我们在 多货币和多时间范围指标一节中讨论过简化版IndSubChartSimple.mq5)。此指标用于在子窗口中显示第三方交易品种的报价,在早期版本中,我们必须通过输入参数向用户请求图表显示方式(蜡烛图、柱线或折线图)。现在不再需要该参数,因为我们可以自动将指标切换为主窗口中所使用的模式。

当前模式存储在全局变量 mode中,并在初始化期间完成首次赋值。

ENUM_CHART_MODE mode = 0;
   
int OnInit()
{
   ...
   mode = (ENUM_CHART_MODE)ChartGetInteger(0CHART_MODE);
   ...
}

新模式的检测最好在专门设计的事件处理程序 OnChartEvent中完成,我们将在单独的 章节中学习这一内容。在这个阶段,重要的是要知道:如果代码中描述了具有预定义原型(名称和参数列表)的函数,则每当图表发生任何变化时,MQL 程序都可以从终端接收通知。特别是,它的第一个参数包含一个描述其含义的事件标识符。我们仍然关注图表本身,因此我们会检查eventId是否等于 CHARTEVENT_CHART_CHANGE。这是必要的,因为该处理程序还能够追踪图形对象、键盘、鼠标和任意用户消息。

void OnChartEvent(const int eventId,
                 // parameters not used here
                  const long &, const double &, const string &)
{
   if(eventId == CHARTEVENT_CHART_CHANGE)
   {
      const ENUM_CHART_MODE newmode = (ENUM_CHART_MODE)ChartGetInteger(0CHART_MODE);
      if(mode != newmode)
      {
         const ENUM_CHART_MODE oldmode = mode;
         mode = newmode;
         // change buffer bindings and rendering type on the go
         InitPlot(0InitBuffers(mode), Mode2Style(mode));
         // TODO: we will auto-adjust colors later
         // SetPlotColors(0, mode);
         if(oldmode == CHART_LINE || newmode == CHART_LINE)
         {
            // switching to or from CHART_LINE mode requires updating the entire chart,
            // because the number of buffers changes
            Print("Refresh");
            ChartSetSymbolPeriod(0_Symbol_Period);
         }
         else
         {
           // when switching between candles and bars, it is enough
           // just redraw the chart in a new manner,
           // because data doesn't change (previous 4 buffers with values)
            Print("Redraw");
            ChartRedraw();
         }
      }
   }
}

你可以通过在图表上运行新指标并切换绘图方法来自行测试该指标。

这些并非是IndSubChart.mq5中所做的全部改进。稍后,在 图表颜色一节中,我们将展示如何使图形自动适配图表配色方案。