从头开始开发智能交易系统(第 19 部分):新订单系统 (II)(基础篇)
◍ 把旧订单逻辑换成 CTrade 封装
在 MT5 里手写 OrderSend / OrderSendAsync 很容易踩坑:参数顺序、填充策略、返回码处理全得自己兜底。MQL5 标准库里的 CTrade 类把这些脏活封装掉了,新订单系统优先用它,而不是重复造轮子。 下面这段初始化代码在 EA 的 OnInit 里跑一次即可,把交易对象挂到全局,后续下单直接调方法,不用每次重算品种属性。注意 Symbol 和小数位在切换品种时要重新取,别写死。 高风险提示:外汇与贵金属杠杆品种滑点、拒单概率不低,任何封装都不能消除执行风险,回测顺不等于实盘稳。
CTrade trade; class="type">int OnInit() { trade.SetExpertMagicNumber(class="num">123456); trade.SetDeviationInPoints(class="num">10); class="kw">return(INIT_SUCCEEDED); }
把订单系统搬上图表
上一节我们给订单系统打了补丁,让它能同时适配净持与对冲两类结算账户。净持账户下每种资产只能存在一笔持仓,系统会维护一个平均开仓价;而对冲账户允许多笔独立持仓并存,同一资产可同时持有多空,这也是后续理解期权类策略的前提。 现在要做的是把这套订单系统完全可视化。目标是干掉频繁弹出的消息框,直接把每笔持仓的关键数值画在图表上,靠肉眼和交互就能分析,不再依赖弹窗二次计算。 借助新的订单系统,我们可以一次性调整好几处逻辑。EA 实时刷出相关信息后,OCO 订单或挂单的盈亏限额也能直接读出来,不用再手动折算。本节只是第一阶段改造,并非从零起步,而是在现有系统上往交易品种图表里加对象和事件。
「用平台原生对象绕过订单数量天花板」
把订单画到图表上,思路不复杂:只改图表上表示订单的那层,不动底层逻辑。但真要做出来,得靠对数据的建模方式,让 MT5 替你扛住繁琐的维护活。 常见的三种建模里,列表(单/双循环或哈希)实现最容易,代价是要自己写一大堆防数据错乱和限量的逻辑;类数组省了点代码,事件处理却会突然变棘手。我们选的第三条路最反直觉——逼 MQL5 把图表上的动态对象当成一等公民来用。 只要命名唯一,就能建任意多条水平线,每条独立收事件。下面这段 C_HLineTrade 里的函数就是核心:用订单ticket拼出唯一对象名,再交给平台创建。 别把命名当小事 对象名里塞了类型枚举和ticket,保证全局唯一;平台靠这个名字路由事件,你只管按名写响应,不用维护数组下标。 不过光这样还不够。EA 当前环境不能基于一个名字挂无限对象,得再往深改几行,但整体结构不动,稳定性优先。改完之后,屏幕上的线没有数量上限,平台照常替你跑事件。外汇和贵金属波动剧烈,这种无上限对象模型若参数失控,图表对象泛滥会拖慢终端,实盘前务必在策略测试器里压测。
class="kw">inline class="type">void SetLineOrder(class="type">ulong ticket, class="type">class="kw">double price, eHLineTrade hl, class="type">bool select) { class="type">class="kw">string sz0 = def_NameHLineTrade + (class="type">class="kw">string)hl + (class="type">class="kw">string)ticket, sz1; ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, class="num">0, class="num">0, class="num">0); class=class="str">"cmt">//... The rest of the code....
◍ 把水平线类重构成可扩展对象类
把原先的 C_HLineTrade 换成 C_ObjectsTrade,核心意图只有一个:让一套类能挂任意数量的图表对象,而不再局限于水平线。基类里我把取止损、取止盈、取买卖方向的函数全声明成 inline,可执行文件会略微变大,但调用链更短、封装更稳。 protected 区里留了 eIndicatorTrade 枚举,IT_STOP 直接硬编码成 65,这只是给未来扩展留的占位方案。派生类不允许直接碰基类私有值,否则以后改 bug 或加功能会牵一发动全身——哪怕返回值只给派生类用,我也坚持走过程调用。 私密声明里最关键是对象名称的建模串。创建对象时名称按特定格式拼出来,后面用 StringSplit 反向拆开,等于用 MetaTrader 5 自带的字符串函数替我们做了解码器,不必自己写解析。每次 OnTrade 触发,EA 会试着在指定点建指标;若用户删了限制,价格变零,代码里就顺手把无用对象从图表和内存清掉。 重载是同名的 N 合一大杀器。例如建线函数有两个版本,区别仅在一个 price 参数,编译器靠参数数量区分。调用重载版比到处复制代码干净太多,也少了漏写声明的隐患。Ticket0 对应的指标编号写死为 1(def_IndicatorTicket0 1),它在 SHIFT 买 / CTRL 卖 的挂单逻辑里不能删,所以移除函数里专门高亮跳过它。 下面这段是类里的基础定义和限额设置函数,你可以直接贴进 MT5 看编译体积变化,并在私有区补完其余成员后实测封装是否如文中所说更抗改。外汇与贵金属杠杆高,任何 EA 结构改动都先在模拟盘验证再上真实账户。
class C_ObjectsTrade { class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_NameObjectsTrade "SMD_OT" class="macro">#define def_SeparatorInfo &class="macro">#x27;*&class="macro">#x27; class="macro">#define def_IndicatorTicket0 class="num">1 class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">protected: enum eIndicatorTrade {IT_NULL, IT_STOP= class="num">65, IT_TAKE, IT_PRICE}; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// ... The rest of the class code class="kw">inline class="type">class="kw">double GetLimitsTake(class="type">void) const { class="kw">return m_Limits.TakeProfit; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">class="kw">double GetLimitsStop(class="type">void) const { class="kw">return m_Limits.StopLoss; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">bool GetLimitsIsBuy(class="type">void) const { class="kw">return m_Limits.IsBuy; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">void SetLimits(class="type">class="kw">double take, class="type">class="kw">double stop, class="type">bool isbuy) { m_Limits.IsBuy = isbuy; m_Limits.TakeProfit = (m_Limits.TakeProfit < class="num">0 ? take : (isbuy ? (m_Limits.TakeProfit > take ? m_Limits.TakeProfit : take) : (take > m_Limits.TakeProfit ? m_Limits.TakeProfit : take))); m_Limits.StopLoss = (m_Limits.StopLoss < class="num">0 ? stop : (isbuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop))); } class=class="str">"cmt">//+------------------------------------------------------------------+