市场模拟(第五部分):创建 C_Orders 类(二)
概述
在上一篇文章,市场模拟(第四部分):启动 C_Orders 类 (一)中,我当时主要专注于解释发送市价交易指令的代码是什么样子。整个解释旨在演示如何构建类代码,以便解码从 Chart Trade 指标接收的信息。
然而,即使没有看过 EA 交易的源代码,我相信有一定经验的人已经可以在实践中实现它了。尽管上一篇文章中展示的代码似乎无法在真实交易服务器上执行操作,但事实并非完全如此。不过,这段代码确实不足以用于对冲账户。
对于净额结算账户,该代码已经能够开仓和平仓。但对于对冲型账户,情况则略有不同。
例如,如果您尝试使用卖出按钮平掉一个买入头寸,实际上您是开立了一个卖出头寸。这适用于对冲账户。在单边账户中,执行相同的操作会平掉买入头寸,或者至少会产生某种效果,例如:
- 仓位反转(翻转)。如果你卖出的交易量超过了你现有的买入交易量,就会发生这种情况。在这种情况下,你的持仓将从多头转为空头。新空头头寸的大小将是前一个买入头寸的交易量与反转卖出头寸的交易量之差。
- 部分平仓。如果卖出交易量小于现有买入交易量,就会发生这种情况。在这种情况下,部分买单将被平仓,剩余部分仍留在市场上未结清。
这一切都相当有趣,我曾在不久前发表的一系列文章中详细阐述过。该系列旨在深入解释如何创建可以自动运行的 EA 交易系统。它由 15 篇文章组成,每篇文章都涵盖了开发此类 EA 交易所需的一个方面和注意事项。从手动操作模式到全自动系统。有兴趣了解更多内容的人可以参考该系列文章。第一篇文章可以在这里找到:学习构建自动运行的 EA(第 01 部分):概念和结构。
这里显示的大部分代码都来自同一系列。然而,这里的重点将略有不同。这是因为我们的主要目标不同。换句话说,我们的目标不是开发一个发送订单或与实时交易服务器通信的系统。我们真正想要做的 —— 也是我们将要做的 —— 是开发一个模拟服务器。然而,在我们做到这一点之前,我们需要实施一些事情。由于理想的情况是我们的回放/模拟系统和真实交易服务器(无论是在模拟账户还是真实账户中)之间共享组件,我们将首先实现 EA 交易,以便它可以与真实服务器通信。一旦该实现工作完成,我们将继续开发模拟交易服务器。
这样做的原因是定义和限制我们实际需要实现的调用类型。至少在最初,我不打算模拟真实服务器可以响应的每一个调用,因为在大多数情况下,我们根本不需要所有调用。随着时间的推移,我可能会改变主意,但就目前而言,这种方法就足够了。那么,我们现在可以开始本文的第一个主题了。
处理平仓消息
好吧,如果你还没有理解上一篇文章的内容,我建议你先回去复习一下,因为我们将从那里继续。然而,由于对代码结构进行了小幅改进,您将在下方再次看到完整的代码。也就是说,上一篇文章中已经解释过的部分在此不再赘述,因为所做的结构更改不会使之前给出的任何解释无效。那么,让我们继续讨论代码本身。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Defines.mqh" 005. //+------------------------------------------------------------------+ 006. class C_Orders 007. { 008. protected: 009. //+------------------------------------------------------------------+ 010. inline const ulong GetMagicNumber(void) const { return m_Base.MagicNumber; } 011. //+------------------------------------------------------------------+ 012. bool ClosePosition(const ulong ticket) 013. { 014. bool IsBuy; 015. string szContract; 016. 017. if (!PositionSelectByTicket(ticket)) return false; 018. IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; 019. szContract = PositionGetString(POSITION_SYMBOL); 020. ZeroMemory(m_Base.TradeRequest); 021. m_Base.TradeRequest.action = TRADE_ACTION_DEAL; 022. m_Base.TradeRequest.type = (IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY); 023. m_Base.TradeRequest.price = NormalizeDouble(SymbolInfoDouble(szContract, (IsBuy ? SYMBOL_BID : SYMBOL_ASK)), (int)SymbolInfoInteger(szContract, SYMBOL_DIGITS)); 024. m_Base.TradeRequest.position = ticket; 025. m_Base.TradeRequest.symbol = szContract; 026. m_Base.TradeRequest.volume = PositionGetDouble(POSITION_VOLUME); 027. m_Base.TradeRequest.deviation = 1000; 028. 029. return SendToPhysicalServer() != 0; 030. }; 031. //+------------------------------------------------------------------+ 032. private : 033. //+------------------------------------------------------------------+ 034. struct stBase 035. { 036. MqlTradeRequest TradeRequest; 037. ulong MagicNumber; 038. bool bTrash; 039. }m_Base; 040. //+------------------------------------------------------------------+ 041. struct stChartTrade 042. { 043. struct stEvent 044. { 045. EnumEvents ev; 046. string szSymbol, 047. szContract; 048. bool IsDayTrade; 049. ushort Leverange; 050. double PointsTake, 051. PointsStop; 052. }Data; 053. //--- 054. bool Decode(const EnumEvents ev, const string sparam) 055. { 056. string Res[]; 057. 058. if (StringSplit(sparam, '?', Res) != 7) return false; 059. stEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], Res[2], (bool)(Res[3] == "D"), (ushort) StringToInteger(Res[4]), StringToDouble(Res[5]), StringToDouble(Res[6])}; 060. if ((ev == loc.ev) && (loc.szSymbol == _Symbol)) Data = loc; 061. else return false; 062. 063. return true; 064. } 065. //--- 066. }m_ChartTrade; 067. //+------------------------------------------------------------------+ 068. ulong SendToPhysicalServer(void) 069. { 070. MqlTradeCheckResult TradeCheck; 071. MqlTradeResult TradeResult; 072. 073. ZeroMemory(TradeCheck); 074. ZeroMemory(TradeResult); 075. if (!OrderCheck(m_Base.TradeRequest, TradeCheck)) 076. { 077. PrintFormat("Order System - Check Error: %d", GetLastError()); 078. return 0; 079. } 080. m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult); 081. if (TradeResult.retcode != TRADE_RETCODE_DONE) 082. { 083. PrintFormat("Order System - Send Error: %d", TradeResult.retcode); 084. return 0; 085. }; 086. 087. return TradeResult.order; 088. } 089. //+------------------------------------------------------------------+ 090. ulong ToMarket(const ENUM_ORDER_TYPE type) 091. { 092. double price = SymbolInfoDouble(m_ChartTrade.Data.szContract, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID)); 093. double vol = SymbolInfoDouble(m_ChartTrade.Data.szContract, SYMBOL_VOLUME_STEP); 094. uchar nDigit = (uchar)SymbolInfoInteger(m_ChartTrade.Data.szContract, SYMBOL_DIGITS); 095. 096. ZeroMemory(m_Base.TradeRequest); 097. m_Base.TradeRequest.magic = m_Base.MagicNumber; 098. m_Base.TradeRequest.symbol = m_ChartTrade.Data.szContract; 099. m_Base.TradeRequest.price = NormalizeDouble(price, nDigit); 100. m_Base.TradeRequest.action = TRADE_ACTION_DEAL; 101. m_Base.TradeRequest.sl = NormalizeDouble(m_ChartTrade.Data.PointsStop == 0 ? 0 : price + (m_ChartTrade.Data.PointsStop * (type == ORDER_TYPE_BUY ? -1 : 1)), nDigit); 102. m_Base.TradeRequest.tp = NormalizeDouble(m_ChartTrade.Data.PointsTake == 0 ? 0 : price + (m_ChartTrade.Data.PointsTake * (type == ORDER_TYPE_BUY ? 1 : -1)), nDigit); 103. m_Base.TradeRequest.volume = NormalizeDouble(vol + (vol * (m_ChartTrade.Data.Leverange - 1)), nDigit); 104. m_Base.TradeRequest.type = type; 105. m_Base.TradeRequest.type_time = (m_ChartTrade.Data.IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC); 106. m_Base.TradeRequest.stoplimit = 0; 107. m_Base.TradeRequest.expiration = 0; 108. m_Base.TradeRequest.type_filling = ORDER_FILLING_RETURN; 109. m_Base.TradeRequest.deviation = 1000; 110. m_Base.TradeRequest.comment = "Order Generated by Experts Advisor."; 111. 112. MqlTradeRequest TradeRequest[1]; 113. 114. TradeRequest[0] = m_Base.TradeRequest; 115. ArrayPrint(TradeRequest); 116. 117. return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? SendToPhysicalServer() : 0); 118. }; 119. //+------------------------------------------------------------------+ 120. void CloseAllsPosition(void) 121. { 122. for (int count = PositionsTotal() - 1; count >= 0; count--) 123. { 124. if (PositionGetSymbol(count) != m_ChartTrade.Data.szContract) continue; 125. if (PositionGetInteger(POSITION_MAGIC) != m_Base.MagicNumber) continue; 126. ClosePosition(PositionGetInteger(POSITION_TICKET)); 127. } 128. }; 129. //+------------------------------------------------------------------+ 130. public : 131. //+------------------------------------------------------------------+ 132. C_Orders(const ulong magic) 133. { 134. m_Base.MagicNumber = magic; 135. } 136. //+------------------------------------------------------------------+ 137. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 138. { 139. switch (id) 140. { 141. case CHARTEVENT_CUSTOM + evChartTradeBuy : 142. case CHARTEVENT_CUSTOM + evChartTradeSell : 143. case CHARTEVENT_CUSTOM + evChartTradeCloseAll: 144. if (m_ChartTrade.Decode((EnumEvents)(id - CHARTEVENT_CUSTOM), sparam)) switch (m_ChartTrade.Data.ev) 145. { 146. case evChartTradeBuy: 147. ToMarket(ORDER_TYPE_BUY); 148. break; 149. case evChartTradeSell: 150. ToMarket(ORDER_TYPE_SELL); 151. break; 152. case evChartTradeCloseAll: 153. CloseAllsPosition(); 154. break; 155. } 156. break; 157. } 158. } 159. //+------------------------------------------------------------------+ 160. }; 161. //+------------------------------------------------------------------+
C_Orders.mqh 头文件的代码
就在这里,上述代码能够正确响应 Chart Trade 指标发送给 EA 交易系统的所有消息,以便与交易服务器进行通信。从现在开始,Chart Trade 指标中的所有按钮和控件都将变为可用状态。您可以在任何 EA 交易系统中使用此代码 —— 只要您进行一些调整,这些调整将在本文后面讨论。这里的想法正是为了使这成为可能。您将能够使用构成 Chart Trade 和鼠标指标的各个模块。通过使用上面显示的代码,您可以使您的 EA 交易系统与 Chart Trade 交易和鼠标指标中已编译的模块兼容。换句话说:编程一次,测试确认有效后,就不要再动它了。然后进入下一阶段。这就是我的想法。
对于那些刚刚开始并且还不能完全理解代码的人来说,让我们来看看这里的新内容。请记住,我提到的结构更改发生在第 34 行,我将类的私有变量分组到一个结构中。然而,如前所述,这并不意味着上一篇文章中所解释的内容无效。
你可能想知道的第一件事是:当我们在 Chart Trade 指标上按下平仓按钮时,这个类是如何知道应该平掉哪个仓位的?答案是:该类不知道应该关闭哪个仓位。你可能会觉得这很荒谬,因为当你查看按钮时,负责平仓的按钮被标记为 “平掉所有仓位”。所以,从逻辑上讲,你会期望所有仓位都被平仓。是这样吗?错了。实际上,这个类并不是在做这件事。至少,如果不稍作修改,上面的代码是无法实现的。
此时,你可能会感到困惑。如果按下 “平仓所有仓位” 按钮并不能平掉所有仓位,为什么按钮上会显示这个信息?为了理解这一点,我们需要稍微回顾一下过去,然后再深入了解类代码的实际工作原理。
关于跨期订单系统的文章:
我解释了为什么有时我们需要使用一种特殊类型的资产 —— 所谓的历史资产。在那里,我解释说,我们必须确保 Chart Trade 指标能够告诉 EA 交易系统应该交易哪个实际资产。这是因为“历史资产”不是可交易资产;因此,不能使用它发送交易请求。这些历史资产的存续很大程度上是由于合约结构造成的。我在那两篇文章中也解释过它们。然而,在这里,问题变得更加复杂。您可能正在使用历史资产的图表,并要求 EA 交易系统随时从完整合约切换到迷你合约。这将导致 EA 交易系统以及更重要的 Chart Trade 指标改变所交易的资产。
现在真正的问题来了。例如,假设你一直在使用美元的历史图表,并且你已经告诉 EA 交易系统对完整的美元合约进行操作。后来,你改变主意,要求 EA 交易系统切换到迷你合约。EA 交易系统会向 Chart Trade 指标发送消息,告知其合约变更情况。到现在为止,一直都还不错。但如果你持有的是完整合约的未平仓位,问题就出现了。如果你告诉 EA 交易系统切换到迷你合约,即使你有一个未平仓的完整合约头寸,EA 交易系统也会照做。Chart Trade 指标也会相应更新。现在我们遇到问题了。这是您必须理解的问题,以便使代码适应您的需求。
如果此时 Chart Trade 指标显示可交易资产为迷你合约,那么当您请求平仓所有未平仓位时会发生什么情况?请记住,未平仓位属于完整合约。Chart Trade 指标确实会向 EA 交易系统发送消息。如前文所述,此消息包含了 EA 交易系统知道该做什么所需的所有数据。然而,EA 交易系统实际上并不知道该怎么做。这是因为它基于无法交易的图表(合约的历史图表)进行操作。为了解决这个问题,在代码的第 153 行拦截了该消息。到那时,信息应该已经被成功翻译了。然后,代码继续执行到第 120 行,这里真正的工作才刚刚开始。
第 122 行有一个循环。它会读取所有未平仓位。全部都是,无一例外。但为什么要读取所有仓位,即使是那些与我们的美元例子无关的仓位呢?因为我们没有存储服务器在每次开仓时提供的单号。因此,我们必须再次查找那些单号。
下一个问题是:为什么我们不保存这些单号?这肯定会让事情变得更简单。确实如此。但是,这些单号无法在 EA 交易系统本身中存储。对涉及 EA 交易系统的图表进行任何更改都会导致该单号信息丢失。因此,每当需要时,我们都不得不再次检索它们。每次更改图表时间周期或合约类型时,都会发生这种情况。
所以,目前我们将按这个方案进行。但这样做意味着我们不能简单地调用该过程来平仓 —— 因为我们要遍历所有仓位,包括与 Chart Trade 消息无关的仓位。
因此,我们需要应用过滤器。第一个过滤器位于第 124 行,我们检查仓位的交易品种名称,并将其与从 Chart Trade 指标收到的名称进行比较。现在请注意:在我们的示例中,我们持有的是完整合约的未平仓头寸。但 Chart Trade 的消息表明这是迷你合约。因此,此测试将失败,因为资产名称不匹配。反过来也会发生同样的情况 —— 如果我们有一个未平仓的迷你合约头寸,并试图使用 Chart Trade 来平掉全部合约头寸。这项测试可以避免此类混淆。
这里有个重要提示。虽然这项测试可能会使事情变得复杂,但它是绝对必要的。即使您使用历史合约通过跨期订单系统交易迷你合约和完整合约,也绝不能删除第 124 行的此测试。即使你想平掉任何与美元相关的头寸,这个测试也必须保留。因为如果没有它,在使用 Chart Trade 时,可能会平掉完全不同资产的仓位,因为我们会扫描所有未平仓位。
如果第 124 行的测试成功,我们将获得一张新的单号。我们转而进行另一项测试,旨在分离和筛选其他内容。在上一篇文章中,我提到过这个类代表一个独特的实体,由幻数标识。这样,一个 EA 交易系统就可以包含多个独立的交易策略,每个策略都用一个幻数来分隔。
但对于单边账户来说,这没有意义,因为在这种类型的账户中,交易服务器会对持仓价格进行平均。这意味着每种资产只能有一个未平仓头寸。
但在对冲账户中,情况则完全不同。您可以同时持有同一资产的多个仓位 —— 包括买入和卖出。在这种情况下,此类 EA 交易系统必须与其他 EA 交易系统有所区别。为了避免把事情复杂化,我就不详细介绍具体操作方法了。你只需要知道这确实是可行的,并且本类也支持这种做法。
除了 EA 交易系统开仓(这些仓位会用幻数标记,表明是哪个类创建的)之外,还有另一种可能性:交易者可能手动开仓了。事情可能会变得棘手,因为交易者在使用 Chart Trade 时可能不希望手动平仓。在这种情况下,EA 交易系统会将仓位的幻数与类的幻数进行比较 —— 如果不匹配,则忽略该仓位并保持开仓状态。因此,如果交易者手动开仓,EA 交易系统将保持原样,必须手动平仓。
你可以看到这些行为是如何展开的。虽然可以进行修改,但整个系统主要设计用于对冲账户,以确保它不会干扰其范围之外的任何事情。因此,如果两个测试都通过,则执行第 126 行 —— 捕获仓位单号并调用平掉指定仓位的过程。此过程位于第 12 行。请注意,它位于受保护的子句中,因此可以通过继承访问,但不能直接从继承系统外部访问。简而言之:它不能直接从 EA 交易系统代码或任何其他外部代码中调用。
现在,这段代码有一些特殊之处,我最终可能会将这部分删除。情况是这样的:当调用第 12 行的代码时,它已经知道一些信息,例如资产名称和单号。但是,在第 17 行,它再次检查该单号是否属于未平仓位。为什么要重复这项检查?因为目前我还不确定另一个工具(仍在开发中)将如何与此过程进行通信。不过,我们仍然需要使用库函数 PositionSelectByTicket 来刷新我们需要从服务器获取的某些数据。然而,当在第 124 行调用 PositionGetSymbol 时,此更新已经发生。换句话说,这里存在一些重复内容。但就目前而言,这并不是问题。
无论如何,我们确实需要刷新一下信息。数据更新后,单号被确认为有效仓位,第 18 行检查该仓位是买入还是卖出。这很重要,然后,第 19 行捕获该仓位的资产名称。将来一旦新工具实施,这种情况也可能会改变,因为仅从这个类来看,这些步骤是多余的 —— 第 120 行的过程可以直接传递此信息。再次声明,我目前还不清楚 ClosePosition 未来会如何运作。接下来,我们将进入填写要发送到服务器的结构体的步骤。
与上一篇文章中讨论的 ToMarket 流程不同,我在上一篇文章中说每个字段的解释将在后面进行,而在这里我可以解释主要字段,理解它们对以后很重要。第 21 行,我们告诉服务器要执行哪种操作。现在,有些人可能会问:既然我们要平仓,为什么不使用 TRADE_ACTION_CLOSE_BY 呢?这是一个合理的问题 —— 主要原因在于单边账户。要理解这一点,请查看第 26 行,其中我们指定了交易量。如果我们使用 TRADE_ACTION_CLOSE_BY ,服务器将直接平仓。
这将迫使我们为单边账户已经允许的另一个操作(部分结算)创建额外的逻辑和代码。部分平仓是指我们只平掉一部分仓位。这里,我还没有实现部分平仓或反转的逻辑。这是一个设计选择 —— 取决于应该实现什么或不应该实现什么。由于该系统可用于两种账户类型,我们必须优先考虑某些功能而非其他功能。单边账户和对冲账户各有其功能 —— 有些功能是互斥的。我正在努力兼顾两者。这就是为什么这个解释很重要。
所有其他字段都指定服务器的详细信息:我们是买入还是卖出(第 22 行);交易应该发生的价格(第 23 行);持仓的单号(第 24 行);股票代码名称(第 25 行)—— 请注意,它必须与单号的名称匹配,否则会发生错误;交易量,它决定了要执行的操作(第 26 行);最后是偏差,即允许的价格滑点(第 27 行)。所有这些信息都指示服务器执行市价请求 —— 以最佳可用价格执行交易、修改交易量或方向,或根据需要平仓。
请注意,与仅平仓的 TRADE_ACTION_CLOSE_BY 不同,这种方法提供了更大的灵活性。这就是我使用 TRADE_ACTION_DEAL 的 原因。但是,这仅适用于仓位已经开立的情况。但无法修改止损或止盈水平。这些问题将通过另一项措施来处理,稍后会详细讨论。目前,我们将继续使用 MetaTrader 5 提供的系统。这意味着,即使从技术上讲可以使用历史合约进行交易,你也不会在该图表上看到价格线。它们将出现在实际交易的合约上。将来,这种情况也会改变。
所以,最后需要考虑的就是 EA 交易系统的代码了。就在这里:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property icon "/Images/Market Replay/Icons/Replay - EA.ico" 04. #property description "Demo version between interaction" 05. #property description "of Chart Trade and Expert Advisor" 06. #property version "1.84" 07. #property link "https://www.mql5.com/pt/articles/12598" 08. //+------------------------------------------------------------------+ 09. #include <Market Replay\Order System\C_Orders.mqh> 10. //+------------------------------------------------------------------+ 11. enum eTypeContract {MINI, FULL}; 12. //+------------------------------------------------------------------+ 13. input eTypeContract user00 = MINI; //Cross order in contract 14. //+------------------------------------------------------------------+ 15. C_Orders *Orders; 16. long GL_ID; 17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. GL_ID = 0; 21. Orders = new C_Orders(0xC0DEDAFE78514269); 22. 23. return INIT_SUCCEEDED; 24. } 25. //+------------------------------------------------------------------+ 26. void OnTick() {} 27. //+------------------------------------------------------------------+ 28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 29. { 30. (*Orders).DispatchMessage(id, lparam, dparam, sparam); 31. switch (id) 32. { 33. case CHARTEVENT_CHART_CHANGE: 34. if (GL_ID > 0) break; else GL_ID = ChartID(); 35. case CHARTEVENT_CUSTOM + evChartTrade_At_EA: 36. EventChartCustom(GL_ID, evEA_At_ChartTrade, user00, 0, ""); 37. break; 38. } 39. } 40. //+------------------------------------------------------------------+ 41. void OnDeinit(const int reason) 42. { 43. switch (reason) 44. { 45. case REASON_REMOVE: 46. case REASON_INITFAILED: 47. EventChartCustom(GL_ID, evEA_At_ChartTrade, -1, 0, ""); 48. break; 49. } 50. 51. delete Orders; 52. } 53. //+------------------------------------------------------------------+
EA 交易源代码
这篇文章中已经几乎完整地展示了这段代码。市场模拟(第二部分):跨期订单(二)中仅进行了一些小的改动,即可使用 C_Orders 类。这些改动非常简单。但是,如果你没有太多 C 语言编程经验,有些东西可能会显得有点奇怪。但这并没有什么神秘之处,只是略有不同而已。第一个更改出现在第 9 行,我们在此处引入了本文中介绍的头文件。另一处改动在第 15 行,我们在那里使用指针声明了类。
在第 21 行,我们使用 NEW 运算符初始化类。你可能会在那里发现一些不寻常的东西。类构造函数被声明为期望一个 64 位长的整数,但传递的值看起来很奇怪: 0xC0DEDAFE78514269,包含字母和数字。那是什么奇怪的东西?其实,这根本不奇怪,这是一个十六进制值。注意前缀 0x,它告诉编译器后面的值是十六进制数。这在很多情况下都非常有用。但是,当你稍后将此值视为仓位的幻数时,你会看到完全不同的东西 —— 这个十六进制值的十进制表示。那个十进制数是多少,就留给你们自己去发现吧,我不会在这里透露。
最终,最后一处修改是在第 30 行。这一行代码传递了必要的数据,以便该类能够执行其工作。它使 EA 交易系统能够处理 Chart Trade 指标发送的消息。请注意,由于整个系统被划分为模块,因此只需很少的代码,我们就已经有了一些非凡而有趣的东西。这种模块化设计意味着一个模块的改进不会直接影响另一个模块,从而使代码能够安全、高效地增长。这与单体系统形成了鲜明对比,在单体系统中,所有内容都包含在一个庞大的文件中,这使得开发和维护更加累人,更容易出错。
最后的探讨
在最近的两篇文章中,我介绍了 EA 交易系统以及负责发送市价单的类代码。该系统可完美适用于任何类型的账户 —— 无论是单边账户还是对冲账户 —— 让您可以在外汇、股票或场外交易市场中使用同一个 EA 交易系统。这使得整个过程更加灵活便捷。
然而,该 EA 交易系统尚未完成。在它能够实现其主要目标之前,还有很多工作要做。这是为了模拟交易服务器,用于回放/模拟。
不过,在我们这样做之前,我们需要构建一些其他组件。所以,千万不要错过下一篇文章 —— 我将在下一篇文章中开始解释一些已经可以完成的事情,即使这个 EA 交易系统还没有完成。我想现在就开始讨论,让事情变得更简单。因为如果我们稍后再讨论,解释可能会变得更加复杂。亲爱的读者,这可能会让你很难掌握所有相关的细节。
| 文件 | 描述 |
|---|---|
| Experts\Expert Advisor.mq5 | 演示 Chart Trade 和 EA 交易之间的交互(交互需要鼠标研究) |
| Indicators\Chart Trade.mq5 | 创建用于配置要发送的订单的窗口(需要 Mouse Study 才能进行交互) |
| Indicators\Market Replay.mq5 | 创建用于与回放/模拟器服务交互的控件(交互需要 Mouse Study) |
| Indicators\Mouse Study.mq5 | 实现图形控件和用户之间的交互(操作回放模拟器和实时市场交易所需) |
| Servicios\Market Replay.mq5 | 创建并维护市场回放和模拟服务(整个系统的主文件) |
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12598
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
让手动回测变得简单:为MQL5策略测试器构建自定义工具包
探索达瓦斯箱体突破策略中的高级机器学习技术
交易中的神经网络:具有预测编码的混合交易框架(StockFormer)