图表显示模式
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 property, const int window = 0)
{
return ChartGetInteger(0, property, window);
}
double get(const ENUM_CHART_PROPERTY_DOUBLE property, const int window = 0)
{
return ChartGetDouble(0, property, window);
}
string get(const ENUM_CHART_PROPERTY_STRING property)
{
return ChartGetString(0, property);
}
bool set(const ENUM_CHART_PROPERTY_INTEGER property, const long value, const int window = 0)
{
return ChartSetInteger(0, property, window, value);
}
bool set(const ENUM_CHART_PROPERTY_DOUBLE property, const double value)
{
return ChartSetDouble(0, property, value);
}
bool set(const ENUM_CHART_PROPERTY_STRING property, const string value)
{
return ChartSetString(0, property, value);
}
virtual void snapshot() = 0;
virtual void print() { };
virtual void backup() { }
virtual void restore() { }
};
|
此类还包含预留方法:print(例如用于输出到日志)、backup(用于保存当前状态)以及restore(用于恢复状态)。这些方法被声明为非抽象方法,但实现为空,因为它们是可选的。
将不同类型的特性定义为特定类是合理的,这些类可以作为从ChartModeMonitorInterface继承的单一模板,并接受参数化值 (T) 和枚举 (E) 类型。例如,对于整数类型的特性,你需要设置 T=long和 E=ENUM_CHART_PROPERTY_INTEGER。
该对象包含data数组,用于存储所有请求特性的 [键,值] 对。它具有通用类型 MapArray<K,V>,我们在 多货币和多时间范围指标一章中针对指标IndUnityPercent介绍过该类型。其独特之处在于,除了通常按数字访问数组元素外,还可以使用按键寻址。
为了填充该数组,需要将一个整数数组传递给构造函数,同时将先使用detect方法检查这些整数是否符合给定枚举类型 E 的标识符规范。所有正确的特性将通过 get调用立即读取,得到的值会与其标识符一起存储在映射中。
#include <MQL5Book/MapArray.mqh>
template<typename T,typename E>
class ChartModeMonitorBase: public ChartModeMonitorInterface
{
protected:
MapArray<E,T> data; // 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)v, get((E)v));
return true;
}
return false;
}
public:
ChartModeMonitorBase(int &flags[])
{
for(int i = 0; i < ArraySize(flags); ++i)
{
detect(flags[i]);
}
}
virtual void snapshot() override
{
MapArray<E,T> temp;
// collect the current state of all properties
for(int i = 0; i < data.getSize(); ++i)
{
temp.put(data.getKey(i), get(data.getKey(i)));
}
// compare with previous state, display differences
for(int i = 0; i < 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中。然后,将数组 data和temp 进行逐元素对比,针对每个差异项,显示一条包含特性名称、旧值和新值的消息。此简化示例仅使用日志。然而,如果有必要,程序可以调用一些使行为适应环境的应用函数。
print、backup 和 restore 方法以尽可能简单的方式实现。
template<typename T,typename E>
class ChartModeMonitorBase: public ChartModeMonitorInterface
{
protected:
...
MapArray<E,T> store; // 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 = 0; i < data.getSize(); ++i)
{
set(data.getKey(i), data[i]);
}
}
|
通过结合使用 backup/restore方法,可以在开始对图表进行实验之前保存其状态,并在测试脚本完成后将一切恢复为初始状态。
最后,ChartModeMonitor.mqh文件中的最后一个类是ChartModeMonitor。它组合了三个ChartModeMonitorBase实例,这些实例是为特性类型的可用组合创建的。它们有一个指向基础接口 ChartModeMonitorInterface的m 指针数组。该类本身也从该基础接口继承而来。
#include <MQL5Book/AutoPtr.mqh>
#define CALL_ALL(A,M) for(int i = 0, size = ArraySize(A); i < size; ++i) A[i][].M
class ChartModeMonitor: public ChartModeMonitorInterface
{
AutoPtr<ChartModeMonitorInterface> m[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(m, snapshot());
}
virtual void print() override
{
CALL_ALL(m, print());
}
virtual void backup() override
{
CALL_ALL(m, backup());
}
virtual void restore() override
{
CALL_ALL(m, restore());
}
};
|
为了简化代码,此处使用 CALL_ALL 宏,该宏会为数组中的所有对象调用指定方法,并且在调用时会考虑 AutoPtr类中重载的 [] 运算符(该运算符用于解引用智能指针并获取指向“受保护”对象的直接指针)。
析构函数通常负责释放对象,但在这种情况下,决定使用 AutoPtr数组(之前在 对象类型模板一节中讨论过这个类)。这确保了在正常释放 m数组时,动态对象会被自动删除。
在文件 ChartModeMonitorFull.mqh中提供了一个支持子窗口编号的更完整版本的监控器。
基于 ChartModeMonitor类,可以轻松实现预期的脚本ChartMode.mq5。其任务是每半秒检查一次给定特性集合的状态。目前,我们在此处使用无限循环和 Sleep,但很快我们将学习如何以不同的方式响应图表上的事件:通过终端发送的通知。
#include <MQL5Book/ChartModeMonitor.mqh>
void OnStart()
{
int flags[] =
{
CHART_MODE, CHART_FOREGROUND, CHART_SHIFT, CHART_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(0, CHART_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(0, CHART_MODE);
if(mode != newmode)
{
const ENUM_CHART_MODE oldmode = mode;
mode = newmode;
// change buffer bindings and rendering type on the go
InitPlot(0, InitBuffers(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中所做的全部改进。稍后,在 图表颜色一节中,我们将展示如何使图形自动适配图表配色方案。