TradeObjects: 基于 MetaTrader 图形对象的自动化交易(基础篇)
用图形对象驱动 EA 的来龙去脉
在 MT5 图表上画一条支撑线或斐波那契网格,再挂个 EA 让它自动在那儿进场、跟踪、平仓——这类把几何图形当交易触发器的玩法,早就是老交易员的标配。官方代码库和市场上都能翻到类似小程序,大多逻辑很简单:图表上加个对象,程序读属性、择时开仓。 但现实有点骨感。代码库里的开源版本往往功能砍到最少,且跟不上 MQL 语言和平台更新,跑着跑着就和新版终端不兼容;商用版又动辄收费劝退。本文要做的,是折中出一套轻量但够用的开源工具,同时兼容 MT4 与 MT5,方便你直接改源码。 这里有个硬限制得先说清:MT5 的可视化测试器目前不允许在回测中交互式增删改图表对象,而本工具恰恰要靠实时编辑对象属性来触发交易。所以练手和验证历史对象管理技能,只能靠 MT4 测试器。外汇与贵金属杠杆高、滑点跳空频繁,这类图形化自动交易实战前务必先在 MT4 可视化回测里跑熟。
「图形对象驱动的交易系统边界设定」
做图形对象自动交易,第一性原则是别把界面搞重。我们直接用 MT5 标准图形对象和属性,不堆自定义面板和配置树——实战里那种塞满开关的强劲系统,常因开发成本太高、单个模式复用率低,把优势全耗光。 对象类型只挑高频且语义直观的五种:趋势线、水平线、垂直线、等距通道、菲波纳奇回撤。选它们还有一个硬约束——MetaTrader 5 里的新对象类型不加,否则会破 MT4 兼容;用户自己按本项目已实现的对象处理逻辑加类型很容易。 这五类对象在图表二维空间里画出的都是逻辑边界。价格碰线后的交叉或回滚就是信号源,典型解释就四种:支撑阻力突破/回滚、历史价位突破/回落、触到挂的止损止盈价、走到某根垂直线代表的时间点。 系统该响应的动作必须全覆盖:发警报/推送/邮件通知,市价进场,挂 buy stop、sell stop、buy limit、sell limit,以及全平或半平(含止损止盈触发)。外汇和贵金属波动剧烈、杠杆高风险极大,这些动作触发后是否成交还受滑点和流动性影响。
◍ 用默认对象搭一套无面板的交易标记协议
很多同类产品喜欢堆面板、对话框、拖放交互,本项目反其道而行,只借用 MT5 自带的图表对象做标记,不画任何自定义 GUI。每条自动交易用的线,都要靠命名前缀和属性组合来承载语义,否则 EA 会把终端自己生成的线(比如平仓标记)也误判成信号源。 具体约定五条:前缀区分自有对象;线样式选操作类型,例如 STYLE_DASH 放挂单、STYLE_SOLID 做穿越即时入场;颜色定方向,蓝买红卖灰中性;线相对市价位置加颜色定挂单种类,蓝线在价上即 buy stop、在价下即 buy limit;Description 字段塞手数和有效期等可选参数,默认值在 EA 输入项里给好,降低手工设置量。 带止损止盈至少要三线对象。等距通道用两点定信号、第三点定距;菲波纳奇网格默认取 38.2% 止损、61.8% 入场、161.8% 止盈。激活线用 OBJPROP_BACK 置背景并降亮度,比如蓝变深蓝。线宽大于 1 时激活只把宽度减 1,最多重复 5 次事件,对应 MT5 线宽 1~5 的限制。 价格常在线上下来回蹭,几根 K 内假交叉无数。EA 引入两个输入:热点区域点数与持续时长,只有价格退离线这么远才确认交叉;同一条宽线两次事件的最小间隔存进 OBJPROP_ZORDER(long 型,直接写 datetime),几乎不影响显示层级。 实操只需打开对象属性,加前缀、填 Description(手数/有效期/挂单名)、选颜色样式宽度。例如一条蓝色短划线设 0.02 手、24 根小时柱有效,就是个 buy limit。图表上系统会列出托管订单并注释类型、描述与状态,外汇贵金属波动剧烈,这类标记仅降低漏看概率,不替代风控。
把图表对象变成交易指令的执行骨架
EA 本体用 TradeObjects 类承载,对外只暴露构造、析构和几个标准事件方法;真正的下单逻辑塞在隐藏的 Expert 类里,构造时 new、析构时 delete,TradeObjects 把大部分活儿委托出去。图表上你手动画的线,只要前缀、颜色、样式对得上,就会被 detectLines 扫进内部数组,之后定时器里的 processLine 循环盯每根线是否触发。 内部数组是「对象名 + 状态」的结构。startRefresh 先把所有状态位清 0,循环里命中的对象经 attachObject 标回 1,stopRefresh 再揪出仍是 0 的废线并提示用户。checkObjectCompliance 除了比前缀,还验类型、样式和颜色标志,靠 Set 辅助类完成。 processLines 里先用 OBJPROP_BACK 排除已用对象,getCurrentPrice 按类型取第 0 根柱的供给价(多段线传两个价位)。checkActivation 返回逻辑标志,checkMarket 返回 +1/-1 方向,两者都用输入里的 EventTimeSpan 秒级 PreviousBid 判穿越。外汇和贵金属波动剧烈,这类自动触发在高滑点时段可能漏单或重复触发,实盘前务必在 MT5 策略测试器跑至少 3 个月Tick数据。 手数写在对象描述里,或写负数表示占可用保证金百分比。挂单线用 PendingType 样式 + 中性 ActivationColor,被价格穿越时按描述里斜杠分隔的线名去布单;描述空就按样式全找。disableLine 把线宽+1 并把时间存进 OBJPROP_ZORDER,普通线则丢背景降亮度,背景对象即视为禁用。 Expert 类用 MT4Orders 库在 MT5 上模拟 MT4 下单隐喻,含按风险算手数,但不支持改挂单——拖价靠终端「显示交易价位」。想接自己策略,直接换 Expert 类即可,源码 MT4/MT5 都能编。
class="kw">input class="type">int Magic = class="num">0; class="kw">input class="type">class="kw">double Lot = class="num">0.01 class=class="str">"cmt">/*默认手数*/; class="kw">input class="type">int Deviation = class="num">10 class=class="str">"cmt">/*订单执行期间能够容忍的价格变化*/; class="kw">input class="type">int DefaultTakeProfit = class="num">0 class=class="str">"cmt">/*点数*/; class="kw">input class="type">int DefaultStopLoss = class="num">0 class=class="str">"cmt">/*点数*/; class="kw">input class="type">int DefaultExpiration = class="num">0 class=class="str">"cmt">/*柱线数*/; class="kw">input class="type">class="kw">string CommonPrefit = "exp" class=class="str">"cmt">/*清空以便处理所有兼容的对象*/; class="kw">input class="type">color BuyColor = clrBlue class=class="str">"cmt">/*用于买入的市价和挂单 - 开单价, 收盘价, 止损, 止盈*/; class="kw">input class="type">color SellColor = clrRed class=class="str">"cmt">/*用于卖出的市价和挂单 - 开单价, 收盘价, 止损, 止盈*/; class="kw">input class="type">color ActivationColor = clrGray class=class="str">"cmt">/*放置挂单的激活警报*/; class="kw">input ENUM_LINE_STYLE InstantType = STYLE_SOLID class=class="str">"cmt">/*开市价单*/; class="kw">input ENUM_LINE_STYLE PendingType = STYLE_DASH class=class="str">"cmt">/*定义可能的挂单 (需要激活)*/
「把画线事件接进交易对象的生命周期」
EA 里若想让手动画的止损止盈线真正驱动持仓,得把它们挂到一套对象管理类里。下面这段 TradeObjects 类就是把图表事件、定时扫描和 trailing stop 串起来的核心骨架,复制进 MT5 能直接编译跑。 类构造时 new 了一个 Expert 实例,析构时 delete,避免句柄泄漏;handleInit 只在加载时扫一次已有线条,handleTick 里兼顾 MQL4 测试器用静态 lastTick 模拟定时器,实盘则直接调 e.trailStops() 推止损。 定时器逻辑有个细节:counter 累计到 EventTimeSpan(默认 10 秒)才处理一次 processLines,也就是说短线刺穿可能要等 10 秒窗口才被识别,高频刷单场景可把该参数调小。图表事件分支里,新建或改动对象会先 checkObjectCompliance 验证是不是合规的线,通过才 attach 并 describe,删线则即时移除并打印日志。 外汇和贵金属杠杆高,这类自动跟随画线平仓的逻辑可能在某些滑点行情下失效,上真仓前务必在策略测试器用至少 3 个月 tick 数据回测。
class="kw">input ENUM_LINE_STYLE CloseStopLossTakeProfitType = STYLE_DOT class=class="str">"cmt">/*适用于持仓*/; class="kw">input class="type">int EventHotSpot = class="num">10 class=class="str">"cmt">/*点数*/; class="kw">input class="type">int EventTimeSpan = class="num">10 class=class="str">"cmt">/*秒数*/; class="kw">input class="type">int EventInterval = class="num">10 class=class="str">"cmt">/*柱线数*/; class TradeObjects { class="kw">private: Expert *e; class="kw">public: class="type">void handleInit() { detectLines(); } class="type">void handleTick() { class="macro">#ifdef __MQL4__ if(MQLInfoInteger(MQL_TESTER)) { class="kw">static class="type">class="kw">datetime lastTick = class="num">0; if(TimeCurrent() != lastTick) { handleTimer(); lastTick = TimeCurrent(); } } class="macro">#endif e.trailStops(); } class="type">void handleTimer() { class="kw">static class="type">int counter = class="num">0; detectLines(); counter++; if(counter == EventTimeSpan) class=class="str">"cmt">// 等待 EventTimeSpan, 直到我们有的价格的历史纪录 { counter = class="num">0; if(PreviousBid > class="num">0) processLines(); if(PreviousBid != Bid) PreviousBid = Bid; } } class="type">void handleChart(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { if(id == CHARTEVENT_OBJECT_CREATE || id == CHARTEVENT_OBJECT_CHANGE) { if(checkObjectCompliance(sparam)) { if(attachObject(sparam)) { display(); describe(sparam); } } else { detectLines(); } } else if(id == CHARTEVENT_OBJECT_DELETE) { if(removeObject(sparam)) { display(); Print("Line deleted: ", sparam); } } } TradeObjects() { e = new Expert(Magic, Lot, Deviation); } ~TradeObjects() { class="kw">delete e; } }; TradeObjects to;