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

从头开始开发智能交易系统(第 26 部分):面向未来(I)

MetaTrader 5示例 | 16 十一月 2022, 08:55
1 622 0
Daniel Jose
Daniel Jose

概述

尽管在“从头开始开发交易系统”系列的第 24 部分第 25 部分的篇幅中展示了代码修复和改进,其中我们已经看到了如何提高系统健壮性,但仍有一些细节留待改进。 但并非因为它们的重要性较低,事实上它们真的很重要。

我们现有的一些问题,是与在交易日里我们想要如何工作,以及我们做什么事情相关。 许多交易者简单地在某个价位处下单,且不会再从该点位挪动。 无论发生什么,他们都会认定这是一个完美的入场点,故不会挪动订单。 他们也许会平移破位级别,甚至删除破位级别,但他们不会改变入场点。

因此,代码中的剩余缺陷不会影响交易者的实际操作方式。 他们甚至可能意识到订单系统含有缺陷(例如,我们将在本文中修复的那些)。 但那些喜欢追逐价格,无论如何都要试图完成一笔交易,但又不想入场的人,会见证系统中的许多错误。 他们中的一些人可能会干预,并令业务不安全(委婉地说),而另一些人却在赚钱;徒留这些交易者在市场面前茫然无助。


2.0. 实现

为了开始本文的旅程,我们先修复 EA 中一个令真金白银爆仓的缺陷。 再者,如果您不会随时更改入场点,则此问题不会影响您。 不过,我建议考虑更新代码,以防万一。 即使修复已经在文后附带的代码中实现,您也许想到这会损害 EA,因为它会损失一些性能,这是事实。 然而,哪个更好:是损失一些性能,亦或冒着风险盲目入场而赔钱?


2.0.1. 入场点错误

该错误是我们第一时间要修复的,尽管它们都需要以某种方式进行修复。 然而,这一个是迄今为止最灾难性的。 当我们放置一笔挂单入场时,它就会发生,比如说破位买入(BUY STOP),并移动入场点,如此,该订单现在应该是限价买入(BUY LIMIT)类型。 似乎这里没问题,但若失败则是相当灾难性的,因为当前开发阶段的 EA 将无法以正确的方式进行更改。 事实上,许多 EA 都希望执行这种修改,且如果发生了,您会在图表上看到某些信息,但服务器上是不同的信息。 只在开仓时,系统才会正确更新,在此之前,EA 在图表上显示的内容与服务器上的数据或将是不一致的。

在某些情况下,我们能容忍这种不一致,而在其它情况下,问题将是一场彻底的灾难。 为了理解这一点,请仔细阅读本文。

为了消除此错误,我们有一个解决方案,即在应用之前也许要遍历不同的路径。 但操作原理始终如一:从订单簿中删除订单,将其移动到新位置,更改订单类型,并将其放回到订单簿。 应该就是这样做的,但如何做到就要取决于具体的实现。

因此,我们就要实现最基本的解决方案,但由于它不太理想,我们不得不处置一些问题。

解决方案是修改以下函数,添加高亮显示的代码行。

void SetPriceSelection(double price)
{
        char Pending;
                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

尽管此方案部分解决了问题,但并未彻底解决它。 例如,对于破位买入(BUY STOP)破位卖出(SELL STOP)订单,可以添加这些简单的线条来解决问题。 但对于限价买入(BUY LIMIT)破位限价(STOP LIMIT),一旦我们点击更改入场点,服务器就会立即填单。 更糟糕的则是,我们这笔持仓入场即亏。 如果订单配置为空订单(带有止盈或止损价位),且止损点超出限价,那么除了服务器将立即执行订单之外,它还会立即平仓,而这意味着我们的交易账户彻底遭殃。 这就是为什么交易系统如此难以开发的原因。 我们在模拟账户上进行了若干测试,如果一切看着还正常,我们就会转到实盘账户,而从此我们就开始亏损,且不晓得到底发生了什么。

我再重复一遍:若入场点只放置一次,且永不更改时,该错误不会有任何影响。 只当交易者移动点位时,才会出现问题。

实际上,破位(STOP)订单执行良好。 现在我们需要解决限价(LIMIT)挂单的问题。 尽管这个问题看起来很容易解决,但有一件事需要理解:没有完美的解决方案,对于系统开发人员来说工作优异的解决方案,也许并不适合您

我将在此展示解决此问题的可能解决方案之一。 该解决方案将在如上所示的相同函数中实现。 这是它的新代码:

void SetPriceSelection(double price)
{
        char Pending;
        double last;
        long orderType;
                                
        if (m_Selection.ticket == 0) return;
        Mouse.Show();
        if (m_Selection.ticket == def_IndicatorTicket0)
        {
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                RemoveIndicator(def_IndicatorTicket0);
                return;
        }
        if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
        m_TradeLine.SpotLight();
        switch (m_Selection.it)
        {
                case IT_TAKE:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                        else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                        break;
                case IT_STOP:
                        if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                        else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                        break;
                case IT_PENDING:
                        orderType = OrderGetInteger(ORDER_TYPE);
                        if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                        {
                                last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
                                {
                                        RemoveOrderPendent(m_Selection.ticket);
                                        RemoveIndicator(m_Selection.ticket);
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                                        break;
                                }
                        }
                        if (!ModifyOrderPendent(m_Selection.ticket, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr)))
                        {
                                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                                m_TradeLine.SpotLight();
                        }
                        break;
        }
        RemoveIndicator(def_IndicatorGhost);
}

该操作如下。 当我们要更改挂单的入场点时,我们会检查订单簿(市场深度)中的订单是破位限价(STOP LIMIT)、还是限价买入(BUY LIMIT)类型。 如果都不是,则执行流程将跳到到代码中的另一个点。 如果是,那么我们会立即捕获当前资产价格,且我们将采用以下准则:对于买入订单,捕获当前要价(ASK)值。 分别地,卖出订单则是出价(BID)值。 这替换了采用 LAST 值的旧方法,但由于在某些市场中未用到该值,故我们不会将其用作参考。 然后检查订单簿中的订单是否已变成无效或仅被修改。

如果订单仍然有效,系统将忽略验证码,并转到订单将被更改的部分。 但如果市场深度中的订单已无效,系统将执行以下代码:

RemoveOrderPendent(m_Selection.ticket);
RemoveIndicator(m_Selection.ticket);
CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
break;

但上面的代码只会将限价卖出(SELL LIMIT)和限价买入(BUY LIMIT)订单分别更改为破位卖出(SELL STOP)和破位买入(BUY STOP)。 如果我们想将这些类型恢复到原本的类型,或只是防止这种更改该怎么办?

如果我们不希望系统更改已执行订单的类型,我们只需要将高亮显示的片段替换为以下代码:

if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
{
        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last)))
        {
                RemoveOrderPendent(m_Selection.ticket);
                RemoveIndicator(m_Selection.ticket);
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, (m_Selection.tp == 0 ? 0 : price + m_Selection.tp - m_Selection.pr), (m_Selection.sl == 0 ? 0 : price + m_Selection.sl - m_Selection.pr), m_Selection.bIsDayTrade);
                MoveSelection(macroGetLinePrice(def_IndicatorGhost, IT_PENDING));
                m_TradeLine.SpotLight();
                break;
        }
}

此段代码将防止订单类型被更改。 您可以更改挂单的填单点位,但不能将限价订单更改为破位订单,反之亦然。 现在,如果您想继续追逐价格,并在某个点位强制入场,请用如下显示的代码。 这是将在 EA 中使用的代码。

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price,  price + m_Selection.tp - m_Selection.pr, price + m_Selection.sl - m_Selection.pr, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, m_Selection.pr,  m_Selection.tp, m_Selection.sl, m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorFloat);
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

重要说明:由于 ForceNewType 宏替换的原因,使用此代码时要小心。 此宏替换包含一个 “break” 语句,若其执行,则导致代码退出 “case” 模块。 因此,您在修改此模块时应格外小心。

系统移动入场点位时将不再遭遇错误,但我们还有其它问题需要解决。 我已经展示了纠正问题的途径,来修改或保持订单的相同类型 — 您来选择最适合您的那个。 请记住,这些方案均各有其优点和缺点。 但我不会深入细节。 我只展示如何纠正和实现系统。

这些修改的结果可在以下视频中看到:



2.0.2. 为将来做准备

上述修改解决了问题,但还有更多的事情可以做。 在此,我将展示变化的开始。 查看 EA 的订单体系,依然还有很大的提升空间。 这还需要少量的修改,我想解释一下,以便您可以选择最适合自己的路径,因为每名交易者都有自己应对市场的行为方式。 我不想令您觉得有义务去使用我将向您展示的系统。 取而代之,我想打造一个基础,如此任何人都可以开发自定义 EA。

因此,我们迈入下一个事实:从第 18 部分开始,我一直在展示如何开发一个订单系统,易于交易特定资产的人使用。 但在第 20 部分中,订单系统加进了了视觉元素,因为在某些时候 Chart Trade 对于交易来说变得并无必要,因为一切都将由订单系统本身指示,如此您就能够在图表上正确更改和配置所有内容。 为了达到这一点,我们需要从某个地方开始,我们现在就去做。

如何直接在订单内更改交易量,而无需从图表中删除订单,难道在 Chart Trade 中更改交易量,之后要在图表上重新下订单? 很有趣,不是吗? 我们现在就来实现此功能。 它在某些情况下有很大帮助,但您应当学习并理解如何使用该系统,因为您在任何其它平台上找不到它。 老实说,我从未见过拥有此类功能的 EA。 我们来看看您在任何拥有此功能的 EA 中能做哪些操作。

首先,定义一个新的指标索引。

#define def_IndicatorFloat      3

当挂单收到此值作为单号时,能够以完全不同的方式进行处理。 以前存在的所有东西都还保留在订单系统之中,而我们只添加一个新索引。

之后,我们将往系统中添加一个新对象:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose,
                        m_BtnCheck;
C_Object_Edit           m_EditInfo1,
                        m_EditInfo2;
C_Object_Label          m_BtnMove;

此对象始终在订单挂起时启用一些功能。

现在我们迈入 C_Object_BitMap 类编辑它。 添加一些定义:

#define def_BtnClose            "Images\\NanoEA-SIMD\\Btn_Close.bmp"
#define def_BtnCheckEnabled     "Images\\NanoEA-SIMD\\CheckBoxEnabled.bmp"
#define def_BtnCheckDisabled    "Images\\NanoEA-SIMD\\CheckBoxDisabled.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
#resource "\\" + def_BtnCheckEnabled
#resource "\\" + def_BtnCheckDisabled

我们需要知道该类里发生了什么。 如此,添加以下函数:

bool GetStateButton(string szObjectName) const
{
        return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
}
//+------------------------------------------------------------------+
inline void SetStateButton(string szObjectName, bool bState)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, bState);
}

GetStateButton 返回按钮的状态。 MetaTrader 5 会改变状态,因此我们实现额外的步骤,而只需查询按钮值是 True 或是 False。 但也许会发生状态出乎我们所料的情况。 然后调用 SetStateButton 设置状态,来反映出交易服务器和 EA 都看到的实际状态。

另一处简单的修改是在 C_Object_Edit 类之中:

inline void SetOnlyRead(string szObjectName, bool OnlyRead)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, OnlyRead);
}

它示意该值是否可以编辑。 我们希望能够直接在图表上修改订单交易量,而无需借助 Chart Trade。 创建的任何挂单将始终处于只读模式,但我们将创建一个系统来修改这些。

故此,我们回到 C_IndicatorTradeView,并实现更多修改。 我们将为系统创建一个新函数。 它如下所示:

#define macroSwapAtFloat(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorFloat, A, B));
                bool PendingAtFloat(ulong ticket)
                        {
                                eIndicatorTrade it;
                                
                                if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;
                                macroSwapAtFloat(IT_PENDING, EV_CHECK);
                                for (char c0 = 0; c0 < 3; c0++)
                                {
                                        switch(c0)
                                        {
                                                case 0: it = IT_PENDING;        break;
                                                case 1: it = IT_STOP;           break;
                                                case 2: it = IT_TAKE;           break;
                                                default:
                                                        return false;
                                        }
                                        macroSwapAtFloat(it, EV_CLOSE);
                                        macroSwapAtFloat(it, EV_MOVE);
                                        macroSwapAtFloat(it, EV_EDIT);
                                        macroSwapAtFloat(it, EV_GROUND);
                                        macroSwapAtFloat(it, EV_LINE);
                                        m_EditInfo1.SetOnlyRead(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT), false);
                                }
                                return true;
                        }
#undef macroSwapAtFloat

当调用此函数时,所有指标对象都被重命名,即指向订单的单号将被另一个值替换。 在这种情况下,它是我们在本主题开头研究过的那个指标。 我们还有一个问题。 我不使用任何结构来维护指标对象列表,我会以不同的方式来完成这一点。 这种方式就是,我们让 MetaTrader 5 为我们操心这个列表。 但也正因如此,我无法创建无限的浮动订单,因为我们只能有一笔浮动订单。 这一点可用以下代码行来检查:

if (macroGetLinePrice(def_IndicatorFloat, IT_PENDING) > 0) return false;

此处的检查很简单:如果指标线位于某处,宏替换将返回一个不同于 0 的值,如此我们就可知道已有某个指标占用了预留单号。 若请求被拒绝,这一点稍后对于 EA 恢复指标数据非常重要。 MetaTrader 5 会自动更改位图对象的状态,故此我们需要通知调用方有关失败信息。

下一个需要修改的地方是在创建指标的函数之中:

#define macroCreateIndicator(A, B, C, D)        {                                                                               \
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                        \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                                     \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : (A == IT_PENDING ? 108 : 92)), (A == IT_RESULT ? 34 : 22));       \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                                   \
                m_EditInfo1.Size(sz0, 60, 14);                                                                                  \
                if (A != IT_RESULT)     {                                                                                       \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);            \
                        m_BtnMove.Size(sz0, 21, 23);                                                                            \
                                        }else                   {                                                               \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);                   \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                                       \
                                                }
                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING:
                                                macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit);
                                                m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
                                                m_BtnCheck.SetStateButton(sz0, true);
                                                break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

所有高亮显示的部分都已加入,能够支持我们的新系统。 基本上,我们在此创建一个始终设置为 true 的复选框,这意味着订单将被立即放入订单簿之中。 我不打算修改这种交易方式,但将复选框的值从 “true” 改为 “false” 并事实上并不简单,这样会防止直接下订单。 此更改需要进行其它更深层次的修改,问题在于某些时候,您也许来下单,却忘记勾选复选框。 因此,若入场点被错过,您会认为这是 EA 的缺陷,而实际上这一切都是由于健忘。 所以,为避免这种情况,默认情况下,挂单将直接进入订单簿,因此您必须明确更改其状态。

下一个真正重要的函数如下所示:

#define def_AdjustValue(A) (A == 0 ? 0 : price + A - m_Selection.pr)
#define macroForceNewType       {                                                                                                                                               \
                RemoveOrderPendent(m_Selection.ticket);                                                                                                                         \
                RemoveIndicator(m_Selection.ticket);                                                                                                                            \
                CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);      \
                break;                                                                                                                                                          \
                                }

                void SetPriceSelection(double price)
                        {
                                char Pending;
                                double last;
                                long orderType;
                                
                                if (m_Selection.ticket == 0) return;
                                Mouse.Show();
                                if (m_Selection.ticket == def_IndicatorTicket0)
                                {
                                        CreateOrderPendent(m_Selection.vol, m_Selection.bIsBuy, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl), m_Selection.bIsDayTrade);
                                        RemoveIndicator(def_IndicatorTicket0);
                                        return;
                                }
                                if (m_Selection.ticket == def_IndicatorFloat)
                                {
                                        switch(m_Selection.it)
                                        {
                                                case IT_STOP   : m_Selection.sl = price; break;
                                                case IT_TAKE   : m_Selection.tp = price; break;
                                                case IT_PENDING:
                                                        m_Selection.sl = def_AdjustValue(m_Selection.sl);
                                                        m_Selection.tp = def_AdjustValue(m_Selection.tp);
                                                        m_Selection.pr = price;
                                                        break;
                                        }
                                        m_Selection.ticket = 0;
                                        m_TradeLine.SpotLight();
                                        return;
                                }
                                if ((Pending = GetInfosTradeServer(m_Selection.ticket)) == 0) return;
                                m_TradeLine.SpotLight();
                                switch (m_Selection.it)
                                {
                                        case IT_TAKE:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, price, m_Selection.sl);
                                                else ModifyPosition(m_Selection.ticket, price, m_Selection.sl);
                                                break;
                                        case IT_STOP:
                                                if (Pending < 0) ModifyOrderPendent(m_Selection.ticket, m_Selection.pr, m_Selection.tp, price);
                                                else ModifyPosition(m_Selection.ticket, m_Selection.tp, price);
                                                break;
                                        case IT_PENDING:
                                                orderType = OrderGetInteger(ORDER_TYPE);
                                                if ((orderType == ORDER_TYPE_BUY_LIMIT) || (orderType == ORDER_TYPE_SELL_LIMIT))
                                                {
                                                        last = SymbolInfoDouble(Terminal.GetSymbol(), (m_Selection.bIsBuy ? SYMBOL_ASK : SYMBOL_BID));
                                                        if (((m_Selection.bIsBuy) && (price > last)) || ((!m_Selection.bIsBuy) && (price < last))) macroForceNewType;
                                                }
                                                if (!ModifyOrderPendent(m_Selection.ticket, price, def_AdjustValue(m_Selection.tp), def_AdjustValue(m_Selection.sl))) macroForceNewType;
                                }
                                RemoveIndicator(def_IndicatorGhost);
                        }
#undef def_AdjustValue
#undef macroForceNewType

高亮显示的代码部分会完成一件有趣的事情:它们只更新将在选择器中用到的数值,但这些数值实际上存储在指标本身当中。 若我们以更常见的方式移动系统,这也许会发生,因此我们需要在选择器中指定这些数值,如此函数就能够在执行位置计算时指定正确的数值。

该函数中有些内容可能没有意义。 它负责创建和修改挂单的数据,但如果您查看它,您将看不到任何挂单返回到订单簿后的变化。 您可以直接在图表上移动、修改、和调整订单的交易量,但您将无法看到它如何反馈到图表。

这是事实。 更改和创建挂单的整个系统均在上述函数中实现。 特奇怪的是,这个函数不会仅仅迁就我们的希望,就将订单放回订单簿之中,这是因为它实际上只是发出了请求,如下所示。 为免复杂化,我只展示负责在市场深度中请求下订单的部分。

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

// ... Internal code...

        case CHARTEVENT_OBJECT_CLICK:
                if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                {
                        case EV_CLOSE:
                                if (ticket == def_IndicatorFloat) RemoveIndicator(def_IndicatorFloat, it);
                                else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                {
                        case IT_PENDING:
                        case IT_RESULT:
                                if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                break;
                        case IT_TAKE:
                        case IT_STOP:
                                m_Selection.ticket = ticket;
                                m_Selection.it = it;
                                SetPriceSelection(0);
                        break;
                }
                break;
        case EV_MOVE:
                if (ticket == def_IndicatorFloat)
                {
                        m_Selection.ticket = ticket;
                        m_Selection.it = it;
                }else   CreateGhostIndicator(ticket, it);
                break;
        case EV_CHECK:
                if (ticket != def_IndicatorFloat)
                {
                        if (PendingAtFloat(ticket)) RemoveOrderPendent(ticket);
                        else m_BtnCheck.SetStateButton(macroMountName(ticket, IT_PENDING, EV_CHECK), true);
                } else
                {
                        m_Selection.ticket = def_IndicatorTicket0;
                        m_Selection.it = IT_PENDING;
                        m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
                        m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
                        m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
                        m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
                        m_Selection.bIsDayTrade = true;
                        m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
                        SetPriceSelection(m_Selection.pr);
                        RemoveIndicator(def_IndicatorFloat);
                }

// ... Rest of the code...

看看系统如何自我构建:随着系统变得越来越大,我们的编程却越来越少。

高亮显示的代码与我们在主题开头创建的指标有关。 虽然一切似乎都工作良好,但有一些东西我们稍后会改变,因为当浮动订单返回订单簿时,日内交易订单还有一个缺点,原因则是它会在一天结束时平单。 这个稍后会加以修改,但您应当留意这一点。 现在您可能会对这一切感到困惑,并且仍然不明白当我们单击复选框时,挂单实际上是如何进入和离开订单簿的。 参见如下示意图:

能看到所有调用都来自同一个位置。 我们有一笔订单从市场深度中删除,但它还继续出现在图表上。 所有操作都遵照前面文章所示执行。 但是,如果您尝试查找订单返回到市场深度的特定时间时,您可能会在代码中迷失方向。 现在,如果您查看示意图,您可看到调用来自 DispatchMessage 函数,因为这是调用 SetPriceSelect 函数的唯一位置。 但如果我们看一下 SetPriceSelect 函数,并未涉及依据浮动系统中的索引创建订单。 但要注意一件事。 我们曾依据索引 0 创建订单,而这正是我们所用的。 我们更改订单单号,并通报它将作为索引 0 的单号 — 以这种方式创建订单。 请参阅下面的代码以了解其工作原理。

m_Selection.ticket = def_IndicatorTicket0;
m_Selection.it = IT_PENDING;
m_Selection.pr = macroGetLinePrice(def_IndicatorFloat, IT_PENDING);
m_Selection.sl = macroGetLinePrice(def_IndicatorFloat, IT_STOP);
m_Selection.tp = macroGetLinePrice(def_IndicatorFloat, IT_TAKE);
m_Selection.bIsBuy = (m_Selection.pr < m_Selection.tp) || (m_Selection.sl < m_Selection.pr);
m_Selection.bIsDayTrade = true;
m_Selection.vol = m_EditInfo1.GetTextValue(macroMountName(def_IndicatorFloat, IT_PENDING, EV_EDIT)) * Terminal.GetVolumeMinimal();
SetPriceSelection(m_Selection.pr);
RemoveIndicator(def_IndicatorFloat);

除了高亮显示的代码行之外,代码是完美的。 目前没有办法解决这个问题。 这将在下一篇文章中完成,因为我们必须对类本身进行一些修改。

下面的视频演示修改完毕的结果。 注意交易量是如何修改的,以及如何在指定点位发送新订单。 EA 现在更加易于使用了。



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

附加的文件 |
数据科学与机器学习 — 神经网络(第 01 部分):前馈神经网络解密 数据科学与机器学习 — 神经网络(第 01 部分):前馈神经网络解密
许多人喜欢它们,但却只有少数人理解神经网络背后的整个操作。 在本文中,我尝试用淳朴的语言来解释前馈多层感知,解密其封闭大门背后的一切。
学习如何基于强力指数(Force Index)设计交易系统 学习如何基于强力指数(Force Index)设计交易系统
欢迎阅读我们系列中的新篇章,有关如何基于最流行的技术指标设计交易系统。 在本文中,我们将学习一个新的技术指标,以及如何运用强力指数(Force Index)指标创建交易系统。
神经网络变得轻松(第二十三部分):构建迁移学习工具 神经网络变得轻松(第二十三部分):构建迁移学习工具
在本系列文章中,我们已经不止一次提到了迁移学习。 然而,都只是提及而已。 在本文中,我建议填补这一空白,并仔细研究迁移学习。
利用对象轻松制作复杂指标 利用对象轻松制作复杂指标
本文提供了一种创建复杂指标的方法,同时还避免了在处置多个作图板、缓冲区、和/或组合来自多个来源的数据时出现的问题。