MQL5自动化交易策略(第十四部分):基于MACD-RSI统计方法的交易分层策略·进阶篇
◍ 做空触发与级联加仓的仓位管理
卖出信号与买入对称:当 RSI 前一根 K 线值 rsiValues[1] 上穿超买线、当前 rsiValues[0] 下穿该线时,可能触发做空条件。若启用 useStatisticalFilter,还需验证 rsiValues[0] 低于 rsiAverage + statDeviationFactor * rsiStdDeviation,且 MACD 主线与信号线同处零轴下方,概率上过滤假突破更稳。 开仓后不是死拿。系统用 ModifyTrades 函数遍历 PositionsTotal 返回的持仓数,按 positionType 匹配方向,把止损改写为 newStopLoss,止盈保留原 POSITION_TP。这套机制直接服务于级联加仓:买序列激活且 askPrice 触 takeProfitLevel 时,新 TP 设为 previousTakeProfit + stopLossPoints*riskRewardRatio*_Point,SL 设为 askPrice - minStopLossPoints*_Point,随后 obj_Trade.Buy 补仓并统一改损。 卖序列镜像处理:sellSequenceActive 且 bidPrice 触 TP,新 TP 从 previousTakeProfit 减去同值,SL 设为 bidPrice + minStopLossPoints*_Point,obj_Trade.Sell 加空。回测日志里出现过“CASCADING BUY”并打印了多级止损调整,说明该逻辑在 MT5 策略测试器可跑通。 图表上的止盈线靠 DrawTradeLevelLine 绘制,退出时必须清场。DeleteTradeLevelObjects 用 ObjectDelete 删掉 takeProfitLineName 与 takeProfitTextName,再挂到 OnDeinit,移除 EA 时不留垃圾对象。外汇与贵金属杠杆高,级联加仓会放大回撤,上 MT5 用 0.01 手先验证再放大。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| MACD-RSI LAYERING STRATEGY.mq5 | class=class="str">"cmt">//| Copyright class="num">2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. | class=class="str">"cmt">//| https://youtube.com/@ForexAlgo-Trader? | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader" class="macro">#class="kw">property link "https:class=class="str">"cmt">//youtube.com/@ForexAlgo-Trader?" class="macro">#class="kw">property description "MACD-RSI-based layering strategy with adjustable Risk:Reward and visual levels" class="macro">#class="kw">property version "class="num">1.0" class="macro">#include <Trade\Trade.mqh>class=class="str">"cmt">//---- Includes the Trade.mqh library for trading operations CTrade obj_Trade;class=class="str">"cmt">//---- Declares a CTrade object for executing trade operations class="type">int rsiHandle = INVALID_HANDLE;class=class="str">"cmt">//---- Initializes RSI indicator handle as invalid class="type">class="kw">double rsiValues[];class=class="str">"cmt">//---- Declares an array to store RSI values class="type">int handleMACD = INVALID_HANDLE;class=class="str">"cmt">//---- Initializes MACD indicator handle as invalid class="type">class="kw">double macdMAIN[];class=class="str">"cmt">//---- Declares an array to store MACD main line values class="type">class="kw">double macdSIGNAL[];class=class="str">"cmt">//---- Declares an array to store MACD signal line values class="type">class="kw">double takeProfitLevel = class="num">0;class=class="str">"cmt">//---- Initializes the take profit level variable class="type">class="kw">double stopLossLevel = class="num">0;class=class="str">"cmt">//---- Initializes the stop loss level variable class="type">bool buySequenceActive = false;class=class="str">"cmt">//---- Flag to track if a buy sequence is active class="type">bool sellSequenceActive = false;class=class="str">"cmt">//---- Flag to track if a sell sequence is active class=class="str">"cmt">// Inputs with clear names input class="type">int stopLossPoints = class="num">300; class=class="str">"cmt">// Initial Stop Loss(points) input class="type">class="kw">double tradeVolume = class="num">0.01; class=class="str">"cmt">// Trade Volume(lots) input class="type">int minStopLossPoints = class="num">100; class=class="str">"cmt">// Minimum SL for cascading orders(points)
RSI与MACD句柄的初始化落点
这段初始化逻辑把 RSI(14) 与 MACD(12,26,9) 两个指标句柄在 OnInit 里一次性建好,避免每跳 tick 重复申请系统资源。默认参数里超买 70、超卖 30 是经典阈值,统计过滤开关打开时取最近 20 根 K 线、1.0 倍标准差做参考带。 rsiHandle = iRSI(_Symbol, _Period, rsiLookbackPeriod, PRICE_CLOSE); 这行用当前品种与周期建 RSI 句柄;handleMACD = iMACD(...) 同理建标准 MACD。若任一返回 INVALID_HANDLE,直接 Print 报错并返回 INIT_FAILED,EA 不会上线跑。 ArraySetAsSeries 对 rsiValues、macdMAIN、macdSIGNAL 都置为 true,意味着数组下标 0 永远是最新一根棒。写策略时若忘了这步,用 Close[1] 对 RSI[1] 会错位,回测结果可能完全失真。 外汇与贵金属杠杆高,句柄初始化失败常见于品种停盘或报价中断,真仓前先在策略测试器用 XAUUSD 的 M15 跑一遍 OnInit 日志确认无 UNABLE TO LOAD 输出。
input class="type">int rsiLookbackPeriod = class="num">14; class=class="str">"cmt">// RSI Lookback Period input class="type">class="kw">double rsiOverboughtLevel = class="num">70.0; class=class="str">"cmt">// RSI Overbought Threshold input class="type">class="kw">double rsiOversoldLevel = class="num">30.0; class=class="str">"cmt">// RSI Oversold Threshold input class="type">bool useStatisticalFilter = true; class=class="str">"cmt">// Enable Statistical Filter input class="type">int statAnalysisPeriod = class="num">20; class=class="str">"cmt">// Statistical Analysis Period(bars) input class="type">class="kw">double statDeviationFactor = class="num">1.0; class=class="str">"cmt">// Statistical Deviation Factor input class="type">class="kw">double riskRewardRatio = class="num">1.0; class=class="str">"cmt">// Risk:Reward Ratio class=class="str">"cmt">// Object names for visualization class="type">class="kw">string takeProfitLineName = "TakeProfitLine";class=class="str">"cmt">//---- Name of the take profit line object for chart visualization class="type">class="kw">string takeProfitTextName = "TakeProfitText";class=class="str">"cmt">//---- Name of the take profit text object for chart visualization class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit(){class=class="str">"cmt">//---- Expert advisor initialization function rsiHandle = iRSI(_Symbol, _Period, rsiLookbackPeriod, PRICE_CLOSE);class=class="str">"cmt">//---- Creates RSI indicator handle with specified parameters handleMACD = iMACD(_Symbol,_Period,class="num">12,class="num">26,class="num">9,PRICE_CLOSE);class=class="str">"cmt">//---- Creates MACD indicator handle with standard class="num">12,class="num">26,class="num">9 settings if(rsiHandle == INVALID_HANDLE){class=class="str">"cmt">//---- Checks if RSI handle creation failed Print("UNABLE TO LOAD RSI, REVERTING NOW");class=class="str">"cmt">//---- Prints error message if RSI failed to load class="kw">return(INIT_FAILED);class=class="str">"cmt">//---- Returns initialization failure code } if(handleMACD == INVALID_HANDLE){class=class="str">"cmt">//---- Checks if MACD handle creation failed Print("UNABLE TO LOAD MACD, REVERTING NOW");class=class="str">"cmt">//---- Prints error message if MACD failed to load class="kw">return(INIT_FAILED);class=class="str">"cmt">//---- Returns initialization failure code } ArraySetAsSeries(rsiValues, true);class=class="str">"cmt">//---- Sets RSI values array as a time series(latest data at index class="num">0) ArraySetAsSeries(macdMAIN,true);class=class="str">"cmt">//---- Sets MACD main line array as a time series ArraySetAsSeries(macdSIGNAL,true);class=class="str">"cmt">//---- Sets MACD signal line array as a time series class="kw">return(INIT_SUCCEEDED);class=class="str">"cmt">//---- Returns successful initialization code } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(){class=class="str">"cmt">//---- Function called on each price tick
「抓取报价与多指标缓冲区的防错写法」
在 EA 的 OnTick 里,第一件事应当是拿到规范化的买卖价。用 NormalizeDouble 包住 SymbolInfoDouble(_Symbol, SYMBOL_ASK/BID) 并传入 _Digits,能避免后续下单价因小数位不一致被券商拒绝。 指标数据不能假设一定到位。CopyBuffer(rsiHandle,0,1,3,rsiValues) 的返回值若小于 3,说明最近 3 根 K 的 RSI 还没凑齐,这时 Print 提示并 return,能防止用空数组算信号。MACD 的主线和信号线也各用一次 CopyBuffer,任一失败直接退出当前 tick。 自己算 RSI 的统计特征时,先写个 CalculateRSIAverage(bars):ArraySetAsSeries 把缓冲排成时间序列,CopyBuffer 拉满 bars 根,循环累加再除以 bars。标准差函数 CalculateRSIStandardDeviation 则复用这个均值,对每根差值平方求和。外汇与贵金属波动剧烈,这类自定义统计在点差扩大时可能失真,上 MT5 用策略测试器跑不同 bars 参数(如 14 与 30)对比曲线更稳妥。
class="type">class="kw">double askPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);class=class="str">"cmt">//---- 获取并规范化当前卖价 class="type">class="kw">double bidPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);class=class="str">"cmt">//---- 获取并规范化当前买价 if(CopyBuffer(rsiHandle, class="num">0, class="num">1, class="num">3, rsiValues) < class="num">3){class=class="str">"cmt">//---- 复制3个RSI值到数组,检查是否成功 Print("INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK");class=class="str">"cmt">//---- 数据不足时打印错误 class="kw">return;class=class="str">"cmt">//---- 复制失败则退出函数 } if (!CopyBuffer(handleMACD,MAIN_LINE,class="num">0,class="num">3,macdMAIN))class="kw">return;class=class="str">"cmt">//---- 复制3个MACD主线值,失败则退出 if (!CopyBuffer(handleMACD,SIGNAL_LINE,class="num">0,class="num">3,macdSIGNAL))class="kw">return;class=class="str">"cmt">//---- 复制3个MACD信号线值,失败则退出 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate RSI Average | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CalculateRSIAverage(class="type">int bars){class=class="str">"cmt">//---- 计算RSI均值的函数 class="type">class="kw">double sum = class="num">0;class=class="str">"cmt">//---- 初始化求和变量 class="type">class="kw">double buffer[];class=class="str">"cmt">//---- 声明RSI值缓冲数组 ArraySetAsSeries(buffer, true);class=class="str">"cmt">//---- 将缓冲设为时间序列 if(CopyBuffer(rsiHandle, class="num">0, class="num">0, bars, buffer) < bars) class="kw">return class="num">0;class=class="str">"cmt">//---- 复制RSI值,失败返回0 for(class="type">int i = class="num">0; i < bars; i++){class=class="str">"cmt">//---- 遍历指定根数 sum += buffer[i];class=class="str">"cmt">//---- 每个RSI值累加到sum } class="kw">return sum / bars;class=class="str">"cmt">//---- 返回RSI平均值 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate RSI STDDev | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CalculateRSIStandardDeviation(class="type">int bars){class=class="str">"cmt">//---- 计算RSI标准差的函数 class="type">class="kw">double average = CalculateRSIAverage(bars);class=class="str">"cmt">//---- 先计算RSI均值 class="type">class="kw">double sumSquaredDiff = class="num">0;class=class="str">"cmt">//---- 初始化平方差之和 class="type">class="kw">double buffer[];class=class="str">"cmt">//---- 声明RSI值缓冲数组 ArraySetAsSeries(buffer, true);class=class="str">"cmt">//---- 将缓冲设为时间序列 if(CopyBuffer(rsiHandle, class="num">0, class="num">0, bars, buffer) < bars) class="kw">return class="num">0;class=class="str">"cmt">//---- 复制RSI值,失败返回0 for(class="type">int i = class="num">0; i < bars; i++){class=class="str">"cmt">//---- 遍历指定根数
◍ 把统计滤波和画线塞进开仓判定
统计滤波的开关由 useStatisticalFilter 控制,开启时先算 RSI 在 statAnalysisPeriod 周期内的均值与标准差,再拿当前 RSI 跟「均值减去 statDeviationFactor 倍标准差」比。这套写法让信号只在 RSI 偏离常态足够远时才被放行,噪声触发概率会低一些。 IsNewBar 用静态变量 previousBarCount 存上次 K 线数,iBars(_Symbol,_Period) 返回值变了才认新柱。配合 PositionsTotal()==0,能保证空仓且换柱的瞬间去算条件,避免一根柱内反复发单。 原文里 buyCondition 先判 RSI 上穿超卖,若开了统计滤波再叠一个偏离条件,但紧接着被 macdMAIN[0]<0 && macdSIGNAL[0]<0 整句覆盖重写——这段逻辑有坑,实盘前要把前面条件用 && 接上而不是赋值覆盖,否则统计滤波形同虚设。外汇与贵金属杠杆高,信号错乱可能瞬间放大亏损。 DrawTradeLevelLine 在画图前先 DeleteTradeLevelObjects 清旧对象,再用 ObjectCreate 以 OBJ_HLINE 画水平线,颜色 clrBlue、宽度 2、实线。把止盈位直接画在图上,比只看终端日志直观,小布盯盘时可一眼核对挂单位置。
class="type">class="kw">double diff = buffer[i] - average;class=class="str">"cmt">//---- Calculates difference from average sumSquaredDiff += diff * diff;class=class="str">"cmt">//---- Adds squared difference to sum } class="kw">return MathSqrt(sumSquaredDiff / bars);class=class="str">"cmt">//---- Returns standard deviation } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Is New Bar | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsNewBar(){class=class="str">"cmt">//---- Function to detect a new bar class="kw">static class="type">int previousBarCount = class="num">0;class=class="str">"cmt">//---- Stores the previous bar count class="type">int currentBarCount = iBars(_Symbol, _Period);class=class="str">"cmt">//---- Gets current number of bars if(previousBarCount == currentBarCount) class="kw">return false;class=class="str">"cmt">//---- Returns false if no new bar previousBarCount = currentBarCount;class=class="str">"cmt">//---- Updates previous bar count class="kw">return true;class=class="str">"cmt">//---- Returns true if new bar detected } class=class="str">"cmt">// Calculate statistical measures if enabled class="type">class="kw">double rsiAverage = useStatisticalFilter ? CalculateRSIAverage(statAnalysisPeriod) : class="num">0;class=class="str">"cmt">//---- Calculates RSI average if filter enabled class="type">class="kw">double rsiStdDeviation = useStatisticalFilter ? CalculateRSIStandardDeviation(statAnalysisPeriod) : class="num">0;class=class="str">"cmt">//---- Calculates RSI std dev if filter enabled if(PositionsTotal() == class="num">0 && IsNewBar()){class=class="str">"cmt">//---- Checks for no positions and new bar class=class="str">"cmt">// Buy Signal class="type">bool buyCondition = rsiValues[class="num">1] <= rsiOversoldLevel && rsiValues[class="num">0] > rsiOversoldLevel;class=class="str">"cmt">//---- Checks RSI crossing above oversold if(useStatisticalFilter){class=class="str">"cmt">//---- Applies statistical filter if enabled buyCondition = buyCondition && (rsiValues[class="num">0] < (rsiAverage - statDeviationFactor * rsiStdDeviation));class=class="str">"cmt">//---- Adds statistical condition } buyCondition = macdMAIN[class="num">0] < class="num">0 && macdSIGNAL[class="num">0] < class="num">0;class=class="str">"cmt">//---- Confirms MACD below zero for buy signal } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw TrendLine | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void DrawTradeLevelLine(class="type">class="kw">double price, class="type">bool isBuy){class=class="str">"cmt">//---- Function to draw take profit line on chart class=class="str">"cmt">// Delete existing objects first DeleteTradeLevelObjects();class=class="str">"cmt">//---- Removes existing trade level objects class=class="str">"cmt">// Create horizontal line ObjectCreate(class="num">0, takeProfitLineName, OBJ_HLINE, class="num">0, class="num">0, price);class=class="str">"cmt">//---- Creates a horizontal line at specified price ObjectSetInteger(class="num">0, takeProfitLineName, OBJPROP_COLOR, clrBlue);class=class="str">"cmt">//---- Sets line class="type">class="kw">color to blue ObjectSetInteger(class="num">0, takeProfitLineName, OBJPROP_WIDTH, class="num">2);class=class="str">"cmt">//---- Sets line width to class="num">2 ObjectSetInteger(class="num">0, takeProfitLineName, OBJPROP_STYLE, STYLE_SOLID);class=class="str">"cmt">//---- Sets line style to solid class=class="str">"cmt">// Create text, above for buy, below for sell with increased spacing