在上一篇文章我解释了我们将如何构建订单系统。在那篇文章中,我们查阅了大部分代码,并触及了我们必须解决的复杂性和问题。虽然代码看似简单易用,但事实远非如此。因此,在这篇文章中,我们将深入探讨我们在实现方面的实际情况和仍然需要做的事情。我们还需要讨论和解释 C_Manager 类中的最后一个方法,并对 EA 代码进行注释。就 EA 代码而言,我们将主要关注修改过的部分。这样,您就无需在 MetaTrader 5 平台上进行测试,就能知道它将如何表现。你可以尽情测试,因为代码将包含在本文的附录中。
许多人可能会觉得没有必要按照展示的那样对系统进行测试,希望在实际实验之前能有一个更兼容的系统。这不是一个好主意,因为如果你现在不了解系统是如何运行的,那么你在了解它将来如何运行时就会遇到很大的问题。更糟糕的是,如果由于某种原因没有在这里展示,而您又对开发不感兴趣,那么您就无法对其进行调整,使其生成您希望看到的内容。随着系统的逐步发展,你可以更加关注一些细节,测试其他细节,或者等到系统成熟到你真正觉得是时候把它放到平台上并分析它的表现时再进行测试。
C_Manager 类的主要函数:DispatchMessage
这个函数无疑是我们创建的整个类的核心。它处理从 MetaTrader 5 平台生成并发送到我们程序的事件,这些事件是我们希望平台向我们发送的。例如,CHARTEVENT_MOUSE_MOVE。此外,还有其他发送的事件,我们的程序可能会忽略它们,因为它们对我们正在创建的项目并无太大帮助。CHARTEVENT_OBJECT_CLICK 就是一个例子。
所有的事件处理都集中在类中,这使得在模块中运行项目变得更加容易。虽然这看起来似乎很费事,但你很快就会发现,这样可以更方便地将代码从一个项目转移到另一个项目,从而加快新项目的开发。
这里有两个要点:
首先,通过将事件处理集中在一个地方,以及使代码更容易在项目之间移植和移动,我们减少了需要放在主代码(这里是 EA)中的代码量。这使得调试变得更加容易,因为它减少了在重用类和处理事件时可能出现的错误数量。
第二个要点则要复杂一些。这关系到每个程序如何运行。某些类型的代码应该按照一定的顺序执行。很多时候,人们编写的代码必须按照特定的顺序执行,否则就无法运行或产生错误的结果。
有了这两点,我们就可以查看该方法的代码,找出并理解执行该方法的原因:
void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
static double price = 0;
bool bBuy, bSell;
def_AcessTerminal.DispatchMessage(id, lparam, dparam, sparam);
def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
switch (id)
{
case CHARTEVENT_KEYDOWN:
if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
{
if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP)) ToMarket(ORDER_TYPE_BUY);
if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))ToMarket(ORDER_TYPE_SELL);
}
break;
case CHARTEVENT_MOUSE_MOVE:
bBuy = def_AcessMouse.CheckClick(C_Mouse::eSHIFT_Press);
bSell = def_AcessMouse.CheckClick(C_Mouse::eCTRL_Press);
if (bBuy != bSell)
{
if (!m_Objects.bCreate)
{
def_AcessTerminal.CreateObjectGraphics(def_LINE_PRICE, OBJ_HLINE, m_Objects.corPrice, 0);
def_AcessTerminal.CreateObjectGraphics(def_LINE_STOP, OBJ_HLINE, m_Objects.corStop, 0);
def_AcessTerminal.CreateObjectGraphics(def_LINE_TAKE, OBJ_HLINE, m_Objects.corTake, 0);
EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_HideMouse, 0, 0, "");
m_Objects.bCreate = true;
}
ObjectMove(def_InfoTerminal.ID, def_LINE_PRICE, 0, 0, def_InfoMouse.Position.Price);
ObjectMove(def_InfoTerminal.ID, def_LINE_TAKE, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceTake, m_Infos.Leverage) * (bBuy ? 1 : -1)));
ObjectMove(def_InfoTerminal.ID, def_LINE_STOP, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceStop, m_Infos.Leverage) * (bSell ? 1 : -1)));
if ((def_AcessMouse.CheckClick(C_Mouse::eClickLeft)) && (price == 0)) CreateOrder((bBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), price = def_InfoMouse.Position.Price);
}else if (m_Objects.bCreate)
{
EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_ShowMouse, 0, 0, "");
ObjectsDeleteAll(def_InfoTerminal.ID, def_Prefix);
m_Objects.bCreate = false;
price = 0;
}
break;
}
}
不要害怕上面的代码,虽然这些代码乍一看似乎很复杂,但你真的不应该害怕它。我们所做的一切都很简单,也相对普通。代码的外观给人的第一印象是非常复杂和难以理解,所以,让我们一步一步来。首先,让我们来看看第一部分调用。为了更好地解释它们,我将把它们分成几个部分。我相信这样的解释会更容易理解。我们将集中讨论几个要点,这样您就不必滚动页面就能找到我们要讨论的内容。
def_AcessTerminal.DispatchMessage(id, lparam, dparam, sparam);
def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
还记得我们提到过,在某些情况下,我们需要所有事情都按照一定的顺序发生吗?上面这两行正是关于这一点的。它们将防止您忘记或(更糟糕的是)在 EA 代码中将事件处理放在错误的顺序中。在编写代码的现阶段,这并不会产生任何影响,但我们在 EA 中放置的代码越少,将来的效果就会越好。错过某个事件,我们可能会导致整个项目以完全出乎意料的方式运行,或者不是我们想要的方式运行。
这一切都很清楚。现在,我们可以从CHARTEVENT_KEYDOWN事件开始。它将处理您按下某个键时发生的触发行为。
case CHARTEVENT_KEYDOWN:
if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
{
if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP)) ToMarket(ORDER_TYPE_BUY);
if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))ToMarket(ORDER_TYPE_SELL);