外汇EA编写教程:EA 交易中的资金管理函数

简介

MQL5 语言提供一个机会来获取有关当前客户端状况、MQL5 程序以及金融工具和交易帐户的海量信息。为了组织资金管理函数,我们将需要研究列出的最后两个部分所提供的属性,并且让我们自己熟悉以下函数:

  • SymbolInfoInteger()
  • SymbolInfoDouble()
  • SymbolInfoString()
  • AccountInfoInteger()
  • AccountInfoDouble()
  • AccountInfoString()

尽管本文的重点在于在 EA 交易中使用函数,不过所有这些说明也适用于指标和脚本。

获取有关您的帐户余额的信息

交易帐户的前两个重要特征 – 余额和资产净值。要获取这些值,使用 AccountInfoDouble() 函数

   double balance=AccountInfoDouble(ACCOUNT_BALANCE);
   double equity=AccountInfoDouble(ACCOUNT_EQUITY);

我们感兴趣的下一件事情是针对未平仓位的入金基金,以及帐户中针对所有未平仓位的整体滚动盈利或损失。

   double margin=AccountInfoDouble(ACCOUNT_MARGIN);
   double float_profit=AccountInfoDouble(ACCOUNT_PROFIT);

为了建立新仓位或增加现有仓位,我们需要未进入入金的可用资金。

   double free_margin=AccountInfoDouble(ACCOUNT_FREEMARGIN);

应在这里指出,以上值是用货币术语表示的。

AccountInfoDouble() 函数返回的货币值以入金货币表示。要找出入金货币,使用 AccountInfoString() 函数。

string account_currency=AccountInfoString(ACCOUNT_CURRENCY);

个人资金水平

帐户有另一重要特征 – 止损的发生水平(由于缺少维持未平仓位所必需的个人资金而强制平仓)。要获取此值,再次使用 AccountInfoDouble() 函数:

double stopout_level=AccountInfoDouble(ACCOUNT_MARGIN_SO_SO);

函数仅返回值本身,但是不解释此值是用什么类型的单位表示的。对于止损,有两种模式的水平指定:一种以百分比,另一种以货币。要找出指定模式,使用 AccountInfoInteger() 函数:  

//--- 获取帐户的币种
string account_currency=AccountInfoString(ACCOUNT_CURRENCY);

//--- 停止水平
   double stopout_level=AccountInfoDouble(ACCOUNT_MARGIN_SO_SO);

//--- 停止模式
   ENUM_ACCOUNT_STOPOUT_MODE so_mode=(ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);
   if(so_mode==ACCOUNT_STOPOUT_MODE_PERCENT)

      PrintFormat("Stop Out level in percents %.2f%%",stopout_level);
   else
      PrintFormat("Stop Out level in currency %.2f %s",stopout_level,account_currency);


有关帐户的其他信息

在计算中经常需要知道提供给交易帐户的杠杆大小。您可以通过使用 AccountInfoInteger() 函数获得此信息:

   int leverage=(int)AccountInfoInteger(ACCOUNT_LEVERAGE);

为了避免在实际帐户上意外运行没有受到管控的 EA 交易,您需要知道帐户的类型。

   ENUM_ACCOUNT_TRADE_MODE mode=(ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE);
   switch(mode)
     {
      case ACCOUNT_TRADE_MODE_DEMO:    Comment("Account demo");               break;
      case ACCOUNT_TRADE_MODE_CONTEST: Comment(com,"Account Contest");        break;
      case ACCOUNT_TRADE_MODE_REAL:    Comment(com,"Account Real");           break;
      default:                         Comment(com,"Account unknown type");
     }

并不是每一个帐户都能交易,例如,在竞价帐户上,交易操作只能在竞价开始后才能进行。此信息也可以通过 AccountInfoInteger() 函数获得:

   bool trade_allowed=(bool)AccountInfoInteger(ACCOUNT_TRADE_ALLOWED);
   if(trade_allowed)
      Print("Trade is allowed");
   else
      Print(com,"Trade is not allowed");

即使允许在此帐户上进行交易,也不意味着 EA 交易有权交易。要检查 EA 交易是否有权交易,编写以下代码:

   if(trade_allowed)
     {
      bool trade_expert=(bool)AccountInfoInteger(ACCOUNT_TRADE_EXPERT);
      if(trade_expert)
         Print("Experts are allowed to trade");

      else
         Print("Experts are not allowed to trade");

可以在附带的 EA 交易 Account_Info.mq5 中找到这些例子。它们可以在任意复杂的 MQL5 程序中使用。

有关金融工具的信息

每个金融工具有其自己的说明,并且放在此工具指定的路径上。如果我们在客户端中打开 EURUSD 属性窗口,我们将看到如下所示的画面:

在这个例子中,EURUSD 的说明为 “EURUSD, Euro vs US Dollar”(EURUSD,欧元兑美元)。要获取此信息,我们使用 SymbolInfoString() 函数:

   string symbol=SymbolInfoString(_Symbol,SYMBOL_DESCRIPTION);
   Print("Symbol: "+symbol);

   string symbol_path=SymbolInfoString(_Symbol,SYMBOL_PATH);
   Print("Path: "+symbol_path);

要找出标准合约的大小,使用 SymbolInfoDouble():

   double lot_size=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_CONTRACT_SIZE);
   Print("Standard contract: "+DoubleToString(lot_size,2));

卖出一种货币的同时买入另一种货币是 FOREX 工具的一个特征。合约以执行购买所需的货币表示。这是基础货币,可使用 SymbolInfoString() 函数获得:

   string base_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_BASE);
   Print("Base currency: "+base_currency);

工具中的价格变动导致所购买资产的价格变动,进而导致未平仓位的盈利变化(如果仓位亏损,则盈利可以是负值)。因此,价格变动导致以具体货币表示的收入也出现变动。此货币被称为报价货币。对于货币对 EURUSD,基础货币为欧元,而报价货币为美元。要获得报价货币,您也可以使用 SymbolInfoString() 函数:

   string profit_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_PROFIT);

   Print("Currency quotes: "+profit_currency);

要在工具上建仓,您需要资金,这些基金也以某种货币表示。此货币被称为预付款货币或入金货币。对于 FOREX 工具,预付款和基础货币通常是一样的。要获取入金货币的值,使用 SymbolInfoString() 函数:

   string margin_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN);
   Print("Currency deposit: "+margin_currency);

EA 交易 Symbol_Info.mq5  的代码给出了以上所有函数。下图说明使用 Comment() 函数输出交易品种 EURUSD 信息。

计算入金数额

交易者最需要的金融工具的相关信息是在其上建仓所需的资金量。不知道买入或卖出指定手数需要多少资金,我们就不能用 EA 交易系统进行资本管理。此外,控制帐户余额也变得困难。

如果您难以理解进一步的讨论,我建议您阅读《外汇交易 ABC》一文。该文中的解释同样适用于本文。

我们需要计算以入金货币表示的预付款数额,即通过将获得的值除以给定帐户杠杆,从保证金货币到入金货币重新计算入金。为此,我们编写了 GetMarginForOpening() 函数:

//+------------------------------------------------------------------+
//|  返回开仓需要的净值数额                                             |
//+------------------------------------------------------------------+
double GetMarginForOpening(double lot,string symbol,ENUM_POSITION_TYPE direction)
  {
   double answer=0;
//--- 
    ...
//--- 返回结果:开指定仓量所需的,以帐户币种计算的净值数额
   return(answer);
  }

其中:

  • 手数 – 未平仓位的数量;
  • 交易品种 – 金融工具的名称;
  • 所谓的仓位方向。

因此,我们拥有用于计算预付款(未平仓位的保证金)数额的以下信息:

  • 入金货币
  • 保证金货币
  • 货币报价(交叉货币对可能需要)
  • 合约大小

以 MQL5 语言编写:

//--- 获取合约大小
   double lot_size=SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);

//--- 获取帐户的币种
   string account_currency=AccountInfoString(ACCOUNT_CURRENCY);

//--- 预付款的币种
   string margin_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN);

//--- 利润的币种
   string profit_currency=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_PROFIT);

//--- 计算的币种
   string calc_currency="";
//--- 反向报价 - true,直接报价 - false
   bool mode;

mode(模式)变量影响我们如何计算以入金货币表示的合约大小。依据示例考虑这一点,在以后所有的示例中,让我们假定入金货币为美元。

货币对通常分为三类:

  • 直接货币对 – 美元兑某种货币的汇率。例如:USDCHF、USDCAD、USDJPY、USDSEK;
  • 反向货币对 – 某种货币兑美元的汇率。例如:EURUSD、GBPUSD、AUDUSD、NZDUSD;
  • 交叉货币对 – 不涉及美元的货币对。例如:AUDCAD、EURJPY、EURCAD。

1. EURUSD – 反向货币对

我们将报价货币为帐户货币的货币对称为反向货币对。在我们的例子中,帐户货币以美元表示,因此我们的货币对分类将与普遍接受的分类一致。但是,如果您的交易帐户使用不同的货币(不是美元),则分类将不一致。在这种情况下,为了理解任何进一步的说明,请考虑帐户货币。

针对 EURUSD 的合约大小 – 100 000 欧元。我们以入金货币(美元)来表示 100 000 欧元。为此,您需要知道将欧元换算为美元所依据的汇率。我们引入了 计算货币 的概念,即将保证金货币换算为入金货币所需的货币。

//--- 计算的币种
   string calc_currency="";

很幸运,EURUSD 货币对会显示欧元兑美元的汇率,因此,对于这种情况,您需要为其计算保证金数额的交易品种 EURUSD 是精确的汇率:

//--- 如果利润币种和帐户币种一致
   if(profit_currency==account_currency)
     {
      calc_currency=symbol;
      mode=true;
     }

我们已经将 mode 的值设为 true,这表示将欧元转换为美元(保证金货币转换为入金货币),我们将当前的 EURUSD 汇率乘以合约大小。如果 mode  = false,则我们将合约大小除以计算货币的汇率。要获取工具上的当前价格,使用 SymbolInfoTick() 函数。

//---我们知道计算的币种,让我们获取它最新的价格
   MqlTick tick;
   SymbolInfoTick(calc_currency,tick);

此函数将当前价格和价格的上次更新时间设置 MqlTick 类型的变量 – 此结构专为此目的设计的。 

因此,获取此交易品种的最新价格,将其乘以合约大小,再乘以手数就足够了。但是,考虑到此工具有一个买入价和一个卖出价,我们应采用哪个计算价格?因此,从逻辑上:如果我们买入,则用于计算的价格等于卖出价 (Ask price),如果我们卖出,则我们将采用买入价 (Bid price)。

//--- 现在我们已具备计算所需的一切 
   double calc_price;
//--- 计算买单的情况
   if(direction==POSITION_TYPE_BUY)
     {
      //--- 反向报价
      if(mode)
        {
         //--- 用反向报价的买入价计算
         calc_price=tick.ask;
         answer=lot*lot_size*calc_price;
        }
     }

//--- 计算卖出的情况
   if(direction==POSITION_TYPE_SELL)
     {
      //--- 反向报价
      if(mode)
        {
         //--- 用反向报价的卖出价计算

         calc_price=tick.bid;
         answer=lot*lot_size*calc_price;
        }
     }

因此,在我们的例子中,对于交易品种 EURUSD,入金货币为欧元,合约大小为 100 000,最新卖出价 = 1.2500。帐户货币为美元,计算货币是相同的 EURUSD 货币对。如果卖出价 =1.2500,将 100 000 乘以 1.2500,得到 125 000 美元 – 这是买入 1 手 EURUSD 的标准合约所需的资金。

我们可以得出这样的结论:如果报价货币等于帐户货币,则要获得以帐户货币表示的一手的价值,我们只需要将合约大小乘以相应的价格(买入价或卖出价,视仓位的预期方向而定)。

margin=lots*lot_size*rate/leverage;

2. USDCHF – 直接货币对

USDCHF 的保证金货币和帐户货币是一样的,都是美元。对于保证金货币和帐户货币相同的货币对,我们称之为直接货币对。合约大小 – 100 000。这是最简单的情形,只需返回积即可。

//--- 如果基本币种和入金币种一致
   if(margin_currency==account_currency)
     {
      calc_currency=symbol;
      //--- 返回合约价值,乘以手数
      return(lot*lot_size);
     }

如果入金货币与帐户货币相同,则以帐户货币表示的入金值等于标准合约乘以手数(合约)的积再除以杠杆大小。

margin=lots*lot_size/leverage;

3. CADCHF – 交叉货币对

CADCHF 货币对用于说明目的,也可以使用入金货币与报价货币与帐户货币相同的任意其他货币对。这些货币对被称为交叉货币对,因为要计算它们的预付款和盈利,我们需要知道在其中一种货币上与该货币对相交的其他货币对的汇率。

一般情况下,交叉货币对是报价货币不使用美元的货币对。但是我们将在其报价中不包含帐户货币的所有货币对都称为交叉货币对。因此,如果帐户货币为欧元,则货币对 GBPUSD 将为一个交叉货币对,因为入金货币为英镑,而报价货币为美元。在这种情况下,要计算预付款,我们必须以欧元 (EUR) 来表示英镑 (GBP)。

但是我们将继续考虑一个交易品种为货币对 CADCHF 的例子。入金货币为加拿大元 (CAD),与美元 (USD) 不相同。报价货币为瑞士法郎,与美元也不相同。

我们只能说建 1 手仓的入金等于 100,000 加拿大元。我们的任务是将入金重新计算为美元。为此,我们需要找出其汇率包含美元和入金货币 – CAD 的货币对。总共有两种可能的选项:

  • CADUSD
  • USDCAD

我们拥有 CADCHF 的输出数据:

margin_currency=CAD (Canadian dollar)
profit_currency=CHF (Swiss frank)

我们事先不知道在客户端中存在哪一货币对,从 MQL5 语言的角度来看,任何一个选项都不是最佳的。因此,我们编写了 GetSymbolByCurrencies() 函数,对于给定的货币,该函数将向我们提供用于计算的第一匹配货币对。

//+------------------------------------------------------------------+
//| 返回指定预付款币种和利润币种的交易品种名称                             |
//+------------------------------------------------------------------+
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);
  }

如代码所示,我们开始枚举 “Market View”(市场视图)窗口中的所有交易品种(含有 “true” 参数的 SymbolsTotal() 函数将向我们提供此数量)。为了按在 “Market View”(市场视图)窗口中列表内的编号获取每个交易品种的名称,我们使用含有 true 参数的 SymbolName() 函数!如果我们将参数设为 “false”,则我们将枚举交易服务器中的所有交易品种,这通常比在客户端中选择的交易品种要多得多。

接下来,我们使用交易品种的名称获得入金货币和报价货币,并且将它们与传递到 GetSymbolByCurrencies() 函数的货币进行比较。如果成功,我们返回交易品种的名称,并且函数的工作成功地提前完成。如果循环完成,并且我们到达函数的最后一行,未发现合适对象,并且没有找到交易品种,-返回 NULL。

现在,我们可以通过使用 GetSymbolByCurrencies() 函数获得交叉货币对的计算货币,我们将进行两次尝试:在第一次尝试中,我们将查找其入金货币为margin_currency(CADCHF 的入金货币 – CAD),报价货币为帐户货币 (USD) 的交易品种。换言之,我们将查找类似于货币对 CADUSD 的交易品种。

//--- 如果计算币种仍旧不能确定
//--- 那么我们遇到的是交叉货币对
   if(calc_currency="")
     {
      calc_currency=GetSymbolByCurrencies(margin_currency,account_currency);
      mode=true;
      //--- 如果获得的值为空,那么这个交易品种不存在
      if(calc_currency==NULL)
        {
         //--- 让我们反过来尝试
         calc_currency=GetSymbolByCurrencies(account_currency,margin_currency);
         mode=false;
        }
     }

如果第一尝试失败,则尝试另一选项:查找其入金货币为 account_currency (USD)、报价货币为 margin_currency(CADCHF 的入金货币 – CAD)的交易品种。我们将查找类似于 USDCAD 的交易品种。

现在,我们找到计算货币对,它可能是两个选项之一:直接或反向。对于反向货币对,假定 mode 变量的值为 “true”。如果我们获得直接货币对,则该值等于 “false”。对于 “true” 值,我们将其乘以货币对的汇率,对于 false 值,我们将其除以用帐户货币表示的标准合约的入金值。

以下是以帐户货币表示的针对找到的计算货币的入金数额的最终计算。它适合两种情形:直接货币对和反向货币对。

//---我们知道计算的币种,让我们获取它最新的价格
   MqlTick tick;
   SymbolInfoTick(calc_currency,tick);

//--- 现在我们已具备计算所需的一切
   double calc_price;
//--- 计算买单的情况
   if(direction==POSITION_TYPE_BUY)
     {
      //--- 反向报价
      if(mode)
        {
         //--- 用反向报价的买入价计算
         calc_price=tick.ask;
         answer=lot*lot_size*calc_price;
        }
      //--- 直接报价 
      else
        {
         //--- 用直接报价的卖出价计算
         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);

GetMarginForOpening() 函数在此时完成其工作。需要完成的最后一件事情是将获得的值除以所提供杠杆的大小 – 然后,我们将获得在假定方向上指定数量的未平仓位的预付款值。请记住,对于表示反向或交叉货币对的交易品种,预付款值在每一即时价格都会有所变化。

以下是部分 SymbolInfo_Advanced.mq5  EA 交易代码。完整的代码作为一个随附文件。

//+------------------------------------------------------------------+
//| EA的tick函数                                                      |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 用于备注的字符串变量
   string com="/r/n";
   StringAdd(com,Symbol());
   StringAdd(com,"/r/n");

//--- 标准合约的大小
   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,"/r/n");

//--- 杠杆
   int leverage=(int)AccountInfoInteger(ACCOUNT_LEVERAGE);
   StringAdd(com,StringFormat("Leverage: 1/%d",leverage));
   StringAdd(com,"/r/n");

//--- 按帐户币种计算的合约价值
   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);
  }

以及其在图表上的运行结果。

总结

提供的例子说明了获得交易帐户最重要的特征以及金融工具的属性的相关信息有多么容易、多么简单。

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/113

附加的文件 |

下载ZIP
account_info.mq5
(7.59 KB)
symbol_info.mq5
(4.21 KB)
symbolinfo_advanced.mq5
(9.95 KB)

 

 


MyFxtop迈投-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投