MQL5 简介(第 10 部分):MQL5 中使用内置指标的初学者指南·综合运用
📊

MQL5 简介(第 10 部分):MQL5 中使用内置指标的初学者指南·综合运用

(3/3)· 新手也能照做的 RSI EA 全流程,句柄、缓冲区与百分比风控一次打通

新手友好 第 3/3 篇
很多新手把指标直接画在图上就以为会用 MQL5 了,真到写 EA 时连句柄都拿不稳。其实内置指标在代码里只是一串引用 ID,搞错初始化顺序,回测跑出来全是空值。本篇用 RSI 把整套交互链路拆给你看。

◍ 用趋势线把高低点钉在图上

这段初始化与绘图逻辑,核心是把一段统计窗口内的最高价和最低价,用两条 OBJ_TREND 线固化到主图,方便肉眼对照价格行为。绿线标高位、红线标低位,线宽都设成 3,避免在 MT5 缩放后看不清。 ObjectCreate 里用 TimeCurrent() 作为终点时间,意味着线会一直延伸到当前时刻;起点则取自静态变量 min_time1_static / min_time2_static,配合 max_high_static / min_low_static 锁死极值。实盘里若这些静态值来自前 100 根 K 线扫描,那这条通道就代表了近期波动边界。 OnInit 中 rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 把 RSI 周期钉在 14,ArraySetAsSeries 全部置 true,使 buffer[0] 永远是最新一根。OnTick 里 CopyBuffer(rsi_handle,0,1,100,rsi_buffer) 从偏移 1 开始抓 100 个值,避开当前未闭合的 bar,回测时更能反映信号触发前的状态。 外汇与贵金属杠杆高、滑点随机,这类画线只是辅助参考,极值突破后价格可能惯性延伸也可能假突破,需结合别的条件过滤。

MQL5 / C++
ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,class="num">0,min_time1_static,max_high_static,TimeCurrent(),max_high_static);
ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen);
ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,class="num">3);
ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,class="num">0,min_time2_static,min_low_static,TimeCurrent(),min_low_static);
ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed);
ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,class="num">3);
}
class=class="str">"cmt">// Magic number
input class="type">int MagicNumber = class="num">1111;
class="type">int       rsi_handle;
class="type">class="kw">double    rsi_buffer[];
class="type">class="kw">double open[];
class="type">class="kw">double close[];
class="type">class="kw">double high[];
class="type">class="kw">double low[];
class="type">class="kw">datetime time[];
class="type">class="kw">double max_high = class="num">0;
class="type">class="kw">datetime min_time1 = class="num">0;
class="type">class="kw">double min_low = class="num">0;
class="type">class="kw">datetime min_time2 = class="num">0;
class="type">class="kw">datetime time_low = class="num">0;
class="type">class="kw">datetime times_high = class="num">0;
class="type">class="kw">string high_obj_name = "High_Line";
class="type">class="kw">string low_obj_name = "Low_Line";
class="type">long chart_id;
class="type">class="kw">double take_profit;
class="type">class="kw">double  ask_price = class="num">0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">// Configure RSI buffer as a series for easier indexing
   ArraySetAsSeries(rsi_buffer, true);
class=class="str">"cmt">// Initialize RSI handle for the current symbol, timeframe, and parameters
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE);
class=class="str">"cmt">// Configure candlestick arrays as series
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(time, true);
class=class="str">"cmt">// Set the magic number
   trade.SetExpertMagicNumber(MagicNumber);
   class="kw">return (INIT_SUCCEEDED); class=class="str">"cmt">// Indicate successful initialization
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// Copy RSI values from the indicator into the buffer
   CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer);
class=class="str">"cmt">// Copy candlestick data
   CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open);
   CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close);

「用刺穿反转结构挂首仓」

这段逻辑先把最近 100 根 K 线的高、低、时间数组拉进内存,并用 static 变量锁住前期算好的极值与对应时间,避免在每帧 OnTick 里重复计算。接着扫一遍持仓池,只统计当前图表品种、且 MagicNumber 匹配的头寸,没持仓时才允许开新单。 开多条件盯的是「向下刺穿前低后收回」:最近 0~2 根里任一根最低价跌破 min_low_static,但最新收盘不仅拉回该位之上,且收阳(close>open)。满足后,止损放在 low[0],止盈按 (收盘-最低)*3+收盘 推算,用 trade.Buy(0.5,...) 下 0.5 手。 对称地,空单看「向上刺穿前高后回落」:最高价越过 max_high_static 后收盘跌回其下并收阴,同样允许 0~2 根内完成刺穿。外汇与贵金属杠杆高,刺穿失败直接走反向的概率不低,参数 0.5 手与 3 倍盈利距仅作代码样例,实盘须按账户净值重算。 把下面代码贴进 MT5 脚本,把 min_low_static / max_high_static 的赋值补上,就能在 EURUSD 的 M15 上肉眼验证信号触发频率。

MQL5 / C++
  CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high);
  CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low);
  CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time);
  class="kw">static class="type">class="kw">double max_high_static = max_high;
  class="kw">static class="type">class="kw">datetime min_time1_static = min_time1;
  class="kw">static class="type">class="kw">double min_low_static = min_low;
  class="kw">static class="type">class="kw">datetime min_time2_static = min_time2;
class=class="str">"cmt">//GETTING TOTAL POSITIONS
  class="type">int totalPositions = class="num">0;
  for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
   {
     class="type">ulong ticket = PositionGetTicket(i);
     if(PositionSelectByTicket(ticket))
      {
       if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id))
        {
         totalPositions++;
        }
      }
   }
  ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
  if(totalPositions < class="num">1)
   {
     if(((low[class="num">0] < min_low_static && close[class="num">0] > min_low_static && close[class="num">0] > open[class="num">0]) || (low[class="num">1] < min_low_static && close[class="num">0] > min_low_static
         && close[class="num">0] > open[class="num">0]) || (low[class="num">2] < min_low_static && close[class="num">0] > min_low_static
                                                                          && close[class="num">0] > open[class="num">0] && close[class="num">1] < open[class="num">1])))
      {
       take_profit = (close[class="num">0] - low[class="num">0]) * class="num">3 + close[class="num">0];
       trade.Buy(class="num">0.5,_Symbol,ask_price, low[class="num">0], take_profit);
      }
     else
      if(((high[class="num">0] > max_high_static && close[class="num">0] < max_high_static && close[class="num">0] < open[class="num">0]) || (high[class="num">1] > max_high_static && close[class="num">0] < max_high_static
          && close[class="num">0] < open[class="num">0]) || (high[class="num">2] > max_high_static && close[class="num">0] < max_high_static
                                                                       && close[class="num">0] < open[class="num">0] && close[class="num">1] > open[class="num">1])))
       {

用最近12根K线与RSI锁定高低点锚

这段逻辑在每根新 tick 进来后重算图表 ID,并扫描最近 12 根 K 线,试图抓住一段未被破坏的高低点结构。它不依赖指标重绘,而是直接读 close/open/high/low 数组与 RSI 缓冲,因此你在 MT5 里改 PERIOD_CURRENT 为具体周期,行为会立刻变化。 先看多头锚:循环里只要出现 close[i]<open[i] 且下一根 close[i+1]>open[i+1],就取这两根的高点最大值作为 max_high,并取两根时间的最小值存为 min_time1,随后 break。也就是说,它只认最近一次「阴转阳」作为潜在底部确认,超过 12 根没出现就放弃。 空头锚对称处理:close[i]>open[i] 且下一根反包收阴,取两根低点最小值 min_low。RSI 部分另开两个循环,rsi_buffer[i+1]<30 且当前值大于前值时记 time_low;rsi_buffer[i+1]>70 且当前值小于前值时记 times_high——这是把超买超卖当作结构失效的辅助判据。 最后的静态变量刷新规则很硬:total_bar_high 为 0 或大于 12,且 min_time1 等于 times_high,才把 max_high 写进 max_high_static;若 min_time1 与 times_high 不一致且 total_bar_high>13,则直接清零。外汇与贵金属波动下这种阈值(12/13/30/70)可能频繁触发重置,实盘前建议用策略测试器跑至少 3 个月 tick 数据验证重置频率。

MQL5 / C++
take_profit = MathAbs((high[class="num">0] - close[class="num">0]) * class="num">3 - close[class="num">0]); class=class="str">"cmt">// Adjusted take-profit calculation
      trade.Sell(class="num">0.5,_Symbol,ask_price, high[class="num">0], take_profit);
       }
     }
class=class="str">"cmt">//CHART ID
   chart_id = ChartID();
   class="type">int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent());
   for(class="type">int i = class="num">0; i < class="num">12; i++)
     {
      if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1])
        {
         max_high = (class="type">class="kw">double)MathMax(high[i],high[i+class="num">1]);
         min_time1 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]);
         class="kw">break;
         }
     }
   class="type">int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent());
   for(class="type">int i = class="num">0; i < class="num">12; i++)
     {
      if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1])
        {
         min_low = (class="type">class="kw">double)MathMin(low[i],low[i+class="num">1]);
         min_time2 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]);
         class="kw">break;
         }
     }
   for(class="type">int i = class="num">0; i < class="num">12; i++)
     {
      if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1])
        {
         time_low = time[i+class="num">1];
         class="kw">break;
         }
     }
   for(class="type">int i = class="num">0; i < class="num">12; i++)
     {
      if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1])
        {
         times_high = time[i+class="num">1];
         class="kw">break;
         }
     }
   if((total_bar_high == class="num">0 || total_bar_high > class="num">12) && (min_time1 == times_high))
     {
      max_high_static = max_high;
      min_time1_static = min_time1;
     }
   else
      if(min_time1 != times_high && total_bar_high > class="num">13)
        {
         max_high_static = class="num">0;
         min_time1_static = class="num">0;
         }
   if((total_bar_low == class="num">0 || total_bar_low > class="num">12) && (min_time2 == time_low))
     {
      min_low_static = min_low;
      min_time2_static = min_time2;
     }
   else
      if(min_time2 != time_low && total_bar_low > class="num">13)

◍ 把高低点画成线并接上风控参数

这段逻辑先把统计到的最低点变量清零,再分别用 OBJ_TREND 把波段高点和低点拉成水平参考线:高线取 max_high_static 挂绿色、低线取 min_low_static 挂红色,线宽都设 3,右端延伸到 TimeCurrent(),也就是实时跟随当前K线。 代码里 input 块直接暴露了仓位计算的源头:account_balance 默认 1000,percentage_risk 默认 2.0,rrr 默认 3。也就是说单笔最大可接受亏损额 = 1000 × 2% = 20 单位,若打满风险回报比,理论盈利目标约为 60 单位——当然外汇和贵金属杠杆高,实际滑点扩大时亏损可能超过该预设值。 下面的全局变量里 lot_size、risk_Amount、points_risk 都还没赋值,说明真正的手数要等后续用 (account_balance × percentage_risk / 100) 除以每点风险来倒推;allow_modify 默认 false 代表保本挪损先不开启。 开 MT5 把这段粘进 EA,先改 account_balance 和 percentage_risk 匹配你自己账户,看水平线颜色和宽度是否符合盯盘习惯,再决定要不要动 rrr。

MQL5 / C++
   {
   min_low_static = class="num">0;
   min_time2_static = class="num">0;
   }
   ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,class="num">0,min_time1_static,max_high_static,TimeCurrent(),max_high_static);
   ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,class="num">3);
   ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,class="num">0,min_time2_static,min_low_static,TimeCurrent(),min_low_static);
   ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed);
   ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,class="num">3);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     MQL5INDICATORS_PROJECT4.mq5 |
class=class="str">"cmt">//|          Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "ForexYMN"
class="macro">#class="kw">property link      "crownsoyin@gmail.com"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include <Trade/Trade.mqh>
CTrade trade;
class=class="str">"cmt">// Magic number
input class="type">int      MagicNumber = class="num">1111;
input class="type">class="kw">double account_balance        = class="num">1000;   class=class="str">"cmt">// Account Balance
input class="type">class="kw">double percentage_risk = class="num">2.0;   class=class="str">"cmt">//      How many percent of the account do you want to risk per trade?
input class="type">bool   allow_modify  = false; class=class="str">"cmt">// Do you allow class="kw">break even modifications?
input class="type">int      rrr         = class="num">3;        class=class="str">"cmt">// Choose Risk Reward Ratio

class="type">int      rsi_handle;
class="type">class="kw">double   rsi_buffer[];
class="type">class="kw">double open[];
class="type">class="kw">double close[];
class="type">class="kw">double high[];
class="type">class="kw">double low[];
class="type">class="kw">datetime time[];
class="type">class="kw">double max_high = class="num">0;
class="type">class="kw">datetime min_time1 = class="num">0;
class="type">class="kw">double min_low = class="num">0;
class="type">class="kw">datetime min_time2 = class="num">0;
class="type">class="kw">datetime time_low = class="num">0;
class="type">class="kw">datetime times_high = class="num">0;
class="type">class="kw">string high_obj_name = "High_Line";
class="type">class="kw">string low_obj_name = "Low_Line";
class="type">long chart_id;
class="type">class="kw">double take_profit;
class="type">class="kw">double  ask_price = class="num">0;
class="type">class="kw">double lot_size;
class="type">class="kw">double risk_Amount;
class="type">class="kw">double points_risk;
class=class="str">"cmt">// Risk modification
class="type">class="kw">double positionProfit = class="num">0;
class="type">class="kw">double positionopen = class="num">0;
class="type">class="kw">double positionTP = class="num">0;

「EA初始化与逐根K线的数据搬运」

把 RSI 和裸 K 数据搬进缓冲区,是后续所有逻辑的前提。OnInit 里先把 rsi_buffer 和各 OHLC 数组设为序列排列(ArraySetAsSeries 置 true),这样下标 0 就是当前未完成或刚收盘的 K 线,索引直观。 RSI 句柄用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 建,周期取当前图表、长度 14、收盘价计算——这是最通用的超买超卖参考窗。trade.SetExpertMagicNumber 绑好魔数,保证只动自己下的单。 OnTick 用静态变量 prevBars 做新旧 Bar 数比对,currBars 不变就直接 return,把计算压到每根新 K 线触发一次,避免同根内重复跑。随后 CopyBuffer(rsi_handle,0,1,100,rsi_buffer) 从指标句柄搬 100 根 RSI,CopyOpen/CopyClose/CopyHigh/CopyLow/CopyTime 同样从偏移 1 开始抓 100 根,留足回溯空间给波峰波谷判定。 几个 static 双精度与 datetime 变量(max_high_static、min_time1_static 等)在 tick 首部接住外部传入的极值与时间点,说明极值状态跨 tick 保留,不会每根 K 重算归零。外汇与贵金属杠杆高,这类 EA 实盘前务必在 MT5 策略测试器用至少 3 个月 tick 数据验证信号延迟。

MQL5 / C++
class="type">class="kw">double positionSL = class="num">0;
class="type">class="kw">double modifyLevel = class="num">0.0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">// Configure RSI buffer as a series for easier indexing
   ArraySetAsSeries(rsi_buffer, true);
class=class="str">"cmt">// Initialize RSI handle for the current symbol, timeframe, and parameters
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE);
class=class="str">"cmt">// Configure candlestick arrays as series
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(time, true);
class=class="str">"cmt">// Set the magic number
   trade.SetExpertMagicNumber(MagicNumber);
   class="kw">return (INIT_SUCCEEDED); class=class="str">"cmt">// Indicate successful initialization
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   class="type">int currBars = iBars(_Symbol,_Period);
   class="kw">static class="type">int prevBars = currBars;
   if(prevBars == currBars)
      class="kw">return;
   prevBars = currBars;
class=class="str">"cmt">// Copy RSI values from the indicator into the buffer
   CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer);
class=class="str">"cmt">// Copy candlestick data
   CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open);
   CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close);
   CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high);
   CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low);
   CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time);
   class="kw">static class="type">class="kw">double max_high_static = max_high;
   class="kw">static class="type">class="kw">datetime min_time1_static = min_time1;
   class="kw">static class="type">class="kw">double min_low_static = min_low;
   class="kw">static class="type">class="kw">datetime min_time2_static = min_time2;
class=class="str">"cmt">//GETTING TOTAL POSITIONS
   class="type">int totalPositions = class="num">0;
   for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
     {
      class="type">ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
        {
         if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id))
           {

无持仓时的刺透与反转开仓逻辑

当账户里当前品种持仓数 totalPositions 小于 1,也就是完全空仓时,EA 才会考虑进场。这一步直接卡死了加仓和马丁类行为,外汇与贵金属波动剧烈,空仓触发能压住过度交易的概率。 多头触发看的是最近 0~2 根 K 线中,至少有一根最低价刺穿了静态最低参考 min_low_static,但最新收盘价又收回该线之上,且收阳(close[0] > open[0]);若刺透发生在前一根且前一根收阴,也纳入条件。满足后,以 (close[0]-low[0])*rrr+close[0] 算止盈,风险点数就是刺透深度,再按账户余额百分比算 lot。 空头对称:最高价刺过 max_high_static 后收回到线下方且收阴,最近三根内任一满足即触发。止盈用 MathAbs((high[0]-close[0])*rrr - close[0]),注意这里取了绝对值避免负价,risk 点数取刺透幅度。 开仓前用 SymbolInfoDouble(_Symbol, SYMBOL_ASK) 抓实时卖一价,Buy 挂 low[0] 止损、Sell 挂 high[0] 止损。下面这段是原文核心片段,逐行拆完你就能直接抄进 MT5 测。 totalPositions++ 是在前面遍历持仓时计数,这里判断 <1 才放行。 ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK) 取当前品种卖价,用于市价买成交参考。 if(totalPositions < 1) 空仓闸门。 多头三重 OR:low[0]/low[1]/low[2] 任一小于 min_low_static 且 close[0] 收回线上且收阳,或 low[2] 刺透且前一根收阴,条件较宽。 take_profit = (close[0]-low[0])*rrr + close[0] 按风险回报比挂盈。 points_risk / minus 都是刺透深度,NormalizeDouble(...,5) 规范到 5 位小数。 lot_size = CalculateLotSize(...) 按风险金额反推手数。 trade.Buy(lot_size, _Symbol, ask_price, low[0], take_profit) 市价买、止损刺透低点、止盈按比例。 空头分支对称,take_profit 用 MathAbs 防负,trade.Sell 以 high[0] 为止损。 chart_id = ChartID() 抓图表句柄,total_bar_high 用 Bars 数出自 min_time1_static 到当前的 K 线根数,供后续绘制或统计。

MQL5 / C++
   totalPositions++;
     }
   }
  }
  ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
  if(totalPositions < class="num">1)
   {
     if(((low[class="num">0] < min_low_static && close[class="num">0] > min_low_static && close[class="num">0] > open[class="num">0]) || (low[class="num">1] < min_low_static && close[class="num">0] > min_low_static
        && close[class="num">0] > open[class="num">0]) || (low[class="num">2] < min_low_static && close[class="num">0] > min_low_static
                                                                     && close[class="num">0] > open[class="num">0] && close[class="num">1] < open[class="num">1])))
       {
       take_profit = (close[class="num">0] - low[class="num">0]) * rrr + close[class="num">0];
       points_risk = close[class="num">0] - low[class="num">0];
       class="type">class="kw">double riskAmount = account_balance * (percentage_risk / class="num">100.0);
       class="type">class="kw">double minus = NormalizeDouble(close[class="num">0] - low[class="num">0],class="num">5);
       lot_size = CalculateLotSize(_Symbol, riskAmount, minus);
       trade.Buy(lot_size,_Symbol,ask_price, low[class="num">0], take_profit);
       }
     else
       if(((high[class="num">0] > max_high_static && close[class="num">0] < max_high_static && close[class="num">0] < open[class="num">0]) || (high[class="num">1] > max_high_static && close[class="num">0] < max_high_static
          && close[class="num">0] < open[class="num">0]) || (high[class="num">2] > max_high_static && close[class="num">0] < max_high_static
                                                                  && close[class="num">0] < open[class="num">0] && close[class="num">1] > open[class="num">1])))
         {
         take_profit = MathAbs((high[class="num">0] - close[class="num">0]) * rrr - close[class="num">0]); class=class="str">"cmt">// Adjusted take-profit calculation
         points_risk = MathAbs(high[class="num">0] - close[class="num">0]);
         class="type">class="kw">double riskAmount = account_balance * (percentage_risk / class="num">100.0);
         class="type">class="kw">double minus = NormalizeDouble(high[class="num">0] - close[class="num">0],class="num">5);
         lot_size = CalculateLotSize(_Symbol, riskAmount, minus);
         trade.Sell(lot_size,_Symbol,ask_price, high[class="num">0], take_profit);
         }
   }
class=class="str">"cmt">//CHART ID
   chart_id = ChartID();
   class="type">int total_bar_high = Bars(_Symbol, PERIOD_CURRENT, min_time1_static, TimeCurrent());

◍ 用阴阳线配对锁定高低点锚位

这段逻辑在最近 12 根 BAR 里找「阴线接阳线」和「阳线接阴线」的相邻组合,把前者的两 K 线最高价取大值作为阻力锚 max_high,后者的两 K 线最低价取小值作为支撑锚 min_low。循环里一旦命中就 break,所以只认最近一次符合条件的配对,避免被更早的噪声干扰。 RSI 部分同样扫 12 根:当 rsi_buffer[i+1] 低于 30 且当前值高于前一根,记 time_low;高于 70 且当前值低于前一根,记 times_high。这两个条件把超买卖区里的动量拐点也纳入锚位校验。 后面的 if 判断用 total_bar_high / total_bar_low 控制失效:若统计 BAR 数大于 12 且锚时间不等于 RSI 拐点时间、或大于 13,就把 static 变量清零,相当于撤掉旧趋势线。最后 ObjectCreate 画一条从 min_time1_static 到当前的绿色 OBJ_TREND,宽度 3,肉眼能直接看到阻力延长线。 开 MT5 把这段塞进 EA 的 OnCalculate,调一下 12 这个窗口和 RSI 的 30/70 阈值,黄金 15M 上大概率能画出比裸眼更准的短期通道。外汇和贵金属杠杆高,锚位被扫后 static 清零机制只是降低假信号,不等于不会连续止损。

MQL5 / C++
for(class="type">int i = class="num">0; i < class="num">12; i++)
  {
    if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1])
      {
       max_high = (class="type">class="kw">double)MathMax(high[i],high[i+class="num">1]);
       min_time1 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]);
       class="kw">break;
       }
   }
 class="type">int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent());
 for(class="type">int i = class="num">0; i < class="num">12; i++)
   {
     if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1])
       {
        min_low = (class="type">class="kw">double)MathMin(low[i],low[i+class="num">1]);
        min_time2 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]);
        class="kw">break;
       }
   }
 for(class="type">int i = class="num">0; i < class="num">12; i++)
   {
     if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1])
       {
        time_low = time[i+class="num">1];
        class="kw">break;
       }
   }
 for(class="type">int i = class="num">0; i < class="num">12; i++)
   {
     if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1])
       {
        times_high = time[i+class="num">1];
        class="kw">break;
       }
   }
 if((total_bar_high == class="num">0 || total_bar_high > class="num">12) && (min_time1 == times_high))
   {
     max_high_static = max_high;
     min_time1_static = min_time1;
   }
 else
    if(min_time1 != times_high && total_bar_high > class="num">13)
      {
       max_high_static = class="num">0;
       min_time1_static = class="num">0;
      }
 if((total_bar_low == class="num">0 || total_bar_low > class="num">12) && (min_time2 == time_low))
   {
     min_low_static = min_low;
     min_time2_static = min_time2;
   }
 else
    if(min_time2 != time_low && total_bar_low > class="num">13)
      {
       min_low_static = class="num">0;
       min_time2_static = class="num">0;
      }
 ObjectCreate(ChartID(),high_obj_name,OBJ_TREND,class="num">0,min_time1_static,max_high_static,TimeCurrent(),max_high_static);
 ObjectSetInteger(chart_id,high_obj_name,OBJPROP_COLOR,clrGreen);
 ObjectSetInteger(chart_id,high_obj_name,OBJPROP_WIDTH,class="num">3);

「用价格线把止损拖成保本」

先画一条红色趋势线把阶段低点钉死:ObjectCreate 用 ChartID 建 OBJ_TREND,起点取 min_time2_static 与 min_low_static,终点拉到 TimeCurrent() 且价格仍锁在 min_low_static,宽度设 3 方便肉眼盯。这条线不是装饰,是后面改仓的参照边界。 allow_modify 开关打开后,脚本会扫一遍 PositionsTotal() 下的所有持仓。通过 PositionGetTicket 拿 ticket 再 PositionSelectByTicket 选中,把开仓价、TP、SL、浮盈全部读进变量;只处理 MagicNumber 与 ChartSymbol 都匹配的单子,避免动到手动单或其他 EA 的仓位。

卖单的逻辑是:modifyLevel =(SL-开仓价)-开仓价归一化到 4 位,当 ask_price <= modifyLevel 时,把止损直接推到 positionopen 实现保本,TP 不变。买单对称处理,modifyLevel =(开仓价-SL)+开仓价,ask_price >= 该值才改。外汇与贵金属杠杆高,这种自动保本在跳空时可能不成交,实盘前请在 MT5 策略测试器用历史数据跑一遍确认触发频率。

下方 CalculateLotSize 的开头只取到 SYMBOL_POINT,完整手数公式要看后续片段;但已知 point 是算每 pip 价值的基础,stopLossPips 传进来后大概率会拿 point * stopLossPips 去反推 lot。先把这个 point 读取留好,接手写不会断。

MQL5 / C++
ObjectCreate(ChartID(),low_obj_name,OBJ_TREND,class="num">0,min_time2_static,min_low_static,TimeCurrent(),min_low_static);
ObjectSetInteger(chart_id,low_obj_name,OBJPROP_COLOR,clrRed);
ObjectSetInteger(chart_id,low_obj_name,OBJPROP_WIDTH,class="num">3);
if(allow_modify)
  {
    for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
      {
       class="type">ulong ticket = PositionGetTicket(i);
       if(PositionSelectByTicket(ticket))
         {
          positionopen = PositionGetDouble(POSITION_PRICE_OPEN);
          positionTP = PositionGetDouble(POSITION_TP);
          positionSL = PositionGetDouble(POSITION_SL);
          positionProfit = PositionGetDouble(POSITION_PROFIT);
          if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id))
            {
             modifyLevel = MathAbs(NormalizeDouble((positionSL - positionopen) - positionopen,class="num">4));
             if(ask_price <= modifyLevel)
               {
                trade.PositionModify(ticket, positionopen, positionTP);
               }
            }
          if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id))
            {
             modifyLevel = MathAbs(NormalizeDouble((positionopen - positionSL) + positionopen,class="num">4));
             if(ask_price >= modifyLevel)
               {
                trade.PositionModify(ticket, positionopen, positionTP);
               }
            }
         }
      }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to calculate the lot size based on risk amount and stop loss
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CalculateLotSize(class="type">class="kw">string symbol, class="type">class="kw">double riskAmount, class="type">class="kw">double stopLossPips)
  {
class=class="str">"cmt">// Get symbol information
   class="type">class="kw">double point = SymbolInfoDouble(symbol, SYMBOL_POINT);

按风险敞口反推开仓手数

在 MT5 里做仓位管理,核心不是拍脑袋定手数,而是先锁定这笔单子最多亏多少美元,再反推能开多少 lot。下面这段逻辑把账户余额、风险百分比和止损距离串起来,直接算出合规手数。 double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); // 取当前品种每 tick 对应的账户币种价值 double pipValuePerLot = tickValue / point; // 用 tick 价值除以 point 得到每 lot 每最小价位的价值 double stopLossValue = stopLossPips * pipValuePerLot; // 止损点数乘每 lot 价值 = 单 lot 止损金额 double lotSize = riskAmount / stopLossValue; // 用总风险金额除以单 lot 止损,得理论手数 double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); // 取经纪商规定的手数步长 double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); // 读取最小最大允许手数 lotSize = MathFloor(lotSize / lotStep) * lotStep; // 向下取整到步长倍数,避免报手数非法 if(lotSize < minLot) lotSize = minLot; if(lotSize > maxLot) lotSize = maxLot; // 越界则夹到边界 实盘里若账户 1000 美元、单笔风险 2%,理论风险金额就是 20 美元;若止损 50 点、EURUSD 每 lot 每点约 10 美元,则算出来 lotSize 倾向落在 0.04 附近,再被步长夹到 0.04。外汇和贵金属杠杆高,滑点可能让实际亏损偏离计算值,开仓前最好在策略测试器跑一遍。 输入参数这块也直接决定了风险骨架:account_balance=1000、percentage_risk=2.0、rrr=3 意味着盈亏比锁 3 倍,take_profit 用 (close[0]-low[0])*3+close[0] 推出来。allow_modify 打开后,脚本会扫持仓把空单止损搬到开仓价做保本,但仅当 ask 触到 modifyLevel 才改。

MQL5 / C++
class="type">class="kw">double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
class=class="str">"cmt">// Calculate pip value per lot
class="type">class="kw">double pipValuePerLot = tickValue / point;
class=class="str">"cmt">// Calculate the stop loss value in currency
class="type">class="kw">double stopLossValue = stopLossPips * pipValuePerLot;
class=class="str">"cmt">// Calculate the lot size
class="type">class="kw">double lotSize = riskAmount / stopLossValue;
class=class="str">"cmt">// Round the lot size to the nearest acceptable lot step
class="type">class="kw">double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
lotSize = MathFloor(lotSize / lotStep) * lotStep;
class=class="str">"cmt">// Ensure the lot size is within the allowed range
class="type">class="kw">double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
class="type">class="kw">double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
if(lotSize < minLot)
    lotSize = minLot;
if(lotSize > maxLot)
    lotSize = maxLot;
class="kw">return lotSize;
}
input class="type">class="kw">double account_balance   = class="num">1000;   class=class="str">"cmt">// Account Balance
input class="type">class="kw">double percentage_risk   = class="num">2.0;    class=class="str">"cmt">// How many percent of the account do you want to risk per trade?
input class="type">bool   allow_modify      = false; class=class="str">"cmt">// Do you allow class="kw">break even modifications?
input class="type">int    rrr               = class="num">3;      class=class="str">"cmt">// Choose Risk Reward Ratio
take_profit = (close[class="num">0] - low[class="num">0]) * rrr + close[class="num">0];
points_risk = close[class="num">0] - low[class="num">0];
class="type">class="kw">double riskAmount = account_balance * (percentage_risk / class="num">100.0);
class="type">class="kw">double minus = NormalizeDouble(close[class="num">0] - low[class="num">0],class="num">5);
lot_size = CalculateLotSize(_Symbol, riskAmount, minus);
trade.Buy(lot_size,_Symbol,ask_price, low[class="num">0], take_profit);
if(allow_modify)
  {
   for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
     {
      class="type">ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
        {
         positionopen = PositionGetDouble(POSITION_PRICE_OPEN);
         positionTP = PositionGetDouble(POSITION_TP);
         positionSL = PositionGetDouble(POSITION_SL);
         positionProfit = PositionGetDouble(POSITION_PROFIT);
         if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id))
           {
            modifyLevel = MathAbs(NormalizeDouble((positionSL - positionopen) - positionopen,class="num">4));
            if(ask_price <= modifyLevel)
              {
               trade.PositionModify(ticket, positionopen, positionTP);
              }
           }
         }
      }
  }

◍ 多单保本移损的触发写法

在 MT5 的持仓遍历逻辑里,想只对特定 EA 开的某品种多单做保本处理,得先三重过滤:持仓类型、魔术码、图表品种三者同时命中才动手。上面这段就是典型判断,少一个条件都可能误改别的策略仓位。 核心计算行把「开仓价与止损的距离」叠回开仓价,得到一条保本线 modifyLevel,并用 NormalizeDouble 锁到 4 位小数。当实时 ask 大于等于这条线,就调用 PositionModify 把止损推到开仓价、止盈不变。 直接把 MagicNumber 和 chart_id 换成你实盘的值,开 MT5 策略测试器用 2023 年 XAUUSD 的 M15 跑一遍,能看见浮亏单在价格回踩开仓价时被锁零风险,但碰上点差突扩至 3~5 点,modifyLevel 触发瞬间可能滑到微亏。外汇与贵金属杠杆高,移损不代表无风险。

MQL5 / C++
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == ChartSymbol(chart_id))
  {
  modifyLevel = MathAbs(NormalizeDouble((positionopen - positionSL) + positionopen,class="num">4));
  if(ask_price >= modifyLevel)
    {
    trade.PositionModify(ticket, positionopen, positionTP);
    }
  }

「EA 落地的几个硬约束」

把 RSI 超买超卖信号接进 EA 时,别只盯入场。用风险回报率把利润目标和计算风险对齐,每笔单固定 % 风险,才能扛住不同烛形大小带来的仓位波动。 流动性清扫过滤加盈亏平衡移动,是这套 EA 能适应突变的关键。前者剔除假突破,后者在行情朝有利方向走一段后锁住成本,让系统不至于被瞬时反转洗掉利润。 外汇和贵金属杠杆高、跳空频繁,哪怕逻辑完整,实盘前也该用策略测试器跑历史数据确认参数稳定性。打开 MT5 把附带的 mq5 加载进编辑器,改 RSI 周期或 RRR 数值,自己跑一遍比看结论有用。

把指标诊断交给小布盯盘
这些 RSI 高低点标记与每笔百分比风险的计算逻辑,小布盯盘的 AIGC 已内置到对应品种页,打开就能看到实时扫流动性位的辅助视图,你只管定策略参数。

常见问题

常见是参数类型传错或指标尚未在终端可用,建议在 OnInit 里检查返回值是否为 INVALID_HANDLE,再决定能否进入交易逻辑。
MQL5 缓冲区 0 号通常是未闭合的实时烛形,用 shift=1 取已闭合柱的值可以避免信号抖动,回测也更接近实盘。
目前小布盯盘侧重盘面标注与风险视图,不直接吐 MQL5 源码,但你能照它的高低压位提示反推 EA 的入场条件。
先量出信号烛形的真实波幅,再按账户净值反推手数,使单笔最大亏损锁定在预设百分比内,避免大棒线扫损过多。
用 OBJ_ARROW 或 OBJ_TEXT 绑定到对应时间的烛形坐标,循环检测 RSI 极值并标注,方便肉眼复核策略触发点。