MQL5 中的定量分析:实现有前途的算法·进阶篇
◍ EA 初始化与持仓统计的底子
把 ZigZag 接进 EA 的第一步,是在自定义初始化函数里用 iCustom 拿指标句柄。下面这段把品种、周期和指标参数(深度、偏离、回退、枢轴点)一次性传进去,拿不到句柄就直接 INIT_FAILED,避免在后续行情里对着空指针算折返。 global 变量 zigzagHandle 只存一个 descriptor,真正跨周期复用靠的是 OnIniti(symb) 里把 symb 显式传入,这样同一套逻辑能挂不同品种而不用重写。 持仓统计函数 AllProfit 给外部提供了按魔术码和订单方向过滤的能力:type 传 -1 表示不区分买卖、只认本 EA 的 Magic。实盘里若同时跑多个策略,不隔离 Magic 会把这些单子的浮动盈亏全混进一个数字,回测和盯盘都会失真。 外汇与贵金属杠杆高,这类统计若漏掉滑点或未平盈亏波动,可能让平仓逻辑误判总权益,开 MT5 把 Magic 和 InpDepth 调成与你图表一致再验证。
class=class="str">"cmt">// Global variable for storing the indicator descriptor class="type">int zigzagHandle; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { trade.SetExpertMagicNumber(Magic); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function custom | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnIniti(class="type">class="kw">string symb) {class=class="str">"cmt">// Loading the ZigZag indicator zigzagHandle = iCustom(symb, _Period, "ZigZag", InpDepth, InpDeviation, InpBackstep, InpPivotPoint); if (zigzagHandle == INVALID_HANDLE) { Print("Error loading the ZigZag indicator: ", GetLastError()); class="kw">return(INIT_FAILED); } class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { Comment(""); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Position Profit | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double AllProfit(class="type">int type=-class="num">1) { class="type">class="kw">double p=class="num">0; for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetInteger(POSITION_MAGIC)==Magic) { if(PositionGetInteger(POSITION_TYPE)==type || type==-class="num">1) p+=PositionGetDouble(POSITION_PROFIT); } } } class="kw">return(p); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CloseAll | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CloseAll(class="type">int type=-class="num">1) { for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) {
按魔术码和类型清仓的底层循环
这段逻辑跑在 EA 的持仓遍历里:先拿 PositionGetTicket(i) 取出第 i 个持仓的 ticket,再用 PositionSelectByTicket 把它激活到当前上下文。只有选中成功才继续判断,避免对无效持仓调用属性函数导致返零。 内层先比 POSITION_MAGIC 是否等于脚本里的 Magic 变量,不是自己开的单一律跳过。接着看 POSITION_TYPE:若传入的 type 与持仓方向一致,或 type 写成了 -1(代表不限方向),就直接 trade.PositionClose 平掉这一张。外汇与贵金属杠杆高,这种按魔术码隔离平仓能防止误伤手动单,但平仓失败不会抛异常,实盘前应在 MT5 策略测试器里跑一遍观察成交回执。 CountTrades 给网格加仓踩刹车用:倒序扫 PositionsTotal(),只统计 POSITION_SYMBOL 等于传入 symb 的持仓数。返回的是当前符号下真实挂着的单量,配合上限判断能控住马丁类策略的暴露。 Lot 函数把风险百分比落成手数:Risk>0 时手数 = 账户净值 × Risk ÷ 100000,再 NormalizeDouble 到两位小数。以 1 万美金、Risk=1 为例,算出来是 0.1 手,注意这是按标准手 10 万合约估的,黄金(XAUUSD)点值不同,需另测。 FindLastBuyPrice 专找某符号下最近一张买持仓的开仓价:遇到 POSITION_TYPE==0 且符号匹配就记下 POSITION_PRICE_OPEN 并 break。返回 0 代表没买单,上层可据此决定是否补仓或反向。
if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetInteger(POSITION_MAGIC)==Magic) { if(PositionGetInteger(POSITION_TYPE)==type || type==-class="num">1) trade.PositionClose(PositionGetTicket(i)); } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CountTrades | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CountTrades(class="type">class="kw">string symb) { class="type">int count=class="num">0; for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetString(POSITION_SYMBOL)==symb) { count++; } } } class="kw">return(count); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Lot | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double Lot() { class="type">class="kw">double lot=Lots; if(Risk>class="num">0) lot=AccountInfoDouble(ACCOUNT_BALANCE)*Risk/class="num">100000; class="kw">return(NormalizeDouble(lot,class="num">2)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double FindLastBuyPrice(class="type">class="kw">string symb) { class="type">class="kw">double pr=class="num">0; for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { if(PositionSelectByTicket(PositionGetTicket(i)) && PositionGetInteger(POSITION_TYPE)==class="num">0) { if(PositionGetString(POSITION_SYMBOL)==symb) { pr=PositionGetDouble(POSITION_PRICE_OPEN); break; } } } class="kw">return(pr);
「持仓回溯与波幅统计的函数骨架」
要在 MT5 里做价格行为复盘,先得把当前持仓的卖单开仓价和持仓类型捞出来。下面这段函数从持仓池倒序遍历,遇到指定品种且类型为 1(SELL)的仓位就返回其开仓价,没找到则返回 0。 FindLastSellPrice 只解决「上一张卖单在哪开的」;PositionType 则更泛化,返回指定品种持仓的类型编号,初始值设 8 表示无持仓匹配。注意 POSITION_TYPE 在 MQL5 里 0 是买、1 是卖,这两个函数都靠 PositionSelectByTicket 定位再读字段。 波幅统计交给 CalculateAverageMovement:写死回看 500 根 K 线,用 CopyBuffer 把 ZigZag 指标值塞进长度 500 的数组。若复制失败直接 Print 报错。外汇与贵金属杠杆高,这类统计仅作概率参考,实盘须自担风险。 别把初始值 8 当常态 PositionType 里 type=8 只是「没逮到仓位」的占位,不是真实持仓枚举。写 EA 时若拿 8 去比大小会出逻辑漏洞,建议调用方显式判断返回值是否小于 2。
class="type">class="kw">double FindLastSellPrice(class="type">class="kw">string symb) { class="type">class="kw">double pr=class="num">0; for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { if(PositionSelectByTicket(PositionGetTicket(i)) && PositionGetInteger(POSITION_TYPE)==class="num">1) { if(PositionGetString(POSITION_SYMBOL)==symb) { pr=PositionGetDouble(POSITION_PRICE_OPEN); break; } } } class="kw">return(pr); } class="type">int PositionType(class="type">class="kw">string symb) { class="type">int type=class="num">8; for(class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetString(POSITION_SYMBOL)==symb) { type=(class="type">int)PositionGetInteger(POSITION_TYPE); break; } } } class="kw">return(type); } class="type">void CalculateAverageMovement(class="type">class="kw">string symb, class="type">class="kw">double &averageMovement, class="type">class="kw">double ¤tMovement) { class="kw">const class="type">int lookback = class="num">500; class=class="str">"cmt">// Number of bars for analysis class="type">class="kw">double sumMovements = class="num">0.0; class="type">int countMovements = class="num">0; class="type">class="kw">double lastExtremePrice = class="num">0.0; class="type">class="kw">double zigzagArray[class="num">500]; class=class="str">"cmt">// Array to store ZigZag values OnIniti(symb); class=class="str">"cmt">// Copy ZigZag values to array if (CopyBuffer(zigzagHandle, class="num">0, class="num">0, lookback, zigzagArray) <= class="num">0) { Print("Error copying indicator data");
◍ 把 ZigZag 均值位移接进交易判定
这段逻辑紧接前文的 CalculateAverageMovement,把算出的 averageMovement 与 currentMovement 直接喂给下单函数 Trade()。先通过 SymbolInfoDouble 取 Ask、Bid,再用 SymbolInfoInteger 拿小数位 dg、SymbolInfoDouble 取点值 pp,这些都是后续挂单距离计算的底料。 ATR 周期写死为 3:double atr = iATR(symb, PERIOD_CURRENT, 3); 用极短周期 ATR 去衡量当下波动,对外汇与贵金属这类高杠杆品种只是粗筛,真实滑点可能吞掉窄止损,务必在 MT5 里把周期改成 14 对比一下触发频率。 买卖信号是反直觉写法:sell 条件是 currentMovement > -averageMovement,buy 条件是 -currentMovement > averageMovement。意思是价格从最近极值回拉幅度若超过历史平均位移的反向值,就倾向反向开仓,属于均值回归思路,并非趋势跟随。 下面给出本小节涉及的 MQL5 片段,注意 CopyBuffer 失败会直接把两个位移置 -1 并 return,调用方 Trade() 里若拿到 -1 应当跳过下单,否则会用脏数据计算。
averageMovement = -class="num">1; currentMovement = -class="num">1; class="kw">return; } class=class="str">"cmt">// Copy ZigZag values to array if (CopyBuffer(zigzagHandle, class="num">0, class="num">0, lookback, zigzagArray) <= class="num">0) { Print("Error copying indicator data"); averageMovement = -class="num">1; currentMovement = -class="num">1; class="kw">return; } for (class="type">int i = class="num">0; i < lookback; i++) { if (zigzagArray[i] != class="num">0 && zigzagArray[i] != lastExtremePrice) { if (lastExtremePrice != class="num">0) { class=class="str">"cmt">// Determine the movement direction class="type">class="kw">double movement = zigzagArray[i] - lastExtremePrice; sumMovements += movement; countMovements++; } lastExtremePrice = zigzagArray[i]; } } class=class="str">"cmt">// Calculate the current movement class="type">class="kw">double lastMovement = iClose(symb, _Period, class="num">0) - lastExtremePrice; currentMovement = lastMovement; class=class="str">"cmt">// Calculate the average movement averageMovement = countMovements > class="num">0 ? sumMovements / countMovements : class="num">0.0; class=class="str">"cmt">// Print the result Print("Average movement: ", averageMovement); Print("Current movement: ", currentMovement); class=class="str">"cmt">// Release resources IndicatorRelease(zigzagHandle); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert Trade unction | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Trade(class="type">class="kw">string symb) { class="type">class="kw">double averageMovement = class="num">0; class="type">class="kw">double currentMovement = class="num">0; class="type">class="kw">double pr=class="num">0,sl=class="num">0,tp=class="num">0,hi=class="num">0,lo=class="num">0; class=class="str">"cmt">// Call function for calculation CalculateAverageMovement(symb, averageMovement, currentMovement); class=class="str">"cmt">// Use results class="type">class="kw">double Ask = SymbolInfoDouble(symb, SYMBOL_ASK); class="type">class="kw">double Bid = SymbolInfoDouble(symb, SYMBOL_BID); class="type">int dg=(class="type">int)SymbolInfoInteger(symb,SYMBOL_DIGITS); class="type">class="kw">double pp=SymbolInfoDouble(symb,SYMBOL_POINT); class="type">class="kw">double atr = iATR(symb, PERIOD_CURRENT, class="num">3); class=class="str">"cmt">// Here define your logic for buying and selling class="type">bool sell = currentMovement > -averageMovement; class=class="str">"cmt">// Buy condition class="type">bool buy = -currentMovement > averageMovement; class=class="str">"cmt">// Sell condition if(AllProfit()>Profit && Profit>class="num">0)