从用户面板
📘

从用户面板"动态"更改"EA 交易"参数·进阶篇

第 2/3 篇

EA 生命周期里的事件钩子与参数读取

MT5 的 Expert Advisor 靠几个系统回调把节奏切分开:报价来了跑 OnTick,定时器触发跑 OnTimer,从图表移除时跑 OnDeinit。把交易逻辑塞进 NewBar 判断之后,只在每根 K 线第一跳执行 TradingBlock,能避免同根 K 线内重复下单。 OnTimer 里只做两件轻量事——SetParameters 刷参数、SetInfoPanel 刷面板,不和报价事件抢计算资源。回测环境里 NotTest 返回假,这部分会被跳过,实盘才走完整定时器。 OnDeinit 遇到 REASON_REMOVE 会清掉 EA 自建对象、杀定时器、释放指标句柄 hdlSI;外汇与贵金属杠杆高,误留对象可能干扰后续手动下单,卸载脚本时建议确认图表已干净。 参数读取有个双模开关:flgRead 为 false 时从文件读入并缓存进 arrParamIP 数组,面板数值一变就清零重读。Period_Ind 若 ≤0 会被强行置为 10,Lot ≤0 则落回 0.1,这些是防御性兜底,不是最优仓位建议。

MQL5 / C++
class=class="str">"cmt">//| CURRENT SYMBOL TICKS                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// If the bar is not new, exit
   if(!NewBar()) { class="kw">return; }
   else
     { TradingBlock(); }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| TIMER                                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
   SetParameters(); SetInfoPanel();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EXPERT ADVISOR DEINITIALIZATION                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">// Get the deinitialization reason code
   if(NotTest()) {
     { Print(getUnitReasonText(reason)); }
class=class="str">"cmt">//---
class=class="str">"cmt">// When deleting from the chart
   if(reason==REASON_REMOVE)
     {
      class=class="str">"cmt">// Delete all objects created by the Expert Advisor
      DeleteAllExpertObjects();
      class=class="str">"cmt">//---
      if(NotTest()) { EventKillTimer(); } class=class="str">"cmt">// Stop the timer
      IndicatorRelease(hdlSI); class=class="str">"cmt">// Delete the indicator handle
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SETTING PARAMETERS IN TWO MODES                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// If this variable is set to false, the parameters in the file are read from the array
class=class="str">"cmt">// where they are saved for quick access after they have been read for the first time.
class=class="str">"cmt">// The variable is zeroed out upon the change in the value on the panel.
class="type">bool flgRead=false;
class="type">class="kw">double arrParamIP[]; class=class="str">"cmt">// Array where the parameters from the file are saved
class=class="str">"cmt">//---
class="type">void SetParameters()
  {
class=class="str">"cmt">// If currently in the tester or
class=class="str">"cmt">// in real time but with the "On The Fly" Setting mode disabled
   if(!NotTest() || (NotTest() && !SettingOnTheFly))
     {
      class=class="str">"cmt">// Zero out the variable and parameter array
      flgRead=false;
      ArrayResize(arrParamIP,class="num">0);
      class=class="str">"cmt">//---
      class=class="str">"cmt">// Check the Indicator Period for correctness
      if(Period_Ind<=class="num">0)
        { lcheck=class="num">10; }
      else { lcheck=Period_Ind; }
      gPeriod_Ind=(class="type">int)lcheck;
      class=class="str">"cmt">//---
      gStopLoss=StopLoss;
      gTakeProfit=TakeProfit;
      gReverse=Reverse;
      class=class="str">"cmt">//---
      class=class="str">"cmt">// Check the Lot for correctness
      if(Lot<=class="num">0)
        { lcheck=class="num">0.1; }

「随盘改参的配置文件读写逻辑」

在「On The Fly」模式下,EA 不靠输入框固化参数,而是把仓位、止损、指标周期等写进一个 ini 文件,运行时再读回来。这样你不用重编译就能在 MT5 文件目录里手动改 ParametersOnTheFly.ini 调仓。 当 CheckCreateGetPath() 返回有效路径,程序调用 WriteReadParameters() 去打开同名 ini。文件句柄若不是 INVALID_HANDLE,说明盘上已有参数记录,直接灌进 arrParamIP 数组;若打开失败,则走 iZeroMemory() 把变量清零,避免脏值进场。 读参时有两道硬校验:指标周期 arrParamIP[0] 小于等于 0 会被强制改成 10,手数 arrParamIP[4] 小于等于 0 则兜底成 0.1。外汇与贵金属杠杆高,这类默认值能防止零周期算指标或裸 0 手下单把账户瞬间打爆。 数组长度必须严格等于 szArrIP 才赋值给全局变量 gPeriod_IndgTakeProfitgStopLossgReversegLot。长度对不上就静默跳过,意味着你手改 ini 少写一行,EA 可能倾向沿用旧内存值而非报错,排查时得盯紧数组尺寸。

MQL5 / C++
else { lcheck=Lot; }
gLot=lcheck;
 }
 else class=class="str">"cmt">// If "On The Fly" Setting mode is enabled
 {
 class=class="str">"cmt">// Check whether there is a file to write/read parameters to/from the file
 class="type">class="kw">string lpath="";
 class=class="str">"cmt">//---
 class=class="str">"cmt">// If the folder exists
 if((lpath=CheckCreateGetPath())!="")
  {
  class=class="str">"cmt">// Write or read the file
  WriteReadParameters(lpath);
  }
 }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| WRITE DATA TO FILE                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void WriteReadParameters(class="type">class="kw">string pth)
 {
 class="type">class="kw">string nm_fl=pth+"ParametersOnTheFly.ini"; class=class="str">"cmt">// File name and path
class=class="str">"cmt">//---
class=class="str">"cmt">// Get the file handle to read the file
 class="type">int hFl=FileOpen(nm_fl,FILE_READ|FILE_ANSI);
class=class="str">"cmt">//---
 if(hFl!=INVALID_HANDLE) class=class="str">"cmt">// If the handle has been obtained, the file exists
  {
  class=class="str">"cmt">// Get parameters from the file
  if(!flgRead)
   {
   class=class="str">"cmt">// Set the array size
   ArrayResize(arrParamIP,szArrIP);
   class=class="str">"cmt">//---
   class=class="str">"cmt">// Fill the array with values from the file
   flgRead=GetValuesParamsFromFile(hFl,arrParamIP);
   }
  class=class="str">"cmt">//---
  class=class="str">"cmt">// If the array size is correct,...
  if(ArraySize(arrParamIP)==szArrIP)
   {
   class=class="str">"cmt">// ...set the parameters to the variables
   class=class="str">"cmt">//---
   class=class="str">"cmt">// Check the Indicator Period for correctness
   if((class="type">int)arrParamIP[class="num">0]<=class="num">0) { lcheck=class="num">10; }
   else { lcheck=(class="type">int)arrParamIP[class="num">0]; }
   gPeriod_Ind=(class="type">int)lcheck;
   class=class="str">"cmt">//---
   gTakeProfit=arrParamIP[class="num">1];
   gStopLoss=arrParamIP[class="num">2];
   gReverse=arrParamIP[class="num">3];
   class=class="str">"cmt">//---
   class=class="str">"cmt">// Check the Lot for correctness
   if(arrParamIP[class="num">4]<=class="num">0)
     { lcheck=class="num">0.1; }
   else { lcheck=arrParamIP[class="num">4]; }
   gLot=lcheck;
   }
  }
 else class=class="str">"cmt">// If the file does not exist
  {
  iZeroMemory(); class=class="str">"cmt">// Zero out variables
  class=class="str">"cmt">//---
  class=class="str">"cmt">// When creating the file, write current parameters of the Expert Advisor

◍ 把参数落盘成可读的 CSV

EA 初始化时把内部参数导出成文件,核心就是拿到一个写句柄然后逐行写。下面这段代码用 FileOpen 以写模式打开一个 ANSI 编码的 CSV,分隔符设为等号,遍历两个数组把参数名和值成对写进去。 写文件前必须判断句柄不等于 INVALID_HANDLE,否则磁盘权限或路径问题会让你以为写成功了实际啥也没落。循环里 FileWrite 每调一次写一行,arr_nmparams 存名字、arr_vparams 存数值,两者长度由 szArrIP 控制。 文件句柄用完后 FileClose 必须配对出现,外层 hFl 和里层 hFl2 各关各的;漏关在 MT5 里会悄悄占着句柄,多跑几次就可能报句柄数超限。成功后会 Print 一条带 EA 名的提示,开 MT5 跑一下能在数据文件夹里直接翻到这个等号分隔的文本。

MQL5 / C++
  class=class="str">"cmt">//---
  class=class="str">"cmt">// Get the file handle to write to the file
  class="type">int hFl2=FileOpen(nm_fl,FILE_WRITE|FILE_CSV|FILE_ANSI,"");
  class=class="str">"cmt">//---
  if(hFl2!=INVALID_HANDLE) class=class="str">"cmt">// If the handle has been obtained
    {
     class="type">class="kw">string sep="=";
     class=class="str">"cmt">//---
     class=class="str">"cmt">// Parameter names and values are obtained from arrays in the ARRAYS.mqh file
     for(class="type">int i=class="num">0; i<szArrIP; i++)
       { FileWrite(hFl2,arr_nmparams[i],sep,arr_vparams[i]); }
     class=class="str">"cmt">//---
     FileClose(hFl2); class=class="str">"cmt">// Close the file
     class=class="str">"cmt">//---
     Print("File with parameters of the "+NAME_EXPERT+" Expert Advisor created successfully.");
     }
   }
class=class="str">"cmt">//---
   FileClose(hFl); class=class="str">"cmt">// Close the file
  }

面板点击与输入框的事件路由

在 MT5 自定义面板里,和用户交互主要吃两类事件:CHARTEVENT_OBJECT_CLICK(左键点图形对象)和 CHARTEVENT_OBJECT_EDIT(Edit 对象结束文本编辑)。实时模式下必须先用 NotTest() && SettingOnTheFly 拦一道,否则回测环境里点面板只会空转。 主入口 OnChartEvent 只做分发:命中点击就丢给 ChartEvent_ObjectClick,命中编辑结束就丢给 ChartEvent_ObjectEndEdit,两个函数返回 true 就直接 return,不再往下走。这样把交互逻辑拆干净,后面加按钮也不用动主干。 代码里全局区先声明了对话窗口要用的状态量:currVal 存面板当前值或输入框内容,flgDialogWin 标记窗口是否已存在,number 初始为 -1 表示还没点中列表里的具体参数。颜色常量如 clrFonMsg=C'15,15,15' 是直接写 RGB 的暗灰底,和面板风格统一。 想验证这套路由,开 MT5 挂上 EA,把 SettingOnTheFly 设为 true,点信息面板列表项应弹出对话框;在输入框改完按回车,应触发 OBJECT_EDIT 并写回文件。外汇和贵金属波动剧烈,面板改参数只是辅助,实盘仍需自行承担高风险。

MQL5 / C++
class=class="str">"cmt">// Current value on the panel or
class=class="str">"cmt">// entered in the input box
class="type">class="kw">string currVal="";
class="type">bool flgDialogWin=false; class=class="str">"cmt">// Flag for panel existence
class="type">int
szArrList=class="num">0,class=class="str">"cmt">// Size of the option list array
number=-class="num">1; class=class="str">"cmt">// Parameter number in the panel list
class="type">class="kw">string
nmMsgBx="",  class=class="str">"cmt">// Name of the dialog window
nmValObj=""; class=class="str">"cmt">// Name of the selected object
class=class="str">"cmt">//---
class=class="str">"cmt">// Option list arrays in the dialog window
class="type">class="kw">string lenum[],lenmObj[];
class=class="str">"cmt">//---
class=class="str">"cmt">// colors of the dialog window elements
class="type">color
clrBrdBtn=clrWhite,
clrBrdFonMsg=clrDimGray,clrFonMsg=C&class="macro">#x27;class="num">15,class="num">15,class="num">15&class="macro">#x27;,
clrChoice=clrWhiteSmoke,clrHdrBtn=clrBlack,
clrFonHdrBtn=clrGainsboro,clrFonStr=C&class="macro">#x27;class="num">22,class="num">39,class="num">38&class="macro">#x27;;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| USER EVENTS                                                        | 
class=class="str">"cmt">//+------------------------------------------------------------------+
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)
  {
class=class="str">"cmt">// If the event is real time and the "On The Fly" setting mode is enabled
   if(NotTest() && SettingOnTheFly)
     {
      class=class="str">"cmt">//+------------------------------------------------------------------+
      class=class="str">"cmt">//| THE CHARTEVENT_OBJECT_CLICK EVENT                               | 
      class=class="str">"cmt">//+------------------------------------------------------------------+
      if(ChartEvent_ObjectClick(id,lparam,dparam,sparam)) { class="kw">return; }
      class=class="str">"cmt">//---
      class=class="str">"cmt">//+------------------------------------------------------------------+
      class=class="str">"cmt">//| THE CHARTEVENT_OBJECT_ENDEDIT EVENT                             | 
      class=class="str">"cmt">//+------------------------------------------------------------------+
      if(ChartEvent_ObjectEndEdit(id,lparam,dparam,sparam)) { class="kw">return; }
     }
class=class="str">"cmt">//---
   class="kw">return;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「点一下图表对象就弹参数对话框」

在 MT5 的 EA 或指标里,想让用户点一下面板上的参数名就弹出编辑框,核心是拦截 CHARTEVENT_OBJECT_CLICK 事件(id 固定为 1)。下面这段处理函数演示了从点击事件到打开信息面板的完整链路,复制进你的工程就能跑。 事件进来先调 Get_STV() 抓当前品种数据,再用 sparam 拿到被点对象的名字。随后用 ChartGetInteger 取图表像素宽高:CHART_WIDTH_IN_PIXELS 和 CHART_HEIGHT_IN_PIXELS 两个属性,返回值转 int 后存进 width_chart / height_chart,对话框定位就靠这两个数。 DialogWindowInfoPanel 函数判断 flgDialogWin 标志:没开窗口时,先 GetNumberClickedObjIP 找被点对象在数组里的序号,返回 -1 说明点到了空白区直接 return;过了这关再 InitArraysAndDefault 初始化数组,然后 SetDialogWindow 画窗体,把标志置 true 并 ChartRedraw 强制重绘。 窗口已开的状态下再点击,走 SetEditObjInDialogWindow 把对应编辑框激活;若 sparam 是 btnApply 或 btnCancel,就进按钮分支。btnApply 被触发时会比对面板值和列表值,不一致才写文件更新——避免无谓的 IO。外汇与贵金属波动剧烈,这类交互改的是风控参数,实盘前务必在策略测试器里点一遍验证逻辑。

MQL5 / C++
class="type">bool ChartEvent_ObjectClick(class="type">int id,class="type">long lparam,class="type">class="kw">double dparam,class="type">class="kw">string sparam)
  {
   class=class="str">"cmt">// If there was an event of clicking on a graphical object
   if(id==CHARTEVENT_OBJECT_CLICK) class=class="str">"cmt">// id==class="num">1
     {
      Get_STV(); class=class="str">"cmt">// Get all data on the symbol
      class=class="str">"cmt">//---
      class="type">class="kw">string clickedChartObject=sparam; class=class="str">"cmt">// Name of the clicked object
      class=class="str">"cmt">//---
      class=class="str">"cmt">// Get the chart size
      width_chart=(class="type">int)ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS,class="num">0);
      height_chart=(class="type">int)ChartGetInteger(class="num">0,CHART_HEIGHT_IN_PIXELS,class="num">0);
      class=class="str">"cmt">//---
      DialogWindowInfoPanel(clickedChartObject);
     }
class=class="str">"cmt">//---
   class="kw">return(false);
  }
class="type">void DialogWindowInfoPanel(class="type">class="kw">string clickObj)
  {
class=class="str">"cmt">// If there is currently no dialog window
   if(!flgDialogWin)
     {
      class=class="str">"cmt">// Get the object number in the array
      class=class="str">"cmt">// Exit if none of the parameters displayed on the panel has been clicked
      if((number=GetNumberClickedObjIP(clickObj))==-class="num">1) { class="kw">return; }
      class=class="str">"cmt">//---
      class=class="str">"cmt">// Initialization of class="kw">default values
      class=class="str">"cmt">//and determination of the list array size
      if(!InitArraysAndDefault()) { class="kw">return; }
      class=class="str">"cmt">//---
      class=class="str">"cmt">// Set the dialog window
      SetDialogWindow();
      class=class="str">"cmt">//---
      flgDialogWin=true; class=class="str">"cmt">// Mark the dialog window as open
      ChartRedraw();
     }
   else class=class="str">"cmt">// If the dialog window is open
     {
      class=class="str">"cmt">// Set the input box for the modification of the value
      SetEditObjInDialogWindow(clickObj);
      class=class="str">"cmt">//---
      class=class="str">"cmt">// If one of the buttons in the dialog box is clicked
      if(clickObj=="btnApply" || clickObj=="btnCancel")
        {
         class=class="str">"cmt">// If the Apply button is clicked
         if(clickObj=="btnApply")
           {
            class=class="str">"cmt">// Compare values on the panel with the ones on the list
            class=class="str">"cmt">// If the value on the list is different from the one that is currently displayed on the panel
class=class="str">"cmt">// (which means it is different from the one in the file),
            class=class="str">"cmt">// ...change the value on the panel and update the file

常见问题

在面板按钮的点击事件里调用参数重写函数并刷新输入变量,下一次 OnTick 读取新值即可,无需重新加载 EA。
在 OnInit 里用文件函数读取 CSV 首行,按字段顺序赋给外部输入变量;文件不存在则走默认参数。
小布可读取你导出的参数 CSV 并比对品种波动特征,标出不合理数值与冲突项,省去人工核对。
用 OBJPROP_ONCLICK 绑定图表对象,在 OnChartEvent 里判断 id 与 sprite 名,触发参数对话框函数。
确认退出原因码后先写文件再释放资源,且用 FileFlush 强制刷盘,避免缓存未落盘就结束进程。