EA 交易中的资金管理函数·进阶篇
📘

EA 交易中的资金管理函数·进阶篇

第 2/2 篇

扒开 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 就能在专家日志看到结果,建议手动跑一次对照属性窗口。

MQL5 / C++
  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 里实测数值。

MQL5 / C++
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 这类真实交叉对跑一遍该函数验证返回值。

MQL5 / C++
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 跳动重算,实盘显示值可能和收盘静态测算有偏差,开仓前应以终端预警为准。

MQL5 / C++
  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 拖进脚本区跑一遍,比盲信说明书少踩很多坑。

常见问题

用 SymbolInfoDouble 取 SYMBOL_TRADE_TICK_VALUE 和 SYMBOL_SPREAD 即可,回测时切到对应品种页面验证数值是否随账户货币变化。
优先读 SYMBOL_MARGIN_INIT,读不到就按合约大小乘报价再除账户杠杆,并在日志打印取值来源便于排查。
小布盯盘的 AIGC 已内置品种保证金诊断,打开对应品种页即可直接看到按手数拆好的预付款占用。
因为做市报价与交易所模式对预付款计算字段含义不同,需先读 SYMBOL_TRADE_MODE 再决定用哪组字段拆分。
黄金合约规格与杠杆折算方式和外汇不同,用 SYMBOL_MARGIN_INIT 按品种取而非写死系数,外汇贵金属均属高风险。