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

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

(3/3)·从对象规划到图表交互,弄懂MetaTrader 5里每笔订单背后那15个对象的来龙去脉

偏理论 第 3/3 篇
很多人以为图表上拖一下就能下单,却不知道一个OCO仓位在后台要占用15个对象。对象连接关系没理清,可视化订单系统不是卡顿就是触发错乱,决策反而更慢。

◍ 挂单与止损对象的图形着色和清理

在 MT5 自定义指标里给交易对象画图形时,先按类型分颜色:止损单(IT_STOP)用珊瑚红配栗色,挂单(IT_PENDING)及缺省情况用金色配暗金菊色,这样肉眼扫图就能区分订单性质。 代码片段里用 m_TradeLine.Create 按 MountName 生成边界线,若 ticket 等于 def_IndicatorTicket0 还会调用 SpotLight 高亮挂单线;背景块 m_BackGround 的尺寸随类型变:止盈止损(IT_TAKE/IT_STOP)是 92×22,挂单是 110×22,多出的宽度给文字留空间。 删除时用宏 macroDelete 一口气清掉背景和线两个对象,再在 RemoveIndicatorTrade 里关掉 CHART_EVENT_OBJECT_DELETE 避免误触发。外汇和贵金属市场波动剧烈,这类图形对象只是辅助,实际成交风险依旧很高。 开 MT5 把这段塞进你的指标 EA,改 IT_PENDING 的 clrGold 为别的色值,就能验证挂单高亮是否按预期显示。

MQL5 / C++
case IT_STOP:
      cor1 = clrCoral;
      cor2 = clrMaroon;
      class="kw">break;
case IT_PENDING:
class="kw">default:
      cor1 = clrGold;
      cor2 = clrDarkGoldenrod;
      class="kw">break;
      }
      m_TradeLine.Create(MountName(ticket, it, EV_LINE), cor2);
      if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE));
      m_BackGround.Create(sz0 = MountName(ticket, it, EV_GROUND), cor1);
      class="kw">switch (it)
      {
      case IT_TAKE:
      case IT_STOP:
            m_BackGround.Size(sz0, class="num">92, class="num">22);
            class="kw">break;
      case IT_PENDING:
            m_BackGround.Size(sz0, class="num">110, class="num">22);
            class="kw">break;
      }
}
class="macro">#define macroDelete(A)      {                                                                           \
            ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));                          \
            ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));                             \
            }

class="kw">inline class="type">void RemoveIndicatorTrade(class="type">class="kw">ulong ticket, eIndicatorTrade it = IT_NULL)
            {
                  ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, class="kw">false);

「挂单与指示器的清理和坐标轴重定位」

这段逻辑处理交易对象删除时的图形清理:若当前指示器指针有效且不是挂单占位(IT_PENDING)或结果占位(IT_RESULT),就直接删掉它;否则把挂单、结果、止盈(IT_TAKE)、止损(IT_STOP)四类占位全部 macroDelete 清掉,再打开图表的对象删除事件开关。 清理完之后用宏重设坐标轴。macroSetAxleY(A) 把名为 MountName(ticket, A, EV_GROUND) 的背景对象和 EV_LINE 类型的交易线同时移到纵坐标 y;macroSetAxleX(A, B) 则把同类两对象的横坐标挪到 B。这样订单图形和背景锚点始终同步,不会在拖拽时脱节。 PositionAxlePrice 函数接收 price、ticket、交易类型 it 以及止盈止损点数、杠杆、买卖方向,是上述宏的实际调用入口。外汇与贵金属杠杆波动剧烈,重绘坐标时若 ticket 对应的挂单已被部分成交,可能触发 IT_PENDING 残留,建议开 MT5 在策略测试器里用 EURUSD 1 分钟数据跑一遍删除流程验证。

MQL5 / C++
if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it)
else
{
   macroDelete(IT_PENDING);
   macroDelete(IT_RESULT);
   macroDelete(IT_TAKE);
   macroDelete(IT_STOP);
}
ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
class="macro">#undef macroDelete
class="macro">#define macroSetAxleY(A)    {   \
   m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \
   m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);    \
}
class="macro">#define macroSetAxleX(A, B)  {   \
   m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \
   m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);    \
}
class="kw">inline class="type">void PositionAxlePrice(class="type">class="kw">double price, class="type">class="kw">ulong ticket, eIndicatorTrade it, class="type">int FinanceTake, class="type">int FinanceStop, class="type">int Leverange, class="type">bool isBuy)
{
   class="type">class="kw">double ad;

挂单止盈止损的画布坐标换算

在 MT5 自定义指标或 EA 面板里,把挂单的触发价、止盈价、止损价映射到屏幕坐标,靠的是 ChartTimePriceToXY 这个函数。它接收图表 ID、时间(这里传 0 表示用当前最右可见 bar)、价格,以及两个引用型 int 变量 x、y,调用后把像素位置写回这两个变量。 下面这段逻辑只处理挂单(IT_PENDING)。先判断杠杆 Leverange 是否为 0,是就直接 return,避免除零。接着用 Terminal.GetAdjustToTrade() 除以(杠杆 × 最小交易量)算出每最小手的价位调整单位 ad,买挂单和卖挂单通过 isBuy 三元表达式翻转方向。 止盈坐标在基准 X 上偏移 +120 像素,止损再偏移 +220 像素,Y 轴则分别用 Take / Stop 对应的价格走一次 ChartTimePriceToXY 重算。这样挂单线、TP 线、SL 线在面板里就能横向错开,不挤在同一列。外汇与贵金属杠杆品种波动剧烈,坐标算错可能把止损画到反方向,实盘前务必在策略测试器里用 1 小时图肉眼核对一次。

MQL5 / C++
class="type">int x, y;

ChartTimePriceToXY(Terminal.Get_ID(), class="num">0, class="num">0, price, x, y);
macroSetAxleY(it);
macroSetAxleX(it, m_PositionMinimalAlxeX);
if (Leverange == class="num">0) class="kw">return;
if (it == IT_PENDING)
{
   ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal());
   ChartTimePriceToXY(Terminal.Get_ID(), class="num">0, class="num">0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y);
   macroSetAxleY(IT_TAKE);
   macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + class="num">120);
   ChartTimePriceToXY(Terminal.Get_ID(), class="num">0, class="num">0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y);
   macroSetAxleY(IT_STOP);
   macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + class="num">220);
}
class="macro">#undef macroSetAxleX
class="macro">#undef macroSetAxleY

◍ 一点提醒

这套订单系统目前还差最后一块拼图:把完整订单直接画到图表上。但这一步牵动代码其它位置的改动,幅度不小,所以留到后续篇章单独拆。 若你在改系统时报错,就回退一步重来,直到跑出自己顺手的状态。外汇与贵金属杠杆高、滑点猛,任何自定义 EA 都先在策略测试器用历史数据验证再上真仓。 下一篇会给出带图表订单显示的系统雏形,表面看改动不大,底层逻辑却要动不少。到时照着 ZIP 里的 EA_-_Sistema_de_ordens_k_III_1.zip(约 11.7 MB)比对即可。

把对象拓扑交给小布盯盘
这些对象数量与连接状态的诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到当前图表的对象负载,你只需专注价位和止损布局。

常见问题

因为每类指标基础包含5个对象,OCO涉及订单与仓位两侧共3组,平台需分别维护显示、拖拽与触发连接,合计15个对象由系统同步。
挂单对象侧重触发前价位与方向,持仓对象额外携带浮动盈亏与移动止损连接,比例与交互形态不同但底层都是5对象一组的拓扑。
单品种下通常不是瓶颈,但多品种多OCO并行时对象总数会快速累积,了解硬件需求有助于避免在低配环境出现延迟。
目前小布盯盘提供对象负载与连接状态的可视化诊断,具体下单交互面板仍需在MT5内按本文逻辑自行实现或加载EA。
定位方式决定了事件响应与坐标映射写法,选错会导致拖拽计算和触发判定要重写,应在设计指标时就固定。