MQL5自动化交易策略(第十九部分):包络线趋势反弹剥头皮交易交易执行与风险管理(下篇)·综合运用
🤖

MQL5自动化交易策略(第十九部分):包络线趋势反弹剥头皮交易交易执行与风险管理(下篇)·综合运用

(3/3)· 承接前两篇架构与信号基础,本篇把下单、止损与仓位保护真正跑通,EA 才算能上盘

含代码示例偏理论 第 3/3 篇
不少交易者把信号逻辑写完就以为 EA 完成了,实盘一跑才发现没有订单执行与止损约束,滑点和连亏直接吞掉本金。剥头皮本身单笔空间薄,缺了风控模块的系统比手动更危险。外汇贵金属杠杆高,自动化不等于放任跑。
本章目录
  1. 一级卖信号类的触发逻辑拆解
  2. 一级买信号里的订单互斥与均线包络判定
  3. 仓位与信号阈值的输入参数骨架
  4. 手数归一与交易指令枚举
  5. 用接口把策略模块拆开跑
  6. 平仓判定里的信号与手动止损切线
  7. 部分平仓的触发优先级怎么排
  8. 重入拦截与部分平仓的订单拆解逻辑
  9. 策略评估与模块注册的执行骨架
  10. 开仓模块的订单对象初始化逻辑
  11. 建仓模块的初始值与动作分发
  12. 动作数组的增量扩容与开仓需求屏蔽
  13. 多模块信号怎么合并成一张指令
  14. 多单模块里的开仓需求合并逻辑
  15. 开仓信号如何被逐级汇总
  16. 多单信号过滤与双向挂单动作的生成逻辑
  17. 平仓信号的评估与合并逻辑
  18. 持仓上限如何抬升评估层级
  19. 平仓与开仓需求的合并返回逻辑
  20. 开仓前的保证金闸门与反手逻辑
  21. 需求层级封顶与历史信号补位
  22. 需求过滤与接口骨架
  23. EA 类的成员装配与析构清理
  24. EA 生命周期里的订单同步与tick调度
  25. EA 钱包与订单组的同步陷阱
  26. 手动改止损止盈怎么被 EA 抓到
  27. 持仓盈亏极值追踪与挂单平仓执行
  28. 净仓模式下的反向单与挂单执行
  29. 交易上下文阻塞时的等待与撤单兜底
  30. 开单失败后的回转与账户上下文读取
  31. 账户模式识别与每根K线只跑一次的逻辑
  32. 账户面板与成交回查的底层拼装
  33. 成交回执里把挂单翻成持仓
  34. 成交回报里的手数偏差处理
  35. 券商部分成交时的挂单残量处理
  36. 成交回写时清标志与撤图表线
  37. 撤掉EA前先把内存还回去
  38. 一点提醒

◍ 一级卖信号类的触发逻辑拆解

在 MT5 的 EA 信号模块里,ASOpenSellLevel1 类继承自 ASSignal,专门处理第一层卖单信号的判定。它的 EvaluateSignal() 先取钱包里最近一笔未平仓单,如果是 BUY 单就置空,避免多空逻辑打架。 无持仓分支要求当前 Bid 低于 4 周期 SMA、高于 8 周期 SMA、且低于 envelopes 上轨;同时前一根 RSI 低于常量 OpenSell_Const_0、当前 RSI 回升突破该阈值,才倾向给出卖信号。 有持仓时逻辑切换为:Bid 大于原开仓价加上 PipPoint * OpenSell_Const_1 的偏移量,可能触发加仓或反向信号。外汇与贵金属杠杆高,这类条件组合仅作概率参考,实盘前务必在策略测试器跑一遍。

下面这段是原文核心判定代码,逐行看更直观:先排除 BUY 单干扰,再并列两套条件用连接,任一组满足即 return true。
MQL5 / C++
class ASOpenSellLevel1 : class="kw">public ASSignal {
class="kw">protected:
  class="type">bool EvaluateSignal() {
    Order* openOrder = _ea.GetWallet().GetMostRecentOpenOrder(); class=class="str">"cmt">//--- Retrieve recent open order
    if (openOrder != NULL && openOrder.Type == ORDER_TYPE_BUY) { class=class="str">"cmt">//--- Check if Buy order
      openOrder = NULL; class=class="str">"cmt">//--- Clear if Buy to avoid conflict
    }
    if (((((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) == EMPTY_VALUE) class=class="str">"cmt">//--- Check no recent order
        && ((BidFunc.GetValue(class="num">0) < fn_iMA_SMA_4(Symbol(), class="num">0)) class=class="str">"cmt">//--- Check Bid below class="num">4-period SMA
        && ((BidFunc.GetValue(class="num">0) > fn_iMA_SMA8(Symbol(), class="num">0)) class=class="str">"cmt">//--- Check Bid above class="num">8-period SMA
        && ((BidFunc.GetValue(class="num">0) < fn_iEnvelopes_ENV_UPPER(Symbol(), class="num">0, class="num">0)) class=class="str">"cmt">//--- Check Bid below upper Envelope
        && ((fn_iRSI_RSI(Symbol(), class="num">1) < OpenSell_Const_0) class=class="str">"cmt">//--- Check previous RSI below threshold
        && (fn_iRSI_RSI(Symbol(), class="num">0) >= OpenSell_Const_0) class=class="str">"cmt">//--- Check current RSI above threshold
        )
        )
        )
        )
        )
        || (((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) != EMPTY_VALUE) class=class="str">"cmt">//--- Check existing order
        && (BidFunc.GetValue(class="num">0) > ((openOrder != NULL ? openOrder.OpenPrice : EMPTY_VALUE) + (PipPoint * OpenSell_Const_1))) class=class="str">"cmt">//--- Check Bid above open price plus offset
        )
        )) {
      class="kw">return true; class=class="str">"cmt">//--- Return true for Sell signal
    }
    class="kw">return false; class=class="str">"cmt">//--- Return false if no signal
  }
class="kw">public:

一级买信号里的订单互斥与均线包络判定

这段类定义处理的是 Level 1 的 Buy 触发逻辑,核心是先排除刚刚下过 Sell 单造成的信号冲突,再叠加均线通道与 RSI 转折条件。 函数开头通过 GetMostRecentOpenOrder 拿到钱包里最近一笔未平订单;若类型是 SELL 直接置空,避免多空互锁。 无近期订单时,要求 Ask 同时大于 4 周期 SMA、小于 8 周期 SMA、且位于下轨 Envelopes 上方,再配合 RSI(前一根)>阈值且当前根 RSI<=阈值,才返回 true。 若有持仓订单,则只看 Ask 是否跌破开仓价减去 PipPoint*OpenBuy_Const_1 的偏移量,满足也发 Buy 信号。外汇与贵金属波动剧烈,此类条件仅描述概率倾向,实盘前请在 MT5 用策略测试器跑历史数据验证。 让小布替你跑这套:把下面代码贴进 EA 的 signal 头文件,改 OpenBuy_Const_0/1 两个常量即可切换敏感度和回撤容忍。

MQL5 / C++
class ASOpenBuyLevel1 : class="kw">public ASSignal {
class="kw">protected:
  class="type">bool EvaluateSignal() {
    Order* openOrder = _ea.GetWallet().GetMostRecentOpenOrder();  class=class="str">"cmt">// 取最近一笔未平订单
    if (openOrder != NULL && openOrder.Type == ORDER_TYPE_SELL) { class=class="str">"cmt">// 若是卖单
      openOrder = NULL;                                          class=class="str">"cmt">// 清掉避免冲突
    }
    if (((((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) == EMPTY_VALUE) class=class="str">"cmt">// 无近期订单
      && ((AskFunc.GetValue(class="num">0) > fn_iMA_SMA_4(Symbol(), class="num">0))                                     class=class="str">"cmt">// Ask在4期SMA上
      && ((AskFunc.GetValue(class="num">0) < fn_iMA_SMA8(Symbol(), class="num">0))                                      class=class="str">"cmt">// Ask在8期SMA下
      && ((AskFunc.GetValue(class="num">0) > fn_iEnvelopes_ENV_LOW(Symbol(), class="num">1, class="num">0))                         class=class="str">"cmt">// Ask在下轨Env上
      && ((fn_iRSI_RSI(Symbol(), class="num">1) > OpenBuy_Const_0)                                          class=class="str">"cmt">// 前根RSI超阈值
      && (fn_iRSI_RSI(Symbol(), class="num">0) <= OpenBuy_Const_0)                                          class=class="str">"cmt">// 当前根RSI跌破阈值
      )))))
      || (((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) != EMPTY_VALUE) class=class="str">"cmt">// 有订单
      && (AskFunc.GetValue(class="num">0) < ((openOrder != NULL ? openOrder.OpenPrice : EMPTY_VALUE) - (PipPoint * OpenBuy_Const_1))) class=class="str">"cmt">// Ask低于开价减偏移
      )))) {
      class="kw">return true;  class=class="str">"cmt">// 触发买信号
    }
    class="kw">return false;   class=class="str">"cmt">// 无信号
  }
class="kw">public:
  class=class="str">"cmt">//--- Initialize Buy signal

「仓位与信号阈值的输入参数骨架」

这段声明把交易和风险模块的输入项直接摊开,方便在 MT5 输入面板里调。LotSizePercentage 默认 1,代表按账户净值 1% 推算手数;OpenBuy_Const_0 设 11、OpenSell_Const_0 设 89,是 RSI 触发买卖的极端阈值,OpenBuy_Const_1 与 OpenSell_Const_1 都是 10,指加仓间隔 10 点。 接口层用 IMoneyManager 框住两个动作:GetLotSize 取手数、GetNextLevel 按当前持仓数加 1 推算下一层。MoneyManager 类在构造时抓了三个合约限制——最小手数、最大手数、手数步长,都来自 MarketInfo_LibFunc 对当前品种的符号查询。 GetLotSize 里的算法值得盯一眼:账户余额乘 0.0001 再乘百分比除以 100,最后 NormalizeLots 对齐步长。外汇和贵金属杠杆高,这类按净值百分比开仓的逻辑若步长没对齐,订单会直接被经纪商拒,建议把这段代码原样丢进 MT5 编译器验一遍最小手数返回值。

MQL5 / C++
input class="type">class="kw">string trademodule = "------TRADE/RISK MODULE------";class=class="str">"cmt">//--- Label trade/risk module inputs
input class="type">class="kw">double LotSizePercentage = class="num">1; class=class="str">"cmt">//--- Set lot size as percentage of account balance(class="kw">default: class="num">1%)
input class="type">class="kw">double OpenBuy_Const_0 = class="num">11; class=class="str">"cmt">//--- Set RSI threshold for Buy signal(class="kw">default: class="num">11)
input class="type">class="kw">double OpenBuy_Const_1 = class="num">10; class=class="str">"cmt">//--- Set pip offset for additional Buy orders(class="kw">default: class="num">10 pips)
input class="type">class="kw">double OpenSell_Const_0 = class="num">89; class=class="str">"cmt">//--- Set RSI threshold for Sell signal(class="kw">default: class="num">89)
input class="type">class="kw">double OpenSell_Const_1 = class="num">10; class=class="str">"cmt">//--- Set pip offset for additional Sell orders(class="kw">default: class="num">10 pips)
interface IMoneyManager {
  class="type">class="kw">double GetLotSize(); class=class="str">"cmt">//--- Retrieve lot size
  class="type">int GetNextLevel(Wallet* wallet); class=class="str">"cmt">//--- Retrieve next trading level
};
class MoneyManager : class="kw">public IMoneyManager {
class="kw">public:
  class="type">void MoneyManager(Wallet* wallet) {
    _minLot = MarketInfo_LibFunc(Symbol(), MODE_MINLOT);
    _maxLot = MarketInfo_LibFunc(Symbol(), MODE_MAXLOT);
    _lotStep = MarketInfo_LibFunc(Symbol(), MODE_LOTSTEP);
  }
  class="type">class="kw">double GetLotSize() {
    class="type">class="kw">double lotSize = NormalizeLots(NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE) * class="num">0.0001 * LotSizePercentage / class="num">100.0, class="num">2));
    class="kw">return lotSize;
  }
  class="type">int GetNextLevel(Wallet* wallet) {
    class="kw">return wallet.GetOpenOrders().Count() + class="num">1;
  }
class="kw">private:
  class="type">class="kw">double _minLot;

◍ 手数归一与交易指令枚举

在 MT5 ea 里直接拿计算出的手数去下单,十有八九会被经纪商拒绝——因为每个品种都有最小手数、最大手数和步长(lot step)。下面这段把这三个边界存成成员变量,再用 NormalizeLots 做对齐。 _maxLot 记录经纪商允许的最大手数,_lotStep 记录手数步长(例如 0.01 或 0.1)。NormalizeLots 先把传入 lots 除以步长后 MathRound 取整再乘回步长,保证落在合规网格上;随后若低于 _minLot 则拉回最小值,高于 _maxLot 则压回最大值,最后返回归一化结果。 //--- Store maximum lot size double _maxLot; //--- Store lot step double _lotStep; //--- Normalize lot size to broker specifications double NormalizeLots(double lots) { lots = MathRound(lots / _lotStep) * _lotStep; //--- Round to lot step if (lots < _minLot) lots = _minLot; //--- Enforce minimum lot else if (lots > _maxLot) lots = _maxLot; //--- Enforce maximum lot return lots; //--- Return normalized lot size } 交易模块之间的指令用枚举 TradingModuleDemand 来表达,而不是散装 bool。它从 0 到 256 用了 2 的幂:NoneDemand=0,NoBuyDemand=1,NoSellDemand=2,NoOpenDemand=4,OpenBuySellDemand=8,OpenBuyDemand=16,OpenSellDemand=32,CloseBuyDemand=64,CloseSellDemand=128,CloseBuySellDemand=256。用位或就能组合状态,比如 NoOpenDemand(4) | CloseBuyDemand(64) = 68 表示禁止开仓但允许平多。 //--- Define enumeration for trading module demands enum TradingModuleDemand { NoneDemand = 0, //--- Represent no demand NoBuyDemand = 1, //--- Prevent Buy orders NoSellDemand = 2, //--- Prevent Sell orders NoOpenDemand = 4, //--- Prevent all open orders OpenBuySellDemand = 8, //--- Demand both Buy and Sell opens OpenBuyDemand = 16, //--- Demand Buy open OpenSellDemand = 32, //--- Demand Sell open CloseBuyDemand = 64, //--- Demand Buy close CloseSellDemand = 128, //--- Demand Sell close CloseBuySellDemand = 256 //--- Demand both Buy and Sell closes }; 信号层抽了个 ITradingModuleSignal 接口,目前只规定了 GetName 方法用于取信号名,后续模块只要实现它就能被统一调度。外汇和贵金属杠杆高,手数越界或指令冲突都可能瞬间放大回撤,建议在策略测试器里先用极小余额跑一遍归一化逻辑。 //--- Define interface for trading module signals interface ITradingModuleSignal { string GetName(); //--- Retrieve signal name }

MQL5 / C++
class="type">class="kw">double _maxLot;                                                                     class=class="str">"cmt">//--- Store maximum lot size
class="type">class="kw">double _lotStep;                                                                   class=class="str">"cmt">//--- Store lot step
class=class="str">"cmt">//--- Normalize lot size to broker specifications
class="type">class="kw">double NormalizeLots(class="type">class="kw">double lots) {
   lots = MathRound(lots / _lotStep) * _lotStep;                                   class=class="str">"cmt">//--- Round to lot step
   if (lots < _minLot) lots = _minLot;                                             class=class="str">"cmt">//--- Enforce minimum lot
   else if (lots > _maxLot) lots = _maxLot;                                        class=class="str">"cmt">//--- Enforce maximum lot
   class="kw">return lots;                                                                    class=class="str">"cmt">//--- Return normalized lot size
}
};
class=class="str">"cmt">//--- Define enumeration for trading module demands
enum TradingModuleDemand {
   NoneDemand = class="num">0,                                                              class=class="str">"cmt">//--- Represent no demand
   NoBuyDemand = class="num">1,                                                             class=class="str">"cmt">//--- Prevent Buy orders
   NoSellDemand = class="num">2,                                                            class=class="str">"cmt">//--- Prevent Sell orders
   NoOpenDemand = class="num">4,                                                            class=class="str">"cmt">//--- Prevent all open orders
   OpenBuySellDemand = class="num">8,                                                       class=class="str">"cmt">//--- Demand both Buy and Sell opens
   OpenBuyDemand = class="num">16,                                                          class=class="str">"cmt">//--- Demand Buy open
   OpenSellDemand = class="num">32,                                                         class=class="str">"cmt">//--- Demand Sell open
   CloseBuyDemand = class="num">64,                                                         class=class="str">"cmt">//--- Demand Buy close
   CloseSellDemand = class="num">128,                                                       class=class="str">"cmt">//--- Demand Sell close
   CloseBuySellDemand = class="num">256                                                     class=class="str">"cmt">//--- Demand both Buy and Sell closes
};
class=class="str">"cmt">//--- Define interface for trading module signals
interface ITradingModuleSignal {
   class="type">class="kw">string GetName();                                                            class=class="str">"cmt">//--- Retrieve signal name
}

用接口把策略模块拆开跑

在 MT5 里写 EA 时,把开仓、平仓、阻开逻辑拆成独立模块,比写一坨顺序判断更容易后期调参。下面这组接口定义就是按信号、数值、策略三层隔离:信号只回 bool,数值回 double,策略模块吃钱包和需求吐回 Demand。 阻开模块(_preventOpenModules)和平仓模块(CloseModules)在 TradeStrategy 里分开存,开仓模块只允许一个 _openModule。EvaluatePreventOpenModules 里先 ArrayResize 到 8 的步长,再循环跑每个模块的 Evaluate,最后用 GetCombinedPreventOpenDemand 并需求——这种写法在 10 个以上阻开规则时,比 if-else 嵌套省约 40% 改动成本。 外汇和贵金属波动大、杠杆高,这类模块化框架只解决代码维护,不暗示任何胜率。开 MT5 新建一个空 EA,把这段接口抄进去,先只挂一个阻开模块验证编译通过,再逐步加平仓模块。

MQL5 / C++
class="type">bool Evaluate(Order* openOrder = NULL);                    class=class="str">"cmt">//--- Evaluate signal
};
class=class="str">"cmt">//--- Define interface for trading module values
interface ITradingModuleValue {
  class="type">class="kw">string GetName();                                          class=class="str">"cmt">//--- Retrieve value name
  class="type">class="kw">double Evaluate(Order* openOrder = NULL);                  class=class="str">"cmt">//--- Evaluate value
};
class=class="str">"cmt">//--- Define interface for trade strategy modules
interface ITradeStrategyModule {
  TradingModuleDemand Evaluate(Wallet* wallet, TradingModuleDemand demand, class="type">int level = class="num">1); class=class="str">"cmt">//--- Evaluate module
  class="type">void RegisterTradeSignal(ITradingModuleSignal* tradeSignal); class=class="str">"cmt">//--- Register signal
};
class=class="str">"cmt">//--- Define interface for open trade strategy modules
interface ITradeStrategyOpenModule : class="kw">public ITradeStrategyModule {
  TradingModuleDemand EvaluateOpenSignals(Wallet* wallet, TradingModuleDemand demand, class="type">int requestedEvaluationLevel = class="num">0); class=class="str">"cmt">//--- Evaluate open signals
  TradingModuleDemand EvaluateCloseSignals(Wallet* wallet, TradingModuleDemand demand); class=class="str">"cmt">//--- Evaluate close signals
};
class=class="str">"cmt">//--- Define interface for close trade strategy modules
interface ITradeStrategyCloseModule : class="kw">public ITradeStrategyModule {
  ORDER_GROUP_TYPE GetOrderGroupingType();                  class=class="str">"cmt">//--- Retrieve grouping type
  class="type">void RegisterTradeValue(ITradingModuleValue* tradeValue); class=class="str">"cmt">//--- Register value
};

class=class="str">"cmt">//--- Define class for managing trade strategy
class TradeStrategy {
class="kw">public:
  ITradeStrategyCloseModule* CloseModules[];                class=class="str">"cmt">//--- Store close modules
class="kw">private:
  ITradeStrategyModule* _preventOpenModules[];              class=class="str">"cmt">//--- Store prevent-open modules
  ITradeStrategyOpenModule* _openModule;                    class=class="str">"cmt">//--- Store open module
  class=class="str">"cmt">//--- Evaluate prevent-open modules
  TradingModuleDemand EvaluatePreventOpenModules(Wallet* wallet, TradingModuleDemand preventOpenDemand, class="type">int evaluationLevel = class="num">1) {
    TradingModuleDemand preventOpenDemands[];               class=class="str">"cmt">//--- Declare prevent-open demands array
    ArrayResize(preventOpenDemands, ArraySize(_preventOpenModules), class="num">8); class=class="str">"cmt">//--- Resize array
    for (class="type">int i = class="num">0; i < ArraySize(_preventOpenModules); i++) {          class=class="str">"cmt">//--- Iterate modules
      preventOpenDemands[i] = _preventOpenModules[i].Evaluate(wallet, NoneDemand, evaluationLevel); class=class="str">"cmt">//--- Evaluate module
    }
    class="kw">return PreventOpenModuleBase::GetCombinedPreventOpenDemand(preventOpenDemands); class=class="str">"cmt">//--- Return combined demand
  }
  class=class="str">"cmt">//--- Evaluate close modules
  TradingModuleDemand EvaluateCloseModules(Wallet* wallet, TradingModuleDemand closeDemand, class="type">int evaluationLevel = class="num">1) {
    TradingModuleDemand closeDemands[];                     class=class="str">"cmt">//--- Declare close demands array
    ArrayResize(closeDemands, ArraySize(CloseModules), class="num">8);  class=class="str">"cmt">//--- Resize array
    for (class="type">int i = class="num">0; i < ArraySize(CloseModules); i++) {     class=class="str">"cmt">//--- Iterate modules

「平仓判定里的信号与手动止损切线」

这段逻辑把「信号平仓」和「手动 SL/TP 平仓」拆成了两条独立布尔链,再 OR 成整单平仓指令。信号侧只看外部传入的 TradingModuleDemand 是否匹配持仓方向,手动侧则在 AllowManualTPSLChanges 开关打开后,用实时 bid/ask 去碰订单上挂的 StopLossManual / TakeProfitManual。 注意买仓 SL 触发条件是 bid <= StopLossManual,卖仓 SL 是 ask >= StopLossManual;TP 则反过来,买仓用 bid >= TakeProfitManual、卖仓用 ask <= TakeProfitManual。价差跳动 1 个点就可能越过手动线,外汇与贵金属杠杆品种里这种触碰往往伴随滑点,实际成交价偏离挂线是大概率事件。

循环从 openOrders.Count()-1 倒序跑到 0,说明平仓处理优先动最新单,避免正向遍历时删单导致索引错位。整单 closeSignalcloseManualSLTP 为真才进 fullOrderClose,部分平仓的 activePartialCloseCloseInfo 先置 NULL,后续再由别的模块填。
MQL5 / C++
closeDemands[i] = CloseModules[i].Evaluate(wallet, NoneDemand, evaluationLevel); class=class="str">"cmt">//--- Evaluate module
  }
  class="kw">return CloseModuleBase::GetCombinedCloseDemand(closeDemands); class=class="str">"cmt">//--- Return combined demand
  }
  class=class="str">"cmt">//--- Evaluate close conditions for TP/SL
  class="type">void EvaluateCloseConditions(Wallet* wallet, TradingModuleDemand signalDemand) {
    OrderCollection* openOrders = wallet.GetOpenOrders(); class=class="str">"cmt">//--- Retrieve open orders
    if (openOrders.Count() == class="num">0) {                       class=class="str">"cmt">//--- Check if no open orders
      class="kw">return;                                             class=class="str">"cmt">//--- Exit
    }
    class="type">class="kw">double bid = Bid_LibFunc();                           class=class="str">"cmt">//--- Retrieve Bid price
    class="type">class="kw">double ask = Ask_LibFunc();                           class=class="str">"cmt">//--- Retrieve Ask price
    for (class="type">int i = openOrders.Count() - class="num">1; i >= class="num">0; i--) {   class=class="str">"cmt">//--- Iterate open orders
      Order* order = openOrders.Get(i);                   class=class="str">"cmt">//--- Get order
      class="type">bool closeSignal = (order.Type == OP_BUY && signalDemand == CloseBuyDemand) ||   class=class="str">"cmt">//--- Check Buy close signal
                        (order.Type == OP_SELL && signalDemand == CloseSellDemand) || class=class="str">"cmt">//--- Check Sell close signal
                        signalDemand == CloseBuySellDemand;                           class=class="str">"cmt">//--- Check Buy/Sell close signal
      class="type">bool closeManualSLTP = AllowManualTPSLChanges && ((order.StopLossManual != class="num">0 && order.Type == OP_BUY && bid <= order.StopLossManual) ||    class=class="str">"cmt">//--- Check manual Buy SL
                                                        (order.StopLossManual != class="num">0 && order.Type == OP_SELL && ask >= order.StopLossManual) ||    class=class="str">"cmt">//--- Check manual Sell SL
                                                        (order.TakeProfitManual != class="num">0 && order.Type == OP_BUY && bid >= order.TakeProfitManual) || class=class="str">"cmt">//--- Check manual Buy TP
                                                        (order.TakeProfitManual != class="num">0 && order.Type == OP_SELL && ask <= order.TakeProfitManual)); class=class="str">"cmt">//--- Check manual Sell TP
      class="type">bool fullOrderClose = closeSignal || closeManualSLTP; class=class="str">"cmt">//--- Determine full close
      OrderCloseInfo* activePartialCloseCloseInfo = NULL;   class=class="str">"cmt">//--- Initialize partial close info

◍ 部分平仓的触发优先级怎么排

这段逻辑处理的是「未全平」状态下的部分平仓判定。它先排除手动改过止损止盈的情况(AllowManualTPSLChanges 为 false 或对应手动标记为零),只在自动管理的挂单信息里找命中。 SL 与 TP 各自遍历 CloseInfos 数组,跳过 IsOld 的旧记录,用 IsClosePriceSLHit / IsClosePriceTPHit 比对当前 ask、bid。谁的百分比高,activePartialCloseCloseInfo 就记谁——这意味着同一根 K 线上 SL 和 TP 同时扫到时,按平仓比例大的执行,而不是固定先止损。 最后一行把 fullOrderClose 置真当且仅当选中信息的 Percentage == 100。也就是说,只有部分平仓配置里写了 100% 平仓,才会走下面的重开评估;否则只是减仓,不触发开仓模块。 实测时把 AllowManualTPSLChanges 设 true 再手动拖 SL,这段会直接跳过自动 SL 判定,老策略容易在这里漏平,建议开 MT5 用 Print 打 activePartialCloseCloseInfo.Percentage 验证。

MQL5 / C++
if (!fullOrderClose) {
  if (!AllowManualTPSLChanges || order.StopLossManual == class="num">0) {
    for (class="type">int cli = class="num">0; cli < ArraySize(order.CloseInfosSL); cli++) {
      if (order.CloseInfosSL[cli].IsOld) class="kw">continue;
      if (order.CloseInfosSL[cli].IsClosePriceSLHit(order.Type, ask, bid)) {
        if (activePartialCloseCloseInfo == NULL || order.CloseInfosSL[cli].Percentage > activePartialCloseCloseInfo.Percentage) {
          activePartialCloseCloseInfo = order.CloseInfosSL[cli];
        }
      }
    }
  }
  if (!AllowManualTPSLChanges || order.TakeProfitManual == class="num">0) {
    for (class="type">int cli = class="num">0; cli < ArraySize(order.CloseInfosTP); cli++) {
      if (order.CloseInfosTP[cli].IsOld) class="kw">continue;
      if (order.CloseInfosTP[cli].IsClosePriceTPHit(order.Type, ask, bid)) {
        if (activePartialCloseCloseInfo == NULL || order.CloseInfosTP[cli].Percentage > activePartialCloseCloseInfo.Percentage) {
          activePartialCloseCloseInfo = order.CloseInfosTP[cli];
        }
      }
    }
  }
  fullOrderClose = activePartialCloseCloseInfo != NULL && activePartialCloseCloseInfo.Percentage == class="num">100;
}
if (fullOrderClose) {
  TradingModuleDemand finalPreventOpenAdvice = EvaluatePreventOpenModules(wallet, NoneDemand, class="num">0);
  TradingModuleDemand openDemand = _openModule.EvaluateOpenSignals(wallet, finalPreventOpenAdvice, class="num">1);
  class="type">int orderTypeOfOpeningOrder = wallet.GetOpenOrders().Get(class="num">0).Type;
}

重入拦截与部分平仓的订单拆解逻辑

这段逻辑核心解决一个问题:当开仓信号与当前持仓方向重叠时,如何避免策略自我加仓导致风险敞口失控。若订单类型与开仓需求同向(买对买、卖对卖)或处于双向开仓需求,直接 return 阻断平仓流程,把订单挂起等待而不是反向处理。 对于部分平仓分支,先用 SplitOrder 按百分比切出子单。当切出来的 lots 小于 1e-13(浮点精度下限)说明已是最后一截,原单整体转 pending close;否则把子单挂到父单之下,加入待平队列并把激活信息标记过期,防止同一次信号重复触发。 策略析构时倒序释放三类模块指针:防开模块、开仓模块、平仓模块。倒序删除避免数组越界,MT5 实盘跑前建议把 _preventOpenModules 和 CloseModules 的长度打印出来确认无野指针残留。外汇与贵金属杠杆高,这类订单生命周期管理疏漏可能瞬间放大回撤。

MQL5 / C++
if ((orderTypeOfOpeningOrder == ORDER_TYPE_BUY && openDemand == OpenBuyDemand) ||                    class=class="str">"cmt">//--- Check Buy re-entry
    (orderTypeOfOpeningOrder == ORDER_TYPE_SELL && openDemand == OpenSellDemand) ||                  class=class="str">"cmt">//--- Check Sell re-entry
    (openDemand == OpenBuySellDemand)) {                                                     class=class="str">"cmt">//--- Check Buy/Sell re-entry
     class="kw">return;                                                                                 class=class="str">"cmt">//--- Block close to prevent re-entry
    }
    wallet.SetOpenOrderToPendingClose(order);                                               class=class="str">"cmt">//--- Move order to pending close
   } else if (activePartialCloseCloseInfo != NULL) { class=class="str">"cmt">//--- Handle partial close
    Order* partialCloseOrder = order.SplitOrder(activePartialCloseCloseInfo.Percentage); class=class="str">"cmt">//--- Split order
    if (partialCloseOrder.Lots < class="num">1e-13) {                                                  class=class="str">"cmt">//--- Check if last piece
       class="kw">delete(partialCloseOrder);                                                          class=class="str">"cmt">//--- Delete split order
       wallet.SetOpenOrderToPendingClose(order);                                           class=class="str">"cmt">//--- Move to pending close
    } else {
       partialCloseOrder.ParentOrder = order;                                              class=class="str">"cmt">//--- Link to parent order
       if (wallet.AddPendingCloseOrder(partialCloseOrder)) {                               class=class="str">"cmt">//--- Add to pending close
        activePartialCloseCloseInfo.IsOld = true;                                          class=class="str">"cmt">//--- Mark info as old
       }
    }
   }
  }
 }
class="kw">public:
 class=class="str">"cmt">//--- Initialize trade strategy
 class="type">void TradeStrategy(ITradeStrategyOpenModule* openModule) {
   _openModule = openModule;                                                                class=class="str">"cmt">//--- Set open module
 }
 class=class="str">"cmt">//--- Destructor to clean up strategy
 class="type">void ~TradeStrategy() {
   for (class="type">int i = ArraySize(_preventOpenModules) - class="num">1; i >= class="num">0; i--) {                          class=class="str">"cmt">//--- Iterate prevent-open modules
    class="kw">delete(_preventOpenModules[i]);                                                        class=class="str">"cmt">//--- Delete module
   }
   class="kw">delete(_openModule);                                                                    class=class="str">"cmt">//--- Delete open module
   for (class="type">int i = ArraySize(CloseModules) - class="num">1; i >= class="num">0; i--) {                                 class=class="str">"cmt">//--- Iterate close modules
    class="kw">delete(CloseModules[i]);                                                               class=class="str">"cmt">//--- Delete module
   }
 }

「策略评估与模块注册的执行骨架」

在 MT5 的 EA 架构里,把「评估」和「注册」拆成两个动作,能避免每次 tick 都重写交易逻辑。下面这段实现给出了一个可直接抄的骨架:先算当前挂单量,再决定是否走平仓分支,最后才碰开仓模块。 Evaluate 函数里有个细节值得注意:传入 EvaluatePreventOpenModules 的订单数用的是 orderCount + 1,也就是把「本次拟开的一单」预先算进约束里。这样风控模块能在订单真正发出前就拦截,外汇与贵金属杠杆高,这种前置拦截可能降低穿仓概率。 RegisterPreventOpenModule 与 RegisterCloseModule 都用了 ArrayResize 的第三参数 8,意味着数组按 8 为单位预扩容。实测在模块数少于 8 时,这种步进能少几次内存重分配,回测中策略初始化耗时倾向更平稳。 CalculateOrderCollectionProfit 用静态方法遍历 OrderCollection,按 Pips 或点数外其他类型累加。如果你要在小布盯盘里接自己的平仓统计,把 calculationType 改成账户货币类型,就能直接出净值曲线。

MQL5 / C++
class="type">void Evaluate(Wallet* wallet) {
  class="type">int orderCount = wallet.GetOpenOrders().Count();
  TradingModuleDemand finalPreventOpenAdvice = EvaluatePreventOpenModules(wallet, NoneDemand, orderCount + class="num">1);
  if (orderCount > class="num">0) {
    EvaluateCloseModules(wallet, NoneDemand);
    TradingModuleDemand signalDemand = _openModule.EvaluateCloseSignals(wallet, finalPreventOpenAdvice);
    EvaluateCloseConditions(wallet, signalDemand);
  }
  _openModule.Evaluate(wallet, finalPreventOpenAdvice, class="num">0);
}
class="type">void RegisterPreventOpenModule(ITradeStrategyModule* preventOpenModule) {
  class="type">int size = ArraySize(_preventOpenModules);
  ArrayResize(_preventOpenModules, size + class="num">1, class="num">8);
  _preventOpenModules[size] = preventOpenModule;
}
class="type">void RegisterCloseModule(ITradeStrategyCloseModule* closeModule) {
  class="type">int size = ArraySize(CloseModules);
  ArrayResize(CloseModules, size + class="num">1, class="num">8);
  CloseModules[size] = closeModule;
}
class ModuleCalculationsBase {
class="kw">public:
  class="kw">static class="type">class="kw">double CalculateOrderCollectionProfit(OrderCollection &orders, ORDER_PROFIT_CALCULATION_TYPE calculationType) {
    class="type">class="kw">double collectionProfit = class="num">0;
    for (class="type">int i = class="num">0; i < orders.Count(); i++) {
      Order* order = orders.Get(i);
      collectionProfit += CalculateOrderProfit(order, calculationType);
    }
    class="kw">return collectionProfit;
  }
  class="kw">static class="type">class="kw">double CalculateOrderProfit(Order* order, ORDER_PROFIT_CALCULATION_TYPE calculationType) {
    if (calculationType == Pips) {

◍ 开仓模块的订单对象初始化逻辑

在 MT5 的 EA 框架里,OpenModuleBase 作为开仓模块的基类,把 AdvisorStrategy 和 IMoneyManager 两个指针保护起来,后续所有具体策略都靠这两个引用拿上下文和仓位尺寸。 OpenOrder 方法先 new 一个 Order 对象,把当前 Symbol()、订单类型、MagicNumber 以及由资金管理器算出的 Lots 一次性写入。注意 Lots 不是硬编码,而是 _moneyManager.GetLotSize() 动态给的,这对外汇和贵金属这类高杠杆品种尤其关键,仓位错了可能瞬间吞掉净值。 买单分支里 OpenPrice 取 Ask_LibFunc(),卖单取 Bid_LibFunc(),SL 初值设 -DBL_MAX、TP 设 DBL_MAX,等于先放开风控边界,等具体策略再覆盖。这种写法让你在调试时能看到订单先以极端边界挂出,再被策略修正,方便排查谁改了止损。 下面这段是原文里 OpenOrder 的核心片段,逐行拆一下: Order* order = new Order(mustBeVisibleOnChart); // 新建订单,按参数决定是否图表可见 order.SymbolCode = Symbol(); // 绑定当前图表交易品种 order.Type = orderType; // 写入市价买/卖等类型 order.MagicNumber = MagicNumber; // 打上标识码,避免与其他EA冲突 order.Lots = _moneyManager.GetLotSize(); // 动态手数,由资金管理器决定 if (order.Type == ORDER_TYPE_BUY) { // 买单处理 order.OpenPrice = Ask_LibFunc(); // 开仓价用卖一价 order.StopLoss = -DBL_MAX; // 初始SL压到最小 order.TakeProfit = DBL_MAX; // 初始TP顶到最大 } else if (order.Type == ORDER_TYPE_SELL) { // 卖单处理 order.OpenPrice = Bid_LibFunc(); // 开仓价用买一价

MQL5 / C++
Order* order = new Order(mustBeVisibleOnChart);      class=class="str">"cmt">//--- Create new order
   order.SymbolCode = Symbol();                            class=class="str">"cmt">//--- Set symbol
   order.Type = orderType;                                 class=class="str">"cmt">//--- Set order type
   order.MagicNumber = MagicNumber;                        class=class="str">"cmt">//--- Set magic number
   order.Lots = _moneyManager.GetLotSize();                class=class="str">"cmt">//--- Set lot size
   if (order.Type == ORDER_TYPE_BUY) {                     class=class="str">"cmt">//--- Check Buy order
      order.OpenPrice = Ask_LibFunc();                     class=class="str">"cmt">//--- Set open price to Ask
      order.StopLoss = -DBL_MAX;                           class=class="str">"cmt">//--- Set initial SL to minimum
      order.TakeProfit = DBL_MAX;                          class=class="str">"cmt">//--- Set initial TP to maximum
   } else if (order.Type == ORDER_TYPE_SELL) {             class=class="str">"cmt">//--- Check Sell order
      order.OpenPrice = Bid_LibFunc();                     class=class="str">"cmt">//--- Set open price to Bid

建仓模块的初始值与动作分发

在订单对象初始化阶段,把止损设为 DBL_MAX、止盈设为 -DBL_MAX,相当于先占位‘不触发’,等后续逻辑再改写。LowestProfitPips 与 HighestProfitPips 也用同样的极值初始化,方便后面比较时一定会被真实盈亏刷新。 OpenModuleBase 只做两件事:把策略实例和资金管理实例存进成员变量。真正的交易动作在 GetTradeActions 里产生——只要钱包里已有持仓(Count() > 0),就取第一单的方向。 若首单是 Buy,代码会向临时数组追加 OpenBuyAction 与 CloseBuyAction 两个动作;若是 Sell 则追加 OpenSellAction。ArrayResize 每次 +1 且预留 8 个内存槽,避免在高频调用里频繁重分配。外汇与贵金属杠杆高,这类模块逻辑错误可能瞬间放大亏损,建议在 MT5 策略测试器里单步验证数组下标。

MQL5 / C++
order.StopLoss = DBL_MAX;                      class=class="str">"cmt">//--- Set initial SL to maximum
order.TakeProfit = -DBL_MAX;                   class=class="str">"cmt">//--- Set initial TP to minimum
}
order.LowestProfitPips = DBL_MAX;              class=class="str">"cmt">//--- Set initial lowest profit
order.HighestProfitPips = -DBL_MAX;            class=class="str">"cmt">//--- Set initial highest profit
order.Comment = OrderComment;                  class=class="str">"cmt">//--- Set order comment
OrderRepository::CalculateAndSetCommision(order); class=class="str">"cmt">//--- Calculate and set commission
class="kw">return order;                                  class=class="str">"cmt">//--- Return order
}
class="kw">public:
class=class="str">"cmt">//--- Initialize open module
class="type">void OpenModuleBase(AdvisorStrategy* advisorStrategy, IMoneyManager* moneyManager) {
  _advisorStrategy = advisorStrategy;           class=class="str">"cmt">//--- Set advisor strategy
  _moneyManager = moneyManager;                 class=class="str">"cmt">//--- Set money manager
}
class=class="str">"cmt">//--- Retrieve trade actions
class="type">void GetTradeActions(Wallet* wallet, TradingModuleDemand preventOpenDemand, TradeAction& result[]) {
  TradeAction tempresult[];                     class=class="str">"cmt">//--- Declare temporary actions array
  if (wallet.GetOpenOrders().Count() > class="num">0) {     class=class="str">"cmt">//--- Check if open orders exist
    Order* firstOrder = wallet.GetOpenOrders().Get(class="num">0); class=class="str">"cmt">//--- Get first open order
    if (firstOrder.Type == ORDER_TYPE_BUY) {    class=class="str">"cmt">//--- Check if Buy order
      ArrayResize(tempresult, ArraySize(tempresult) + class="num">1, class="num">8); class=class="str">"cmt">//--- Resize array
      tempresult[class="num">0] = OpenBuyAction;            class=class="str">"cmt">//--- Add open Buy action
      ArrayResize(tempresult, ArraySize(tempresult) + class="num">1, class="num">8); class=class="str">"cmt">//--- Resize array
      tempresult[class="num">1] = CloseBuyAction;           class=class="str">"cmt">//--- Add close Buy action
    } else if (firstOrder.Type == ORDER_TYPE_SELL) { class=class="str">"cmt">//--- Check if Sell order
      ArrayResize(tempresult, ArraySize(tempresult) + class="num">1, class="num">8); class=class="str">"cmt">//--- Resize array
      tempresult[class="num">0] = OpenSellAction;           class=class="str">"cmt">//--- Add open Sell action

「动作数组的增量扩容与开仓需求屏蔽」

这段逻辑在交易模块里负责把待执行动作先塞进临时数组 tempresult,再按当前开仓限制过滤后搬进正式数组 result。ArrayResize 的第三个参数 8 是预留内存步长,连续多次 +1 扩容时能减少堆重分配次数,在高频信号刷新场景下可降低约数毫秒级的延迟抖动。 当检测到持仓单类型不受支持时,代码用 Alert 把订单类型打印出来而非静默跳过,这对排查 EA 在贵金属跳空时段误判订单类别很有用。外汇与贵金属杠杆交易风险偏高,这类日志能在实盘前帮你在策略测试器里复现异常路径。 for 循环里的 preventOpenDemand 判断是核心闸门:NoOpenDemand 会拦掉所有 OpenBuy/OpenSell,NoBuyDemand 只拦多单,NoSellDemand 只拦空单,其余动作 continue 跳过。改写时若把 result 数组直接赋临时值而非 ArraySize(result)-1 追加,可能在并发信号下丢失前序动作。 GetCombinedOpenDemand 用 NoneDemand 起步,遇到 OpenBuySellDemand 立即返回,说明多空同开诉求优先级最高。你可以把这段抄进 MT5 的 include 里,把 8 改成 16 观察大数组下的内存占用曲线。

MQL5 / C++
ArrayResize(tempresult, ArraySize(tempresult) + class="num">1, class="num">8); class=class="str">"cmt">//--- Resize array
tempresult[class="num">1] = CloseSellAction;                 class=class="str">"cmt">//--- Add close Sell action
} else {
   Alert("Unsupported ordertype. Ordertype: " + DoubleToStr(firstOrder.Type)); class=class="str">"cmt">//--- Log error
}
} else {
   ArrayResize(tempresult, ArraySize(tempresult) + class="num">1, class="num">8); class=class="str">"cmt">//--- Resize array
   tempresult[class="num">0] = OpenBuyAction;                       class=class="str">"cmt">//--- Add open Buy action
   ArrayResize(tempresult, ArraySize(tempresult) + class="num">1, class="num">8); class=class="str">"cmt">//--- Resize array
   tempresult[class="num">1] = OpenSellAction;                      class=class="str">"cmt">//--- Add open Sell action
}
for (class="type">int i = class="num">0; i < ArraySize(tempresult); i++) {       class=class="str">"cmt">//--- Iterate actions
   if ((preventOpenDemand == NoOpenDemand && (tempresult[i] == OpenBuyAction || tempresult[i] == OpenSellAction)) || class=class="str">"cmt">//--- Check no open demand
       (preventOpenDemand == NoBuyDemand && tempresult[i] == OpenBuyAction) || class=class="str">"cmt">//--- Check no Buy demand
       (preventOpenDemand == NoSellDemand && tempresult[i] == OpenSellAction)) { class=class="str">"cmt">//--- Check no Sell demand
      class="kw">continue;                                                        class=class="str">"cmt">//--- Skip action
   }
   ArrayResize(result, ArraySize(result) + class="num">1, class="num">8);     class=class="str">"cmt">//--- Resize result array
   result[ArraySize(result) - class="num">1] = tempresult[i];    class=class="str">"cmt">//--- Add action
}
class=class="str">"cmt">//--- Register trade signal(empty implementation)
class="kw">virtual class="type">void RegisterTradeSignal(ITradingModuleSignal* tradeSignal) {} class=class="str">"cmt">//--- Do nothing
class=class="str">"cmt">//--- Combine open demands
class="kw">static TradingModuleDemand GetCombinedOpenDemand(TradingModuleDemand &openDemands[]) {
   TradingModuleDemand result = NoneDemand;           class=class="str">"cmt">//--- Initialize result
   for (class="type">int i = class="num">0; i < ArraySize(openDemands); i++) { class=class="str">"cmt">//--- Iterate demands
      if (result == OpenBuySellDemand) {              class=class="str">"cmt">//--- Check if Buy/Sell demand
         class="kw">return OpenBuySellDemand;                    class=class="str">"cmt">//--- Return Buy/Sell demand
      }
      if (openDemands[i] == OpenBuySellDemand) {      class=class="str">"cmt">//--- Check if demand is Buy/Sell

◍ 多模块信号怎么合并成一张指令

在 MT5 的 EA 架构里,常把不同逻辑拆成多个交易模块,每个模块独立产出开仓或平仓倾向。问题来了:当模块 A 要买、模块 B 要卖,引擎该听谁的?这段静态函数就是干合并用的。 开仓合并的逻辑先看 result 初值 NoneDemand,遍历 openDemands 数组。若当前已是 OpenBuySellDemand 则维持;若为空且碰到 OpenBuyDemand 或 OpenSellDemand 就单向赋值;若已有买而碰到卖、或已有卖而碰到买,直接升级成双向 OpenBuySellDemand。 平仓侧 GetCombinedCloseDemand 更狠:只要 result 或数组里任一元素等于 CloseBuySellDemand,立刻 return 双向平仓,不再往下比。这意味着平仓冲突的优先级高于开仓,实盘里能更快解锁保证金。 把下面代码贴进 MT5 的 include 里,起个 EURUSD 的回测,故意让两个模块反向,观察日志里 result 是不是如预期跳到双向——外汇和贵金属杠杆高,信号合并错乱可能瞬间放大回撤,验证完再上真仓。

MQL5 / C++
result = OpenBuySellDemand;                 class=class="str">"cmt">//--- Set Buy/Sell demand
      } else if (result == NoneDemand && openDemands[i] == OpenBuyDemand) { class=class="str">"cmt">//--- Check Buy demand
            result = OpenBuyDemand;                            class=class="str">"cmt">//--- Set Buy demand
      } else if (result == NoneDemand && openDemands[i] == OpenSellDemand) { class=class="str">"cmt">//--- Check Sell demand
            result = OpenSellDemand;                           class=class="str">"cmt">//--- Set Sell demand
      } else if (result == OpenBuyDemand && openDemands[i] == OpenSellDemand) { class=class="str">"cmt">//--- Check mixed demands
            result = OpenBuySellDemand;                        class=class="str">"cmt">//--- Set Buy/Sell demand
      } else if (result == OpenSellDemand && openDemands[i] == OpenBuyDemand) { class=class="str">"cmt">//--- Check mixed demands
            result = OpenBuySellDemand;                        class=class="str">"cmt">//--- Set Buy/Sell demand
      }
      }
      class="kw">return result;                                           class=class="str">"cmt">//--- Return combined demand
   }
   class=class="str">"cmt">//--- Combine close demands
   class="kw">static TradingModuleDemand GetCombinedCloseDemand(TradingModuleDemand &closeDemands[]) {
      TradingModuleDemand result = NoneDemand;                 class=class="str">"cmt">//--- Initialize result
      for (class="type">int i = class="num">0; i < ArraySize(closeDemands); i++) {      class=class="str">"cmt">//--- Iterate demands
         if (result == CloseBuySellDemand) {                   class=class="str">"cmt">//--- Check if Buy/Sell demand
            class="kw">return CloseBuySellDemand;                         class=class="str">"cmt">//--- Return Buy/Sell demand
         }
         if (closeDemands[i] == CloseBuySellDemand) {          class=class="str">"cmt">//--- Check if demand is Buy/Sell
            result = CloseBuySellDemand;                       class=class="str">"cmt">//--- Set Buy/Sell demand
         } else if (result == NoneDemand && closeDemands[i] == CloseBuyDemand) { class=class="str">"cmt">//--- Check Buy demand
            result = CloseBuyDemand;                           class=class="str">"cmt">//--- Set Buy demand
         } else if (result == NoneDemand && closeDemands[i] == CloseSellDemand) { class=class="str">"cmt">//--- Check Sell demand
            result = CloseSellDemand;                          class=class="str">"cmt">//--- Set Sell demand
         } else if (result == CloseBuyDemand && closeDemands[i] == CloseSellDemand) { class=class="str">"cmt">//--- Check mixed demands

多单模块里的开仓需求合并逻辑

这段代码片段展示了开仓模块里如何处理买卖需求冲突,以及限制同时持仓数量的基础结构。核心在于当 CloseBuyDemand 与 CloseSellDemand 同时出现时,代码将其合并为 CloseBuySellDemand,避免双向平仓指令互相打架。 实际跑起来时,输入参数 MaxNumberOfOpenOrders1 默认设为 1,意味着同一时刻钱包里只允许 1 笔未平订单;外汇与贵金属杠杆高,这种硬限制能压住过度交易冲动,但也可能错过顺势加仓机会。 MultipleOpenModule_1 构造函数里调用了 _advisorStrategy.SetFireOnlyWhenReset(true),信号只在重置后触发一次。想验证的话,把 true 改成 false,MT5 策略测试器里能看到信号 firing 频率明显上升,回测曲线波动大概率更剧烈。

MQL5 / C++
result = CloseBuySellDemand;                     class=class="str">"cmt">//--- Set Buy/Sell demand
      } else if (result == CloseSellDemand && closeDemands[i] == CloseBuyDemand) { class=class="str">"cmt">//--- Check mixed demands
            result = CloseBuySellDemand;                     class=class="str">"cmt">//--- Set Buy/Sell demand
      }
      }
      class="kw">return result;                                        class=class="str">"cmt">//--- Return combined demand
   }
   class=class="str">"cmt">//--- Retrieve number of open orders
   class="type">int GetNumberOfOpenOrders(Wallet* wallet) {
      class="kw">return wallet.GetOpenOrders().Count();                class=class="str">"cmt">//--- Return open order count
   }
};
class=class="str">"cmt">//--- Define input for maximum open orders
input class="type">int MaxNumberOfOpenOrders1 = class="num">1;                       class=class="str">"cmt">//--- Set maximum number of open orders(class="kw">default: class="num">1)
class=class="str">"cmt">//--- Define class for multiple open module
class MultipleOpenModule_1 : class="kw">public OpenModuleBase {
class="kw">protected:
   TradingModuleDemand previousSignalDemand;                class=class="str">"cmt">//--- Store previous signal demand
class="kw">public:
   class=class="str">"cmt">//--- Initialize multiple open module
   class="type">void MultipleOpenModule_1(AdvisorStrategy* advisorStrategy, MoneyManager* moneyManager)
      : OpenModuleBase(advisorStrategy, moneyManager) {
      _advisorStrategy.SetFireOnlyWhenReset(true);          class=class="str">"cmt">//--- Configure signals to fire only when reset
   }
   class=class="str">"cmt">//--- Evaluate and act on signals
   TradingModuleDemand Evaluate(Wallet* wallet, TradingModuleDemand preventOpenDemand, class="type">int level) {
      TradingModuleDemand newSignalsDemand = EvaluateSignals(wallet, preventOpenDemand, level); class=class="str">"cmt">//--- Evaluate signals
      if (newSignalsDemand != NoneDemand) {                 class=class="str">"cmt">//--- Check if demand exists
         EvaluateOpenConditions(wallet, newSignalsDemand); class=class="str">"cmt">//--- Evaluate open conditions
      }
      class="kw">return newSignalsDemand;                              class=class="str">"cmt">//--- Return new signal demand
   }
   class=class="str">"cmt">//--- Evaluate open signals without acting
   TradingModuleDemand EvaluateOpenSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand, class="type">int requestedEvaluationLevel) {
      TradingModuleDemand openDemands[];                    class=class="str">"cmt">//--- Declare open demands array
      TradeAction tradeActionsToEvaluate[];                 class=class="str">"cmt">//--- Declare actions to evaluate
      GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); class=class="str">"cmt">//--- Retrieve actions
      AddPreviousDemandTradeActionIfMissing(tradeActionsToEvaluate); class=class="str">"cmt">//--- Add previous demand actions
      class="type">int level;                                            class=class="str">"cmt">//--- Declare level
      if (requestedEvaluationLevel == class="num">0) {                  class=class="str">"cmt">//--- Check if level unspecified

「开仓信号如何被逐级汇总」

这段逻辑处在策略评估的末端:先决定评估层级 level,再逐个扫描待评估动作,把通过的开放类动作塞进 openDemands 数组,最后用 GetCombinedOpenDemand 做合并。 若外部没指定 requestedEvaluationLevel(值为 0),代码会调 GetTopLevel 给 level 封顶;当钱包里已开订单数达到 MaxNumberOfOpenOrders1 时,level 再 +1,意味着在订单数触顶后倾向于用更高层级过滤信号。外汇与贵金属杠杆高,这种层级抬升可能直接压低开仓频率。 循环里遇到 CloseBuyAction 或 CloseSellAction 直接 continue 跳过,说明平仓动作不在这里参与开放需求统计。OpenBuy 与 OpenSell 分支对称,都用 ArrayResize(openDemands, newSize, 8) 以 8 为储备步长扩容,再写入对应 Demand 常量。 下面这段是原文核心片段,逐行拆完你就能在 MT5 里对照改自己的信号聚合器: level = _moneyManager.GetNextLevel(wallet); // 若走资金管理分支,按钱包状态取下一评估层级 } else { level = requestedEvaluationLevel; // 否则用调用方指定的层级 } for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { // 遍历所有待评估交易动作

if (tradeActionsToEvaluate[i] == CloseBuyActiontradeActionsToEvaluate[i] == CloseSellAction) { // 碰到平仓动作

continue; // 直接跳过,不纳入开仓需求 } if (requestedEvaluationLevel == 0) { // 若调用方没指定层级 level = GetTopLevel(tradeActionsToEvaluate[i], level); // 按动作类型封顶层级 if (wallet.GetOpenOrders().Count() >= MaxNumberOfOpenOrders1) { // 已开订单数达上限 level += 1; // 层级加一,提高过滤门槛 } } if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], level)) { // 策略在該层级给出建议 if (tradeActionsToEvaluate[i] == OpenBuyAction) { // 是买开动作 int size = ArraySize(openDemands); // 取当前数组长度 int newSize = size + 1; // 新长度 ArrayResize(openDemands, newSize, 8); // 以8为步长扩容 openDemands[newSize - 1] = OpenBuyDemand; // 尾部写入买开需求 } else if (tradeActionsToEvaluate[i] == OpenSellAction) { // 是卖开动作 int size = ArraySize(openDemands); // 取当前数组长度 int newSize = size + 1; // 新长度 ArrayResize(openDemands, newSize, 8); // 以8为步长扩容 openDemands[newSize - 1] = OpenSellDemand; // 尾部写入卖开需求 } } } TradingModuleDemand combinedOpenSignalDemand = OpenModuleBase::GetCombinedOpenDemand(openDemands); // 合并所有开放需求为统一信号 把 MaxNumberOfOpenOrders1 调小,比如从默认设成 3,能在回测中明显看到层级 +1 触发更早,从而观察信号稀疏化对净值曲线的可能影响。

MQL5 / C++
level = _moneyManager.GetNextLevel(wallet);              class=class="str">"cmt">//--- Set level based on orders
} else {
   level = requestedEvaluationLevel;                       class=class="str">"cmt">//--- Use specified level
}
for (class="type">int i = class="num">0; i < ArraySize(tradeActionsToEvaluate); i++) { class=class="str">"cmt">//--- Iterate actions
   if (tradeActionsToEvaluate[i] == CloseBuyAction || tradeActionsToEvaluate[i] == CloseSellAction) { class=class="str">"cmt">//--- Skip close actions
      class="kw">continue;                                            class=class="str">"cmt">//--- Move to next
   }
   if (requestedEvaluationLevel == class="num">0) {                    class=class="str">"cmt">//--- Check if level unspecified
      level = GetTopLevel(tradeActionsToEvaluate[i], level); class=class="str">"cmt">//--- Cap level
      if (wallet.GetOpenOrders().Count() >= MaxNumberOfOpenOrders1) { class=class="str">"cmt">//--- Check order limit
         level += class="num">1;                                       class=class="str">"cmt">//--- Increment level
      }
   }
   if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], level)) { class=class="str">"cmt">//--- Check if action advised
      if (tradeActionsToEvaluate[i] == OpenBuyAction) {    class=class="str">"cmt">//--- Check Buy action
         class="type">int size = ArraySize(openDemands);                class=class="str">"cmt">//--- Get current size
         class="type">int newSize = size + class="num">1;                           class=class="str">"cmt">//--- Calculate new size
         ArrayResize(openDemands, newSize, class="num">8);             class=class="str">"cmt">//--- Resize array
         openDemands[newSize - class="num">1] = OpenBuyDemand;         class=class="str">"cmt">//--- Add Buy demand
      } else if (tradeActionsToEvaluate[i] == OpenSellAction) { class=class="str">"cmt">//--- Check Sell action
         class="type">int size = ArraySize(openDemands);                class=class="str">"cmt">//--- Get current size
         class="type">int newSize = size + class="num">1;                           class=class="str">"cmt">//--- Calculate new size
         ArrayResize(openDemands, newSize, class="num">8);             class=class="str">"cmt">//--- Resize array
         openDemands[newSize - class="num">1] = OpenSellDemand;        class=class="str">"cmt">//--- Add Sell demand
      }
   }
}
TradingModuleDemand combinedOpenSignalDemand = OpenModuleBase::GetCombinedOpenDemand(openDemands); class=class="str">"cmt">//--- Combine open demands

◍ 多单信号过滤与双向挂单动作的生成逻辑

这段逻辑先基于前一级需求修正当前的需求信号,再把被禁止开仓的需求过滤掉,最后回传最终的开仓信号。注意 level - 1 的传参,意味着信号是参照上一档位的需求做的回溯,不是当前价附近的裸信号。 GetTradeActions 负责把钱包状态翻译成具体交易动作。若钱包里已有持仓(Count() > 0),就取第一张单的类型:Buy 单则往结果里塞 OpenBuyAction 和 CloseBuyAction,Sell 单则对应塞 OpenSellAction 和 CloseSellAction;遇到非买卖类型的单会弹 Alert("Unsupported ordertype") 报错。 若钱包无持仓,函数直接构造一组双向动作:先 resize 加一位放 OpenBuyAction,再 resize 加一位放 OpenSellAction。ArrayResize 的第三个参数 8 是内存预留步长,连续加元素时能把重新分配次数压到较低水平。 在 MT5 里把这段接进 EA 后,可故意让钱包先空仓再已有 Buy 单,分别打印 result 数组长度——空仓时应为 2(买+卖),有 Buy 单时也为 2(开买+平买),以此验证分支没写反。外汇与贵金属波动剧烈,这类自动动作链一旦分支错配,可能在没有止损预期的位置下单,实盘前务必用策略测试器跑通。

MQL5 / C++
TradingModuleDemand multiOrderOpenSignal = GetOpenDemandBasedOnPreviousOpenDemand(combinedOpenSignalDemand, level - class="num">1); class=class="str">"cmt">//--- Adjust for previous demand
   multiOrderOpenSignal = FilterPreventOpenDemand(multiOrderOpenSignal, preventOpenDemand); class=class="str">"cmt">//--- Filter prevent-open demands
   class="kw">return multiOrderOpenSignal;                                                                  class=class="str">"cmt">//--- Return final open signal
   }
   class=class="str">"cmt">//--- Retrieve trade actions(custom for multiple orders)
   class="type">void GetTradeActions(Wallet* wallet, TradingModuleDemand preventOpenDemand, TradeAction& result[]) {
      if (wallet.GetOpenOrders().Count() > class="num">0) {                     class=class="str">"cmt">//--- Check if open orders exist
         Order* firstOrder = wallet.GetOpenOrders().Get(class="num">0);         class=class="str">"cmt">//--- Get first open order
         if (firstOrder.Type == ORDER_TYPE_BUY) {                   class=class="str">"cmt">//--- Check if Buy order
            ArrayResize(result, ArraySize(result) + class="num">1, class="num">8);          class=class="str">"cmt">//--- Resize array
            result[class="num">0] = OpenBuyAction;                              class=class="str">"cmt">//--- Add open Buy action
            ArrayResize(result, ArraySize(result) + class="num">1, class="num">8);          class=class="str">"cmt">//--- Resize array
            result[class="num">1] = CloseBuyAction;                             class=class="str">"cmt">//--- Add close Buy action
         } else if (firstOrder.Type == ORDER_TYPE_SELL) {           class=class="str">"cmt">//--- Check if Sell order
            ArrayResize(result, ArraySize(result) + class="num">1, class="num">8);          class=class="str">"cmt">//--- Resize array
            result[class="num">0] = OpenSellAction;                             class=class="str">"cmt">//--- Add open Sell action
            ArrayResize(result, ArraySize(result) + class="num">1, class="num">8);          class=class="str">"cmt">//--- Resize array
            result[class="num">1] = CloseSellAction;                            class=class="str">"cmt">//--- Add close Sell action
         } else {
            Alert("Unsupported ordertype");                         class=class="str">"cmt">//--- Log error
         }
      } else {
         ArrayResize(result, ArraySize(result) + class="num">1, class="num">8);             class=class="str">"cmt">//--- Resize array
         result[class="num">0] = OpenBuyAction;                                 class=class="str">"cmt">//--- Add open Buy action
         ArrayResize(result, ArraySize(result) + class="num">1, class="num">8);             class=class="str">"cmt">//--- Resize array
         result[class="num">1] = OpenSellAction;                                class=class="str">"cmt">//--- Add open Sell action
      }
   }

平仓信号的评估与合并逻辑

EA 在每一轮 tick 里要先判断该不该平掉已有仓位,这段函数就是专门处理平仓诉求的。它先取出当前钱包允许的交易动作,再逐个筛查其中属于 CloseBuyAction 和 CloseSellAction 的指令,非平仓类直接 continue 跳过。 筛查时用 _advisorStrategy.GetAdvice(action, 1) 做一级信号确认。若策略在 level 1 建议平多,就给 closeDemands 数组追加 CloseBuyDemand;若建议平空则追加 CloseSellDemand。数组每次扩容预留 8 个元素(ArrayResize 第三参),减少频繁重分配的开销。 所有平仓诉求收集完后,交给 OpenModuleBase::GetCombinedCloseDemand 做合并,返回统一的 TradingModuleDemand。你在 MT5 里接自己策略时,可以把 level 1 换成别的过滤强度,观察平仓频率变化——外汇和贵金属波动剧烈,信号误触可能带来高频磨损,务必先在模拟盘验证。 下面这段是原文核心函数,逐行拆解看结构: //--- Evaluate close signals TradingModuleDemand EvaluateCloseSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand) { // 定义平仓信号评估函数,入参为钱包指针与阻止开仓诉求 TradingModuleDemand closeDemands[]; // 声明平仓诉求动态数组 TradeAction tradeActionsToEvaluate[]; // 声明待评估交易动作数组 GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); // 从钱包拉取待评估动作 for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { // 遍历每个动作 if (tradeActionsToEvaluate[i] != CloseBuyAction && tradeActionsToEvaluate[i] != CloseSellAction) { // 若非平买也非平卖 continue; // 跳过,进入下一轮 } if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], 1)) { // 一级建议触发 if (tradeActionsToEvaluate[i] == CloseBuyAction) { // 若是平买动作 int size = ArraySize(closeDemands); // 取当前数组长 int newSize = size + 1; // 新长度+1 ArrayResize(closeDemands, newSize, 8); // 扩容并预留8 closeDemands[newSize - 1] = CloseBuyDemand; // 末尾写入平买诉求 } else if (tradeActionsToEvaluate[i] == CloseSellAction) { // 若是平卖动作 int size = ArraySize(closeDemands); // 取当前数组长 int newSize = size + 1; // 新长度+1 ArrayResize(closeDemands, newSize, 8); // 扩容并预留8 closeDemands[newSize - 1] = CloseSellDemand; // 末尾写入平卖诉求 } } } TradingModuleDemand combinedCloseSignalDemand = OpenModuleBase::GetCombinedCloseDemand(closeDemands); // 合并平仓诉求 return combinedCloseSignalDemand; // 返回合并结果 }

MQL5 / C++
  class=class="str">"cmt">//--- Evaluate close signals
  TradingModuleDemand EvaluateCloseSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand) {
    TradingModuleDemand closeDemands[];              class=class="str">"cmt">//--- Declare close demands array
    TradeAction tradeActionsToEvaluate[];            class=class="str">"cmt">//--- Declare actions to evaluate
    GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); class=class="str">"cmt">//--- Retrieve actions
    for (class="type">int i = class="num">0; i < ArraySize(tradeActionsToEvaluate); i++) { class=class="str">"cmt">//--- Iterate actions
      if (tradeActionsToEvaluate[i] != CloseBuyAction && tradeActionsToEvaluate[i] != CloseSellAction) { class=class="str">"cmt">//--- Skip non-close actions
        class="kw">continue;                                   class=class="str">"cmt">//--- Move to next
      }
      if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], class="num">1)) { class=class="str">"cmt">//--- Check if action advised(level class="num">1)
        if (tradeActionsToEvaluate[i] == CloseBuyAction) { class=class="str">"cmt">//--- Check Buy close
          class="type">int size = ArraySize(closeDemands);        class=class="str">"cmt">//--- Get current size
          class="type">int newSize = size + class="num">1;                    class=class="str">"cmt">//--- Calculate new size
          ArrayResize(closeDemands, newSize, class="num">8);     class=class="str">"cmt">//--- Resize array
          closeDemands[newSize - class="num">1] = CloseBuyDemand;  class=class="str">"cmt">//--- Add Buy close demand
        } else if (tradeActionsToEvaluate[i] == CloseSellAction) { class=class="str">"cmt">//--- Check Sell close
          class="type">int size = ArraySize(closeDemands);        class=class="str">"cmt">//--- Get current size
          class="type">int newSize = size + class="num">1;                    class=class="str">"cmt">//--- Calculate new size
          ArrayResize(closeDemands, newSize, class="num">8);     class=class="str">"cmt">//--- Resize array
          closeDemands[newSize - class="num">1] = CloseSellDemand; class=class="str">"cmt">//--- Add Sell close demand
        }
      }
    }
    TradingModuleDemand combinedCloseSignalDemand = OpenModuleBase::GetCombinedCloseDemand(closeDemands); class=class="str">"cmt">//--- Combine close demands
    class="kw">return combinedCloseSignalDemand;               class=class="str">"cmt">//--- Return combined demand
  }

「持仓上限如何抬升评估层级」

这段逻辑处理的是:在已有挂单或持仓接近上限时,系统如何动态抬高对交易动作的评估层级,再把通过策略顾问认可的开仓意图写进需求数组。 先声明待评估动作数组,从钱包与防重复开仓标记里取出候选动作,并补回上一轮缺失的需求动作。若调用方没指定评估层级(requestedEvaluationLevel==0),就由资金管理器按当前订单数给一个下一层级;否则直接用指定值。 遍历每个动作时,先用 GetTopLevel 封顶,再把层级与钱包实际持仓数比对:当已开订单数 ≥ MaxNumberOfOpenOrders1,评估层级额外 +1,意味着临近满仓时系统倾向更保守地看待新开仓。 若策略顾问在该层级返回 true,就按动作类型把 OpenBuyDemand 或 OpenSellDemand 追加进 openDemands,数组以 8 为步长扩容。外汇与贵金属杠杆交易高风险,这类自动加层逻辑可能让你在震荡市错过突破,也可能在趋势市少挨一刀,开 MT5 把 MaxNumberOfOpenOrders1 改成 2 和 5 分别跑一遍回测最直观。

MQL5 / C++
TradeAction tradeActionsToEvaluate[]; class=class="str">"cmt">//--- Declare actions to evaluate
GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); class=class="str">"cmt">//--- Retrieve actions
AddPreviousDemandTradeActionIfMissing(tradeActionsToEvaluate); class=class="str">"cmt">//--- Add previous demand actions
class="type">int moneyManagementLevel; class=class="str">"cmt">//--- Declare level
if (requestedEvaluationLevel == class="num">0) { class=class="str">"cmt">//--- Check if level unspecified
   moneyManagementLevel = _moneyManager.GetNextLevel(wallet); class=class="str">"cmt">//--- Set level based on orders
} else {
   moneyManagementLevel = requestedEvaluationLevel; class=class="str">"cmt">//--- Use specified level
}
for (class="type">int i = class="num">0; i < ArraySize(tradeActionsToEvaluate); i++) { class=class="str">"cmt">//--- Iterate actions
   class="type">int tradeActionEvaluationLevel = GetTopLevel(tradeActionsToEvaluate[i], moneyManagementLevel); class=class="str">"cmt">//--- Cap level
   if (wallet.GetOpenOrders().Count() >= MaxNumberOfOpenOrders1) { class=class="str">"cmt">//--- Check order limit
      tradeActionEvaluationLevel += class="num">1; class=class="str">"cmt">//--- Increment level
   }
   if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], tradeActionEvaluationLevel)) { class=class="str">"cmt">//--- Check if action advised
      if (tradeActionsToEvaluate[i] == OpenBuyAction) { class=class="str">"cmt">//--- Check Buy open
         class="type">int size = ArraySize(openDemands); class=class="str">"cmt">//--- Get current size
         class="type">int newSize = size + class="num">1; class=class="str">"cmt">//--- Calculate new size
         ArrayResize(openDemands, newSize, class="num">8); class=class="str">"cmt">//--- Resize array
         openDemands[newSize - class="num">1] = OpenBuyDemand; class=class="str">"cmt">//--- Add Buy demand
      } else if (tradeActionsToEvaluate[i] == OpenSellAction) { class=class="str">"cmt">//--- Check Sell open
         class="type">int size = ArraySize(openDemands); class=class="str">"cmt">//--- Get current size
         class="type">int newSize = size + class="num">1; class=class="str">"cmt">//--- Calculate new size
         ArrayResize(openDemands, newSize, class="num">8); class=class="str">"cmt">//--- Resize array
         openDemands[newSize - class="num">1] = OpenSellDemand; class=class="str">"cmt">//--- Add Sell demand
      } else if (tradeActionsToEvaluate[i] == CloseBuyAction) { class=class="str">"cmt">//--- Check Buy close
         class="type">int size = ArraySize(closeDemands); class=class="str">"cmt">//--- Get current size

◍ 平仓与开仓需求的合并返回逻辑

这段逻辑在遍历交易动作数组后,把识别出的平仓需求分别塞进 closeDemands 动态数组。每次遇到 CloseBuyAction 或 CloseSellAction,先用 ArraySize 取当前长度,newSize 在旧长度上加 1,ArrayResize 的第三参填 8,意味着预留 8 个元素的存储增量,避免频繁重分配。 closeDemands[newSize-1] 写入对应需求枚举后,循环结束调用 GetCombinedCloseDemand 做合并。若合并结果不是 NoneDemand,函数直接 return,优先处理平仓,不再往下走开仓分支。 开仓侧则用 GetCombinedOpenDemand 拿基础信号,再经 GetOpenDemandBasedOnPreviousOpenDemand 按已开单数调整,previousSignalDemand 被更新为本次合并开仓需求,最后过一道 FilterPreventOpenDemand 把禁开信号滤掉再返回。 EvaluateOpenConditions 里若收到 OpenBuySellDemand 直接 return,说明该模块不支持锁仓对冲。否则取 AccountFreeMargin_LibFunc 的可用保证金,对 BUY 信号用 MarginRequired 校验手数所需保证金是否够用。外汇与贵金属保证金波动大,这类校验失败可能随时发生,实盘前建议在 MT5 策略测试器里改 _moneyManager.GetLotSize() 跑一遍边际场景。

MQL5 / C++
class="type">int newSize = size + class="num">1;                      class=class="str">"cmt">//--- Calculate new size
ArrayResize(closeDemands, newSize, class="num">8);       class=class="str">"cmt">//--- Resize array
closeDemands[newSize - class="num">1] = CloseBuyDemand; class=class="str">"cmt">//--- Add Buy close demand
} else if (tradeActionsToEvaluate[i] == CloseSellAction) { class=class="str">"cmt">//--- Check Sell close
class="type">int size = ArraySize(closeDemands);          class=class="str">"cmt">//--- Get current size
class="type">int newSize = size + class="num">1;                      class=class="str">"cmt">//--- Calculate new size
ArrayResize(closeDemands, newSize, class="num">8);       class=class="str">"cmt">//--- Resize array
closeDemands[newSize - class="num">1] = CloseSellDemand; class=class="str">"cmt">//--- Add Sell close demand
}
}
}
TradingModuleDemand combinedCloseSignalDemand = OpenModuleBase::GetCombinedCloseDemand(closeDemands); class=class="str">"cmt">//--- Combine close demands
if (combinedCloseSignalDemand != NoneDemand) {        class=class="str">"cmt">//--- Check if close demand exists
class="kw">return combinedCloseSignalDemand;                     class=class="str">"cmt">//--- Return close demand
}
TradingModuleDemand combinedOpenSignalDemand = OpenModuleBase::GetCombinedOpenDemand(openDemands); class=class="str">"cmt">//--- Combine open demands
TradingModuleDemand multiOrderOpenSignal = GetOpenDemandBasedOnPreviousOpenDemand(combinedOpenSignalDemand, GetNumberOfOpenOrders(wallet)); class=class="str">"cmt">//--- Adjust for previous demand
previousSignalDemand = combinedOpenSignalDemand;       class=class="str">"cmt">//--- Update previous demand
multiOrderOpenSignal = FilterPreventOpenDemand(multiOrderOpenSignal, preventOpenDemand); class=class="str">"cmt">//--- Filter prevent-open demands
class="kw">return multiOrderOpenSignal;                           class=class="str">"cmt">//--- Return final signal
}
class=class="str">"cmt">//--- Evaluate open conditions and add orders
class="type">void EvaluateOpenConditions(Wallet* wallet, TradingModuleDemand signalDemand) {
if (signalDemand == OpenBuySellDemand) {              class=class="str">"cmt">//--- Check Buy/Sell demand
class="kw">return;                                               class=class="str">"cmt">//--- Exit(hedging not supported)
} else {
class="type">class="kw">double currentFreeMargin = AccountFreeMargin_LibFunc(); class=class="str">"cmt">//--- Retrieve free margin
class="type">class="kw">double requiredMargin;                                class=class="str">"cmt">//--- Declare required margin
if (signalDemand == OpenBuyDemand) {                  class=class="str">"cmt">//--- Check Buy demand
if (!MarginRequired(ORDER_TYPE_BUY, _moneyManager.GetLotSize(), requiredMargin)) { class=class="str">"cmt">//--- Check margin

开仓前的保证金闸门与反手逻辑

这段逻辑卡在信号转为实际挂单之前,先过两道保证金校验。Buy 和 Sell 分支都先调 MarginRequired 算所需保证金,若计算失败直接 return;随后比对 currentFreeMargin 与 requiredMargin,不足就走 HandleErrors 并记录日志后退出,避免透支账户。 外汇与贵金属杠杆高,保证金不足时硬开仓可能触发强平,这套前置拦截能降低非预期爆仓概率。 挂单动作本身很轻量:wallet.GetPendingOpenOrders().Add(OpenOrder(ORDER_TYPE_BUY, false)) 把市价 Buy 加入待开池,Sell 同理。注意第二个参数 false 代表非即时执行,交由后续模块统一处理。 反手判定在 GetOpenDemandBasedOnPreviousOpenDemand 里:若当前无持仓或前次信号为空,直接采用新信号;只有前次 Buy 转 Sell 或前次 Sell 转 Buy 才放行反向需求,等同允许一次干净的反手,不追同方向加仓。

MQL5 / C++
      class="kw">return;                                                        class=class="str">"cmt">//--- Exit if margin check fails
      }
      if (currentFreeMargin < requiredMargin) {                       class=class="str">"cmt">//--- Check sufficient margin
         HandleErrors("Not enough free margin to open buy order with requested volume."); class=class="str">"cmt">//--- Log error
         class="kw">return;                                                      class=class="str">"cmt">//--- Exit
      }
      wallet.GetPendingOpenOrders().Add(OpenOrder(ORDER_TYPE_BUY, false)); class=class="str">"cmt">//--- Add Buy order
      } else if (signalDemand == OpenSellDemand) {                    class=class="str">"cmt">//--- Check Sell demand
      if (!MarginRequired(ORDER_TYPE_SELL, _moneyManager.GetLotSize(), requiredMargin)) { class=class="str">"cmt">//--- Check margin
         class="kw">return;                                                      class=class="str">"cmt">//--- Exit if margin check fails
      }
      if (currentFreeMargin < requiredMargin) {                       class=class="str">"cmt">//--- Check sufficient margin
         HandleErrors("Not enough free margin to open sell order with requested volume."); class=class="str">"cmt">//--- Log error
         class="kw">return;                                                      class=class="str">"cmt">//--- Exit
      }
      wallet.GetPendingOpenOrders().Add(OpenOrder(ORDER_TYPE_SELL, false)); class=class="str">"cmt">//--- Add Sell order
      }
   }
   class=class="str">"cmt">//--- Adjust open demand based on previous demand
   TradingModuleDemand GetOpenDemandBasedOnPreviousOpenDemand(TradingModuleDemand openDemand, class="type">int numberOfOpenOrders) {
      if (numberOfOpenOrders == class="num">0 || previousSignalDemand == NoneDemand) { class=class="str">"cmt">//--- Check no orders or no previous demand
         class="kw">return openDemand;                                              class=class="str">"cmt">//--- Return current demand
      }
      if (previousSignalDemand == OpenBuyDemand && openDemand == OpenSellDemand) { class=class="str">"cmt">//--- Check Buy to Sell class="kw">switch
         class="kw">return openDemand;                                              class=class="str">"cmt">//--- Allow Sell demand
      } else if (previousSignalDemand == OpenSellDemand && openDemand == OpenBuyDemand) { class=class="str">"cmt">//--- Check Sell to Buy class="kw">switch
         class="kw">return openDemand;                                              class=class="str">"cmt">//--- Allow Buy demand
      }

「需求层级封顶与历史信号补位」

策略在评估多表达式信号时,必须先给遍历层级设一道天花板。GetTopLevel 会先向 _advisorStrategy 索要当前交易动作对应的表达式总数,若传入的 level 大于该数量,就强制把 level 压回 numberOfExpressions,避免越界访问空表达式。 历史需求补位是另一处容易漏掉的逻辑。AddPreviousDemandTradeActionIfMissing 先判断 previousSignalDemand 是否为 NoneDemand,若为空直接退出;否则按 Buy / Sell / BuySell 三种前一需求,把对应的 OpenBuyAction 或 OpenSellAction 塞进结果数组,保证上一根 K 线未成交的信号在本根不被吞掉。 具体到单一需求写入,AddPreviousDemandTradeAction 先用 foundPreviousDemand 标记,再跑一遍 result 数组确认同类型 action 是否已存在,存在才置 true。这一层去重能压住 MT5 回测里同一信号连发多单的概率,实盘外汇与贵金属波动剧烈,重信号堆叠会放大滑点风险。

MQL5 / C++
class="kw">private:
  class=class="str">"cmt">//--- Cap evaluation level
  class="type">int GetTopLevel(TradeAction tradeAction, class="type">int level) {
    class="type">int numberOfExpressions = _advisorStrategy.GetNumberOfExpressions(tradeAction); class=class="str">"cmt">//--- Retrieve expression count
    if (level > numberOfExpressions) { class=class="str">"cmt">//--- Check if level exceeds expressions
      level = numberOfExpressions; class=class="str">"cmt">//--- Cap level
    }
    class="kw">return level; class=class="str">"cmt">//--- Return capped level
  }
  class=class="str">"cmt">//--- Add previous demand action if missing
  class="type">void AddPreviousDemandTradeActionIfMissing(TradeAction& result[]) {
    if (previousSignalDemand == NoneDemand) { class=class="str">"cmt">//--- Check if no previous demand
      class="kw">return; class=class="str">"cmt">//--- Exit
    }
    class="type">bool foundPreviousDemand = false; class=class="str">"cmt">//--- Initialize found flag
    if (previousSignalDemand == OpenBuyDemand) { class=class="str">"cmt">//--- Check Buy demand
      AddPreviousDemandTradeAction(result, OpenBuyDemand, OpenBuyAction); class=class="str">"cmt">//--- Add Buy action
    } else if (previousSignalDemand == OpenSellDemand) { class=class="str">"cmt">//--- Check Sell demand
      AddPreviousDemandTradeAction(result, OpenSellDemand, OpenSellAction); class=class="str">"cmt">//--- Add Sell action
    } else if (previousSignalDemand == OpenBuySellDemand) { class=class="str">"cmt">//--- Check Buy/Sell demand
      AddPreviousDemandTradeAction(result, OpenBuyDemand, OpenBuyAction); class=class="str">"cmt">//--- Add Buy action
      AddPreviousDemandTradeAction(result, OpenSellDemand, OpenSellAction); class=class="str">"cmt">//--- Add Sell action
    }
  }
  class=class="str">"cmt">//--- Add specific previous demand action
  class="type">void AddPreviousDemandTradeAction(TradeAction& result[], TradingModuleDemand demand, TradeAction action) {
    class="type">bool foundPreviousDemand = false; class=class="str">"cmt">//--- Initialize found flag
    if (previousSignalDemand == demand) { class=class="str">"cmt">//--- Check matching demand
      for (class="type">int i = class="num">0; i < ArraySize(result); i++) { class=class="str">"cmt">//--- Iterate actions
        if (action == result[i]) { class=class="str">"cmt">//--- Check if action exists
          foundPreviousDemand = true; class=class="str">"cmt">//--- Set found flag
        }
      }

◍ 需求过滤与接口骨架

这段逻辑解决一个实战痛点:当多单挂单需求遇上「禁止买入」类风控信号时,系统不能傻等,而是要当场把需求改写为可执行的另一侧或清零。 FilterPreventOpenDemand 函数用枚举比对做硬过滤:若原需求是 OpenBuySellDemand 且风控给的是 NoBuyDemand,直接回退成 OpenSellDemand,保留卖侧可能。若给的是 NoOpenDemand,则无论多空一律返回 NoneDemand,概率上会完全暂停开仓。 末尾的 ITrader 接口只留两个方法——Init 做初始化、HandleTick 吃每笔报价。把交易员逻辑抽象成接口,意味着同一套风控模块可以热插拔进不同 EA,开 MT5 新建类继承它就能复用上面的过滤链。 外汇与贵金属波动剧烈,这类自动拦截只降低误操作概率,不消除爆仓风险,参数须先在策略测试器跑历史段验证。

MQL5 / C++
if (!foundPreviousDemand) {              class=class="str">"cmt">//--- 检查是否缺失先前需求
      ArrayResize(result, ArraySize(result) + class="num">1, class="num">8); class=class="str">"cmt">//--- 数组扩容,预留8元素步长
      result[ArraySize(result) - class="num">1] = action;     class=class="str">"cmt">//--- 把当前动作追加进结果数组
   }
}
class=class="str">"cmt">//--- 过滤阻止开仓的需求
TradingModuleDemand FilterPreventOpenDemand(TradingModuleDemand multiOrderOpendDemand, TradingModuleDemand preventOpenDemand) {
   if (multiOrderOpendDemand == NoneDemand) {      class=class="str">"cmt">//--- 检查无需求
      class="kw">return multiOrderOpendDemand;                class=class="str">"cmt">//--- 返回无需求
   } else if (multiOrderOpendDemand == OpenBuyDemand && (preventOpenDemand == NoBuyDemand || preventOpenDemand == NoOpenDemand)) { class=class="str">"cmt">//--- 检查被阻的买
      class="kw">return NoneDemand;                           class=class="str">"cmt">//--- 阻断买需求
   } else if (multiOrderOpendDemand == OpenSellDemand && (preventOpenDemand == NoSellDemand || preventOpenDemand == NoOpenDemand)) { class=class="str">"cmt">//--- 检查被阻的卖
      class="kw">return NoneDemand;                           class=class="str">"cmt">//--- 阻断卖需求
   } else if (multiOrderOpendDemand == OpenBuySellDemand) { class=class="str">"cmt">//--- 检查买卖双向需求
      if (preventOpenDemand == NoBuyDemand) {      class=class="str">"cmt">//--- 检查禁买
         class="kw">return OpenSellDemand;                    class=class="str">"cmt">//--- 仅允许卖需求
      } else if (preventOpenDemand == NoSellDemand) { class=class="str">"cmt">//--- 检查禁卖
         class="kw">return OpenBuyDemand;                     class=class="str">"cmt">//--- 仅允许买需求
      } else if (preventOpenDemand == NoOpenDemand) { class=class="str">"cmt">//--- 检查禁开
         class="kw">return NoneDemand;                        class=class="str">"cmt">//--- 阻断全部需求
      }
   }
   class="kw">return multiOrderOpendDemand;                   class=class="str">"cmt">//--- 返回未过滤需求
}
};
class=class="str">"cmt">//--- 定义交易员接口
interface ITrader {
   class="type">void HandleTick();                             class=class="str">"cmt">//--- 处理报价事件
   class="type">void Init();                                   class=class="str">"cmt">//--- 初始化交易员
}

EA 类的成员装配与析构清理

在 MT5 的 EA 架构里,把交易逻辑拆成策略、资金、钱包几个独立对象后,真正的粘合点在 EA 类自身。私有成员里用 _firstTick 标记首根 K 线,避免初始化前就跑信号;_tradeStrategy、_advisorStrategy、_moneyManager、_wallet 四个指针分别接管不同职责,互不耦合。 构造函数 EA() 里先置 _firstTick=true,随后 new 出 Wallet 并调用 SetLastClosedOrdersByTimeframe(DisplayOrderDuringTimeframe) 限定历史平仓单的回看周期。AdvisorStrategy 通过 RegisterOpenBuy / RegisterOpenSell 挂上 Level1 信号,权重参数填 1;MoneyManager 以钱包实例为构造入参,TradeStrategy 再包一层 MultipleOpenModule_1,并注册 TakeProfitCloseModule_1 与 StopLossCloseModule_1 做平仓。 析构 ~EA() 必须按创建逆序 delete 三个策略对象,否则 MT5 终端跑久了会漏内存。外汇与贵金属杠杆高,这类对象若未清理,实盘重加载 EA 时可能拖慢 tick 响应。 下面这段是原文里的核心声明与构造骨架,逐行拆完就能在 MetaEditor 里照抄验证。

MQL5 / C++
Wallet* GetWallet(); class=class="str">"cmt">//--- Retrieve wallet
};
class=class="str">"cmt">//--- Declare global trader pointer
ITrader *_ea; class=class="str">"cmt">//--- Store EA instance
class=class="str">"cmt">//--- Define main Expert Advisor class
class EA : class="kw">public ITrader {
class="kw">private:
  class="type">bool _firstTick; class=class="str">"cmt">//--- Track first tick
  TradeStrategy* _tradeStrategy; class=class="str">"cmt">//--- Store trade strategy
  AdvisorStrategy* _advisorStrategy; class=class="str">"cmt">//--- Store advisor strategy
  IMoneyManager* _moneyManager; class=class="str">"cmt">//--- Store money manager
  Wallet* _wallet; class=class="str">"cmt">//--- Store wallet
class="kw">public:
  class=class="str">"cmt">//--- Initialize EA
  class="type">void EA() {
    _firstTick = true; class=class="str">"cmt">//--- Set first tick flag
    _wallet = new Wallet(); class=class="str">"cmt">//--- Create wallet
    _wallet.SetLastClosedOrdersByTimeframe(DisplayOrderDuringTimeframe); class=class="str">"cmt">//--- Set closed orders timeframe
    _advisorStrategy = new AdvisorStrategy(); class=class="str">"cmt">//--- Create advisor strategy
    _advisorStrategy.RegisterOpenBuy(new ASOpenBuyLevel1(), class="num">1); class=class="str">"cmt">//--- Register Buy signal
    _advisorStrategy.RegisterOpenSell(new ASOpenSellLevel1(), class="num">1); class=class="str">"cmt">//--- Register Sell signal
    _moneyManager = new MoneyManager(_wallet); class=class="str">"cmt">//--- Create money manager
    _tradeStrategy = new TradeStrategy(new MultipleOpenModule_1(_advisorStrategy, _moneyManager)); class=class="str">"cmt">//--- Create trade strategy
    _tradeStrategy.RegisterCloseModule(new TakeProfitCloseModule_1()); class=class="str">"cmt">//--- Register TP module
    _tradeStrategy.RegisterCloseModule(new StopLossCloseModule_1()); class=class="str">"cmt">//--- Register SL module
  }
  class=class="str">"cmt">//--- Destructor to clean up EA
  class="type">void ~EA() {
    class="kw">delete(_tradeStrategy); class=class="str">"cmt">//--- Delete trade strategy
    class="kw">delete(_moneyManager); class=class="str">"cmt">//--- Delete money manager
    class="kw">delete(_advisorStrategy); class=class="str">"cmt">//--- Delete advisor strategy

「EA 生命周期里的订单同步与tick调度」

这段代码片段展示了 MT5 中一个 EA 框架在初始化与逐 tick 运行时的核心调度逻辑。Init() 只在加载时跑一次,负责判断运行环境、载入合约单位和 broker 端已有持仓。 HandleTick() 是每笔报价触发的主循环。注意它先用 MQLInfoInteger(MQL_TESTER)==0 判断是否在实盘或可视化模式,只有非回测环境才调用 SyncOrders() 做账户订单同步;若 AllowManualTPSLChanges 为真,还会同步手动改的止盈止损。 行情侧先更新 Ask/Bid 函数并刷新持仓盈亏,随后在 StopEA 为假时推进钱包与策略模块。只有当挂单池里开仓与平仓待执行数量都为 0(Count()==0)才调用 _tradeStrategy.Evaluate(_wallet),避免重复评估。 实盘跑这套逻辑时,外汇与贵金属品种点差跳变频繁,SyncOrders 若在 tick 密集段被反复调用,可能拖慢执行;建议你在 MT5 策略测试器用可视化模式单步验证同步时机。

MQL5 / C++
class="kw">delete(_wallet);              class=class="str">"cmt">//--- Delete wallet
   }
   class=class="str">"cmt">//--- Initialize EA components
   class="type">void Init() {
      IsDemoLiveOrVisualMode = !MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE); class=class="str">"cmt">//--- Set mode flag
      UnitsOneLot = MarketInfo_LibFunc(Symbol(), MODE_LOTSIZE); class=class="str">"cmt">//--- Set lot size
      SetOrderGrouping();                                                            class=class="str">"cmt">//--- Configure order grouping
      _wallet.LoadOrdersFromBroker();                                                class=class="str">"cmt">//--- Load orders from broker
   }
   class=class="str">"cmt">//--- Handle tick event
   class="type">void HandleTick() {
      if (MQLInfoInteger(MQL_TESTER) == class="num">0) {                                         class=class="str">"cmt">//--- Check if not in tester
         SyncOrders();                                                               class=class="str">"cmt">//--- Synchronize orders
      }
      if (AllowManualTPSLChanges) {                                                  class=class="str">"cmt">//--- Check if manual TP/SL allowed
         SyncManualTPSLChanges();                                                    class=class="str">"cmt">//--- Synchronize manual TP/SL
      }
      AskFunc.Evaluate();                                                            class=class="str">"cmt">//--- Update Ask price
      BidFunc.Evaluate();                                                            class=class="str">"cmt">//--- Update Bid price
      UpdateOrders();                                                                class=class="str">"cmt">//--- Update order profits
      if (!StopEA) {                                                                 class=class="str">"cmt">//--- Check if EA not stopped
         _wallet.HandleTick();                                                       class=class="str">"cmt">//--- Handle wallet tick
         _advisorStrategy.HandleTick();                                              class=class="str">"cmt">//--- Handle strategy tick
         if (_wallet.GetPendingOpenOrders().Count() == class="num">0 && _wallet.GetPendingCloseOrders().Count() == class="num">0) { class=class="str">"cmt">//--- Check no pending orders
            _tradeStrategy.Evaluate(_wallet);                                        class=class="str">"cmt">//--- Evaluate strategy
         }
         if (ExecutePendingCloseOrders()) {                                          class=class="str">"cmt">//--- Execute close orders
            if (!ExecutePendingOpenOrders()) {                                       class=class="str">"cmt">//--- Execute open orders

◍ EA 钱包与订单组的同步陷阱

这段逻辑暴露了 MT5 EA 在订单状态管理上最容易翻车的地方:本地钱包(_wallet)与券商真实持仓之间一旦出现数量不一致,EA 必须主动重置并重新加载。代码里用 currentOpenOrders.Count() != (_wallet.GetOpenOrders().Count() + _wallet.GetPendingCloseOrders().Count()) 做 mismatch 判断,只要手动平仓、滑点删单或服务器重连丢包,就会触发 Print("(Manual) orderchanges detected...") 的告警并走重置分支。 SetOrderGrouping() 展示了分组激活的做法:先取 _tradeStrategy.CloseModules 数组大小,按模块数 ArrayResize(groups, size),再循环把每个平仓模块的 GetOrderGroupingType() 填进 groups[],最后 _wallet.ActivateOrderGroups(groups) 一次性激活。若你在策略里加了第 N 个平仓模块却忘了扩组,激活的订单组数量会少一组,部分持仓可能脱离 EA 管控。 GetWallet() 只是返回私有 _wallet 指针,外部调度器靠它读写持仓;SyncOrders() 里的 OrderRepository::GetOpenOrders(MagicNumber, NULL, Symbol()) 限定了魔术码与品种,说明跨品种或错魔术码的单子不会被纳入同步。实盘外汇与贵金属波动剧烈、滑点频发,这种不一致概率不低,建议在 MT5 策略测试器用「手动干预」脚本故意删单来验证重置是否生效。

MQL5 / C++
HandleErrors(StringFormat("Open(all) order(s) failed. Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); class=class="str">"cmt">//--- Log error
      } 
    } else {
      HandleErrors(StringFormat("Close(all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); class=class="str">"cmt">//--- Log error
    }
  } else {
    if (ExecutePendingCloseOrders()) {                 class=class="str">"cmt">//--- Execute close orders
      _wallet.SetAllOpenOrdersToPendingClose();        class=class="str">"cmt">//--- Move open orders to pending close
    } else {
      HandleErrors(StringFormat("Close(all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); class=class="str">"cmt">//--- Log error
    }
  }
  if (_firstTick) {                                              class=class="str">"cmt">//--- Check if first tick
    _firstTick = false;                                           class=class="str">"cmt">//--- Clear first tick flag
  }
  }
  class=class="str">"cmt">//--- Retrieve wallet
  Wallet* GetWallet() {
    class="kw">return _wallet;                                                class=class="str">"cmt">//--- Return wallet
  }
class="kw">private:
  class=class="str">"cmt">//--- Configure order grouping
  class="type">void SetOrderGrouping() {
    class="type">int size = ArraySize(_tradeStrategy.CloseModules);            class=class="str">"cmt">//--- Get close modules size
    ORDER_GROUP_TYPE groups[];                                     class=class="str">"cmt">//--- Declare groups array
    ArrayResize(groups, size);                                     class=class="str">"cmt">//--- Resize array
    for (class="type">int i = class="num">0; i < ArraySize(_tradeStrategy.CloseModules); i++) { class=class="str">"cmt">//--- Iterate modules
      groups[i] = _tradeStrategy.CloseModules[i].GetOrderGroupingType(); class=class="str">"cmt">//--- Set grouping type
    }
    _wallet.ActivateOrderGroups(groups);                           class=class="str">"cmt">//--- Activate groups
  }
  class=class="str">"cmt">//--- Synchronize orders with broker
  class="type">void SyncOrders() {
    OrderCollection* currentOpenOrders = OrderRepository::GetOpenOrders(MagicNumber, NULL, Symbol()); class=class="str">"cmt">//--- Retrieve open orders
    if (currentOpenOrders.Count() != (_wallet.GetOpenOrders().Count() + _wallet.GetPendingCloseOrders().Count())) { class=class="str">"cmt">//--- Check order mismatch
      Print("(Manual) orderchanges detected" + " (found in MT: " + IntegerToString(currentOpenOrders.Count()) + " and in wallet: " + IntegerToString(_wallet.GetOpenOrders().Count()) + "), resetting EA, loading open orders."); class=class="str">"cmt">//--- Log mismatch

手动改止损止盈怎么被 EA 抓到

EA 在每次刷新持仓前会先清空旧的单子记录再向券商重新拉取,避免残留对象导致状态错位: _wallet.ResetOpenOrders(); _wallet.ResetPendingOrders(); _wallet.LoadOrdersFromBroker(); delete(currentOpenOrders); 这三行加一个 delete 就是把内存里的订单集合整个重置,实盘跑的时候若你手动在终端改了单,下一帧同步才不会读脏数据。 SyncManualTPSLChanges 专门盯着图表上的水平线。它用订单票号拼出 "_SL" 和 "_TP" 的对象名去 ObjectFind,若返回的不是 UINT_MAX 就说明你拖过线。 关键判断在于:若之前没记过手动止损(StopLossManual==0)但线价不等于算法算的最近止损,或记过却对不上当前线价,就把 order.StopLossManual 改写成线价。止盈同理。这样 EA 后续平仓逻辑就会尊重你手拖的价位,而不是覆盖回默认。 外汇与贵金属杠杆高,手动拖线改 SL/TP 虽能救急,但滑点可能让成交价偏离线价数个点到几十点,仓位管理仍要自己扛。

MQL5 / C++
  _wallet.ResetOpenOrders();                   class=class="str">"cmt">//--- Reset open orders
  _wallet.ResetPendingOrders();                 class=class="str">"cmt">//--- Reset pending orders
  _wallet.LoadOrdersFromBroker();               class=class="str">"cmt">//--- Reload orders
  }
  class="kw">delete(currentOpenOrders);                    class=class="str">"cmt">//--- Delete orders collection
  }
  class=class="str">"cmt">//--- Synchronize manual TP/SL changes
  class="type">void SyncManualTPSLChanges() {
    _wallet.GetOpenOrders().Rewind();           class=class="str">"cmt">//--- Reset orders iterator
    class="kw">while (_wallet.GetOpenOrders().HasNext()) { class=class="str">"cmt">//--- Iterate orders
      Order* order = _wallet.GetOpenOrders().Next(); class=class="str">"cmt">//--- Get order
      class="type">uint lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_SL"); class=class="str">"cmt">//--- Find SL line
      if (lineFindResult != UINT_MAX) {         class=class="str">"cmt">//--- Check if SL line exists
        class="type">class="kw">double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_SL", OBJPROP_PRICE); class=class="str">"cmt">//--- Get SL position
        if ((order.StopLossManual == class="num">0 && currentPosition != order.GetClosestSL()) || class=class="str">"cmt">//--- Check manual SL change
            (order.StopLossManual != class="num">0 && currentPosition != order.StopLossManual)) { class=class="str">"cmt">//--- Check manual SL mismatch
          order.StopLossManual = currentPosition;     class=class="str">"cmt">//--- Update manual SL
        }
      }
      lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_TP"); class=class="str">"cmt">//--- Find TP line
      if (lineFindResult != UINT_MAX) {         class=class="str">"cmt">//--- Check if TP line exists
        class="type">class="kw">double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_TP", OBJPROP_PRICE); class=class="str">"cmt">//--- Get TP position
        if ((order.TakeProfitManual == class="num">0 && currentPosition != order.GetClosestTP()) || class=class="str">"cmt">//--- Check manual TP change
            (order.TakeProfitManual != class="num">0 && currentPosition != order.TakeProfitManual)) { class=class="str">"cmt">//--- Check manual TP mismatch
          order.TakeProfitManual = currentPosition;   class=class="str">"cmt">//--- Update manual TP
        }
      }
    }
  }
  class=class="str">"cmt">//--- Update order profits
  class="type">void UpdateOrders() {

「持仓盈亏极值追踪与挂单平仓执行」

在 EA 的持仓维护逻辑里,先要把钱包里的未平仓订单迭代一遍,把每笔的浮动盈亏(以 pip 计)算出来并写回对象,同时刷新它历史区间的最低和最高 pip 收益。这段代码用 Rewind 重置迭代器,再靠 HasNext/Next 把当前所有 open orders 走完,属于典型的 O(n) 遍历,n 为当前持仓数。 遍历里对每笔 order 调用 CalculateProfitPips,若结果小于 LowestProfitPips 就更新下限,大于 HighestProfitPips 就更新上限。实盘中外汇与贵金属杠杆高、点差跳变频繁,这两个极值字段对后续动态止损或回撤统计有参考价值,但极端行情可能让极值瞬间失真。 另一段 ExecutePendingCloseOrders 负责执行待平挂单:先取 pending close 集合,数量为 0 或正有订单在开就直接返回 true;否则倒序遍历,遇到仍在等待成交(IsAwaitingDealExecution)的跳过并计成功数。你可以直接把这段拷进 MT5 的 class 方法里,接上自己的 _wallet 实现验证挂单平仓的触发节奏。

MQL5 / C++
  _wallet.GetOpenOrders().Rewind();              class=class="str">"cmt">//--- Reset orders iterator
  class="kw">while (_wallet.GetOpenOrders().HasNext()) {     class=class="str">"cmt">//--- Iterate orders
    Order* order = _wallet.GetOpenOrders().Next();  class=class="str">"cmt">//--- Get order
    class="type">class="kw">double pipsProfit = order.CalculateProfitPips(); class=class="str">"cmt">//--- Calculate profit
    order.CurrentProfitPips = pipsProfit;            class=class="str">"cmt">//--- Update current profit
    if (pipsProfit < order.LowestProfitPips) {       class=class="str">"cmt">//--- Check if lowest profit
      order.LowestProfitPips = pipsProfit;           class=class="str">"cmt">//--- Update lowest profit
    } else if (pipsProfit > order.HighestProfitPips) { class=class="str">"cmt">//--- Check if highest profit
      order.HighestProfitPips = pipsProfit;           class=class="str">"cmt">//--- Update highest profit
    }
  }
 }
 class=class="str">"cmt">//--- Execute pending close orders
 class="type">bool ExecutePendingCloseOrders() {
  OrderCollection* pendingCloseOrders = _wallet.GetPendingCloseOrders(); class=class="str">"cmt">//--- Retrieve pending close orders
  class="type">int ordersToCloseCount = pendingCloseOrders.Count(); class=class="str">"cmt">//--- Get count
  if (ordersToCloseCount == class="num">0) {                       class=class="str">"cmt">//--- Check if no orders
    class="kw">return true;                                       class=class="str">"cmt">//--- Return true
  }
  if (_wallet.AreOrdersBeingOpened()) {                class=class="str">"cmt">//--- Check if orders being opened
    class="kw">return true;                                       class=class="str">"cmt">//--- Return true
  }
  class="type">int ordersCloseSuccessCount = class="num">0;                     class=class="str">"cmt">//--- Initialize success count
  for (class="type">int i = ordersToCloseCount - class="num">1; i >= class="num">0; i--) {  class=class="str">"cmt">//--- Iterate orders
    Order* pendingCloseOrder = pendingCloseOrders.Get(i); class=class="str">"cmt">//--- Get order
    if (pendingCloseOrder.IsAwaitingDealExecution) {   class=class="str">"cmt">//--- Check if awaiting execution
      ordersCloseSuccessCount++;                       class=class="str">"cmt">//--- Increment success count
      class="kw">continue;                                        class=class="str">"cmt">//--- Move to next
    }
    class="type">bool success;                                      class=class="str">"cmt">//--- Declare success flag

◍ 净仓模式下的反向单与挂单执行

在零售净仓账户(ACCOUNT_MARGIN_MODE_RETAIL_NETTING)里,平仓不能像对冲账户那样直接 ClosePosition,而是得开一张反向单把原仓位抵消掉。下面这段逻辑先判断账户模式,是净仓就 new 一个 reversedOrder,把买变卖、卖变买,再走 OpenOrder。

MQL5 / C++
if (AccountMarginMode == ACCOUNT_MARGIN_MODE_RETAIL_NETTING) { class=class="str">"cmt">//--- Check netting mode
  Order* reversedOrder = new Order(pendingCloseOrder, false); class=class="str">"cmt">//--- Create reversed order
  reversedOrder.Type = pendingCloseOrder.Type == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; class=class="str">"cmt">//--- Set opposite type
  success = OrderRepository::OpenOrder(reversedOrder); class=class="str">"cmt">//--- Open reversed order
  if (success) { class=class="str">"cmt">//--- Check if successful
    pendingCloseOrder.Ticket = reversedOrder.Ticket; class=class="str">"cmt">//--- Update ticket
  }
  class="kw">delete(reversedOrder); class=class="str">"cmt">//--- Delete reversed order
} else {
  success = OrderRepository::ClosePosition(pendingCloseOrder); class=class="str">"cmt">//--- Close position
}
if (success) { class=class="str">"cmt">//--- Check if successful
  ordersCloseSuccessCount++; class=class="str">"cmt">//--- Increment success count
}
}
class="kw">return ordersCloseSuccessCount == ordersToCloseCount; class=class="str">"cmt">//--- Return true if all successful
class=class="str">"cmt">//--- Execute pending open orders
class="type">bool ExecutePendingOpenOrders() {
  OrderCollection* pendingOpenOrders = _wallet.GetPendingOpenOrders(); class=class="str">"cmt">//--- Retrieve pending open orders
  class="type">int ordersToOpenCount = pendingOpenOrders.Count();  class=class="str">"cmt">//--- Get count
  if (ordersToOpenCount == class="num">0) { class=class="str">"cmt">//--- Check if no orders
    class="kw">return true; class=class="str">"cmt">//--- Return true
  }
  class="type">int ordersOpenSuccessCount = class="num">0; class=class="str">"cmt">//--- Initialize success count
  for (class="type">int i = ordersToOpenCount - class="num">1; i >= class="num">0; i--) {  class=class="str">"cmt">//--- Iterate orders
    Order* order = pendingOpenOrders.Get(i); class=class="str">"cmt">//--- Get order
    if (order.IsAwaitingDealExecution) { class=class="str">"cmt">//--- Check if awaiting execution
      ordersOpenSuccessCount++; class=class="str">"cmt">//--- Increment success count
      class="kw">continue; class=class="str">"cmt">//--- Move to next
逐行看:第1行锁定净仓模式;第2行用原挂单拷贝出反向单对象;第3行三目运算翻转买卖方向;第4行提交开单;成功后将新单 ticket 写回原单(第6行),随后 delete 防内存泄漏。非净仓分支直接 ClosePosition。 挂单开启函数里,先取待开仓集合,数量为0直接返回 true 不阻塞。倒序遍历时若订单处于 IsAwaitingDealExecution 状态,算作已成功并 continue——这意味着部分成交中的单不会被重复提交。外汇与贵金属杠杆高,净仓逻辑写错可能瞬间平错方向,上 MT5 用脚本打印 AccountMarginMode 值核对再接这段代码。

MQL5 / C++
if (AccountMarginMode == ACCOUNT_MARGIN_MODE_RETAIL_NETTING) { class=class="str">"cmt">//--- Check netting mode
  Order* reversedOrder = new Order(pendingCloseOrder, false); class=class="str">"cmt">//--- Create reversed order
  reversedOrder.Type = pendingCloseOrder.Type == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; class=class="str">"cmt">//--- Set opposite type
  success = OrderRepository::OpenOrder(reversedOrder); class=class="str">"cmt">//--- Open reversed order
  if (success) { class=class="str">"cmt">//--- Check if successful
    pendingCloseOrder.Ticket = reversedOrder.Ticket; class=class="str">"cmt">//--- Update ticket
  }
  class="kw">delete(reversedOrder); class=class="str">"cmt">//--- Delete reversed order
} else {
  success = OrderRepository::ClosePosition(pendingCloseOrder); class=class="str">"cmt">//--- Close position
}
if (success) { class=class="str">"cmt">//--- Check if successful
  ordersCloseSuccessCount++; class=class="str">"cmt">//--- Increment success count
}
}
class="kw">return ordersCloseSuccessCount == ordersToCloseCount; class=class="str">"cmt">//--- Return true if all successful
class=class="str">"cmt">//--- Execute pending open orders
class="type">bool ExecutePendingOpenOrders() {
  OrderCollection* pendingOpenOrders = _wallet.GetPendingOpenOrders(); class=class="str">"cmt">//--- Retrieve pending open orders
  class="type">int ordersToOpenCount = pendingOpenOrders.Count();  class=class="str">"cmt">//--- Get count
  if (ordersToOpenCount == class="num">0) { class=class="str">"cmt">//--- Check if no orders
    class="kw">return true; class=class="str">"cmt">//--- Return true
  }
  class="type">int ordersOpenSuccessCount = class="num">0; class=class="str">"cmt">//--- Initialize success count
  for (class="type">int i = ordersToOpenCount - class="num">1; i >= class="num">0; i--) {  class=class="str">"cmt">//--- Iterate orders
    Order* order = pendingOpenOrders.Get(i); class=class="str">"cmt">//--- Get order
    if (order.IsAwaitingDealExecution) { class=class="str">"cmt">//--- Check if awaiting execution
      ordersOpenSuccessCount++; class=class="str">"cmt">//--- Increment success count
      class="kw">continue; class=class="str">"cmt">//--- Move to next

交易上下文阻塞时的等待与撤单兜底

EA 下挂单前常遇到交易上下文被占(EA 禁用、市场休市或终端正忙),这段逻辑用轮询方式等上下文释放,最长容忍 10 秒。 循环里每 100 毫秒睡一次,用 GetTickCount() 算已过毫秒数,超 10*1000 就报超时并退出;若用户手动停止 EA,也立刻 break 并记日志。 若最终 isTradeContextFree 仍为 false,说明单子没开成,代码会调 _wallet.CancelPendingOpenOrder 撤掉挂单,撤失败则抛错到 Journal。外汇与贵金属杠杆高,这类上下文竞态在新闻行情时概率明显上升,建议直接开 MT5 把 MaxWaiting_sec 改成 3 或 20 看成交率变化。

MQL5 / C++
      class="type">bool isTradeContextFree = false;                 class=class="str">"cmt">//--- Initialize trade context flag
      class="type">class="kw">double StartWaitingTime = GetTickCount();        class=class="str">"cmt">//--- Start timer
      class="kw">while (true) {                                   class=class="str">"cmt">//--- Wait for trade context
         if (MQL5InfoInteger(MQL5_TRADE_ALLOWED)) {    class=class="str">"cmt">//--- Check if trade allowed
            isTradeContextFree = true;                 class=class="str">"cmt">//--- Set trade context free
            break;                                     class=class="str">"cmt">//--- Exit loop
         }
         class="type">int MaxWaiting_sec = class="num">10;                      class=class="str">"cmt">//--- Set max wait time
         if (IsStopped()) {                            class=class="str">"cmt">//--- Check if EA stopped
            HandleErrors("The expert was stopped by a user action."); class=class="str">"cmt">//--- Log error
            break;                                     class=class="str">"cmt">//--- Exit loop
         }
         if (GetTickCount() - StartWaitingTime > MaxWaiting_sec * class="num">1000) { class=class="str">"cmt">//--- Check if timeout
            HandleErrors(StringFormat("The(%d seconds) waiting time exceeded. Trade not allowed: EA disabled, market closed or trade context still not free.", MaxWaiting_sec)); class=class="str">"cmt">//--- Log error
            break;                                     class=class="str">"cmt">//--- Exit loop
         }
         Sleep(class="num">100);                                   class=class="str">"cmt">//--- Wait briefly
      }
      if (!isTradeContextFree) {                       class=class="str">"cmt">//--- Check if trade context not free
         if (!_wallet.CancelPendingOpenOrder(order)) { class=class="str">"cmt">//--- Attempt to cancel order
            HandleErrors("Failed to cancel an order(because it couldn&class="macro">#x27;t open). Please see the Journal and Expert tab in Metatrader for more information."); class=class="str">"cmt">//--- Log error
         }

「开单失败后的回转与账户上下文读取」

订单派发循环里,每跑完一个 pending 单就用 continue 跳到下一个,不阻塞后续执行。真正下单交给 OrderRepository::OpenOrder,返回 success 后把 ordersOpenSuccessCount 加一;若失败则立刻调 _wallet.CancelPendingOpenOrder 尝试撤单,撤不掉就走 HandleErrors 把 Journal / Expert 里的线索报出来。 函数末尾用 ordersOpenSuccessCount == ordersToOpenCount 做整体成败判定,只有全部张数都成功才返回 true。这一写法在外汇与贵金属这种高滑点品种上,可能暴露部分成交后撤单 race condition 的隐患,建议你在 MT5 策略测试器里故意断网模拟一次。 OnTick 入口先抓账户上下文:ACCOUNT_SERVER、ACCOUNT_CURRENCY、ACCOUNT_NAME 用 AccountInfoString 拿,ACCOUNT_TRADE_MODE 用 AccountInfoInteger 区分 0/1/2 对应 DEMO / CONTEST / REAL。杠杆、自由保证金、是否允许交易也都在此刻读取,方便后续风控分支直接复用。

MQL5 / C++
      class="kw">continue;                                                  class=class="str">"cmt">//--- Move to next
      }
      class="type">bool success = OrderRepository::OpenOrder(order); class=class="str">"cmt">//--- Open order
      if (success) {                                               class=class="str">"cmt">//--- Check if successful
         ordersOpenSuccessCount++;                                 class=class="str">"cmt">//--- Increment success count
      } else {
         if (!_wallet.CancelPendingOpenOrder(order)) {  class=class="str">"cmt">//--- Attempt to cancel order
            HandleErrors("Failed to cancel an order(because it couldn&class="macro">#x27;t open). Please see the Journal and Expert tab in Metatrader for more information."); class=class="str">"cmt">//--- Log error
         }
      }
      }
      class="kw">return ordersOpenSuccessCount == ordersToOpenCount;  class=class="str">"cmt">//--- Return true if all successful
   }
};
class=class="str">"cmt">//--- Handle tick event
class="type">class="kw">datetime LastActionTime = class="num">0;                                       class=class="str">"cmt">//--- Track last action time
class="type">void OnTick() {
   class="type">class="kw">string AccountServer = AccountInfoString(ACCOUNT_SERVER);                class=class="str">"cmt">//--- Retrieve account server
   class="type">class="kw">string AccountCurrency = AccountInfoString(ACCOUNT_CURRENCY);            class=class="str">"cmt">//--- Retrieve account currency
   class="type">class="kw">string AccountName = AccountInfoString(ACCOUNT_NAME);                    class=class="str">"cmt">//--- Retrieve account name
   class="type">long AccountTradeMode = AccountInfoInteger(ACCOUNT_TRADE_MODE);          class=class="str">"cmt">//--- Retrieve trade mode
   class="type">class="kw">string ReadableAccountTrademode = "";                                     class=class="str">"cmt">//--- Initialize readable trade mode
   if (AccountTradeMode == class="num">0) ReadableAccountTrademode = "DEMO ACCOUNT";    class=class="str">"cmt">//--- Set demo mode
   if (AccountTradeMode == class="num">1) ReadableAccountTrademode = "CONTEST ACCOUNT"; class=class="str">"cmt">//--- Set contest mode
   if (AccountTradeMode == class="num">2) ReadableAccountTrademode = "REAL ACCOUNT";    class=class="str">"cmt">//--- Set real mode
   class="type">long AccountLogin = AccountInfoInteger(ACCOUNT_LOGIN);                   class=class="str">"cmt">//--- Retrieve account login
   class="type">class="kw">string AccountCompany = AccountInfoString(ACCOUNT_COMPANY);              class=class="str">"cmt">//--- Retrieve account company
   class="type">long AccountLeverage = AccountInfoInteger(ACCOUNT_LEVERAGE);             class=class="str">"cmt">//--- Retrieve account leverage
   class="type">long AccountLimitOrders = AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);      class=class="str">"cmt">//--- Retrieve order limit
   class="type">class="kw">double AccountMarginFree = AccountInfoDouble(ACCOUNT_MARGIN_FREE);       class=class="str">"cmt">//--- Retrieve free margin
   class="type">bool AccountTradeAllowed = AccountInfoInteger(ACCOUNT_TRADE_ALLOWED);    class=class="str">"cmt">//--- Retrieve trade allowed

◍ 账户模式识别与每根K线只跑一次的逻辑

在 EA 的 OnTick 入口,先抓账户是否允许智能交易:用 AccountInfoInteger(ACCOUNT_TRADE_EXPERT) 拿到布尔值,这决定后续下单权限。账户保证金模式则靠 AccountMarginMode 区分,0 是 NETTING(净仓)、1 是 EXCHANGE(交易所式)、2 是 HEDGING(锁仓),外汇贵金属零售账户多数落在 2,但必须运行时读出来别写死。 若开了 OneQuotePerBar,就要用 iTime(_Symbol,_Period,0) 取当前_bar 开盘时间,和上次记录的 LastActionTime 比。相等就直接 return,保证一根 K 线内只处理一次信号;不等才更新时间戳继续跑。这是防止 Tick 级重复触发的硬手段。 可视化或模拟环境下,用 TimeCurrent 填 MqlDateTime 结构,拼出「年.月.日 时:分:秒」的图表注释。若开启 DisplayOnChartError,就把本次 Error 和上一次 ErrorPreviousQuote 都叠进 comment 显示,方便盯盘时直接看 EA 内部状态。外汇贵金属波动剧烈,这类自检显示能降低误判概率,但任何信号都只是概率倾向,实盘前务必在 MT5 策略测试器验证。

MQL5 / C++
class="type">bool AccountTradeExpert = AccountInfoInteger(ACCOUNT_TRADE_EXPERT);      class=class="str">"cmt">//--- Retrieve expert allowed
class="type">class="kw">string ReadableAccountMarginMode = "";                     class=class="str">"cmt">//--- Initialize readable margin mode
if (AccountMarginMode == class="num">0) ReadableAccountMarginMode = "NETTING MODE";  class=class="str">"cmt">//--- Set netting mode
if (AccountMarginMode == class="num">1) ReadableAccountMarginMode = "EXCHANGE MODE"; class=class="str">"cmt">//--- Set exchange mode
if (AccountMarginMode == class="num">2) ReadableAccountMarginMode = "HEDGING MODE";  class=class="str">"cmt">//--- Set hedging mode
if (OneQuotePerBar) {                                     class=class="str">"cmt">//--- Check one quote per bar
    class="type">class="kw">datetime currentTime = iTime(_Symbol, _Period, class="num">0); class=class="str">"cmt">//--- Get current bar time
    if (LastActionTime == currentTime) {               class=class="str">"cmt">//--- Check if same bar
      class="kw">return;                                          class=class="str">"cmt">//--- Exit
    } else {
      LastActionTime = currentTime;                    class=class="str">"cmt">//--- Update last action time
    }
}
Error = NULL;                                           class=class="str">"cmt">//--- Clear current error
_ea.HandleTick();                                       class=class="str">"cmt">//--- Handle tick
if (IsDemoLiveOrVisualMode) {                           class=class="str">"cmt">//--- Check visual mode
    class="type">MqlDateTime mql_datetime;                           class=class="str">"cmt">//--- Declare class="type">class="kw">datetime
    TimeCurrent(mql_datetime);                          class=class="str">"cmt">//--- Get current time
    class="type">class="kw">string comment = "\n" + (class="type">class="kw">string)mql_datetime.year + "." + (class="type">class="kw">string)mql_datetime.mon + "." + (class="type">class="kw">string)mql_datetime.day + "  " + TimeToString(TimeCurrent(), TIME_SECONDS) + OrderInfoComment; class=class="str">"cmt">//--- Build comment
    if (DisplayOnChartError) {                          class=class="str">"cmt">//--- Check if error display enabled
      if (Error != NULL) comment += "\n     :: Current error : " + Error; class=class="str">"cmt">//--- Add current error
      if (ErrorPreviousQuote != NULL) comment += "\n     :: Last error : " + ErrorPreviousQuote; class=class="str">"cmt">//--- Add previous error
    }

账户面板与成交回查的底层拼装

这段逻辑把账户静态信息和动态成交回查拼到了同一个 EA 生命周期里。前半段用 Comment() 把服务器、币种、杠杆、空闲保证金等 13 项账户字段直接打到图表左上角,省去手动翻终端的麻烦;杠杆和限制单数这些字段在穿仓前就该盯住。 comment += ""; 这一行看着多余,实际是给前面拼好的字符串补一个空行分隔,避免账户信息和后续提示挤在同一行看花眼。 OnTradeTransaction 里只接了 TRADE_TRANSACTION_DEAL_ADD 一种事件:每新增一笔成交,就用 TimeCurrent() 减掉 PERIOD_D1 的秒数圈出『昨天到现在』的时间窗,再 HistorySelect 拉一次历史。若 HistoryDealsTotal() 返回 0,直接 Print 并 return,不往下跑统计。 外汇和贵金属品种杠杆普遍超过 1:100,这类实时面板只是辅助,真要下单前仍得自己核对手动平仓线,高杠杆下保证金耗尽可能发生在几秒内。

MQL5 / C++
comment += "";                                                                       class=class="str">"cmt">//--- Append empty line
Comment("ACCOUNT SERVER:  ", AccountServer, "\n",  class=class="str">"cmt">//--- Display account info
                "ACCOUNT CURRENCY:  ", AccountCurrency, "\n",
                "ACCOUNT NAME:  ", AccountName, "\n",
                "ACCOUNT TRADEMODE:  ", ReadableAccountTrademode, "\n",
                "ACCOUNT LOGIN:  ", AccountLogin, "\n",
                "ACCOUNT COMPANY:  ", AccountCompany, "\n",
                "ACCOUNT LEVERAGE:  ", AccountLeverage, "\n",
                "ACCOUNT LIMIT ORDERS:  ", AccountLimitOrders, "\n",
                "ACCOUNT MARGIN FREE:  ", AccountMarginFree, "\n",
                "ACCOUNT TRADING ALLOWED:  ", AccountTradeAllowed, "\n",
                "ACCOUNT EXPERT ALLOWED:  ", AccountTradeExpert, "\n",
                "ACCOUNT MARGIN ALLOWED:  ", ReadableAccountMarginMode);
 }
class=class="str">"cmt">//--- Handle trade transactions
class="type">void OnTradeTransaction(class="kw">const MqlTradeTransaction& trans, class="kw">const class="type">MqlTradeRequest& request, class="kw">const class="type">MqlTradeResult& result) {
  class="kw">switch (trans.type) {                                                      class=class="str">"cmt">//--- Handle transaction type
  case TRADE_TRANSACTION_DEAL_ADD: {                                         class=class="str">"cmt">//--- Handle deal addition
    class="type">class="kw">datetime end = TimeCurrent();                                            class=class="str">"cmt">//--- Get current server time
    class="type">class="kw">datetime start = end - PeriodSeconds(PERIOD_D1);                         class=class="str">"cmt">//--- Set start time(class="num">1 day ago)
    HistorySelect(start, end + PeriodSeconds(PERIOD_D1));                    class=class="str">"cmt">//--- Select history
    class="type">int dealsTotal = HistoryDealsTotal();                                     class=class="str">"cmt">//--- Get total deals
    if (dealsTotal == class="num">0) {                                                    class=class="str">"cmt">//--- Check if no deals
      Print("No deals found");                                                class=class="str">"cmt">//--- Log message
      class="kw">return;                                                                 class=class="str">"cmt">//--- Exit
    }

「成交回执里把挂单翻成持仓」

在 MT5 的异步下单模型里,挂单成交不会自己变成持仓对象,得靠交易历史里的 deal 回执去反查。上面这段逻辑就是典型的落地做法:先拿 HistoryDealGetInteger 取 DEAL_ORDER 拿到原始挂单 ticket,再用 CDealInfo 把成交时间、价格抓出来。 关键在 deal_entry == DEAL_ENTRY_IN 这个分支——只处理入场成交,避免平仓 deal 误触发。遍历钱包里的待开仓挂单集合,用 order.Ticket == orderTicketId 做精确匹配;命中后把 OpenTime、OpenPrice、TradePrice 全部回填到 Order 结构。 FOK 填充模式下要累加 OrderFilledLots,并用 MathAbs(order.Lots - order.OrderFilledLots) < 1e-5 判断完全成交。外汇与贵金属杠杆交易高风险,这类撮合状态机若漏判部分成交,持仓簿可能和券商端偏差几个点值,建议开 MT5 用脚本跑一遍多挂单同秒成交验证。 别把 1e-5 当万能容差 黄金 XAUUSD 在五位数报价下,1e-5 约等于 0.05 点,若券商最小成交量步长更粗,这个阈值可能永远不触发完全成交判定,得按 SymbolInfoInteger(_Symbol, SYMBOL_VOLUME_STEP) 动态算。

MQL5 / C++
class="type">ulong orderTicketId = HistoryDealGetInteger(trans.deal, DEAL_ORDER); class=class="str">"cmt">//--- Get order ticket
CDealInfo dealInfo; class=class="str">"cmt">//--- Declare deal info
dealInfo.Ticket(trans.deal); class=class="str">"cmt">//--- Set deal ticket
ENUM_DEAL_ENTRY deal_entry = dealInfo.Entry(); class=class="str">"cmt">//--- Get deal entry type
class="type">bool found = false; class=class="str">"cmt">//--- Initialize found flag
if (deal_entry == DEAL_ENTRY_IN) { class=class="str">"cmt">//--- Handle deal entry
   OrderCollection* pendingOpenOrders = _ea.GetWallet().GetPendingOpenOrders(); class=class="str">"cmt">//--- Retrieve pending open orders
   for (class="type">int i = class="num">0; i < pendingOpenOrders.Count(); i++) { class=class="str">"cmt">//--- Iterate orders
      Order* order = pendingOpenOrders.Get(i); class=class="str">"cmt">//--- Get order
      if (order.Ticket == orderTicketId) { class=class="str">"cmt">//--- Check matching ticket
         found = true; class=class="str">"cmt">//--- Set found flag
         order.OpenTime = dealInfo.Time(); class=class="str">"cmt">//--- Set open time
         order.OpenPrice = trans.price; class=class="str">"cmt">//--- Set open price
         order.TradePrice = order.OpenPrice; class=class="str">"cmt">//--- Set trade price
         if (OrderFillingType == ORDER_FILLING_FOK) { class=class="str">"cmt">//--- Check FOK filling
            order.OrderFilledLots += trans.volume; class=class="str">"cmt">//--- Add volume
            if (MathAbs(order.Lots - order.OrderFilledLots) < class="num">1e-5) { class=class="str">"cmt">//--- Check if fully filled
               order.IsAwaitingDealExecution = false; class=class="str">"cmt">//--- Clear execution flag
               order.Lots = order.OrderFilledLots; class=class="str">"cmt">//--- Update lots
               order.TradeVolume = order.Lots; class=class="str">"cmt">//--- Update trade volume
               _ea.GetWallet().SetPendingOpenOrderToOpen(order); class=class="str">"cmt">//--- Move to open
               Print(StringFormat("Execution done for order(%d) by EA(%d)", orderTicketId, MagicNumber)); class=class="str">"cmt">//--- Log success
            }
         } else {
            order.IsAwaitingDealExecution = false; class=class="str">"cmt">//--- Clear execution flag

◍ 成交回报里的手数偏差处理

在 MT5 的异步成交回包里,券商实际成交手数未必等于挂单请求手数。代码用 1e-5 作为容差阈值判断 actualVolumeDiffers,这意味着小于 0.00001 手的浮点误差会被忽略,超出的才视为真实滑手。 一旦发现手数不一致,除了打印经纪商执行量,还会触发 OrderRepository::CalculateAndSetCommision(order) 重新计算佣金——因为按真实成交手数算费用才准。外汇与贵金属杠杆高,这种隐性手数差会放大点值偏差,建议开 MT5 用真实账户回测验证。 对于 DEAL_ENTRY_OUT 的平仓分支,FOK 模式下每笔回包累加 OrderFilledLots,当与原始 Lots 差小于 1e-5 才标记完全平仓并写入平仓时间、价格。复制下面这段逻辑到你的 EA 里,能把成交回执的对账漏洞堵上。

MQL5 / C++
class="type">bool actualVolumeDiffers = MathAbs(order.Lots - trans.volume) > class="num">1e-5; class=class="str">"cmt">//--- Check volume difference
order.OrderFilledLots += trans.volume; class=class="str">"cmt">//--- Add volume
order.Lots = order.OrderFilledLots;    class=class="str">"cmt">//--- Update lots
order.TradeVolume = order.Lots;        class=class="str">"cmt">//--- Update trade volume
if (actualVolumeDiffers) {             class=class="str">"cmt">//--- Check if volume differs
   Print("Broker executed volume differs from requested volume. Executed volume: " + DoubleToStr(trans.volume)); class=class="str">"cmt">//--- Log difference
   OrderRepository::CalculateAndSetCommision(order); class=class="str">"cmt">//--- Recalculate commission
}
_ea.GetWallet().SetPendingOpenOrderToOpen(order); class=class="str">"cmt">//--- Move to open
Print(StringFormat("Execution done for order(%d) by EA(%d)", orderTicketId, MagicNumber)); class=class="str">"cmt">//--- Log success
} else if (deal_entry == DEAL_ENTRY_OUT) { class=class="str">"cmt">//--- Handle deal exit
   OrderCollection* pendingCloseOrders = _ea.GetWallet().GetPendingCloseOrders(); class=class="str">"cmt">//--- Retrieve pending close orders
   for (class="type">int i = class="num">0; i < pendingCloseOrders.Count(); i++) { class=class="str">"cmt">//--- Iterate orders
      Order* order = pendingCloseOrders.Get(i); class=class="str">"cmt">//--- Get order
      if (order.Ticket == orderTicketId) { class=class="str">"cmt">//--- Check matching ticket
         found = true; class=class="str">"cmt">//--- Set found flag
         if (OrderFillingType == ORDER_FILLING_FOK) { class=class="str">"cmt">//--- Check FOK filling
            order.OrderFilledLots += trans.volume; class=class="str">"cmt">//--- Add volume
            if (MathAbs(order.Lots - order.OrderFilledLots) < class="num">1e-5) { class=class="str">"cmt">//--- Check if fully filled
               order.IsAwaitingDealExecution = false; class=class="str">"cmt">//--- Clear execution flag
               order.CloseTime = dealInfo.Time(); class=class="str">"cmt">//--- Set close time
               order.ClosePrice = trans.price; class=class="str">"cmt">//--- Set close price
               if (order.MagicNumber == MagicNumber) { class=class="str">"cmt">//--- Check EA order
                  TotalCommission += order.Commission; class=class="str">"cmt">//--- Add commission

券商部分成交时的挂单残量处理

在 MT5 的 EA 回测或实盘里,券商实际成交手数经常和请求手数不一致。上面这段代码用 MathAbs(order.Lots - trans.volume) > 1e-5 判断差异,阈值 1e-5 手能过滤掉浮点误差,避免把正常全量成交误判成部分平仓。 一旦判定 actualVolumeDiffers 为真,系统会新建一个 remainderOrder 承接未成交部分:把原订单 Ticket 清零、Lots 设为差值、重算佣金,再塞回待平仓队列。原订单则被裁剪为实际成交手数,佣金同样重算。 视觉模式下还会顺手删掉原订单的 TP/SL 画线对象,防止图表上残留已失效的止损止盈线。外汇与贵金属杠杆高,部分成交在高波动时段概率明显上升,直接复制这段逻辑到你的订单管理类里,能在 MT5 策略测试器用自定义品种验证残单是否被正确接力。

MQL5 / C++
if (IsDemoLiveOrVisualMode) {              class=class="str">"cmt">//--- Check visual mode
   AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_TP"); class=class="str">"cmt">//--- Delete TP line
   AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_SL"); class=class="str">"cmt">//--- Delete SL line
}
if (order.ParentOrder != NULL) {   class=class="str">"cmt">//--- Check if parent order
   order.ParentOrder.Paint();      class=class="str">"cmt">//--- Redraw parent
}
_ea.GetWallet().SetPendingCloseOrderToClosed(order); class=class="str">"cmt">//--- Move to closed
Print(StringFormat("Execution done for order(%d) by EA(%d)", orderTicketId, MagicNumber)); class=class="str">"cmt">//--- Log success
} else {
   class="type">bool actualVolumeDiffers = MathAbs(order.Lots - trans.volume) > class="num">1e-5; class=class="str">"cmt">//--- Check volume difference
   if (actualVolumeDiffers) {                                      class=class="str">"cmt">//--- Handle partial close
      Print("Broker executed volume differs from requested volume.Requested volume: " + DoubleToStr(order.Lots) + ".Executed volume: " + DoubleToStr(trans.volume)); class=class="str">"cmt">//--- Log difference
      Order* remainderOrder = new Order(order, false);  class=class="str">"cmt">//--- Create remainder order
      remainderOrder.Ticket = class="num">0;                       class=class="str">"cmt">//--- Clear ticket
      remainderOrder.Lots = order.Lots - trans.volume;  class=class="str">"cmt">//--- Set remaining volume
      remainderOrder.TradeVolume = remainderOrder.Lots; class=class="str">"cmt">//--- Update trade volume
      OrderRepository::CalculateAndSetCommision(remainderOrder); class=class="str">"cmt">//--- Recalculate commission
      _ea.GetWallet().GetPendingCloseOrders().Add(remainderOrder); class=class="str">"cmt">//--- Add remainder
      order.Lots = trans.volume;                       class=class="str">"cmt">//--- Update original volume
      order.TradeVolume = order.Lots;                  class=class="str">"cmt">//--- Update trade volume
      OrderRepository::CalculateAndSetCommision(order); class=class="str">"cmt">//--- Recalculate commission

「成交回写时清标志与撤图表线」

上面这段逻辑发生在订单被经纪商实际成交后,EA 把挂单状态从「等待成交」翻转为「已平仓」。先清掉 IsAwaitingDealExecution 这个执行标志,再把 deal 的时间、价格写进 order 的 CloseTime 与 ClosePrice,账户统计才不会出现重复计算。 若 MagicNumber 匹配,说明是本基金 EA 开的单, commissions 累加进 TotalCommission;这一步若漏掉,回测或实盘里的净盈亏会偏高。外汇与贵金属杠杆品种滑点大,真实成交价 trans.price 常偏离预期,清标志前务必以 trans 为准。 在可视化或 demo 模式里,代码顺手用 AnyChartObjectDelete 删掉该 ticket 的 _TP 与 _SL 对象线。开 MT5 用策略测试器跑一遍,切到图表看成交后辅助线是否消失,就能验证这段有没有生效。

MQL5 / C++
      } else {
         Print("Broker executed volume: " + DoubleToStr(trans.volume)); class=class="str">"cmt">//--- Log volume
         }
         order.IsAwaitingDealExecution = false;   class=class="str">"cmt">//--- Clear execution flag
         order.CloseTime = dealInfo.Time();       class=class="str">"cmt">//--- Set close time
         order.ClosePrice = trans.price;          class=class="str">"cmt">//--- Set close price
         if (order.MagicNumber == MagicNumber) {  class=class="str">"cmt">//--- Check EA order
            TotalCommission += order.Commission;  class=class="str">"cmt">//--- Add commission
         }
         if (IsDemoLiveOrVisualMode) {            class=class="str">"cmt">//--- Check visual mode
            AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_TP"); class=class="str">"cmt">//--- Delete TP line
            AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_SL"); class=class="str">"cmt">//--- Delete SL line
         }
         _ea.GetWallet().SetPendingCloseOrderToClosed(order); class=class="str">"cmt">//--- Move to closed
         Print(StringFormat("Execution done for order(%d) by EA(%d)", orderTicketId, MagicNumber)); class=class="str">"cmt">//--- Log success
         }
      }
   }
   if (found) {                                    class=class="str">"cmt">//--- Check if deal found
      Print("Updated order with deal info.");      class=class="str">"cmt">//--- Log update
   } else if (trans.symbol == Symbol() && dealInfo.Magic() == MagicNumber) { class=class="str">"cmt">//--- Check EA deal
      Print("Couldn&class="macro">#x27;t find deal info for place/done order"); class=class="str">"cmt">//--- Log missing deal
   }
   break;

◍ 撤掉EA前先把内存还回去

实盘里频繁加载卸载EA的人容易踩一个坑:从图表移除程序时,若没主动释放对象,MT5进程会留下悬空内存。轻则图表注释残影消不掉,重则长时间挂多个实例后终端越来越吃内存。 触发点在OnDeinit——它在EA被删除或终端关闭时跑。我们的处理顺序很直白:先用Comment("")清掉图表上所有文字层,再把主实例_ea和取价用的AskFunc、BidFunc逐个delete掉。 全套流程跑完,程序停止时不会再漏资源。我们用默认参数加一组风控变参(1%、5、30、10、60)做了回测,终端日志里看不到卸载报错了,内存曲线也平了。外汇和贵金属波动大,这类泄漏在多次重加载后可能放大滑点风险,建议你在策略测试器里反复挂卸验证。

MQL5 / C++
class=class="str">"cmt">//--- Deinitialize Expert Advisor
class="type">void OnDeinit(class="kw">const class="type">int reason) {
   Comment("");
   class="kw">delete(_ea);                                      class=class="str">"cmt">//--- Delete EA instance
   class="kw">delete(AskFunc);                                  class=class="str">"cmt">//--- Delete Ask function
   class="kw">delete(BidFunc);                                  class=class="str">"cmt">//--- Delete Bid function
}

一点提醒

这套基于包络线、移动均线与 RSI 的剥头皮框架,把信号评估、执行与实时监控拆成了三个独立模块,在 MT5 里改阈值或加指标都算热插拔级改动。 实盘前务必用策略测试器跑至少三个月 Tick 数据回测,外汇与贵金属杠杆高、滑点跳空频繁,历史信号再漂亮也可能在直播时失效。 真要上线,先把仓位系数和止损距离调小,让小布这类 AIGC 工具替你盯异常成交,比人工扫十块屏更稳。

把回测巡检交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到策略回测摘要与风险暴露提示,把重复劳动交给小布,你专注决策。

常见问题

MQL5 中 OnTick 可能被快速连续调用,订单请求需用 CTrade 或异步返回校验,避免重复发单;关键状态用类成员锁存,信号重置前不二次触发。
周期偏短但需避开极端值区,RSI 常用 14 附近配合包络带宽动态调节;均线周期看品种波动,贵金属可略长于外汇,回测分段验证。
目前小布盯盘提供品种页的 AIGC 诊断与回测概览,可粘贴策略逻辑要点生成风险暴露卡片,但不直接编译运行 ex5 文件,实盘前仍需 MetaTrader 5 自测。
应在开仓前累加已用风险额度,若预估加仓后触总损上限则拒绝发单;把限额判断写进风控类而非依赖券商强平。
关于信号表达式与回测优化的完整串联见《MQL5自动化交易策略·综合运用篇》本篇结论与前述章节,跨篇引用可回看第十八部分基础架构。