包含图形用户界面 (GUI) 的 EA 交易: 创建面板 (第一部分)·综合运用
「用组合框切换交易时段」
在 MT5 自定义面板里切周期,最顺手的做法是放一个带下拉列表的组合框,而不是挨个按钮去点。这里把元件绑到页面右缘,整体宽 115 像素,按钮占右边 50 像素,列表塞进 21 个时段项,和终端里能选的 M1 到 MN 一一对应。 代码里用 ITEMS_TOTAL2 宏写死 21,随后把 "M1"…"MN" 字符串循环写进 SetValue。注意 lv.SelectItem(18) 这行——索引 18 指向 "D1",也就是面板起来时默认高亮日线,省得每次手动选。 鼠标掠过列表项会高亮,靠 lv.LightsHover(true) 开启。外汇和贵金属波动大、杠杆高,切到小周期盯盘时滑点风险会明显上升,参数按自己屏幕调,别照抄就以为万能。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建用于生成时段的组合框 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateComboBoxTF(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text) { class=class="str">"cmt">//--- 列表项目总数 class="macro">#define ITEMS_TOTAL2 class="num">21 class=class="str">"cmt">//--- 传递面板对象 m_timeframes.MainPointer(m_tabs1); class=class="str">"cmt">//--- 锚点设向页面 m_tabs1.AddToElementsArray(class="num">0,m_timeframes); class=class="str">"cmt">//--- 属性 m_timeframes.XSize(class="num">115); m_timeframes.ItemsTotal(ITEMS_TOTAL2); m_timeframes.AnchorRightWindowSide(true); m_timeframes.GetButtonPointer().XSize(class="num">50); m_timeframes.GetButtonPointer().AnchorRightWindowSide(true); class=class="str">"cmt">//--- 把项目值保存到组合框列表 class="type">class="kw">string items_text[ITEMS_TOTAL2]={"M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30","H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"}; for(class="type">int i=class="num">0; i<ITEMS_TOTAL2; i++) m_timeframes.SetValue(i,items_text[i]); class=class="str">"cmt">//--- 取得列表指针 CListView *lv=m_timeframes.GetListViewPointer(); class=class="str">"cmt">//--- 设置列表属性 lv.LightsHover(true); lv.SelectItem(class="num">18); class=class="str">"cmt">//--- 创建控件 if(!m_timeframes.CreateComboBox(text,x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 把元件指针加到数据库中 CWndContainer::AddToElementsArray(class="num">0,m_timeframes); class="kw">return(true); }
◍ 给时间轴复选框设宽并默认打勾
复选框是 GUI 里最轻量的元件,建出来只要管两件事:宽度,以及它贴在主元件右侧的位置。 上面这段 CProgram::CreateDateScale 把“Date scale”复选框做进标签页第 0 页。先记窗口指针、塞进 m_tabs1 的第 0 组元素数组,再把宽度写死成 70 像素、AnchorRightWindowSide 设 true 让它吸右边缘。 核心在 CreateCheckBox 之后的 IsPressed(true)——这一行直接把复选框置为已选中状态,省去用户手动点的第一步。回测或实盘面板里,若你希望“显示日期刻度”默认开,就这么写;若默认关,传 false 即可。 外汇与贵金属图表插件改这类界面时注意:GUI 逻辑错误不会爆仓,但误设默认态可能让你漏看关键时间维度,属于隐性高风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建 "Date scale" 复选框 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateDateScale(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text) { class=class="str">"cmt">//--- 保存窗口指针 m_date_scale.MainPointer(m_tabs1); class=class="str">"cmt">//---为页面做保留 m_tabs1.AddToElementsArray(class="num">0,m_date_scale); class=class="str">"cmt">//--- 属性 m_date_scale.XSize(class="num">70); m_date_scale.AnchorRightWindowSide(true); class=class="str">"cmt">//--- 创建控件 if(!m_date_scale.CreateCheckBox(text,x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 启用复选框 m_date_scale.IsPressed(true); class=class="str">"cmt">//--- 把对象加到对象组的统一数组中 CWndContainer::AddToElementsArray(class="num">0,m_date_scale); class="kw">return(true); }
MT5 GUI 里两个表格的初始化套路
面板第一页放的是交易品种列表与信号表,第二页是持仓明细表。前者在 CreateSymbolsTable 里写死 2 列 1 行,列宽数组给的是 {95, 58} 像素,左列左对齐、右列右对齐,文字统一缩进 5 像素。 第二页的持仓表有 10 列,覆盖品种、手数、总持仓量、多空分别的量与浮盈、品种占用保证金、中间价等。它比第一页多开了排序开关 IsSortMode(true) 和水平自动伸缩 AutoXResizeMode(true),右侧留 2 像素偏移,方便在窄窗口下横向吃满。 第一列的图片单元实际是个按钮:点一下平掉对应品种单仓,若账户是对冲模式则一键平掉该品种全部仓位。外汇与贵金属杠杆高,这类一键平仓按钮误触代价大,实盘前建议在策略测试器里先验证点击逻辑。 下面这段是品种表的建表代码,逐行看属性怎么落地的。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建交易品种表格 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateSymbolsTable(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap) { class="macro">#define COLUMNS1_TOTAL class="num">2 class="macro">#define ROWS1_TOTAL class="num">1 class=class="str">"cmt">//--- 保存指向主元件的指针 m_table_symb.MainPointer(m_tabs1); class=class="str">"cmt">//---为页面做保留 m_tabs1.AddToElementsArray(class="num">0,m_table_symb); class=class="str">"cmt">//--- 列的宽度数组 class="type">int width[COLUMNS1_TOTAL]={class="num">95,class="num">58}; class=class="str">"cmt">//--- 列中文字对齐方式的数组 ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]={ALIGN_LEFT,ALIGN_RIGHT}; class=class="str">"cmt">//--- X轴方向上文字缩进数组 class="type">int text_x_offset[COLUMNS1_TOTAL]={class="num">5,class="num">5}; class=class="str">"cmt">//--- 属性 m_table_symb.XSize(class="num">168); m_table_symb.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_table_symb.ColumnsWidth(width); m_table_symb.TextAlign(align); m_table_symb.TextXOffset(text_x_offset); m_table_symb.ShowHeaders(true); m_table_symb.SelectableRow(true); m_table_symb.ColumnResizeMode(true); m_table_symb.IsZebraFormatRows(clrWhiteSmoke); m_table_symb.AutoYResizeMode(true); m_table_symb.AutoYResizeBottomOffset(class="num">2); class=class="str">"cmt">//--- 创建控件 if(!m_table_symb.CreateTable(x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 设置表头名称 m_table_symb.SetHeaderText(class="num">0,"Symbol"); m_table_symb.SetHeaderText(class="num">1,"Values"); class=class="str">"cmt">//--- 把对象加到对象组的统一数组中 CWndContainer::AddToElementsArray(class="num">0,m_table_symb); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建仓位表格 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreatePositionsTable(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap) { ... class=class="str">"cmt">//--- 属性 m_table_positions.TableSize(COLUMNS2_TOTAL,ROWS2_TOTAL); m_table_positions.ColumnsWidth(width); m_table_positions.TextAlign(align); m_table_positions.TextXOffset(text_x_offset); m_table_positions.ImageXOffset(image_x_offset); m_table_positions.ImageYOffset(image_y_offset); m_table_positions.ShowHeaders(true); m_table_positions.IsSortMode(true); m_table_positions.SelectableRow(true); m_table_positions.ColumnResizeMode(true); m_table_positions.IsZebraFormatRows(clrWhiteSmoke); m_table_positions.AutoXResizeMode(true); m_table_positions.AutoYResizeMode(true); m_table_positions.AutoXResizeRightOffset(class="num">2); m_table_positions.AutoYResizeBottomOffset(class="num">2); ... class="kw">return(true); }
「用 CStandardChart 挂出 EURUSD D1 主图」
在 MT5 自定义面板里做行情可视化,CStandardChart 是最直接的一块画布。它默认就把 EURUSD 的日线丢上去,省掉手动指定品种的麻烦,同时自带横向滚动和宽高自适应,不用自己算坐标。 实际写的时候,靠 AddSubChart("EURUSD", PERIOD_D1) 把图表塞进元件。如果以后想并排看多个周期,循环调 AddSubChart 传不同参数即可;本例只挂一个,品种和周期交给别的控件切。 右侧留白和底部缩进是两个容易忽略的点:代码里 AutoXResizeRightOffset 设成 125、AutoYResizeBottomOffset 设成 2,意味着图表自动拉伸时右边空 125 像素、底边空 2 像素,给滚动条或标签留位。外汇与贵金属波动剧烈、杠杆高风险,面板只是看盘辅助,下单前仍需独立确认。 下面这段是创建主图的核心函数,逐行拆一下关键调用。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建标准图表 class="num">1 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateSubChart1(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap) { class=class="str">"cmt">//--- 保存窗口指针 m_sub_chart1.MainPointer(m_tabs1); class=class="str">"cmt">//--- 为第一个页面保留 m_tabs1.AddToElementsArray(class="num">0,m_sub_chart1); class=class="str">"cmt">//--- 属性 m_sub_chart1.XScrollMode(true); m_sub_chart1.AutoXResizeMode(true); m_sub_chart1.AutoYResizeMode(true); m_sub_chart1.AutoXResizeRightOffset(class="num">125); m_sub_chart1.AutoYResizeBottomOffset(class="num">2); class=class="str">"cmt">//--- 加上图表 m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1); class=class="str">"cmt">//--- 创建控件 if(!m_sub_chart1.CreateStandardChart(x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 把对象加到对象组的通用数组中 CWndContainer::AddToElementsArray(class="num">0,m_sub_chart1); class="kw">return(true); }
◍ 给EA面板加个进度条控件
在 MT5 的 EA 图形界面里,进度条不是装饰,而是让用户知道后台在跑什么任务的必要反馈。把它挂到状态栏上,用户至少能看出加载、计算或下单流程卡在哪一步。 进度条对象有一组固定属性需要提前设定:总高度、指示条线宽、横纵缩进、主文字与百分比标签的缩进、是否随下拉隐藏、字体、边框与背景色、进度线颜色,以及是否自动右对齐调节。下面这段实例把总高度设为 17、指示条高 14、纵向偏移 1,主文字左缩进 5、上缩进 2,百分比标签同样缩进 5 和 2,字体用 Consolas,边框银色、背景白烟色、进度线浅绿。 别把正态当圣经:这些像素值只是示例里的硬参数,你换高分屏或不同 DPI 的终端时,可能要重调 YSize 和 Gap 才能不挤在一起。外汇与贵金属 EA 界面卡死往往无声,进度条能帮你早点嗅到异常。 代码里先保存主元件指针,再挨个写属性,最后调 CreateProgressBar 生成并塞进元素数组。跑通后这只是一层图形壳,下一步才是写方法让逻辑真按设计走。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建进度条 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateProgressBar(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text) { class=class="str">"cmt">//--- 保存指向主元件的指针 m_progress_bar.MainPointer(m_status_bar); class=class="str">"cmt">//--- 属性 m_progress_bar.YSize(class="num">17); m_progress_bar.BarYSize(class="num">14); m_progress_bar.BarXGap(class="num">0); m_progress_bar.BarYGap(class="num">1); m_progress_bar.LabelXGap(class="num">5); m_progress_bar.LabelYGap(class="num">2); m_progress_bar.PercentXGap(class="num">5); m_progress_bar.PercentYGap(class="num">2); m_progress_bar.IsDropdown(true); m_progress_bar.Font("Consolas"); m_progress_bar.BorderColor(clrSilver); m_progress_bar.IndicatorBackColor(clrWhiteSmoke); m_progress_bar.IndicatorColor(clrLightGreen); m_progress_bar.AutoXResizeMode(true); m_progress_bar.AutoXResizeRightOffset(class="num">2); class=class="str">"cmt">//--- 创建元件 if(!m_progress_bar.CreateProgressBar(text,x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 把元件指针加到数据库中 CWndContainer::AddToElementsArray(class="num">0,m_progress_bar); class="kw">return(true); }
表格排序方向现在能手动锁了
EasyAndFast 库里 CTable 的 SortData() 改了签名:第二个参数 direction 变成可选,传 WRONG_VALUE 时仍按旧逻辑(首次升序、之后反转),传 SORT_ASCEND / SORT_DESCEND 就强制指定方向,不再被当前状态绑架。 同时新增两个只读方法 IsSortDirection() 和 IsSortedColumnIndex(),分别吐出 m_last_sort_direction 与 m_is_sorted_column_index。实战价值在于:用户手动点表头排过序、而后台数据刷新没带相同顺序时,你能先读这两个值,再在重绘后原样恢复,避免界面跳序。 CKeys::KeySymbol() 也补了数字键盘区(右侧独立小键盘)的识别,以前那一块按键直接漏掉,现在能正常录入数字和符号。 下面这段是类声明与排序实现的节选,黄色高亮处就是本次改动核心:可选 direction 参数、方向判定分支、以及两个取值器。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建绘制表格的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CTable : class="kw">public CElement { class="kw">public: ... class=class="str">"cmt">//--- 根据指定列排序数据 class="type">void SortData(class="kw">const class="type">uint column_index=class="num">0,class="kw">const class="type">int direction=WRONG_VALUE); class=class="str">"cmt">//--- (class="num">1) 当前排序方向, (class="num">2) 排序数组索引 class="type">int IsSortDirection(class="type">void) class="kw">const { class="kw">return(m_last_sort_direction); } class="type">int IsSortedColumnIndex(class="type">void) class="kw">const { class="kw">return(m_is_sorted_column_index); } ... }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 根据指定列排序数据 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTable::SortData(class="kw">const class="type">uint column_index=class="num">0,class="kw">const class="type">int direction=WRONG_VALUE) { class=class="str">"cmt">//--- 如果超出表格边界就退出 if(column_index>=m_columns_total) class="kw">return; class=class="str">"cmt">//--- 开始排序的索引 class="type">uint first_index=class="num">0; class=class="str">"cmt">//--- 最后的索引 class="type">uint last_index=m_rows_total-class="num">1; class=class="str">"cmt">//--- 方向不是由用户管理的 if(direction==WRONG_VALUE) { class=class="str">"cmt">//--- 第一次,根据升序排序,随后每次按照相反方向排序 if(m_is_sorted_column_index==WRONG_VALUE || column_index!=m_is_sorted_column_index || m_last_sort_direction==SORT_DESCEND) m_last_sort_direction=SORT_ASCEND; else m_last_sort_direction=SORT_DESCEND; } else { m_last_sort_direction=(ENUM_SORT_MODE)direction; } class=class="str">"cmt">//--- 记住最后一次排序的列的索引
「键盘码到字符的映射落点」
在自定义控件里捕获按键后,最麻烦的是把 MT5 的虚拟键码还原成可读字符。上面这段 CKeys::KeySymbol 给出了一个直接可用的解法:空格单独判 KEY_SPACE 返回单空格,其余走字母、数字、小键盘和标点区间的批量映射。 小键盘区容易漏。代码里特意把 KEY_NUMLOCK_0 到 KEY_NUMLOCK_SLASH 这段纳进条件,意味着 NumLock 开启时按下的 0-9 和 / 都能正确转成符号;若你只判断 KEY_0~KEY_9,实体键盘数字能出字,右侧小键盘大概率返回空串。 转换核心靠两行:TranslateKey 把键码翻成系统字符短整型,ShortToString 再拼成 string。开 MT5 新建脚本调一次 KeySymbol(KEY_NUMLOCK_0),若返回 "0" 说明映射通了;返回空就检查终端版本是否过旧。外汇与贵金属键盘快捷面板开发属高频交互,实盘误触可能引发下单,务必在策略测试器先跑通再挂真账。
m_is_sorted_column_index=(class="type">int)column_index; class=class="str">"cmt">//--- Sorting QuickSort(first_index,last_index,column_index,m_last_sort_direction); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回按下的按钮字符 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CKeys::KeySymbol(class="kw">const class="type">long key_code) { class="type">class="kw">string key_symbol=""; class=class="str">"cmt">//--- 如果需要空格 (空格键) if(key_code==KEY_SPACE) { key_symbol=" "; } class=class="str">"cmt">//--- 如果需要 (class="num">1)一个字母字符或者 (class="num">2) 数字字符 或者 (class="num">3) 特殊字符 else if((key_code>=KEY_A && key_code<=KEY_Z) || (key_code>=KEY_0 && key_code<=KEY_9) || (key_code>=KEY_NUMLOCK_0 && key_code<=KEY_NUMLOCK_SLASH) || (key_code>=KEY_SEMICOLON && key_code<=KEY_SINGLE_QUOTE)) { key_symbol=::ShortToString(::TranslateKey((class="type">int)key_code)); } class=class="str">"cmt">//--- 返回字符 class="kw">return(key_symbol); }
◍ 接下去能直接拿来跑的文件
这一篇只搭了带 GUI 的人工交易面板骨架,真正让按钮干活的逻辑留到第二部分再填。现在你能做的,是把下面这套文件丢进 MT5 对应目录,先跑通界面本身。 MQL5\Experts\TradePanel\TradePanel.mq5 是挂图用的 EA 主体;Program.mqh 管程序类,CreateGUI.mqh 负责把类画成面板;Include\EasyAndFastGUI 下的 Table.mqh 和 Keys.mqh 是改过的控件基类。压缩包 MQL5.zip 约 48 KB,下载后解到终端数据目录即可。 外汇与贵金属带杠杆,GUI 再顺手也只是下单通道,仓位错了一样爆得快,先用模拟盘验流程。第二部分会讲怎么让面板真的发出交易指令,那时候再决定要不要上实盘。
deprecated behavior, hidden method calling will be disabled <span class="keyword">in</span> a future MQL compiler version