包含图形用户界面 (GUI) 的 EA 交易: 增加功能 (第二部分)·进阶篇
🖥️

包含图形用户界面 (GUI) 的 EA 交易: 增加功能 (第二部分)·进阶篇

(2/3)· 界面画好了却还是死板的摆设?本篇教你让表格随品种、指标和持仓自动呼吸

进阶 第 2/3 篇
很多交易者把 GUI 搭出来就以为 EA 完工了,结果面板里的数字从头到尾不动,和图表完全脱节。其实界面只是壳,真正值钱的是把控件绑到品种、指标和持仓的实时流上。这一篇接着把壳接通血管。

数据请求里的界面与图表联动

在 MT5 面板程序里,点一次刷新按钮背后往往不是单纯拉数据,而是先切界面状态再跑计算。上面这段 CProgram::RequestData 就是典型:先比对传入 id 和内部请求对象 Id(),不一致直接返回 false,避免误触发。 接着把品种表格 Hide()、进度条 Show() 并 Redraw(),给用户一个“正在算”的视觉反馈;随后连续调用 GetSymbols、GetHandles、GetIndicatorValues、RebuildingTables 完成数据抓取与表格重建,最后隐藏进度条。 从下拉框取时间帧文本后,用 StringToTimeframe 转成枚举再赋给子图 Period,并 ResetCharts 重绘。外汇与贵金属波动大,这种先隐后显的逻辑能防止半截表格闪给用户看,自己开 MT5 挂个 EA 断点跟一遍就知道卡在哪一步。

MQL5 / C++
class="type">bool CProgram::RequestData(const class="type">long id)
  {
class=class="str">"cmt">//--- 检查元件 ID
   if(id!=m_request.Id())
      class="kw">return(false);
class=class="str">"cmt">//--- 隐藏表格
   m_table_symb.Hide();
class=class="str">"cmt">//--- 显示进度
   m_progress_bar.Show();
   m_chart.Redraw();
class=class="str">"cmt">//--- 初始化图形和表格
   GetSymbols();
   GetHandles();
   GetIndicatorValues();
   RebuildingTables();
class=class="str">"cmt">//--- 隐藏进度条
   m_progress_bar.Hide();
class=class="str">"cmt">//--- 从组合框下拉列表中取值
   class="type">class="kw">string tf=m_timeframes.GetListViewPointer().SelectedItemText();
class=class="str">"cmt">//--- 根据索引取得图的指针
   m_sub_chart1.GetSubChartPointer(class="num">0).Period(StringToTimeframe(tf));
   m_sub_chart1.ResetCharts();
class=class="str">"cmt">//--- 显示表格
   m_table_symb.Show();
   m_chart.Redraw();
   class="kw">return(true);
  }

◍ EA 加载时如何抓取持仓并填充表格

EA 挂上图表那一刻,就得把当前所有持仓捞出来填进 Toolbox 的 Positions 表格。锁仓账户里同一品种可能叠了好几单,GUI 里每行只汇总显示该品种的总浮盈、保证金占用和均价,再给个一键全平按钮——点 Profit 列单元可平单笔,点按钮则清掉该品种全部仓位。 核心入口是 CProgram::GetPositionsSymbols(),传入空 string 数组,它遍历 PositionsTotal() 返回的持仓数,用 PositionGetSymbol(i) 取品种名,借助 StringFind 查重后以逗号拼成字符串,最后 StringSplit 拆回数组并返回品种数。下面这段就是它的实现骨架。 拿到品种数组后,CProgram::PositionsTotal() 按名计仓数,PositionsVolumeTotal() 算总手数(可带类型过滤,默认 WRONG_VALUE 不过滤),PositionsFloatingProfitTotal() 把隔夜息也算进浮盈。均价由 PositionAveragePrice() 用「Σ(价×量)/Σ量」得出。 资金负载走 CProgram::DepositLoad(),第一个参数 false 返存款币种数值、true 返占可用保证金百分比;若账户币别与品种基础币别不同且多仓,要传均价进去算。SetValuesToPositionsTable() 统一把值写进表格,正绿负红零灰,负载列用「资金/百分比」斜杠分隔。 InitializePositionsTable() 在初始化时调上述全部方法:先按持仓建数组,删旧行加新行,有仓时把首列设成 CELL_BUTTON 加图标,再填值刷新。改动后单独留了更新方法可反复调用,避免重算整表。

MQL5 / C++
<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">int</span> CProgram::GetPositionsSymbols(<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> &amp;symbols_name[])
  {
   <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> symbols=<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="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> positions_total=::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">PositionsTotal</span>();
   <span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">for</span>(<span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">int</span> i=<span class="number" style="class="type">class="kw">color:green;">class="num">0</span>; i&lt;positions_total; i++)
     {
      <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> position_symbol=::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">PositionGetSymbol</span>(i);
      <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);">if</span>(position_symbol==<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="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="kw">continue</span>;
      <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>(::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">StringFind</span>(symbols,position_symbol,<span class="number" style="class="type">class="kw">color:green;">class="num">0</span>)==<span class="macro" style="class="type">class="kw">color:red;">WRONG_VALUE</span>)</span>
         ::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">StringAdd</span>(symbols,(symbols==<span class="class="type">class="kw">string" style="class="type">class="kw">color:rgb(class="num">0, class="num">128, class="num">175);">""</span>)? position_symbol : <span class="class="type">class="kw">string" style="class="type">class="kw">color:rgb(class="num">0, class="num">128, class="num">175);">","</span>+position_symbol);
     }
<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">ushort</span> u_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 style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);"><span class="keyword" style="class="type">class="kw">color:rgb(class="num">0, class="num">0, class="num">255);">class="type">int</span> symbols_total=::<span class="functions" style="class="type">class="kw">color:rgb(class="num">138, class="num">43, class="num">226);">StringSplit</span>(symbols,u_sep,symbols_name);</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="kw">return</span>(symbols_total);
  }
<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>

「按品种与方向统计持仓手数和浮盈」

在 EA 里做仓位管控,第一步是搞清楚当前账户到底压了多少仓。下面两个方法都挂在 CProgram 类下,一个数指定品种的未平仓位个数,另一个把同品种、甚至同方向的成交量加总,方便后续判断加仓空间或风险暴露。 PositionsTotal 只认 symbol 参数,从终端总持仓数倒序遍历,遇到非本品种直接 continue,匹配上就给 pos_counter 加一,最后返回整数。逻辑简单,但注意它不区分多空,黄金 XAUUSD 上 3 空 2 多也会返回 5。 PositionsVolumeTotal 多带一个 ENUM_POSITION_TYPE 形参,默认 WRONG_VALUE 表示不筛选方向。若传了 POSITION_TYPE_BUY 或 SELL,就再用 PositionGetInteger(POSITION_TYPE) 比对,不匹配跳过;匹配到的用 PositionGetDouble(POSITION_VOLUME) 累加。这样你能单独算出某品种空单总手数,例如 EURUSD 空单 0.8 手、多单 1.2 手时,只查空单会拿到 0.8。 外汇与贵金属保证金交易杠杆高,持仓量统计只是风控输入项之一,实际批量平仓或反向开仓前,仍要在 MT5 策略测试器里跑一遍多品种回测确认逻辑无漏。

MQL5 / C++
class="type">int CProgram::PositionsTotal(const class="type">class="kw">string symbol)
  {
class=class="str">"cmt">//--- 仓位计数器
   class="type">int pos_counter=class="num">0;
class=class="str">"cmt">//--- 检查仓位中是否有指定属性
   class="type">int positions_total=::PositionsTotal();
   for(class="type">int i=positions_total-class="num">1; i>=class="num">0; i--)
     {
       class=class="str">"cmt">//--- 如果选择仓位失败,转到下一个
       if(symbol!=::PositionGetSymbol(i))
         class="kw">continue;
       class=class="str">"cmt">//--- 增加计数器
       pos_counter++;
     }
class=class="str">"cmt">//--- 返回仓位数量
   class="kw">return(pos_counter);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 根据指定属性得到仓位的总交易量                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CProgram::PositionsVolumeTotal(const class="type">class="kw">string symbol,const class="type">ENUM_POSITION_TYPE type=WRONG_VALUE)
  {
class=class="str">"cmt">//--- 交易量计数器
   class="type">class="kw">double volume_counter=class="num">0;
class=class="str">"cmt">//--- 检查仓位中是否有指定属性
   class="type">int positions_total=::PositionsTotal();
   for(class="type">int i=positions_total-class="num">1; i>=class="num">0; i--)
     {
       class=class="str">"cmt">//--- 如果选择仓位失败,转到下一个
       if(symbol!=::PositionGetSymbol(i))
         class="kw">continue;
       class=class="str">"cmt">//--- 如果我们需要检查类型
       if(type!=WRONG_VALUE)
         {
           class=class="str">"cmt">//--- 如果类型不匹配,就转到下一个仓位
           if(type!=(class="type">ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE))
             class="kw">continue;
         }
       class=class="str">"cmt">//--- 总计交易量
       volume_counter+=::PositionGetDouble(POSITION_VOLUME);
     }
class=class="str">"cmt">//--- 返回交易量
   class="kw">return(volume_counter);
  }

浮盈与均价怎么从持仓里抠出来

做多仓位管理时,实时盯住浮动利润和平均开仓价是基本功。下面两段 MT5 代码直接遍历当前账户所有持仓,按品种过滤后累加,逻辑不绕弯。 第一段 PositionsFloatingProfitTotal 支持按品种和买卖方向筛选,把每笔仓位的 POSITION_PROFITPOSITION_SWAP 加总。注意它把隔夜息一并算进浮盈,黄金或外汇隔夜持仓成本可能吃掉小级别利润,这点不能漏。 第二段 PositionAveragePrice 用成交量加权算均价:先取每笔的 POSITION_PRICE_OPENPOSITION_VOLUME,分别累加到 sum_mult(价格×量)和 sum_volumes,循环结束后两者相除即得加权均价。若某品种有 3 笔欧美多单,手数 0.1/0.2/0.3、开价 1.1000/1.1020/1.0990,均价约为 1.1003 而非简单平均 1.1003 附近,加权才贴近真实保证金占用。 外汇与贵金属杠杆高,浮盈随时回吐,上述函数只反映当下账面,不构成方向判断。

MQL5 / C++
class="type">class="kw">double CProgram::PositionsFloatingProfitTotal(const class="type">class="kw">string symbol,const class="type">ENUM_POSITION_TYPE type=WRONG_VALUE)
  {
class=class="str">"cmt">//--- 当前利润计数器
   class="type">class="kw">double profit_counter=class="num">0.0;
class=class="str">"cmt">//--- 检查仓位中是否有指定属性
   class="type">int positions_total=::PositionsTotal();
   for(class="type">int i=positions_total-class="num">1; i>=class="num">0; i--)
     {
class=class="str">"cmt">//--- 如果选择仓位失败,转到下一个
      if(symbol!=::PositionGetSymbol(i))
         class="kw">continue;
class=class="str">"cmt">//--- 如果我们需要检查类型
      if(type!=WRONG_VALUE)
        {
class=class="str">"cmt">//--- 如果类型不匹配,就转到下一个仓位
         if(type!=(class="type">ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE))
            class="kw">continue;
        }
class=class="str">"cmt">//--- 总计当前利润 + 累积隔夜息
      profit_counter+=::PositionGetDouble(POSITION_PROFIT)+::PositionGetDouble(POSITION_SWAP);
     }
class=class="str">"cmt">//--- 返回结果
   class="kw">return(profit_counter);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 平均仓位价格                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CProgram::PositionAveragePrice(const class="type">class="kw">string symbol)
  {
class=class="str">"cmt">//--- 用于计算平均价格
   class="type">class="kw">double sum_mult    =class="num">0.0;
   class="type">class="kw">double sum_volumes =class="num">0.0;
class=class="str">"cmt">//--- 检查是否有仓位符合指定属性
   class="type">int positions_total=::PositionsTotal();
   for(class="type">int i=positions_total-class="num">1; i>=class="num">0; i--)
     {
class=class="str">"cmt">//--- 如果选择仓位失败,转到下一个
      if(symbol!=::PositionGetSymbol(i))
         class="kw">continue;
class=class="str">"cmt">//--- 取得价格和仓位的交易量
      class="type">class="kw">double pos_price  =::PositionGetDouble(POSITION_PRICE_OPEN);
      class="type">class="kw">double pos_volume =::PositionGetDouble(POSITION_VOLUME);

◍ 算清加权平均价与保证金占用

在持仓循环里,把每个单子的价格乘手数累加到 sum_mult,再把各自手数累加到 sum_volumes,这是后续求账户真实持仓均价的底料。 循环结束后先拦一道除零:sum_volumes 小于等于 0 直接返回 0.0,避免空仓时算均价崩在除法上。 均价用 NormalizeDouble 按品种小数点位数 SYMBOL_DIGITS 规整,返回 sum_mult 除以 sum_volumes。外汇和贵金属杠杆高,空仓误算可能让你对浮亏位置产生误判,属高风险环节。 DepositLoad 这块,不传 symbol 和 volume 就抓账户总占用 ACCOUNT_MARGIN;传了就走品种级计算。杠杆取 ACCOUNT_LEVERAGE,为 0 时兜底成 1,合约量取 SYMBOL_TRADE_CONTRACT_SIZE。 账户币种和品种基础币相同时,保证金 = 手数×合约量÷杠杆;不一致就再乘一个 price 折算。这个 price 通常是交叉盘汇率,漏传会直接低估占用。

MQL5 / C++
sum_mult+=(pos_price*pos_volume);
sum_volumes+=pos_volume;
if(sum_volumes<=class="num">0)
   class="kw">return(class="num">0.0);
class="kw">return(::NormalizeDouble(sum_mult/sum_volumes,(class="type">int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS)));

class="type">class="kw">double CProgram::DepositLoad(const class="type">bool percent_mode,const class="type">class="kw">double price=class="num">0.0,const class="type">class="kw">string symbol="",const class="type">class="kw">double volume=class="num">0.0)
  {
   class="type">class="kw">double margin=class="num">0.0;
   if(symbol=="" || volume==class="num">0.0)
      margin=::AccountInfoDouble(ACCOUNT_MARGIN);
   else
     {
      class="type">class="kw">double leverage       =((class="type">class="kw">double)::AccountInfoInteger(ACCOUNT_LEVERAGE)==class="num">0)? class="num">1 : (class="type">class="kw">double)::AccountInfoInteger(ACCOUNT_LEVERAGE);
      class="type">class="kw">double contract_size   =::SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);
      class="type">class="kw">string account_currency =::AccountInfoString(ACCOUNT_CURRENCY);
      class="type">class="kw">string base_currency    =::SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);
      if(account_currency==base_currency)
         margin=(volume*contract_size)/leverage;
      else
         margin=(volume*contract_size)/leverage*price;
     }

「仓位表里塞进保证金占用双口径」

把每个品种的占用情况写进面板表格时,最好同时给出绝对占用和占净值比例两个口径。下面这段在循环里先抓持仓数、总成交量、买卖分层成交量与浮动盈亏,再调 DepositLoad 算占用。 deposit_load 那行拼出的字符串形如「123.45/8.32%」:前半是 false 模式下的保证金绝对值,后半是 true 模式下 (margin/equity)*100 的百分比。注意 equity 为 0 时函数内部兜底成 1,避免除零把百分比打爆。 表格行数由 m_table_positions.RowsTotal() 决定,若传入品种数组比行数短就直接 return,所以实际展示的永远是已建行对应的品种。开 MT5 把这段接进你自己的 CProgram 派生类,改 DoubleToString 的小数位就能切换精度。 外汇与贵金属杠杆高,保证金占用比例随时因价格波动跳变,面板数值仅反映查询瞬间状态,不构成任何方向建议。

MQL5 / C++
class="type">class="kw">double equity=(::AccountInfoDouble(ACCOUNT_EQUITY)==class="num">0)? class="num">1 : ::AccountInfoDouble(ACCOUNT_EQUITY);
class=class="str">"cmt">//--- 返回当前资金负载
class="kw">return((!percent_mode)? margin : (margin/equity)*class="num">100);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把数值加到仓位表格中                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CProgram::SetValuesToPositionsTable(class="type">class="kw">string &symbols_name[])
  {
class=class="str">"cmt">//--- 检查是否超出范围
   class="type">uint symbols_total =::ArraySize(symbols_name);
   class="type">uint rows_total    =m_table_positions.RowsTotal();
   if(symbols_total<rows_total)
      class="kw">return;
class=class="str">"cmt">//--- 把参数加到表格中
   for(class="type">uint r=class="num">0; r<rows_total; r++)
     {
      class="type">int   positions_total =PositionsTotal(symbols_name[r]);
      class="type">class="kw">double pos_volume      =PositionsVolumeTotal(symbols_name[r]);
      class="type">class="kw">double buy_volume      =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_BUY);
      class="type">class="kw">double sell_volume     =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_SELL);
      class="type">class="kw">double pos_profit      =PositionsFloatingProfitTotal(symbols_name[r]);
      class="type">class="kw">double buy_profit      =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_BUY);
      class="type">class="kw">double sell_profit     =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_SELL);
      class="type">class="kw">double average_price   =PositionAveragePrice(symbols_name[r]);
      class="type">class="kw">string deposit_load    =::DoubleToString(DepositLoad(false,average_price,symbols_name[r],pos_volume),class="num">2)+"/"+
                               ::DoubleToString(DepositLoad(true,average_price,symbols_name[r],pos_volume),class="num">2)+"%";
      class=class="str">"cmt">//--- 设置数值
      m_table_positions.SetValue(class="num">0,r,symbols_name[r]);
      m_table_positions.SetValue(class="num">1,r,(class="type">class="kw">string)positions_total);
      m_table_positions.SetValue(class="num">2,r,::DoubleToString(pos_volume,class="num">2));
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时品种过滤与仓位快照,把重复劳动交给小布,你专注决策。

常见问题

因为在 EA 运行期间可能需要按过滤器重建列表,每次调用前释放旧数组可以避免残留数据污染新表格,属于防御式写法。
先取组合框文本值,再映射到对应的枚举常量,随后在指标创建循环里作为周期参数传入,否则句柄会创建在错误周期上。
在遍历服务器品种时加分类判断,不符的跳过;若以后想全品类显示,直接注释掉该条件即可。
小布的品种页与仓位视图已覆盖同类实时数据呈现,不需自己写 C++ 类;若你自建 EA,可参考本篇绑定逻辑对接小布导出的字段。
因为品种总数在循环前未知,先给一半暗示“正在接收”,等句柄填充阶段再续到满,避免界面看起来像卡死。