下载MetaTrader 5

如何通过 MetaTrader 5 进行外部数字加密货币的交易

15 十二月 2017, 08:31
o_o
0
2 908
不久之前,MQL5 语言的开发者引入了更新的功能,可以开发自定义交易品种和图表,大多数交易者社区还没有时间来体验这种创新的强大,但是只要简明扼要地讨论一下,就可以发现隐藏在自定义交易品种中的巨大潜力。与其他 MQL 工具一起,它们可以使您实现很多最大胆而有趣的想法。

从现在开始,MetaTrader 5 已经不仅仅是与一个数据中心交互的终端,它是一个自我支持的分析平台,可以通过 API(应用程序接口) 来与各种交易市场相连接,并且使价格变化和交易数据流可视化。一小部分新功能把终端变成了一个开放的系统,而超越了包含有限数量交易工具的工具箱。以我的观点,自定义工具也可以变得具有强大的分析功能。

让我们使用热门的数字加密货币主题作为例子,来展示这种语言的新特性,我相信,这将会进一步加强社区对自定义交易品种的兴趣。

可能从本文获益的读者:

  • 数字加密货币交易者;
  • 熟悉 MetaTrader 5 和证券投资的投资者;
  • 自由职业开发人员,他们现在可以使用更简单(并且更便宜)的方法来执行客户关于数字加密货币的交易了;
  • 每个追随 MetaTrader 5 MQL5 语言新特性的人。

首先,我们需要选择一个 web API 来进行数字加密货币的交易。

当开发我的一个代码库产品时, 我曾经使用了 BTC-e, 它现在已经不能用了,所以,我决定换另外一个 — . 它的 API 特性已经足够可以用于演示 MQL 新的和已经存在的功能,包括下载柱形、价格数据流、市场深度,查看当前账户订单和仓位以及订单和交易历史。de history.

让我们专注于下面的计划。

  1. 描述所有由 web 请求返回的数据结构,
  2. 开发用于连接交易所的类,这些类应当实现与它们访问点的 WebRequest。
  3. 开发一个EA交易,它可以在 MetaTrader 5 中接收柱形和数字加密货币价格的更新数据。
  4. 开发用于操作订单和仓位的脚本和EA交易。

1. 交易所数据的 API 结构


与其他一些数字加密货币交易所类似,BITFINEX 有两个访问点,

  • 第一个(即一种请求格式的集合) 提供了用于交换的价格数据 — 柱形,分时,市场深度。数据是通用和匿名的,所以它们是不是用任何签名和认证就可以请求到的。
  • 第二个访问点是由您的账户提供数据 — 账户状态,建立的仓位,挂单,交易历史。这些数据是私有的,所以您应当在请求头部使用您从交易所个人交易处取得的 API 密钥发送 SHA384-HMAC 数字签名,这可以保证请求是由您发送的,而不是由冒用您名字的攻击者请求的。

这两个访问点都是以使用JSON格式的 REST 方式工作的 (注意: 在这里没有考虑 WebSockets)。这对读取数据和写 API 来说都很方便。

交易所有两个协议版本,第二个版本当前还在 beta 模式,它的出现有两个原因,首先,有新的功能 (请求柱形,操作提醒、订单的幻数,等等). 第二个原因也是在 JSON 结构格式中所显而易见的 - 它可以节约流量。让我们比较一下在第一个和第二个协议版本中如何回应分时请求 (当前交易品种数据):

 版本 1
版本 2
{

  "mid":"244.755",
  "bid":"244.75",
  "ask":"244.76",
  "last_price":"244.82",
  "low":"244.2",
  "high":"248.19",
  "volume":"7842.11542563",
  "timestamp":"1444253422.348340958"

}
[
  BID, 
  BID_SIZE, 
  ASK, 
  ASK_SIZE, 
  DAILY_CHANGE, 
  DAILY_CHANGE_PERC, 
  LAST_PRICE, 
  VOLUME, 
  HIGH, 
  LOW
]

您可以看到,第二个版本避免了栏位的命名,但是它们的位置是固定的,也就意味着这里使用的是数组,而不是对象。在本文中,我展示的例子将支持交易所的两个 API 版本。

首先,让我们看看每个协议版本我们应当使用怎样的请求和结构来打开访问点。

对于版本 1:

对于版本 2:

让我首先使用 SymbolDetails 请求作为例子来解释一下这些结构是怎样创建的,其它数据结构实现在 BFxDefine.mqh 文件中。如果您需要其他未包含在文章中的 API 请求,您可以使用类似的方式引入它们。

在文档中的内容
在 BFxDefine.mqh 中的内容
[
  {
    "pair":"btcusd",
    "price_precision":5,
    "initial_margin":"30.0",
    "minimum_margin":"15.0",
    "maximum_order_size":"2000.0",
    "minimum_order_size":"0.01",
    "expiration":"NA"
  },

  ...

]
struct bfxSymbolDetails
{
  string pair;
  int price_precision;
  double initial_margin;
  double minimum_margin;
  double maximum_order_size;
  double minimum_order_size;
  string expiration;
};
struct bfxSymbolsDetails
{
  bfxSymbolDetails symbols[];
};

文档显示,请求会返回对象数组,所以,我已经创建了两个结构。第一个 (bfxSymbolDetails) 在一个单独数组对象中分析和保存数据,第二个 (bfxSymbolsDetails ) 保存所收到的 bfxSymbolDetails 对象数组,并在 web 请求中直接使用它。换句话说, JSON <-> MQL 格式匹配很简单,就像镜像一样,可以扩展到所有文档中。

当操作交易所 API 时, 我们将使用两个类,它们有相同的 CBFx 父类。它的目的是封装相同的数据栏位以及通用的 web 请求函数。

//------------------------------------------------------------------    class CBFx
class CBFx
  {
protected:
   string            m_answer;        // 请求的结果 (在 JSON 去序列化之前)
   enBFxRequestResult m_lastrr;       // 请求结果的代码
   CJAVal            m_lastjs;        // 请求去序列化的结果
public:
                     CBFx() { }

protected:
   string URL() { return "https://api.bitfinex.com/"; }
   string Answer() { return m_answer; }
   enBFxRequestResult Request(string mode,string url_request,string head,string body,char &result[])
     {
      char data[]; int n=StringLen(body); if(n>0) StringToCharArray(body,data,0,n); string res_header="";
      int r=WebRequest(mode, url_request, head, 10000, data, result, res_header);
      if(r<=0) return rrErrorWebRequest;
      return r==200?rrOK:rrBadWebRequest;
     }
  };

CBFx 类有两个继承类:

  • CBFxPublic — 请求公共数据的 API (柱形,报价,市场深度);
  • CBFxTrade — 请求私有数据的 API (交易,账户数据)。

这些继承类会在下面两个章节中介绍。

所有的操作交易所 API 的准备工作都完成了,现在,让我们在 MetaTrader 5 平台中实现它们。

2. CustomSymbol

现在自定义交易品种的状况是怎样的呢?操作它们与操作通常的交易品种几乎是完全相同的,整个管理部分是由一小部分函数来执行的。

我们工作的目标就是创建一个交易所交易品种,上传柱形历史和不断发送价格,所以,我们需要下面的 MQL 函数:

不幸的是,WebRequest 不能从指标中调用,所以,让我们开发一个 EA 交易来不断请求交易品种柱形和当前价格,然后再在 MetaTrader 5 中更新它们。

交易所和 MetaTrader 5 的交互如下:

OnInit

  • 在 MetaTrader 5 中检查交易品种是否存在SymbolSelect.
    • 如果没有找到交易品种 -> 请求指定交易品种的 SymbolDetails
    • 创建 CustomSymbolCreate 交易品种并且填充它的属性 CustomSymbolSetХХХ。
  • 检查 М1 历史.
    • 如果需要下载 -> 使用所需数量的柱形来请求 CandleHist
    • 加上 CustomRatesUpdate 个柱
  • 启动计时器

OnTimer

  • 请求 Ticker 价格 -> 更新 CustomTicksAdd 分时
  • 请求最近柱 CandleLast -> 更新 CustomRatesUpdate

这就是整个工作流程。

让我们完成工作,来得到更多可以运行的例子。为此,生成请求并在市场深度 (OrderBook) 中显示时间和交易 (TradesHist). 时间和交易是在图表中以注释方式显示的,而市场深度是在对应的价格水平上分段显示的。

公用 API 是在 CBFxPublic 类中实现的。

#include "BFxDefine.mqh"
//------------------------------------------------------------------    class CBFxPublic
class CBFxPublic: public CBFx
  {
public:
                     CBFxPublic() {  }
protected:
   virtual enBFxRequestResult Request(string url_request);
public:
   enBFxRequestResult Symbols(bfxSymbols &ret);
   enBFxRequestResult SymbolsDetails(bfxSymbolsDetails &ret);
   enBFxRequestResult OrderBook(string pair,int depth,bool group,bfxOrderBook &ret);

public:
   enBFxRequestResult CandleHist(string pair,enbfcTimeFrame tf,int limit,bfxCandles &ret);
   enBFxRequestResult CandleLast(string pair,enbfcTimeFrame tf,MqlRates &ret);
   enBFxRequestResult TradesHist(string pair,int limit,bfxTrades &ret);
   enBFxRequestResult Ticker(string pair,bfxTicker2 &ret);
  };

让我们看一看 CandleHist  来解释它的工作.

文档描述:

通用的请求格式: https://api.bitfinex.com/v2/candles/trade::TimeFrame::Symbol/Section

请求回应:

[   [ MTS, OPEN, CLOSE, HIGH, LOW, VOLUME ],   ... ]

在这里查找请求参数: https://docs.bitfinex.com/v2/reference#rest-public-candles

请求的例子: https://api.bitfinex.com/v2/candles/trade:1m:tBTCUSD/hist?limit=50

CandleHist MQL 实现:

//------------------------------------------------------------------    CandleHist
enBFxRequestResult CBFxPublic::CandleHist(string pair,enbfcTimeFrame tf,int limit,bfxCandles &ret)
  {
   Request(URL()+"v2/candles/trade:"+bfcTimeFrame[tf]+":t"+STU(pair)+"/hist"+(limit>0?"?limit="+string(limit):""));
   if(m_lastrr==rrOK) m_lastrr=ret.FromJson(m_lastjs)?rrSuccess:rrErrorStruct;
   return m_lastrr;
  }

CandleHist 函数生成 GET 请求. 因为我们对分钟柱有兴趣,请求文本行最终看起来如下: https://api.bitfinex.com/v2/candles/trade:1m:tXXXXXX/hist. 在回应中,我们从历史中取得了最近的1000个柱。

这些使用 API 请求的操作结果是一个简单的 EA 交易,用来构建柱形并在图表上移动价格。

#include "..\BFxAPI\BFxPublicAPI.mqh"

input string Pair="btcusd";
input int ShowBookDepth=0;
input bool ShowTimeSales=false;

CBFxPublic bfx;
string mtPair;
//------------------------------------------------------------------    OnInit
int OnInit()
  {
// 为 MT 交易品种创建名称
#ifdef __MQL4__
   mtPair=StringToUpper(Pair);
#endif          
#ifdef __MQL5__
   mtPair=Pair; StringToUpper(mtPair);
#endif
   mtPair+=".bfx";

// 在 MarketWatch 中选择交易品种
   bool bnew=false;
   if(!SymbolSelect(mtPair,true))
     {
      bfxSymbolsDetails sd;
      enBFxRequestResult brr=bfx.SymbolsDetails(sd);
      if(brr!=rrSuccess) return INIT_FAILED;
      for(int i=0; i<ArraySize(sd.symbols);++i)
        {
         bfxSymbolDetails ss=sd.symbols[i];
         if(ss.pair==Pair)
           {
            bnew=true;
            CustomSymbolCreate(mtPair,"BITFINEX");
            CustomSymbolSetString(mtPair,SYMBOL_ISIN,Pair);
            CustomSymbolSetInteger(mtPair,SYMBOL_DIGITS,ss.price_precision);
            CustomSymbolSetDouble(mtPair,SYMBOL_MARGIN_INITIAL,ss.initial_margin);
            CustomSymbolSetDouble(mtPair, SYMBOL_VOLUME_MAX, ss.maximum_order_size);
            CustomSymbolSetDouble(mtPair, SYMBOL_VOLUME_MIN, ss.minimum_order_size);
            CustomSymbolSetDouble(mtPair,SYMBOL_VOLUME_STEP, ss.minimum_order_size);
           }
        }
      if(!SymbolSelect(mtPair, true)) return INIT_FAILED;
     }
   if(Symbol()!=mtPair) ChartSetSymbolPeriod(0,mtPair,bnew?PERIOD_M1:Period());

// 载入一些历史
   datetime adt[]; ArraySetAsSeries(adt,true);
   int limit=1000;
   if(CopyTime(mtPair,PERIOD_M1,0,1,adt)==1)
      limit=(int)fmax(2,fmin((TimeCurrent()-adt[0])/60,1000));

   bfxCandles bars;
   enBFxRequestResult brr=bfx.CandleHist(Pair,tf1m,limit,bars);
   if(brr==rrSuccess) CustomRatesUpdate(mtPair,bars.rates);

// 启动计时器
   EventSetTimer(3);
   return INIT_SUCCEEDED;
  }
//------------------------------------------------------------------    OnDeinit
void OnDeinit(const int reason) { EventKillTimer(); ObjectsDeleteAll(0,Pair,0); if(ShowTimeSales) Comment(""); }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
// 取得最近的分时数据 
   bfxTicker2 bt;
   MqlTick tick[1]={0};
   tick[0].time=TimeCurrent();
   tick[0].time_msc=TimeCurrent();
   if(bfx.Ticker(Pair,bt)==rrSuccess)
     {
      tick[0].flags=TICK_FLAG_BID|TICK_FLAG_ASK|TICK_FLAG_VOLUME|TICK_FLAG_LAST;
      tick[0].bid=bt.bid; tick[0].ask=bt.ask; tick[0].last=bt.last; tick[0].volume=(long)bt.day_vol;
      CustomTicksAdd(mtPair,tick); ChartRedraw();
     }

// 取得最近的柱形
   MqlRates rate[1];
   if(bfx.CandleLast(Pair,tf1m,rate[0])==rrSuccess)
     {
      rate[0].spread=int((tick[0].ask-tick[0].bid)*MathPow(10,_Digits));
      CustomRatesUpdate(mtPair,rate);
      if(tick[0].flags>0) { tick[0].last=rate[0].close; CustomTicksAdd(mtPair,tick); }
      ChartRedraw();
     }

   if(ShowBookDepth>0) ShowBook(ShowBookDepth);
   if(ShowTimeSales) TimeSales();
  }
//------------------------------------------------------------------ ShowBook
void ShowBook(int depth)
  {
   bfxOrderBook bk;
   if(bfx.OrderBook(Pair, depth, true, bk)!=rrSuccess) return;
   if(ArraySize(bk.asks)>0)
     {
      for(int i=0; i<ArraySize(bk.asks);++i)
         SetLine(Pair+"Asks"+IntegerToString(i),TimeCurrent(),bk.asks[i].price,TimeCurrent()+20*PeriodSeconds(),
                 bk.asks[i].price,clrDodgerBlue,1,STYLE_DOT,i==0?DoubleToString(bk.asks[i].volume,1):"");
     }
   if(ArraySize(bk.bids)>0)
     {
      for(int i=0; i<ArraySize(bk.bids);++i)
         SetLine(Pair+"Bids"+IntegerToString(i),TimeCurrent(),bk.bids[i].price,TimeCurrent()+20*PeriodSeconds(),
                 bk.bids[i].price,clrRed,1,STYLE_DOT,i==0?DoubleToString(bk.bids[i].volume,1):"");
     }
  }
//------------------------------------------------------------------ TimeSales
void TimeSales()
  {
   bfxTrades tr;
   string inf="";
   if(bfx.TradesHist(Pair,10,tr)==rrSuccess)
     {
      for(int i=0; i<ArraySize(tr.trades);++i)
        {
         bfxTrade t=tr.trades[i];
         inf+="\n   "+IntegerToString(t.id)+"  |  "+TimeToString(t.mts,TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"  |  "+DoubleToString(t.price,_Digits)+
              "  |  "+DoubleToString(fabs(t.amount),2)+"  |  "+(t.amount>0?"Buy ":"Sell");
        }
     }
   Comment(inf);
  }
//------------------------------------------------------------------ SetLine
void SetLine(string name,datetime dt1,double pr1,datetime dt2,double pr2,color clr,int width,int style,string st)
  {
   ObjectCreate(0,name,OBJ_TREND,0,0,0); ObjectSetInteger(0,name,OBJPROP_RAY,false);
   ObjectSetInteger(0,name,OBJPROP_TIME,0,dt1); ObjectSetDouble(0,name,OBJPROP_PRICE,0,pr1);
   ObjectSetInteger(0,name,OBJPROP_TIME,1,dt2); ObjectSetDouble(0,name,OBJPROP_PRICE,1,pr2);
   ObjectSetInteger(0,name,OBJPROP_WIDTH,width); ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
   ObjectSetString(0,name,OBJPROP_TEXT,st); ObjectSetInteger(0,name,OBJPROP_STYLE,style);
  }
//------------------------------------------------------------------ SetArrow
void SetArrow(string name,datetime dt,double pr,color clr,int width,string st)
  {
   ObjectCreate(0,name,OBJ_ARROW_LEFT_PRICE,0,dt,pr);
   ObjectSetInteger(0,name,OBJPROP_TIME,0,dt); ObjectSetDouble(0,name,OBJPROP_PRICE,0,pr);
   ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
   ObjectSetString(0,name,OBJPROP_TEXT,st); ObjectSetInteger(0,name,OBJPROP_WIDTH,width);
  }
//+------------------------------------------------------------------+

首先,要确保在调试模式下载入它,然后单步执行所有的调用。检查 API 返回的内容, 回应是如何分析和显示数据的。

EA 交易在 MetaTrader 5 中到底是做什么的呢?

在执行了 OnInit 函数之后, BTCUSD.bfx 交易品种在 BITFINEX: 部分中创建


然后,最近的柱形和分时数据由计时器函数上传:

整个算法可以简化到下面的样子:

  • -> 发送 WebRequest;
  • -> 分析取得的 JSON;
  • -> 更新交易品种数据.

您可以看到,使用公用 API 并不复杂,您也可以自己实现所需的其它函数。

在写这篇文章的时候,平台开发人员在准备另一个 MQL5 更新,有功能来通过 SYMBOL_FORMULA 属性来操作交易品种,这个新功能将让使用简单数学公式来开发新的交叉汇率更加简单。

3. 自动化工具

在此,我将展示如何请求交易所的私有 API 来操作订单和仓位。用于操作访问点的函数集中在单独的 CBFxTrade 类中。

#include "SHA384.mqh"
#include "BFxDefine.mqh"
//------------------------------------------------------------------    class CBFxTrade
class CBFxTrade : public CBFx
  {
protected:
   string            m_key;
   string            m_secret;
public:
                     CBFxTrade(string key, string secret) { Init(key, secret); }
   void Init(string key, string secret) { m_key=key; m_secret=secret; }

protected:
   virtual enBFxRequestResult Request1(string req, const CJAVal* prm=NULL);
   virtual enBFxRequestResult Request2(string req, const CJAVal* prm=NULL);
   
   ulong Nonce() { ulong n=(ulong)GlobalVariableGet("bfx."+m_key)+1; GlobalVariableSet("bfx."+m_key,n); return n; };
   void ResetNonce() { double n=fmax(GlobalVariableGet("bfx."+m_key)+1,TimeTradeServer()); GlobalVariableSet("bfx."+m_key,n); };

   // v1
public:
   enBFxRequestResult AccountInfo(bfxAccInfo &ret);
   enBFxRequestResult OrdersHist(bfxOrders &ret);
   enBFxRequestResult ActiveOrders(bfxOrders &ret);
   enBFxRequestResult OrderStatus(const long order_id,bfxOrder &ret);
   enBFxRequestResult NewOrder(string symbol,double amount,double price,string side,string type,bfxOrder &ret);
   enBFxRequestResult CancelOrder(const long order_id,bfxOrder &ret);
   enBFxRequestResult ReplaceOrder(const long order_id,string symbol,double amount,double price,string side,string type,bfxOrder &ret);

   enBFxRequestResult Positions(bfxPositions &ret);
   enBFxRequestResult Balances(string currency,bfxBalances &ret);

   // v2
public:
   enBFxRequestResult AlertList(bfxAlerts &ret);
   enBFxRequestResult AlertSet(string symbol,double price,bfxAlert &ret);
   enBFxRequestResult AlertDel(string name/*price:SMB:price*/);
  };

请求的创建和 CBFxPublic 类中完全相同。我相信,在此对于初学者唯一的难点就是填充请求的头部。和共有 API 不同, 您需要传入使用密钥哈希签名的 SHA384-HMAC 请求,在 API 版本1中, 请求还要另外转换为 Base64。

根据文档, 签名的哈希是使用下面的顺序创建的:

  • 以 json 格式创建请求体 => body
  • body  重新以 Base64 编码 => bodyBase64
  • 使用密钥计算 bodyBase64 哈希 => signature
  • 在请求头部发送 bodyBase64 signature

bodyBase64 是使用标准 MQL 函数 CryptEncode,以 CRYPT_BASE64 为参数得到的. 为了得到 signature, 要使用本文附件中的 SHA384 类。

这样,请求私有 API 版本1 的函数看起来如下:

//------------------------------------------------------------------    请求
enBFxRequestResult CBFxTrade::Request1(string req,const CJAVal *prm)
  {
   CJAVal js; m_lastjs.Clear();
   if(CheckPointer(prm)!=POINTER_INVALID) js=prm;      // 复制额外的栏位
   js["request"]="/v1/"+req;
   js["nonce"]=string(Nonce());
   string body=js.Serialize();                         // 序列化到 JSON

   uchar cin[],ck[],cout[]; StringToCharArray(body,cin);
   CryptEncode(CRYPT_BASE64,cin,ck,cout);
   string payload=CharArrayToString(cout);
   string signature=SHA384::hmac(payload,m_secret);

   string head="";
   head+="Content-Type: application/json\n";
   head+="Accept: application/json\n";
   head+="X-BFX-APIKEY:"+m_key+"\n";
   head+="X-BFX-PAYLOAD:"+payload+"\n";
   head+="X-BFX-SIGNATURE:"+signature+"\n\n";

   uchar result[];
   m_lastrr=Request("POST", URL()+"v1/"+req, head, body, result);
   m_answer=CharArrayToString(result);
   if(m_lastrr==rrOK) m_lastrr=m_lastjs.Deserialize(result)?rrOK:rrErrorJson;
   else
     {
      Print("req:"+req+", res:"+m_answer);
      if(StringFind(m_answer,"Nonce is too small")>=0) ResetNonce();
     }
   return m_lastrr;
  }


请求私有 API 版本 2 的请求顺序几乎是一样的,尽管没有 Base64 和其它头部的名称。

//------------------------------------------------------------------    Request
enBFxRequestResult CBFxTrade::Request2(string req,const CJAVal *prm)
  {
   CJAVal js; m_lastjs.Clear();
   if(CheckPointer(prm)!=POINTER_INVALID) js=prm;
   string body=js.Serialize();
   string apiPath="v2/auth/"+req;
   string nonce=string(Nonce());
   string signature="/api/"+apiPath+nonce+body;
   signature=SHA384::hmac(signature,m_secret);

   string head="";
   head+="content-type: application/json\n";
   head+="accept: application/json\n";
   head+="bfx-nonce:"+nonce+"\n";
   head+="bfx-signature:"+signature+"\n";
   head+="bfx-apikey:"+m_key+"\n\n";

   uchar result[];
   m_lastrr=Request("POST", URL()+apiPath, head, body, result);
   m_answer=CharArrayToString(result);
#ifdef DEBUG    
   Print(m_answer);
#endif
   if(m_lastrr==rrOK) m_lastrr=m_lastjs.Deserialize(result)?rrOK:rrErrorJson;
   else
     {
      Print("req:"+req+", res:"+m_answer);
      if(StringFind(m_answer,"Nonce is too small")>=0) ResetNonce();
     }
   return m_lastrr;
  }


Nonce 参数会单独提到。它在公有 API 中不需要, 而它的目的很简单 - 指出我们正在发送一个没有被交易所处理的新的请求。Nonce  是一个简单的增量计数器,它的值在每个 web 请求中都比前一个大。

如果您发送的 Nonce 因为出错而等于或者小雨之前的数值,API 会返回错误 10100 — Nonce 太小了。不幸的是,API 没有发送所期待的数值 (和其它交易所不同). 所以,为了第一次重置到正确的数值,要把当前时间赋给它,并且在随后的 web 请求中再增加它。

对私有访问点的请求我们将会使用:

// 交易状态:
// 交易:
// 提醒:

实例 1. 接收账户仓位和订单的数据

因为我们要尝试在最大程度上自动交易,查看现在仓位和活动订单有何状况是非常重要的。这可以通过 API 请求做到:

我们可以看到,这些请求很简单。在 Order Status 中只需要一个参数 (我们所需的订单编号)。

我可以做来演示的最简单的内容就是在图表上以表哥的形式显示数据。我将不会使用图形对象,因为这是一个很简单的人物,当前的文章有另外不同的目的。在伪码中,接收和显示列表看起来如下:

CBFxTrade bfxt(Key, Secret);                     // 使用访问密钥初始化私有 API 
bfxOrders hist;
bfxt.OrdersHist(hist);                           // 从历史中请求订单
for (int i=0; i<ArraySize(hist.order); ++i)      // 遍历接收到的列表并在图表上显示它
  {
   // display hist.order[i];
  }

请求另外的数组 — bfxt.Positions bfxt.ActiveOrders — 是以相同方式进行的。

脚本执行结果:

现在您有了您所需要的所有的订单和仓位的数据,您的EA就可以根据它的交易逻辑来进行交易决定了。

实例 2. 操作提醒

现在,让我们忘记交易一会儿,来探讨一个对我们测试私有 API 有用的例子。提醒发送机制使您可以在账户中安全发送请求来创建/删除对象。例如,您可以使用提醒而不是交易。交易所将会向您的电脑或者移动应用程序中发送提醒信号来提示您操作。

API 提供了这样的三个请求:

我已经以脚本程序的方式实现了它们中的每一个。它们都可以在本文的附件中找到。让我们探讨创建一个提醒 — Alert Set. 根据文档, 我们需要传入交易品种和价格来进行一个请求。为了不要总从用户处请求这些参数, 我们将会使用图表中的交易品种,而价格将是把脚本应用到图表时的价格。

#include "..\..\BFxAPI\BFxTradeAPI.mqh"
//------------------------------------------------------------------    OnInit
void OnStart()
  {
   double price=ChartPriceOnDropped();
   string symbol=SymbolInfoString(Symbol(),SYMBOL_ISIN);
   if(StringLen(symbol)<=0)
    {
     symbol=Symbol();
     if(StringFind(symbol,".")>=0) symbol=StringSubstr(symbol,0,StringFind(symbol,"."));
    }

   if(MessageBox("Set new Alert on "+symbol+" with price "+DoubleToString(price,5)+"?", "Set Alert?", MB_YESNO|MB_ICONQUESTION)!=IDYES)
      return;

   CBFxTrade bfxt(MY_KEY,MY_SECRET);
   bfxAlert o;
   if(bfxt.AlertSet(symbol,price,o)!=rrSuccess)
    {
     MessageBox("错误:\n"+bfxt.Answer(),"Error set alert",MB_ICONERROR);
     return;
    }
   MessageBox("成功:\n"+o.symbol+" on "+DoubleToString(o.price,5)+", current "+DoubleToString(o.curprice,5),"Success");
  }

在所有的MessageBox 中的问题解决后,准备数据,我们只需要向交易所发送请求 bfxt.AlertSet. 如果发送成功,我们就接收到新生成的 bfxAlert 对象。

MetaTrader 5 有 100% 的以上方法的替代方案:

MetaTrader 5 内建的提醒系统不仅可以在本地激活提醒,还可以通过电子邮件,文件或者推送通知把它们发送到手机,换句话说,交易平台提供了比上面所述方法好得多的功能。

实例 3. 操作订单和仓位

我们最终到了最后的也是最重要的例子 — 直接在 MetaTrader 5 中进行交易所的交易操作。

我决定实现代码,制作从图表上管理仓位的EA交易。面板的开发不是我们的主要目标,但是它和一系列脚本程序相比更加方便。EA交易工作的常规角度显示在下面的屏幕截图中:

EA的操作:

  • 在表格中和图表文字中显示挂单和仓位;
  • 当移动线形时修改订单价格;
  • 删除挂单;
  • 关闭仓位;
  • 当在图表上放置水平线时设置限价挂单。

在整体感觉上,所有的操作和API调用都集中在 OnChartEvent.

  • OBJECT_CREATE 事件使我们可以侦测到创建新的图表线来创建一个新的限价挂单。
  • OBJECT_DRAG 事件使我们侦测到 "我们的" 线的移动并修改挂单。
  • 使用 OBJECT_CLICK 事件,我们侦测到控制按钮被点击,并在关闭仓位或者删除订单时更新表格。

EA 事件函数的代码看起来如下:

//------------------------------------------------------------------ OnInit
int OnInit()
{
 Refresh();
 ChartSetInteger(0,  CHART_EVENT_OBJECT_CREATE, true);
 return INIT_SUCCEEDED;
}
//---------------------------------------------------------------   OnChartEvent
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
{
 switch (id)
 {
 case CHARTEVENT_OBJECT_CREATE:
  if (ObjectGetInteger(0, sparam, OBJPROP_TYPE)==OBJ_HLINE)
   if (StringLen(ObjectGetString(0, sparam, OBJPROP_TEXT))<=0)
   {
    NewOrder(sparam);
    ObjectDelete(0, sparam); ChartRedraw();
   }
  break;

 case CHARTEVENT_OBJECT_DRAG:
  if (ObjectGetInteger(0, sparam, OBJPROP_TYPE)==OBJ_HLINE)
   if (StringFind(sparam, "_ord_")>0)
    ReplaceOrder(sparam);
  break;

 case CHARTEVENT_OBJECT_CLICK:
  if (ObjectGetInteger(0, sparam, OBJPROP_TYPE)==OBJ_BUTTON)
  {
   if (StringFind(sparam, "_upd_")>0) Refresh();
   if (StringFind(sparam, "_del_")>0) CancelOrder(sparam);
   if (StringFind(sparam, "_cls_")>0) ClosePos(sparam);
   ObjectSetInteger(0, sparam, OBJPROP_STATE, 0);
  }
  break;
 }
}

您可以在 BFxTrading.mq5 文件中找到完整代码。简短的动画展示了结果:

您完成了!

我们的工具箱已经使用新特性加强了,MetaTrader 5 已经成为外部交易所的平台,如果您对操作自定义交易品种有经验,并且使用 web 请求来集成外部交易,请在留言中分享它。

我祝您好运以及交易顺利!

附件中的文件

 #  名称 类型
 描述
 1  \Experts\BFx\BFxAPI\  
 交易所 API 文件
 1.1
 \Experts\BFx\BFxAPI\BFxDefine.mqh  包含文件  API 常数和定义
 1.2  \Experts\BFx\BFxAPI\BFxPublicAPI.mqh  包含文件  用于操作公有访问点的类
 1.3  \Experts\BFx\BFxAPI\BFxTradeAPI.mqh  包含文件  用于操作私有访问点的类
 1.4  \Experts\BFx\BFxAPI\JAson.mqh  包含文件  JSON 格式序列化类
 1.5  \Experts\BFx\BFxAPI\SHA384.mqh  包含文件
 SHA + HMAC 函数哈希类
 2  \Experts\BFx\Private\    操作私有 API 的例子
 2.1  \Experts\BFx\Private\_BFxTradeInfo.mq5  脚本程序
 getting the current list of positions and orders
 2.2  \Experts\BFx\Private\Alert\_BFxAlertSet.mq5  脚本程序  adding a notification to an account
 2.3  \Experts\BFx\Private\Alert\_BFxAlertList.mq5  脚本程序  从账户中取得通知列表
 2.4  \Experts\BFx\Private\Alert\_BFxAlertDeleteAll.mq5  脚本程序  删除所有的帐户提醒
 2.5  \Experts\BFx\Private\Trading\_BFxOrderStatus.mq5  脚本程序  取得订单数据
 2.6  \Experts\BFx\Private\Trading\_BFxOrderNew.mq5  脚本程序  创建一个订单
 2.7  \Experts\BFx\Private\Trading\_BFxOrderCancel.mq5  脚本程序  删除一个订单
 2.8  \Experts\BFx\Private\Trading\_BFxTrading.mq5  EA  这个 EA 交易可以创建、修改和删除帐户中的订单和仓位
 3  \Experts\BFx\Public\    操作公有 API 的例子
 3.1  \Experts\BFx\Public\_BFxCustomSymbol.mq5  EA  


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

附加的文件 |
MQL5.zip (31.52 KB)
利用卡尔曼 (Kalman) 滤波器预测价格方向 利用卡尔曼 (Kalman) 滤波器预测价格方向

为了成功交易, 我们几乎总是需要指标来把主要价格走势与噪音波动剥离。在本文中, 我们考察最有前途的数字滤波器之一, 卡尔曼滤波器。本文将介绍如何绘制和使用滤波器。

运用 R-平方 评估策略余额曲线的品质 运用 R-平方 评估策略余额曲线的品质

本文介绍如何构建自定义优化标准 R-平方。这一准则可用来评估一个策略的余额曲线的品质, 并选择增长最平滑和稳定的策略。这项工作讨论其构建原理, 以及用于评估属性和衡量品质的统计方法。

将入场信息解析到指标 将入场信息解析到指标

交易者的生活中会出现不同的状况。经常地, 成功交易的历史令我们能够复现策略, 而查看亏损历史, 让我们尝试开发和改进新的策略。在这两种情况下, 我们要将交易与已知指标进行比较。本文推荐了一批拿交易与数个指标进行比较的方法。

利用解析入场点为指标的技术创建新的交易策略 利用解析入场点为指标的技术创建新的交易策略

本文提出了一种技术, 通过汇集一套独立的指标, 以及开发定制的入场信号, 帮助每个人创建定制的交易策略。