EA 交易中的资金管理函数·进阶篇
扒开 EURUSD 的属性底牌
在 MT5 客户端右键 EURUSD 选属性,说明栏会显示「EURUSD, Euro vs US Dollar」。这串文本不是摆设,EA 里用 SymbolInfoString(_Symbol, SYMBOL_DESCRIPTION) 就能直接抓出来,方便在日志里确认当前跑的到底是哪个品种。 外汇品种有三层货币身份:基础货币是下单价对应的钱(EURUSD 里是 EUR),报价货币是盈亏结算的钱(这里是 USD),预付款货币通常和基础货币一致。三层用 SymbolInfoString 配不同枚举就能分别拿到,写多品种 EA 时少了这一步,保证金计算可能直接错。 标准合约大小用 SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE) 取,EURUSD 默认返回 100000.00,代表 1 标准手对应 10 万单位基础货币。外汇和贵金属杠杆高,合约放大效应明显,开仓前务必确认这个数字。 下面这段代码把上面说的字段全打了一遍,直接丢进 EA 的 OnStart 就能在专家日志看到结果,建议手动跑一次对照属性窗口。
class="type">class="kw">string symbol=SymbolInfoString(_Symbol,SYMBOL_DESCRIPTION); Print("Symbol: "+symbol); class="type">class="kw">string symbol_path=SymbolInfoString(_Symbol,SYMBOL_PATH); Print("Path: "+symbol_path); class="type">class="kw">double lot_size=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_CONTRACT_SIZE); Print("Standard contract: "+DoubleToString(lot_size,class="num">2)); class="type">class="kw">string base_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_BASE); Print("Base currency: "+base_currency); class="type">class="kw">string profit_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_PROFIT); Print("Currency quotes: "+profit_currency); class="type">class="kw">string margin_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN); Print("Currency deposit: "+margin_currency);
◍ 按货币对类型拆开仓预付款
做资本管理第一步,是搞清楚开指定手数要占多少保证金。不知道这个数,EA 的仓位控制就是瞎蒙。预付款最终要换算成账户入金货币,再除以账户杠杆,才是实际占用的保证金。 MQL5 里把货币对按账户货币(假定为美元)分成三类:反向对(如 EURUSD,报价货币是美元)、直接对(如 USDCHF,保证金货币就是美元)、交叉对(如 CADCHF,两边都不含美元)。三类算法不同,但核心都是先算出「1 标准合约值多少入金货币」,再乘手数、除杠杆。 以 EURUSD 为例:合约大小 100 000 欧元,最新卖价 1.2500 时,买 1 手需要 100 000 × 1.2500 = 125 000 美元等价保证金占用(未除杠杆)。逻辑上买用 Ask、卖用 Bid。直接对最简单,USDCHF 合约 100 000 美元,margin = lots × 100000 / leverage 即可。 交叉对麻烦在要找「计算货币对」。比如 CADCHF 入金为加元、账户为美元,需从市场窗口里找 CADUSD 或 USDCAD。下面这段代码演示了怎么抓取合约大小、账户币种和保证金币种,并判断用正向还是反向换算: 交叉盘若第一次没找到 CADUSD,就退而求其次找 USDCAD;mode=true 乘汇率,false 除汇率。外汇及贵金属杠杆交易风险高,预付款随即时价格变动,反向与交叉对尤甚,开仓前应在 MT5 里实测数值。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回开仓需要的净值数额 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double GetMarginForOpening(class="type">class="kw">double lot,class="type">class="kw">string symbol,class="type">ENUM_POSITION_TYPE direction) { class="type">class="kw">double answer=class="num">0; class=class="str">"cmt">//--- ... class=class="str">"cmt">//--- 返回结果:开指定仓量所需的,以帐户币种计算的净值数额 class="kw">return(answer); } class=class="str">"cmt">//--- 获取合约大小 class="type">class="kw">double lot_size=SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE); class=class="str">"cmt">//--- 获取帐户的币种 class="type">class="kw">string account_currency=AccountInfoString(ACCOUNT_CURRENCY); class=class="str">"cmt">//--- 预付款的币种 class="type">class="kw">string margin_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN); class=class="str">"cmt">//--- 利润的币种 class="type">class="kw">string profit_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_PROFIT); class=class="str">"cmt">//--- 计算的币种 class="type">class="kw">string calc_currency=""; class=class="str">"cmt">//--- 反向报价 - true,直接报价 - false class="type">bool mode; class=class="str">"cmt">//--- 计算的币种 class="type">class="kw">string calc_currency=""; class=class="str">"cmt">//--- 如果利润币种和帐户币种一致 if(profit_currency==account_currency) { calc_currency=symbol; mode=true; } class=class="str">"cmt">//---我们知道计算的币种,让我们获取它最新的价格 class="type">MqlTick tick; SymbolInfoTick(calc_currency,tick); class=class="str">"cmt">//--- 现在我们已具备计算所需的一切 class="type">class="kw">double calc_price; class=class="str">"cmt">//--- 计算买单的情况
「交叉盘保证金换算的兜底取法」
当账户币种与保证金币种不一致且无法直接定位计算币种时,大概率是碰上了交叉货币对。此时不能硬算,要先去市场报价窗口里找一个同时满足「保证金币种=margin_currency 且 利润币种=account_currency」的交易品种来借价。 下面这个函数就是干这活的:遍历 SymbolsTotal(true) 返回的所有可见品种,逐个用 SymbolInfoString 抓 SYMBOL_CURRENCY_MARGIN 和 SYMBOL_CURRENCY_PROFIT,双匹配就返回品种名,否则跑完回 NULL。 [CODE] string GetSymbolByCurrencies(string margin_currency,string profit_currency) { for(int s=0;s<SymbolsTotal(true);s++) { string symbolname=SymbolName(s,true); string m_cur=SymbolInfoString(symbolname,SYMBOL_CURRENCY_MARGIN); string p_cur=SymbolInfoString(symbolname,SYMBOL_CURRENCY_PROFIT); if(m_cur==margin_currency && p_cur==profit_currency) return(symbolname); } return(NULL); } [/CODE] 逐行拆解:函数入口收两个字符串参数,分别是保证金币种与利润币种。for 循环从 0 扫到 SymbolsTotal(true)-1,true 表示只扫市场报价里可见的。SymbolName(s,true) 按序号拿品种名;随后两行分别取该品种的保证金币种与利润币种。if 判断两者同时命中传入值就直接 return 品种名;循环结束没命中则返回 NULL。 拿到 calc_currency 为空说明是交叉盘,先正着查 GetSymbolByCurrencies(margin_currency,account_currency) 并置 mode=true;若仍 NULL,反过来查 GetSymbolByCurrencies(account_currency,margin_currency) 并置 mode=false。之后 SymbolInfoTick 取最新 tick,买单在 mode 真时用 tick.ask、假时用 tick.bid 乘 lot*lot_size 得 answer。外汇与贵金属杠杆放大效应明显,交叉盘借价偏差会直接扭曲保证金预估,实盘前建议在 MT5 用 CAD/CHF 这类真实交叉对跑一遍该函数验证返回值。
class="type">class="kw">string GetSymbolByCurrencies(class="type">class="kw">string margin_currency,class="type">class="kw">string profit_currency) { for(class="type">int s=class="num">0;s<SymbolsTotal(true);s++) { class="type">class="kw">string symbolname=SymbolName(s,true); class="type">class="kw">string m_cur=SymbolInfoString(symbolname,SYMBOL_CURRENCY_MARGIN); class="type">class="kw">string p_cur=SymbolInfoString(symbolname,SYMBOL_CURRENCY_PROFIT); if(m_cur==margin_currency && p_cur==profit_currency) class="kw">return(symbolname); } class="kw">return(NULL); }
买卖方向与报价模式下的保证金拆分
这段逻辑把「开仓所需净值」按持仓方向和报价结构拆成了四种组合。核心变量 calc_price 在买入直接报价时取 ask、卖出直接报价时取 bid,都是除以 calc_price;而反向报价下无论买卖都用 bid,但卖出时改为乘 calc_price。 [CODE] calc_price=tick.bid; answer=lot*lot_size/calc_price; } } //--- 计算卖出的情况 if(direction==POSITION_TYPE_SELL) { //--- 反向报价 if(mode) { //--- 用反向报价的卖出价计算 calc_price=tick.bid; answer=lot*lot_size*calc_price; } //--- 直接报价 else { //--- 用直接报价的买入价计算 calc_price=tick.ask; answer=lot*lot_size/calc_price; } } //--- 返回结果:开指定仓量所需的,以帐户币种计算的净值数额 return (Answer); //+------------------------------------------------------------------+
| // | EA的tick函数 |
|---|
//+------------------------------------------------------------------+ void OnTick() { //--- 用于备注的字符串变量 string com=" "; StringAdd(com,Symbol()); StringAdd(com," "); //--- 标准合约的大小 double lot_size=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_CONTRACT_SIZE); //--- 预付款的币种 string margin_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN); StringAdd(com,StringFormat("Standard contract: %.2f %s",lot_size,margin_currency)); StringAdd(com," "); //--- 杠杆 int leverage=(int)AccountInfoInteger(ACCOUNT_LEVERAGE); StringAdd(com,StringFormat("Leverage: 1/%d",leverage)); StringAdd(com," "); //--- 按帐户币种计算的合约价值 StringAdd(com,"Deposit for opening positions in 1 lot consists "); //--- 用杠杆计算预付款 double margin=GetMarginForOpening(1,Symbol(),POSITION_TYPE_BUY)/leverage; StringAdd(com,DoubleToString(margin,2)); StringAdd(com," "+AccountInfoString(ACCOUNT_CURRENCY)); Comment(com); } [/CODE] 逐行看,direction==POSITION_TYPE_SELL 分支里 mode 为真代表反向报价(如 EURUSD 报 USD/EUR 这类少数经纪商设置),此时卖出保证金 = lot×合约单位×bid;mode 为假即直接报价,卖出用 ask 做分母。 OnTick 里把合约大小、保证金币种、杠杆和 1 手买单调用的 GetMarginForOpening 结果除以杠杆后,用 Comment 直接打印在图表左上。拿 EURUSD 举例:若合约单位 100000、杠杆 1/100,1 手买入保证金倾向落在账户币种计价的约 1000 单位附近,具体以你 MT5 终端返回数为准。 外汇与贵金属属高杠杆品种,保证金随 tick 跳动重算,实盘显示值可能和收盘静态测算有偏差,开仓前应以终端预警为准。
calc_price=tick.bid; answer=lot*lot_size/calc_price; } } class=class="str">"cmt">//--- 计算卖出的情况 if(direction==POSITION_TYPE_SELL) { class=class="str">"cmt">//--- 反向报价 if(mode) { class=class="str">"cmt">//--- 用反向报价的卖出价计算 calc_price=tick.bid; answer=lot*lot_size*calc_price; } class=class="str">"cmt">//--- 直接报价 else { class=class="str">"cmt">//--- 用直接报价的买入价计算 calc_price=tick.ask; answer=lot*lot_size/calc_price; } } class=class="str">"cmt">//--- 返回结果:开指定仓量所需的,以帐户币种计算的净值数额 class="kw">return (Answer); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA的tick函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- 用于备注的字符串变量 class="type">class="kw">string com=" "; StringAdd(com,Symbol()); StringAdd(com," "); class=class="str">"cmt">//--- 标准合约的大小 class="type">class="kw">double lot_size=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_CONTRACT_SIZE); class=class="str">"cmt">//--- 预付款的币种 class="type">class="kw">string margin_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN); StringAdd(com,StringFormat("Standard contract: %.2f %s",lot_size,margin_currency)); StringAdd(com," "); class=class="str">"cmt">//--- 杠杆 class="type">int leverage=(class="type">int)AccountInfoInteger(ACCOUNT_LEVERAGE); StringAdd(com,StringFormat("Leverage: class="num">1/%d",leverage)); StringAdd(com," "); class=class="str">"cmt">//--- 按帐户币种计算的合约价值 StringAdd(com,"Deposit for opening positions in class="num">1 lot consists "); class=class="str">"cmt">//--- 用杠杆计算预付款 class="type">class="kw">double margin=GetMarginForOpening(class="num">1,Symbol(),POSITION_TYPE_BUY)/leverage; StringAdd(com,DoubleToString(margin,class="num">2)); StringAdd(com," "+AccountInfoString(ACCOUNT_CURRENCY)); Comment(com); }
◍ 把工具请下神坛
前面几节给出的 account_info、symbol_info 与 symbolinfo_advanced 三个脚本,体积分别只有 7.59 KB、4.21 KB 和 9.95 KB,却已经能把账户杠杆、合约规模、保证金计算模式这些关键属性一次性拉到桌面上。 评论区里有人拿 IBM 股票做过对照:同是 ENUM_SYMBOL_CALC_MODE 里的类型 2(无杠杆 CFD),Liteforex 与终端默认环境的保证金却不同,差异正落在公式里的「百分比」参数上——它在某些品种上实际等价于杠杆倒数。 外汇和贵金属品种杠杆浮动大、保证金规则常被平台改写,直接用代码读出来的现场值比手册更可信。开 MT5 把那三个 mq5 拖进脚本区跑一遍,比盲信说明书少踩很多坑。