从头开始开发智能交易系统(第 28 部分):面向未来((III)
概述
在启动开发订单系统时,在文章从头开始开发交易智能交易系统(第 18 部分)之后,我不知道尚需多长时间才能达到这一点。 我们经历了各种时刻、变化、修正、等等。 我向您展示了如何做一些特定的事情,例如标记事物,或令系统更直观。 但还有一些东西我在这个阶段无法展示,因为道路尚未完全准备好。 这段旅程令我们能够以这样一种方式构建概念,即每个人都可以理解这个思路,并理解系统是如何工作的。
在之前的所有文章中,我已设定阶段,如此我们就可以在阅读本文时对系统操作拥有相同水平的理解。 如此,我希望这份资料不会过于混淆或复杂。 从一开始就有一个问题,我一直回避仔细分析它。 但对于更富经验的交易者来说,这非常重要。 猛地一看,也许这很愚蠢,但是当需要交易时,我们就会醒悟我们在 EA 中还缺少一些东西。 然后我们会扪心自问“这里还缺什么? 我所说的是一种恢复止盈和止损数值的方式,这些数值出于某种原因被删除了,而我们打算在图表上恢复它们。
如果您曾经尝试过这样做,那么您应该懂得这是一项相当困难且缓慢的任务,因为您需要“遵循某个场景”才能令使一切工作顺利,否则我们最终会一直犯错误。
MetaTrader 5 提供了一个允许创建和更正订单参数值的单据系统。 该思路是拥有一个智能系统,可令相同的票据系统更快、更高效。 MetaTrader 5 系统本身并不完美;有时它可能比我们正在开发的 EA 更慢、更容易出错。
但直到现在,我从未解释过如何生成 TP 和 SL(止盈和止损)价位。 我认为破位删除位足够清晰且直观。 但是如何实现它,即我们应该如何设置破位,或直接在图表上恢复它们? 这是一件很有趣的事,它带出了一堆问题,当然,这也是创建本文的原因:展示在图表上创建破位级别的众多方式之一 ,且无需求助于任何外部资源,只需用到订单系统 EA。
2.0. 开工:实现
首先,我们需要强制 EA 停止检查,从开发的第一天起,它就已经如此这般做了很久。 为了删除该检查,您需要删除所有划掉的代码,并添加高亮显示的代码:
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()) { IndicatorAdd(ticket = PositionGetInteger(POSITION_TICKET)); SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), Res += PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN)); 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; };
划掉的代码并非一去不返,但它会在某个时刻回来。 然而,在此刻,它得不偿失。 一旦这些完成,我们就可以开始考虑如何在图表上实现 TP 和 SL 系统,而无需借助 EA 以外的任何资源。
每名开发人员都会有自己的解决这个问题的思路:其中一些对交易者来说更容易理解,而另一些则更困难;有些则很难付诸实践,而另一些则相对容易。 我并未说我将在这里采用和展示的是最合适或最简单的方式,但它是迄今为止最适合我工作及使用平台的方式。 另外,我不需要创建任何新元素。 我们仅需调整代码中的一些内容。
2.0.1. 拖拽系统建模
当前开发阶段的 EA 代码本身提供了一些我们该如何针对所要创建的系统进行建模的提示。 请看下面的代码:
#define macroUpdate(A, B) if (B > 0) { \ if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A); \ PositionAxlePrice(ticket, A, B); \ SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B)); \ } else RemoveIndicator(ticket, A); void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy) { double pr; bool b0 = false; if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else { pr = macroGetLinePrice(ticket, IT_RESULT); if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0)) { CreateIndicator(ticket, IT_PENDING); PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr); ChartRedraw(); } pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING)); SetTextValue(ticket, IT_PENDING, vol); } if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp); if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl); if (b0) ChartRedraw(); } #undef macroUpdate
突出显示的行包含将要执行任务的宏替换。 我们需要修改它,如此它就能为实现我们所需提供必要的帮助,即,破位级别指标。 仔细查看宏代码。 它如下所示:
#define macroUpdate(A, B){ if (B > 0) { \ if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A); \ PositionAxlePrice(ticket, A, B); \ SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B)); \ } else RemoveIndicator(ticket, A); }
我们执行以下操作:当 B 值,可以是止盈或止损,大于 0 时,检查指标是否在图表上。 如果没有,则创建它,放置到图表,并为它设置显示值。 如果 B 值等于 0,那么我们将从图表中彻底删除该指标,且我们将在宏代码中高亮显示处执行此操作。 但是,如果不从图表彻底删除指标,我们依旧保留其元素,且若该元素可以配置为显示我们想要执行的操作(即,为订单或持仓创建缺失的破位级别),该元素将转回订单或 OCO 仓位,这是否足够? 是的,这就足够了,该思路是:保留一个元素,在本例中就是一个对象,用于移动破位级别,并创建我们缺少的破位级别。 我们只需要拖拽这个元素,就会创建限价。 这就是我们构建系统所采用的理论框架。
但是,简简单单的这样做,我们并不能获得我们需要的一切。 还需要多一项更改。 在继续之前,我们将执行此操作。 以下代码高亮显示了此更改:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { //... Internal code... m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp))); m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl)); // ... The rest of the code ... }
我们检查经由 Chart Trade 输入的初始值是否为空。 如果是,则不会创建任何指标,图表仅显示入场点。
这为系统的其余部分提供了更线性的操作。 如果未指定破位级别,则整个订单模型将与从我们一开始在图表上放置挂单时就指定数值具有相同的行为。
因此,没有一个交易者会琢磨:这些悬浮在订单或持仓上的数字代表什么? 因为交易者只晓得它们代表可在图表上移动的元素。
但尽管如此,我们仍有一个小问题,为此我们要更改两个宏替换。 见下文:
#define macroSetLinePrice(ticket, it, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price) #define macroGetLinePrice(ticket, it) ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE) #define macroGetPrice(ticket, it, ev) ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE)
这种修改对于我们将要构建系统的其它部分非常重要。 但是我为什么要拉一条切割线呢? 原因在于我们需要令系统更加灵活,为了达成这一点,删除了划掉的代码,并在其位置替换为高亮显示的行。 如果我们无需宏替换来为价格赋值,为什么我们需要 macroGetPrice? 事实上,只有一个点真正执行这种价格调整,将其写入图表上的对象。 这一点可以在下面的代码中看到:
#define macroSetPrice(ticket, it, ev, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, ev), OBJPROP_PRICE, price) //--- // ... Additional code inside the class.... //--- inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price) { int x, y, desl; ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y); if (it != IT_RESULT) macroSetPrice(ticket, it, EV_MOVE, price); macroSetPrice(ticket, it, EV_LINE, price); macroSetAxleY(it); switch (it) { case IT_TAKE: desl = 160; break; case IT_STOP: desl = 270; break; default: desl = 0; } macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2)); }
有因于此,此处不需要宏替换来调整对象上的价格,从而令其在代码中的所有其它地方可见。 实际上,现在它甚至不必是一个宏替换,但我会保持原样,来减少更改代码之后出错的机会。
现在我们的系统是截至最新,我们可以迈入下一个主题,并一切按计划工作。
2.0.2. 新的更新功能
我在上一个主题中已提及过这一点:我们需要做的就是配置 Update 函数,而 EA 将解决所有相关问题。 鉴于主要焦点仅在于止盈和止损指标上,且它们是在宏替换中执行,我们只需要正确设置的宏替换即可。
但有一个问题尚未解决。 我们需要创建移动按钮,它独立于指标的其余部分。 为此,我们来隔离按钮创建代码:
// ... class code ... #define def_ColorLineTake clrDarkGreen #define def_ColorLineStop clrMaroon // ... class code .... inline void CreateBtnMoveIndicator(ulong ticket, eIndicatorTrade it, color C = clrNONE) { string sz0 = macroMountName(ticket, it, EV_MOVE); ObjectDelete(Terminal.Get_ID(), macroMountName(ticket, it, EV_MOVE)); m_BtnMove.Create(ticket, sz0, "Wingdings", "u", 17, (C == clrNONE ? (it == IT_TAKE ? def_ColorLineTake : def_ColorLineStop) : C)); m_BtnMove.Size(sz0, 21, 23); } // ... the rest of the class code ...
此代码仅创建移动按钮,且无其它。 它非常简单,且直截了当。 我曾想过将此代码保留为宏,但还是决定将其作为函数。 因其被声明为内置函数,因此编译器会像对待宏替换一样处置它。 为了使生活更轻松,相同代码部分有两个新定义,因为在某些时候我们只会创建一个移动按钮,且我们希望它与整个系统所用的颜色相同。 在相似情况下,系统的行为或外观不一致是不可取的。 为了减少麻烦,我们保留如上所示的颜色。
现在我们可以转到 Update 函数;其完整代码如上所示。 请注意,下面的版本与本文前面介绍的版本之间的唯一区别在于高亮显示的代码。 此代码是 Update 函数本身所用的宏替换。
#define macroUpdate(A, B){ \ if (B == 0) { if (macroGetPrice(ticket, A, EV_LINE) > 0) RemoveIndicator(ticket, A); \ if (macroGetPrice(ticket, A, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, A); \ } else if (b0 = (macroGetPrice(ticket, A, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, A); \ PositionAxlePrice(ticket, A, (B == 0 ? pr : B)); \ SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B)); \ } void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy) { double pr; bool b0 = false; if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else { pr = macroGetPrice(ticket, IT_RESULT, EV_LINE); if ((pr == 0) && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0)) { CreateIndicator(ticket, IT_PENDING); PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr); ChartRedraw(); } pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE)); SetTextValue(ticket, IT_PENDING, vol); } macroUpdate(IT_TAKE, tp); macroUpdate(IT_STOP, sl); if (b0) ChartRedraw(); } #undef macroUpdate
我们来更详尽地看一下这个宏替换,从而理解发生的真实情况。 理解了这一点是必要的,它能够解决系统使用过程中依然会出现的一些问题。 它还没准备完善,我们尚需做出更多的改变。
第一步,我们有以下行为:当删除破位订单之一(止盈或止损)时,我们将立即从交易品种图表中删除相关指标。 为此,我们检查是否有一条线,它是代表指标存在的要素之一。 然后我们再检查这个指标是否有一个移动对象。 如果不存在,则创建它。 由此,在图表上我们没有了指标,但图表上仍然存在它的其余部分,即移动对象
第二步发生在创建破位级别的情况下:服务器上的订单会有一个破位级别,该级别亦应出现在图表上。 在这种情况下,代表移动的对象将被删除,并会创建一个完整的指标,再将其放置在相应的位置,指示当前破位级别(止盈或止损)的位置。
在最后一步中,我们将指标定位在正确的点位。 一个有趣的细节:如果破位ibie指标只是一个代表可移动的对象,那么它的放置点应恰好是订单或持仓的价位。 换言之,允许创建止盈和止损的移动对象,且链接到它所属的订单或持仓价位。 因此,很容易注意到订单或持仓是否缺少其中一个破位级别。
这基本上就是我们需要做的:当我们单击代表移动的对象时,会像往常一样创建一个幻影,同时还会创建完整指标的表示。 这无需添加或修改任何代码就可完成。 从现在开始,我们可以按照如常方式移动和调整破位级别。 但我们点击某个点之前,破位订单将不存在。 在文章末尾的演示视频中会清楚地展示这些,我还在其中展示了系统如何在实盘账户上工作。
尽管一切似乎都很优秀,但在这里我们有一些不一致的地方,这会迫使我们在某些时候创建或更改代码。 我们将在下一个主题中看到它,这是由于该步骤现在已经完结。
2.0.3. 解决浮动指标的不便
当我们处于浮动指标模式时,第一个不便出现:它在图表上,但并不在服务器上。 有关更多信息,请参阅文章从头开始开发智能交易系统(第 26 部分)和(第 27 部分),在其中我展示了浮动指标的工作原理,及其实现方式。 这些指标都很有用,因此它们不会从 EA 中删除。 但它们并不适合我们上面所见到的系统,因为它们的操作不同于来自交易服务器上存在的真实订单和持仓的指标。 为了解决使用浮动指标时出现的这些问题,我们必须进入 DispatchMessage 函数,并在那里进行调整,如下所示。
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price; // ... Internal code... switch (id) { // ... Internal code... case CHARTEVENT_OBJECT_CLICK: if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev) { case EV_TYPE: if (ticket == def_IndicatorFloat) { macroGetDataIndicatorFloat; m_Selection.tp = (m_Selection.tp == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.tp - m_Selection.pr) * (m_Selection.bIsBuy ? 1 : -1))); m_Selection.sl = (m_Selection.sl == 0 ? 0 : m_Selection.pr + (MathAbs(m_Selection.sl - m_Selection.pr) * (m_Selection.bIsBuy ? -1 : 1))); m_Selection.ticket = 0; UpdateIndicators(def_IndicatorFloat, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy); } else m_BtnInfoType.SetStateButton(sparam, !m_BtnInfoType.GetStateButton(sparam)); break; case EV_DS: if (ticket != def_IndicatorFloat) m_BtnInfo_DS.SetStateButton(sparam, !m_BtnInfo_DS.GetStateButton(sparam)); break; case EV_CLOSE: if (ticket == def_IndicatorFloat) { macroGetDataIndicatorFloat; RemoveIndicator(def_IndicatorFloat, it); if (it != IT_PENDING) UpdateIndicators(def_IndicatorFloat, (it == IT_TAKE ? 0 : m_Selection.tp), (it == IT_STOP ? 0 : m_Selection.sl), m_Selection.vol, m_Selection.bIsBuy); }else if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it) // ... The rest of the code...
按照如上高亮显示进行更改,我们实际上消除了与浮动指标设置相关的所有其它问题,因为现在我们能以相同的方式来调整浮动指标数据,以及代表交易服务器上现有数据的方法。 但这并不能彻底解决我们的问题。 我们必须解决另一个长期存在的不便。 为了讨论这个问题,我们移到下一个主题,因为它值得单独讨论。
2.0.4. 负止盈数值的缺点
我们在本文中讨论的最后一个缺点,就是止盈值通常可以配置为负数值,这种情况已经发生了很长时间。 但这对交易系统毫无意义:如果您尝试将复数值发送到服务器,将返回错误消息。 因此,我们必须修复它,并解决另一个问题,即挂单可以将破位价位改为正数值。
EA 现在允许这样做,更糟糕的是,订单系统指示该值已放到服务器上,而实际上服务器返回出错,但 EA 简单地将其忽略。 在挂单的情况下,问题更加复杂,因为在持仓的情况下,行为应该有所不同,且此错误尚未得到修复。 一旦我们有能力直接在图表上定义破位级别,那么这个缺点就会消失。
这里应当提到的是,在有持仓的情况下,我们可以有一个正数值的止损价位,这表明如果触发止损,我们将把相关值计入账户。 但对于挂单,这是一个错误,它将阻碍服务器正确创建订单。 为了解决该问题,我们必须检查止盈值:当它等于或小于 0 时,那么我们必须防止它变得更小。 此外,对于挂单,我们不应该允许止损大于 0。 事实上,当满足条件 0 时,我们强制 EA 采用最小允许值。 在这种情况下,订单或持仓对于交易系统才具有一定的意义,而止损或止盈等于持仓得开盘价也是没有意义的。
为了令其尽可能简单,我们需要在系统中创建一个变量,如下所示:
struct st00 { eIndicatorTrade it; bool bIsBuy, bIsDayTrade; ulong ticket; double vol, pr, tp, sl, MousePrice; }m_Selection;
我们为什么不能只改鼠标线上的价格点? 原因在于为了正确操作鼠标,必须用到系统调用,也就是说,必须通过 WINDOWS API 操纵鼠标位置值,这将迫使我们启用外部 dll 调用,而我不愿这样做。 这种方式,在 EA 中组装局部值更简单,高亮显示的数据能为我们存储该值。
该值将用在三个不同的地方。 第一个所在是移动函数。 下面的代码准确地显示发生的位置:
void MoveSelection(double price) { if (m_Selection.ticket == 0) return; switch (m_Selection.it) { case IT_TAKE: UpdateIndicators(m_Selection.ticket, price, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy); break; case IT_STOP: UpdateIndicators(m_Selection.ticket, m_Selection.tp, price, m_Selection.vol, m_Selection.bIsBuy); break; case IT_PENDING: PositionAxlePrice(m_Selection.ticket, IT_PENDING, price); UpdateIndicators(m_Selection.ticket, (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.vol, m_Selection.bIsBuy); m_Selection.MousePrice = price; break; } if (Mouse.IsVisible()) { m_TradeLine.SpotLight(macroMountName(m_Selection.ticket, m_Selection.it, EV_LINE)); Mouse.Hide(); } }
为什么我们不将所有内容都放在上面的函数中,只因它负责移动破位单点位? 原因在于我们需要进行一些计算才能正确设置止盈或止损价位,并在不同的点位执行此操作要容易得多。 我们还需要在另一个地方更改此值,故此处是引用该值的第二个位置。 参阅以下代码:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price; bool bKeyBuy, // ... Internal code .... switch (id) { case CHARTEVENT_MOUSE_MOVE: Mouse.GetPositionDP(dt, price); mKeys = Mouse.GetButtonStatus(); bEClick = (mKeys & 0x01) == 0x01; //Left mouse button click bKeyBuy = (mKeys & 0x04) == 0x04; //SHIFT pressed bKeySell = (mKeys & 0x08) == 0x08; //CTRL pressed if (bKeyBuy != bKeySell) { if (!bMounting) { m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl); valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_Selection.vol); valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_Selection.vol); m_Selection.it = IT_PENDING; m_Selection.pr = price; } m_Selection.tp = (valueTp == 0 ? 0 : m_Selection.pr + (bKeyBuy ? valueTp : (-valueTp))); m_Selection.sl = (valueSl == 0 ? 0 : m_Selection.pr + (bKeyBuy ? (-valueSl) : valueSl)); m_Selection.bIsBuy = bKeyBuy; m_BtnInfoType.SetStateButton(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_TYPE), bKeyBuy); if (!bMounting) { IndicatorAdd(m_Selection.ticket = def_IndicatorTicket0); bMounting = true; } MoveSelection(price); if ((bEClick) && (memLocal == 0)) SetPriceSelection(memLocal = price); }else if (bMounting) { RemoveIndicator(def_IndicatorTicket0); memLocal = 0; bMounting = false; }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost)) { if (bEClick) SetPriceSelection(m_Selection.MousePrice); else MoveSelection(price); } break; // ... Rest of the code...
多亏了这一点,我们在系统中具有可预测的行为。 还有一处引用了该值,但由于复杂性,我决定修改整个内容,从而不再需要宏替换。 现在它会变成一个函数。 如此,新的 Update 函数如下所示:
void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy) { double pr; bool b0, bPen = true; if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else { bPen = (pr = macroGetPrice(ticket, IT_RESULT, EV_LINE)) == 0; if (bPen && (macroGetPrice(ticket, IT_PENDING, EV_MOVE) == 0)) { CreateIndicator(ticket, IT_PENDING); PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr); ChartRedraw(); } pr = (pr > 0 ? pr : macroGetPrice(ticket, IT_PENDING, EV_MOVE)); SetTextValue(ticket, IT_PENDING, vol); } b0 = UpdateIndicatorsLimits(ticket, IT_TAKE, tp, vol, pr, isBuy, bPen); b0 = (UpdateIndicatorsLimits(ticket, IT_STOP, sl, vol, pr, isBuy, bPen) ? true : b0); if (b0) ChartRedraw(); }
高亮显示的点位替换了之前的宏替换,但正如我说得,所需的代码要复杂得多,我们来看看第三点也是最后一个实际引用的位置。 在上面的代码中,止盈和止损指标之间没有区别:它们的处理方式相同。 看看下面的代码。
inline bool UpdateIndicatorsLimits(ulong ticket, eIndicatorTrade it, double price, double vol, double pr, bool isBuy, bool isPen) { bool b0 = false; double d1 = Terminal.GetPointPerTick(); if ( price == 0) { if (macroGetPrice(ticket, it, EV_LINE) > 0) RemoveIndicator(ticket, it); if (macroGetPrice(ticket, it, EV_MOVE) == 0) CreateBtnMoveIndicator(ticket, it); } else if (b0 = (macroGetPrice(ticket, it, EV_LINE) == 0 ? true : b0)) CreateIndicator(ticket, it); switch (it) { case IT_TAKE: price = (price == 0 ? 0 : (((isBuy ? price - pr : pr - price) > 0) ? price : (isBuy ? pr + d1 : pr - d1))); break; case IT_STOP: price = (price == 0 ? 0 : (isPen ? (((isBuy ? price - pr : pr - price) < 0) ? price : (isBuy ? pr - d1 : pr + d1)) : price)); break; } if (m_Selection.it == it) m_Selection.MousePrice = price; PositionAxlePrice(ticket, it, (price == 0 ? pr : price)); SetTextValue(ticket, it, vol, (isBuy ? price - pr : pr - price)); return b0; }
从现在开始,挂单的止盈值不能有错,因为我们已限定了允许值。 下一笔买入挂单,然后将止盈价位移到负数值(即,低于入场点位)已不再可能,因为止盈指标计算后会阻止这一点。 以这种方式编写代码的好处是,无论是订单亦或持仓,取值永远不能为负数,因为 EA 本身不允许。
现在,就止损而言,有一处计算略有不同,我们在其中检查我们正在管控的类型 - 订单或持仓。 如果它是一笔订单,则止损值永远不会为正数值;如果是持仓,则 EA 将简单地忽略任何其它条件。 在这种情况下,EA 必须接受交易者指定的数值。 那么,现在我们可以有一个正止损,但仅限于是持仓的情况,且不会对订单系统代码的其余部分造成任何损害,以这种方式 EA 最终与交易服务器交互,且提交的数据不会被拒绝。
结束语
最终,经历若干篇文章以来,我们达至高潮,现在我们拥有一个几乎完整的订单系统,并且能对各种情况和市场条件具备相当的适应性。 从现在开始,我们能够使用鼠标和键盘基于全图形系统进行交易,并可进行市场分析,以便入场交易或离场。
对于那些刚刚入行的人,他们若想看看当前开发阶段的系统行为或外观,请观看下面的视频。 感谢至目前为止一直关注本系列文章的每个人。 但工作尚未完结,我们还有很多工作要做,直到这个智能系统变得令人难忘。 下篇文章再见! 👍
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10635