English Русский Español Deutsch 日本語 Português
preview
开发回放系统(第 36 部分):进行调整(二)

开发回放系统(第 36 部分):进行调整(二)

MetaTrader 5示例 | 19 六月 2024, 13:49
259 0
Daniel Jose
Daniel Jose

概述

在上一篇文章中,开发回放系统(第 35 部分):进行调整 ,我们创建了一个 EA,可以轻松管理市场回放服务。虽然该文中的修正改善了用户体验,但我们还没有完全重新设计与回放/模拟系统相关的所有内容。

尽管用户体验有所改善,但我们仍有一个小问题。在本文中,我们将解决这个问题,尽管它相对简单(理论上),但要正确解决却相当困难。因此,我们将扩充有关 MetaTrader 5 的知识,更详细地学习如何使用 MQL5 语言。


显示市场类型

当 EA 位于图表上时,它会通知它所检测到的账户类型。这对于了解 EA 应如何行动非常重要。不过,尽管这样做效果很好,但当系统在真实账户或模拟账户的图表上运行时,系统不会使用控制重放/模拟系统的控制功能,也不会报告资产所属的账户类型,而是报告平台运行的账户类型。这个问题虽然不大,但确实给我们带来了一些不便。

你可能会认为,解决这个问题并不复杂或者很难,其实很简单。让我们确保回放/模拟系统以某种方式告诉你哪种账户类型是正确的。当然,这取决于它所使用的资产。其实,这个想法很简单。不过,让我们付诸实践吧 - 这将是另一个故事。事实上,让回放/模拟系统告诉我们使用哪种账户类型并非易事。但幸运的是,MetaTrader 5 平台为我们提供了机会,让我们可以实现一个足以满足实际使用需求且合理的解决方案。

然而,我们不会鲁莽行事。我们将以某种方式实施解决方案,防止某些账户类型出现某些情况。这些信息对我们创建订单系统非常重要。首先,让我们想一想我们在谈论什么。回放/模拟系统将能够使用来自不同市场的资产。这意味着我们可以使用 NETTING (净额)或 HEDGING (对冲)账户类型的资产。

。由于 EXCHANGE(交易所) 类型与 NETTING 非常相似,我们实际上不会使用这种 EXCHANGE 类型。我们将其视为 NETTING。

作为回放/模拟系统的用户,您知道资产使用的是哪种类型的市场,或者更具体地说是哪种类型的账户。然后,我们就可以在回放/模拟系统中添加用户指示功能。这是最简单的部分,因为我们只需在交易品种的配置文件中添加一条新命令。但这并不能保证真正需要信息的地方能够获得这些信息。那么,我们的回放/模拟系统在哪里使用这些信息呢?这些信息由 EA 交易(即 C_Manager 类和 C_Orders 类)使用。这就是实际使用这些特定信息的地方。即使目前没有这样做,也有必要从更广阔和通用的角度进行思考。我们可能需要使用我们在前面系列中看到的创建自动运行的 EA 的方法。

在该系列文章中,EA 需要知道正在使用的账户类型。正是出于同样的原因,我们需要确保回放/模拟系统也能告知 EA 这一点。否则,该系列中讨论的功能将丢失,我们也无法将其转移到我们的系统中。我们已经知道如何告诉回放/模拟服务特定资产使用的账户类型。但问题是如何将这些信息传递到 EA 中?

这才是我们真正需要停下来思考的地方。如果查看代码,就能知道账户类型。看一看吧:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                
      switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
         case ACCOUNT_MARGIN_MODE_EXCHANGE      : szInfo = "EXCHANGE";           break;
      }
      Print("Detected Account ", szInfo);
   }

这样,EA 就可以确定使用的账户类型。但这里有一个问题,那就是 AccountInfoInteger 函数。其实,函数本身并没有问题,因为它报告的正是我们要求它报告的内容。问题是,在使用回放/模拟服务时,AccountInfoInteger 函数的结果将是交易服务器上账户类型的信息。换句话说,如果我们连接到 NETTING 服务器,即使回放/模拟资产是 HEDGING,我们最终也将在 NETTING 帐户上运行。

这就是问题所在。现在说说想法。我们可以指示 EA 读取回放/模拟资产的配置文件。从某种意义上说,如果我们只使用一个文件,这样做是合适的。因此,我们可以要求回放/模拟服务传递这些账户类型信息。这样,EA 就能知道正确的类型。是的,没错。但这里有一个小问题。与 C/C++ 不同,MQL5 没有特定的代码构造类型。这并不是说完全不能使用某些编码形式在程序之间传递信息。我们已经多次看到这种可能性。但问题是,我们的信息必须包含在一个 8 字节的数据块中。这样就可以使用终端全局变量在程序之间传递信息。请记住,我们将使用 MQL5 进行此操作。还有其他方法可以做到这一点,但在这里我想使用 MQL5 平台和语言的功能。

让我们回到我们的问题上来:我们可以使用一种已经沿用已久的方法。为此,我们使用 InterProcess.mqh 文件来提供所需的通信。但这个文件有一个问题,我们将在本文中加以解决。为了了解这个问题,让我们来看看它的代码。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

问题就出在这些布尔型的值上。与 C/C++ 不同,在 C/C++ 中,每个布尔值可以包含在一个位中,而在 MQL5 中,每个布尔值将占用八个位。也许你不明白这到底是什么意思,但当我们只需要一个比特来存储所需的内容时,就会使用整数字节。对这一事实的忽视造成了问题。请记住,ushort 需要两个字节来传输信息。st_0 结构实际上占用四个字节,而不是我们预期的三个字节。如果我们在 st_0 结构中再增加四个布尔值,也就是只增加四个布尔值,那么我们将达到使用 8 个字节的极限。

这样一来,编程就会变得复杂一些,因此,完全在 MQL5 中完成所有工作的想法要比想象的复杂一些。如果事情变得更加复杂,或者我们需要在布尔模式下传递更多数据,我们就必须彻底改变这种结构。这简直就是一场后勤噩梦。不过,直到现在,我们还可以保留相同的结构。尽管如此,如果 MQL5 语言的开发人员允许我们使用与 C/C++ 相同的编程模式,那就更好了,至少在布尔类型的情况下是如此。这样,我们就可以确定每个变量所使用的位数,而编译器将完成组织、分离和分组变量的所有工作。这将使我们不必为实现这些目标而进行底层编程。只有一点不同:如果由 MQL5 开发人员来完成,结果会更有效率,因为它可以使用汇编代码或非常类似于机器语言的东西来组织和构建。如果只有我们开发人员来做这些工作,代码的效率就不会那么高,需要做的工作也会更多。

因此,这里为 MQL5 的改进提供了一个提示。一些看似微不足道,但在用这种语言编程时却非常有用的东西。

现在,让我们回到我们的代码中,因为我们仍然只能使用手头的代码。Interprocess.mqh 文件的新代码如下所示:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                bool    isHedging;      // If true we are in a Hedging account, if false the account is Netting...
                bool    isSync;         // If true indicates that the service is synchronized...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

请注意,我们在这里所做的只是添加了两个新的布尔型的变量。这足以解决眼前的问题,但却使我们非常接近 8 字节的限制。虽然我们只使用了四个布尔数(对应四个比特),但每个布尔数占八个比特,这意味着 st_0 结构的大小是六个字节,而不是预期的三个字节。

重要提示:对于那些不知道如果MQL5语言使用类似的C/C++建模来定义布尔结构,那么相同的代码会是什么样子的人,我只想说,阅读和编写代码不会带来任何麻烦。相同代码将只占用三个字节,而不是六个字节:

union u_Interprocess
{
        union u_0
        {
                double  df_Value;      // Value of the terminal global variable...
                ulong   IdGraphic;     // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {           
    		char  isPlay    : 1// Indicates whether we are in Play or Pause mode...
    		char  isWait    : 1// Tells the user to wait...                
    		char  isHedging : 1;   // If true we are in a Hedging account, if false the account is Netting...                
    		char  isSync    : 1// If true indicates that the service is synchronized...
                ushort  iPosShift;     // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};

MQL5 语言编译器无法理解上述代码,至少在撰写本文时是这样,但在这里我们告诉编译器,我们将在每个声明中只使用一位。适当地设置变量是编译器的工作,而不是程序员的工作,这样当我们访问一个位时,就能以更可控的方式进行,并使用标签而不是编译定义指令来实现。

 尽管在纯 MQL5 中仍然可以进行这种构造,但使用编译指令进行这种构造会导致错误和失败,而且还会使代码变得非常难以维护。我想把这一点说清楚,这样你就不会认为语言不允许我们做某些事情。

但如果世界给了我们柠檬,那就让我们做柠檬水吧。我们不要抱怨世界不是我们所期望的那样。对 Interprocess.mqh 文件进行上述更改后,我们就可以进入下一步了。现在,我们将修改 C_ConfigService.mqh。我们将从下面的代码开始进行一些修改:

        private :
                enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
                enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
                struct st001
                {
                        C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
                        int     Line;
                }m_GlPrivate;
                string  m_szPath;
                bool    m_AccountHedging;

我们在 C_ConfigService 类中添加了一个私有变量。这样,我们就可以记住下一步要处理的内容。首先,我们必须初始化这个变量。这是在类的构造函数中完成的:

C_ConfigService()
        :m_szPath(NULL),
         m_ModelLoading(1),
         m_AccountHedging(false)
   {
   }

之所以需要一个用于记忆的变量,是因为我们需要控制指标来创建一个全局终端变量,用于接收已配置的数据。不过,这个全局终端变量只有在模板将控制指标加载到图表时才会出现。只有在回放/模拟服务完成整个系统的设置后,图表才会出现。如果用户指定的设置值没有存储在某个地方,我们就无法告诉 EA 账户类型。但是,通过仔细编程,我们可以将回放/模拟服务中配置的账户类型传递给 EA。这样我们就能知道如何工作了。但前提是,您必须掌握如何创建自动 EA 的知识。

由于变量是私有的,而 C_Replay 类设置了一个全局终端变量,因此我们需要一个 C_Replay 类的方法来访问账户类型变量。代码如下:

inline const bool TypeAccountIsHedging(void) const
   {
      return m_AccountHedging;
   }

这将返回一个值。现在,我们需要确保用户指定的设置可以被我们的代码捕获和使用。为此,我们需要在捕捉系统中添加新代码。如下图所示:

inline bool Configs(const string szInfo)
   {
      const string szList[] = {
                               "PATH",
                               "POINTSPERTICK",
                               "VALUEPERPOINTS",
                               "VOLUMEMINIMAL",
                               "LOADMODEL",
                               "ACCOUNT"
                              };
      string  szRet[];
      char    cWho;
                                
      if (StringSplit(szInfo, '=', szRet) == 2)
      {
         StringTrimRight(szRet[0]);
         StringTrimLeft(szRet[1]);
         for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
         switch (cWho)
         {
            case 0:
               m_szPath = szRet[1];
               return true;
            case 1:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
               return true;
            case 2:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
               return true;
            case 3:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
               return true;
            case 4:
               m_ModelLoading = StringInit(szRet[1]);
               m_ModelLoading = ((m_ModelLoading < 1) && (m_ModelLoading > 4) ? 1 : m_ModelLoading);
               return true;
            case 5:
               if (szRet[1] == "HEDGING") m_AccountHedging = true;
               else if (szRet[1] == "NETTING") m_AccountHedging = false;
               else
               {
                  Print("Entered account type is not invalid.");                                                          
                  return false;
               }
               return true;                    
         }
         Print("Variable >>", szRet[0], "<< not defined.");
      }else
         Print("Configuration definition >>", szInfo, "<< invalidates.");
                                        
      return false;
   }

有了这个新代码,我们就能知道使用哪个账户。它非常容易操作,同时也绝对实用。现在,配置文件可能如下所示:

[Config]
Path = Forex\EURUSD
PointsPerTick = 0.00001
ValuePerPoints = 1.0
VolumeMinimal = 0.01
Account = HEDGING

或者像这样:

[Config]
Path = Petrobras PN
PointsPerTick = 0.01
ValuePerPoints = 1.0
VolumeMinimal = 100.0
Account = NETTING

我们有各种资产可供回放/模拟服务使用,但作为用户,您可以决定要使用的账户类型。它非常简单直接。这样,我们就可以将我们的分析扩展到任何类型的市场。它允许我们使用我们已知的或我们开发的任何方法。因为现在与 C_ConfigService 类相关的工作已经完成,那就让我们来看看如何让 C_Replay 类为我们设置这些数据。要实现这一点,并让用户指定的配置对整个回放/模拟系统可见,我们不需要做太多工作。我们需要做的一切都显示在下面的代码中:

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
      u_Interprocess info;
                                
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
         macroError("Asset configuration is not complete, it remains to declare the size of the ticket.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
         macroError("Asset configuration is not complete, need to declare the ticket value.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
         macroError("Asset configuration not complete, need to declare the minimum volume.");
      if (m_IdReplay == -1) return false;
      if ((m_IdReplay = ChartFirst()) > 0) do
      {
         if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
         {
            ChartClose(m_IdReplay);
            ChartRedraw();
         }
      }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
      Print("Waiting for [Market Replay] indicator permission to start replay ...");
      info.ServerTime = ULONG_MAX;
      CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value);
      info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
      ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
      CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
      while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      info.s_Infos.isHedging = TypeAccountIsHedging();
      info.s_Infos.isSync = true;
      GlobalVariableSet(def_GlobalVariableReplay, info.u_Value.df_Value);

      return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
   }

我们从代码中删除了一行。原因是我们需要 C_Replay 类加载控制指标放置的变量的值,这样我们才能知道我们正在处理什么。请记住,我们不应该做出假设。将来,一旦控制指标开始运行,我们可能需要向服务传递值。之后,我们将读取用户指定的值,并报告系统已同步,然后将值发送到 MetaTrader 5,通过全局终端变量提供数据。这就完成了回放/模拟服务的部分,但我们还没有编写代码。现在,我们需要修改 EA,使其能够知道用户是如何配置回放/模拟的。同样,我们将在另一个专题中探讨所有相关的解释:


让 EA 交易知道账户类型

为了了解如何让 EA 识别用户自定义账户类型,我们需要了解整个系统是如何工作的。让我们来看看图 01,它简要描述了初始化过程是如何进行的。

图 01

图 01 - 回放/模拟系统初始化过程。

为什么了解这张图如此重要?因为 C_Terminal 类是控制指标和 EA 交易的通用类。如果不了解这一点,就无法理解 MetaTrader 5 中系统如何以及为何能够遵循用户配置的设置。此外,如果我们不了解这种初始化,就可能会通过编辑配置回放/模拟程序的文件来做蠢事。这将导致整个系统无法正常工作。因此,当我们在实践中使用通过回放/模拟过程所学到的知识时,我们可能会对自己应该如何行动产生错误的印象,而不是获得接近真实市场的经验。因此,有必要了解第一张图是什么意思。

  • 所有蓝色箭头表示初始化的第一阶段。这发生在我们作为用户在 MetaTrader 5 中启动服务的那一刻。
  • 之后,我们进入第二阶段,即服务创建图表并使用模板初始化 EA 和控制指标的阶段。黄色箭头表示这一阶段。
  • 下一步(绿色箭头所示)是使用控制指标创建一个全局终端变量,并将该变量配置到回放/模拟服务中。此时,我们看到 C_Terminal 类正在向控制指标发送数据,但没有向 C_Manager 类发送数据,而 C_Manager 类目前正在被 EA 初始化。
  • 最后一步(紫色箭头)是根据用户在文件中提供的数据进行设置,指定回放/模拟的工作方式。EA 需要这些信息来了解正在使用的账户类型。

虽然看起来有些混乱,但我们必须非常小心,以确保 C_Terminal 类允许控制指标执行其工作。同时,在正确配置 C_Manager 类之前,不要让 EA 执行任何操作。为什么我说应该在 C_Terminal 类而不是在 C_Manager 类中进行管理?在 C_Manager 类中实现这一点可能更容易。为什么不呢?原因并不只是选择用哪个类来保存东西,真正的原因是 C_Orders 类。如果控制系统在 C_Manager 类中,C_Orders 类将无法访问我们需要的数据。由于我们将控制放在 C_Terminal 类中,因此即使我们以后使用其他类来管理订单,它仍能访问我们需要的数据。如图 01 所示,我们不能随意选择一种方式来实现这一点。我们需要确保这一切都正确无误。否则,整个系统可能会在启动后立即发生故障。我知道这很难理解,甚至可能令人震惊,但请相信我,如果编程工作做得不够严格,系统就会出错。让我们看看该系统是如何实现的。让我们从下面的代码开始:

class C_Terminal
{
        protected:
                enum eErrUser {ERR_Unknown, ERR_PointerInvalid};
                enum eEvents {ev_Update};
//+------------------------------------------------------------------+
                struct st_Terminal
                {
                        ENUM_SYMBOL_CHART_MODE   ChartMode;
                        ENUM_ACCOUNT_MARGIN_MODE TypeAccount;
                        long    ID;
                        string  szSymbol;
                        int     Width,
                                Height,
                                nDigits;
                        double  PointPerTick,
                                ValuePerPoint,
                                VolumeMinimal,
                                AdjustToTrade;
                };
//+------------------------------------------------------------------+
        private :
                st_Terminal m_Infos;
                struct mem
                {
                        long    Show_Descr,
                                Show_Date;
                        bool    AccountLock;
                }m_Mem;

通过该变量,可以使用本文中已经实现的方法访问账户类型。我们不必在类中添加新的代码来获取这些信息,这非常好。但我们还有另一个变量。它的作用是 "锁",但你很快就会明白它的用途和使用方法。现在让我们来看看该类的构造函数:

C_Terminal()
   {
      m_Infos.ID = ChartID();
      m_Mem.AccountLock = false;
      CurrentSymbol();
      m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
      m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
      m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
      m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
      m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
      m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
      m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
      m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
      m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
      m_Infos.ChartMode     = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
      if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
      ResetLastError();
   }

在这里,我们初始化 "lock "变量,向 C_Terminal 类表明账户类型值尚未取得,但我们正在为此做些什么,这一点非常重要。当系统检测到某资产不是回放系统资产时,C_Terminal 类必须初始化账户数据。如果是回放系统资产,则数据不会现在初始化,而是稍后在 C_Manager 类中初始化。我们必须理解这一点,因为它很快就会变得非常重要。如果资产是回放系统资产,C_Manager 类将初始化数据。如果资产不是回放系统资产,则在此阶段对其进行初始化。接下来我们要调用以下方法:

inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
   {
      if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
      m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
   }

这个方法非常重要。没有它,就无法知道所使用的账户类型。因为在这种情况下,你会假设使用的是这种或另一种类型。重要的是,一旦开始使用某种账户类型,变量值就不会改变。这就是我们使用这种锁的原因。我强调这一点:只有当 lock 变量设置为 false 时,我们才能根据传递给方法的参数初始化其值。此后,在代码执行结束之前,变量将无法更改。另一个细节是,我们将仅限于 NETTING 和 HEDGING 类型。这是因为 EXCHANGE 的操作与 NETTING 类型相同。既然我们使用的是非常接近真实市场的交易系统,我认为将自己限制在 NETTING 型和 HEDGING 型没有问题。至此,C_Terminal 类的代码部分就完成了。现在让我们看看 C_Manager 类中有哪些需要修改的地方。在这个 C_Manager 类中,我们只需修改构造函数代码即可。现在看起来是这样的:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
      u_Interprocess info;
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                                                                        
      if (def_InfoTerminal.szSymbol == def_SymbolReplay)
      {
         do
         {
            while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag)) Sleep(750);
         }while ((!info.s_Infos.isSync) && (!_StopFlag));
         def_AcessTerminal.SetTypeAccount(info.s_Infos.isHedging ? ACCOUNT_MARGIN_MODE_RETAIL_HEDGING : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
      };
      switch (def_InfoTerminal.TypeAccount)
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
      }
      Print("Detected Account ", szInfo);
   }

你可能会觉得一切都没变。更糟糕的是,你可能不知道发生了什么。因为在这里,我们有一个循环,在某些条件下,它可以进入一个无休止的循环。但大部分代码都没有变化,只有一点不同,而这一点正是关键所在。在此之前,我们检查了所有账户类型。现在我们只留下两个。而用于查找账户类型的信息就在 C_Terminal 类中创建的变量中。不过,让我们仔细看看代码中真正新的部分。当它判断所使用的资产与回放/模拟系统中的资产相同时,它就会启动。如果检查通过,我们将进入一个双循环,即一个外部循环和嵌套在第一个循环中的另一个循环。

在这个嵌套循环中,我们将等待控制指标创建一个全局终端变量,以便回放/模拟服务可以对其进行设置。如果用户终止了程序,或者控制指标创建了全局终端变量,我们就会退出嵌套循环,进入外部循环。这个外部循环只会在两种情况下结束:第一种情况是回放/模拟服务设置了全局终端变量;第二种情况是用户关闭了程序。除这两种情况外,循环都不会结束,而是重新进入嵌套循环。如果一切顺利,外循环结束,那么账户类型值将被发送到 C_Terminal 类。 


结论

在这篇文章中,我们解决了一个问题,这个问题虽小,但将来可能会让我们头疼不已。我希望您能理解这些更改的重要性,最重要的是,我们是如何使用 MQL5 解决这个问题的。附件中包含源代码以及用于回放/模拟的更新文件。如果您已经在使用该服务,即使还不能用于分析,也请务必更新负责设置回放/模拟服务的文件,以便使用适当的账户类型。否则,系统将使用默认的 HEDGING 账户类型。

这可能会导致未来出现问题。所以,别忘了更新。


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

附加的文件 |
Files_-_FOREX.zip (3744 KB)
Files_-_BOLSA.zip (1358.28 KB)
Files_-_FUTUROS.zip (11397.55 KB)
种群优化算法:Nelder-Mead(NM),或单纯形搜索方法 种群优化算法:Nelder-Mead(NM),或单纯形搜索方法
本文表述针对 Nelder-Mead 方法进行的彻底探索,解释了如何在每次迭代中修改和重新排列单纯形(函数参数空间),从而达成最优解,并讲述了如何改进该方法。
神经网络变得简单(第 65 部分):距离加权监督学习(DWSL) 神经网络变得简单(第 65 部分):距离加权监督学习(DWSL)
在本文中,我们将领略一个有趣的算法,它是在监督和强化学习方法的交叉点上构建的。
交易者容易使用的止损和止盈 交易者容易使用的止损和止盈
止损(stop loss)和止盈(take profit)对交易结果有重大影响。本文将介绍几种寻找最佳止损单价格的方法。
如何在自由职业者服务中通过完成交易员的订单来赚钱 如何在自由职业者服务中通过完成交易员的订单来赚钱
MQL5 自由职业者是一项在线服务,开发人员可以通过这项服务为交易员客户创建交易应用程序而获得收入。该服务自 2010 年起成功运营,迄今已完成超过 10 万个项目,总价值达 700 万美元。我们可以看到,这里涉及到大量资金。