English Русский Español Deutsch 日本語 Português
preview
构建自动运行的 EA(第 02 部分):开始编码

构建自动运行的 EA(第 02 部分):开始编码

MetaTrader 5交易 | 10 三月 2023, 09:46
4 001 0
Daniel Jose
Daniel Jose

概述

在上一篇文章创建自动运行的 EA(第 01 部分):概念和结构中,我们讨论了任何人在继续创建自动交易的智能系统之前需要了解的第一步。 在那篇文章中,我展示了应该考虑哪些概念,以及应该创建什么结构。

但我们并没有讨论如何编写代码。 我的目的是提高读者的知识水平,尤其是那些在编程领域尚无实践知识的初学者。 因此,我尝试带领这些人更接近 MQL5 的世界,展示如何创建一个可以在全自动模式下工作的 EA。 如果您还没有阅读上一篇文章,我强烈建议您阅读它,因为在创建自动 EA 时,了解您的工作所需的关联环境非常重要。

好了,我们来继续编写代码。 我们将从基本系统开始,即订单系统。 在我们决定创建任何 EA 时,这将是其起点。


计划

MetaTrader 5 标准库提供了一种方便且便捷的方式来操控订单系统。 然而,在本文中,我想更进一步,向您展示 MetaTrader 5 交易库的幕后情况。 但我今天的目标并非阻止您使函数库。 相反,我想曝光这个黑匣子中存在的东西。 为此,我们将开发自己的函数库来发送订单。 它不会拥有 MetaTrader 5 标准库中存在的所有这些资源,它只包含创建和维护功能强大、稳健可靠的订单系统所需的功能。

在我们的开发中,我们不得不使用特定类型的结构。 我向那些刚接触编程的人道歉,因为您必须努力才能跟上解释。 我会尽力让一切尽可能简单,以便您可以遵循和理解思路和概念。 我所担心的是,如果不付出努力,您只会停留在欣赏我们将要构建的东西,但无法利用 MQL5 自行开发,现在,我们开始吧。


创建 C_Orders 类

为了创建一个类,我们首先需要创建一个包含类代码的文件。 在 MetaEditor 浏览器窗口中,导航到 “Include” 文件夹,并右键单击它。 选择 "新建文件",然后按照下图所示的说明进行操作:

图例 1

图例 01. 添加包含文件

图例 02

图例 02. 这就是所需文件如何创建的


您在完成图例 01 和 02 中的步骤后,就会创建一个文件,并在 MetaEditor 中打开。 其内容可以在下面找到。 在进一步讨论之前,我想快速解释一些事情。 请看图例 01 — 它所示您可以直接创建一个类。 您可能好奇为什么我们不这样做。 这是正确的问题。 原因在于,如果我们使用图例 01 中所示的方式创建一个类,那么该类就不是从头开始创建,而是已经包含一些预定义的信息或格式。 然而,在本系列文章中,从头开始创建类对于我们来说很重要。 我们回到代码。

#property copyright "Daniel Jose"
#property link      ""
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
// #define MacrosHello   "Hello, world!"
// #define MacrosYear    2010
//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+
// #import "user32.dll"
//   int      SendMessageA(int hWnd,int Msg,int wParam,int lParam);
// #import "my_expert.dll"
//   int      ExpertRecalculate(int wParam,int lParam);
// #import
//+------------------------------------------------------------------+
//| EX5 imports                                                      |
//+------------------------------------------------------------------+
// #import "stdlib.ex5"
//   string ErrorDescription(int error_code);
// #import
//+------------------------------------------------------------------+

浅灰色文本的所有行都是注释(作为示例代码),如果需要,可以将其删除。 我们的兴趣从这里开始。 一旦打开文件后,我们就可以在其内添加代码。 在此,我们将添加代码,这有助于我们操控我们的订单系统。

但为了令代码安全、可靠和健壮,我们将利用 MQL5 从 C++ 引入的工具:类。 如果您不知道什么是类,我建议您先找些看看。 您无需直接转到 C++ 文档中的相关类。 您在 MQL5 文档中就可阅读有关类的信息,这将为您提供一个良好的起点。 此外,比之 C++ 可能给从未听说过类的人造成的所有困惑,MQL5 文档的内容更容易理解。

一般来说,类是迄今为止将各部分的代码隔离的最安全、最有效的方法。 这意味着类不被视为代码,而是作为一种特殊的数据类型,比之基元类型,譬如整数型、双精度型、布尔值、等等,,您可以用类做更多的事情。 换言之,对于程序来说,类是一个多功能工具。 实际上不需要知道已创建类的有关内容,及其内含。 程序只需要知道如何使用它。 将类视为一个强力工具:您不需要知道它是如何构建的,或它有哪些组件。 所有您需要知道就是如何插入和使用它,这样的工作方式不会对您如何使用它产生任何影响。 这是类的简单定义。

现在,我们继续前进。 我们要做的第一件事是生成以下行:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
class C_Orders
{
        private :
        public  :
};
//+------------------------------------------------------------------+

它们是我们类中最基本的部分。 我们为类取名时,通常与包含它的文件名相同,但这不是必需的。然而,这可以帮助我们以后查找类。 在类内部,我指明了两个保留字,表示它们之间共享信息的级别。 如果您不加入这些字眼,则类的所有元素都将被视为公开元素,即任何人都可以读取、写入、或访问它们。 而这打破了我们使用类的一些初衷 — 安全性和健壮性 — 因为代码的任何部分都能够被访问,并更改类内部的内容。

简言之: 声明保留字 private 和 public 之间的所有内容只能在类中访问。 您可以在此处使用全局变量,它们将无法在类代码之外访问。 在保留字 public 之后声明的任何内容都可以在代码中的任何位置访问,无论它是否是类的一部分。 任何人都可以访问那里的任何内容。

一旦如上所示创建此文件后,我们就可以将其加到我们的 EA 之中。 然后我们的 EA 就拥有以下代码:

#property copyright "Daniel Jose"
#property version   "1.00"
#property link      "https://www.mql5.com/pt/articles/11223"
//+------------------------------------------------------------------+
#include <Generic Auto Trader\C_Orders.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+

在此步骤中,我们用 Include 预编译指令将我们的类包含在 EA 代码中。如此,当我们使用此指令时,编译器明白,从那一刻起,来自 Generic Auto Trader 文件夹的 include 目录中的头文件 C_Orders.mqh 必须包含在系统中,并进行编译。 这有一些技巧,但我不会深入地详细介绍,因为经验不足的人可能会在试图理解一些细节时完全迷失。 但正在发生的事情正是我刚才所描述的。


定义 C_Orders 类的第一个函数

与许多人的想法相反,程序员不会无休止地键入代码。 相反,在开始做某事之前,程序员必须仔细考虑和研究需要做什么。

因此,刚刚开始职业生涯的您也应该这样做:在添加任何代码行之前,您应该每时每刻全面考虑。 我们来思考一下,为了尽可能少地做工作,并充分利用 MetaTrader 5 平台,我们在 C_Orders 类中真正需要什么?

进入脑海的唯一念头就是如何发送订单。 我们并不真的需要某种方法来移动和删除订单或平仓,因为 MetaTrader 5 8平台为我们提供了所有这些可能性。 当我们在图表上下订单时,它会出现在 Toolbox 窗口的 “Trade” 选项卡当中,因此我们不需要对此做太多操作。

至少在第一阶段,我们并不真正需要的另一件事是在图表上显示订单或持仓的某种机制,因为平台为我们执行此操作。 故此,事实上,我们唯一需要实现的是直接从 EA 发送订单的某种方式,而无需经由 MetaTrader 5 中的订单系统。

基于这个思路,我们就可在这个早期阶段开始编写我们真正需要的程序,即允许 EA 发送订单的函数或过程。 在这一点上,许多人,尤其是那些在编程方面没有太多知识或经验的人很容易开始犯错误。

由于我们已经确定利用 MetaTrader 5 平台的工具箱管理订单和持仓,因此我们必须开发一个将订单发送到服务器的系统,这是最基本的部分。 但要做到这一点,我们需要了解 EA 所操控资产的一些信息,从而避免不断调用 MQL5 标准库函数查找信息。 我们从在类中定义一些全局变量开始。

class C_Orders
{
        private :
//+------------------------------------------------------------------+
                MqlTradeRequest m_TradeRequest;
                struct st00
                {
                        int     nDigits;
                        double  VolMinimal,
                                VolStep,
                                PointPerTick,
                                ValuePerPoint,
                                AdjustToTrade;
                        bool    PlotLast;
                }m_Infos;

这些变量存储我们所需的数据,并将在整个类中可见。 它们不能从类外部访问,因为它们是由 private 保留字声明的。 这意味着只能在类内部查看和访问它们。

现在我们需要按某种方式初始化这些变量。 有若干种方式可以做到这一点,不过据本人经验,我知道很多时候我最终忘记了这样做。 故此,我们要这样做,以便我们永远不会忘记这一点 — 我们将采用如下所示的类构造函数。 我们看下面的一个示例:

                C_Orders()
                        {
                                m_Infos.nDigits         = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
                                m_Infos.VolMinimal      = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                                m_Infos.VolStep         = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                                m_Infos.PointPerTick    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                                m_Infos.ValuePerPoint   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
                                m_Infos.AdjustToTrade   = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
                                m_Infos.PlotLast        = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);
                        };

类构造函数是在使用之前初始化类的安全方法。 请注意,这里我用的是默认构造函数,即它不接收任何参数。 在某些情况下,构造函数可能会接收参数,以便初始化类中的某些内容。 但在这里我们不需要这样做。

现在请注意,每个变量都需以某种方式初始化,如此这般当我们访问任何值时,无论是用于测试还是用于特定计算,它们都已被正确初始化。 出于此原因,在代码中执行任何其它操作之前,应始终优先使用类构造函数来初始化全局和内部类变量。

由于我们使用的是构造函数,因此还需要为类声明一个析构函数。 它可以像下面这样简单:

                ~C_Orders() { }

看看语法。 构造函数和析构函数名称与类的名称相同。 但在析构函数声明前面有一个波浪号(~)。 需要注意的另一件事是构造函数和析构函数都没有返回值。 尝试返回某种结果被视为错误,并且会令代码无法编译。

如果您需要在类初始化或终止期间返回值,则您必须调用常规过程。 构造函数和析构函数都不能用于此目的。

那么,这种类型的编码究竟如何,使用构造函数和析构函数,能否提供帮助? 如上所述,当您尝试访问或使用全局变量而不对其进行初始化时,这是很常见的。 而这很可能会导致异常结果和编程错误(译者按:溢出或内存泄露),即使对于经验丰富的程序员也是如此。 经由构造函数来初始化全局类变量,我们确保它们在需要时始终安全可用。

因此,不要养成坏习惯,即采用面向对象编程进行编码,但实际上未用到构造函数和析构函数。 否则,当您试图找出程序无法正常工作的原因时,可能会非常头疼。

在继续之前,请注意类构造函数中存在的一个细节。 它在下面突出显示:

        m_Infos.PlotLast = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);

与许多人的想法相反,市场之间存在差异,但并非如您想象的那样。 差异在于资产在图表上的绘制方式。 前面的代码行确定资产所用的直方图类型。

但为什么它如此重要? 如果我们想创建一个适用于不同市场或资产的 EA,我们必须记住它们可以有不同的表示系统。 基本上,有两种主要方法:

  • 在外汇市场可以看到的 BID(出价) 图形;
  • 我们在股票市场上常见的 LAST(最后成交价)图形。

尽管它们看起来相同,但 EA 和交易服务器将它们视为不同的绘图类型。 当 EA 开始发送订单时,这一点变得特别清晰和重要 — 不是以最佳价格执行的市价订单,而是挂单。 在这种情况下,创建一个能够发送此类订单的类非常重要,这些订单将保留在市场深度中。

但是为什么 EA 需要知道该用哪种图形表现系统? 原因很简单:当资产基于 BID(出价) 表示时,LAST(最后成交价)价格始终为零。 在这种情况下,EA 将无法将某些类型的订单发送到服务器,因为它不知道哪种订单类型是正确的。 即使它设法发送订单,并且服务器能接受它,订单也不会正确执行,因为订单类型填写不正确。

如果您为股票市场创建 EA,并尝试在外汇上使用它,就会出现此问题。 反之依然成真:如果系统使用 LAST 价格进行绘制,但 EA 是为外汇市场创建的,其中图表是根据 BID 价格绘制的,则 EA 可能无法在正确的时刻发送订单,因为 LAST 价格可以变化,而 BID 和 ASK 保持不变。

然而,在股票市场中,出价(BID)和要价(ASK)之间的差值将始终大于零,这意味着它们之间将始终存在点差。 问题在于,当创建要发送到服务器的订单时,EA 可能会错误地创建它,尤其是在它不遵循 BID 和 ASK 值的情况下。 这对于试图在现有点差内进行交易的 EA 来说可能是致命的。

出于这个原因,EA 检查它立身于哪个市场,或者更确切地说是绘图系统,以便它可以从外汇市场移植到股票市场或返回,而无需任何修改或重新编译。

我们已经看到了细微差别的重要性。 一个简单的细节可能会危及一切。 现在我们来看看如何将挂单发送到交易服务器。


向服务器发送挂单

在研究 MQL5 语言文档当之中与交易系统如何工作相关内容时,我发现了 OrderSend 函数。 您不必直接使用该函数,因为您可以选择使用 MetaTrader 5 标准库及其 CTrade 类。 不过,在此我想展示此功能如何在幕后工作。

为了向服务器发送操作请求,我们只需使用 OrderSend 函数,但在直接使用此函数时必须小心。 为了确保它仅在类中使用,我们将它放在私密部分当中。 以这种方式,我们可以在类的公开函数中分离出与填写请求相关的所有复杂性,可由 EA 访问这些函数。

重要的是要非常小心,并执行所需的测试,从而我们明白何时出错,以及何时走在正途。 由于所有请求都通过 OrderSend 函数,因此仅为它创建一个过程很方便,如下所示:

                ulong ToServer(void)
                        {
                                MqlTradeCheckResult TradeCheck;
                                MqlTradeResult      TradeResult;
                                bool bTmp;
                                
                                ResetLastError();
                                ZeroMemory(TradeCheck);
                                ZeroMemory(TradeResult);
                                bTmp = OrderCheck(m_TradeRequest, TradeCheck);
                                if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult);
                                if (_LastError != ERR_SUCCESS) MessageBox(StringFormat("Error Number: %d", GetLastError()), "Order System", MB_OK);
                
                                return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
                        }

面对上面的代码无需惊恐。 简而之,它保证请求已发送,并通知我们是否正确发送。 如果发送失败,它将立即通知我们。 现在我们仔细看看它是如何工作的。 首先,我们重置内部出错变量,以便您可在 EA 中的任何位置分析任何产生的错误。

不要忘记始终检查最关键过程的返回值。 一旦此操作完成,我们重置函数的内部结构,并发送请求检查订单注意: 与其检查是否返回失败,以便找出发生何种错误,不如检查错误变量的值。 如果它显示的值与预期不符,则订单将不会发送到服务器。 如果检查显示没有错误,则订单将被发送到交易服务器

细节决定一切:无论因谁产生,如果发生错误,其代码将显示在图表上。 如果一切完美,则不会显示该消息。 这种方式无所谓我们发送何种请求类型到服务器。 如果我们仅使用上面显示的过程,我们将始终具有相同类型的处理。 如果需要更多信息或测试,只需在上面的过程中加上它们就足够了,这样做您就会始终保证系统尽可能稳定可靠。

该函数返回两个可能的值:服务器在成功时返回订单单号,或零值指示出错。 若要找出错误类型,请检查 _LastError 变量的值。 在自动 EA 中,此消息或位于另一个位置,或只是日志消息,具体要取决于 EA 目的。 但由于本意是向您展示如何创建 EA,因此我添加了此信息,如此您就能知道消息的来源。

现在我们还有另一个问题要解决:您无法向服务器发送任何随机价格,因为订单可能会被拒绝。 您应该输入正确的价格。 有人说规范化值就足够了,但在大多数情况下,这不起作用。 事实上,我们需要经过一个小换算才能使用正确的价格。 我们在此处调用以下函数:

inline double AdjustPrice(const double value)
                        {
                                return MathRound(value / m_Infos.PointPerTick) * m_Infos.PointPerTick;
                        }

这个简单计算能调整价格,以便无论输入的数值如何,它都将被更正为足够的值,并被服务器接受。

要操心的事情少了一件。 然而,事实上我们创建上述的函数不会创建订单,或向服务器发送订单。 为此,我们必须填写 MqlTradeRequest 结构,我们已在前面的代码中见过它,并通过私有全局变量 m_TradeRequest 访问该结构。 此结构也应正确填充。

每次请求都应按特定格式填写。 但在这里我们只想发送一笔请求,如此这般服务器就能正确创建一笔挂单。 一旦此操作完成后,MetaTrader 5 平台将在图表和工具箱中显示挂单。

填充此结构是导致智能系统出问题的主要原因之一,无论手工还是自动模式都会如此。 因此,我们将用我们的类来创建抽象,并便于正确的填充,如此我们的请求就可被交易服务器接受。

但在创建函数之前,我们要多一点思考。 我们需要回答几个问题:

  • 我们要实现哪种类型的操作(买入或卖出)?
  • 我们希望以什么价格执行交易?
  • 交易量是多少?
  • 操作所需的时间要多久?
  • 我们将使用何种类型的订单:buy limit(限价买入)、buy stop(破位买入)、sell limit(限价卖出)、或 sell stop(破位卖出)(我们现在不谈论 sell stop limit(破位限价卖出)、或 buy stop limit(破位限价买入))?
  • 我们的交易针对哪个市场:外汇还是股票市场?
  • 止损如何设置?
  • 我们达成的目标止盈是什么?
  • 如您所见,有若干个问题,其中一些不能直接在服务器所需的结构中使用。 因此,我们将创建一个抽象来实现更实际的建模,如此我们的 EA 就可以毫无障碍地操作。 所有调整和校正由类完成。 因此,我们得到以下函数:

                    ulong CreateOrder(const ENUM_ORDER_TYPE type, double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                            {
                                            double  bid, ask, Desloc;                      
    					
                                    	Price = AdjustPrice(Price);
                                            bid = SymbolInfoDouble(_Symbol, (m_Infos.PlotLast ? SYMBOL_LAST : SYMBOL_BID));
                                            ask = (m_Infos.PlotLast ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK));
                                            ZeroMemory(m_TradeRequest);
                                            m_TradeRequest.action           = TRADE_ACTION_PENDING;
                                            m_TradeRequest.symbol           = _Symbol;
                                            m_TradeRequest.volume           = NormalizeDouble(m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1)), m_Infos.nDigits);
                                            m_TradeRequest.type             = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
                                                                                                        (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));
                                            m_TradeRequest.price            = NormalizeDouble(Price, m_Infos.nDigits);
                                            Desloc = FinanceToPoints(FinanceStop, Leverage);
                                            m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), m_Infos.nDigits);
                                            Desloc = FinanceToPoints(FinanceTake, Leverage);
                                            m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), m_Infos.nDigits);
                                            m_TradeRequest.type_time        = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
                                            m_TradeRequest.type_filling     = ORDER_FILLING_RETURN;
                                            m_TradeRequest.deviation        = 1000;
                                            m_TradeRequest.comment          = "Order Generated by Experts Advisor.";
                                    
                                            return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
                    };
    
    

    这个简单的函数做了一件很棒的事情。 它获取我们想要交易的数据,并将其转换为服务器期望接收的形式,无论我们交易的是外汇、还是股票。

    为了调用此功能,我们简单地告之我们想要买入或卖出、我们想要的价格、我们想要的止损、我们想要的止盈、我们想要的杠杆,以及交易是日内交易、还是长期交易。 该函数将计算其余部分,从而确保创建的订单无误。 但为此,我们需要进行一些计算并调整,从而获得恰当的数值。 当调用上述函数时,重要的是要记住两点,从而避免服务器创建订单时出现问题。

    由于这些问题,服务器经常拒绝订单。 譬如是止损和止盈。 如果我们在任何财务值中指示零值,则该函数将不会生成止盈和止损。 故此,如果策略需要,您必须小心指定零值,因为不会创建止损价位(您可以稍后执行此操作,但此刻不会添加它们)。 另一件事是,买入交易的负止损价位,和卖出交易的负止盈价位毫无意义。 该函数将忽略这些内容。 您只能用正数值操作,这样一切都会完美。

    查看此函数,您可能会感到困惑,认为它太复杂了。 但如果我们看到它是如何工作的,您就会改变主意。 但在了解其工作原理之前,我们先分析一下上述过程中提到的 FinanceToPoints 函数。 FinanceToPoints 函数的代码如下所示:

    inline double FinanceToPoints(const double Finance, const uint Leverage)
                            {
                                    double volume = m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1));
                                    
                                    return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
                            };
    
    

    我们试着理清这里发生了什么,因为它可能有点棘手。 如果您不了解该函数,您将很难理解当类向服务器发送订单,并且服务器下达止盈和止损订单时会发生什么,它与入场价格有一定的偏移。 因此,了解系统的工作原理非常重要,如此才可明白此函数。

    任何资产都拥有可将财务价值转换为点数的信息。 若要了解这些,请查看下图:

    图例 05  图例 06  图例 07


    以上所有图例高亮显示的所在都是用于将财务价值转换为点数。 对于外汇品种,不显示单据规模和价值,但它们的单据值为 1.0,单位值(点数)为 0.00001。 不要忘记这些数值。

    现在我们来研究以下内容:财务价值是交易成交量除以点数,再乘以每个点的价值的结果。 例如,如果我们假设正在交易某种资产,其最小交易量为 100,且订单大小为 2 倍,即最小交易量的两倍。 在这种情况下,交易量为 100 x 2,即 200。 目前务须担心这个数值,我们继续。 为了找出每个点的价值,我们可以进行以下计算:

    点值等于每点的价值除以点数。 很多人认为点数是 1,但这是一个错误。 您可从上图中看到,点数可以不同。 对于外汇,它是 0.00001。 因此,您应当注意点值。 确认程序捕获正确的点值,再用它。 例如,假设点数为 0.01,则每个点值为 0.01。 在这种情况下,将一个值除以另一个值将得到值 1。 但此值可能不同。 例如,在外汇中是 0.00001,而在 B3(巴西证券交易所)上交易的美元,该值为 10。

    现在,我们转移到另一件事。 为了更容易起见,我们假设用户想要交易的资产,其最小交易量为 100,该笔交易量为 x3。 单据规模为 0.01,值为 0.01。 然而,用户希望承担 250 的财务风险。 距入场价的偏移量应该是多少点才能匹配这个值 250? 这就是上述过程的作用。 它计算加hi,并进行调整,如此我们根据情况进行正或负偏移,从而令财务价值匹配 250。 在这种情况下,我们将有 2.5 或 250 的点数。

    这个例子看起来很简单。 但当波动性很高时,尝试在交易市场当中快速完成,并且您必须非常快速地决定使用哪种规模,以免超出可接受的风险限额,且您不会后悔开仓不足。 在此刻,您将明白拥有一个编程优良的 EA 伴您身边是多么重要。

    运用该类,EA 可以向服务器发送正确配置的订单。 不过,要看到它是如何完成的,我们来创建一种方式来测试我们的订单系统。


    测试订单系统

    我想强调的是,本系列文章的意图并非创建一个可以手动控制的 EA。 本意是向初学者展示程序员如何以尽可能少的工作量创建一个自动交易的 EA。 如果您想知道如何依据手工交易创建一款 EA,请阅读另一个系列“从头开始开发 EA”。 在该系列当中,我展示了如何依据手动交易创建 EA。 尽管,该系列可能有点过时,且很快您就会明白为什么。 至于目前,我们需要专注于我们的主要目标。

    为了测试 EA 的订单系统,并检查它是否能将订单发送到交易服务器,我们需用到一些技巧。 我们看看如何做到这一点。 通常,我会在图表中添加一条水平线,但在这种情况下,我们只想检查订单系统是否正常工作。 故此,为了令测试更容易,我们使用以下内容:

    #property copyright "Daniel Jose"
    #property description "This one is an automatic Expert Advisor"
    #property description "for demonstration. To understand how to"
    #property description "develop yours in order to use a particular"
    #property description "operational, see the articles where there"
    #property description "is an explanation of how to proceed."
    #property version   "1.03"
    #property link      "https://www.mql5.com/pt/articles/11223"
    //+------------------------------------------------------------------+
    #include <Generic Auto Trader\C_Orders.mqh>
    //+------------------------------------------------------------------+
    C_Orders *orders;
    ulong m_ticket;
    //+------------------------------------------------------------------+
    input int       user01   = 1;           //Lot increase
    input int       user02   = 100;         //Take Profit (financial)
    input int       user03   = 75;          //Stop Loss (financial)
    input bool      user04   = true;        //Day Trade ?
    input double    user05   = 84.00;       //Entry price...
    //+------------------------------------------------------------------+
    int OnInit()
    {
            orders = new C_Orders();
            
            return INIT_SUCCEEDED;
    }
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
    {
            delete orders;
    }
    //+------------------------------------------------------------------+
    void OnTick()
    {
    }
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
    {
    #define KEY_UP          38
    #define KEY_DOWN        40
    
            switch (id)
            {
                    case CHARTEVENT_KEYDOWN:
                            switch ((int)lparam)
                            {
                                    case KEY_UP:
                                            m_ticket = orders.CreateOrder(ORDER_TYPE_BUY, user05, user03, user02, user01, user04);
                                            break;
                                    case KEY_DOWN:
                                            m_ticket = orders.CreateOrder(ORDER_TYPE_SELL, user05, user03, user02, user01, user04);
                                            break;
                            }
                            break;
            }
    #undef KEY_DOWN
    #undef KEY_UP
    }
    //+------------------------------------------------------------------+
    
    

    尽管它很简单,但这段代码允许我们测试订单系统。 为此,请按照以下步骤按所需价格下订单:

    1. 指定下订单的价格。 请记住,您不能使用当前价格;
    2. 如果您认为价格会上涨,请用向上箭头,如果您认为价格会下跌,请用向下箭头;
    3. 然后转到工具箱窗口中的“交易”选项卡。 应当出现一笔订单,其中包含 EA 指定的条件。

    数据将按照 EA 与用户交互表单中的指示进行填充。 注意如何通过构造函数初始化类。 还要注意析构函数代码。 按这种方式,我们可以确保在调用其任何内部过程之前,该类均能始终被初始化。


    结束语

    虽然这个 EA 非常简单,但您能够测试订单系统,并用它发送订单。 然而,记住不要取用当前价格,因为您无法看到系统是否在正确的位置和正确的价值下订单。

    在下面的视频中,您可以看到系统正确操作的演示。 附件提供了我们涵盖本文中所述代码的完整版本。 用它来实验和研究。

    但我们仍处于最开始阶段。 在下一篇文章中,事情会变得更加有趣。 不过,在此阶段,重要的是要好好地了解在订单簿中如何下单。


    演示视频


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

附加的文件 |
构建自动运行的 EA(第 03 部分):新函数 构建自动运行的 EA(第 03 部分):新函数
今天,我们将看到如何创建一个在自动模式下简单安全地工作的智能系统。 在上一篇文章中,我们已启动开发一个在自动化 EA 中使用的订单系统。 然而,我们只创建了一个必要的函数。
构建自动运行的 EA(第 01 部分):概念和结构 构建自动运行的 EA(第 01 部分):概念和结构
今天,我们将看到如何创建一个在自动模式下简单安全地工作的智能系统。
构建自动运行的 EA(第 04 部分):手工触发器(I) 构建自动运行的 EA(第 04 部分):手工触发器(I)
今天,我们将看到如何创建一个在自动模式下简单安全地工作的智能系统。
DoEasy. 控件 (第 26 部分): 完成 ToolTip(工具提示)WinForms 对象,并转移至 ProgressBar(进度条)开发 DoEasy. 控件 (第 26 部分): 完成 ToolTip(工具提示)WinForms 对象,并转移至 ProgressBar(进度条)开发
在本文中,我将完成 ToolTip(工具提示)控件的开发,并启动 ProgressBar(进度条) WinForms 对象开发。 在处理对象时,我将针对控件及其组件开发动画处理的通用功能。