构建自动运行的 EA(第 02 部分):开始编码·进阶篇
(2/3)· 从空文件起步写出可发送市价单与挂单的 C_Orders 类,不再被标准库封装绕晕
很多交易者直接调用标准库发单,却看不懂订单请求在终端里到底走了哪一步。一旦报错只会对着日志发呆,因为从没见过黑匣子里的结构。本篇接上篇概念,带你从零写一个自己的订单类。
◍ EA 下单类该从哪下手
写交易类库的人常有个误区:以为要一口气把移单、删单、平仓、图表标记全包了。其实 MT5 终端的「交易」标签和图表本身已经处理了这些,EA 真正缺的只是绕过手动下单界面、直接往服务器发指令的通道。 所以 C_Orders 类早期唯一刚需是「发单函数」。但为了少调标准库,先把资产上下文缓存成私有变量:报价精度、最小成交量、跳步、每跳价值、点值、以及图表是按 BID 还是 LAST 绘制。这些用 private 声明,类外不可见,只在类内流通。 初始化别靠记忆。默认构造函数 C_Orders() 不收参,但必须把上面每个字段填上,否则后面算手数或挂单价时容易溢出或读脏值。对应地要写析构函数 ~C_Orders(),名字同类名前加波浪号,二者都不能有返回值,否则编译直接报错。 外汇和股票不是同一套绘图逻辑。外汇图表基于 BID,LAST 价格恒为 0;股票用 LAST,BID/ASK 差始终大于 0。若 EA 不判别当前是哪种绘制系统,挂单可能填错类型——外汇里发基于 LAST 的挂单会被拒,股票里误用 BID 算价会卡在点差外。构造函数里顺手读 SYMBOL_DIGITS 等,就是给跨市场移植留后路。 下面这段是类骨架与构造初始化的最小可编译形态,注意 m_Infos 结构体把跨市场判别字段 PlotLast 也收进去了:
class C_Orders { class="kw">private : class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">MqlTradeRequest m_TradeRequest; class="kw">struct st00 { class="type">int nDigits; class="type">class="kw">double VolMinimal, VolStep, PointPerTick, ValuePerPoint, AdjustToTrade; class="type">bool PlotLast; }m_Infos; C_Orders() { m_Infos.nDigits = (class="type">int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); m_Infos.VolMinimal = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); } };
把合约规格塞进结构体再算调整系数
EA 初始化时先把当前品种的合约参数读进自定义结构体,后面下单和画图都直接复用,不反复调系统函数。 下面这段把交易量最小步长、每跳点值、每跳金额分别存好,再用金额除以点值得到 AdjustToTrade,相当于把报价单位换算成账户货币的系数。 m_Infos.VolStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); m_Infos.PointPerTick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); m_Infos.ValuePerPoint = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; m_Infos.PlotLast = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST); 最后一行判断图表用的是收盘价还是最新价模式,PlotLast 为真时画线贴最新成交价,否则贴收盘价。外汇与贵金属杠杆高,点值随品种和杠杆浮动,实盘前应在 MT5 用具体品种跑一遍看 AdjustToTrade 是否和手册一致。
m_Infos.VolStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); m_Infos.PointPerTick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); m_Infos.ValuePerPoint = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; m_Infos.PlotLast = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);
「把挂单请求安全地递给服务器」
在 MQL5 里直接向交易服务器发挂单,核心就是 OrderSend。但这个函数最好藏进类的私有段,EA 只调公开封装函数,避免到处手写请求结构而出错。 下面这段是请求发送前的标准动作:先清掉上一次的错误码,再把检查结构和结果结构清零,然后跑一次 OrderCheck 看请求合不合法。
class="type">class="kw">ulong ToServer(class="type">void) { MqlTradeCheckResult TradeCheck; class="type">MqlTradeResult TradeResult; class="type">bool bTmp; ResetLastError(); ZeroMemory(TradeCheck); ZeroMemory(TradeResult); bTmp = OrderCheck(m_TradeRequest, TradeCheck);
class="type">class="kw">ulong ToServer(class="type">void) { MqlTradeCheckResult TradeCheck; class="type">MqlTradeResult TradeResult; class="type">bool bTmp; ResetLastError(); ZeroMemory(TradeCheck); ZeroMemory(TradeResult); bTmp = OrderCheck(m_TradeRequest, TradeCheck);
◍ 挂单价格的对齐与请求封装
挂单能不能顺利递到 MT5 交易服务器,第一道坎是价格对齐。AdjustPrice 用 MathRound(value / m_Infos.PointPerTick) * m_Infos.PointPerTick 把任意输入价吸附到 tick 粒度,避免因为小数位超出品种精度被经纪商拒单。 CreateOrder 里先调 AdjustPrice(Price) 规整入场价,再按 m_Infos.PlotLast 决定取 SYMBOL_LAST 还是 SYMBOL_BID 作基准,ask 在非 PlotLast 模式下单独取 SYMBOL_ASK。这套逻辑让 EA 在现货黄金(XAUUSD)这类点值特殊的品种上,挂单偏移计算倾向更稳定。 随后 ZeroMemory 清空请求结构,把 action 置为 TRADE_ACTION_PENDING、symbol 填为 _Symbol,准备发挂单。外汇与贵金属杠杆波动剧烈,错误价格对齐可能瞬间触发滑点或拒单,开 MT5 用策略测试器跑一段 XAUUSD 的 M1 回测就能验证对齐效果。
if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult); if (_LastError != ERR_SUCCESS) MessageBox(StringFormat("Error Number: %d", GetLastError()), "Order System", MB_OK); class="kw">return (_LastError == ERR_SUCCESS ? TradeResult.order : class="num">0); } class="kw">inline class="type">class="kw">double AdjustPrice(class="kw">const class="type">class="kw">double value) { class="kw">return MathRound(value / m_Infos.PointPerTick) * m_Infos.PointPerTick; } class="type">class="kw">ulong CreateOrder(class="kw">const ENUM_ORDER_TYPE type, class="type">class="kw">double Price, class="kw">const class="type">class="kw">double FinanceStop, class="kw">const class="type">class="kw">double FinanceTake, class="kw">const class="type">uint Leverage, class="kw">const class="type">bool IsDayTrade) { class="type">class="kw">double bid, ask, Desloc; Price = AdjustPrice(Price); bid = SymbolInfoDouble(_Symbol, (m_Infos.PlotLast ? SYMBOL_LAST : SYMBOL_BID)); ask = (m_Infos.PlotLast ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK)); ZeroMemory(m_TradeRequest); m_TradeRequest.action = TRADE_ACTION_PENDING; m_TradeRequest.symbol = _Symbol;
挂单与止损止盈的字段拼装逻辑
这段赋值块把一笔待发订单的所有核心字段一次性填好,重点在 volume、type、sl、tp 四个量的推导方式,复制进 EA 的订单构建函数即可直接编译跑。 volume 用 VolMinimal 加 VolStep 乘 (Leverage-1) 再 NormalizeDouble 到品种小数位,意味着杠杆每升一档就多跳一个最小步长,而不是按资金比例算手数。 type 的判断很直白:买向时若 ask>=Price 挂 BUY_LIMIT,否则 BUY_STOP;卖向时 bid<Price 挂 SELL_LIMIT,否则 SELL_STOP,价格落在市价哪一侧决定限价还是停损。 sl 与 tp 都先由 FinanceToPoints 把金额止损/止盈转成点值 Desloc,为 0 就填 0 不挂;买单 sl 在 Price 减 Desloc、tp 加 Desloc,卖单符号反向,偏离写死 1000 点容错。 外汇与贵金属杠杆放大波动,这类自动拼单在极端跳空时可能触发滑点以外的拒单,上 MT5 用策略测试器改 Leverage 和 FinanceStop 看成交率变化最直观。
m_TradeRequest.volume = NormalizeDouble(m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - class="num">1)), m_Infos.nDigits); m_TradeRequest.type = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP)); m_TradeRequest.price = NormalizeDouble(Price, m_Infos.nDigits); Desloc = FinanceToPoints(FinanceStop, Leverage); m_TradeRequest.sl = NormalizeDouble(Desloc == class="num">0 ? class="num">0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -class="num">1 : class="num">1)), m_Infos.nDigits); Desloc = FinanceToPoints(FinanceTake, Leverage); m_TradeRequest.tp = NormalizeDouble(Desloc == class="num">0 ? class="num">0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? class="num">1 : -class="num">1)), m_Infos.nDigits); m_TradeRequest.type_time = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC); m_TradeRequest.type_filling = ORDER_FILLING_RETURN; m_TradeRequest.deviation = class="num">1000; m_TradeRequest.comment = "Order Generated by Experts Advisor.";