MQL5自动化交易策略(第十八部分):基于包络线趋势反弹的剥头皮交易核心架构与信号生成(1)·进阶篇
(2/3)· 从指标轨道到信号验证,手把手搭出可回测的高频剥头皮程序骨架
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。外汇与贵金属波动剧烈、杠杆风险高,这类基础定义虽不直接下单,但错一个宏值会让后续报价判断整体偏移。
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)) 这类语句自测一下返回值。
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,意味着默认状态机不触发任何增删改查,避免未初始化就误执行下单。外汇与贵金属杠杆高,这类默认值缺失可能引发非预期建仓,实盘前务必在策略测试器逐枚举走查。
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,实盘里调用方靠这个值就能判断指标句柄是否还活着。
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">// 失败返回空值 }
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 到来时刷新。
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 就能调节历史深度,风险仍高,参数误用可能读出偏差很大的上下文。
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