从头开始开发智能交易系统(第 11 部分):交叉订单系统·综合运用
(3/3)· 当迷你美元旧合约到期,图表只剩历史却还能下单,这套代码规则帮你彻底甩掉手动换模板
挂单与持仓的同名事件分叉处理
这段逻辑把「挂单」和「已成交持仓」用 PositionSelectByTicket 做了一次分流:能选中说明单子已在场上,走持仓分支;选不中则走早前的挂单修改分支。 在挂单分支里,HL_TAKE 事件触发 ModifyOrderPendent,把挂单的 open 价和原 SL 重新提交,TP 传 0 表示不动。注意这里改的是待成交单,不是市价单。 持仓分支用 switch(hl) 接三个动作:HL_PRICE 直接 ClosePosition 平掉;HL_STOP 调 ModifyPosition 把 TP 置 0、SL 保留;HL_TAKE 则反过来把 SL 置 0、TP 保留。 开 MT5 把这段塞进你的 EA 事件回调,用日志打印 ticket 和分支走向,能立刻看清同名信号在挂单期与持仓期是不是走了两条路。外汇与贵金属杠杆高,改单逻辑跑错分支可能瞬间反向加仓,先用模拟盘验证。
case HL_TAKE: ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), class="num">0, OrderGetDouble(ORDER_SL)); class="kw">break; } }else if (PositionSelectByTicket(ticket)) { class="kw">switch (hl) { case HL_PRICE: ClosePosition(ticket); class="kw">break; case HL_STOP: ModifyPosition(ticket, OrderGetDouble(ORDER_TP), class="num">0); class="kw">break; case HL_TAKE: ModifyPosition(ticket, class="num">0, OrderGetDouble(ORDER_SL)); class="kw">break;
◍ 拖线改单的事件分支怎么写
在 MT5 图表事件回调里,OBJECT_DRAG 是手动拖拽水平线后触发的关键信号。下面这段处理挂单价格与止损线联动的逻辑,直接决定了你拖一根线,账户里的 pending order 会不会跟着变。 [CODE]case CHARTEVENT_OBJECT_DRAG: if (GetNewInfosOrder(sparam, ticket, price, hl)) { price = AdjustPrice(price); if (OrderSelect(ticket)) switch(hl) { case HL_PRICE: pp = price - OrderGetDouble(ORDER_PRICE_OPEN); pt = OrderGetDouble(ORDER_TP); ps = OrderGetDouble(ORDER_SL); if (!ModifyOrderPendent(ticket, price, (pt > 0 ? pt + pp : 0), (ps > 0 ? ps + pp : 0))) UpdatePosition(); break; case HL_STOP: if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition();[/CODE] 逐行拆一下:GetNewInfosOrder 从拖拽对象名里解析出 ticket、price、hl 类型;AdjustPrice 按品种点位取整,避免报价小数位不匹配被拒。HL_PRICE 分支算出新开仓价与旧价的差 pp,把 TP、SL 同步平移 pp,保持盈亏空间不变;HL_STOP 分支只改止损价,开仓价和 TP 不动。 外汇和贵金属杠杆高,拖线改单若未校验 AdjustPrice 返回值,可能在 XAUUSD 这种 3 位小数品种上静默失败。开 MT5 把这段塞进你的 EA 图表事件函数,拖一根 HLine 看 ticket 对应的挂单是否实时跟随。
case CHARTEVENT_OBJECT_DRAG: if (GetNewInfosOrder(sparam, ticket, price, hl)) { price = AdjustPrice(price); if (OrderSelect(ticket)) class="kw">switch(hl) { case HL_PRICE: pp = price - OrderGetDouble(ORDER_PRICE_OPEN); pt = OrderGetDouble(ORDER_TP); ps = OrderGetDouble(ORDER_SL); if (!ModifyOrderPendent(ticket, price, (pt > class="num">0 ? pt + pp : class="num">0), (ps > class="num">0 ? ps + pp : class="num">0))) UpdatePosition(); class="kw">break; case HL_STOP: if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition();
「挂单与持仓的尾随改单分支」
这段逻辑承接上一节的水平线事件,按挂单(Order)和持仓(Position)分别走 switch 分支。HL_TAKE 命中挂单时,调用 ModifyOrderPendent 用原开仓价、传入 price 与新止损重挂;若返回失败则 UpdatePosition 兜底刷新持仓状态。 持仓侧用 PositionSelectByTicket 锁定票据后,HL_PRICE 直接 UpdatePosition 同步最新价;HL_STOP 调 ModifyPosition 只改止损位为 price、止盈保留 POSITION_TP;HL_TAKE 则反向只改止盈、止损沿用 POSITION_SL。 代码里所有 break 都紧跟在改单函数后,避免穿透到下一个 case。实盘跑 MT5 时,把 def_NameHLineTrade 对应的水平线拖到图表上,就能触发这几条分支,观察订单注释是否随线移动——外汇与贵金属杠杆高,改单失效可能瞬间放大滑点风险。
class="kw">break; case HL_TAKE: if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), price, OrderGetDouble(ORDER_SL))) UpdatePosition(); class="kw">break; } if (PositionSelectByTicket(ticket)) class="kw">switch (hl) { case HL_PRICE: UpdatePosition(); class="kw">break; case HL_STOP: ModifyPosition(ticket, PositionGetDouble(POSITION_TP), price); class="kw">break; case HL_TAKE: ModifyPosition(ticket, price, PositionGetDouble(POSITION_SL)); class="kw">break; } }; class="kw">break; } } class="kw">inline class="type">void Select(class="kw">const class="type">class="kw">string &sparam) { class="type">int i0 = StringLen(def_NameHLineTrade);
水平线被选中的视觉反馈处理
这段逻辑解决一个很实际的问题:图表上多条交易水平线共存时,怎么让用户一眼看出当前选中的是哪一条。核心做法是先清掉旧选中态,再给新选中的线加粗。 代码先判断 m_SelectObj 是否非空,若之前有被选中的对象,就把它取消选中(OBJPROP_SELECTED 设为 false)并把线宽恢复为 1。随后把 m_SelectObj 清空,准备接收新的选中对象。 接着用 StringSubstr 比对事件参数前缀是否为 def_NameHLineTrade,确认是自家脚本画的交易线。若该行允许选中(OBJPROP_SELECTABLE 为真),则将其 OBJPROP_SELECTED 设为 true、线宽改为 2,并把对象名写回 m_SelectObj。线宽从 1 到 2 的变化就是肉眼可辨的选中提示。 在 MT5 里跑这段时,把 def_NameHLineTrade 换成你画线用的前缀,就能在 OnChartEvent 里直接复用。外汇与贵金属图表波动快,这类视觉反馈能降低误删或误改水平线的概率,但交易本身仍属高风险,线宽变化不代表任何方向判断。
if (m_SelectObj != "") { ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, class="kw">false); ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_WIDTH, class="num">1); } m_SelectObj = ""; if (StringSubstr(sparam, class="num">0, i0) == def_NameHLineTrade) { if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE)) { ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true); ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_WIDTH, class="num">2); m_SelectObj = sparam; }; }
◍ 画得少,看得清
交叉订单系统跑在 MT5 上,平台本身几乎不给错误处理兜底,这意味着任何报价瞬间的异常都要靠你自己迅速干预,否则外汇、贵金属这类高杠杆品种可能瞬间放大亏损。 每笔订单都在每次即时报价 tick 上做分析,同时挂多单时,机器能扛的最大订单数、允许的最大点差、开单波动阈值,都得在演示账户里用不同行情压测出来,而不是凭代码看起来干净就直接上实盘。 下面这段符号年份自增的循环,就是前文读者争论过的点:i1 从当前年减 2000 开始,一直递增到合约到期日大于系统日期才 break,所以 12 月遇到跨年符号也会自动接上,不用手动改。 for (int i0 = 0, i1 = mdt1.year - 2000;;) // i0 遍历月份字符,i1 初始为年份偏移(如2022年则为22) { m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1); // 拼出短符号名,例如前缀+月字母+年份尾号 m_Infos.szFullSymbol = StringFormat("%s%s%d", sz2, StringSubstr(sz1, i0, 1), i1); // 拼出完整符号名 if (i0 < StringLen(sz1)) i0++; else // 月字符未遍历完则前进,否则重置并进位年份 { i0 = 0; i1++; // 年份尾号递增,跨年时自动切换新符号 } if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break; // 合约未过期则退出循环,锁定有效符号 } 真要验证,就开个 demo 把这段塞进 EA,打印 szFullSymbol 看跨年那一两根 K 线是不是无缝换合约。系统不神话,跑通场景再谈真金白银。
for (class="type">int i0 = class="num">0, i1 = mdt1.year - class="num">2000;;) { m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, class="num">1), i1); m_Infos.szFullSymbol = StringFormat("%s%s%d", sz2, StringSubstr(sz1, i0, class="num">1), i1); if (i0 < StringLen(sz1)) i0++; else { i0 = class="num">0; i1++; } if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) class="kw">break; }