English Русский Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
从头开始开发智能交易系统(第 22 部分):新订单系统 (V)

从头开始开发智能交易系统(第 22 部分):新订单系统 (V)

MetaTrader 5交易 | 7 十月 2022, 12:19
1 876 0
Daniel Jose
Daniel Jose

概述

实现一个新系统并非那么容易,因为我们经常会遇到各种问题令过程复杂化。 当这些问题出现时,我们必须停下来重新分析我们前进的方向,决定我们是否能够保持现状,或者我们应该给它一个新面貌。

在创建一个系统时,这种决定可能会非常频繁,特别是如果我们没有清晰的最后期限或预算:当我们没有压力时,我们可以测试并调整任何事情,尽最大努力确保开发和改进方面的稳定性。

严谨的程序,特别是在特定预算或截止日期下开发的商业程序,通常包含许多需要稍后改正的错误。 经常是,开发系统的人员没有时间实现或了解那些可能对系统非常有益的解决方案。 这就是我们如何得到程序的,在许多情况下,程序员所创建的代码都有所不足。 有时公司会一个接一个地发布新版本,其中包含次要改进或错误修复。 这并不是因为在程序发布后检测到了漏洞,而是因为在发布之前程序员承受太大压力。

实际上,这些会发生在整个生产链之中。 然而,当我们在创建解决方案的过程中,我们正在寻找最佳的、最简单的方法。 甚至,我们有能力探索所有可能和可行的解决办法。 如有必要,我们可以停下来稍作回顾,从而修正和改进系统开发过程。 通常,稍作停顿并改变方向可以极大地帮助所需系统的开发。

在文章从头开始开发交易系统(第 21 部分)当中,该系统就几乎已经准备就绪。 它只是缺少负责在图表上直接移动订单的部分。 为了实现这个部分,我检查并注意到代码中的一些奇怪之处:其中有许多重复的部分。 尽管如此,我们还是非常小心地避免了许多不必要的重复。 更糟糕的是,有些东西并没有像它们应该做到的那样工作,例如,当应用于截然不同的的资产时。 当我们开始设计 EA 时,我就着手在发布于此的文章里记录它了,我曾考虑将其主要用于 B3 交易所的期货交易。 事实上,我最终开发出来的产品是用来交易美元期货或被已知的 WDO 和 WIN 指数。 但随着这个系统越来越接近我最初期望的系统,我意识到它可以扩展到其它市场或资产。 这就是问题所在。


1.0. 返回剪贴板

为了理解这个问题,请注意许多人在开发智能系统时可能忽略的一个重要细节。 交易系统中的合约是如何定义的? MetaTrader 5 提供此信息,而 MQL5 能够访问它。 故此,我们需要理解数据,从而创建数学算法来概括计算,并获得最完整的系统。

我们正在开发的订单系统旨在直接从图表上进行交易,而无需任何其它外部资源、或未来可能创建的任何东西。 此项开发尝试创建一个与平台中所用的交易系统非常相似的交易系统,但完全开源,如此您便可以自行定制和添加所需信息。

这样做的目的是让用户只需瞟一眼位置就可以知道发生了什么。 虽然这个思路似乎很好,但对于那些尝试针对外汇和股票市场(如 B3)创建交易系统的人来说,这样做并不能令他们的生活变得轻松。 现有的信息水平足够为一个或多个市场定制订单,但要想创建通用的东西已成为一个问题,几乎是对个人的侮辱。 然而,我决定直面这个问题,并尝试创建一个通用系统。

虽然我不确定我真的能做到这一点,但我至少可以向您展示如何找到所需的信息;因此如果您需要为本地系统定制 EA,您将拥有如何实现这个目标的经验和知识。 即使系统最初无法处理建模,您也可以根据需要进行调整。

一项观察:可以利用 MQL5 代码获得数据,但为了简化操作,我将用 MetaTrader 5 来显示数据的所在。


1.0.1. 在 B3 上交易公司资产(股份)

请看以下两张图片:

     

高亮显示部分展示的数据,就是我们计算下单位置所需的。

如果您根据财经价值创建持仓,您会需要这些数值来计算止损和止盈价位。 如果您不用 OCO 订单,事情会更简单,但在此我们假设所有订单都是 OCO 订单或持仓。

重要:如果针对某项资产执行零星股票(不足一手)交易(B3 市场之间存在差异),最小交易量为指定值的 1%,这就是为什么我们将以 1:1 的比例进行交易,而不是以 100:100 的比例交易。 计算结果会有所不同,所以不要忘记这一点。


1.0.2. B3 中的期货合约

期货合约的规则不同于股票交易规则,成交量的变化取决于您交易的是完整合约,亦或是迷你合约,甚至有可能从一种资产转移到另一种资产。 例如,若要交易 BOI,我们应该看看杠杆(乘数)是如何填写的,因为这就是我们在这里要做的。 我将专注于合约。 在某些情况下,一份完整合约等于 25 份迷你合约,但这可能会有所不同,所以您应该始终检查交易量的规则。

现在,我们看看以下迷你美元的图片:

     

这些合约都有到期日,但对本文来说并不重要,因为在文章从头开始开发智能交易系统(第 11 部分)之中,我们研究了如何建立交叉订单系统,基于您的历史交易交易期货合约,如果您采用这种方法,您就不必担心当前交易的是哪一份合约,因为 EA 会为您做好这件事。 注意带有标记的重点;它们不同于股份系统。 有时这也许会导致出问题,但 EA 已经在处理这个问题,尽管这部分不是很直观。 无论如何,随着时间的推移,您就会习惯在杠杆交易中采用正确的价值,并按特定财务交易量来进行交易。

然而,我决定设定一个更高的目标。 这就是困难开始的地方。


1.0.3. 外汇

我遇到的问题与外汇系统有关,且我的 EA 尚不能解决它。 当我设法处理外汇杠杆时,我发现 EA 中相同的代码对 B3 无效了。 这令我极其困扰,因为我不明白为什么应该分成两个 EA,而它们之间只在这一点上不同。 为了了解发生了什么,请看以下图片:

     

在 B3 中,我们有四个数值,而在外汇中只有两个数值。 两个市场之间的计算不并不匹配,因为缺了两个数值。

由此,EA 所执行的计算令人很难理解它实际执行的是什么操作:有时计算出的数值太高,有时乘数导致交易系统拒绝订单,因为交易量与预期的最小交易量相比太小,有时 OCO 限制点由于持仓错误而不被接受。 因之,有无休止的烦恼。

觉悟到这点之后,我决定修改 EA。 故此,我们不会再采用以前的方法计算该值,绝不,我们要采用不同的方法。 事实上,无论是在股票市场还是在外汇市场,该值现在都要根据我们所选的资产进行调整。 因此,EA 将适配数据,并正确执行计算。 然而,我们需要知道我们交易的是什么,以便正确设置乘数。 尽管现在,出于计算原因,我们允许在交易量中使用浮点值,但您应该避免这种情况。 事实上,您应该使用整数来指示正在采用的杠杆。

如此,我们不会告诉 EA 我们想交易多少。 相反,我们指示一个乘数,能够应用在最小允许交易量。 这就是我如何解决问题的。 您可以自行测试一下,看看它是如何工作的。

为了便于理解和减少工作量,在下一个主题中,我会提供一些该结果的简要表述。


2.0. 数据可视化

首先,我们研究股票市场的情况,特别是巴西股票市场(B3)。 这就是图例 5 中 EA 的数据呈现方式。


2.0.1. B3 资产

如果公司股票在巴西证券交易所上市,最低交易量为 100 股。 我们为 EA 设置的最小允许交易量指定为 1。 以这种方式,处理交易量就容易得多了 — 无需确切知道最小手数,我们只需采用乘数,EA 则进行必要的计算,来创建正确的订单。

如果您采用零星数值,则只需指示所采用的零星股数,也就是,50 意味您采用 50 零星股数,如果指定 15,则为 15 零星股,依此类推。

结果显示在下面的 MetaTrader 5 工具箱窗口中。 它展示了如何创建含有最小手数值的订单。 如果您分析止盈和止损值,您将看到它们与图表上指示的数值相匹配,这意味着 EA 在此处有效。


2.0.2. 迷你美元

此处的交易量是 1:1,但在完整合约的情况下,值是不同的。 看看 Chart Trade:显示的止盈和止损值与上述值相同。 但 EA 能正确调整这些值。 如此,可考虑从图表指标获取该值,Chart Trade 的数值应被忽略,但它们应接近图表上指示的数值。


工具箱中显示了以下数据:

就像在以前的资产中一样,如果我们运行计算来检查止损和止盈价位,我们将看到它们与 EA 指定的相匹配,也就是说,EA 也经历了这个阶段,而无需重新编译代码,只需更改资产即可。


2.0.3. 外汇

这部分稍有些复杂。 对于那些不熟悉外汇的人来说,这些要点相当奇怪,但 EA 仍然设法把它们理清了。


MetaTrader 5 将按以下方式通知我们这一笔未决交易:


请记住,外汇的杠杆级别与上述不同。 但如果您执行计算,您将看到 EA 设法提供了正确的点数,并且所创建的订单很完美。

所有这些都是在没有任何额外变化的情况下完成的,除了我将在实现部分中展示的那些变化之外,尽管除此之外还进行了许多其它修改,如此这般 EA 就真正能适用于股票市场和外汇市场均可,而无需重新编译。 在此,我们现在就补全了前面的文章。 我们将看看如何在图表上移动止损和止盈价位。


3.0. 实现

我们从代码中的一些修改开始。

首先,我删除了 3 或 4 个版本之前实现的限制系统。 这是因为 EA 有时会错误地调整股票市场和外汇之间的手数计算。

我已添加了一个新的计算模型,以便令 EA 在外汇和股票市场上同样有效。 在之前的版本里,这是不可能的。 起初,EA 专注于股票市场,但我决定将其功能扩展至外汇,因为交易方法没有太大差异。

有一些关于手数计算问题的详细信息,EA 在以前的版本中无法处理,但随着所做的修改,现在它在外汇和股票市场中均可使用,且无需任何重大代码更改。 然而,为了保持与外汇和股票市场的兼容性,我不得不进行多次修改。

其中一次修改出现在代码的最开始:

int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        Mouse.Init(user50, user51, user52);
        if (memSzUser01 != user01)
        {
                Chart.ClearTemplateChart();
                Chart.AddThese(memSzUser01 = user01);
        }
        Chart.InitilizeChartTrade(user20 * Terminal.GetVolumeMinimal(), user21, user22, user23);
        VolumeAtPrice.Init(user32, user33, user30, user31);
        TimesAndTrade.Init(user41);
        TradeView.Initilize();
                
        OnTrade();
        EventSetTimer(1);
   
        return INIT_SUCCEEDED;
}

高亮显示的部分以前不存在。 还有许多其它变化。 然而,我们不会关注这一实现。 取而代之,我们将看到如何利用 EA 在图表上直接移动止盈和止损价位,而无需借助任何其它技巧。 那么,我们就进入这一部分。


3.0.1 在图表上直接移动订单

这一部分在以前的版本中非常艰难,因为随着时间的推移,它们有许多未解决的问题,这令任务变得困难。 其中一个原因是代码非常贫乏,这使得很难直接在图表上有效地实现订单移动系统。 最初,系统并未推测要这样做。

为了让您了解 EA 所需更改的范围(从而能够处理订单移动,包括挂单和持仓,以便您可以用鼠标控制图表上的限价订单移动),我们先看看 EA 以前的样子。

问题在于,当创建对象时,它们替换了 C_HLineTrade 类。 这是在 从头开始开发交易 EA(第 20 部分)一文中完成的。 现在的系统结构要复杂得多,既如此,为了不再显示上面的全貌,我们只看发生了什么。

箭头指向的连接点,其中删除了 C_HLineTrade 类,从而为新类留出空间。 而这比我们在前几篇文章中所做的实现更多。 但是 C_OrderView 类的存在干扰了开发,最终不得不将其排除在外。 但这还不是全部。 C_TradeGraphics 类与旧的 C_OrderView 类合并,故出现了名为 C_IndicatorTradeView 的新类。 因此,这个类替换了两个旧类,这样我们就能够开发订单移动系统了。

我在这里要介绍的是这个系统的第一个版本。 目前正在开发另一个版本,但会在另一篇文章中介绍。


3.0.1.1 - 编写新系统的代码


合并后,新系统具有以下配置:


绿色区域表示一组自由的类,也就是说,它们将由 MetaTrader 5 管理,而不是由 EA 管理。 但它是如何做到的呢? 我们来详细研究一下这个过程。 事实上,EA 将只创建、放置和删除这些类,以及由这些类创建的所有对象。 查看 EA 代码内部,您找不到任何引用绿色区域中所创建对象的结构或变量。 这允许创建无限数量的对象,只要 MetaTrader 5 能够在操作系统中分配内存。 对象的数量不受 EA 内的结构或变量的限制。

您可能认为只有疯子才会创造出这样的结构。 好吧,您可以说我疯了,因为确实是我创造了它,而且它很管用。 甚至,令人惊讶的是,它并没有令系统过载太多。 大家都说我疯了,而这没什么大不了的... 我们更进一步。 在移动挂单或限价订单时,您可能会注意到有一定的延迟。 这并非因为代码故障或您的计算机或 MetaTrader 5 的问题,问题在于系统将经由交易服务器来移动挂单或限价来移动它们,而终端和服务器之间存在响应延迟。 但在某些情况下,这是最好且更安全的操作方式,尽管这不允许我们做些我希望 EA 去做的其它事情。 因此,在下一篇文章中,我们将在 EA 中添加新功能来解决这个问题,我们还将令系统更加流畅,但这无需修改上面的结构。 我们只会正确地操作代码,虽然这会降低系统的安全性,但在那之前,谁知道呢,我可能会找到解决这个问题的好办法。

我们看看当前新代码中的一些要点。 我们从一个曾稍微研究过的函数开始,它的代码如下:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        ulong ticket;
        
        if (trans.symbol == Terminal.GetSymbol()) switch (trans.type)
        {
                case TRADE_TRANSACTION_DEAL_ADD:
                case TRADE_TRANSACTION_ORDER_ADD:
                        ticket = trans.order;
                        ticket = (ticket == 0 ? trans.position : ticket);
                        TradeView.IndicatorInfosAdd(ticket);
                        TradeView.UpdateInfosIndicators(0, ticket, trans.price, trans.price_tp, trans.price_sl, trans.volume, (trans.position > 0 ? trans.deal_type == DEAL_TYPE_BUY : def_IsBuy(trans.order_type)));
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                                if (trans.order != trans.position) TradeView.RemoveIndicator(trans.order);
                                else
                                        TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                                if (!PositionSelectByTicket(trans.position))
                                        TradeView.RemoveIndicator(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        TradeView.UpdateInfosIndicators(0, trans.order, trans.price, trans.price_tp, trans.price_sl, trans.volume, def_IsBuy(trans.order_type));
                        break;
                case TRADE_TRANSACTION_POSITION:
                        TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                        break;
        }
        
        
#undef def_IsBuy
}

这段代码非常有趣,因为它令我们不必检查出现或正在修改的每笔新持仓。 实际上,服务器本身会通知我们发生了什么,所以我们只需要确保 EA 竜正确响应事件。 好好研究这段编码,以及调用 OnTradeTransaction 事件的方式,因为如果我还沿用以前版本中的依据模型进行分析的方式,我们在检查上就会花费大量时间。 在这种情况下,服务器替我们做了所有的艰苦工作,我们可以肯定地知道,图表上显示的值确实是服务器当前看到的内容。

在我们了解以上代码的重点之前,我们看一看另一个代码片段。

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Mouse.DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        TradeView.DispatchMessage(id, lparam, dparam, sparam);
        ChartRedraw();
}

如此,所有一切都在一个地方完成。 我们可以进入类之内,看看里面发生了什么。


3.1. C_IndicatorTradeView 类

该类用于表现和操作数据。 如前所述,它基本上包括旧的 C_OrderView 和 C_TradeGraphics 类。 但它以完全不同的方式处理数据。 我们看看该类的一些要点。

我们将从初始化函数开始,其代码如下:

void Initilize(void)
{
        int orders = OrdersTotal();
        ulong ticket;
        bool isBuy;
        long info;
        double tp, sl;

        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_OBJECT_DESCR, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_DRAG_TRADE_LEVELS, false);
        for (int c0 = 0; c0 <= orders; c0++) if ((ticket = OrderGetTicket(c0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                info = OrderGetInteger(ORDER_TYPE);
                isBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY));
                IndicatorInfosAdd(ticket);
                UpdateInfosIndicators(-1, ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), OrderGetDouble(ORDER_SL), OrderGetDouble(ORDER_VOLUME_CURRENT), isBuy);
        }
        orders = PositionsTotal();
        for (int c0 = 0; c0 <= orders; c0++) if (PositionGetSymbol(c0) == Terminal.GetSymbol())
        {
                tp = PositionGetDouble(POSITION_TP);
                sl = PositionGetDouble(POSITION_SL);
                ticket = PositionGetInteger(POSITION_TICKET);
                IndicatorInfosAdd(ticket);
                UpdateInfosIndicators(1, ticket, PositionGetDouble(POSITION_PRICE_OPEN), tp, sl, PositionGetDouble(POSITION_VOLUME), PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
        }
        CreateIndicatorTrade(def_IndicatorTicket0, IT_PENDING);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP);
}

基本上,我们正在做的是创建所需的指标,并显示账户中当前存在的任何内容,例如持仓或挂单。 但高亮显示的行在此处很重要,因为如果您没有使用交叉订单系统,您将在图表上显示订单指示点(来自 MetaTrader 5),如果您单击并拖动这些指示点,EA 将根据指标的变化更新新的指示点。 这并没有太多阻碍,但我们必须实际使用我们正在开发的系统,否则开发它的目的是什么?

接下来,请注意以下代码:

void UpdateInfosIndicators(char test, ulong ticket, double pr, double tp, double sl, double vol, bool isBuy)
{
        bool isPending;
                                
        isPending = (test > 0 ? false : (test < 0 ? true : (ticket == def_IndicatorTicket0 ? true : OrderSelect(ticket))));
        PositionAxlePrice(ticket, (isPending ? IT_RESULT : IT_PENDING), 0);
        PositionAxlePrice(ticket, (isPending ? IT_PENDING : IT_RESULT), pr);
        SetTextValue(ticket, (isPending ? IT_PENDING : IT_RESULT), vol);
        PositionAxlePrice(ticket, IT_TAKE, tp);
        PositionAxlePrice(ticket, IT_STOP, sl);
        SetTextValue(ticket, IT_TAKE, vol, (isBuy ? tp - pr : pr - tp));
        SetTextValue(ticket, IT_STOP, vol, (isBuy ? sl - pr : pr - sl));
}

它接收并更新数据,根据财会值和订单所在点显示正确的数值。 基本上,我们不用担心是否有挂单或持仓 — 函数将对其进行定位,以便我们可以正确地在图表上看到它。

这是下一个函数。

inline double SecureChannelPosition(void)
{
        double Res = 0, sl, profit, bid, ask;
        ulong ticket;
                                
        bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
        ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ticket = PositionGetInteger(POSITION_TICKET);
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (ask < sl) ClosePosition(ticket);
                }else
                {
                        if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                }
                Res += profit;
        }
        return Res;
};

它由 OnTick 事件调用,因此在速度和系统负载方面非常关键。 除了检查之外,它唯一要做的就是更新图表上的数值,这是通过高亮显示的代码实现的。 请注意,仓位票据在这里非常重要。

我们仔细看看上面高亮显示的函数。

void SetTextValue(ulong ticket, eIndicatorTrade it, double value0, double value1 = 0.0, double priceOpen = 0.0)
{
        double finance;
                                
        switch (it)
        {
                case IT_RESULT  :
                        PositionAxlePrice(ticket, it, priceOpen);
                        PositionAxlePrice(ticket, IT_PENDING, 0);
                        m_EditInfo2.SetTextValue(MountName(ticket, it, EV_PROFIT), value1);
                case IT_PENDING:
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0 / Terminal.GetVolumeMinimal(), def_ColorVolumeEdit);
                        break;
                case IT_TAKE    :
                case IT_STOP    :
                        finance = (value1 / Terminal.GetAdjustToTrade()) * value0;
                        m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance);
                        break;
        }
}

这就是数值如何正确显示的方式。 但问题是如何移动它们? 这由其它三段代码完成。 当然,您可以避免它们,并利用 MetaTrader 5 系统本身,它比当前的 EA 系统快得多。 但是,正如我所说的,我更喜欢使用 EA,因为它很快就会得到其它改进。

下面可以看到负责移动的第一个函数,但它只是显示移动点所需的片段,无论是限价还是订单本身,由于整个代码要宽泛得多,且不需要理解如何使用鼠标移动。

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;

// ... Code ....

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click 
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT press 
                        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL press 
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        Mouse.Hide();
                                        bIsDT = Chart.GetBaseFinance(leverange, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / leverange);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / leverange);
                                        m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE));
                                        bMounting = true;
                                }
                                tp = price + (bKeyBuy ? valueTp : (-valueTp));
                                sl = price + (bKeyBuy ? (-valueSl) : valueSl);
                                UpdateInfosIndicators(0, def_IndicatorTicket0, price, tp, sl, leverange, bKeyBuy);
                                if ((bEClick) && (memLocal == 0)) CreateOrderPendent(leverange, bKeyBuy, memLocal = price, tp, sl, bIsDT);
                        }else if (bMounting)
                        {
                                UpdateInfosIndicators(0, def_IndicatorTicket0, 0, 0, 0, 0, false);
                                Mouse.Show();
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell))
                        {
                                if (bEClick)
                                {
                                        bIsMove = false;
                                        m_TradeLine.SpotLight();
                                }
                                MoveSelection(price, mKeys);
                        }
                        break;

// ... Code ...
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev)
                        {

// ... Code ...

                                case EV_MOVE:
                                        if (bIsMove)
                                        {
                                                m_TradeLine.SpotLight();
                                                bIsMove = false;
                                        }else
                                        {
                                                m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE));
                                                bIsMove = true;
                                        }
                                        break;
                        }
                        break;
        }
}

我们试着来理解发生了什么。 文章末尾有一段视频,展示了如何做到这一点,以及实际将会发生什么。 但首先我们来试着弄清楚。

每个指示都有一个允许其选择的对象(无法移动的结果除外)。 在其上点击一次将改变其指示线 — 它将变得更宽。 当发生这种情况时,会捕获鼠标移动,并将其转换为该对象的新位置,直到我们在所选对象以外再次单击,这样就能够移动对象了。 看仔细,不必一直按住鼠标按钮,只需单击一次,拖动,然后再次单击。

但实际上,只有一部分操作是在这里完成的。 还有两个其它函数可以帮助我们。 上面已经看到一个,它负责显示计算出的数值;下一个负责订单或限价移动系统运作时,令 EA 看起来像一块用力抛出的石头:如下所示:

void MoveSelection(double price, uint keys)
{
        static string memStr = NULL;
        static ulong ticket = 0;
        static eIndicatorTrade it;
        eEventType ev;
        double tp, sl, pr;
        bool isPending;
                                
        string sz0 = m_TradeLine.GetObjectSelected();
        
        if (sz0 != NULL)
        {
                if (memStr != sz0) GetIndicatorInfos(memStr = sz0, ticket, pr, it, ev);
                isPending = OrderSelect(ticket);
                switch (it)
                {
                        case IT_TAKE:
                                if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), price, macroGetPrice(IT_STOP));
                                else ModifyPosition(ticket, price, macroGetPrice(IT_STOP));
                                break;
                        case IT_STOP:
                                if (isPending) ModifyOrderPendent(ticket, macroGetPrice(IT_PENDING), macroGetPrice(IT_TAKE), price);
                                else ModifyPosition(ticket, macroGetPrice(IT_TAKE), price);
                                break;
                        case IT_PENDING:
                                pr = macroGetPrice(IT_PENDING);
                                tp = macroGetPrice(IT_TAKE);
                                sl = macroGetPrice(IT_STOP);
                                ModifyOrderPendent(ticket, price, (tp == 0 ? 0 : price + tp - pr), (sl == 0 ? 0 : price + sl - pr));
                                break;
                }
        };
}

我将其称为石头函数,因为它负责令仓位系统变慢。 如果您不理解,请查看高亮显示的要点。 它们中的每一个都是 C_Router 类中的一个函数,它将向交易服务器发送一个请求,因此,如果服务器出于某种原因需要一段时间来响应(这总是因为延迟而发生),仓位系统将或多或少变慢一些,但如果服务器响应迅速,系统将是顺滑的,或者说,事情会更顺利。 稍后我们将对此进行修改,因为该系统不允许我们执行其它操作。 无论如何,请您务必记住,这样您将以一种更安全的方式进行操作,尤其是那些喜欢在高度波动的市场中进行操作的人,那里的价格可能会波动迅速,但即使如此,您也会冒着限价跳升的风险。 没有办法,有些东西必须牺牲。 对于那些认同操作的人来说,知道这正是服务器内部的内容,EA 现在已经准备好了。 但是,对于那些想在 EA 的功能中获得流动性的人来说,即使以不精准为代价;在接下来的文章中事情会发生变化,变得更加有趣。

下面的视频显示了系统的实际工作方式,因此请注意​在图表和工具箱中的这些数值​。


结束语

虽然我们现在有一个非常有趣的智能交易系统,但我建议您在演示帐户上先使用一段时间,以便熟悉它的工作原理。 我保证,它的工作方式不会有更大的变化。 只会有一些改进,在下一篇文章中,我们将添加此 EA 所缺少的一些东西,但会牺牲它所提供的一些安全性。 无论如何,这是学习交易系统如何工作,以及如何操作平台,从而获得我们需要的任何类型的数据建模的一个很好的来源。

不要忘记,如果您发现移动订单或限价太慢,可以删除我在文章中示意的要点,利用 MetaTrader 5 本身来移动订单或限价,并用 EA 作为支持来帮助解释数据。 您是否会如此做是您自己的选择...


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

附加的文件 |
您应该知道的 MQL5 向导技术(第 02 部分):Kohonen 映射 您应该知道的 MQL5 向导技术(第 02 部分):Kohonen 映射
这些系列文章所提议的是,MQL5 向导应作为交易员的支柱。 为什么呢? 因为交易员不仅可以利用 MQL5 向导装配他的新想法来节省时间,还可以大大减少重复编码带来的错误;他最终可把精力投向自我交易哲学中的几个关键领域。
学习如何基于 Williams PR 设计交易系统 学习如何基于 Williams PR 设计交易系统
本系列中的一篇新文章,介绍了如何依据 MQL5 最流行的技术指标为 MetaTrader 5 设计交易系统。 在本文中,我们将学习如何依据 Williams‘ %R 指标设计交易系统。
价格走势模型及其主要规定(第 1 部分):概率价格域演化方程与发生的可观测随机游走 价格走势模型及其主要规定(第 1 部分):概率价格域演化方程与发生的可观测随机游走
本文研究的是概率价格域演化方程,与即将到来的价格尖峰准则。 它还揭示了图表上价格数值的本质,以及这些数值随机游走的发生机制。
CCI 指标。 升级和新特征 CCI 指标。 升级和新特征
在本文中,我将研究升级 CCI 指标的可能性。 此外,我将对指标进行修改。