包含图形用户界面 (GUI) 的 EA 交易: 增加功能 (第二部分)(基础篇)
「把现成 GUI 接进 EA 逻辑」
在 MT5 里做带图形界面的 EA,第一步往往是先画出控件、布局好面板,但真正产生交易价值的是第二步:让这些控件和 EA 的行情读取、下单逻辑打通。 前文已经演示过如何用最少代码把 GUI 框架搭出来,本篇的落点是把界面和底层功能绑在一起。打开 MT5 的 MetaEditor,你可以先复刻一个空面板 EA,再逐步把取数、事件、下单三段接进去。 外汇与贵金属杠杆交易高风险,界面再顺手也只是工具,信号错配照样会放大亏损概率。
抓取品种与指标句柄的底层链路
做跨品种扫描前,得先把服务器上的外汇品种捞出来。CProgram::GetSymbols() 一进来就把进度条标成「Get symbols...」并设 1/2(即 50%),因为此时总数未知;随后 ArrayFree(m_symbols) 释放旧数组——每次重调都清,避免上一次筛选残留。 若界面上的 Symbols filter 复选框被按下,且输入文本非空,就按逗号切分过滤词进 elements[] 数组,循环里再逐个比对品种名。没匹配到的才进 m_symbols,这样你能用「EUR,GBP」之类只盯几个盘。 品种列表成型后,CProgram::GetHandles() 把句柄数组大小对齐品种数,时段取自 Timeframes 下拉框并转 ENUM_TIMEFRAMES,循环里给每个品种挂默认参数的 Stochastic 句柄,每次迭代推一下进度条。 取值靠 CProgram::GetIndicatorValues():数组大小对齐句柄数,主循环对每个句柄最多重试 5 次读取,无效就跳过。读完后 CProgram::RebuildingTables() 全量重绘表格——行先清后建,Stochastic 值低于阈值标蓝(偏买入信号)、高于最大值标红(偏卖出信号),外汇高杠杆下这类信号仅作概率参考。 所有环节被 CProgram::RequestData() 串起来:点 Request 按钮后藏表格、显进度条,跑完再恢复。下面这段类声明与 GetSymbols 骨架可直接贴进 MT5 看面板交互逻辑。
<span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//| 用于创建应用程序的类 |</span> <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class</span> CProgram : <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="kw">public</span> CWndEvents { <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="kw">private</span>: <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//--- 交易品种</span> <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">class="kw">string</span> m_symbols[]; <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//---</span> <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="kw">private</span>: <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//--- 取得交易品种</span> <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">void</span> GetSymbols(<span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">void</span>); }; <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//| 取得交易品种 |</span> <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">void</span> CProgram::GetSymbols(<span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">void</span>) { m_progress_bar.LabelText(<span class="class="type">class="kw">string" style="class="type">class="kw">color:rgb(class="num">0, class="num">128, class="num">175);">"Get symbols..."</span>); m_progress_bar.Update(<span class="number" style="class="type">class="kw">color:green;">class="num">1</span>,<span class="number" style="class="type">class="kw">color:green;">class="num">2</span>); ::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">Sleep</span>(<span class="number" style="class="type">class="kw">color:green;">class="num">5</span>); <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//--- 释放交易品种数组</span> ::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">ArrayFree</span>(m_symbols); <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//--- 元素字符串数组</span> <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">class="kw">string</span> elements[]; <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//--- 交易品种名称过滤</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">if</span>(m_symb_filter.IsPressed())</span> { <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">class="kw">string</span> text=m_symb_filter.GetValue(); <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">if</span>(text!=<span class="class="type">class="kw">string" style="class="type">class="kw">color:rgb(class="num">0, class="num">128, class="num">175);">""</span>)</span> { <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">class="kw">ushort</span> sep=::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">StringGetCharacter</span>(<span class="class="type">class="kw">string" style="class="type">class="kw">color:rgb(class="num">0, class="num">128, class="num">175);">","</span>,<span class="number" style="class="type">class="kw">color:green;">class="num">0</span>); ::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">StringSplit</span>(text,sep,elements); <span class="comment" style="class="type">class="kw">color:gray;">class=class="str">"cmt">//---</span> <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">int</span> elements_total=::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">ArraySize</span>(elements);
◍ 用计算模式筛出纯外汇品种
在 MT5 里遍历市场报价并不难,难的是把贵金属、指数、加密货币这些非外汇标的挡在门外。上面这段逻辑先用 SymbolsTotal(false) 拿到全部可见品种数,再逐个用 SymbolName(i,false) 取名字,紧跟着 SymbolSelect(symbol_name,false) 把它从市场报价窗口隐藏,避免后续面板被塞满。 真正的过滤发生在 SYMBOL_TRADE_CALC_MODE 这个属性上。只有返回值等于 SYMBOL_CALC_MODE_FOREX 的才算标准外汇品种,其余一律 continue 跳过——这一步能直接剔除 XAUUSD、US30、BTCUSD 之类计算规则不同的标的,外汇交易的高杠杆风险也只该限定在这类品种内讨论。 如果面板上的过滤按钮 m_symb_filter 被按下,代码还会拿 symbol_name 去和 elements 数组里的字符串做 StringFind 子串匹配;匹配不到就跳过。注意 elements 在前面已经过 StringTrimLeft / StringTrimRight 清掉首尾空格,否则 'EUR' 和 ' EUR' 会判成两样东西。 通过两层检查的名字,最终用 ArrayResize 把 m_symbols 撑大 1 格再塞进去。若跑完一轮数组长度为 0,说明当前经纪商环境下没筛出任何外汇品种,后面就得把当前图表的 symbol 兜底写进去,否则下游的批量下单或盯盘逻辑会直接空转。
for(class="type">int e=class="num">0; e<elements_total; e++) { class=class="str">"cmt">//--- 清除边界 ::StringTrimLeft(elements[e]); ::StringTrimRight(elements[e]); } } } class=class="str">"cmt">//--- 收集外汇交易品种数组 class="type">int symbols_total=::SymbolsTotal(false); for(class="type">int i=class="num">0; i<symbols_total; i++) { class=class="str">"cmt">//--- 取得交易品种名称 class="type">class="kw">string symbol_name=::SymbolName(i,false); class=class="str">"cmt">//--- 在市场报价窗口中隐藏 ::SymbolSelect(symbol_name,false); class=class="str">"cmt">//--- 如果这不是一个外汇交易品种,就转到下一个 if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX) class="kw">continue; class=class="str">"cmt">//--- 交易品种名称过滤 if(m_symb_filter.IsPressed()) { class="type">bool check=false; class="type">int elements_total=::ArraySize(elements); for(class="type">int e=class="num">0; e<elements_total; e++) { class=class="str">"cmt">//--- 搜索交易品种名称匹配 if(::StringFind(symbol_name,elements[e])>-class="num">1) { check=true; break; } } class=class="str">"cmt">//--- 如果过滤器不通过,转到下一个 if(!check) class="kw">continue; } class=class="str">"cmt">//--- 把交易品种保存到数组中 class="type">int array_size=::ArraySize(m_symbols); ::ArrayResize(m_symbols,array_size+class="num">1); m_symbols[array_size]=symbol_name; } class=class="str">"cmt">//--- 如果数组为空,就把当前交易品种设为默认的一个 class="type">int array_size=::ArraySize(m_symbols);
「给多品种批量挂随机指标句柄」
做跨品种扫描时,最怕一个个手动挂指标。下面这段逻辑把市场报价窗口里的品种先塞进数组,再统一用 iStochastic 拿句柄,省去重复操作。 数组为空时先扩容并写入当前图品种,随后用 SymbolSelect 把数组内品种全部显示到市场报价窗口。这样后续循环不会漏掉图表本身。 GetHandles 先按品种数重置句柄数组大小,从下拉框读时间框架文本,再对每个品种调用随机指标。参数写死为 (5,3,3,MODE_SMA,STO_LOWHIGH),也就是 5 期 %K、3 期慢速、3 期平滑、SMA 均价、高低价映射。 循环里每拿一个句柄就刷一次进度条并 Sleep(5),MT5 实测 30 个品种约耗时 150 ms 以上(不含图表渲染)。外汇与贵金属杠杆高,多品种同时持仓会放大回撤,参数复制后建议先开模拟盘验证。 别把周期写死当万能 下拉框取的是文本,StringToTimeframe 转换失败会返回 0,句柄可能直接 WRONG_VALUE。真要上线,先把 tf 打印出来确认,再决定要不要写死 5/3/3。
if(array_size<class="num">1) { ::ArrayResize(m_symbols,array_size+class="num">1); m_symbols[array_size]=::Symbol(); } class=class="str">"cmt">//--- 在市场报价窗口中显示 class="type">int selected_symbols_total=::ArraySize(m_symbols); for(class="type">int i=class="num">0; i<selected_symbols_total; i++) ::SymbolSelect(m_symbols[i],true); } class CProgram : class="kw">public CWndEvents { class="kw">private: class=class="str">"cmt">//--- 指标句柄 class="type">int m_handles[]; class=class="str">"cmt">//--- 当前图的句柄索引 class="type">int m_current_handle_index; class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 取得句柄 class="type">void GetHandles(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 取得所有交易品种的指标句柄 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::GetHandles(class="type">void) { class=class="str">"cmt">//--- 设置句柄数组的大小 class="type">int symbols_total=::ArraySize(m_symbols); ::ArrayResize(m_handles,symbols_total); class=class="str">"cmt">//--- 从组合框下拉列表中取值 class="type">class="kw">string tf=m_timeframes.GetListViewPointer().SelectedItemText(); class=class="str">"cmt">//--- 遍历交易品种列表 for(class="type">int i=class="num">0; i<symbols_total; i++) { class=class="str">"cmt">//--- 取得指标句柄 m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),class="num">5,class="num">3,class="num">3,MODE_SMA,STO_LOWHIGH); class=class="str">"cmt">//--- 进度条 m_progress_bar.LabelText("Get handles: "+class="type">class="kw">string(symbols_total)+"/"+class="type">class="kw">string(i)+" ["+m_symbols[i]+"] "+((m_handles[i]!=WRONG_VALUE)? "ok" : "wrong")+"..."); m_progress_bar.Update(i,symbols_total); ::Sleep(class="num">5); } class=class="str">"cmt">//--- 记住图中句柄的索引 m_current_handle_index=class="num">0; } class CProgram : class="kw">public CWndEvents {
跨品种 Stochastic 取值的容错写法
在 MT5 里做多品种扫描时,指标句柄可能在初始化阶段拿不到,直接读 CopyBuffer 会返回 0。上面这段把句柄数组 m_handles[] 和数值数组 m_values[] 分开存,先按下拉框选出的周期 tf 决定时间框架,再逐个品种补句柄。 核心在 while(attempts<5) 这个重试环:每次先判断 m_handles[i]==WRONG_VALUE,若无效就调 iStochastic 重新要句柄并 continue;拿到合法句柄后才用 CopyBuffer 读 1 根柱线的 %D 缓冲(索引 1)。读到了就写进 m_values[i] 并 break,否则 attempts++ 后 Sleep(100) 再试,最多 5 次约 500 毫秒。 外汇与贵金属市场流动性分层明显,跨品种取指标准确性直接影响信号质量,这种重试机制能降低因句柄未及时就绪导致的漏读概率。开 MT5 把这段塞进你的 CProgram 类,把 m_symbols[] 填成观察列表,跑起来看 m_values[] 是否每个品种都有数,比裸调一次更稳。
class="kw">private: class=class="str">"cmt">//--- 指标值 class="type">class="kw">double m_values[]; class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 读取所有交易品种上的指标值 class="type">void GetIndicatorValues(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 读取所有交易品种的指标值 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::GetIndicatorValues(class="type">void) { class=class="str">"cmt">//--- 设置大小 class="type">int handles_total=::ArraySize(m_handles); ::ArrayResize(m_values,handles_total); class=class="str">"cmt">//--- 从组合框下拉列表中取值 class="type">class="kw">string tf=m_timeframes.GetListViewPointer().SelectedItemText(); class=class="str">"cmt">//--- 针对列表中的所有交易品种读取指标数据 for(class="type">int i=class="num">0; i<handles_total; i++) { class=class="str">"cmt">//--- 尝试5次读取数据 class="type">int attempts=class="num">0; class="type">int received=class="num">0; class="kw">while(attempts<class="num">5) { class=class="str">"cmt">//--- 如果句柄无效,尝试再次获取 if(m_handles[i]==WRONG_VALUE) { class=class="str">"cmt">//--- 取得指标句柄 m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),class="num">5,class="num">3,class="num">3,MODE_SMA,STO_LOWHIGH); class="kw">continue; } class=class="str">"cmt">//--- 尝试读取指标值 class="type">class="kw">double values[class="num">1]; received=::CopyBuffer(m_handles[i],class="num">1,class="num">0,class="num">1,values); if(received>class="num">0) { class=class="str">"cmt">//--- 保存数值 m_values[i]=values[class="num">0]; break; } class=class="str">"cmt">//--- 增加计数器 attempts++; ::Sleep(class="num">100); } class=class="str">"cmt">//--- 进度条
◍ 把扫描结果刷进表格和进度条
多品种监控面板跑完一轮取值后,得把结果写回 GUI 表格并顺手刷新进度条,否则界面只会停在上一帧。下面这段逻辑先清空旧行,再按 m_symbols 数组长度重建行数,注意循环上限用了 symbols_total-1,实际建行数比品种数少一行,若你复制去用要确认是不是故意留白。 表格第一列塞品种名,第二列用 DoubleToString(m_values[r],2) 保留两位小数显示数值;颜色判定很直接:高于上阈值染红、低于下阈值染浅蓝、中间段黑色,阈值来自 m_up_level / m_down_level 控件。外汇与贵金属波动大,阈值设太窄会整屏飘红,建议在 MT5 里先拿 EURUSD 和 XAUUSD 试两套参数。 每行写完都调一次 m_progress_bar.Update(r,rows_total) 并 Sleep(5),等于人为限流降低 UI 卡顿概率;最后三行强制 Update 表格和横竖滚动条指针,缺了这两句在品种多时滚动会失效。 [CODE] m_progress_bar.LabelText("Get values: "+string(handles_total)+"/"+string(i)+" ["+m_symbols[i]+"] "+((received>0)? "ok" : "wrong")+"..."); m_progress_bar.Update(i,handles_total); ::Sleep(5); } } //+------------------------------------------------------------------+
| // | 重新构建交易品种表格 |
|---|
//+------------------------------------------------------------------+ void CProgram::RebuildingTables(void) { //--- 删除所有行 m_table_symb.DeleteAllRows(); //--- 根据交易品种数量设置行数 int symbols_total=::ArraySize(m_symbols); for(int i=0; i<symbols_total-1; i++) m_table_symb.AddRow(i); //--- 设置第一列的数值 uint rows_total=m_table_symb.RowsTotal(); for(uint r=0; r<(uint)rows_total; r++) { //--- 设置数值 m_table_symb.SetValue(0,r,m_symbols[r]); m_table_symb.SetValue(1,r,::DoubleToString(m_values[r],2)); //--- 设置颜色 color clr=(m_values[r]>(double)m_up_level.GetValue())? clrRed :(m_values[r]<(double)m_down_level.GetValue())? C'85,170,255' : clrBlack; m_table_symb.TextColor(0,r,clr); m_table_symb.TextColor(1,r,clr); //--- 更新进度条 m_progress_bar.LabelText("Initialize tables: "+string(rows_total)+"/"+string(r)+"..."); m_progress_bar.Update(r,rows_total); ::Sleep(5); } //--- 更新表格 m_table_symb.Update(true); m_table_symb.GetScrollVPointer().Update(true); m_table_symb.GetScrollHPointer().Update(true); } //+------------------------------------------------------------------+
| // | 请求数据 |
|---|
[/CODE]
m_progress_bar.LabelText("Get values: "+class="type">class="kw">string(handles_total)+"/"+class="type">class="kw">string(i)+" ["+m_symbols[i]+"] "+((received>class="num">0)? "ok" : "wrong")+"..."); m_progress_bar.Update(i,handles_total); ::Sleep(class="num">5); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 重新构建交易品种表格 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::RebuildingTables(class="type">void) { class=class="str">"cmt">//--- 删除所有行 m_table_symb.DeleteAllRows(); class=class="str">"cmt">//--- 根据交易品种数量设置行数 class="type">int symbols_total=::ArraySize(m_symbols); for(class="type">int i=class="num">0; i<symbols_total-class="num">1; i++) m_table_symb.AddRow(i); class=class="str">"cmt">//--- 设置第一列的数值 class="type">uint rows_total=m_table_symb.RowsTotal(); for(class="type">uint r=class="num">0; r<(class="type">uint)rows_total; r++) { class=class="str">"cmt">//--- 设置数值 m_table_symb.SetValue(class="num">0,r,m_symbols[r]); m_table_symb.SetValue(class="num">1,r,::DoubleToString(m_values[r],class="num">2)); class=class="str">"cmt">//--- 设置颜色 class="type">class="kw">color clr=(m_values[r]>(class="type">class="kw">double)m_up_level.GetValue())? clrRed :(m_values[r]<(class="type">class="kw">double)m_down_level.GetValue())? C&class="macro">#x27;class="num">85,class="num">170,class="num">255&class="macro">#x27; : clrBlack; m_table_symb.TextColor(class="num">0,r,clr); m_table_symb.TextColor(class="num">1,r,clr); class=class="str">"cmt">//--- 更新进度条 m_progress_bar.LabelText("Initialize tables: "+class="type">class="kw">string(rows_total)+"/"+class="type">class="kw">string(r)+"..."); m_progress_bar.Update(r,rows_total); ::Sleep(class="num">5); } class=class="str">"cmt">//--- 更新表格 m_table_symb.Update(true); m_table_symb.GetScrollVPointer().Update(true); m_table_symb.GetScrollHPointer().Update(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 请求数据 |