MQL5 中的事件处理:快速更改 MA 周期·进阶篇
📘

MQL5 中的事件处理:快速更改 MA 周期·进阶篇

第 2/2 篇

线性加权均值的权重累加与主循环

线性加权移动平均(LWMA)的核心在于权重系数随距离递减:离当前柱越近的价格乘的权重越大。代码里先用 weightsum 把总权重一次性算死,公式是 MA_Period*(MA_Period+1)/2,比如周期 14 时 weightsum=105,之后每根柱都复用这个值,避免重复运算。 首次计算时(prev_calculated==0),循环里逐根把 k*price[i] 累进 firstValue,同时 weightsum+=k 累加权重,最后 firstValue/(double)weightsum 得到缓冲首值赋给 ExtLineBuffer[limit-1]。 非首次走主循环时 limit=prev_calculated-1,只对新增柱计算:内层 j 从 0 到 InpMAPeriod-1,sum+=(InpMAPeriod-j)*price[i-j],再除以 weightsum 写回 ExtLineBuffer[i]。这套写法把外汇、贵金属 1 分钟到日线都适用,但杠杆品种跳空时 LWMA 可能滞后放大,属高风险信号,调参前建议在 MT5 策略测试器用历史数据验证权重分配。

MQL5 / C++
 weightsum+=k;                                              class=class="str">"cmt">// <-- 使用 weightsum
   firstValue+=k*price[i];
   
}
   firstValue/=(class="type">class="kw">double)weightsum;
   ExtLineBuffer[limit-class="num">1]=firstValue;
   
}
  else limit=prev_calculated-class="num">1;
class=class="str">"cmt">//--- 主循环
   for(i=limit;i<rates_total;i++)
   {
    sum=class="num">0;
    for(class="type">int j=class="num">0;j<InpMAPeriod;j++) sum+=(InpMAPeriod-j)*price[i-j];
    ExtLineBuffer[i]=sum/weightsum;              class=class="str">"cmt">// <-- 使用 weightsum
    
}
class=class="str">"cmt">//---
   
}
class="type">int weightsum = MA_Period *( MA_Period + class="num">1 ) / class="num">2;

◍ 用键盘热键实时改均线参数

在 MT5 图表上做价格行为分析时,反复打开指标面板调均线周期很打断节奏。把按键事件接到 OnChartEvent 里,就能在盘面直接用键盘改 MA 的方法、周期和偏移。 下面这段逻辑只处理 CHARTEVENT_KEYDOWN:Tab 键切换平滑方式(MA_Method ±1),方向键上下改周期(MA_Period ±1),小键盘上下改偏移(MA_Shift ±1)。每次改动都同步把终端全局变量 Updated 置 0,通知主循环重算。 外汇与贵金属杠杆高、滑点大,这类热键脚本只解决交互效率,不预示任何方向;参数乱切可能让你在黄金 5 分钟图上画出完全失真的均线。 别把全局变量当摆设 Updated 置 0 是给指标或 EA 主循环发的“脏标记”。如果你只改了 MA_Period 却忘了 GlobalVariableSet("Updated",0),画面可能停留在上一个周期,造成误判。

MQL5 / C++
class="type">void OnChartEvent( const class="type">int       id,
                     const class="type">long&     lparam,
                     const class="type">class="kw">double&   dparam,
                     const class="type">class="kw">string&   sparam )
{
   if( id == CHARTEVENT_KEYDOWN )
   class="kw">switch( lparam )
   {
     case( KEY_TAB        ):  changeTerminalGlobalVar( "MA_Method",  class="num">1 );
                              GlobalVariableSet( "Updated",  class="num">0 );
                              Updated = class="num">0;
                              break;

     case( KEY_UP         ):  changeTerminalGlobalVar( "MA_Period",  class="num">1 );
                              GlobalVariableSet( "Updated",  class="num">0 );
                              Updated = class="num">0;
                              break;
     case( KEY_DOWN       ):  changeTerminalGlobalVar( "MA_Period", -class="num">1 );
                              GlobalVariableSet( "Updated",  class="num">0 );
                              Updated = class="num">0;
                              break;

     case( KEY_NUMLOCK_UP   ):  changeTerminalGlobalVar( "MA_Shift",   class="num">1 );
                              GlobalVariableSet( "Updated",  class="num">0 );
                              Updated = class="num">0;
                              break;
     case( KEY_NUMLOCK_DOWN ):  changeTerminalGlobalVar( "MA_Shift",  -class="num">1 );

「用全局变量热调均线参数」

在 MT5 里直接改 EA 或指标参数往往要重加载,借助终端全局变量可以把移动均线的周期、方法和偏移做成运行时热调。下面这段函数把参数名和步进方向传进去,先读 GlobalVariableGet 拿到旧值,再按规则写回并同步本地变量,最后 ChartRedraw 刷新图表。 对于 MA_Period,新值必须大于 0,否则强制落回 1——也就是说均线周期下限就是 1 根 K 线,再减就无意义。MA_Method 用 (var+1)%4 做循环,对应平台 0~3 四种平滑方式(SMA、EMA、SMMA、LWMA),每触发一次就切到下一种。 MA_Shift 则直接加减 dir,没有上下限保护,实盘里若被外部脚本误写为超大整数,均线会整体平移导致形态失真。外汇与贵金属杠杆高、滑点无常,这类热调只建议在模拟环境先验证再上真仓。 别把正态当圣经:全局变量是跨 EA 共享的,若同时挂了多个读取同名的脚本,一个改周期会牵连其他图表,排查时先看 GlobalVariableGet 的返回而非只看本地变量。

MQL5 / C++
class="type">void changeTerminalGlobalVar( class="type">class="kw">string name, class="type">int dir = class="num">0 )
{
  class="type">int var = GlobalVariableGet( name );
  class="type">int newparam = var + dir;
  if( name == "MA_Period" )
  {
    if( newparam > class="num">0 )    class=class="str">"cmt">/// 对MA有效的时间周期
    {
      GlobalVariableSet( name, newparam );
      MA_Period = newparam;    class=class="str">"cmt">/// 不要忘记改变全局变量
    }
    else                  class=class="str">"cmt">/// 我们不改变时间周期,因为MA周期已等于最小值1
    {  
      GlobalVariableSet( name, class="num">1 );  
      MA_Period = class="num">1;    class=class="str">"cmt">/// 不要忘记改变全局变量 
    }
  }
  if( name == "MA_Method" )    class=class="str">"cmt">/// 这里当你调用“dir”,它总是等于1,dir 的值并不重要 
  {
    newparam = ( var + class="num">1 ) % class="num">4;
    GlobalVariableSet( name, newparam );  
    MA_Method = newparam;
  }      
  if( name == "MA_Shift" )
  {
    GlobalVariableSet( name, newparam );    
    MA_Shift = newparam;
  }      
  ChartRedraw( );
  class="kw">return;
}

图表静默时 MA 为何迟迟不重绘

把指标挂上图,随手按热键改 MA 参数,逻辑是通的:TGV 用 F3 调出来看会立刻变,但 MA 线要等下一笔价格跳动才重绘。美国时段报价流活跃,这点延迟基本看不见;可一到夜间平静段,没新 tick 进来,改完参数可能干等好几分钟线才动。 根子在 MT5 的事件模型。旧版(build 245 前)指标只有一个主入口 OnCalculate(),外加 OnInit/OnDeinit 管初始化和收尾。后来加了 Timer 和 ChartEvent 这类新事件入口,但 OnChartEvent() 只管自己那摊事,跟 OnCalculate() 没有正式关联,所以你在 OnChartEvent 里改了参数,它不会主动触发重算。 能想到的路子有两条。其一是在 OnChartEvent() 里直接“嵌套”调用 OnCalculate(),把参数都预填好,参数一变就从头重算整段历史——没有任何计算优化,数据量大的时候代价明显。 其二是往图形缓冲区塞一个“人工价格跳动”逼 OnCalculate 跑起来。文档里没正经写法,得去翻 PostMessageA、API 那类野路子。这种未载入官方文献的依赖以后很可能被改掉,不在实盘代码里赌它。

◍ 把价格更新塞进图表事件里

实测下来,最关键的改造已经跑通:把自定义更新函数直接插到 OnChartEvent() 的 return 之前,编译挂图后,报价跳动与指标刷新脱钩,肉眼看不出延迟。 下面这段就是核心函数,逻辑极简——先取当前品种当前周期的柱数,再把收盘价整段拷进 _price 数组,最后手动调一次 OnCalculate。注意它用的是 CopyClose,所以落进 price[] 的都是收盘价。 int OnCalculate_Void() { const int rates_total = Bars( _Symbol, PERIOD_CURRENT ); CopyClose( _Symbol, PERIOD_CURRENT, 0, rates_total, _price ); OnCalculate( rates_total, 0, 0, _price ); return( 1 ); } 若你想用其他价格源,别死磕 CopyClose。在指标属性 Settings 里改 Apply to 字段,或把函数换成 CopyOpen/CopyHigh/CopyLow;中间价、典型价、加权价这类没有现成 CopyXXXX 的,就得自己先算数组再喂进去。 我对外汇贵金属这类高杠杆品种提醒一句:历史负载是隐性坑。EURUSD H1 灌进 1999 年以前的约 70 万根柱做检查,CopyClose 本身没拖慢指标;真要卡,大概率不是拷贝函数,而是复杂指标被强制从头重算历史。实盘前务必在 MT5 用满历史数据自测一次。

MQL5 / C++
class="type">int OnCalculate_Void()
{
  const class="type">int rates_total = Bars( _Symbol, PERIOD_CURRENT );
  CopyClose( _Symbol, PERIOD_CURRENT, class="num">0, rates_total, _price );
  OnCalculate( rates_total, class="num">0, class="num">0, _price );
  class="kw">return( class="num">1 );
}

「画得少,看得清」

把 MA 周期塞进终端全局变量(F3 可查)后,指标外部参数就脱离了句柄固定的死锁,这点在 MT5 里比 MQL4 的即时调用更绕但也更稳。嵌套娃娃式变体(MyMA_ind_with_ChartEvent_Matryoshka.mq5)能在不等价格跳动时刷新,简单 MA 重算开销小,肉眼看曲线平滑伸缩还不卡。 一个坑得刻进肌肉记忆:全局变量对话框里手改 TGV 后,终端没有 CHARTEVENT_GLOBALVAR_ENDEDIT 这类事件,只能靠下一次按键把 Updated 置零触发全量重算;不然优化的局部计算会沿用旧值,图表 silently 跑偏。外汇和贵金属杠杆高,这类手动干预失误可能直接放大滑点。 作者早年写的 C 级串联只在 tick 到来时更新、性能垫底,如今早没人用;真要验证,下载那三个 mq5 挂上图,改周期按一下键,比读十遍文档来得实在。

常见问题

多数情况是图表未获得焦点或脚本未挂在全球热键钩子上;先点一下图表再按,或把改参逻辑放到独立指标而非脚本里。
全局变量写入后需等下一次 OnCalculate 或定时器触发才会重算;加一个短周期定时器或图表事件推送可让生效更跟手。
可以,小布盯盘的 AIGC 已内置参数切换与诊断,打开对应品种页即可直接调周期并看重绘结果。
不是算错,是主循环没收到价格更新事件;把价格更新塞进图表事件里或开轻量定时器即可解决。
单根 K 线内重算权重开销极小,但每 tick 全重算会掉帧;只在参数变更或新柱形成时重算权重最稳。