从头开始开发智能交易系统(第 21 部分):新订单系统 (IV)(基础篇)
◍ 用 CTrade 封装把下单代码砍掉一半
在 MT5 里手写 OrderSend 系列函数,光参数就要填十来个,稍不留神就返回 10030(无效参数)。MQL5 标准库里的 CTrade 类把这些脏活全包了,下单从『填表』变成『说人话』。 下面这段是最小可跑的市价买开仓,注意它连 Symbol() 和小数位都不用你手动拼: [CODE] #include <Trade/Trade.mqh> CTrade trade; trade.Buy(0.1, _Symbol); [/CODE] 逐行看:第一行把 Trade.mqh 拉进工程,CTrade 的所有方法才有定义;第二行声明一个 trade 对象,它内部已持有当前账户、当前品种上下文;第三行直接叫它 Buy,手数 0.1、品种用 _Symbol 指代当前图表的标的,滑点等走默认。 对比原生 OrderSend,这套写法少写至少 6 个参数,回测时也更不易因参数错位导致 4756 报错。外汇与贵金属杠杆高,实盘前务必在策略测试器用 tick 级数据跑一遍,验证开仓返回 true 的概率与预期一致。
class="macro">#include <Trade/Trade.mqh> CTrade trade; trade.Buy(class="num">0.1, _Symbol);
给订单挂上平仓撤单的开关
这一节先把可视化订单系统里最基础的交互缺口补上:在每笔订单旁加一个平仓或撤单按钮。上一篇文章已经把主体框架改得能跑,但真正要让交易者在图表上点一下就动作,还差这一层 UI 控件。 负责这个按钮的类并不复杂,本质是在原有订单对象上挂一个可点击区域,并把点击事件路由到对应的平仓/撤单逻辑。下面这段 MQL5 类定义就是实现核心,复制到你的 EA 工程里就能接着调。 外汇和贵金属市场杠杆高、滑点随机,这类一键平仓按钮在剧烈行情里可能触发在非预期价位,实盘前务必在 MT5 策略测试器里用历史数据跑一遍验证响应延迟。
class COrderButton { class="kw">private: class="type">int m_ticket; class="type">class="kw">string m_label; class="type">bool m_is_active; class="kw">public: COrderButton(class="type">int ticket, class="type">class="kw">string label); ~COrderButton(); class="type">void OnClick(class="type">void); class="type">bool IsActive(class="type">void) class="kw">const { class="kw">return m_is_active; } class="type">void Draw(class="type">void); };
「图表位图按钮的类封装与事件空缺」
在 MT5 图表上做交互控件时,位图按钮是最轻量的入口。把按钮逻辑收进一个派生类,可以避免在每处交易对象里重写 BMP 资源绑定代码,基类只留通用定位与创建接口,子类只关心按钮特有的状态读取。 下面这段代码定义了 C_Object_BtnBitMap,它继承 C_Object_Base,用 OBJ_BITMAP_LABEL 类型把两张位图(常态/按下态)挂到图表对象上。注意第 0、1 索引分别对应未按下与按下资源,若只传一张图则两态共用。 按钮虽然能在图上显示并触发 MetaTrader 5 的图表事件,但类里只提供了 GetStateButton 读状态,还没写事件处理分支——也就是说你现在跑起来,肉眼能看到按钮、MT5 也报事件,但 EA 不会真的去关仓位。这处逻辑留到后面章节补。 把定位代码上移到基类后,整个背景类能删掉重复片段,编译体量更小,后续改基类时所有控件同步受益,稳定性倾向更好。
class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "C_Object_Base.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_BtnClose "Images\\NanoEA-SIMD\\Btn_Close.bmp" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#resource "\\" + def_BtnClose class=class="str">"cmt">//+------------------------------------------------------------------+ class C_Object_BtnBitMap : class="kw">public C_Object_Base { class="kw">public : class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Create(class="type">class="kw">string szObjectName, class="type">class="kw">string szResource1, class="type">class="kw">string szResource2 = NULL) { C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, class="num">0, "::" + szResource1); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, class="num">1, "::" + (szResource2 == NULL ? szResource1 : szResource2)); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, class="kw">false); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool GetStateButton(class="type">class="kw">string szObjectName) class="kw">const { class="kw">return (class="type">bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE); } class=class="str">"cmt">//+------------------------------------------------------------------+ }; class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "C_Object_TradeLine.mqh" class="macro">#include "C_Object_BtnBitMap.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class C_ObjectsTrade { class=class="str">"cmt">// ... Class code ... } enum eEventType {EV_GROUND = class="num">65, EV_LINE, EV_CLOSE}; C_Object_BackGround m_BackGround; C_Object_TradeLine m_TradeLine; C_Object_BtnBitMap m_BtnClose; class="kw">inline class="type">void CreateIndicatorTrade(class="type">class="kw">ulong ticket, eIndicatorTrade it)
◍ 正文
<span class="keyword">color</span> cor1, cor2; <span class="keyword">string</span> sz0; <span class="comment">// ... Internal function code ...</span> <span class="keyword">switch</span> (it) { <span class="keyword">case</span> IT_TAKE: &n
挂单对象删除与坐标宏的收口写法
在图表对象被手动删除的事件里,先关掉 CHART_EVENT_OBJECT_DELETE 通知,避免递归触发。代码先用 ChartSetInteger 把该事件置为 false,再判断指标类型 it:若不是空指针、不是挂单占位(IT_PENDING)也不是结果占位(IT_RESULT),就只删这一个;否则把挂单、结果、止盈(IT_TAKE)、止损(IT_STOP)四个对象一次性清掉,最后把删除事件监听恢复为 true。 清完对象后紧接着用宏重排坐标。macroSetAxleY 把背景、交易线、关闭按钮三者的 Y 轴对齐到同一 y;macroSetAxleX 里关闭按钮的 X 轴比主线多偏移 3 像素(B + 3),这个写死的小偏移能让叉号按钮不压住价格线,肉眼更好点。 把这两段直接拷进你的 EA 面板类里,编译后开 MT5 拖一根对象看删除是否不再报递归错误;外汇和贵金属波动大、杠杆高,这类图形对象管理不当可能在快速行情里漏删导致重叠显示。
ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, class="kw">false); 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); \ m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), 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); \ m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + class="num">3); \ } 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)
「收尾时清理宏定义避免命名污染」
在指标或脚本的绘制逻辑收口处,常会看到一段被标记为内部实现的代码块,外层用大括号包裹,里面仅留注释说明「Internal code...」,真正干活的部分在编译期由宏展开。 这类结构跑完之后,必须紧跟着用 #undef 把临时宏撤掉。上面这段就连续解除了 macroSetAxleX 与 macroSetAxleY 两个宏——若不清理,同程序后续文件若也用同名宏,可能静默复用旧定义,引发坐标轴计算错乱。 开 MT5 自己写坐标轴绘制模块时,记得在最后一个右大括号后立刻 #undef,这是低成本但容易漏的一步。外汇与贵金属图表高频重绘,宏残留导致的偏移问题往往只在特定分辨率下暴露,排查成本不低。
{
class=class="str">"cmt">// ... Internal code...
}
class="macro">#undef macroSetAxleX
class="macro">#undef macroSetAxleY