
从头开始开发智能交易系统(第 23 部分):新订单系统 (VI)
概述
在上一篇文章从头开始开发交易系统(第 22 部分)中,我们开发了一个移动挂单和持仓破位价的系统。 尽管这种方法相对安全(因为它反映的是交易服务器上的内容),然而它不是快速移动价位的最佳方式。
问题在于,每次我们用鼠标更改某些内容时,该事件都会发送到服务器,然后我们必须等待响应。 该问题与每次跳价时都要发送事件这一事实有关,也就是说,如果在某个时刻由于若干次跳价我们需要移动一次价位,我们不得不遍历所有中间值,这令整个过程非常缓慢。 这就是我们要如何实现代码的修改,令其更灵活且更快速地修改价位。
1.0. 计划
为了实现修改,我们需要做一件非常简单的事情:不要通知服务器所有更改,只通知所需的变更。 即便只是这样做,业已令一切操作正常,尽管我们不能绝对确保一切都恰如我们正在做的一样。
现在我们来看看我们需要在哪里修改代码。 我们所用的单独函数如下所示:
#define macroGetPrice(A) StringToDouble(ObjectGetString(Terminal.Get_ID(), MountName(ticket, A, EV_LINE), OBJPROP_TOOLTIP)) 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; } }; } #undef macroGetPrice
但是,伴随函数内部的修改,我们还需要实现一些与鼠标事件相关的修改。 这就是我们首先要关注的。
高亮显示的片段需要替换为其它内容,从而表示将会用到新仓位。 但是,对于用户来说,所有修改都应该能够理解。
我已找到了一种有效的方式,可令所有内容易于理解,同时又可以运作,而且无需对整个代码结构进行重大更改。 这就是创建一个幻影指示标签,其在需要之前将不可见。 幸运的是,MetaTrader 5 提供了一种非常简单的途径来做到这一点。 如此,对于那些已熟悉本系列早期文章中的素材的人来说,很容易就能理解本文内容。
2.0. 实现
为了实现一个幻影标签,我们简单地按照真实标签来创建它。 这它是真实标签的精确阴影,直到我们继续按照上一篇文章中展示的方式操控价格。 在此刻,幻影标签就会随着真实标签的移动而出现在图表上。 这将令您能够轻松比较正在发生的事情,并了解是否要进行更改。
2.0.1. 创建一个幻影指示标签
所有修改都在 C_IndicatorTradeView 类中实现。 我们从定义三条新指令开始:
#define def_IndicatorGhost "G" #define def_IndicatorReal "R" #define def_IndicatorGhostColor clrDimGray
这可令 MetaTrader 5 为我们工作。 该规则是这样的:首先我们创建一个幻影标签,然后我们创建一个真实标签。 因此,MetaTrader 5 会注意不显示幻影标签,直到我们真正需要看到它的那一刻。 由于这是由 MetaTrader 5 自身完成的,我们在编程时就节省了大量精力。
一个重要的细节:如果您打算更改幻影的颜色,简单地更改在所选部件中指定的颜色即可。
因此,下一步是修改函数,只允许生成唯一的名称。
inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev, bool isGhost = false) { return StringFormat("%s%c%c%c%llu%c%c%c%s", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)(isGhost ? ev + 32 : ev), def_SeparatorInfo, (isGhost ? def_IndicatorGhost : def_IndicatorReal)); }
来自先前版本的高亮显示部分已经过添加或修改。 我们让 MetaTrader 5 生成唯一的名称。 如此,我们就不需要再担心这一点。 请注意,我已向事件中添加了一些数值。 我如此做是为了防止幻影接收事件,并试图控制。
下一步是显而易见的 — 我们必须创建标签本身:
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2, cor3; string sz0, sz1; switch (it) { case IT_TAKE : cor1 = clrForestGreen; cor2 = clrDarkGreen; cor3 = clrNONE; break; case IT_STOP : cor1 = clrFireBrick; cor2 = clrMaroon; cor3 = clrNONE; break; case IT_PENDING: cor1 = clrCornflowerBlue; cor2 = clrDarkGoldenrod; cor3 = def_ColorVolumeEdit; break; case IT_RESULT : default: cor1 = clrDarkBlue; cor2 = clrDarkBlue; cor3 = def_ColorVolumeResult; break; } m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE, true), def_IndicatorGhostColor); m_TradeLine.Create(ticket, MountName(ticket, it, EV_LINE), cor2); if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE)); if (it != IT_RESULT) m_BackGround.Create(ticket, sz0 = MountName(ticket, it, EV_GROUND, true), def_IndicatorGhostColor); m_BackGround.Create(ticket, sz1 = MountName(ticket, it, EV_GROUND), cor1); switch (it) { case IT_TAKE: case IT_STOP: case IT_PENDING: m_BackGround.Size(sz0, 92, 22); m_BackGround.Size(sz1, 92, 22); break; case IT_RESULT: m_BackGround.Size(sz1, 84, 34); break; } m_BtnClose.Create(ticket, MountName(ticket, it, EV_CLOSE), def_BtnClose); m_EditInfo1.Create(ticket, sz0 = MountName(ticket, it, EV_EDIT, true), def_IndicatorGhostColor, 0.0); m_EditInfo1.Create(ticket, sz1 = MountName(ticket, it, EV_EDIT), cor3, 0.0); m_EditInfo1.Size(sz0, 60, 14); m_EditInfo1.Size(sz1, 60, 14); if (it != IT_RESULT) { m_BtnMove.Create(ticket, sz0 = MountName(ticket, it, EV_MOVE, true), "Wingdings", "u", 17, def_IndicatorGhostColor); m_BtnMove.Create(ticket, sz1 = MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2); m_BtnMove.Size(sz1, 21, 21); }else { m_EditInfo2.Create(ticket, sz1 = MountName(ticket, it, EV_PROFIT), clrNONE, 0.0); m_EditInfo2.Size(sz1, 60, 14); } }
所有高亮显示的行都会产生幻影。 有一件事也许看起来很奇怪:为什么我们不重现所有的元素。 事实上,幻影并不是真实标签的精确复制品,而只是它的影子。 因此,我们不需要创建所有元素。 真实标签定义了应该发生的事情,而幻影只作为参考,即交易服务器将看到的内容。
现在进入需要更详细研究的部分,其中 MetaTrader 5 将真正努力工作。 您也许会认为在正确的位置排列对象需要做很多工作,但来看看源代码中实际更改的内容。
#define macroSetAxleY(A, B) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND, B), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE, B), y); \ m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE, B), y); \ m_EditInfo1.PositionAxleY(MountName(ticket, A, EV_EDIT, B), y, (A == IT_RESULT ? -1 : 0)); \ m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE, B), (A == IT_RESULT ? 9999 : y)); \ m_EditInfo2.PositionAxleY(MountName(ticket, A, EV_PROFIT, B), (A == IT_RESULT ? y : 9999), 1); \ } #define macroSetAxleX(A, B, C) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND, C), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE, C), B); \ m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE, C), B + 3); \ m_EditInfo1.PositionAxleX(MountName(ticket, A, EV_EDIT, C), B + 21); \ m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE, C), B + 80); \ m_EditInfo2.PositionAxleX(MountName(ticket, A, EV_PROFIT, C), B + 21); \ } //+------------------------------------------------------------------+ inline void ReDrawAllsIndicator(void) { int max = ObjectsTotal(Terminal.Get_ID(), -1, -1); ulong ticket; double price; eIndicatorTrade it; eEventType ev; for (int c0 = 0; c0 <= max; c0++) if (GetIndicatorInfos(ObjectName(Terminal.Get_ID(), c0, -1, -1), ticket, price, it, ev)) PositionAxlePrice(ticket, it, price); } //+------------------------------------------------------------------+ inline void PositionAxlePrice(ulong ticket, eIndicatorTrade it, double price) { int x, y, desl; ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y); ObjectSetString(Terminal.Get_ID(), MountName(ticket, it, EV_LINE), OBJPROP_TOOLTIP, DoubleToString(price)); macroSetAxleY(it, true); macroSetAxleY(it, false); switch (it) { case IT_TAKE: desl = 110; break; case IT_STOP: desl = 220; break; default: desl = 0; } macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), true); macroSetAxleX(it, desl + (int)(ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS) * 0.2), false); } #undef macroSetAxleX #undef macroSetAxleY
全部就这些吗? 我们只需简单地更改宏? 是的,没有必要重新创建整个代码,我们只需调整它即可。 我们真正需要做的所有就是告诉 MetaTrader 5 我们正在操作的对象名称,而 MetaTrader 5 将为我们完成剩下的工作。 我们不需要为此创建一系列函数。 只需添加高亮显示的部分即可。
要更改的另一个函数在此如下所示:
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: value0 = value0 / Terminal.GetVolumeMinimal(); m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit); m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor); break; case IT_TAKE : case IT_STOP : finance = (value1 / Terminal.GetAdjustToTrade()) * value0; m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance); m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor); break; } }
于此,我们已添加了选定的片段。 这就是我们的幻影如何被选择的。 它精准地反映了真实标签正在发生的事情。 事实上,它反映得太好了,以至于我们不得不再实现一些东西,才能让它正常工作。 代码没有错,但幻影与真实标签的关系太紧密,而这我们实际上应该避免。
此处最后一个要更改的函数如下所示:
inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL) { #define macroDestroy(A, B) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND, B)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE, B)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE, B)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT, B)); \ if (A != IT_RESULT) ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE, B)); \ else ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_PROFIT, B)); \ } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT)) { macroDestroy(IT_RESULT, true); macroDestroy(IT_RESULT, false); macroDestroy(IT_PENDING, true); macroDestroy(IT_PENDING, false); macroDestroy(IT_TAKE, true); macroDestroy(IT_TAKE, false); macroDestroy(IT_STOP, true); macroDestroy(IT_STOP, false); } else { macroDestroy(it, true); macroDestroy(it, false); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); #undef macroDestroy }
高亮显示的片段已经过更改,这里没什么特别的。
2.0.2. 将幻影自真实剥离
上一章节中所做的修改会创建一个幻影。 但我们遇到一个问题 — 它太靠近真实对象了。 有些事乍一看,很难实现。 但当我们查看代码时,我们看到已经有了一个解决方案。 不过,此解决方案位于错误的位置。 我们需要改变解决方案所处的位置,并令其在整个类中更加明显。
该解决方案的代码如下所示:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, tp, sl; bool isBuy, bKeyBuy, bKeySell, bEClick; long info; datetime dt; uint mKeys; eIndicatorTrade it; eEventType ev; static bool bMounting = false, bIsDT = false, bIsMove = false; static double leverange = 0, valueTp = 0, valueSl = 0, memLocal = 0; switch (id) { case CHARTEVENT_MOUSE_MOVE: // ... The rest of the code...
高亮显示的代码就是该解决方案。 但这怎么可能呢? 请记住,当您下挂单时,系统会设法创建和操纵数据,如此最终您在图表上得到一个表述标签,指示下订单的所在。 这是通过以下代码完成的:
case CHARTEVENT_MOUSE_MOVE: Mouse.GetPositionDP(dt, price); mKeys = Mouse.GetButtonStatus(); bEClick = (mKeys & 0x01) == 0x01; //left mouse click bKeyBuy = (mKeys & 0x04) == 0x04; //SHIFT pressed bKeySell = (mKeys & 0x08) == 0x08; //CTRL pressed 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; //... Rest of the code...
取决于您是按 SHIFT 买入,亦或是按 CTRL 卖出,系统依据正在创建的内容生成挂单的表示形式。 它将直接显示在图表上。 在 Chart Trade 中捕获止盈或止损的数值,然后您可将表述移动到下订单的位置,之后,单击鼠标左键,告诉系统必须在那里下订单。 一旦鼠标再次移动,则表述订单的标签就会被删除,而订单指示将予以保留。
到目前为止,没有什么特别的。 但是,如果您查看代码,您将看到以下内容:
// ... CHARTEVENT_MOUSE_MOVE code.... }else if ((!bMounting) && (bKeyBuy == bKeySell)) { if (bEClick) { bIsMove = false; m_TradeLine.SpotLight(); } MoveSelection(price, mKeys); } break; // ... The rest of the code....
如此,当我们不创建挂单,并释放键时,我们会将鼠标价格位置发送到可能选中的某个标签。 这是在高亮显示的行中完成的。 一旦我们点击左键,我们就结束转移,指标会被取消选择,这将改变当前局部变量的状态,bIsMove。 但我们来改变它。 该变量的状态仅由 DispatchMessage 函数内部的另一个事件进行修改。 该事件如下所示:
// ... Code .... case EV_MOVE: if (bIsMove) { m_TradeLine.SpotLight(); bIsMove = false; }else { m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE)); bIsMove = true; } break;
该段代码将更改 bIsMove 变量的状态,同时更改标签,从而示意它是否被选中。
那么,如果我们令该变量在整个类中可见,我们就可把幻影与真实标签分开,并且有可能只操纵真实标签或幻影 — 这取决于您觉得哪个更有趣。 但在这此,我们将更改真实标签,而幻影将显示交易服务器之所见。
以此方式,我们就不必过多地搅乱代码,只需调整一些细节即可。 一旦左键点击完成,且对象已被移动,则发送更改挂单或止损位的订单。
我们看看这在实践中是如何做到的。 首先,我们创建一个私密变量。
bool m_bIsMovingSelect;
这反映了我上面解释的内容。 但它需要初始化。
C_IndicatorTradeView() : m_bIsMovingSelect(false) {}
现在,我们转到DispatchMessage,并用它来替代 bIsMove。
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; // ... Internal code... switch (id) { case CHARTEVENT_MOUSE_MOVE: // ... Internal code... }else if ((!bMounting) && (bKeyBuy == bKeySell)) { if (bEClick) { m_bIsMovingSelect = false; m_TradeLine.SpotLight(); } MoveSelection(price, mKeys); } break; // ... Internal code... case CHARTEVENT_OBJECT_CLICK: if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev) { // ... Internal code.... case EV_MOVE: if (m_bIsMovingSelect) { m_TradeLine.SpotLight(); m_bIsMovingSelect = false; }else { m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE)); m_bIsMovingSelect = true; } break; } break; } }
这些修改都以高亮显示。 因此,现在整个类都知道我们何时依据用户选择的某个标签操作。 以这种方式,我们可以将幻影与真实标签分开,并拥有更准确的表示。
2.0.3. 只移动所要的材料
在之前两个主题中,我们创建并修复了系统,从而能用一个幻影指代标签。 现在我们需要移动组件,为此,我们将不得不做一些工作。 第一步是创建一个结构来存储我们所需的各种信息,从而避免函数之间的过多调用。 该结构如下图所示:
struct st00 { eIndicatorTrade it; bool bIsMovingSelect, bIsBuy; ulong ticket; double vol, pr, tp, sl; }m_InfoSelection;
高亮显示的片段以前就存在于代码当中,但现在它作为结构的一部分。 不用担心。 随着我们走得更远,结构为什么拥这些元素将变得更加清晰。
在此,我们研究已修改并需要解释的函数。 第一处是设置文本值。
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: value0 = value0 / Terminal.GetVolumeMinimal(); m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), value0, def_ColorVolumeEdit); if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), value0, def_IndicatorGhostColor); break; case IT_TAKE : case IT_STOP : finance = (value1 / Terminal.GetAdjustToTrade()) * value0; m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT), finance); if (!m_InfoSelection.bIsMovingSelect) m_EditInfo1.SetTextValue(MountName(ticket, it, EV_EDIT, true), finance, def_IndicatorGhostColor); break; } }
当我们移动某些东西时,我们不希望幻影跟随新数据变更,我们希望它保持原样,以便展示标签曾经的位置。 这可以通过添加高亮显示的点轻松做到。 因此,当标签自由移动时,幻影立定不动。 在此,我们并没有移动其本身,而是服务器上的数值指示。
后跟的移动代码如下所示:
void MoveSelection(double price) { double tp, sl; if (!m_InfoSelection.bIsMovingSelect) return; switch (m_InfoSelection.it) { case IT_TAKE: UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy); break; case IT_STOP: UpdateInfosIndicators(0, m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price, m_InfoSelection.vol, m_InfoSelection.bIsBuy); break; case IT_PENDING: tp = (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr); sl = (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr); UpdateInfosIndicators(0, m_InfoSelection.ticket, price, tp, sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy); break; } }
请注意高亮显示的部分。 它调整移动,如此系统便可正确指示数据。 这也许看起来有些奇怪,但 UpdateINfosIndicators 函数还有另一处修复。 如果我们现在不这样做,我们以后会遇到问题。 其它函数非常简单,无需解释。
void SetPriceSelection(double price) { bool isPending; if (!m_InfoSelection.bIsMovingSelect) return; isPending = OrderSelect(m_InfoSelection.ticket); m_InfoSelection.bIsMovingSelect = false; m_TradeLine.SpotLight(); switch (m_InfoSelection.it) { case IT_TAKE: if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, price, m_InfoSelection.sl); else ModifyPosition(m_InfoSelection.ticket, price, m_InfoSelection.sl); break; case IT_STOP: if (isPending) ModifyOrderPendent(m_InfoSelection.ticket, m_InfoSelection.pr, m_InfoSelection.tp, price); else ModifyPosition(m_InfoSelection.ticket, m_InfoSelection.tp, price); break; case IT_PENDING: ModifyOrderPendent(m_InfoSelection.ticket, price, (m_InfoSelection.tp == 0 ? 0 : price + m_InfoSelection.tp - m_InfoSelection.pr), (m_InfoSelection.sl == 0 ? 0 : price + m_InfoSelection.sl - m_InfoSelection.pr)); break; } }
上述函数将通知服务器正在发生的事情,以及新数据是什么。 请注意,必须在高亮显示的行中选择一些指标,否则将不会向服务器发出请求。
最后推荐的函数如下所示:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price; bool bKeyBuy, bKeySell, bEClick; datetime dt; uint mKeys; char cRet; eIndicatorTrade it; eEventType ev; static bool bMounting = false, bIsDT = false; static double valueTp = 0, valueSl = 0, memLocal = 0; switch (id) { case CHARTEVENT_MOUSE_MOVE: Mouse.GetPositionDP(dt, price); mKeys = Mouse.GetButtonStatus(); bEClick = (mKeys & 0x01) == 0x01; //Left mouse click bKeyBuy = (mKeys & 0x04) == 0x04; //Pressed SHIFT bKeySell = (mKeys & 0x08) == 0x08; //Pressed CTRL if (bKeyBuy != bKeySell) { if (!bMounting) { Mouse.Hide(); bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl); valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol); valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol); m_TradeLine.SpotLight(MountName(def_IndicatorTicket0, IT_PENDING, EV_LINE)); m_InfoSelection.it = IT_PENDING; m_InfoSelection.ticket = def_IndicatorTicket0; m_InfoSelection.bIsMovingSelect = true; m_InfoSelection.pr = price; bMounting = true; } m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp)); m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl); m_InfoSelection.bIsBuy = bKeyBuy; MoveSelection(price); if ((bEClick) && (memLocal == 0)) { MoveSelection(0); m_InfoSelection.bIsMovingSelect = false; CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price, price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT); } }else if (bMounting) { MoveSelection(0); m_InfoSelection.bIsMovingSelect = false; Mouse.Show(); memLocal = 0; bMounting = false; }else if ((!bMounting) && (bKeyBuy == bKeySell)) { if (bEClick) SetPriceSelection(price); else MoveSelection(price); } break; case CHARTEVENT_OBJECT_DELETE: if (GetIndicatorInfos(sparam, ticket, price, it, ev)) { CreateIndicatorTrade(ticket, it); GetInfosTradeServer(ticket); m_InfoSelection.bIsMovingSelect = false; UpdateInfosIndicators(0, ticket, m_InfoSelection.pr, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy); } break; case CHARTEVENT_CHART_CHANGE: ChartSetInteger(ChartID(), CHART_SHOW_OBJECT_DESCR, false); ReDrawAllsIndicator(); break; case CHARTEVENT_OBJECT_CLICK: if (GetIndicatorInfos(sparam, ticket, price, it, ev)) switch (ev) { case EV_CLOSE: 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_InfoSelection.bIsMovingSelect = true; SetPriceSelection(0); break; } break; case EV_MOVE: if (m_InfoSelection.bIsMovingSelect) { m_TradeLine.SpotLight(); m_InfoSelection.bIsMovingSelect = false; }else { m_InfoSelection.ticket = ticket; m_InfoSelection.it = it; if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0)) m_TradeLine.SpotLight(MountName(ticket, it, EV_LINE)); } break; } break; } }
关于该函数,有些事情需要注意,即要关注与以前版本的区别:我们在此重用了更多的代码。 故此,若在其它地方,我们的代码中有错误,我们就可以快速察觉它,并予以修复。 以前,该函数代码有点不稳定,其它部分存在一些修复和缺陷。 其中一个要点就是,当我们下挂单时,有一个单独的系统用于移动指标;现在我们有一个单一的系统,与在图表上下订单的系统相同。 它也能用于移动对象,即现在相同的 CHARTEVENT_MOUSE_MOVE 事件既用于下挂单,又可用于移动对象。 它也许看起来像小事一桩,但它令代码的任何更改均可见,如果我们遇到问题,它就会尽可能长地显示出来,如同我们所用的鼠标事件。
结束语
请观看下面的视频,以便理清思路,了解所做的更改会发生什么。 您将看到,现在我们只需稍多细节即可令 EA 在订单系统方面全部完成。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10563
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.


你好,何塞、
如何在 US30 指数或 S&P500 指数上使用此 EA?当我按住 Shift 键并左键单击时,会出现10015 错误?如何解决?
问候
Florian
问候
Florian
我不知道到底是怎么回事。您报告的错误表明价格计算失败。但所有计算都是在 C_IndicatorTradeView 类中的 DispatchMessage 过程中进行的。该计算会考虑到服务器提供的数据,这些数据来自 C_Terminal 类。
也许您使用了错误的合约。我不知道你提到的这些资产是否有到期日。如果是这种情况,您必须在 C_Terminal 类中的 CurrentSymbol 过程中添加正确的合约搜索规则。这样,应用程序就能完成所有计算,并正确地将订单发送到交易服务器。
您可以使用更新的代码。因为这篇文章已经完全过时。请参阅我最近的文章,因为我更注重对代码的解释,所以代码更好理解也更简单。这样您就可以根据自己的需要调整系统。