动量弹球交易策略(基础篇)
📘

动量弹球交易策略(基础篇)

第 1/3 篇

动量弹球策略的出处与样本量

动量弹球(Momentum Pinball)是一套在 MetaTrader 5 上跑的示例策略,作者 Alexander Puzanov 于 2018 年 1 月 15 日 08:40 发布。 该帖在源平台累计获得 9 981 次查看、5 条评论,说明它作为 MT5 内置示例有一定的受众基础,但并非经过大规模实盘验证的成熟系统。 外汇与贵金属交易本身高风险,这类示例策略仅适合在 MT5 策略测试器中用历史数据复现,不建议直接上真实账户。

◍ 动量弹球策略要怎么用结构重写

这一节先把要落地的东西说清楚:照着 L. Raschke 和 L. Connors 在《华尔街智慧》里写的动量弹球(Momentum Pinball)思路,用 MQL5 把价格范围边界的测试策略写成代码。它的形态只跨两根日线柱——第一根定第二天方向,第二根开盘后的价变直接给出进场和退场价位,逻辑不复杂但很吃执行精度。 面向已经会 MQL5 的人,这一版刻意不用类,全用 struct 代替。对从过程化编程转过来的人,结构和类的差别最小,又能覆盖信号计算这种轻量任务,没必要上完整 OOP。 落地路径分三步:先写信号模块,再写调用它的人工标记指标,最后是挂这个模块自动跑的 EA。作者原书用的是 20 年前报价,我们会在最新 tick 上重测一遍,看信号在当下外汇与贵金属行情里还灵不灵——这类品种杠杆高、滑点跳空频繁,历史胜率不能直接当现在概率。

「用 LBR/RSI 和 ROC 框定次日多空」

Raschke 与 Connors 发现 Taylor 的日内方向预判法在实际交易中常被自己打破,导致规则失去一致性。他们改用 ROC(变化率)作为方向过滤器,再把 RSI 套在 ROC 数值上,由此得到 LBR/RSI 指标,用来在超买超卖边界内识别更适合的买入日与卖出日。 买入侧的核心条件很硬:D1 周期里,LBR/RSI 在最近一根已收盘日线必须低于 30(超卖区)。新交易日第一根 H1 收盘后,在高于该小时线最高价处挂 buy stop;单子触发后,止损放在这根 H1 的最低价。若被打损,就在同一价位重新挂买。 若持仓到当日结束仍盈利,则留仓到次交易日,且必须在第二天平仓,不可再延。实测中 2017 年 10 月 30 日出现过 D1 上 LBR/RSI 落入超卖、随后第一小时范围给出挂单水平的典型形态,可在 MT5 历史数据里复核对齐。 出场原文未写死,作者仅提到跟踪止损、次晨平仓、或突破首交易日最高价时离场三种倾向。卖出规则对称:LBR/RSI 需高于 70,挂单置于首小时线最低价下方。外汇与贵金属杠杆高,信号失效时回撤可能快速扩大,仅作概率参考。

把 ROC 和 RSI 揉进一个独立指标

人工盯盘时,若信号模块只吐数值不画区域,超买超卖形态全靠脑补。单独挂一个 LBR/RSI 指标,用颜色块把 70 以上、30 以下区域填出来,肉眼扫一下就知道价格处在哪一侧。它不在每根分时上反复算,只在日柱收盘后取数,资源占用低,对 MT5 终端几乎无负担。 这个指标把 ROC(变动率)和 RSI 算法合并:先按 Linda Raschke 的改法算 ROC——不是相邻柱比价,而是当前柱和往前数第 3 个交易日的柱比价,再用这个结果跑标准 RSI。显示上用了 9 个缓冲区,其中 5 个画图、4 个做辅助计算,超买填粉红、超卖填浅蓝。 原交易系统没给的参数这里都开放了:RSI 周期默认 3,边界 70/30,价格源可从 ENUM_APPLIED_PRICE 的 7 种里挑,默认用收盘价,也可换典型价或加权价。文件必须命名 LBR_RSI.mq5 丢进标准指标目录,因为信号模块里 iCustom 调用的就是这名,改了就接不上。 下面这段是缓冲区与输入的声明骨架,逐行看:#property 那几行定义独立窗口、9 缓冲、3 绘图(两填充一曲线),坐标锁 0–100;input 块暴露价格类型、RSI 周期、超买超卖阈值;double 数组里 buff_Overbought_High/Low 管超买背景,buff_Oversold_High/Low 管超卖背景,buff_ROC 存变动率,buff_RSI 存最终曲线,buff_Positive/Negative 供 RSI 递推。OnInit 里 SetIndexBuffer 把 0 号缓冲绑给超买上沿。

MQL5 / C++
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers  class="num">9
class="macro">#class="kw">property indicator_plots    class="num">3
class="macro">#class="kw">property indicator_label1  “超买区域"
class="macro">#class="kw">property indicator_type1   DRAW_FILLING
class="macro">#class="kw">property indicator_color1  C&class="macro">#x27;class="num">255,class="num">208,class="num">234&class="macro">#x27;
class="macro">#class="kw">property indicator_width1  class="num">1
class="macro">#class="kw">property indicator_label2  “超卖区域"
class="macro">#class="kw">property indicator_type2   DRAW_FILLING
class="macro">#class="kw">property indicator_color2  C&class="macro">#x27;class="num">179,class="num">217,class="num">255&class="macro">#x27;
class="macro">#class="kw">property indicator_width2  class="num">1
class="macro">#class="kw">property indicator_label3  "RSI от ROC"
class="macro">#class="kw">property indicator_type3   DRAW_LINE
class="macro">#class="kw">property indicator_style3  STYLE_SOLID
class="macro">#class="kw">property indicator_color3  clrTeal
class="macro">#class="kw">property indicator_width3  class="num">2
class="macro">#class="kw">property indicator_minimum class="num">0
class="macro">#class="kw">property indicator_maximum class="num">100
class="kw">input ENUM_APPLIED_PRICE   TS_MomPin_Applied_Price = PRICE_CLOSE;   class=class="str">"cmt">// 用于 ROC 计算的价格
class="kw">input class="type">uint    TS_MomPin_RSI_Period = class="num">3;                        class=class="str">"cmt">// RSI 周期数
class="kw">input class="type">class="kw">double  TS_MomPin_RSI_Overbought = class="num">70;                   class=class="str">"cmt">// RSI 超卖水平
class="kw">input class="type">class="kw">double  TS_MomPin_RSI_Oversold = class="num">30;                     class=class="str">"cmt">// RSI 超买水平
class="type">class="kw">double
  buff_Overbought_High[], buff_Overbought_Low[],    class=class="str">"cmt">// 超买区域背景
  buff_Oversold_High[], buff_Oversold_Low[],        class=class="str">"cmt">// 超卖区域背景
  buff_Price[],                                    class=class="str">"cmt">// 计算价格的数组
  buff_ROC[],                                      class=class="str">"cmt">// 来自计算价格的 ROC 数组
  buff_RSI[],                                      class=class="str">"cmt">// ROC 的 RSI
  buff_Positive[], buff_Negative[]                 class=class="str">"cmt">// RSI 计算的辅助数组
;
class="type">int OnInit() {
  class=class="str">"cmt">// 指标缓冲区设计:
  class=class="str">"cmt">// 超买区域
  SetIndexBuffer(class="num">0, buff_Overbought_High, INDICATOR_DATA);

◍ 缓冲区绑定与价格源切换的实现细节

指标初始化阶段把 9 个缓冲区依次挂到对应插槽:0~3 号存超买/超卖区域的上下边界,4 号存 RSI 主线,5~8 号是计算用的中间量(价格、ROC、正负变化)。其中超买与超卖区域缓冲都设了 PLOT_SHOW_DATA=false,意味着它们只参与填充色块、不单独在数据窗口露脸。 PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE) 这类调用把空值定义为 EMPTY_VALUE,避免图表上画出 0 轴误判。IndicatorSetInteger(INDICATOR_DIGITS, 2) 锁定两位小数显示,短名直接写成 "LBR/RSI",在 MT5 导航树里一眼能认。 计算循环从第二根柱(i_Period_Bar=1)起步,用 switch 按 TS_MomPin_Applied_Price 把收盘价、开盘价、最高价、最低价或几种加权价写进 buff_Price。中位数价乘 0.5、典型价乘 0.33333、加权价乘 0.25,这些系数是写死在代码里的,改价格源不用动循环逻辑,只切 case 即可。 在 MT5 里把这段粘进 OnInit 和 OnCalculate 头部,先确认导航栏出现 "LBR/RSI" 且子窗口无多余线型,再调 TS_MomPin_Applied_Price 从 PRICE_CLOSE 切到 PRICE_TYPICAL,观察 buff_Price 数值是否按 0.33333 倍 High+Low+Open 变化。外汇与贵金属波动剧烈,这类自定义指标仅作概率参考,实盘前务必用历史数据验证。

MQL5 / C++
PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetInteger(class="num">0, PLOT_SHOW_DATA, false);
SetIndexBuffer(class="num">1, buff_Overbought_Low, INDICATOR_DATA);

class=class="str">"cmt">// 超卖区域
SetIndexBuffer(class="num">2, buff_Oversold_High, INDICATOR_DATA);
PlotIndexSetDouble(class="num">1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetInteger(class="num">1, PLOT_SHOW_DATA, false);
SetIndexBuffer(class="num">3, buff_Oversold_Low, INDICATOR_DATA);

class=class="str">"cmt">// RSI 曲线
SetIndexBuffer(class="num">4, buff_RSI, INDICATOR_DATA);
PlotIndexSetDouble(class="num">2, PLOT_EMPTY_VALUE, EMPTY_VALUE);

class=class="str">"cmt">// RSI 计算使用的缓冲区
SetIndexBuffer(class="num">5, buff_Price, INDICATOR_CALCULATIONS);
SetIndexBuffer(class="num">6, buff_ROC, INDICATOR_CALCULATIONS);
SetIndexBuffer(class="num">7, buff_Negative, INDICATOR_CALCULATIONS);
SetIndexBuffer(class="num">8, buff_Positive, INDICATOR_CALCULATIONS);

IndicatorSetInteger(INDICATOR_DIGITS, class="num">2);
IndicatorSetString(INDICATOR_SHORTNAME, "LBR/RSI");

class="kw">return(INIT_SUCCEEDED);
}
class="type">int
  i_RSI_Period = class="type">int(TS_MomPin_RSI_Period),        class=class="str">"cmt">// 把 RSI 周期数转换为整数类型
  i_Bar, i_Period_Bar                              class=class="str">"cmt">// 用于同时访问的两个柱的索引
;
class="type">class="kw">double
  d_Sum_Negative, d_Sum_Positive,                 class=class="str">"cmt">// 用于 RSI 计算的辅助变量
  d_Change                                         class=class="str">"cmt">// 用于 ROC 计算的辅助变量
;
class=class="str">"cmt">// Fill in ROC buffer and fill areas:
i_Period_Bar = class="num">1;
class="kw">while(++i_Period_Bar < rates_total && !IsStopped()) {
class=class="str">"cmt">// 计算的柱的价格:
  class="kw">switch(TS_MomPin_Applied_Price) {
    case PRICE_CLOSE:     buff_Price[i_Period_Bar] = Close[i_Period_Bar]; class="kw">break;
    case PRICE_OPEN:      buff_Price[i_Period_Bar] = Open[i_Period_Bar]; class="kw">break;
    case PRICE_HIGH:      buff_Price[i_Period_Bar] = High[i_Period_Bar]; class="kw">break;
    case PRICE_LOW:       buff_Price[i_Period_Bar] = Low[i_Period_Bar]; class="kw">break;
    case PRICE_MEDIAN:    buff_Price[i_Period_Bar] = class="num">0.50000 * (High[i_Period_Bar] + Low[i_Period_Bar]); class="kw">break;
    case PRICE_TYPICAL:   buff_Price[i_Period_Bar] = class="num">0.33333 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar]); class="kw">break;
    case PRICE_WEIGHTED:  buff_Price[i_Period_Bar] = class="num">0.25000 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar] + Open[i_Period_Bar]); class="kw">break;
  }

「RSI 平滑递推的尾段实现」

这段逻辑承接前面 ROC 的计算,把动量变化转成类 RSI 的摆动值。初始化阶段只跑一次:当已计算柱数不足 RSI 周期时,用 while 循环把前 i_RSI_Period 根 BAR 的 d_Change 正负分别累加,再除以周期得到首值 buff_Positive / buff_Negative。 递推部分从 i_RSI_Period+1 开始,每根新 BAR 用「前一值×(周期-1)+当前增量」除以周期做滑动平均。若负向和为 0 且正向和非 0,RSI 直接给 100;两者皆 0 则给 50,这是边界保护。 外汇与贵金属杠杆高,这类指标仅描述动量饱和概率,金叉死叉都可能假突破,实盘前请在 MT5 用不同 i_RSI_Period(如 14 与 21)对比缓冲区的尖刺密度。

MQL5 / C++
  class=class="str">"cmt">// 计算价格的差 (ROC 数值):
  if(i_Period_Bar > class="num">1) buff_ROC[i_Period_Bar] = buff_Price[i_Period_Bar] - buff_Price[i_Period_Bar - class="num">2];
  
  class=class="str">"cmt">// 填充背景:
  buff_Overbought_High[i_Period_Bar] = class="num">100;
  buff_Overbought_Low[i_Period_Bar] = TS_MomPin_RSI_Overbought;
  buff_Oversold_High[i_Period_Bar] = TS_MomPin_RSI_Oversold;
  buff_Oversold_Low[i_Period_Bar] = class="num">0;
}
i_Period_Bar = prev_calculated - class="num">1;
if(i_Period_Bar <= i_RSI_Period) {
  buff_RSI[class="num">0] = buff_Positive[class="num">0] = buff_Negative[class="num">0] = d_Sum_Positive = d_Sum_Negative = class="num">0;
  i_Bar = class="num">0;
  class="kw">while(i_Bar++ < i_RSI_Period) {
    buff_RSI[class="num">0] = buff_Positive[class="num">0] = buff_Negative[class="num">0] = class="num">0;
    d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - class="num">1];
    d_Sum_Positive += (d_Change > class="num">0 ? d_Change : class="num">0);
    d_Sum_Negative += (d_Change < class="num">0 ? -d_Change : class="num">0);
  }
  buff_Positive[i_RSI_Period] = d_Sum_Positive / i_RSI_Period;
  buff_Negative[i_RSI_Period] = d_Sum_Negative / i_RSI_Period;
  
  if(buff_Negative[i_RSI_Period] != class="num">0)
    buff_RSI[i_RSI_Period] = class="num">100 - (class="num">100 / (class="num">1. + buff_Positive[i_RSI_Period] / buff_Negative[i_RSI_Period]));
  else
    buff_RSI[i_RSI_Period] = buff_Positive[i_RSI_Period] != class="num">0 ? class="num">100 : class="num">50;
  
  i_Period_Bar = i_RSI_Period + class="num">1;
}
i_Bar = i_Period_Bar - class="num">1;
class="kw">while(++i_Bar < rates_total && !IsStopped()) {
  d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - class="num">1];
  
  buff_Positive[i_Bar] = (buff_Positive[i_Bar - class="num">1] * (i_RSI_Period - class="num">1) + (d_Change> class="num">0 ? d_Change : class="num">0)) / i_RSI_Period;
  buff_Negative[i_Bar] = (buff_Negative[i_Bar - class="num">1] * (i_RSI_Period - class="num">1) + (d_Change <class="num">0 ? -d_Change : class="num">0)) / i_RSI_Period;
  
  if(buff_Negative[i_Bar] != class="num">0)
    buff_RSI[i_Bar] = class="num">100 - class="num">100. / (class="num">1. + buff_Positive[i_Bar] / buff_Negative[i_Bar]);
  else
    buff_RSI[i_Bar] = buff_Positive[i_Bar] != class="num">0 ? class="num">100 : class="num">50;
}

常见问题

由 Linda Raschke 在《Street Smarts》中系统整理,建议至少用 10 年日线样本验证,样本太小容易过拟合。
用前日高低中点叠次日 ROC 方向即可,不必套用原版指标,直接在 K 线旁标出突破位和动量箭头。
小布可读取品种的 LBR/RSI 与 ROC 状态,自动标注次日多空倾向并推送提醒,你只需确认入场。
必须分别申请独立 buffer 并 SetIndexBuffer 绑定,价格源用 Close 而非 High/Low,避免重绘错位。
尾段用上一根平滑值递推:rsi = (prev*(n-1)+curr)/n,前 n 根用简单平均seed,别从 0 开始循环。