MQL5交易管理面板开发(第十二部分):汇率计算器的集成(基础篇)
把汇率换算塞进交易面板
在 MT5 里做交易管理面板时,跨品种净值换算是个绕不开的坑。比如账户是美元结算,但持仓里有 XAUUSD 和 EURUSD,想实时看总敞口折合多少美元,就得自己算交叉汇率,而不是只调 AccountInfoDouble。 MQL5 没有现成的『一键换算』接口,常规做法是先抓 SymbolInfoDouble 的 SYMBOL_BID / SYMBOL_ASK,再按账户币种与品种币种的配对关系手算。下面这段演示了如何取某品种的实时买价并做基础判断,跑之前把 _symbol 换成你面板里选中的品种名即可。 外汇与贵金属杠杆高、点差跳变快,换算结果只反映当下快照,别拿它当风控唯一依据,盘面异常时数字可能瞬间失真。
class="type">class="kw">double GetBid(class="kw">const class="type">class="kw">string _symbol) { class="type">class="kw">double bid = SymbolInfoDouble(_symbol, SYMBOL_BID); if(bid <= class="num">0) class="kw">return EMPTY_VALUE; class="kw">return bid; }
◍ 在交易面板里塞进一个汇率换算器
把多品种净值折算成统一计价货币,是盯盘时的高频动作。MQL5 面板默认只显示报价货币金额,跨品种算实际占用要手动敲计算器,误差和延迟都够喝一壶。 实现路径分四步:先改挂单模块布局,给新控件腾出空间;再写独立的汇率计算器类;接着把它挂进交易管理面板;最后调管理面板 EA 的刷新逻辑,让换算结果随 tick 更新。外汇和贵金属杠杆高,换算错一位小数可能误判可用保证金,实盘前务必在策略测试器用历史 tick 跑一遍。 测试时重点看两处:非美直盘交叉盘(如 EURJPY 兑 USD)的折算是否与 MT5 账户净值页一致;面板在 30 个以上品种同开时是否卡顿。结论很直接——这套控件不预测行情,只把账户视图从‘碎片’拼成‘整张表’,开 MT5 照这四步改你的面板 EA 就能验证。
「把汇率计算器焊进交易终端」
做外汇和贵金属交易,最打断节奏的事之一就是在 MT5 和外部网页之间来回切:查个点值、算个保证金,浏览器一开一关,行情已经走了几根 K 线。MT5 自带的 GUI 接口足够我们在管理面板里直接塞一个原生汇率计算器,把持仓规模、风险金额、点值、保证金需求、盈亏预估、掉期、风险回报比、点差成本这些参数全留在同一个窗口里算。 对外汇和贵金属来说,这类品种高杠杆、高波动,参数算错一次就可能触发强平。仅保证金水平与杠杆影响两项,就决定了你能不能扛过一次非常规跳空;而套息交易者若忽略隔夜利息,长线持仓成本会被悄悄吃掉一大块。 我们不做外部 API 的搬运,而是用 MetaTrader 5 的本地接口写专属算法,行情与新闻流也能在终端内直连。这样你开仓前不用离开图表界面,输入账户余额和止损距离,面板直接吐出该下多少手、占多少保证金、盈亏平衡点在哪。 下一节会具体拆这套计算模块的落地路径。
把面板拆开重排给计算器让路
模块化改造之后,交易管理面板不再是一整块铁板,各功能可以单独迭代。这次我们借 MQL5 标准库的扩展类,把原先铺开的输入区压成「下拉菜单 + 单行输入」,给后面的计算组件硬挤出空间。 不是所有交易参数都得常驻界面。决策要用到的关键数必须一眼能抓,剩下的靠 MQL5 实时行情直接取,不用算也不用填。 界面侧,订单类型用 ComboBox 做下拉(A 区),原本堆着的输入收成单行(B 区),到期日字段(C 区)换成 DatePicker,少点手敲。布局定完再接计算逻辑,实测每项计算通常不超过 3 个输入字段就能跑。 外汇和贵金属波动大、杠杆高,这类面板改造只解决操作效率,不替你兜底行情风险。下一步从订单模块动刀,把计算器前端真正嵌进去。
◍ 把风控公式塞进MT5函数里
做外汇或贵金属交易,先得把账算清楚:持仓规模、风险金额、点值、保证金需求、盈亏预估、隔夜利息、风险回报比、保证金水平、点差成本、杠杆影响,这十项是一套基础计算骨架。表格里列的公式属于通用研究结论,建议自己再找权威渠道交叉验证一遍,别直接当圣经用。 下面这段 MQL5 把其中几个核心项写成了可直接抄的函数。外汇和贵金属都是高杠杆品种,参数算错可能瞬间放大亏损,跑之前先在策略测试器里用历史数据核对。
<span class="keyword">class="type">class="kw">double</span> CalculatePositionSize(<span class="keyword">class="type">class="kw">double</span> accountBalance, <span class="keyword">class="type">class="kw">double</span> riskPercent, <span class="keyword">class="type">class="kw">double</span> stopLossPips, <span class="keyword">class="type">class="kw">string</span> symbol) { <span class="keyword">if</span> (accountBalance <= <span class="number">class="num">0</span> || riskPercent <= <span class="number">class="num">0</span> || stopLossPips <= <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; <span class="keyword">class="type">class="kw">double</span> pipValue = CalculatePipValue(symbol, <span class="number">class="num">1.0</span>, AccountCurrency()); <span class="keyword">if</span> (pipValue == <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; <span class="keyword">class="type">class="kw">double</span> positionSize = (accountBalance * (riskPercent / <span class="number">class="num">100.0</span>)) / (stopLossPips * pipValue); <span class="keyword">class="type">class="kw">double</span> lotStep = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_LOTSTEP</span>); <span class="keyword">class="type">class="kw">double</span> minLot = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_MINLOT</span>); <span class="keyword">class="type">class="kw">double</span> maxLot = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_MAXLOT</span>); <span class="keyword">class="kw">return</span> <span class="functions">NormalizeDouble</span>( <span class="functions">MathMax</span>(minLot, <span class="functions">MathMin</span>(maxLot, positionSize)), (<span class="keyword">class="type">int</span>)-<span class="functions">MathLog10</span>(lotStep)); } <span class="keyword">class="type">class="kw">double</span> CalculateRiskAmount(<span class="keyword">class="type">class="kw">double</span> positionSize, <span class="keyword">class="type">class="kw">double</span> stopLossPips, <span class="keyword">class="type">class="kw">string</span> symbol) { <span class="keyword">if</span> (positionSize <= <span class="number">class="num">0</span> || stopLossPips <= <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; <span class="keyword">class="type">class="kw">double</span> pipValue = CalculatePipValue(symbol, positionSize, AccountCurrency()); <span class="keyword">class="kw">return</span> <span class="functions">NormalizeDouble</span>(positionSize * stopLossPips * pipValue, <span class="number">class="num">2</span>); } <span class="keyword">class="type">class="kw">double</span> CalculatePipValue(<span class="keyword">class="type">class="kw">string</span> symbol, <span class="keyword">class="type">class="kw">double</span> lotSize, <span class="keyword">class="type">class="kw">string</span> accountCurrency) { <span class="keyword">class="type">class="kw">double</span> tickSize = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_TICKSIZE</span>); <span class="keyword">class="type">class="kw">double</span> tickValue = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_TICKVALUE</span>); <span class="keyword">class="type">class="kw">double</span> pipSize = <span class="functions">StringFind</span>(symbol, <span class="class="type">class="kw">string">"JPY"</span>) >= <span class="number">class="num">0</span> ? <span class="number">class="num">0.01</span> : <span class="number">class="num">0.0001</span>; <span class="keyword">class="type">class="kw">double</span> conversionRate = <span class="number">class="num">1.0</span>; <span class="keyword">if</span> (accountCurrency != <span class="functions">SymbolInfoString</span>(symbol, <span class="macro">SYMBOL_CURRENCY_PROFIT</span>)) { <span class="keyword">class="type">class="kw">string</span> conversionPair = <span class="functions">SymbolInfoString</span>( symbol, <span class="macro">SYMBOL_CURRENCY_PROFIT</span>) + accountCurrency; <span class="keyword">if</span> (<span class="functions">SymbolSelect</span>(conversionPair, <span class="keyword">true</span>)) { conversionRate = <span class="functions">MarketInfo</span>(conversionPair, <span class="macro">MODE_BID</span>); } <span class="keyword">else</span> { <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Warning: Conversion pair "</span>, conversionPair, <span class="class="type">class="kw">string">" not found, class="kw">using class="num">1.0"</span>); } } <span class="keyword">if</span> (tickSize == <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; <span class="keyword">class="kw">return</span> <span class="functions">NormalizeDouble</span>((tickValue / tickSize) * pipSize * lotSize * conversionRate, <span class="number">class="num">2</span>); } <span class="keyword">class="type">class="kw">double</span> CalculateMarginRequirement(<span class="keyword">class="type">class="kw">double</span> lotSize, <span class="keyword">class="type">class="kw">string</span> symbol) { <span class="keyword">class="type">class="kw">double</span> marginRequired = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_MARGINREQUIRED</span>); <span class="keyword">if</span> (marginRequired == <span class="number">class="num">0</span>) { <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Error: Margin requirement not available "</span>, symbol); <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; } <span class="keyword">class="kw">return</span> <span class="functions">NormalizeDouble</span>(lotSize * marginRequired, <span class="number">class="num">2</span>); } <span class="keyword">class="type">class="kw">double</span> CalculateProfitLoss(<span class="keyword">class="type">class="kw">double</span> entryPrice, <span class="keyword">class="type">class="kw">double</span> exitPrice, <span class="keyword">class="type">class="kw">double</span> lotSize, <span class="keyword">class="type">class="kw">string</span> symbol) { <span class="keyword">if</span> (lotSize <= <span class="number">class="num">0</span> || entryPrice <= <span class="number">class="num">0</span> ||
CalculatePositionSize 先拦掉余额、风险百分比、止损点数为零的脏输入,再借 CalculatePipValue 拿 1 标准手点值,用「余额×风险% ÷(止损点数×点值)」算出裸手数,最后用 MarketInfo 取最小/最大手数和步长做夹紧并标准化。CalculatePipValue 里对含 JPY 的品种把 pipSize 设 0.01,其余 0.0001,还尝试拼一个「盈利货币+账户货币」的转化对取汇率,取不到就告警用 1.0。
往后做 UI 时,可以用标准库 CComboBox 把这类计算器收进交易管理面板,省空间;再挂个 DatePicker 管订单到期日,比手填字符串稳。外汇贵金属波动快,这种小控件能减少手误,但替代不了你自己对风险的判断。
<span class="keyword">class="type">class="kw">double</span> CalculatePositionSize(<span class="keyword">class="type">class="kw">double</span> accountBalance, <span class="keyword">class="type">class="kw">double</span> riskPercent, <span class="keyword">class="type">class="kw">double</span> stopLossPips, <span class="keyword">class="type">class="kw">string</span> symbol) { <span class="keyword">if</span> (accountBalance <= <span class="number">class="num">0</span> || riskPercent <= <span class="number">class="num">0</span> || stopLossPips <= <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; <span class="keyword">class="type">class="kw">double</span> pipValue = CalculatePipValue(symbol, <span class="number">class="num">1.0</span>, AccountCurrency()); <span class="keyword">if</span> (pipValue == <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; <span class="keyword">class="type">class="kw">double</span> positionSize = (accountBalance * (riskPercent / <span class="number">class="num">100.0</span>)) / (stopLossPips * pipValue); <span class="keyword">class="type">class="kw">double</span> lotStep = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_LOTSTEP</span>); <span class="keyword">class="type">class="kw">double</span> minLot = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_MINLOT</span>); <span class="keyword">class="type">class="kw">double</span> maxLot = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_MAXLOT</span>); <span class="keyword">class="kw">return</span> <span class="functions">NormalizeDouble</span>( <span class="functions">MathMax</span>(minLot, <span class="functions">MathMin</span>(maxLot, positionSize)), (<span class="keyword">class="type">int</span>)-<span class="functions">MathLog10</span>(lotStep)); } <span class="keyword">class="type">class="kw">double</span> CalculateRiskAmount(<span class="keyword">class="type">class="kw">double</span> positionSize, <span class="keyword">class="type">class="kw">double</span> stopLossPips, <span class="keyword">class="type">class="kw">string</span> symbol) { <span class="keyword">if</span> (positionSize <= <span class="number">class="num">0</span> || stopLossPips <= <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; <span class="keyword">class="type">class="kw">double</span> pipValue = CalculatePipValue(symbol, positionSize, AccountCurrency()); <span class="keyword">class="kw">return</span> <span class="functions">NormalizeDouble</span>(positionSize * stopLossPips * pipValue, <span class="number">class="num">2</span>); } <span class="keyword">class="type">class="kw">double</span> CalculatePipValue(<span class="keyword">class="type">class="kw">string</span> symbol, <span class="keyword">class="type">class="kw">double</span> lotSize, <span class="keyword">class="type">class="kw">string</span> accountCurrency) { <span class="keyword">class="type">class="kw">double</span> tickSize = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_TICKSIZE</span>); <span class="keyword">class="type">class="kw">double</span> tickValue = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_TICKVALUE</span>); <span class="keyword">class="type">class="kw">double</span> pipSize = <span class="functions">StringFind</span>(symbol, <span class="class="type">class="kw">string">"JPY"</span>) >= <span class="number">class="num">0</span> ? <span class="number">class="num">0.01</span> : <span class="number">class="num">0.0001</span>; <span class="keyword">class="type">class="kw">double</span> conversionRate = <span class="number">class="num">1.0</span>; <span class="keyword">if</span> (accountCurrency != <span class="functions">SymbolInfoString</span>(symbol, <span class="macro">SYMBOL_CURRENCY_PROFIT</span>)) { <span class="keyword">class="type">class="kw">string</span> conversionPair = <span class="functions">SymbolInfoString</span>( symbol, <span class="macro">SYMBOL_CURRENCY_PROFIT</span>) + accountCurrency; <span class="keyword">if</span> (<span class="functions">SymbolSelect</span>(conversionPair, <span class="keyword">true</span>)) { conversionRate = <span class="functions">MarketInfo</span>(conversionPair, <span class="macro">MODE_BID</span>); } <span class="keyword">else</span> { <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Warning: Conversion pair "</span>, conversionPair, <span class="class="type">class="kw">string">" not found, class="kw">using class="num">1.0"</span>); } } <span class="keyword">if</span> (tickSize == <span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; <span class="keyword">class="kw">return</span> <span class="functions">NormalizeDouble</span>((tickValue / tickSize) * pipSize * lotSize * conversionRate, <span class="number">class="num">2</span>); } <span class="keyword">class="type">class="kw">double</span> CalculateMarginRequirement(<span class="keyword">class="type">class="kw">double</span> lotSize, <span class="keyword">class="type">class="kw">string</span> symbol) { <span class="keyword">class="type">class="kw">double</span> marginRequired = <span class="functions">MarketInfo</span>(symbol, <span class="macro">MODE_MARGINREQUIRED</span>); <span class="keyword">if</span> (marginRequired == <span class="number">class="num">0</span>) { <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Error: Margin requirement not available "</span>, symbol); <span class="keyword">class="kw">return</span> <span class="number">class="num">0.0</span>; } <span class="keyword">class="kw">return</span> <span class="functions">NormalizeDouble</span>(lotSize * marginRequired, <span class="number">class="num">2</span>); } <span class="keyword">class="type">class="kw">double</span> CalculateProfitLoss(<span class="keyword">class="type">class="kw">double</span> entryPrice, <span class="keyword">class="type">class="kw">double</span> exitPrice, <span class="keyword">class="type">class="kw">double</span> lotSize, <span class="keyword">class="type">class="kw">string</span> symbol) { <span class="keyword">if</span> (lotSize <= <span class="number">class="num">0</span> || entryPrice <= <span class="number">class="num">0</span> ||
「把持仓成本与风险系数一次算清」
这段 MT5 自定义函数把单子从开仓到持仓的各项账面损耗都拆成了可调用接口。外汇与贵金属杠杆高、点值随币种切换,手动估成本容易漏掉掉期与价差,直接用代码拉数据更稳。 CalculateSwap 里有个细节:当 TimeDayOfWeek 返回 3(周四)时,days 乘 3,对应银行间对周三持仓收三倍隔夜息的惯例。跑黄金 XAUUSD 时若账号币种是 USD 而品种盈利币种也是 USD,conversionRate 保持 1.0,否则会拼出如 XAUUSD 的衍生对去取 MODE_BID 换算。 CalculateMarginLevel 用 AccountEquity 除以 AccountMargin 再乘 100,输出的是平台显示的保证金水平百分比。该值低于 100 时多数券商会禁开新仓,低于 50 可能触发强平,属高风险预警信号。 CalculateLeverageImpact 把持仓规模乘合约大小乘市价再除权益,得出当前头寸对净值的撬动倍数。调一下 positionSize 参数,就能在策略测试器里看不同手数下爆仓概率的边际变化。
exitPrice <= class="num">0) class="kw">return class="num">0.0; class="type">class="kw">double contractSize = MarketInfo(symbol, MODE_LOTSIZE); class="type">class="kw">double conversionRate = class="num">1.0; if (AccountCurrency() != SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT)) { class="type">class="kw">string conversionPair = SymbolInfoString( symbol, SYMBOL_CURRENCY_PROFIT) + AccountCurrency(); if (SymbolSelect(conversionPair, true)) { conversionRate = MarketInfo(conversionPair, MODE_BID); } } class="type">class="kw">double priceDiff = exitPrice - entryPrice; class="type">class="kw">double pips = priceDiff / (StringFind(symbol, "JPY") >= class="num">0 ? class="num">0.01 : class="num">0.0001); class="kw">return NormalizeDouble(pips * CalculatePipValue(symbol, lotSize, AccountCurrency()), class="num">2); } class="type">class="kw">double CalculateSwap(class="type">class="kw">double lotSize, class="type">class="kw">string symbol, class="type">bool isBuy, class="type">int days = class="num">1) { class="type">class="kw">double swapLong = MarketInfo(symbol, MODE_SWAPLONG); class="type">class="kw">double swapShort = MarketInfo(symbol, MODE_SWAPSHORT); if (swapLong == class="num">0 && swapShort == class="num">0) { Print("Error: Swap rates not available ", symbol); class="kw">return class="num">0.0; } class="type">class="kw">double swap = isBuy ? swapLong : swapShort; class="type">class="kw">datetime currentTime = TimeCurrent(); if (TimeDayOfWeek(currentTime) == class="num">3) days *= class="num">3; class="type">class="kw">double totalSwap = lotSize * swap * days; class="kw">return NormalizeDouble(totalSwap, class="num">2); } class="type">class="kw">double CalculateRiskRewardRatio(class="type">class="kw">double takeProfitPips, class="type">class="kw">double stopLossPips) { if (stopLossPips <= class="num">0 || takeProfitPips <= class="num">0) class="kw">return class="num">0.0; class="kw">return NormalizeDouble(takeProfitPips / stopLossPips, class="num">2); } class="type">class="kw">double CalculateMarginLevel() { class="type">class="kw">double equity = AccountEquity(); class="type">class="kw">double margin = AccountMargin(); if (margin == class="num">0) class="kw">return class="num">0.0; class="kw">return NormalizeDouble((equity / margin) * class="num">100, class="num">2); } class="type">class="kw">double CalculateSpreadCost(class="type">class="kw">double lotSize, class="type">class="kw">string symbol) { class="type">class="kw">double spreadPips = MarketInfo(symbol, MODE_SPREAD) / class="num">10.0; class="type">class="kw">double pipValue = CalculatePipValue(symbol, lotSize, AccountCurrency()); class="kw">return NormalizeDouble(spreadPips * pipValue * lotSize, class="num">2); } class="type">class="kw">double CalculateLeverageImpact(class="type">class="kw">double positionSize, class="type">class="kw">string symbol, class="type">class="kw">double accountEquity) { if (positionSize <= class="num">0 || accountEquity <= class="num">0) class="kw">return class="num">0.0; class="type">class="kw">double contractSize = MarketInfo(symbol, MODE_LOTSIZE); class="type">class="kw">double marketPrice = MarketInfo(symbol, MODE_BID); class="kw">return NormalizeDouble((positionSize * contractSize * marketPrice) / accountEquity, class="num">2); }