向指标或者EA中快速添加控制面板·进阶篇
(2/3)· 还在每次调参数都重启EA?用标准类库挂面板,实时改设置不中断策略
控件对话框里的勾选与事件钩子
在 MT5 自定义面板里,复选组(check group)的状态读写靠两个虚函数承接:SetCheck 按索引写值,GetCheck 按索引返回值。下面这段来自 CControlsDialog 的实现,把对 m_check_group 的调用包了一层,外部只需传 idx 和布尔量。
class="type">bool CControlsDialog::SetCheck(class="kw">const class="type">int idx,class="kw">const class="type">bool check) { class="kw">return(m_check_group.Check(idx,check)); } class="type">int CControlsDialog::GetCheck(class="kw">const class="type">int idx) { class="kw">return(m_check_group.Check(idx)); }
class="kw">virtual class="type">bool OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam); class=class="str">"cmt">//--- 元素的SetCheck()函数 class="kw">virtual class="type">bool SetCheck(class="kw">const class="type">int idx,class="kw">const class="type">int value); class=class="str">"cmt">//--- 元素的GetCheck()函数 class="kw">virtual class="type">int GetCheck(class="kw">const class="type">int idx) class="kw">const; class="kw">protected: class=class="str">"cmt">//--- 创建独立控件 class="type">bool CreateCheckGroup(class="type">void); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 为元素设置检查 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CControlsDialog::SetCheck(class="kw">const class="type">int idx,class="kw">const class="type">bool check) { class="kw">return(m_check_group.Check(idx,check)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 获取元素的检查结果 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CControlsDialog::GetCheck(class="kw">const class="type">int idx) { class="kw">return(m_check_group.Check(idx)); } class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_plots class="num">0 class="macro">#include "PanelDialog.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 全局变量 | class=class="str">"cmt">//+------------------------------------------------------------------+ CControlsDialog ExtDialog; class=class="str">"cmt">//--- 输入参数 class="kw">input class="type">bool bln_mail=class="kw">false; class=class="str">"cmt">// 通过email通知 class="kw">input class="type">bool bln_push=class="kw">false; class=class="str">"cmt">// 通过短信push通知 class="kw">input class="type">bool bln_alert=true; class=class="str">"cmt">// 通过alert通知 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ExtDialog.ChartEvent(id,lparam,dparam,sparam); } class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓存映射 class=class="str">"cmt">//--- 创建程序对话框
◍ 对话框初始化失败就直接退出
在 MT5 的 OnInit 阶段,如果自定义通知对话框没能建出来,最干脆的做法是立刻返回 INIT_FAILED,让 EA 或指标根本不起跑。上面这段把窗口建在屏幕坐标 (50,50)、宽 180 高 160,名字叫 "Notification",任何一步 Create 或 Run 不成功都直接拦死。 建完之后用 SetCheck 把三个开关按预设布尔量复位:索引 0 对应邮件(bln_mail)、1 对应推送(bln_push)、2 对应弹窗警报(bln_alert)。全部设完才返回 INIT_SUCCEEDED,意味着用户看到的第一帧界面状态已经是你代码里的默认配置。 开 MT5 自己验证时,可以把 Create 的第三参(x=50)改成 300,看窗口是否真的挪位;若故意把宽高设成 0,终端会报 INIT_FAILED,证明这段拦截逻辑是硬生效而非摆设。外汇与贵金属品种波动剧烈,这类通知开关若默认全开,可能在非农时段被消息刷屏,建议先按品种波动率手动调默认布尔值。
if(!ExtDialog.Create(class="num">0,"Notification",class="num">0,class="num">50,class="num">50,class="num">180,class="num">160)) class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- 运行程序 if(!ExtDialog.Run()) class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- ExtDialog.SetCheck(class="num">0,bln_mail); ExtDialog.SetCheck(class="num">1,bln_push); ExtDialog.SetCheck(class="num">2,bln_alert); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); }
「把MACD样例EA和参数面板拧成一股绳」
拿 MT5 自带的 MACD Sample.mq5 开刀,把它和 PanelDialog2.mqh 面板绑在一起,核心收益就一条:在图表上实时改 Lots、Trailing Stop Level 这些参数,点 Apply changes 立刻生效;而 Mail / Push / Alert 通知开关是自动生效的,不用再点按钮。外汇和贵金属波动剧烈,这种热改参数能让你在行情突变时少点几步鼠标,但仓位失控的风险也同步放大。 EA 侧先动刀:把标准样例复制到自定义 Notification 文件夹,在全局变量区加一套带 Ext 前缀的副本变量(ExtLots、ExtTakeProfit 等),OnInit 里把 Inp 开头的外部变量拷过去,再把 CSampleExpert 几个 Init 函数里的原变量替换成副本。编译零报错才算过关。 面板侧不能还是空壳。给 CControlsDialog 加外部变量存输入状态,构造函数初始化,Create 里按内部变量铺开关组。EA 里声明 ExtDialog 并加载面板,补上 OnChartEvent 和 OnDeinit——后者负责销毁面板,前者接图表事件。 两边真正对话靠 Initialization() 和 GetValues():EA 加载后把参数喂给面板,面板填好控件;点 Apply changes 后 mModification 置位,把改完的值回传 EA 重新初始化。最后一步防策略测试器里跑面板,加 bool_tester 标识,OnChartEvent 里侦测到参数变就热重启 EA 逻辑。 别在测试器里裸跑面板 面板依赖图表事件和可视控件,丢进策略测试器会直接失效。代码里用 bool_tester 在 OnInit 拦掉加载,实盘图表才允许 ExtDialog 起来,否则回测日志里只会看到一堆空引用。
class="macro">#include <Trade\PositionInfo.mqh> class="macro">#include <Trade\AccountInfo.mqh> class=class="str">"cmt">//--- 输入参数 class="kw">input class="type">bool InpMail=class="kw">false; class=class="str">"cmt">//通过email进行通知 class="kw">input class="type">bool InpPush=class="kw">false; class=class="str">"cmt">// 通过短信push通知 class="kw">input class="type">bool InpAlert=true; class=class="str">"cmt">// 通过alert通知 class=class="str">"cmt">//--- class="kw">input class="type">class="kw">double InpLots =class="num">0.1; class=class="str">"cmt">// 交易量 class="kw">input class="type">int InpTakeProfit =class="num">50; class=class="str">"cmt">// 止赢(点数) class="kw">input class="type">int InpMACDCloseLevel=class="num">2; class=class="str">"cmt">// MACD水平(以点数计) class="kw">input class="type">int InpMATrendPeriod =class="num">26; class=class="str">"cmt">// MA周期 class=class="str">"cmt">//--- ext 变量 class="type">bool ExtMail; class="type">bool ExtPush; class="type">bool ExtAlert; class="type">class="kw">double ExtLots; class="type">int ExtTakeProfit; class="type">int ExtTrailingStop; class="type">int ExtMACDOpenLevel; class="type">int ExtMACDCloseLevel; class="type">int ExtMATrendPeriod; class=class="str">"cmt">//--- class="type">int ExtTimeOut=class="num">10; class=class="str">"cmt">// 交易操作之间的间隔时间(秒) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| MACD 样例EA类 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA初始化函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit(class="type">void) {
EA 面板对象的参数绑定与初值
这段代码片段来自一个基于 MACD + MA 趋势的 EA 控制面板类,负责把外部输入参数接到内部成员变量上,并在构造时给一套默认交易设置。 先看参数映射部分:外部输入 InpMail、InpPush、InpAlert 等被直接赋给 ExtMail、ExtPush、ExtAlert 一类全局变量,随后通过 ExtExpert.Init() 创建所需图形对象;若初始化失败则不会继续。 类内部用 m 前缀成员变量承接策略参数:mLots 默认 0.1 手,mTakeProfit 默认 50 点,mTrailingStop 默认 30 点,mMACDOpenLevel 与 mMACDCloseLevel 分别为 3 点和 2 点,mMATrendPeriod 控制均线周期。mAlert_ 构造时置 true,mMail、mPush 置 false,意味着默认只弹本地警告不发邮件和推送。 在 MT5 里把这段逻辑塞进自己的面板类时,重点核对点数单位:若品种报价小数位为 5 位,50 点实际对应 0.0050 价格差,黄金等高风险品种点值异于直盘,参数照搬可能让止损过窄被秒扫。
ExtMail=InpMail; ExtPush=InpPush; ExtAlert=InpAlert; ExtLots=InpLots; ExtTakeProfit=InpTakeProfit; ExtTrailingStop=InpTrailingStop; ExtMACDOpenLevel=InpMACDOpenLevel; ExtMACDCloseLevel=InpMACDCloseLevel; ExtMATrendPeriod=InpMATrendPeriod; class=class="str">"cmt">//--- 创建所有需要的对象 if(!ExtExpert.Init()) class="kw">private: class=class="str">"cmt">//--- 元素的GetCheck()函数 class="kw">virtual class="type">int GetCheck(class="kw">const class="type">int idx); class=class="str">"cmt">//--- class="type">bool mMail; class="type">bool mPush; class="type">bool mAlert_; class="type">class="kw">double mLots; class=class="str">"cmt">// 交易量 class="type">int mTakeProfit; class=class="str">"cmt">// 止赢(点数) class="type">int mTrailingStop; class=class="str">"cmt">// 追踪止损水平(点数) class="type">int mMACDOpenLevel; class=class="str">"cmt">// MACD开始水平(点数) class="type">int mMACDCloseLevel; class=class="str">"cmt">//MACD结束水平(点数) class="type">int mMATrendPeriod; class=class="str">"cmt">// MA 周期 class=class="str">"cmt">//--- class="type">bool mModification; class=class="str">"cmt">// 值是否改变 }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 事件处理 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CControlsDialog::CControlsDialog(class="type">void) : mMail(class="kw">false), mPush(class="kw">false), mAlert_(true), mLots(class="num">0.1), mTakeProfit(class="num">50), mTrailingStop(class="num">30), mMACDOpenLevel(class="num">3), mMACDCloseLevel(class="num">2),
◍ EA初始化与通知面板的接线细节
这段代码片段展示了把 MACD 趋势 EA 和图形通知面板接起来的关键初始化逻辑。OnInit 里先把外部 input 参数逐一搬进全局变量,再调用 ExtExpert.Init() 和 ExtDialog.Create(0,"Notification",0,100,100,360,380) 建出 360×380 像素的悬浮对话框。 CreateButtonOK() 失败会直接 return(false),而 SetCheck(0,mMail)、SetCheck(1,mPush)、SetCheck(2,mAlert_) 三行决定了邮件、推送、弹窗三种通知渠道默认勾选状态,改这里就能调面板初始行为。 OnDeinit 用 ExtDialog.Destroy(reason) 释放资源并 Comment("") 清屏,避免残留文字干扰盯盘。外汇与贵金属杠杆高,EA 自动通知不代表信号可靠,实盘前请在 MT5 策略测试器跑通 Init/Deinit 生命周期。 想验证的话,把 InpMail 和 InpPush 设 true 再编译,面板第二项第三项应自动打勾;若 Create 返回 INIT_FAILED,优先查终端是否禁用了 DLL 或对象权限。
mMATrendPeriod(class="num">26), mModification(class="kw">false) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数 if(!CreateButtonOK()) class="kw">return(class="kw">false); class=class="str">"cmt">//--- SetCheck(class="num">0,mMail); SetCheck(class="num">1,mPush); SetCheck(class="num">2,mAlert_); class=class="str">"cmt">//--- 成功 class="kw">return(true); } class="macro">#include <Trade\PositionInfo.mqh> class="macro">#include <Trade\AccountInfo.mqh> class="macro">#include "PanelDialog2Original.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 全局变量 class=class="str">"cmt">//+------------------------------------------------------------------+ CControlsDialog ExtDialog; class=class="str">"cmt">//--- 输入参数 class="kw">input class="type">bool InpMail=class="kw">false; class=class="str">"cmt">//通过email进行通知 class="kw">input class="type">bool InpPush=class="kw">false; class=class="str">"cmt">// 通过短信push通知 class="type">int OnInit(class="type">void) { ExtMail=InpMail; ExtPush=InpPush; ExtAlert=InpAlert; ExtLots=InpLots; ExtTakeProfit=InpTakeProfit; ExtTrailingStop=InpTrailingStop; ExtMACDOpenLevel=InpMACDOpenLevel; ExtMACDCloseLevel=InpMACDCloseLevel; ExtMATrendPeriod=InpMATrendPeriod; class=class="str">"cmt">//--- 创建所有需要的对象 if(!ExtExpert.Init()) class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- 创建程序对话框 if(!ExtDialog.Create(class="num">0,"Notification",class="num">0,class="num">100,class="num">100,class="num">360,class="num">380)) class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- 运行程序 if(!ExtDialog.Run()) class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- 成功 class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//--- 成功 class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA反初始化函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- Comment(""); class=class="str">"cmt">//--- 销毁对话框 ExtDialog.Destroy(reason); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA中处理新报价到来的函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(class="type">void) class=class="str">"cmt">//--- 如果EA执行则改变超时的限制时间(秒) if(ExtExpert.Processing())
「把图表事件接进控件对话框」
EA 面板要能响应鼠标点击和键盘输入,靠的是 OnChartEvent 把 MT5 图表事件转发给自定义对话框类。下面这段代码只做一件事:收到任意图表事件,立刻交给 ExtDialog.ChartEvent 处理,控件上的按钮状态才会实时刷新。 void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { ExtDialog.ChartEvent(id,lparam,dparam,sparam); } 类声明里 OnEvent 是虚函数,意味着子类可以重写事件逻辑;Initialization 则把邮件、推送、警报开关以及 Lots、TakeProfit、TrailingStop、MACDOpenLevel、MACDCloseLevel、MATrendPeriod 这些实盘参数一次性灌进对象。 bool CControlsDialog::Initialization(const bool Mail,const bool Push,const bool Alert_, const double Lots,const int TakeProfit, const int TrailingStop,const int MACDOpenLevel, const int MACDCloseLevel,const int MATrendPeriod) { mMail=Mail; mPush=Push; mAlert_=Alert_; mLots=Lots; mTakeProfit=TakeProfit; mTrailingStop=TrailingStop; 在 MT5 里开个测试 EA,把这组初始化参数接上你自己的面板类,改一行 MACDOpenLevel 从 3 调到 5,回测里触发开仓的次数可能明显减少——外汇和贵金属波动大,这类参数微调只代表概率变化,实盘前务必用策略测试器跑够样本。
limit_time=TimeCurrent()+ExtTimeOut; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { ExtDialog.ChartEvent(id,lparam,dparam,sparam); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">virtual class="type">bool OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam); class=class="str">"cmt">//--- 初始化 class="kw">virtual class="type">bool Initialization(class="kw">const class="type">bool Mail,class="kw">const class="type">bool Push,class="kw">const class="type">bool Alert_, class="kw">const class="type">class="kw">double Lots,class="kw">const class="type">int TakeProfit, class="kw">const class="type">int TrailingStop,class="kw">const class="type">int MACDOpenLevel, class="kw">const class="type">int MACDCloseLevel,class="kw">const class="type">int MATrendPeriod); class="kw">protected: class=class="str">"cmt">//--- 创建独立控件 class="type">bool CreateCheckGroup(class="type">void); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 初始化 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CControlsDialog::Initialization(class="kw">const class="type">bool Mail,class="kw">const class="type">bool Push,class="kw">const class="type">bool Alert_, class="kw">const class="type">class="kw">double Lots,class="kw">const class="type">int TakeProfit, class="kw">const class="type">int TrailingStop,class="kw">const class="type">int MACDOpenLevel, class="kw">const class="type">int MACDCloseLevel,class="kw">const class="type">int MATrendPeriod) { mMail=Mail; mPush=Push; mAlert_=Alert_; mLots=Lots; mTakeProfit=TakeProfit; mTrailingStop=TrailingStop;