从头开始开发智能交易系统(第 11 部分):交叉订单系统·综合运用
🔁

从头开始开发智能交易系统(第 11 部分):交叉订单系统·综合运用

(3/3)· 当迷你美元旧合约到期,图表只剩历史却还能下单,这套代码规则帮你彻底甩掉手动换模板

含代码示例 第 3/3 篇
很多人以为历史合约图表上能发单只是显示 bug,其实订单根本没落到当前可交易标的,回测和实盘会悄无声息地错位。期货合约一换月,手动导模板续分析的习惯迟早漏掉关键支撑阻力。把旧合约历史和新合约交易混在同一画面,是多数期货 EA 翻车的隐形坑。

挂单与持仓的同名事件分叉处理

这段逻辑把「挂单」和「已成交持仓」用 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 和分支走向,能立刻看清同名信号在挂单期与持仓期是不是走了两条路。外汇与贵金属杠杆高,改单逻辑跑错分支可能瞬间反向加仓,先用模拟盘验证。

MQL5 / C++
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 对应的挂单是否实时跟随。

MQL5 / C++
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 对应的水平线拖到图表上,就能触发这几条分支,观察订单注释是否随线移动——外汇与贵金属杠杆高,改单失效可能瞬间放大滑点风险。

MQL5 / C++
                  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 里直接复用。外汇与贵金属图表波动快,这类视觉反馈能降低误删或误改水平线的概率,但交易本身仍属高风险,线宽变化不代表任何方向判断。

MQL5 / C++
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 线是不是无缝换合约。系统不神话,跑通场景再谈真金白银。

MQL5 / C++
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;
}
把换月判断交给小布盯盘
小布盯盘的 AIGC 已内置合约到期识别,打开 WDO 或 WIN 品种页就能看到新旧合约衔接提示,你只需专注规则适配而非盯日历。

常见问题

历史图表关联的是已过期合约,MT5 仍允许向该品种发指令,但当前图形上下文不渲染挂单,导致视觉与成交分离,极易误判持仓状态。
目前小布盯盘提供合约换月诊断和品种页提示,不自动产出完整 EA 源码;你可依据本文规则手写适配,再用小布核对到期逻辑。
重点读取合约规格里的到期日、交割月代码及交易时段,结合 SYMBOL_EXPIRATION_TIME 做动态替换,避免硬编月份字符串。
逻辑通用,只要正确解析目标交易所的合约规格与历史数据映射,代码层适配后 EA 便能自动切换,不限于贵金属或指数。
用交叉订单系统在后台引用过期合约时间序列,将关键价位以指标线重绘至新合约图,省去导出模板的人工步骤。