从零开始在MQL5中实现移动平均线:简单明了·进阶篇
(2/3)·从算术平均到加权递归,四种均线在MT5里的计算细节与提速写法一次看清
「手搓一条主图 SMA 指标的最小骨架」
想在 MT5 主图里挂一条自己的均线,最薄的可编译骨架只需要 1 个绘图缓冲、1 个周期入参。下面这段代码把版本号、主图窗口属性、缓冲数量先定死,再把画线类型设为红色实线,宽度 1,标签叫 SMA。
class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_buffers class="num">1 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//--- plot MA class="macro">#class="kw">property indicator_label1 "SMA" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrRed class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- input parameters input class="type">int InpPeriod = class="num">10; class=class="str">"cmt">// SMA Period class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double MABuffer[]; class=class="str">"cmt">//--- global variables class="type">int ExtPeriod; class=class="str">"cmt">// SMA calculation period class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,MABuffer,INDICATOR_DATA); class=class="str">"cmt">//--- successful initialization class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- Adjust the entered value of the SMA calculation period ExtPeriod=(InpPeriod<class="num">1 ? class="num">10 : InpPeriod); class=class="str">"cmt">//--- If there is not enough historical data for calculation, class="kw">return class="num">0 if(rates_total<ExtPeriod) class="kw">return class="num">0; class="type">int start=class="num">0; class=class="str">"cmt">// calculation start bar class=class="str">"cmt">//--- If this is the first run or a change in historical data if(prev_calculated==class="num">0) { class=class="str">"cmt">//--- initialize the indicator buffer with an empty value and
#property indicator_buffers 1 声明只用 1 个数据缓冲;input int InpPeriod = 10 把默认均线周期设为 10 根 K 线,改这个数就能换周期。OnInit 里 SetIndexBuffer(0,MABuffer,INDICATOR_DATA) 把数组绑到 0 号绘图位,缺这行编译能过但图上空白。
OnCalculate 开头做了两层防护:ExtPeriod 把小于 1 的非法输入强制拉回 10;rates_total<ExtPeriod 时直接 return 0,避免前几根 K 线数量不够算均线而报错。外汇与贵金属杠杆高,这类自定义指标仅作价格行为辅助,信号失效概率不低,实盘前务必在策略测试器跑历史数据验证。
class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_buffers class="num">1 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//--- plot MA class="macro">#class="kw">property indicator_label1 "SMA" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrRed class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- input parameters input class="type">int InpPeriod = class="num">10; class=class="str">"cmt">// SMA Period class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double MABuffer[]; class=class="str">"cmt">//--- global variables class="type">int ExtPeriod; class=class="str">"cmt">// SMA calculation period class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,MABuffer,INDICATOR_DATA); class=class="str">"cmt">//--- successful initialization class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- Adjust the entered value of the SMA calculation period ExtPeriod=(InpPeriod<class="num">1 ? class="num">10 : InpPeriod); class=class="str">"cmt">//--- If there is not enough historical data for calculation, class="kw">return class="num">0 if(rates_total<ExtPeriod) class="kw">return class="num">0; class="type">int start=class="num">0; class=class="str">"cmt">// calculation start bar class=class="str">"cmt">//--- If this is the first run or a change in historical data if(prev_calculated==class="num">0) { class=class="str">"cmt">//--- initialize the indicator buffer with an empty value and
◍ SMA 指标的增量计算与初始化细节
这段 MQL5 片段展示了一个贴近收盘价 SMA 指标的骨架:首次加载时把计算起点推到 ExtPeriod,避免前几根 bar 样本不足;非首次则从上次已算位置的前一根继续,只对新增 bar 做循环,属于典型的增量更新写法。 核心循环里对每根 bar i 向前取 ExtPeriod 根收盘价求和再除以周期,结果写进 MABuffer[i]。若 ExtPeriod=10,则第 9 根 bar(索引从 0 起)才开始有有效值,此前缓冲区靠 ArrayInitialize 填 EMPTY_VALUE 不绘线。 指标属性段把 plot 设成红色实线、宽度 1,输入参数 InpPeriod 默认 10,全局变量 ExtPeriod 承接周期。开 MT5 新建指标把下面代码贴进编辑器,改 InpPeriod 就能直接看不同周期平滑效果;外汇与贵金属波动剧烈,均线仅描述已发生价格关系,滞后与假突破风险高。
class=class="str">"cmt">//--- set the start of the calculation to ExtPeriod ArrayInitialize(MABuffer,EMPTY_VALUE); start=ExtPeriod; } class=class="str">"cmt">//--- If this is not the first run, the calculation will start at the current bar else start=prev_calculated-class="num">1; class=class="str">"cmt">//--- in the loop from the &class="macro">#x27;start&class="macro">#x27; value to the current history bar for(class="type">int i=start; i<rates_total; i++) { class=class="str">"cmt">//--- calculate average close prices for ExtPeriod bars class="type">class="kw">double result=class="num">0; for(class="type">int j=class="num">0; j<ExtPeriod; j++) result+=close[i-j]; result/=ExtPeriod; class=class="str">"cmt">//--- Write the calculation result to the buffer MABuffer[i]=result; } class=class="str">"cmt">//--- class="kw">return the number of bars calculated for the next OnCalculate call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SMAonPriceClose.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 "Copyright class="num">2024, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_buffers class="num">1 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//--- plot MA class="macro">#class="kw">property indicator_label1 "SMA" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrRed class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- input parameters input class="type">int InpPeriod = class="num">10; class=class="str">"cmt">// SMA Period class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double MABuffer[]; class=class="str">"cmt">//--- global variables class="type">int ExtPeriod; class=class="str">"cmt">// SMA calculation period class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,MABuffer,INDICATOR_DATA); class=class="str">"cmt">//--- successful initialization class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+
SMA 指标里 OnCalculate 的增量算路
自定义指标跑在 MT5 上,核心就在 OnCalculate 这一层。它每次被终端回调,传入的 rates_total 是可用 K 线总数,prev_calculated 是上轮已算完的柱数——靠这两个值决定从哪根开始算,避免每 tick 重算全历史。 代码开头先把周期参数收口:ExtPeriod=(InpPeriod<1 ? 10 : InpPeriod),也就是用户填 0 或负数时强制退回 10 根均线周期。若 rates_total<ExtPeriod 直接 return 0,因为不够一根完整均线,画不出来。 首跑或历史变动时 prev_calculated==0,先把 MABuffer 用 EMPTY_VALUE 填空,start 定在 ExtPeriod,再把前 ExtPeriod 根 close 加总除以周期,落进 MABuffer[start-1] 作为种子值。之后非首跑就只从 prev_calculated-1 这根续算。 循环里用的是增量公式:MABuffer[i]=ma_prev+(close[i]-price_first)/ExtPeriod。把 ExtPeriod 根前的收盘价踢出去、当前收盘价加进来,比每根重加平均省一个数量级算力。外汇与贵金属波动大、跳空频繁,这种写法在 1 分钟图上跑几千根也几乎不掉帧,但均线本身滞后,信号只作概率参考。
class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- Adjust the entered value of the SMA calculation period ExtPeriod=(InpPeriod<class="num">1 ? class="num">10 : InpPeriod); class=class="str">"cmt">//--- If there is not enough historical data for calculation, class="kw">return class="num">0 if(rates_total<ExtPeriod) class="kw">return class="num">0; class="type">int start=class="num">0; class=class="str">"cmt">// calculation start bar class=class="str">"cmt">//--- If this is the first run or a change in historical data if(prev_calculated==class="num">0) { class=class="str">"cmt">//--- initialize the indicator buffer with an empty value, class=class="str">"cmt">//--- set the start of the calculation to ExtPeriod and class=class="str">"cmt">//--- calculate average close prices for ExtPeriod bars for the first SMA value on the start-class="num">1 bar ArrayInitialize(MABuffer,EMPTY_VALUE); start=ExtPeriod; class="type">class="kw">double value=class="num">0; for(class="type">int i=class="num">0; i<start; i++) value+=close[i]; MABuffer[start-class="num">1]=value/ExtPeriod; } class=class="str">"cmt">//--- If this is not the first run, the calculation will start at the current bar else start=prev_calculated-class="num">1; class=class="str">"cmt">//--- in the loop from the &class="macro">#x27;start&class="macro">#x27; value to the current history bar for(class="type">int i=start; i<rates_total; i++) { class=class="str">"cmt">//--- define prices for calculating SMA class="type">class="kw">double ma_prev=MABuffer[i-class="num">1]; class=class="str">"cmt">// Previous calculated SMA value class="type">class="kw">double price_first=close[i-ExtPeriod]; class=class="str">"cmt">// Close price ExtPeriod bars ago class=class="str">"cmt">//--- Set the current SMA value to the buffer calculated as class=class="str">"cmt">//--- (past SMA value + (current close price - close price ExtPeriod bars ago) / SMA ExtPeriod calculation period) MABuffer[i]=ma_prev+(close[i]-price_first)/ExtPeriod; } class=class="str">"cmt">//--- class="kw">return the number of bars calculated for the next OnCalculate call class="kw">return(rates_total); }
「指数移动平均线 (EMA)」
指数平滑移动平均线是通过将当前收盘价的一定比例加到之前计算的移动平均值上来计算的。在指数平滑移动平均线中,最近的收盘价更有价值。在计算EMA时,参与计算的K线的价格权重会以由平滑常数k(1 > k > 0)指定的速率呈指数级下降。最常见的情况是,k根据移动平均计算周期来计算:k = 2.0 / (N + 1),其中N是计算周期的值。 EMA: 对价格变化的反应更快,使其更适合短期交易, 在识别趋势以及买入和卖出信号方面更有效,因为它考虑了最新的数据, 常用于与其他EMA(例如50和200)的交叉点,以确定买入或卖出点。 图例2. 指数移动平均线,计算周期为10的收盘价EMA 计算 P-百分比指数移动平均线,其计算如下: 其中: CLOSE(i) — 当前周期收盘价; EMA(i - 1) — 前一周期的移动平均值; P — 使用价格数值的百分比(平滑常数)。 让我们编写一个简单的指标,用于展示指数移动平均线的计算过程: 与SMA的计算方式类似,以下展示的EMA计算过程没有对输入参数(例如计算周期)进行校验,同时展示出资源节约型的指标计算逻辑。在首次启动时,指标会计算整个可用的历史数据,然后在每个tick时重新计算整个历史数据。这样不可避免地导致出现提示指标运行太慢的信息。 您可以在附件中找到EMAonPriceCloseRAW.mq5指标文件。 计算优化 让我们来实现一个资源节约型指标计算。在首次启动时,它将计算整个历史数据,然后在每个新的tick时,仅重新计算当前K线: 如果输入的计算周期值小于1,则将移动平均线的默认值设置为10。 EMA的计算使用前一根K线的已计算出的EMA值。 首次启动时,由于尚未执行这类计算,因此我们直接使用收盘价(Close)作为初始数据。 该指标实现了资源节约型数据计算:在首次启动时,计算整个可用的历史数据,在随后的OnCalculate()调用中,仅计算当前K线。 您可以在下方找到EMAonPriceClose.mq5指标文件。 [CODE] EMA = (CLOSE(i) * P) + (EMA(i - <span class="number">1</span>) * (<span class="number">1</span> - P)) <span class="comment">//+------------------------------------------------------------------+</span>
| <span class="comment">// | EMAonPriceCloseRAW.mq5 | </span> |
|---|
<span class="comment">//| &nb
◍ 用收盘价自己写一遍 EMA 循环
下面这段是自定义指标里计算 EMA 的核心循环,没调内置 iMA,而是手算。函数入参把 high/low/close 等六个序列以引用方式传进来,循环从 i=0 开始一路算到 rates_total,也就是从头扫历史。 平滑因子 smf 写死成 2.0/(InpPeriod+1.0),当 InpPeriod 输入 10 时 smf≈0.1818,代表收盘价权重约 18%。前一根值 prev_value 在 i>=1 时取 MABuffer[i-1],第一根没前值时退化为 close[i],避免数组越界。 逐根公式就一行:result = close[i]*smf + prev_value*(1-smf),算完写回 MABuffer[i]。最后 return(rates_total) 把已处理柱数交还终端,供下次增量调用。 指标头里 #property indicator_buffers 1、plot 类型 DRAW_LINE 且染红,input 的 InpPeriod 默认 10。把周期改成 5 或 20,在 MT5 加载后肉眼能看出红线对价格穿透频率明显变化,外汇与贵金属波动大,此类均线仅描述概率倾向,实盘属高风险。
const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- in the loop from zero(the beginning of history) to the current bar for(class="type">int i=class="num">0; i<rates_total; i++) { class="type">class="kw">double result=class="num">0; class="type">class="kw">double smf=class="num">2.0/(InpPeriod+class="num">1.0); class=class="str">"cmt">// smoothing factor class="type">class="kw">double prev_value=(i>=class="num">1 ? MABuffer[i-class="num">1] : close[i]);class=class="str">"cmt">// previous calculated EMA value class=class="str">"cmt">//--- calculate EMA based on the Close price of the bar, the previous EMA value and the smoothing factor result=close[i]*smf+prev_value*(class="num">1-smf); class=class="str">"cmt">//--- Write the calculation result to the buffer MABuffer[i]=result; } class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EMAonPriceClose.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 "Copyright class="num">2024, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_buffers class="num">1 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//--- plot MA class="macro">#class="kw">property indicator_label1 "EMA" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrRed class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- input parameters input class="type">int InpPeriod = class="num">10; class=class="str">"cmt">// EMA Period class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double MABuffer[]; class=class="str">"cmt">//--- global variables class="type">int ExtPeriod; class=class="str">"cmt">// EMA calculation period
EMA 指标的初始化与逐根重算逻辑
自定义指标在 MT5 里跑起来,靠的是 OnInit 与 OnCalculate 两个生命周期函数。OnInit 只做一件事:把 MABuffer 绑定到指标主数据缓冲,返回 INIT_SUCCEEDED 即代表加载成功。 OnCalculate 的入参里 rates_total 是当前历史总数,prev_calculated 是上一次已算过的柱数。代码里先对周期做兜底:ExtPeriod=(InpPeriod<1 ? 10 : InpPeriod),也就是外部周期填 0 或负数时自动退回 10 根,避免除零和空算。 若 rates_total 小于 ExtPeriod 直接 return 0,因为 EMA 首值需要足够样本。首次运行 prev_calculated==0 时,用 ArrayInitialize 把缓冲填成 EMPTY_VALUE 并从 0 号柱起步;后续 tick 更新则只从 prev_calculated-1 重算当前柱,省去全量回扫。 核心平滑系数 smf=2.0/(ExtPeriod+1.0),例如周期 10 时 smf≈0.1818。循环内 prev_value 取前一缓冲值,首根则用 close[i] 替代,递推式 result=close[i]*smf+prev_value*(1-smf)。外汇与贵金属杠杆高、滑点跳空频繁,EMA 重算点可能与实盘成交价偏离,上 MT5 用不同 InpPeriod 跑同段行情可验证缓冲更新节奏。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,MABuffer,INDICATOR_DATA); class=class="str">"cmt">//--- successful initialization class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- Adjust the entered value of the EMA calculation period ExtPeriod=(InpPeriod<class="num">1 ? class="num">10 : InpPeriod); class=class="str">"cmt">//--- If there is not enough historical data for calculation, class="kw">return class="num">0 if(rates_total<ExtPeriod) class="kw">return class="num">0; class="type">int start=class="num">0; class=class="str">"cmt">// calculation start bar class=class="str">"cmt">//--- If this is the first run or a change in historical data if(prev_calculated==class="num">0) { class=class="str">"cmt">//--- initialize the indicator buffer with an empty value and set the start of the calculation to class="num">0 ArrayInitialize(MABuffer,EMPTY_VALUE); start=class="num">0; } class=class="str">"cmt">//--- If this is not the first run, the calculation will start at the current bar else start=prev_calculated-class="num">1; class=class="str">"cmt">//--- in the loop from the &class="macro">#x27;start&class="macro">#x27; value to the current history bar for(class="type">int i=start; i<rates_total; i++) { class="type">class="kw">double result=class="num">0; class="type">class="kw">double smf=class="num">2.0/(ExtPeriod+class="num">1.0); class=class="str">"cmt">// smoothing factor class="type">class="kw">double prev_value=(i>=class="num">1 ? MABuffer[i-class="num">1] : close[i]); class=class="str">"cmt">// previous calculated EMA value class=class="str">"cmt">//--- calculate the EMA value for the current loop bar result=close[i]*smf+prev_value*(class="num">1-smf);