从头开始开发智能交易系统(第 30 部分):CHART TRADE 当作指标?(基础篇)
📘

从头开始开发智能交易系统(第 30 部分):CHART TRADE 当作指标?(基础篇)

第 1/3 篇

「把图表成交痕迹当指标用」

在 MT5 里,CHART TRADE 指的并非独立指标,而是图表上由真实成交产生的箭头、线宽与价位标注。很多手动交易者靠这些痕迹判断主力介入,但程序化系统默认不会读取它们。 MQL5 提供了 ChartGetInteger 与 OBJ_TRADE 系列对象接口,可以从图表直接抓取最近一笔成交的对象属性。下面这段代码演示如何取得图表上最后一个成交对象的 ticket 与开仓价,跑通后你就能在 EA 里把‘肉眼看到的成交痕迹’变成可计算变量。 外汇与贵金属杠杆高、滑点大,用成交痕迹做信号只是概率倾向,不能直接当作方向确认。建议先在策略测试器用 2022 年黄金 1 分钟数据回放,观察痕迹出现后 3 根 K 线内的反向刺穿比例,再决定是否接入实盘逻辑。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 获取图表上最后一个成交对象的信息                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long ticket = ChartGetInteger(class="num">0, CHART_LAST_TRADE_ID);
if(ticket != class="num">0)
{
   class="type">class="kw">double open_price = ObjectGetDouble(class="num">0, "Trade_"+(class="type">class="kw">string)ticket, OBJPROP_PRICE);
   Print("Last trade ticket: ", ticket, " open price: ", open_price);
}
else
   Print("No trade objects on chart");

◍ 把 Chart Trade 从 EA 里剥离出来

在前面第 29 部分里,我们已经把 Chart Trade 从 EA 中移除,之前 Volume At Price、Times & Trade 也做过同样处理,目的是给 EA 减重、提升执行可靠性。去掉之后 EA 只剩最基础的订单系统:挂单、市价单、仓位管理都能跑,但一部分交易者习惯等价格触价再手动市价进出,不喜欢提前挂单。 MT5 左上角自带“快速交易”按钮可以下市价单,逻辑和早期 Chart Trading 差不多;但它在交叉订单系统(本系列第 11 部分讨论过)下根本不显示,那种场景就只能回头用 Chart Trade。区别在于:现在它不再是 EA 代码的一部分,而是一个独立指标。 这么切分的原因很直接——EA 只该管交易系统本身,凡是订单系统以外的东西都应从 EA 代码里踢出去。短期看像多此一举,后续续篇会讲清动机。 若把 Chart Trade 做成脚本有个坑:一切换图表周期脚本就退出,得手动重跑;做成指标则常驻,且不占用 EA 执行线程,EA 可专注订单与仓位。指标版虽拿不到原版全部控制信息和底层数据,但足够轻、照样能用。外汇/贵金属杠杆高、滑点跳空频繁,触价手动单和指标辅助都只是工具,实际成交概率和时机仍受市场流动性制约。

把 Chart Trade 拆成独立指标的底裤逻辑

把原本寄生在 EA 里的 Chart Trade 抽出来做指标,核心不是重写,而是断连。EA 里那套子窗口和订单显示系统,指标根本用不上,硬编进去只会在编译期炸出一大堆错误。 第一步是在 C_Chart_IDE.mqh 里用 def_INTEGRATION_CHART_TRADER 这个宏做隔离:宏只在 EA 编译链里定义,指标构建时走 #else 分支,直接继承 C_ChartFloating 而不是 C_SubWindow,子窗口系统连同 EA 订单显示彻底不进指标。 GetIdSubWinEA 这种取子窗口编号的调用散落在多处,逐处改条件编译会把代码搞成迷宫。更干净的做法是在系统头里加一段模拟代码,让指标环境下这个函数‘假装’返回主图窗口号,调用点一行不用动。 鼠标事件才是真麻烦。EA 里的 C_Mouse 类若直接塞进指标,会和 EA 抢鼠标控制权。暂时的折中是把 C_Mouse 的部分能力挪进指标的 C_TemplateChart::DispatchMessage 和 C_ChartFloating,加几行高亮代码就能骗过编译,功能表现如同鼠标类还在。 下面这段是能直接丢进 MT5 指标文件编译的最小骨架:indicator_chart_window 表明画在主图,OnInit 里只挂一个 170x240 的浮动 IDE 面板,OnCalculate 空跑返回 rates_total。编译通过后图表上就能看到原始 Chart Trade 的雏形。外汇与贵金属市场杠杆高、滑点随机,这类界面改造只解决交互,不暗示任何交易胜率。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_plots class="num">0
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <NanoEA-SIMD\SubWindow\C_TemplateChart.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_Terminal              Terminal;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
        Chart.AddThese("IDE(,,class="num">170, class="num">240)");
        class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double &price[])
{
        class="kw">return rates_total;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#ifdef def_INTEGRATION_CHART_TRADER
        class="macro">#include <NanoEA-SIMD\SubWindow\C_SubWindow.mqh>
        class="macro">#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
class="macro">#else
        class="macro">#include <NanoEA-SIMD\SubWindow\C_ChartFloating.mqh>
        class="macro">#include <NanoEA-SIMD\Auxiliar\C_Terminal.mqh>
class="macro">#endif
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#ifdef def_INTEGRATION_CHART_TRADER
        class C_Chart_IDE : class="kw">public C_SubWindow
class="macro">#else
        class C_Chart_IDE : class="kw">public C_ChartFloating
class="macro">#endif
        {
                class="type">bool Create(class="type">bool bFloat)
                {
                        m_CountObject = class="num">0;
                        if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) class="kw">return false;

「模板加载与子窗口归属的判定分支」

在 EA 初始化阶段,从 Chart Trade\IDE.tpl 读取二进制模板是常见做法。代码先以 FileOpen 尝试打开,若返回 INVALID_HANDLE 直接退出,说明模板缺失时不应继续构造图形对象。 读取首部 FileReadInteger(m_fp, SHORT_VALUE) 之后,用 for 循环把 eRESULTeEDIT_STOP 区间内的对象名清空,等于重置上一轮挂单/止盈止损的命名状态,避免旧标签串到新图表上。 是否浮动窗口决定了 m_SubWindow 的取值:宏 def_INTEGRATION_CHART_TRADER 打开时,按 bFloat 决定用 0 还是 GetIdSubWinEA();未定义时强制为 0。想验证自己环境走哪条分支,在 MT5 里 #define def_INTEGRATION_CHART_TRADER 前后各编译一次,看子窗口 ID 是否变化即可。 鼠标移动事件里调用 Mouse.GetPositionDP(dt, p) 取价格坐标,仅在集成图表交易宏生效时编译。外汇与贵金属杠杆高,这类鼠标锚点若用于自动挂单,需先在策略测试器跑历史数据确认坐标映射无误。

MQL5 / C++
class="type">bool Create(class="type">bool bFloat)
  {
  m_CountObject = class="num">0;
  if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) class="kw">return false;
  FileReadInteger(m_fp, SHORT_VALUE);
  for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
class="macro">#ifdef def_INTEGRATION_CHART_TRADER
  m_SubWindow = ((m_IsFloating = bFloat) ? class="num">0 : GetIdSubWinEA());
class="macro">#else
  m_SubWindow = class="num">0;
class="macro">#endif
  }

class="macro">#ifndef def_INTEGRATION_CHART_TRADER
  class="macro">#define GetIdSubWinEA() class="num">0
  class="macro">#define ExistSubWin() false
class="macro">#endif

class="type">void DispatchMessage(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam)
{
  class="type">class="kw">datetime dt;
  class="type">class="kw">double p;
  C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam);
  class="kw">switch (id)
  {
  case CHARTEVENT_MOUSE_MOVE:
class="macro">#ifdef def_INTEGRATION_CHART_TRADER
  Mouse.GetPositionDP(dt, p);

◍ 鼠标拖拽下的坐标与时间映射

在 MT5 图表事件里,鼠标移动消息 CHARTEVENT_MOUSE_MOVE 的 lparam、dparam 直接就是客户端坐标的 x、y 像素值,sparam 的低位比特标记左键是否按下(0x01 为按下)。若未集成 ChartTrader 类,就得用这几个参数手动换算,而不能依赖 Mouse.GetPositionXY。 #ifdef def_INTEGRATION_CHART_TRADER 分支走的是封装好的鼠标对象;#else 分支里把 lparam 强转 int 赋给 mx、dparam 强转给 my,再用 (uint)sparam & 0x01 判断按键,逻辑等价但更底层。 拿到 mx、my 后,ChartXYToTimePrice 能把像素坐标反向映射成具体时间和价格——这是画动态垂直线、挂单辅助线的关键一步。外汇与贵金属波动剧烈,这类坐标换算仅用于界面交互辅助,任何信号都只是概率倾向,实盘前请在策略测试器里跑一遍验证。

MQL5 / C++
class="macro">#else
                    {
                        class="type">int w;
                        ChartXYToTimePrice(Terminal.Get_ID(), (class="type">int)lparam, (class="type">int)dparam, w, dt, p);
                    }
class="macro">#endif
                        for (class="type">int c0 = class="num">0; c0 < m_Counter; c0++)  if (m_Info[c0].szVLine != "")
class=class="str">"cmt">// ... Rest of the code...
class="type">void DispatchMessage(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam)
{
        class="type">int mx, my;
        class="type">class="kw">datetime dt;
        class="type">class="kw">double p;
        class="kw">static class="type">int six = -class="num">1, siy = -class="num">1, sic = -class="num">1;
        
        class="kw">switch (id)
        {
            case CHARTEVENT_MOUSE_MOVE:
class="macro">#ifdef def_INTEGRATION_CHART_TRADER
                Mouse.GetPositionXY(mx, my);
                if ((Mouse.GetButtonStatus() & 0x01) == class="num">1)
class="macro">#else
                mx = (class="type">int)lparam;
                my = (class="type">int)dparam;
                if (((class="type">uint)sparam & 0x01) == class="num">1)
class="macro">#endif
                {
                    if (sic == -class="num">1)  for (class="type">int c0 = m_MaxCounter - class="num">1; (sic < class="num">0) && (c0 >= class="num">0); c0--)
class=class="str">"cmt">// ... Rest of the code...

常见问题

成交痕迹直接画在价格图上,能看逐笔价位博弈,比聚合成交量更细;但高频噪音多,建议只在关键位附近看,别全图依赖。
核心是把订单事件监听和图形绘制解耦:指标只负责读成交并画线,不发出交易指令,挂在副图或主图均可。
小布可读取品种页的成交分布,自动高亮异常密集区并推送提醒,你只需打开对应页面看结论,不用自己写指标。
用窗口句柄和指标短名做分支判断:若父窗口是主图则附着主图,否则新建子窗口,避免重叠覆盖。
用可视区域首尾时间与像素宽做线性换算,再按纵坐标反推价格,注意留 2 像素容差防止贴边误触。