同时双向工作的通用 RSI 指标(基础篇)
📘

同时双向工作的通用 RSI 指标(基础篇)

第 1/3 篇

「让 RSI 在双向行情里同时发声」

标准 RSI 只盯着收盘价单向变化,遇到震荡或反向重叠时容易失语。Anatoli Kazharski 在 2018-09-25 发布的通用版本改了内部计算逻辑,能同时处理多空两条线,在同一根 K 线上给出双向强度。 该版本在 MQL5 社区发布后累计查看 8078 次、评论 14 条,说明实战派对「同根 K 线看双向」确有需求。打开 MT5 加载后,你会看到传统 RSI 窗口之外多出一条反向线,可用于识别隐性背离。 外汇与贵金属杠杆高、滑点突兀,双向 RSI 只降低盲区,不消除误判概率;用它做确认而非触发,更稳妥。

先看清这篇要改什么

这篇实操不是从零写 RSI,而是拿 MT5 自带的 RSI 指标做五轮增量改造,逐步把它从副图振荡器变成主图可直接看信号的工具。原文给出的改造路线分得很碎:先加信号缓冲区,再加信号计数器,然后扩信号数并消跳空,再把指标挪到主图,最后做成双向通用的 RSI。 顺着这个目录你能直接对照后续每一版的代码 diff:第一版只动缓冲区定义,第二版多一个计数缓冲,第三版开始处理 K 线跳空造成的假信号。外汇和贵金属波动大、跳空频繁,这类改写对实盘过滤噪音有现实意义,但任何信号都只是概率倾向,不构成方向保证。 开 MT5 后建议先把标准 RSI 拖出来存一份副本,再按目录顺序逐版改,避免一次改太多无法定位报错。

◍ 把趋势和盘整策略塞进同一个EA

写交易算法绕不开一个老问题:趋势从哪根K线起、盘整在哪根K线终,边界永远模糊。硬找一个完美切点不现实,更直接的做法是把趋势型与盘整型逻辑并存在一个算法里,让信号生成层尽量薄。 本文的思路是做一个通用指标,把两类策略信号合并输出。假定双向持仓在震荡与单边切换时更稳,那么EA里只挂这一个复合指标比挂一堆独立指标省事。内部可以用同指标多周期参数叠加,也可以主指标加过滤指标双层的办法。 用OOP最顺手:每个指标或它的子模块写成类,直接塞进主文件。下面框架里 Indicator.mq5 同时含 CIndicator1 与 CIndicator2,后者是前者的计算依赖。为避免在非当前时段残留柱上重复算,引入了之前多品种指标文章里的 CFirstTrueBar 类,所有指标都继承它来锁定真实柱。

「拿振荡器打底,RSI只是个例子」

标准终端分发包里的指标都能拿来生成信号,彼此间没有谁天然优于谁。某组指标加过滤器在一段行情好用,换段时间可能另一组更顺,但做策略研究最好先盯振荡器——有趋势和盘整时它都能给信号,数据还能顺手画价格通道,等于攒出一个通用底子,后面堆任何复杂度的逻辑都方便。 本篇拿 RSI 当样例,图里是周期 8 的 AUDUSD H1 计算结果。乍看靠它赚钱很容易,其实是错觉:摸到下一级理解前得干大量脏活,且仍不敢保证达标。 最直白的想法是把 70/30 默认水平当边界——下穿 70 做卖、上穿 30 做买。实盘里这种信号一堆假动作,价格常反着走。图 3 里 AUDUSD 跌了很久,红线是 RSI 上穿 30 的买点,算法照做就是浮动回撤;每次带止损就会被洗掉几笔。从最后一次买信号到绿线卖信号出来前,价格始终没给正结果,账户是亏的。这段走势本身像趋势跟随,看不出不确定因子藏在哪。 这类坑在任何指标上都绕不开,所以后面选哪个指标本身不重要,关键是往里加功能让它好嵌进 EA。我们做了 5 个 RSI 版本,从最简到最复杂排好序,方便一层层看明白。

第一版. 加上信号缓冲区

标准版本的 RSI 位于终端的 \MQL5\Indicators\Examples 目录下,让我们把它复制一份,再开始修改它。我们在固定的参数列表上加上了另外两个指标缓冲区, 它们的总数将也等于 5 , 和 3 ,将显示在图表上 。两个缓冲区将为辅助计算保留,买入信号的标签将使用绿色 ( clrMediumSeaGreen ), 而卖出信号使用红色 ( clrRed )。 定义用于信号标签的代码。如果需要以点显示,代码为 159 ,如果信号使用箭头来显示,使用的代码分别是 233 和 234 。 指标线与边界的交叉也可以既作为买入也作为卖出信号,所以,外部参数将需要一个枚举,它可以用来指定如何解释指标信号。 Break in — 突破进入边界的范围。向上突破底部边界是一个买入信号,而向下突破顶部边界是一个卖出信号。 Break in reverse — 突破进入边界的范围 (与波动方向相反). 与 'Break in' 模式相同的信号,只是买入和卖出的条件相反。 Break out — 突破出边界的范围。向上突破顶部边界是买入信号,而向下突破底部边界是卖出信号。 Break out reverse — 突破出边界的范围 (与波动方向相反). 与 'Break out' 模式相同的信号, 只是买入和卖出的条件相反. 所有这些模式将显示在下面的图表中。 指标将一共有三个外部参数: RSI Period — 指标周期数; Signal Level — 指标水平线; Break Mode — 水平突破模式. 指标的属性是在 SetPropertiesIndicator () 函数中设置的,最后会设置辅助数组,所有的指标数组在 ZeroIndicatorBuffers () 函数中使用0来做初始化,然后, 我们指定0值不应当显示在图表上 , 意思是这样的数值是空的。 为方便起见, RSI 指标值的预先计算和主体计算部分移动到了独立的函数 PreliminaryCalculations () 和 CalculateRSI () 中,它们的内容和标准分发包中的 RSI 指标是一样的让我们只探讨用于确定指标信号的函数 — CalculateSignals (),在这里,首先会根据外部参数中设置的模式来检查条件,然后,如果条件满足,就把 RSI 指标值保存到对应的指标数组中。如果条件不满足,就保存0值,它们将不会显示在图表中。 这样,主指标函数的代码,例如 OnInit() 和 OnCalculate() , 就变得清晰易读了: 把指标附加到图表上,看看会发生什么。下面是四种运行模式 (外部参数 Break Mode ) 的结果. 图 4. 演示修改版 RSI 指标的运行。 在这样修改了 RSI 指标之后,它所生成的错误信号的数量看起来好转了。分析了图形之后,我们可以下结论,这个指标有的时候在盘整的时候效果更好,有的时候在有趋势的时候更好。 Break in 模式是设计用于在盘整时候在 RSI 的方向上进行交易。 Break in reverse 模式是设计用于在跟随趋势交易时与 RSI 相反的方向上进行交易。 Break out 模式是设计用于在跟随趋势交易时与 RSI 相同的方向上进行交易。 Break out reverse 模式是设计用于在盘整时在与 RSI 相反的方向上进行交易。 这个信号能够只在盘整时或者只在有趋势的时候进行交易吗?如何才能提高获利的概率和入场的准确性呢?很有可能如果仓位不是根据连续序列中的第一个信号所开启,则可能会提升结果。 [COD

◍ 给 RSI 箭头指标收尾初始化

这段初始化代码把指标的外观和信号触发逻辑钉死,开 MT5 新建自定义指标时可直接照抄结构。先给第 4 号缓冲 NegBuffer 挂上计算用途,再用 ZeroIndicatorBuffers() 把全部数组清零,避免上一根 K 线的残留值污染当前信号。 文字标签和线型分开循环设置:plot_label 数组里 "RSI" 走普通线,"buy"/"sell" 走箭头;宽度统一压到 1,太粗会在小周期图表上糊成一片。箭头代码写死 ARROW_BUY 和 ARROW_SELL,对应缓冲索引 1 和 2。 水平线只设两条,值由 SignalLevel 反推:down_level = SignalLevel,up_level = 100 - SignalLevel,线型用 STYLE_DOT 点线。若 SignalLevel 取 30,则超卖线在 30、超买线在 70,这是 RSI 经典阈值。 空值统一设 0,表示不画的地方直接留白。Y 轴偏移按 BreakMode 分支:反转模式下买箭头向下挪 arrow_shift、卖箭头向上挪,非反转则反过来,防止箭头压住 K 线实体。 CalculateSignals 里 condition1 的判断很直白——前一根 RSI 低于 down_level 且当前根上穿,即为突破进入超卖通道的买点雏形。外汇与贵金属波动剧烈,这种穿越信号在震荡市可能频繁假突破,实盘前务必用策略测试器跑至少 3 个月 tick 数据验证。

MQL5 / C++
  ::SetIndexBuffer(class="num">4,neg_buffer,INDICATOR_CALCULATIONS);
class=class="str">"cmt">//--- 初始化数组
  ZeroIndicatorBuffers();
class=class="str">"cmt">//--- 设置文字标签
  class="type">class="kw">string plot_label[]={"RSI","buy","sell"};
  for(class="type">int i=class="num">0; i<indicator_plots; i++)
      ::PlotIndexSetString(i,PLOT_LABEL,plot_label[i]);
class=class="str">"cmt">//--- 设置指标数组的宽度
  for(class="type">int i=class="num">0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_LINE_WIDTH,class="num">1);
class=class="str">"cmt">//--- 设置指标数组的类型
  ENUM_DRAW_TYPE draw_type[]={DRAW_LINE,DRAW_ARROW,DRAW_ARROW};
  for(class="type">int i=class="num">0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_DRAW_TYPE,draw_type[i]);
class=class="str">"cmt">//--- 标签代码
  ::PlotIndexSetInteger(class="num">1,PLOT_ARROW,ARROW_BUY);
  ::PlotIndexSetInteger(class="num">2,PLOT_ARROW,ARROW_SELL);
class=class="str">"cmt">//--- 开始计算的起始元件的索引
  for(class="type">int i=class="num">0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_DRAW_BEGIN,period_rsi);
class=class="str">"cmt">//--- 指标水平线的数量
  ::IndicatorSetInteger(INDICATOR_LEVELS,class="num">2);
class=class="str">"cmt">//--- 指标水平线的值
  up_level   =class="num">100-SignalLevel;
  down_level =SignalLevel;
  ::IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">0,down_level);
  ::IndicatorSetDouble(INDICATOR_LEVELVALUE,class="num">1,up_level);
class=class="str">"cmt">//--- 线型
  ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,class="num">0,STYLE_DOT);
  ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,class="num">1,STYLE_DOT);
class=class="str">"cmt">//--- 表示不画任何内容的空值
  for(class="type">int i=class="num">0; i<indicator_buffers; i++)
      ::PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,class="num">0);
class=class="str">"cmt">//--- 在 Y 轴上的偏移
  if(BreakMode==BREAK_IN_REVERSE || BreakMode==BREAK_OUT_REVERSE)
   {
     ::PlotIndexSetInteger(class="num">0,PLOT_ARROW_SHIFT,arrow_shift);
     ::PlotIndexSetInteger(class="num">1,PLOT_ARROW_SHIFT,-arrow_shift);
   }
  else
   {
     ::PlotIndexSetInteger(class="num">0,PLOT_ARROW_SHIFT,-arrow_shift);
     ::PlotIndexSetInteger(class="num">1,PLOT_ARROW_SHIFT,arrow_shift);
   }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 计算指标信号                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CalculateSignals(class="kw">const class="type">int i)
  {
  class="type">bool condition1 =class="kw">false;
  class="type">bool condition2 =class="kw">false;
class=class="str">"cmt">//--- 突破进入通道
  if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE)
   {
     condition1 =rsi_buffer[i-class="num">1]<down_level && rsi_buffer[i]>down_level;

常见问题

把趋势过滤和盘整阈值分开写进同一套逻辑,RSI只作振荡底,双向条件各自触发缓冲区,就能同屏显示两类信号。
先加信号缓冲区并初始化,再补箭头绘制;不清缓冲区会导致旧信号残留,回测和盯盘都会误判。
小布可加载该品种页直接标出双向RSI信号区,省去手动改代码,你只管确认入场。
多半是SetIndexBuffer未绑定或OnCalculate里没写绘制分支;检查缓冲区索引与箭头code赋值即可。
CCI、随机指标同理,只要留好双缓冲区并把策略判断拆成趋势/盘整两块就能复用框架。