包含图形用户界面 (GUI) 的 EA 交易: 增加功能 (第二部分)·进阶篇
(2/3)· 界面画好了却还是死板的摆设?本篇教你让表格随品种、指标和持仓自动呼吸
数据请求里的界面与图表联动
在 MT5 面板程序里,点一次刷新按钮背后往往不是单纯拉数据,而是先切界面状态再跑计算。上面这段 CProgram::RequestData 就是典型:先比对传入 id 和内部请求对象 Id(),不一致直接返回 false,避免误触发。 接着把品种表格 Hide()、进度条 Show() 并 Redraw(),给用户一个“正在算”的视觉反馈;随后连续调用 GetSymbols、GetHandles、GetIndicatorValues、RebuildingTables 完成数据抓取与表格重建,最后隐藏进度条。 从下拉框取时间帧文本后,用 StringToTimeframe 转成枚举再赋给子图 Period,并 ResetCharts 重绘。外汇与贵金属波动大,这种先隐后显的逻辑能防止半截表格闪给用户看,自己开 MT5 挂个 EA 断点跟一遍就知道卡在哪一步。
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 加图标,再填值刷新。改动后单独留了更新方法可反复调用,避免重算整表。
<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> &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<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 策略测试器里跑一遍多品种回测确认逻辑无漏。
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_PROFIT 与 POSITION_SWAP 加总。注意它把隔夜息一并算进浮盈,黄金或外汇隔夜持仓成本可能吃掉小级别利润,这点不能漏。
第二段 PositionAveragePrice 用成交量加权算均价:先取每笔的 POSITION_PRICE_OPEN 和 POSITION_VOLUME,分别累加到 sum_mult(价格×量)和 sum_volumes,循环结束后两者相除即得加权均价。若某品种有 3 笔欧美多单,手数 0.1/0.2/0.3、开价 1.1000/1.1020/1.0990,均价约为 1.1003 而非简单平均 1.1003 附近,加权才贴近真实保证金占用。
外汇与贵金属杠杆高,浮盈随时回吐,上述函数只反映当下账面,不构成方向判断。
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 通常是交叉盘汇率,漏传会直接低估占用。
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 的小数位就能切换精度。 外汇与贵金属杠杆高,保证金占用比例随时因价格波动跳变,面板数值仅反映查询瞬间状态,不构成任何方向建议。
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));