English Русский Español Português
preview
从基础到进阶:对象事件(II)

从基础到进阶:对象事件(II)

MetaTrader 5 — 示例 |
65 0
CODE X
CODE X

引言

在上一篇文章《从基础到进阶:对象事件(I)》中,我们讲解并演示了基础部分,主要聚焦对象所产生的各类事件。不过,还有另外三类在用户与图表对象交互时可触发的事件有待探讨。这三类事件中,有一类必须手动显式启用,MetaTrader 5 才会真正生成该事件。否则,即便代码已经写好了事件处理逻辑,程序也永远收不到该事件已经发生的通知。

在组合使用这些事件前,理解每一类事件至关重要。本文将按如下方式讲解:分小节单独说明 MetaTrader 5 在何种场景、以何种方式生成剩下这三类事件。待你理解原理后,我们再来学习如何组合运用它们,实现一些有趣的功能。那么,我们开始吧。


CHARTEVENT_OBJECT_DELETE 事件

该事件和 CHARTEVENT_OBJECT_CREATE 以及其他鼠标相关事件一样,需要由我们的应用手动开启或关闭。这样做是为了让 MetaTrader 5 知晓:运行在图表上的应用,需要接收图表对象被删除的通知。

该事件主要用于预防。这点非常关键:如果我们希望在程序中维持特定的对象配置,借助这个事件,就可以防止用户删除图表内重要、关键的对象 —— 无论用户是删除对象时误操作,或是其他原因导致的删除行为。

原则上,这个事件本身十分简单。但是在编写和实现代码时,必须留意一些注意事项。之所以需要这些注意事项,是因为在某些场景下,我们会遇到和配色方案相关的问题。这最终会导致程序需要自动重建对象时,很难正确显示该对象。

不过本文并不会深入探讨上述问题。本文的目的,并不是讲解如何实现一套能够覆盖所有场景的通用解决方案。更何况,每种场景都各不相同。

目前不存在一套完整集成、通用的方法,可以重建被误删除的图表对象。作为开发者,你可以想出很多办法来实现这一需求。就我个人而言,建议使用一个小型结构体,用来保存由我们(也就是我们的应用程序)创建的对象属性。这样一来,当程序需要重建对象时,就可以按照对应的配色方案与位置信息来完成重建。除此之外,当然还包含字体、字号、对象尺寸等其他属性;我想这个思路已经很清楚了。

简短介绍到此结束,接下来你就可以查看本文的第一段代码。代码如下:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
07. //+------------------------------------------------------------------+
08. string gl_szObjectName;
09. //+------------------------------------------------------------------+
10. int OnInit()
11. {
12.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
13.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
14. 
15.     gl_szObjectName = macro_NameObject;
16.     Proc_Object();
17. 
18.     return INIT_SUCCEEDED;
19. };
20. //+------------------------------------------------------------------+
21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
22. {
23.     return rates_total;
24. };
25. //+------------------------------------------------------------------+
26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
27. {
28.     switch (id)
29.     {
30.         case CHARTEVENT_MOUSE_MOVE:
31.             ObjectSetString(0, gl_szObjectName, OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
32.             break;        
33.         case CHARTEVENT_OBJECT_DELETE:
34.             if (sparam == gl_szObjectName) Proc_Object();
35.             break;
36.     }
37.     ChartRedraw();
38. };
39. //+------------------------------------------------------------------+
40. void OnDeinit(const int reason)
41. {
42.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
43.     ObjectsDeleteAll(0, def_Prefix);
44.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
45.     ChartRedraw();
46. };
47. //+------------------------------------------------------------------+
48. void Proc_Object(void)
49. {
50.     ObjectCreate(0, gl_szObjectName, OBJ_LABEL, 0, 0, 0);
51.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_SELECTABLE, true);
52.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_XDISTANCE, 50);
53.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_YDISTANCE, 50);
54.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_XSIZE, 150);
55.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_COLOR, clrMediumBlue);
56.     ObjectSetInteger(0, gl_szObjectName, OBJPROP_FONTSIZE, 20);
57.     ObjectSetString(0, gl_szObjectName, OBJPROP_FONT, "Lucida Console");
58. }
59. //+------------------------------------------------------------------+

代码 01

代码 01 的目的十分简单:演示从图表上删除对象时会发生什么。我们需要开启、关闭事件通知,让 MetaTrader 5 告知我们对象是否被删除,借助这段代码,能更容易理解整套机制的工作原理。

最难的部分或许并不是开启通知 —— 这个操作非常简单,只需要第 13 行代码。通常我们会在程序刚启动时就开启通知,避免遗漏任何可能触发的删除事件。真正的难点在于确定何时关闭该通知。MetaTrader 5 有其固有的运行机制;关闭通知的时机不同,会得到一些看起来很奇怪的结果,不过大多数情况下这类结果并不会造成损害。

想要理解这一点,可以尝试改动第 42 行的代码位置。这一行的作用是关闭图表对象删除事件的通知。源代码就在程序内,你可以试着交换第 42 行与第 43 行的代码内容。改动后,将指标从图表移除时,你会看到一个很有意思的现象。由此就能发现,这些操作并不是可以随意调换执行顺序。这也能帮你理解很多单用文字很难讲清楚的问题。

无论如何,运行代码 01 时,我们基本上可以执行两类操作。第一个示例可参考下面的动图:

动图 01

在这个场景里,我们选中对象,按下 Delete 键删除。请注意:按下 Delete 键后对象会立刻重建。但由于信息更新绑定了鼠标事件,需要移动鼠标或触发鼠标事件,相关信息才会正常显示。第二种场景:通过对象列表窗口尝试删除对象。参考下面的动图:

动图 02

和动图 01 的现象一样,在动图 02 中可以清晰看到:我们删除对象的瞬间,对象就完成重建。即便如此,对象列表窗口不会立刻刷新显示。需要重新打开窗口,重建后的对象才会再次出现在图表对象列表中。

综上,该事件的作用就是通知我们:图表上某个对象已被删除。接下来我们还要讲解另外两种事件。这里会引出一些很值得探讨的问题。为更好地说明,我们开启新小节。


CHARTEVENT_OBJECT_CHANGE 事件

如果弄懂原理,你会发现我们接下来要介绍的这个事件非常实用。因为每当对象的任意属性发生变化时,该事件就会触发,我们可以依托不同场景,实现不少有意思的功能。

上一节提到,不存在能 100% 通用的机制来恢复被删除的对象。这个说法是成立的。但根据代码实现方式的不同,我们可以找到合适方案,用相对通用的逻辑重建几乎所有类型的对象。从目前演示的内容来看,这是一项相当复杂的工作。不必心急:虽然依靠相对简单的机制就能写出不错的程序,但未来我们还会找到更优的实现方案。知识与经验,都是逐步积累起来的。所以持续学习和实操练习永远有益。不必急于求成,但要坚持学习。

为展示该事件的价值,以及精准理解代码实现逻辑的重要性,我们将修改上一节的代码 01,以此演示本节介绍的事件。修改后的代码如下:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
07. //+------------------------------------------------------------------+
08. struct st_Obj
09. {
10. //+----------------+
11.     private:
12.         string  szName,
13.                 font;
14.         bool    Selectable;
15.         int     x,
16.                 y,
17.                 w,
18.                 fontSize;
19.         color   cor;
20. //+----------------+
21.     public:
22. //+----------------+
23.         void SetDefault(const string arg)
24.         {
25.             szName      = arg;
26.             font        = "Lucida Console";
27.             Selectable  = true;
28.             x           = 50;
29.             y           = 50;
30.             w           = 150;
31.             cor         = clrMediumBlue;
32.             fontSize    = 20;
33.         }
34. //+----------------+
35.         void Create(void)
36.         {
37.             ObjectCreate(0, szName, OBJ_LABEL, 0, 0, 0);
38.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable);
39.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
40.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
41.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
42.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
43.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
44.             ObjectSetString(0, szName, OBJPROP_FONT, font);
45.         }
46. //+----------------+
47.         string GetName(void)
48.         {
49.             return szName;
50.         }
51. //+----------------+
52. }gl_Obj;
53. //+------------------------------------------------------------------+
54. int OnInit()
55. {
56.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
57.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
58. 
59.     gl_Obj.SetDefault(macro_NameObject);
60.     gl_Obj.Create();
61. 
62.     return INIT_SUCCEEDED;
63. };
64. //+------------------------------------------------------------------+
65. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
66. {
67.     return rates_total;
68. };
69. //+------------------------------------------------------------------+
70. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
71. {
72.     switch (id)
73.     {
74.         case CHARTEVENT_MOUSE_MOVE:
75.             ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
76.             break;        
77.         case CHARTEVENT_OBJECT_DELETE:
78.             if (sparam == gl_Obj.GetName()) gl_Obj.Create();
79.             break;
80.         case CHARTEVENT_OBJECT_CHANGE:
81.             Comment("An event occurred: CHARTEVENT_OBJECT_CHANGE in object ["+sparam+"]");
82.             break;
83.     }
84.     ChartRedraw();
85. };
86. //+------------------------------------------------------------------+
87. void OnDeinit(const int reason)
88. {
89.     Comment("");
90.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
91.     ObjectsDeleteAll(0, def_Prefix);
92.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
93.     ChartRedraw();
94. };
95. //+------------------------------------------------------------------+

代码 02

运行代码 02 时,我们会观察到两种现象。第一种现象见下面的动图。

动图 03

请注意:只要我们修改对象的任意一项属性,就会触发事件,该事件会在第 80 行对应的分支中被捕获。由于我们没有对事件来源做过滤,任意对象的任何属性发生改动,都会在图表的左上角输出第 81 行对应的提示信息,动图 03 中就可以看到该效果。但最有意思的现象出现在第二个事件触发时,参见动图 04:

动图 04

“真是奇怪。为什么对象没有保留修改后的属性?”我们调用了第 35 行的过程函数。对于代码 01 的情况我能理解原因,但在本案例中我以为:如果对象从图表被删除,程序会使用完全相同的属性将它重建。代码并没有调用 SetDefault 函数去还原动图 03 中被修改过的各项数值。在我看来,这一点十分反常。”

亲爱的读者,很多编程新手都会掉进同一个陷阱:写了代码,却并不理解代码背后的部分内部机制。这种情况往往出现在直接复制别处代码的时候。但也会发生在我们测试新功能实现的时候 —— 看起来一切都完美无误,测试阶段却发现还有部分细节并未实现。无论如何,这并不算错误;恰恰相反。这是练习、验证新方案的很好方式,这些新方案未来可能会发挥价值。

好了,那问题到底出在哪?关键点在于:你在 MetaTrader 5 中修改对象的某项属性时,并不会更新对象创建时所使用的原始属性。你修改的仅仅是该对象当前的属性值。因此,当对象被删除,代码 02 的第 80 行重新创建该对象时,重建所使用的,依旧是保存在全局变量里的原始参数与属性。

所以,重建出来的对象完全是代码里预设的配置状态。如果想要保留用户做出的修改,我们就必须在程序内部同步更新该对象,把发生的改动记录下来。而这就需要借助第 82 行完成的事件捕获逻辑。这也是代码 02 中我们没有对事件来源进行过滤的原因。我希望你理解:程序是如何接收到通知,得知图表上某个对象的属性发生了改变。

MetaTrader 只会告诉我们被修改对象的名称,并不会告知具体是哪几项属性发生了变化,因此我们需要捕获全部改动。至少要捕获我们真正关心的那些属性。虽然很多图表对象拥有相似属性,但也存在部分属性为特定对象独有,其他对象并不具备。这就是为什么我们之前说,实现一套 100% 通用的解决方案是一件难度很高的工作。

明白这一点之后,我们就可以修改代码 02,让部分属性得以保留。至少在程序运行期间可以保留。一旦程序从图表上移除,用户修改的属性就会全部丢失,对象会恢复为代码中预先定义的配置。解决该问题有多种实现思路。不过这个话题我们留到下次再讨论。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
007. //+------------------------------------------------------------------+
008. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font;
014.         bool    Selectable;
015.         int     x,
016.                 y,
017.                 w,
018.                 fontSize;
019.         color   cor;
020. //+----------------+
021.     public:
022. //+----------------+
023.         void SetDefault(const string arg)
024.         {
025.             szName      = arg;
026.             font        = "Lucida Console";
027.             Selectable  = true;
028.             x           = 50;
029.             y           = 50;
030.             w           = 150;
031.             cor         = clrMediumBlue;
032.             fontSize    = 20;
033.         }
034. //+----------------+
035.         void Create(void)
036.         {
037.             ObjectCreate(0, szName, OBJ_LABEL, 0, 0, 0);
038.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, Selectable);
039.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
040.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
041.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
042.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
043.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
044.             ObjectSetString(0, szName, OBJPROP_FONT, font);
045.         }
046. //+----------------+
047.         string GetName(void)
048.         {
049.             return szName;
050.         }
051. //+----------------+
052.         void Update(void)
053.         {
054.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
055.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
056.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
057.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
058.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
059.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
060.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
061.         }
062. //+----------------+
063. }gl_Obj;
064. //+------------------------------------------------------------------+
065. int OnInit()
066. {
067.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
068.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
069. 
070.     gl_Obj.SetDefault(macro_NameObject);
071.     gl_Obj.Create();
072. 
073.     return INIT_SUCCEEDED;
074. };
075. //+------------------------------------------------------------------+
076. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
077. {
078.     return rates_total;
079. };
080. //+------------------------------------------------------------------+
081. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
082. {
083.     switch (id)
084.     {
085.         case CHARTEVENT_MOUSE_MOVE:
086.             ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
087.             break;        
088.         case CHARTEVENT_OBJECT_DELETE:
089.             if (sparam == gl_Obj.GetName()) gl_Obj.Create();
090.             break;
091.         case CHARTEVENT_OBJECT_CHANGE:
092.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
093.             break;
094.     }
095.     ChartRedraw();
096. };
097. //+------------------------------------------------------------------+
098. void OnDeinit(const int reason)
099. {
100.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
101.     ObjectsDeleteAll(0, def_Prefix);
102.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
103.     ChartRedraw();
104. };
105. //+------------------------------------------------------------------+

代码 03

代码 03 展示了需要实现的逻辑。代码运行效果可参考下面的动图:

动图 05

请注意:现在重建对象时,会使用用户几分钟之前自定义的属性。即便对象从图表中被删除,由于该对象十分重要,程序会自动重建它。但和之前不同,重建对象不再使用程序内预设的属性,而是读取用户设置并由第 92 行代码保存下来的属性。

但请注意,本次代码加入了过滤逻辑。考虑到我们正在实现的功能本身,这一步至关重要。设想如下场景:图表上存在两个对象,你修改其中一个对象的属性。到这里一切正常。但是,当我们程序管控的对象被删除时,它会立刻重建。倘若没有第 92 行的过滤逻辑,当修改另一个不受本程序管控的对象时,该对象的属性就会被错误赋值给程序重建出来的对象,这会导致图表上所有对象相关操作彻底混乱。这就是第 92 行的过滤代码对我们如此重要的原因。

你可以测试不带第 92 行过滤逻辑的代码 03,观察运行结果。有些场景下这种效果也有它的用处。所以读者朋友,理解并观察这个现象,在未来某些时刻会对你有所帮助。

我们不只是在写对代码的时候才学到知识。

代码运行不符合预期的时候,同样也是学习的过程。

在这种情况下,理解解决方案的推导过程,和方案本身同等重要。

好了,现在我们掌握了足够的知识,可以来研究最后一类对象事件。本次我们针对单一对象,研究与该对象直接相关的事件,并做一个小实验。在我看来,这个实验非常有意思,而且极具教学意义。我们要实现 MetaTrader 5 默认并不具备的功能。但是可以通过编写程序实现。结合这套实现逻辑来看,会非常有趣。

接下来开启新小节,把内容划分清楚。


CHARTEVENT_OBJECT_ENDEDIT 事件

我发现一件很有意思的事:很多用户根本不知道,我们能对 MetaTrader 5 做多大程度的自定义开发,来实现各类任务。很多功能,人们固执地认为不可能实现、不切实际。

本质上这里有一个问题:一方面,这个需求看起来比较特殊;另一方面,如果直接按常规方式实现,意义不大。最重要的一点是要有耐心。和很多人的固有认知相反,MQL5 并不只是一门专门用来开发 MetaTrader 5 应用的语言。虽然大体上,它确实主要服务于这个用途。本质而言,MQL5 赋予我们一定程度的控制权,让我们可以自定义 MetaTrader 5 的运行方式,或者说按照我们想要的方式运行。当然,一切都需要遵守相应的规则。我之所以这么说,是因为如果搞不懂这个简单核心的要点,就很难理解本节我们要做的功能。

绝大多数用户,尤其是新手,会觉得接下来要实现的内容简直超乎想象。甚至会以为这是 AI 或者外星人才写得出来的代码。但事实并非如此。实现这套功能不需要很深的知识储备,更多依靠想象力、创造力。当然还需要细心观察。

不知道你有没有留意:OBJ_LABEL 对象和 OBJ_EDIT 对象非常相似。大体来说,二者最主要,甚至可以说是唯一的区别是:OBJ_EDIT 支持直接在图表上编辑文本,而 OBJ_LABEL 对象不支持。但事实真的是这样吗?

亲爱的读者,真相是:虽然两者外观很像,但OBJ_EDIT 和 OBJ_LABEL 的特性并不相同,反之亦然。这是从用户视角来看的情况。但如果站在编程角度,情况就不一样了。从代码层面看,这两个对象本质上很接近,只要找对思路,甚至可以协同工作。

我们本节要实现的功能如下:找到一种方法,让 OBJ_LABEL 对象支持直接在图表上编辑文本,而不用打开属性窗口输入文字。听起来不可能?那我们看看如何实现。记住,这里的目的是教学,不必过度纠结代码具体怎么写。重点是弄懂这套机制为什么能够生效。这才是最关键的。

首先,我们需要源代码。我们不想从零开始编写大量代码,花费大量时间讲解每一步操作,所以基于上一节的代码做修改。代码如下:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
007. //+------------------------------------------------------------------+
008. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font;
014.         bool    Selectable;
015.         int     x,
016.                 y,
017.                 w,
018.                 h,
019.                 fontSize;
020.         color   cor,
021.                 backColor;
022.         ENUM_OBJECT actual;
023. //+----------------+
024.     public:
025. //+----------------+
026.         void SetDefault(const string arg)
027.         {
028.             szName      = arg;
029.             font        = "Lucida Console";
030.             Selectable  = true;
031.             x           = 50;
032.             y           = 50;
033.             w           = 150;
034.             h           = 27;
035.             cor         = clrMediumBlue;
036.             backColor   = clrWhite;
037.             fontSize    = 20;
038.         }
039. //+----------------+
040.         void Create(const ENUM_OBJECT type)
041.         {
042.             actual = type;
043.             Recreates();
044.         }
045. //+----------------+
046.         void Recreates(void)
047.         {
048.             ObjectCreate(0, szName, actual, 0, 0, 0);
049.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false));
050.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
051.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
052.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
053.             ObjectSetInteger(0, szName, OBJPROP_YSIZE, h);
054.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
055.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
056.             ObjectSetString(0, szName, OBJPROP_FONT, font);
057.             if (actual == OBJ_EDIT)
058.             {
059.                 ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor);
060.                 ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
061.             }
062.         }
063. //+----------------+
064.         string GetName(void)
065.         {
066.             return szName;
067.         }
068. //+----------------+
069.         void Update(void)
070.         {
071.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
072.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
073.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
074.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
075.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
076.             h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
077.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
078.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
079.             backColor   = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR));
080.         }
081. //+----------------+
082. }gl_Obj;
083. //+------------------------------------------------------------------+
084. int OnInit()
085. {
086.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
087.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
088. 
089.     gl_Obj.SetDefault(macro_NameObject);
090.     gl_Obj.Create(OBJ_LABEL);
091. 
092.     return INIT_SUCCEEDED;
093. };
094. //+------------------------------------------------------------------+
095. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
096. {
097.     return rates_total;
098. };
099. //+------------------------------------------------------------------+
100. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
101. {
102.     switch (id)
103.     {
104.         case CHARTEVENT_MOUSE_MOVE      :
105.             ObjectSetString(0, gl_Obj.GetName(), OBJPROP_TEXT, StringFormat("%03d : %03d", (ushort)lparam, (ushort)dparam));
106.             break;        
107.         case CHARTEVENT_OBJECT_DELETE   :
108.             if (sparam == gl_Obj.GetName())gl_Obj.Recreates();
109.             break;
110.         case CHARTEVENT_OBJECT_CHANGE   :
111.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
112.             break;
113.     }
114.     ChartRedraw();
115. };
116. //+------------------------------------------------------------------+
117. void OnDeinit(const int reason)
118. {
119.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
120.     ObjectsDeleteAll(0, def_Prefix);
121.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
122.     ChartRedraw();
123. };
124. //+------------------------------------------------------------------+

代码 04

我们在这里新增了一些内容。但需要注意:代码 04 的运行方式和代码 03 大体相同。留意第 40 行:调用创建对象的过程函数时,我们需要指定待创建对象的类型。在本案例中,我们会用到 OBJ_EDIT 和 OBJ_LABEL 这两种对象。通过这些修改,我们开始搭建另一项功能的基础,你很快就能理解它的核心原理:操控 OBJ_LABEL 对象,实现直接在图表上编辑文本。

接下来请留意代码 04 里的另一个关键点。在真正创建对象的第 49 行,我们设置了 OBJPROP_SELECTABLE 属性。这个属性十分关键,必须正确配置。对于 OBJ_LABEL 对象:如果后续需要选中该对象(就像前面示例那样),就需要把这个属性设为 true。但如果在 OBJ_EDIT 对象中将该属性设为 true,就无法直接在图表上编辑文本。

前面提到过,每种对象都有自身的特性。根据我们想要执行的操作,需要按特定方式设置对象属性。简单总结:当 OBJ_EDIT 对象的 OBJPROP_SELECTABLE 属性为 true 时,不能编辑文本,但可以在图表上拖动移动对象。如果把该属性设置为 false,则可以编辑文本,但无法移动对象。

请看第 90 行,这里指定了我们想要创建的对象类型,也就是 OBJ_LABEL。但第 108 行不再调用 Create 创建函数,而是调用另一个函数来重建对象。原因是我们代码里已经明确知道要创建哪一类对象;但程序无法直接获取 —— 如果用查询方式向 MT5 获取对象类型,得到的结果几乎总是错误的。这是因为 MetaTrader 5 已经把该对象销毁了。

当我们收到 CHARTEVENT_OBJECT_DELETE 事件时,MT5 销毁对象并不会征求许可。触发该事件的时候,对象其实已经被 MT5 删除。因此,除非使用零类型对象(即 OBJ_VLINE),否则如果向 MetaTrader 5 查询,我们永远无法获知被删除的对象原本是什么类型。MT5 无法给出信息,因为该对象已经不存在了。

到这里为止,我认为所有内容都简单易懂。接下来是最有意思的部分:我们将修改代码 04,让多个事件协同工作,以此实现目标效果。

一次性展示全部代码会显得比较复杂,所以我们拆成两个简单步骤实现。第一个示例代码如下。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
007. //+------------------------------------------------------------------+
008. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font,
014.                 text;
015.         bool    Selectable;
016.         int     x,
017.                 y,
018.                 w,
019.                 h,
020.                 fontSize;
021.         color   cor,
022.                 backColor;
023.         ENUM_OBJECT actual;
024. //+----------------+
025.     public:
026. //+----------------+
027.         void SetDefault(const string arg)
028.         {
029.             szName      = arg;
030.             font        = "Lucida Console";
031.             Selectable  = true;
032.             x           = 50;
033.             y           = 50;
034.             w           = 250;
035.             h           = 27;
036.             cor         = clrMediumBlue;
037.             backColor   = clrWhite;
038.             fontSize    = 20;
039.         }
040. //+----------------+
041.         void Create(const ENUM_OBJECT type)
042.         {
043.             actual  = type;
044.             text    = EnumToString(type);
045.             Recreates();
046.         }
047. //+----------------+
048.         void Recreates(void)
049.         {
050.             ObjectCreate(0, szName, actual, 0, 0, 0);
051.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false));
052.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
053.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
054.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
055.             ObjectSetInteger(0, szName, OBJPROP_YSIZE, h);
056.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
057.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
058.             ObjectSetString(0, szName, OBJPROP_FONT, font);
059.             ObjectSetString(0, szName, OBJPROP_TEXT, text);
060.             if (actual == OBJ_EDIT)
061.             {
062.                 ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor);
063.                 ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
064.             }
065.         }
066. //+----------------+
067.         string GetName(void)
068.         {
069.             return szName;
070.         }
071. //+----------------+
072.         void Update(void)
073.         {
074.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
075.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
076.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
077.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
078.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
079.             h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
080.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
081.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
082.             backColor   = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR));
083.         }
084. //+----------------+
085. }gl_Obj;
086. //+------------------------------------------------------------------+
087. int OnInit()
088. {
089.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
090. 
091.     gl_Obj.SetDefault(macro_NameObject);
092.     gl_Obj.Create(OBJ_LABEL);
093. 
094.     return INIT_SUCCEEDED;
095. };
096. //+------------------------------------------------------------------+
097. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
098. {
099.     return rates_total;
100. };
101. //+------------------------------------------------------------------+
102. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
103. {
104.     switch (id)
105.     {
106.         case CHARTEVENT_OBJECT_CLICK    :
107.             if (sparam == gl_Obj.GetName())
108.             {
109.                 string sz0 = ObjectGetString(0, sparam, OBJPROP_TEXT);
110.                 if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) == OBJ_EDIT)
111.                     break;
112.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
113.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
114.                 ObjectDelete(0, sparam);
115.                 gl_Obj.Create(OBJ_EDIT);
116.                 ObjectSetString(0, sparam, OBJPROP_TEXT, sz0);
117.             }
118.             break;
119.         case CHARTEVENT_OBJECT_CREATE   :
120.             if (sparam == gl_Obj.GetName())
121.             {
122.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false);
123.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
124.             }
125.             break;
126.         case CHARTEVENT_OBJECT_DELETE   :
127.             if (sparam == gl_Obj.GetName())gl_Obj.Recreates();
128.             break;
129.         case CHARTEVENT_OBJECT_CHANGE   :
130.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
131.             break;
132.     }
133.     ChartRedraw();
134. };
135. //+------------------------------------------------------------------+
136. void OnDeinit(const int reason)
137. {
138.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
139.     ObjectsDeleteAll(0, def_Prefix);
140.     ChartRedraw();
141. };
142. //+------------------------------------------------------------------+

代码 05

在代码 05 中,会出现一个非常奇妙的现象。你可以参考下面这张动图查看效果:

动图 06

读者朋友,请仔细观察动图 06。注意第 92 行,这里请求创建一个 OBJ_LABEL 对象。正因如此,第 44 行设置了文本,用来标识当前使用的对象类型。到这里为止,一切都按常规运行。但是,只要点击这个 OBJ_LABEL 对象,它就会转换为 OBJ_EDIT 对象。该转换逻辑正是由第 106 行捕获事件的处理代码实现的。请注意,我们只针对最开始创建的这个对象进行操作。

而得益于第 109 行代码,OBJ_LABEL 对象内的文本会在第 116 行被传递给 OBJ_EDIT 对象。这一步至关重要,如果不做文本传递,OBJ_EDIT 对象里就会显示其他文本。这并不是我们想要的效果。我们真正的目标,是可以直接在图表上修改 OBJ_LABEL 对象的文字。

接下来请注意:在 CHARTEVENT_OBJECT_CLICK 事件的处理逻辑里,我们先删除 OBJ_LABEL 对象,再新建 OBJ_EDIT 对象。但因为我们已经在第 89 行开启了 CHARTEVENT_OBJECT_DELETE 事件通知,所以需要临时关闭它,这一步在第 112 行完成。紧接着,我们开启另一个事件,用来告知 MetaTrader 5,程序需要接收对象创建的通知。这样一来,程序新建对象触发的创建事件,就会在第 119 行被捕获。在这个事件处理函数内,我们关闭对象创建通知,并重新开启对象删除通知。

经过这样处理,第 126 行的事件处理函数就可以再次运行,不过还不算尽善尽美。原因是:如果删除 OBJ_EDIT 对象,程序虽然会重建对象,但重建后的文本和对象销毁前原本的文本不再一致。

这个小问题就留给你当作练习,你可以自行寻找方案解决这个小缺陷。实现起来并不难。但要记住,需要处理部分数据,才能还原创建 OBJ_EDIT 那一刻的文本。而如果想要保留 OBJ_EDIT 对象从创建到删除期间修改过的文本,则需要更多开发工作。你还需要处理键盘和鼠标事件,在内存里持续更新文本内容,才能复原修改后的文字。

我个人认为后面这部分完全没必要实现,甚至有些麻烦。毕竟,如果用户在把文本保存到 OBJ_LABEL 对象之前就删掉了内容,那就让用户自己记住 OBJ_EDIT 里原来写了什么吧。(笑)。

好了,到了最后一部分,也就是使用本节介绍的事件。为此,我们要对代码做最后一次修改。这可以在下面看到。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
007. //+------------------------------------------------------------------+
008. struct st_Obj
009. {
010. //+----------------+
011.     private:
012.         string  szName,
013.                 font,
014.                 text;
015.         bool    Selectable;
016.         int     x,
017.                 y,
018.                 w,
019.                 h,
020.                 fontSize;
021.         color   cor,
022.                 backColor;
023.         ENUM_OBJECT actual;
024. //+----------------+
025.     public:
026. //+----------------+
027.         void SetDefault(const string arg)
028.         {
029.             szName      = arg;
030.             font        = "Lucida Console";
031.             Selectable  = true;
032.             x           = 50;
033.             y           = 50;
034.             w           = 250;
035.             h           = 27;
036.             cor         = clrMediumBlue;
037.             backColor   = clrWhite;
038.             fontSize    = 20;
039.         }
040. //+----------------+
041.         void Create(const ENUM_OBJECT type)
042.         {
043.             actual  = type;
044.             text    = EnumToString(type);
045.             Recreates();
046.         }
047. //+----------------+
048.         void Recreates(void)
049.         {
050.             ObjectCreate(0, szName, actual, 0, 0, 0);
051.             ObjectSetInteger(0, szName, OBJPROP_SELECTABLE, (actual != OBJ_EDIT ? Selectable : false));
052.             ObjectSetInteger(0, szName, OBJPROP_XDISTANCE, x);
053.             ObjectSetInteger(0, szName, OBJPROP_YDISTANCE, y);
054.             ObjectSetInteger(0, szName, OBJPROP_XSIZE, w);
055.             ObjectSetInteger(0, szName, OBJPROP_YSIZE, h);
056.             ObjectSetInteger(0, szName, OBJPROP_COLOR, cor);
057.             ObjectSetInteger(0, szName, OBJPROP_FONTSIZE, fontSize);
058.             ObjectSetString(0, szName, OBJPROP_FONT, font);
059.             ObjectSetString(0, szName, OBJPROP_TEXT, text);
060.             if (actual == OBJ_EDIT)
061.             {
062.                 ObjectSetInteger(0, szName, OBJPROP_BGCOLOR, backColor);
063.                 ObjectSetInteger(0, szName, OBJPROP_READONLY, false);
064.             }
065.         }
066. //+----------------+
067.         string GetName(void)
068.         {
069.             return szName;
070.         }
071. //+----------------+
072.         void Update(void)
073.         {
074.             font        = ObjectGetString(0, szName, OBJPROP_FONT);
075.             Selectable  = (int)ObjectGetInteger(0, szName, OBJPROP_SELECTABLE);
076.             x           = (int)ObjectGetInteger(0, szName, OBJPROP_XDISTANCE);
077.             y           = (int)ObjectGetInteger(0, szName, OBJPROP_YDISTANCE);
078.             w           = (int)ObjectGetInteger(0, szName, OBJPROP_XSIZE);
079.             h           = (int)ObjectGetInteger(0, szName, OBJPROP_YSIZE);
080.             fontSize    = (int)ObjectGetInteger(0, szName, OBJPROP_FONTSIZE);
081.             cor         = (color)ObjectGetInteger(0, szName, OBJPROP_COLOR);
082.             backColor   = (color)((ENUM_OBJECT)ObjectGetInteger(0, szName, OBJPROP_TYPE) == OBJ_LABEL ? backColor : ObjectGetInteger(0, szName, OBJPROP_BGCOLOR));
083.             text        = ObjectGetString(0, szName, OBJPROP_TEXT);
084.         }
085. //+----------------+
086. }gl_Obj;
087. //+------------------------------------------------------------------+
088. int OnInit()
089. {
090.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
091. 
092.     gl_Obj.SetDefault(macro_NameObject);
093.     gl_Obj.Create(OBJ_LABEL);
094. 
095.     return INIT_SUCCEEDED;
096. };
097. //+------------------------------------------------------------------+
098. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
099. {
100.     return rates_total;
101. };
102. //+------------------------------------------------------------------+
103. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
104. {
105. //+----------------+
106.     #define macro_SWAP(A)   {                                           \
107.                 string sz0 = ObjectGetString(0, sparam, OBJPROP_TEXT);  \
108.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);   \
109.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);    \
110.                 ObjectDelete(0, sparam);                                \
111.                 gl_Obj.Create(A);                                       \
112.                 ObjectSetString(0, sparam, OBJPROP_TEXT, sz0);          \
113.                 gl_Obj.Update();                                        \
114.                             }
115. //+----------------+
116.     switch (id)
117.     {
118.         case CHARTEVENT_OBJECT_CLICK    :
119.             if (sparam == gl_Obj.GetName())
120.                 if ((ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE) != OBJ_EDIT)
121.                     macro_SWAP(OBJ_EDIT);
122.             break;
123.         case CHARTEVENT_OBJECT_CREATE   :
124.             if (sparam == gl_Obj.GetName())
125.             {
126.                 ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, false);
127.                 ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
128.             }
129.             break;
130.         case CHARTEVENT_OBJECT_DELETE   :
131.             if (sparam == gl_Obj.GetName())gl_Obj.Recreates();
132.             break;
133.         case CHARTEVENT_OBJECT_CHANGE   :
134.             if (sparam == gl_Obj.GetName()) gl_Obj.Update();
135.             break;
136.         case CHARTEVENT_OBJECT_ENDEDIT  :
137.             if (sparam == gl_Obj.GetName()) macro_SWAP(OBJ_LABEL)
138.             break;
139.     }
140.     ChartRedraw();
141. //+----------------+
142.     #undef macro_SWAP
143. //+----------------+
144. };
145. //+------------------------------------------------------------------+
146. void OnDeinit(const int reason)
147. {
148.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, false);
149.     ObjectsDeleteAll(0, def_Prefix);
150.     ChartRedraw();
151. };
152. //+------------------------------------------------------------------+

代码 06

代码 06 已经实现了多项功能,其中就包含上述小问题的修复方案。但我们刻意改动了一部分代码,隐藏了具体的解决办法。这样做是为了避免经验尚浅的读者一眼就看懂实现思路。

代码 06 和前面几份代码一样简洁,只要认真学习、动手练习,任何人都能轻松解决这个问题,因此我不打算过多讲解。只说明这一点:当 OBJ_EDIT 对象结束编辑(通常按下回车键时),MetaTrader 5 会触发 CHARTEVENT_OBJECT_ENDEDIT 事件。该事件会在第 136 行被捕获,原本的 OBJ_EDIT 对象就会再次转换回 OBJ_LABEL 对象。

一种运行效果如下:

动图 07

读者朋友,不要被动图 07 的表面现象误导。试着研究并理解底层真正发生的逻辑。你在动图 07 里看到的,仅仅是巨大冰山露出水面的一角。本文只介绍了这件事最基础的部分。我们还可以在此基础上实现更多高级功能。


后记

很多读者可能觉得本文略显枯燥,因为文中反复贴出完整代码,对原理只做简要说明。但在我看来,本系列其他文章已经讲过的内容,就不必重复赘述。当然,我们实现的功能可能会让很多人感到惊讶,甚至心生怀疑。这很正常,本就是学习过程的一部分。

在结束本文、开启下一篇文章之前,我还有一点建议送给各位读者。在上一节你已经看到,我们可以实现很多有意思的功能。仔细观察就能发现,我们修改了 OBJ_LABEL 对象的尺寸,用来容纳更长一点的文本。我的建议是:弄懂 OBJ_EDIT 对象中的文本创建机制。只有理解这一点,你才能在图表展示文本时,动态调整 OBJ_LABEL 对象的大小。

另外,可以尝试思考如何让 OBJ_LABEL 对象在图表上拖动移动。当前它会固定在创建的位置无法挪动。原因在于:选中对象时,CHARTEVENT_OBJECT_CLICK 事件会把它转换成 OBJ_EDIT 对象,这就导致我们无法修改它的位置。

除此之外,我们还有其他可以拓展的功能,大概率会放到下一篇文章讲解。如果我后续发现该内容趣味性不强、文章价值有限,我会单独说明。无论如何,读完本文,你对 MQL5 和 MetaTrader 5 的了解,相比文章开头已经有所增长。

请使用附件里提供的代码多多学习、动手实操。我们下篇文章再见。

MQL5 文件 说明
代码 01 对象事件演示
代码 02 对象事件演示
代码 03  对象事件演示
代码 04  对象事件演示

本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/16050

附加的文件 |
Anexo.zip (4.5 KB)
MQL5自优化智能交易系统(第十二部分):利用矩阵分解构建线性分类器 MQL5自优化智能交易系统(第十二部分):利用矩阵分解构建线性分类器
本文探讨矩阵分解在算法交易中的强大作用,重点介绍其在 MQL5 程序里的应用。从回归模型到多目标分类器,我们通过实战示例演示如何借助 MQL5 内置函数轻松集成这类技术。无论你是预测价格方向,还是构建指标行为模型,这份教程都为你使用矩阵方法搭建智能交易系统打下坚实基础。
市场模拟:MQL5 中的 SQL 入门(一) 市场模拟:MQL5 中的 SQL 入门(一)
在今天的文章中,我们将开始学习如何在 MQL5 代码中使用 SQL。我们还将探讨如何创建数据库。或者更准确地说,如何使用 MQL5 内置的功能来创建 SQLite 数据库文件。我们还将了解如何创建表,以及如何通过使用主键和外键来建立表之间的关系。所有这些都将再次使用 MQL5 来完成。我们将看到,通过使用一个有助于封装底层实现细节的类,创建代码是多么容易,这些代码稍后可以迁移到其他 SQL 实现中。而且,最重要的是,我们将看到,在使用 SQL 时,我们可能会在不同阶段面临出错的风险。这是因为,在 MQL5 代码中,SQL 代码总是被放置在字符串中。
决斗者算法 决斗者算法
如果您的交易策略能像真实的决斗者一样互相较量、彼此学习,会怎样?决斗者算法(Duelist Algorithm)是一种新的优化方法:在该算法中,不同的参数组合会像决斗者一样相互较量,争夺最优解的位置。
交易中的神经网络:自适应周期分段(结论) 交易中的神经网络:自适应周期分段(结论)
我们诚邀您一同探索LightGTS的精彩世界 —— 这是一款轻量但性能强大的时间序列预测框架,融合了自适应卷积、旋转位置编码(RoPE)与创新注意力机制。本文将详细阐述框架内全部组件:从分块(patch)构建,一直到解码器内复杂的混合专家模块,这些实现思路已具备集成到MQL5项目中的条件。您将了解LightGTS如何令自动化交易提升至全新高度!