English Русский Español Deutsch 日本語 Português
preview
市场模拟(第八部分):套接字(二)

市场模拟(第八部分):套接字(二)

MetaTrader 5示例 |
22 0
Daniel Jose
Daniel Jose

概述

在上一篇文章,市场模拟(第七部分):套接字(一)中,我向你演示了学习套接字的第一步。然而,那里显示的应用程序可能并不特别令人兴奋。说实话,它更像是一个 “HELLO WORLD” 程序 —— 这是我们在学习编程新知识时通常尝试的第一个程序。这是漫长旅程的第一步。

因此,在本文中,我将向您展示如何创建更有趣的东西。需要明确的是,它不会特别有用,但它会让你在学习相关概念的同时进行实验并获得一些乐趣。就像上一篇文章一样,我们需要依赖外部代码。再次强调,使用外部代码的原因是我不想向 MetaTrader 5 添加 DLL,至少目前不想。

关于外部代码,我不会详细介绍它是如何工作的。通过研究我在上一篇文章中提到的参考文献,您可以找到执行相同类型任务的各种代码或创建自己的代码。那么,本文我们将要做什么呢?从本质上讲,我想展示套接字的有趣之处。我还打算在回放/模拟器系统中使用它们。我们将以最简单的方式来实现这一点:创建一个迷你聊天室。没错 —— 我将向您展示如何在 MetaTrader 5 中创建迷你聊天室。


计划

迷你聊天背后的概念非常简单明了,您有一个可以键入消息的区域、一个发送消息的按钮和另一个查看他人消息的区域。简而言之,它很容易实现。我们需要添加一个编辑框、一个按钮和一个用于显示已发布消息的对象。MetaTrader 5 提供了所有这些工具。

好,通常,聊天程序使用内置的客户端-服务器系统。但在这里,要使用纯 MQL5,服务器将是一个外部程序,而客户端将在 MQL5 中实现。这是使用套接字的优点之一:您不局限于单一的方法。你可以用多种方式来实现它们。您可能会想:如何管理连接,才能允许任意数量的参与者加入我们的迷你聊天室?开发起来肯定非常复杂。是这样吗?嗯,这取决于你想要实现什么以及你如何实现它。

我将演示的服务器程序非常简单,您可以在树莓派上运行它并将其用作迷你服务器。此设置可以支持大量参与者,而无需重新编译代码,因为服务器将是动态的。所谓“动态”,我的意思是连接限制将由操作系统或硬件功能决定,而不是由服务器代码本身决定。在深入服务器代码之前,让我们先看看客户端部分。它将使用 MQL5 实现。


基本实现

实现的这一部分会非常有趣,尤其是在 MQL5 中,因为我们将采用一种不太寻常的方法。首先,我们需要创建一个窗口来与迷你聊天室进行交互。在 MetaTrader 5 的 MQL5 中,最简单的实现方法是使用指标。但是,需要注意的是:指标不能使用套接字,因为根据套接字的实现方式,它们可能会阻塞其他指标的计算流程。所有指标共享同一个计算空间。那么我们该如何创建窗口呢?最简单的代码如下:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_separate_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.    return INIT_SUCCEEDED;
09. }
10. //+------------------------------------------------------------------+
11. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
12. {
13.    return rates_total;
14. }
15. //+------------------------------------------------------------------+

初始指标代码

这段基本代码为我们创建了一个窗口,这要归功于第 3 行,它表示将这段代码添加到图表中会生成一个新窗口。但这里有个问题。如果我们保持原样,用户可以在图表中添加多个聊天窗口。另一个问题是指标不能使用套接字。

我们需要修改代码,以便在允许套接字的上下文中使用。为简化起见,我们将所有内容都嵌入到一个 EA 交易中。这只是为了演示。用脚本也可以实现同样的功能。但是,当图表时间周期改变时,脚本会被移除,所以我将使用 EA 交易来展示实现方式。第一步是创建以下代码:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #define def_IndicatorMiniChat   "Indicators\\Mini Chat\\Mini Chat.ex5"
06. #resource "\\" + def_IndicatorMiniChat
07. //+------------------------------------------------------------------+
08. long gl_id;
09. int subWin;
10. //+------------------------------------------------------------------+
11. int OnInit()
12. {
13.    gl_id = ChartID();
14.    subWin = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL);
15.    
16.    ChartIndicatorAdd(gl_id, subWin, iCustom(NULL, 0, "::" + def_IndicatorMiniChat));
17. 
18.    return INIT_SUCCEEDED;
19. }
20. //+------------------------------------------------------------------+
21. void OnDeinit(const int reason)
22. {
23.    ChartIndicatorDelete(gl_id, subWin, ChartIndicatorName(gl_id, subWin, 0));
24. }
25. //+------------------------------------------------------------------+
26. void OnTick()
27. {
28. }
29. //+------------------------------------------------------------------+
30. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
31. {
32. }
33. //+------------------------------------------------------------------+

EA 交易启动代码

这是我们 EA 交易的基本框架,允许打开一个窗口。注意第 5 行:它明确指出,在第 6 行,我们将使用该指标作为 EA 交易的内部资源。为什么呢?因为我们希望指标为我们创造窗口。但 EA 交易无法直接做到这一点。这就是我们使用指标的原因。

当 EA 交易附加到图表上时,它会添加指标(第 16 行)以创建必要的窗口。然而,之前的指标代码还不够完善,因为用户可以直接将其添加到图表中。我们希望 EA 交易能够自动添加,防止用户手动添加。

我们该如何解决这个问题?很简单,我们修改了指标,使用户无法将其放置在图表上。EA 交易编译完成后,指标可执行文件甚至可以删除,因为它已被嵌入。但是,我们也需要采取安全措施,以防可执行文件可以被访问。解决方案在以下代码中:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Base indicator for Mini Chat."
04. #property description "It cannot be used without outside assistance."
05. #property version   "1.00"
06. #property indicator_chart_window
07. #property indicator_plots 0
08. //+------------------------------------------------------------------+
09. #define def_ShortName "Mini Chat"
10. //+------------------------------------------------------------------+
11. int OnInit()
12. {
13.    long id = ChartID();
14.    string sz0 = def_ShortName + "_TMP";
15.    int i0;
16.    
17.    IndicatorSetString(INDICATOR_SHORTNAME, sz0);
18.    for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--)
19.       if (ChartIndicatorName(id, c0, 0) == def_ShortName)
20.       {
21.          ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0);
22.          return INIT_FAILED;
23.       }
24.    IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);   
25.    i0 = ChartWindowFind(id, def_ShortName);
26.    if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1))
27.    {
28.       ChartIndicatorDelete(id, i0, def_ShortName);
29.       return INIT_FAILED;
30.    }
31.    
32.    return INIT_SUCCEEDED;
33. }
34. //+------------------------------------------------------------------+
35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
36. {
37.    return rates_total;
38. }
39. //+------------------------------------------------------------------+

指标代码的第一次修改

这段代码阻止用户将指标放置在图表上,确保只有 EA 交易才能添加该指标。我们来试着理解一下为什么这段代码不允许用户将指标放置在图表上。请注意,在第 06 行中,我们明确规定该指标不会创建新窗口。

第 09 行定义了指标的名称,该名称将在测试中使用。请注意,要从列表中删除一个指标,我们需要它有一个名称。这个名称不应该是最终名称,而应该是临时名称。这是在第 14 行完成的。然后,我们将这个临时名称设置为指标名称,这是在第 17 行完成的。

现在到了有趣的部分:在第 18 行的循环中,我们将在图表上的指标列表中搜索指标名称。我们要找的名字在第 09 行。执行此操作的测试位于第 19 行。当找到指标时,执行第 21 行。这一行删除的不是图表上已有的指标,而是我们正在尝试添加的指标。紧接着,在第 22 行,我们报告初始化失败。

但如果可以放置指标,则第 24 行设置嵌入式指标的最终名称,确保只有 EA 交易才能使用它。

还有一点细节,这样可以防止用户在图表上已经存在指标之后再添加该指标。但用户仍然可以尝试先添加它。为防止这种情况发生,我们进行额外的检查。

第 25 行检测指标要放置的窗口。如果指标位于主窗口中,则其值为 0。否则,返回子窗口编号。第 26 行检查此值。如果值为 0,则移除该指标。如果窗口中有多个指标,也会被移除。

这可能令人困惑。“如果 EA 要在图表上放置指标,难道不应该在主窗口中放置吗?第 6 行不就是说指标会放置在主窗口中吗?”事实上,当 EA 在图表上放置指标时,它会创建一个新窗口。因此,如果第 25 行返回的值为 0,我们可以安全地删除该指标。但是,如果同一个返回窗口中还有其他元素,我们也可以将其删除,因为这表明是用户试图放置指标。

这样可以确保用户无法手动将指标添加到图表中。达到这个目标之后,我们就可以进行下一步了。


实现交互对象

为了简化我们的 MQL5 应用程序,我们将执行以下操作:交互对象将放置在指标中,而连接逻辑将位于 EA 交易中。该设计将为未来增强迷你聊天室功能预留空间。我们将使用头文件来组织工作。由于我们所做的一切都是回放/模拟器项目的一部分,我们将把迷你聊天功能集成到同一个框架中。第一步是将两个新事件添加到我们的事件枚举中。这段代码如下所示:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay         "RePlay"
14. #define def_MaxPosSlider          400
15. #define def_MaskTimeService      0xFED00000
16. #define def_IndicatorTimeFrame   (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame         4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.    double    dValue;
22.    long      _long;                                 // 1 Information
23.    datetime _datetime;                              // 1 Information
24.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents    {
30.          evTicTac,                     //Event of tic-tac
31.          evHideMouse,                  //Hide mouse price line
32.          evShowMouse,                  //Show mouse price line
33.          evHideBarTime,                //Hide bar time
34.          evShowBarTime,                //Show bar time
35.          evHideDailyVar,               //Hide daily variation
36.          evShowDailyVar,               //Show daily variation
37.          evHidePriceVar,               //Hide instantaneous variation
38.          evShowPriceVar,               //Show instantaneous variation
39.          evCtrlReplayInit,             //Initialize replay control
40.          evChartTradeBuy,              //Market buy event
41.          evChartTradeSell,             //Market sales event 
42.          evChartTradeCloseAll,         //Event to close positions
43.          evChartTrade_At_EA,           //Event to communication
44.          evEA_At_ChartTrade,           //Event to communication
45.          evChatWriteSocket,            //Event to Mini Chat
46.          evChatReadSocket              //Event To Mini Chat
47.                   };
48. //+------------------------------------------------------------------+

Defines.mqh 文件源代码

请注意,旧文件 Defines.mqh 已添加了两行新内容:第 45 行和第 46 行。这些代码行将使我们能够通过套接字进行通信。然而,添加这些事件仅仅是因为我们将迷你聊天与 EA 交易的内部代码分离。换句话说,我们需要一种方法让指标显示通过套接字接收的消息。由于指标被禁止直接使用套接字,因此 EA 交易将监控套接字,一旦有消息可用,就将其转发到迷你聊天窗口。这样我们就可以访问指标中的信息。

好,现在让我们来看一下负责在图表上创建对象的头文件。如下所示:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include  "..\Defines.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ShortName    "Mini Chat"
007. #define def_MaxRows      256
008. #define def_FontName     "Lucida Console"
009. #define def_FontSize     12
010. #define def_SizeControls (m_txtHeight + 6)
011. #define macroColorRGBA(A) ((uint)((0xFF << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16)))
012. //+------------------------------------------------------------------+
013. class C_Chat
014. {
015.    private   :
016.       long   m_id;
017.       int    m_sub;
018.       int    m_txtHeight;
019.       bool   m_full;
020.       ushort m_Width,
021.              m_Height,
022.              m_index;
023.       uint   m_Pixel[];
024.       string m_ObjEdit,
025.              m_ObjBtn,
026.              m_ObjPanel;
027.       struct st0
028.       {
029.          string info;
030.          bool   loc;
031.       }m_Msgs[def_MaxRows + 1];
032. //+------------------------------------------------------------------+
033.       void Add(string szMsg, bool isloc = false)
034.       {
035.          m_Msgs[m_index].info = szMsg;
036.          m_Msgs[m_index].loc = isloc;
037.          if ((++m_index) > def_MaxRows)
038.          {
039.             m_full = true;
040.             m_index = 0;
041.          }
042.          Paint();
043.       };
044. //+------------------------------------------------------------------+
045.       void Paint(void)
046.       {
047.          int max, count, p0, p1;
048.          
049.          ArrayInitialize(m_Pixel, macroColorRGBA(clrBlack));
050.          if ((p0 = m_Height - def_SizeControls) < 0) return;
051.          max = (int)(floor(p0 / (m_txtHeight * 1.0)));
052.          p1 = m_index - max;
053.          if (m_full)
054.             count = (max > def_MaxRows ? m_index + 1 : (p1 > 0 ? p1 : (def_MaxRows + p1 + 1)));
055.          else
056.             count = (p1 > 0 ? p1 : 0);         
057.          for (ushort row = 0; row < p0; count++)
058.          {
059.             count = (count > def_MaxRows ? 0 : count);
060.             if (count == m_index) break;
061.             TextOut(m_Msgs[count].info, 2, row, 0, m_Pixel, m_Width, m_Height, macroColorRGBA(m_Msgs[count].loc ? clrSkyBlue : clrLime), COLOR_FORMAT_ARGB_NORMALIZE);
062.             row += (ushort) m_txtHeight;
063.          }
064.          ResourceCreate("::" + m_ObjPanel, m_Pixel, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE);
065.          ChartRedraw();
066.       }
067. //+------------------------------------------------------------------+
068.       void CreateObjEdit(const string szArg)
069.       {
070.          ObjectCreate(m_id, szArg, OBJ_EDIT, m_sub, 0, 0);
071.          ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2);
072.          ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0);
073.          ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls);
074.          ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName);
075.          ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize);
076.          ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrDarkGray);
077.          ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack);
078.          ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrNavy);
079.       }
080. //+------------------------------------------------------------------+
081.       void CreateObjButton(const string szArg, const string szTxt)
082.       {
083.          ObjectCreate(m_id, szArg, OBJ_BUTTON, m_sub, 0, 0);
084.          ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0);
085.          ObjectSetInteger(m_id, szArg, OBJPROP_XSIZE, 70);
086.          ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls);
087.          ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName);
088.          ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize);
089.          ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrSkyBlue);
090.          ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack);
091.          ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrBlack);
092.          ObjectSetString(m_id, szArg, OBJPROP_TEXT, szTxt);
093.       }
094. //+------------------------------------------------------------------+
095.       void CreateObjPanel(const string szArg)
096.       {      
097.          ObjectCreate(m_id, szArg, OBJ_BITMAP_LABEL, m_sub, 0, 0);
098.          ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2);
099.          ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, m_txtHeight + 8);
100.          ObjectSetString(m_id, szArg, OBJPROP_BMPFILE, "::" + m_ObjPanel);
101.       }
102. //+------------------------------------------------------------------+
103.    public   :
104. //+------------------------------------------------------------------+
105.       C_Chat()
106.          :m_index(0),
107.           m_full(false),
108.           m_Width(0),
109.           m_Height(0)
110.       {         
111.          int tmp;
112.          
113.          m_sub = ChartWindowFind(m_id = ChartID(), def_ShortName);
114.          TextSetFont(def_FontName, -10 * def_FontSize, 0, 0);
115.          TextGetSize("M", tmp, m_txtHeight);
116.          CreateObjEdit(m_ObjEdit = def_ShortName + " Edit" + (string)ObjectsTotal(m_id));
117.          CreateObjButton(m_ObjBtn = def_ShortName + " Button" + (string)ObjectsTotal(m_id), "Send");
118.          CreateObjPanel(m_ObjPanel = def_ShortName + " Panel" + (string)ObjectsTotal(m_id));
119.       }
120. //+------------------------------------------------------------------+
121.       ~C_Chat()
122.       {
123.          ObjectsDeleteAll(m_id, def_ShortName);
124.       };
125. //+------------------------------------------------------------------+
126.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
127.       {
128.          switch(id)
129.          {
130.             case CHARTEVENT_CHART_CHANGE:
131.                m_Width = (ushort)ChartGetInteger(m_id, CHART_WIDTH_IN_PIXELS, m_sub);
132.                m_Height = (ushort)ChartGetInteger(m_id, CHART_HEIGHT_IN_PIXELS, m_sub);
133.                ObjectSetInteger(m_id, m_ObjEdit, OBJPROP_XSIZE, m_Width - 75);
134.                ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_XDISTANCE, m_Width - 72);
135.                ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_XSIZE, m_Width - 4);
136.                ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_YSIZE, m_Height - 4);
137.                ArrayResize(m_Pixel, m_Width * m_Height);
138.                Paint();
139.                break;
140.             case CHARTEVENT_OBJECT_CLICK:
141.                if (sparam == m_ObjBtn)
142.                {
143.                   string sz0 = ObjectGetString(m_id, m_ObjEdit, OBJPROP_TEXT);
144.                   if (sz0 != "")
145.                   {
146.                      EventChartCustom(m_id, evChatWriteSocket, 0, 0, sz0);
147.                      Add(sz0, true);
148.                      ObjectSetString(m_id, m_ObjEdit, OBJPROP_TEXT, "");
149.                      ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_STATE, 0);
150.                   }                  
151.                }
152.                break;
153.             case CHARTEVENT_CUSTOM + evChatReadSocket:
154.                Add(sparam);
155.                break;
156.             }
157.       }
158. //+------------------------------------------------------------------+
159. };
160. //+------------------------------------------------------------------+
161. #undef macroColorRGBA
162. #undef def_MaxRows
163. //+------------------------------------------------------------------+

C_Chat.mqh 文件代码

这段代码生成我们将要与之交互的对象。因为我知道很多爱好者都在阅读这些文章,所以我要求那些已经有很强编程技能的人耐心等待。我将详细解释这段代码,以便任何希望使用和改进它的人都可以这样做。同时,它旨在激励更多的读者开发自己的解决方案。

让我们一起来看看这些解释。第 04 行包含头文件,其中包含我们的一些定义。然而,我们实际使用的定义是最近添加的定义。第 06 行定义了我们指标的名称。第 07 行设置迷你聊天窗口将显示的最大行数。稍后会详细解释。第 08 行和第 09 行定义了字体名称和大小。这样可以更轻松地全局调整尺寸:一旦在此处设置,整个迷你聊天室的窗口都会相应调整。第 10 行指定控件的高度,防止出现显示问题。第 11 行是一个宏,用于在图表上放置文本。下文将对此进行更详细的解释。

有了这些定义,我们就可以开始类的部分了。第 15 行声明了私有部分。从此之后声明的所有变量和函数都专属属于 C_Chat 类。第 16 行至第 26 行定义了所需的最小变量集。如果你想为迷你聊天添加更多功能,你应该增加对象的数量。这应该在此处完成。第 27 行声明了一个用于存储消息的结构,以便快速访问其他用户发布的消息。

重要细节:服务器不会存储消息。连接之前发布的任何消息都将丢失。还有另一个重要细节:更改图表时间周期将删除所有图表对象。因此,存储在这种结构中的消息也会丢失。为了保存消息,你需要实现一个将消息保存到文件和恢复到文件的方法。你只需要保存第 27 行定义的结构,然后在读取它时调用下面解释的过程即可。这很简单。

现在看第 31 行。它使用了第 7 行定义的值。请注意,我们添加 1 是因为 MQL5 与 C/C++ 一样,使用从 0 开始的索引。添加 1 可确保保留最后 256 行,如声明的那样。您可以根据需要调整此数字。

现在我们进入类的功能部分:使迷你聊天功能正常运行的过程。第一个过程已安排妥当。此过程会将新消息添加到第 27 行声明的结构中。第 35 行和第 36 行分别给相应的字段赋值。

第 37 行检查消息计数器是否已达到最大值。如果是这样,计数器将被设置为 0。这是在第 40 行完成的。为了知道何时达到此限制,我们使用第 39 行,该行设置一个标志,指示消息列表已满。

为什么需要这样?当消息数量达到上限时,较旧的消息将被覆盖。这形成了一个循环缓冲区:新消息被写入最旧的位置,同时保持列表的满容量。如果需要,可以修改此行为。

关键的下一步是在图表上显示消息,这是从第 45 行开始的过程中处理的。

在分析之前,让我们先看一下其他一些设置步骤,以便更好地理解第 45 行。跳转至第 95 行,这里我们创建一个面板来容纳循环列表中的文本。我们使它非常简单。第 97 行创建了一个 OBJ_BITMAP_LABEL 对象。第 98 至 99 行确定对象的左上角。真正重要的行是第 100 行,它指定将使用哪个位图。

但等等,位图?您可能想知道为什么我们使用位图而不是纯文本。这没有道理。但是,MQL5 并没有提供像我们需要的那样在图表上精确绘制文本的简便方法。Comment() 函数达不到要求,我们需要控制文本的显示位置和显示方式,使用位图可以让我们精确地绘制字母,MetaTrader 5 会将它们渲染显示。

虽然听起来很复杂,但实际上很简单。第 100 行只是定义了位图名称。现在我们可以回到第 45 行了。那里将要发生的事情很快就会变得合理。

首先,在第 49 行,我们清理像素矩阵。你可以使用任何颜色。在这种情况下,背景颜色为 clrBlack,这意味着文本面板的背景将是黑色。可以将其设置为任何所需的值。随后,我们立即进行一个小计算,以检查迷你聊天窗口是否应该包含文本面板。如果不是,我们退出。否则我们就绘制一段文字。

然后,第 51 行计算文本窗格允许区域内的最大行数。第 52 行查找循环列表的起始点,以显示所有可能的行。这里有一个细节:当列表已满时,变量 m_index 可能小于、大于或等于面板中的行数。因此,我们需要修正这个值。

第 53 至 56 行正确调整了该索引。现在,用于计数的变量确实指向要显示的最旧的一行。然后我们从第 57 行进入循环。这个循环看起来可能很奇怪,但其实很简单。它所做的就是逐个位置地遍历循环列表,并在第 61 行调用 MQL5 库,将文本绘制到位图上。

循环结束后,位图将包含绘制在其上的文本。然后我们需要通知 MetaTrader 5 现在可以使用该位图了。这是在第 64 行完成的。请仔细留意以下事件顺序。首先,我们定义位图的名称。然后我们把它画出来。最后,我们告诉 MetaTrader 5,位图现在可以显示在图表上了。这正是第 64 行发生的情况。很简单,不是吗?

第 68 行到第 93 行的步骤在 MQL5 编程中相当常见,因此无需进一步解释。然而,我们还没有结束。现在我们来看类构造函数,它出现在第 103 行的公有子句声明之后。因此,第 103 行之后的所有内容都将在类外可见。在这个位于第 105 行的构造函数中,我们初始化了我们的迷你聊天室。这是通过几个非常简单的步骤完成的。

首先,我们需要捕获显示迷你聊天的窗口的索引。这是在第 113 行完成的。紧接着,在第 114 行,我们告诉 MetaTrader 5 如何定义用于创建位图文本的字体。在第 115 行,我们获取字母的高度。这是因为,根据操作系统配置的不同,字母的大小可能会有所不同。因此,为了避免显示效果怪异,我们会获取文本高度。

接下来,在第 116 行和第 118 行之间,我们创建所需的对象。如果需要其他对象,此时应该添加它们。只需注意遵循代码中显示的对象所演示的创建规则。

第 121 行的析构函数只有一个任务:从图表中移除创建的对象。所以它只包含第 123 行。

最后,我们来到第 126 行,在这里我们处理 MetaTrader 5 将发送到我们指标的消息。记住,我们仍然在指标代码内部。为了使事情尽可能简单,我们在这里只处理三种类型的事件或消息。其中之一是 CHARTEVENT_CHART_CHANGE,它定义了对象的正确位置和尺寸。这是我们的代码放置在图表上后,MetaTrader 5 所生成的第一条消息。

我们还处理 CHARTEVENT_OBJECT_CLICK 消息,该消息会在图表上发生点击时触发。在这种情况下,在第 141 行,我们检查点击是否在发送消息按钮上。如果是这样,在第 143 行,我们会捕获编辑区域中的文本,以便稍后将其发送出去。然后,在第 144 行,我们验证文本是否为空。如果不是,则在第 146 行触发一个自定义事件,以便将相同的文本发送给 EA 交易。

一个重要的细节:虽然 EA 交易可以直接访问编辑对象中的文本,但我们不应该冒险让 EA 交易检索文本。这是因为它可能已经被指标破坏了,如第 148 行所示。总之,我们通过自定义事件发送的相同文本将显示在面板的第 147 行。

最后,在第 153 行,我们有一个自定义事件。它的目的是捕获 EA 交易从套接字接收到的文本,并将其放置在我们迷你聊天窗口的文本面板中。亲爱的读者,在这一点上,这可能会让你感到困惑,特别是如果你直接进入这篇文章。在本系列的前几篇文章中,我详细解释了如何使用自定义事件。如果你很好奇,看看那些早期的文章。它们将极大地帮助你理解这里发生的事情。

很好,那么,作为本文要展示的最后一个代码,让我们来看一下迷你聊天指标代码的最终版本。您可以在下面看到:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Base indicator for Mini Chat."
04. #property description "It cannot be used without outside assistance."
05. #property version   "1.00"
06. #property link "https://www.mql5.com/pt/articles/12672"
07. #property indicator_chart_window
08. #property indicator_plots 0
09. //+------------------------------------------------------------------+
10. #include <Market Replay\Mini Chat\C_Chat.mqh>
11. //+------------------------------------------------------------------+
12. C_Chat *Chat;
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {
16.    long id = ChartID();
17.    string sz0 = def_ShortName + "_TMP";
18.    int i0;
19.    
20.    IndicatorSetString(INDICATOR_SHORTNAME, sz0);
21.    for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--)
22.       if (ChartIndicatorName(id, c0, 0) == def_ShortName)
23.       {
24.          ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0);
25.          return INIT_FAILED;
26.       }
27.    IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);   
28.    i0 = ChartWindowFind(id, def_ShortName);
29.    if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1))
30.    {
31.       ChartIndicatorDelete(id, i0, def_ShortName);
32.       return INIT_FAILED;
33.    }
34.    
35.    Chat = new C_Chat();
36.    
37.    return INIT_SUCCEEDED;
38. }
39. //+------------------------------------------------------------------+
40. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
41. {
42.    return rates_total;
43. }
44. //+------------------------------------------------------------------+
45. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
46. {
47.    (*Chat).DispatchMessage(id, lparam, dparam, sparam);
48. }
49. //+------------------------------------------------------------------+
50. void OnDeinit(const int reason)
51. {
52.    delete Chat;
53. }
54. //+------------------------------------------------------------------+

指标的源代码


最后的探讨

本文介绍了迷你聊天室代码的第一部分。EA 交易和服务器代码还有待讨论。由于这是一个比较长的主题,您可以回顾并尝试目前为止的指标代码。在下一篇文章中,我们将完成迷你聊天的实现,包括服务器,并看到它的实际应用。

文件描述
Experts\Expert Advisor.mq5
演示 Chart Trade 和 EA 交易之间的交互(交互需要 Mouse Study)
Indicators\Chart Trade.mq5创建一个窗口,用于配置要发送的订单(需要 Mouse Study 才能进行交互)
Indicators\Market Replay.mq5创建用于与回放/模拟器服务交互的控件(交互需要 Mouse Study)
Indicators\Mouse Study.mq5实现图形控件和用户之间的交互(操作回放模拟器和实时市场交易所需)
Servicios\Market Replay.mq5创建并维护市场回放和模拟服务(整个系统的主文件)
VS C++ Server.cpp用 C++ 创建并维护一个套接字服务器(简易聊天室版)
Python code Server.py创建并维护用于 MetaTrader 5 和 Excel 之间通信的 Python 套接字。
ScriptsCheckSocket.mq5检查与外部套接字的连接
Indicators\Mini Chat.mq5实现迷你聊天功能作为指标(需要服务器)
Experts\Mini Chat.mq5实现迷你聊天功能作为 EA 交易(需要服务器)

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

附加的文件 |
Anexo.zip (560.03 KB)
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
利用 MQL5 经济日历进行交易(第 8 部分):通过智能事件过滤和有针对性的日志来优化新闻驱动策略的回测 利用 MQL5 经济日历进行交易(第 8 部分):通过智能事件过滤和有针对性的日志来优化新闻驱动策略的回测
在本文中,我们利用智能事件过滤和有针对性的日志来优化我们的经济日历,以便在实时和离线模式下实现更快、更清晰的回测。我们简化了事件处理程序,并将日志集中在关键交易和仪表盘事件上,从而增强了策略的可视化效果。这些改进使得对新闻驱动型交易策略进行顺畅的测试和优化成为可能。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
价格行为分析工具包开发(第 22 部分):相关性仪表盘 价格行为分析工具包开发(第 22 部分):相关性仪表盘
该工具是一个相关性仪表盘,用于计算并显示多个货币对之间的实时相关系数。通过可视化货币对之间的相互走势,它为您的价格行为分析提供了宝贵的视角,并帮助您预测跨市场的动态。继续阅读以探索其功能和应用。