开发轴心均值振荡器:一款新颖的累积移动平均线指标·进阶篇
「PMO 指标的缓冲区与均值支撑函数」
PMO 指标不需要从公式反推输入,只需三个外部参数:起始时间 startingTime 作为计算基准点;MA_close 默认 3,决定收盘 PM 信号均值的样本数;MA_open 默认 21,决定开盘 PM 信号均值的样本数。这三个数直接控制底层信号平滑度,调小则灵敏、调大则滞后。 指标内部开三个缓冲区:PMOBuffer 存最终输出,closeBuffer 与 openBuffer 分别承接收盘、开盘派生的 PM 信号。全局变量里放一个计数器 counter(初值 1)、sum_close 与 sum_open 两个累加器,另加 first_val_checked 和 first_valid_index 用来锁定起始时间之后的首个有效索引。 均值计算函数 simpleMA 要绕开一个坑:在起始时间附近,缓冲区头部没有足够元素。按 PM 定义,越界部分直接补 1,均值就不会凭空滞后。下面这段是核心声明的骨架,含输入、缓冲区和 simpleMA 实现。 外汇与贵金属波动剧烈,PMO 仅作概率参考,实盘前请在 MT5 策略测试器用历史数据验证参数敏感性。
class=class="str">"cmt">//--- class="kw">input parameters class="kw">input class="type">class="kw">datetime startingTime; class="kw">input class="type">int MA_close=class="num">3; class="kw">input class="type">int MA_open=class="num">21; class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double PMOBuffer[]; class="type">class="kw">double closeBuffer[]; class="type">class="kw">double openBuffer[]; class=class="str">"cmt">//----global vars--- class="type">int counter=class="num">1; class="type">class="kw">double sum_close=class="num">0; class="type">class="kw">double sum_open=class="num">0; class="type">bool first_val_checked; class="type">int first_valid_index; class="type">class="kw">double simpleMA(class="kw">const class="type">int pos, class="kw">const class="type">int avg_positions, class="kw">const class="type">class="kw">double &data[],class="type">int arr_tail_pos){ class="type">class="kw">double _local_sum = class="num">0; for(class="type">int i=pos+avg_positions-class="num">1; i >= pos;i++){ if(i > arr_tail_pos){ _local_sum += class="num">1; class=class="str">"cmt">// when requested data exceed buffer limit set trailing 1s }else{ _local_sum += data[i]; } } class="kw">return _local_sum/avg_positions; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">class="kw">datetime &time[], class="kw">const class="type">class="kw">double &open[], class="kw">const class="type">class="kw">double &high[], class="kw">const class="type">class="kw">double &low[], class="kw">const class="type">class="kw">double &close[], class="kw">const class="type">long &tick_volume[], class="kw">const class="type">long &volume[], class="kw">const class="type">int &spread[]) { class=class="str">"cmt">//--- ArraySetAsSeries(PMOBuffer,true); ArraySetAsSeries(closeBuffer,true); ArraySetAsSeries(openBuffer,true); ArraySetAsSeries(open,true); ArraySetAsSeries(high,true); ArraySetAsSeries(time,true); class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="type">int total = rates_total - prev_calculated; class="type">class="kw">double _tmp_sum_close,_tmp_sum_open; if(total > class="num">0){
从起点时间倒推的 PMO 累加逻辑
这段循环是 PMO 指标在 MT5 里落地的核心:从 K 线数组尾部(total-1)往 0 号柱倒推,只处理 time[i] 大于等于 startingTime 的柱。倒序遍历能保证累加和(sum_close / sum_open)按时间先后累积,避免未来函数偏移。 首次命中有效时间阈值的柱会被标记为 first_valid_index,后续 simpleMA 调用都以它为基准起点。counter 从 0 开始自增,closeBuffer[i] 与 openBuffer[i] 分别用 close[i]*counter/sum_close 与 open[i]*counter/sum_open 归一,再相减得到 PMOBuffer[i]。 若 time[i] 早于 startingTime,PMOBuffer 直接置 0,不污染统计窗口。实时第 0 根柱走 else 分支,用 _tmp_sum_close 把当前 tick 的 close[0] 并入累加和再算缓冲值,保证最新价即时反映。 开 MT5 把这段塞进自定义指标 OnCalculate,把 startingTime 设成你盯盘的会话起点,就能看到 PMO 在指定窗口内的真实摆动。外汇与贵金属杠杆高,PMO 仅作概率倾向参考,不代表方向确定性。
for(class="type">int i=total-class="num">1;i>=class="num">0;i--){ if(time[i] >= startingTime){ if(first_val_checked == class="kw">false){ first_val_checked = true; first_valid_index = i; } sum_close += close[i]; sum_open += open[i]; closeBuffer[i] = close[i] *counter / sum_close; openBuffer[i] = open[i]*counter / sum_open; PMOBuffer[i] = simpleMA(i,MA_close,closeBuffer,first_valid_index)-simpleMA(i,MA_open,openBuffer,first_valid_index); counter++; } else{ PMOBuffer[i] = class="num">0; } } } else{ _tmp_sum_close = sum_close +close[class="num">0]; _tmp_sum_open = sum_open + open[class="num">0]; closeBuffer[class="num">0] = close[class="num">0] *counter / _tmp_sum_close; openBuffer[class="num">0] = open[class="num">0] *counter / _tmp_sum_open; PMOBuffer[class="num">0] = simpleMA(class="num">0,MA_close,closeBuffer,first_valid_index)-simpleMA(class="num">0,MA_open,openBuffer,first_valid_index); } class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ EURUSD 上 PMO 与 RSI 的实战对照
在 EURUSD 图表跑 PMO,最直观的发现是它和 RSI(14) 几乎重合,但 2019 年 9 月是个分水岭。9 月 12 日前后欧洲央行释放官方通讯,PMO 上走出左肩高于右肩的“M”形(Merrill 形态),而同期 RSI 与 EURUSD 价格恰恰相反——因为 9 月价格上推时,12 日 PM 相对次日的 CMA 偏高,这条偏离对第二峰后的空头布局有参考价值。 PMO 不受 RSI 的 0–100 边界限制,零轴穿越可直接视作趋势确认。把信号压缩成 IF-THEN 规则并不复杂:零线下正反转→早警买入;零线上负反转→早警卖出;上穿零线→建议买入;下穿零线→建议卖出。外汇与贵金属波动剧烈、杠杆风险高,单一信号不可盲从,实盘中建议叠加其他确认。 响应速度由参数决定:PMO(m,n) 里 m 管振荡器滞后,n 管零轴交叉滞后。PMO(1,1) 最快,等于两期 PM 差分不做均化;折衷可用 PMO(1,5),既保快响应,零轴交叉也不至于太拖。 数值层面,H1 周期取 2019 年前 8 个月 EURUSD 跑 PMO(3,21),分布呈以零为中心的钟形,略左偏可能源于当时堆积的空头持仓。零附近的对称优势暗示多空短期总体平衡。PMO 与其非规范化版本 R² 强相关,说明常规化未扭曲开盘/收盘底层信号,还方便跨品种比对。
「PMO 还能往哪挖」
轴心均值振荡器(PMO)本质是拿累积移动平均线(CMA)套在轴心均值(PM)概念上做的指标,挂在 MT5 里和 RSI、MACD 同属振荡器家族,但 CMA 的平滑逻辑让它对中枢的贴合更死,拐点滞后更小。 原文给出的延伸方向里,最值得动手的是基于 PMO 值绕零轴的统计分布写智能系统——也就是说,先跑一批历史数据看 PMO 在正负区间的概率密度,再拿偏离均值的标准差当过滤。 附带的指标文件直接拖进 MT5 的 Indicators 目录就能编译验证,建议先调 CMA 窗口长度看零轴穿越频率的变化,外汇和贵金属波动大,这类统计策略实盘前务必用小资金测,高风险。
随文附带的 PMO 源码与日志版
这篇 PMO 文章在末尾打包了四份可直接拖进 MT4/MT5 的源码:PMO.mq4(4.32 KB)、PMO_logger.mq4(4.93 KB)、PMO.mq5(4.5 KB)、PMO_logger.mq5(5.06 KB)。其中带 logger 的两个版本多暴露了 data_logging(true/false 开关)和 filename(日志文件名字符串)两个输入参数,跑历史行情时能把 PMO 数值落盘,方便你用 Python 或 Excel 后续做分布检验。 外汇与贵金属市场杠杆高、滑点跳空频繁,PMO 单独使用可能给出信号,但作者未提供任何市场的胜率统计,实盘前务必在策略测试器里用自有数据回测。 下载 ZIP 后建议先编译 PMO_logger.mq5,把 data_logging 设为 true,选一个品种跑 2023 年全年 M5 数据,看日志里 PMO 穿越零轴的延迟是否在你容忍范围内。