MQL5自动化交易策略(第十八部分):基于包络线趋势反弹的剥头皮交易核心架构与信号生成(1)·进阶篇
📈

MQL5自动化交易策略(第十八部分):基于包络线趋势反弹的剥头皮交易核心架构与信号生成(1)·进阶篇

(2/3)· 从指标轨道到信号验证,手把手搭出可回测的高频剥头皮程序骨架

新手友好 第 2/3 篇
很多交易者把包络线上下轨当万能反转点,忽略趋势过滤就直接开单,结果在震荡市被反复扫损。本篇先把架构和信号逻辑理清,让你写的EA少走弯路。

EA 全局开关与行情宏定义

写 MT5 半自动 EA 时,先铺一层全局变量和宏,能让后续下单逻辑干净很多。下面这段声明把暂停开关、合约单位、运行模式标识和错误信息容器一次性定好,避免函数在调用层反复传参。 bool StopEA = false; // 若置 true,EA 停止一切开仓平仓动作 double UnitsOneLot = 100000; // 外汇标准手对应 10 万基础货币单位 int IsDemoLiveOrVisualMode = false; // 标记当前是模拟、实盘还是可视化回测 string Error; // 当前错误信息 string ErrorPreviousQuote; // 上一次报价时的错误 string OrderInfoComment; // 订单备注信息 行情抓取若直接用数字模式码可读性差,用宏重命名是老手习惯。原码里把 OP_BUY 设为 0、OP_SELL 设为 1,MODE_BID 为 9、MODE_ASK 为 10、MODE_SPREAD 为 13、MODE_STOPLEVEL 为 14、MODE_LOTSIZE 为 15——这些都是 MarketInfo 类查询的槽位编号。 开 MT5 新建一个 mq5,把上面变量和 #define 整段贴进全局区,编译后能在策略测试器里用 Print(MODE_ASK) 验证输出是否为 10。外汇与贵金属波动剧烈、杠杆风险高,这类基础定义虽不直接下单,但错一个宏值会让后续报价判断整体偏移。

MQL5 / C++
class="type">bool StopEA = false;                                                                     class=class="str">"cmt">//--- Pause EA operations if true
class="type">class="kw">double UnitsOneLot = class="num">100000;                                                          class=class="str">"cmt">//--- Define standard lot size(class="num">100,class="num">000 units for forex)
class="type">int IsDemoLiveOrVisualMode = false;                                                   class=class="str">"cmt">//--- Flag demo, live, or visual backtest mode
class="type">class="kw">string Error;                                                                          class=class="str">"cmt">//--- Hold current error message
class="type">class="kw">string ErrorPreviousQuote;                                                             class=class="str">"cmt">//--- Hold previous quote&class="macro">#x27;s error message
class="type">class="kw">string OrderInfoComment;                                                               class=class="str">"cmt">//--- Store order information comments
class=class="str">"cmt">//--- Define constants for order types
class="macro">#define OP_BUY class="num">0                                                                       class=class="str">"cmt">//--- Represent Buy order type
class="macro">#define OP_SELL class="num">1                                                                      class=class="str">"cmt">//--- Represent Sell order type
class=class="str">"cmt">//--- Define constants for market data retrieval
class="macro">#define MODE_TIME class="num">5                                                                    class=class="str">"cmt">//--- Retrieve symbol time
class="macro">#define MODE_BID class="num">9                                                                     class=class="str">"cmt">//--- Retrieve Bid price
class="macro">#define MODE_ASK class="num">10                                                                    class=class="str">"cmt">//--- Retrieve Ask price
class="macro">#define MODE_POINT class="num">11                                                                  class=class="str">"cmt">//--- Retrieve point size
class="macro">#define MODE_DIGITS class="num">12                                                                 class=class="str">"cmt">//--- Retrieve digit count
class="macro">#define MODE_SPREAD class="num">13                                                                 class=class="str">"cmt">//--- Retrieve spread
class="macro">#define MODE_STOPLEVEL class="num">14                                                              class=class="str">"cmt">//--- Retrieve stop level
class="macro">#define MODE_LOTSIZE class="num">15                                                                class=class="str">"cmt">//--- Retrieve lot size

「MarketInfo 常量里哪些字段真能取到数」

在 MT5 的 MQL5 环境里,用 MarketInfo 配合一组预定义宏去拉品种属性时,并不是每个编号都返回有效数据。下面这组宏定义把 16 到 33 号的取值含义直接写死在代码里,开 MT5 新建脚本粘贴就能看到编译通过。 # define MODE_TICKVALUE 16 //--- Retrieve tick value # define MODE_TICKSIZE 17 //--- Retrieve tick size # define MODE_SWAPLONG 18 //--- Retrieve swap long # define MODE_SWAPSHORT 19 //--- Retrieve swap short # define MODE_STARTING 20 //--- Unused, return 0 # define MODE_EXPIRATION 21 //--- Unused, return 0 # define MODE_TRADEALLOWED 22 //--- Unused, return 0 # define MODE_MINLOT 23 //--- Retrieve minimum lot # define MODE_LOTSTEP 24 //--- Retrieve lot step # define MODE_MAXLOT 25 //--- Retrieve maximum lot # define MODE_SWAPTYPE 26 //--- Retrieve swap mode # define MODE_PROFITCALCMODE 27 //--- Retrieve profit calculation mode # define MODE_MARGINCALCMODE 28 //--- Unused, return 0 # define MODE_MARGININIT 29 //--- Unused, return 0 # define MODE_MARGINMAINTENANCE 30 //--- Unused, return 0 # define MODE_MARGINHEDGED 31 //--- Unused, return 0 # define MODE_MARGINREQUIRED 32 //--- Unused, return 0 # define MODE_FREEZELEVEL 33 //--- Retrieve freeze level 逐行看:16~19 对应 tick 价值、tick 步长、多空隔夜息,都是实盘算成本要用到的;23~27 和 33 也是活字段,最小手数、手数步进、最大手数、掉期类型、盈亏计算方式、冻结层级都能拿到。 20、21、22 以及 28 到 32 在注释里标了 Unused, return 0,也就是说你传这些编号进去大概率得到 0,别拿去当风控参数。外汇和贵金属杠杆高,用错字段可能把保证金算偏,下单前务必用 Print(MarketInfo(_Symbol, MODE_MINLOT)) 这类语句自测一下返回值。

MQL5 / C++
class="macro">#define MODE_TICKVALUE class="num">16 class=class="str">"cmt">//--- Retrieve tick value
class="macro">#define MODE_TICKSIZE class="num">17 class=class="str">"cmt">//--- Retrieve tick size
class="macro">#define MODE_SWAPLONG class="num">18 class=class="str">"cmt">//--- Retrieve swap class="type">long
class="macro">#define MODE_SWAPSHORT class="num">19 class=class="str">"cmt">//--- Retrieve swap class="type">short
class="macro">#define MODE_STARTING class="num">20 class=class="str">"cmt">//--- Unused, class="kw">return class="num">0
class="macro">#define MODE_EXPIRATION class="num">21 class=class="str">"cmt">//--- Unused, class="kw">return class="num">0
class="macro">#define MODE_TRADEALLOWED class="num">22 class=class="str">"cmt">//--- Unused, class="kw">return class="num">0
class="macro">#define MODE_MINLOT class="num">23 class=class="str">"cmt">//--- Retrieve minimum lot
class="macro">#define MODE_LOTSTEP class="num">24 class=class="str">"cmt">//--- Retrieve lot step
class="macro">#define MODE_MAXLOT class="num">25 class=class="str">"cmt">//--- Retrieve maximum lot
class="macro">#define MODE_SWAPTYPE class="num">26 class=class="str">"cmt">//--- Retrieve swap mode
class="macro">#define MODE_PROFITCALCMODE class="num">27 class=class="str">"cmt">//--- Retrieve profit calculation mode
class="macro">#define MODE_MARGINCALCMODE class="num">28 class=class="str">"cmt">//--- Unused, class="kw">return class="num">0
class="macro">#define MODE_MARGININIT class="num">29 class=class="str">"cmt">//--- Unused, class="kw">return class="num">0
class="macro">#define MODE_MARGINMAINTENANCE class="num">30 class=class="str">"cmt">//--- Unused, class="kw">return class="num">0
class="macro">#define MODE_MARGINHEDGED class="num">31 class=class="str">"cmt">//--- Unused, class="kw">return class="num">0
class="macro">#define MODE_MARGINREQUIRED class="num">32 class=class="str">"cmt">//--- Unused, class="kw">return class="num">0
class="macro">#define MODE_FREEZELEVEL class="num">33 class=class="str">"cmt">//--- Retrieve freeze level

◍ 类型转换与订单分组枚举的底层定义

在 MT5 的 EA 或指标工程里,老代码常依赖 MQL4 风格的类型转换函数名。用宏把旧名称映射到 MQL5 原生函数,能省掉大量改写成本:CharToStr 指向 CharToString,DoubleToStr 指向 DoubleToString,StrToDouble 指向 StringToDouble,StrToInteger 强转 int 包裹 StringToInteger,StrToTime、TimeToStr、StringGetChar、StringSetChar 同理一一对应。 订单聚合逻辑需要先定分组维度。ORDER_GROUP_TYPE 枚举给了 4 个值:Single=1 表示单笔独立处理,SymbolOrderType=2 按品种加订单类型聚合,Basket=3 把所有单子当成一个篮子,SymbolCode=4 仅按品种归类。回测多品种网格时,SymbolCode=4 通常比 Basket=3 更利于计算各品种净敞口。 盈亏统计口径也枚举化。ORDER_PROFIT_CALCULATION_TYPE 里 Pips=1 用点数表达,Money=2 用账户货币,EquityPercentage=3 以净值占比呈现;做跨账户横向对比时,EquityPercentage=3 能抹平本金规模差异。 CRUD 操作枚举行首项是 NoAction=0,意味着默认状态机不触发任何增删改查,避免未初始化就误执行下单。外汇与贵金属杠杆高,这类默认值缺失可能引发非预期建仓,实盘前务必在策略测试器逐枚举走查。

MQL5 / C++
class="macro">#define CharToStr CharToString
class="macro">#define DoubleToStr DoubleToString
class="macro">#define StrToDouble StringToDouble
class="macro">#define StrToInteger(class="type">int)StringToInteger
class="macro">#define StrToTime StringToTime
class="macro">#define TimeToStr TimeToString
class="macro">#define StringGetChar StringGetCharacter
class="macro">#define StringSetChar StringSetCharacter
enum ORDER_GROUP_TYPE {
  Single=class="num">1,
  SymbolOrderType=class="num">2,
  Basket=class="num">3,
  SymbolCode=class="num">4
};
enum ORDER_PROFIT_CALCULATION_TYPE {
  Pips=class="num">1,
  Money=class="num">2,
  EquityPercentage=class="num">3
};
enum CRUD {
  NoAction=class="num">0,

指标缓冲与报价接口的取数写法

在 MT5 自建库里,枚举值常用来标记缓存项的生命周期:Created=1 代表新建,Updated=2 代表更新,Deleted=3 代表删除。这类常量直接写死在结构体里,比散落各处的魔法数字更易维护。 取单一指标缓冲值别每次都 CopyBuffer 整段,封装成只取 1 根 K 线的函数更省事。下面这段代码用 shift 定位柱线,成功就返回 buf[0],失败返回 EMPTY_VALUE,实盘里调用方靠这个值就能判断指标句柄是否还活着。

MQL5 / C++
class="type">class="kw">double CopyBufferOneValue(class="type">int handle, class="type">int index, class="type">int shift) {
  class="type">class="kw">double buf[];                 class=class="str">"cmt">// 声明接收缓冲数据的数组
  if(CopyBuffer(handle, index, shift, class="num">1, buf) > class="num">0)
    class="kw">return(buf[class="num">0]);             class=class="str">"cmt">// 取到值就返回缓冲首元素
  class="kw">return EMPTY_VALUE;           class=class="str">"cmt">// 失败返回空值
}
Ask 和 Bid 不要再用老版 MarketInfo 碰运气,统一走 MqlTick 结构。SymbolInfoTick(_Symbol, last_tick) 抓最新 tick,last_tick.ask / last_tick.bid 就是当前买卖价,外汇和贵金属点差跳动快,这种取法延迟更低。 账户层面的权益与自由保证金,直接调 AccountInfoDouble 并传 ACCOUNT_EQUITY、ACCOUNT_MARGIN_FREE。贵金属杠杆高,开仓前读一下自由保证金,可避免保证金不足被强平的概率升高。

MQL5 / C++
class="type">class="kw">double CopyBufferOneValue(class="type">int handle, class="type">int index, class="type">int shift) {
  class="type">class="kw">double buf[];
  if(CopyBuffer(handle, index, shift, class="num">1, buf) > class="num">0)
    class="kw">return(buf[class="num">0]);
  class="kw">return EMPTY_VALUE;
}
class="type">class="kw">double Ask_LibFunc() {
  class="type">MqlTick last_tick;
  SymbolInfoTick(_Symbol, last_tick);
  class="kw">return last_tick.ask;
}
class="type">class="kw">double Bid_LibFunc() {
  class="type">MqlTick last_tick;
  SymbolInfoTick(_Symbol, last_tick);
  class="kw">return last_tick.bid;
}
class="type">class="kw">double AccountEquity_LibFunc() {
  class="kw">return AccountInfoDouble(ACCOUNT_EQUITY);
}
class="type">class="kw">double AccountFreeMargin_LibFunc() {
  class="kw">return AccountInfoDouble(ACCOUNT_MARGIN_FREE);
}

「用 switch 把旧版 MarketInfo 参数映射到 SymbolInfo」

在把老代码迁移到 MT5 时,最常见的坑是 MarketInfo() 已被弃用,但很多 EA 仍按 MODE_LOW、MODE_BID 这类旧枚举去取行情。下面这段分发逻辑直接把旧 type 映射到对应的 SymbolInfo 系列函数,复制进脚本即可编译通过。 注意 MODE_TIME、MODE_DIGITS、MODE_SPREAD、MODE_STOPLEVEL 返回的是 integer 类型,必须强转成 double 才能和原函数签名对齐;否则在严格编译模式下会报类型不匹配。外汇与贵金属杠杆高,SYMBOL_TRADE_STOPS_LEVEL 返回的止损距离可能随经纪商规则突变,实盘前应在 MT5 _symbol 规格里核对一次。 别把 SymbolInfo 当同步快照 SymbolInfoDouble / Integer 每次调用都向终端请求实时值,在 tick 密集的 XAUUSD 上循环调 15 个 case 可能拖慢 OnTick;倾向把需要的值缓存到结构体,只在新 tick 到来时刷新。

MQL5 / C++
class="kw">switch(type) {
   case MODE_LOW:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_LASTLOW));   class=class="str">"cmt">//--- Return last low price
   case MODE_HIGH:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_LASTHIGH));  class=class="str">"cmt">//--- Return last high price
   case MODE_TIME:
      class="kw">return((class="type">class="kw">double)SymbolInfoInteger(symbol, SYMBOL_TIME));               class=class="str">"cmt">//--- Return symbol time
   case MODE_BID:
      class="kw">return(Bid_LibFunc());                                                    class=class="str">"cmt">//--- Return Bid price
   case MODE_ASK:
      class="kw">return(Ask_LibFunc());                                                    class=class="str">"cmt">//--- Return Ask price
   case MODE_POINT:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_POINT));                           class=class="str">"cmt">//--- Return point size
   case MODE_DIGITS:
      class="kw">return((class="type">class="kw">double)SymbolInfoInteger(symbol, SYMBOL_DIGITS));             class=class="str">"cmt">//--- Return digit count
   case MODE_SPREAD:
      class="kw">return((class="type">class="kw">double)SymbolInfoInteger(symbol, SYMBOL_SPREAD));             class=class="str">"cmt">//--- Return spread
   case MODE_STOPLEVEL:
      class="kw">return((class="type">class="kw">double)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL));  class=class="str">"cmt">//--- Return stop level
   case MODE_LOTSIZE:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE));         class=class="str">"cmt">//--- Return contract size
   case MODE_TICKVALUE:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE));            class=class="str">"cmt">//--- Return tick value
   case MODE_TICKSIZE:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE));             class=class="str">"cmt">//--- Return tick size
   case MODE_SWAPLONG:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_SWAP_LONG));                   class=class="str">"cmt">//--- Return swap class="type">long
   case MODE_SWAPSHORT:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_SWAP_SHORT));                  class=class="str">"cmt">//--- Return swap class="type">short
   case MODE_MINLOT:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));                  class=class="str">"cmt">//--- Return minimum lot
   case MODE_LOTSTEP:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP));                 class=class="str">"cmt">//--- Return lot step
   case MODE_MAXLOT:
      class="kw">return(SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));                  class=class="str">"cmt">//--- Return maximum lot
}

◍ 用环形缓冲封装品种属性读取

上面这段 switch 收口后,真正能复用的是把品种属性塞进环形缓冲的封装思路。DoubleFunction 继承 IFunction,用 _values[] 存历史值、_zeroIndex 标记当前位置,对外只暴露 GetValue / Evaluate / Init 三个方法,调用方不用关心底层是 SYMBOL_SWAP_MODE 还是 SYMBOL_TRADE_FREEZE_LEVEL。 Init 里先把 _zeroIndex 置 -1,再 ArrayResize 到 ValueCount,最后用 ArrayInitialize 把整段缓冲填成 GetCurrentValue() 的实时值;也就是说,若 ValueCount 设 100,启动瞬间缓冲里就是 100 个相同当前值,回测和实盘衔接不会因空数组报错。 Evaluate 每次只取 GetCurrentValue() 写进缓冲,配合前面 return 0 的 default 分支,未知 type 不会让 EA 崩,只是静默给 0。外汇和贵金属点差、掉期随时变,这种封装让你在 MT5 里改一个 ValueCount 就能调节历史深度,风险仍高,参数误用可能读出偏差很大的上下文。

MQL5 / C++
  case MODE_SWAPTYPE:
      class="kw">return((class="type">class="kw">double)SymbolInfoInteger(symbol, SYMBOL_SWAP_MODE));          class=class="str">"cmt">//--- Return swap mode
  case MODE_PROFITCALCMODE:
      class="kw">return((class="type">class="kw">double)SymbolInfoInteger(symbol, SYMBOL_TRADE_CALC_MODE));    class=class="str">"cmt">//--- Return profit calc mode
  case MODE_FREEZELEVEL:
      class="kw">return((class="type">class="kw">double)SymbolInfoInteger(symbol, SYMBOL_TRADE_FREEZE_LEVEL)); class=class="str">"cmt">//--- Return freeze level
  class="kw">default:
      class="kw">return(class="num">0);                                                            class=class="str">"cmt">//--- Return class="num">0 for unknown type
  }
  class="kw">return(class="num">0);                                                                class=class="str">"cmt">//--- Ensure fallback class="kw">return
}
class=class="str">"cmt">//--- Define function interface
interface IFunction {
  class="type">class="kw">double GetValue(class="type">int index);                                               class=class="str">"cmt">//--- Retrieve value at index
  class="type">void Evaluate();                                                          class=class="str">"cmt">//--- Execute function evaluation
  class="type">void Init();                                                              class=class="str">"cmt">//--- Initialize function
};
class=class="str">"cmt">//--- Define base class for handling class="type">class="kw">double values in a circular buffer
class DoubleFunction : class="kw">public IFunction {
class="kw">private:
  class="type">class="kw">double _values[];                                                         class=class="str">"cmt">//--- Store array of historical values
  class="type">int _zeroIndex;                                                           class=class="str">"cmt">//--- Track current index in circular buffer
class="kw">protected:
  class="type">int ValueCount;                                                           class=class="str">"cmt">//--- Define number of values to store
class="kw">public:
  class=class="str">"cmt">//--- Initialize the circular buffer
  class="type">void Init() {
    _zeroIndex = -class="num">1;                                                        class=class="str">"cmt">//--- Set initial index to -class="num">1
    ArrayResize(_values, ValueCount);                                       class=class="str">"cmt">//--- Resize array to hold ValueCount elements
    ArrayInitialize(_values, GetCurrentValue());                            class=class="str">"cmt">//--- Fill array with current value
  }
  class=class="str">"cmt">//--- Update buffer with new value
  class="type">void Evaluate() {
    class="type">class="kw">double currentValue = GetCurrentValue();                                class=class="str">"cmt">//--- Retrieve current value
交给小布盯盘看轨道偏离
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到包络线轨道与价格触碰的实时标注,你专注决策就好。

常见问题

常见范围在0.1%至1.4%之间,具体取决于品种波动率和交易周期,建议在回测中分段测试再固定。
小布盯盘内置了包络线轨道与趋势过滤的实时标注,可辅助你核对信号,但EA编写仍需在MetaEditor中完成。
二者分别提供长周期方向锚点和短周期动量确认,组合使用可压低震荡市的虚假信号概率。
在流动性正常的时段通常够用,但贵金属跳空时可能拒单,建议按品种点差特性调整。
见《MQL5自动化交易策略·网格马丁剥头皮基础篇》中的动态看板与风控部分。