Download MetaTrader 5

How to trade on an external cryptocurrency exchange via MetaTrader 5

12 December 2017, 14:04
o_o
17
11 728

Not so long ago, the MQL5 language developers have introduced the updated functionality featuring the ability to develop custom symbols and charts. The broad traders' community has not yet had time to appreciate the power of this innovation, but even an easy and unobtrusive brainstorm shows an enormous potential hidden in the custom symbols. Together with other MQL tools, they allow you to implement many of the most daring and interesting ideas.

From now on, MetaTrader 5 is not just a terminal that interacts with one DC. Instead, it is a self-sufficient analytical platform able to connect to various exchanges via the API, as well as visualize price movements and trade flows. A small set of new features turns the terminal into an open system rather than a toolbox containing a limited number of trading tools. In my opinion, custom tools can also become powerful analytical capabilities.

Let's illustrate the new language features using the popular subject of cryptocurrencies as an example. I believe, it will further strengthen the community's interest in custom symbols.

Who might benefit from the article:

  • cryptocurrency exchange traders;
  • investors familiar with MetaTrader 5 and portfolio investments;
  • freelance programmers who can now execute the customers' orders related to cryptocurrency trading in a simpler (and cheaper) way;
  • everyone who follows the new MetaTrader 5 and MQL5 language features.

First, we need to choose a cryptocurrency exchange providing a web API.

When developing one of my CodeBase products, I used BTC-e, which is no longer relevant. Therefore, I decided to switch to an alternative — . Its API features are sufficient to decently demonstrate both the new and already existing MQL functionality, including downloading bars, price flow, market depth, viewing current account orders and positions as well as order and trade history.

Let's stick to the following plan.

  1. Describing all data structures returned by web requests.
  2. Developing the classes for connecting to the exchange. These classes should implement WebRequest to their access points.
  3. Developing an Expert Advisor that receives bars and updates cryptocurrency prices in MetaTrader 5.
  4. Developing scripts and an EA working with orders and positions.

1. API structures of the exchange data


Like some other cryptocurrency exchanges, BITFINEX has two access points.

  • The first one (which is a set and format of requests) gives out the price data of the exchange — bars, ticks, market depth. The data are generalized and anonymous, so they are requested without any signatures and authorizations.
  • The second access point provides data on your account — account status, open positions, pending orders, trades history. These data are private, so you should send the SHA384-HMAC data signature in the request header using the API key received in your personal trader's room of the exchange. This guarantees that the request is made by you and not by an attacker under your name.

Both access points work in REST style (note: WebSockets are not considered here) using JSON format. This is convenient both for reading data and writing API.

The exchange has two protocol versions. The second version is currently in beta mode. There are two reasons for its appearance. First, there is a new functionality (requesting bars, working with alerts, orders' magic numbers, etc.). The second reason is also clearly visible in the format of JSON structures - this is traffic saving. Let's compare how the response to the Ticker request (current symbol data) looks like in the first and second protocol versions:

 Version 1
Version 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
]

As you can see, the second version avoids naming the fields, but their position is rigidly fixed meaning that arrays are used here instead of objects. In this article, I will show examples for both API versions of the exchange.

First, let's see what requests and structures of the open access point we should use for each protocol version.

For version 1:

For version 2:

Let me first explain how these structures are made using the SymbolDetails request as an example. Other data structures are presented in the BFxDefine.mqh file. If you need other API requests not included in the article, you can check them out in a similar way.

How it looks in the documentation
How it looks in 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[];
};

The documentation shows that the request returns the object array. So, I have created two structures. The first one (bfxSymbolDetails) parses and stores data on a single array object. The second one (bfxSymbolsDetails ) stores the received bfxSymbolDetails object array and is directly used in a web request. In other words, the JSON <-> MQL matching format is simple, mirror-like and extends to all documentation.

When working with the exchange API, we will use two classes having the common CBFx parent. Its objective is to encapsulate common data fields and the general web request function.

//------------------------------------------------------------------    class CBFx
class CBFx
  {
protected:
   string            m_answer;        // result of request (before JSON deserialization)
   enBFxRequestResult m_lastrr;       // code of requests result
   CJAVal            m_lastjs;        // deserialized answer from request
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;
     }
  };

The CBFx class has two descendants:

  • CBFxPublic — requests to public API (bars, quotes, depth of market);
  • CBFxTrade — requests to private API (trading, account data).

These descendants are described in the next two sections.

All preparatory work with the exchange API has been done. Now, let's implement it in the MetaTrader 5 platform.

2. CustomSymbol

What is the situation with custom symbols at the moment? Working with them is almost completely similar to working with ordinary symbols even considering that the entire management is conducted by a small set of functions.

The purpose of our work is to create an exchange symbol, upload the bars history and send prices regularly. Thus, we need the following MQL functions:

Unfortunately, WebRequest cannot be called from the indicator. Therefore, let's develop an EA to regularly request symbol bars and current prices, and then update them in MetaTrader 5.

The interaction between the exchange and MetaTrader 5 is as follows:

OnInit

  • Check the presence of a symbol in MetaTrader 5 SymbolSelect.
    • If no symbol found -> Request SymbolDetails of the specified symbol.
    • Create the CustomSymbolCreate symbol and fill in its properties CustomSymbolSetХХХ.
  • Check М1 history.
    • If download is required -> Request CandleHist with the necessary number of last bars
    • Add CustomRatesUpdate bars
  • Start the timer

OnTimer

  • Request the Ticker price -> Update the CustomTicksAdd tick
  • Request the last bar CandleLast -> Update CustomRatesUpdate

This is the entire work cycle.

Let's complicate the work to get more working examples. To do this, make a request and display the market depth (OrderBook) and time and sales (TradesHist). Time and sales is displayed as a comment on a chart, while the market depth is displayed as segments at the corresponding price levels.

The public API is implemented in the CBFxPublic class.

#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);
  };

Let's have a look at CandleHist to explain its work.

Documentation description:

General request form: https://api.bitfinex.com/v2/candles/trade::TimeFrame::Symbol/Section

Request response:

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

Find the request parameters here: https://docs.bitfinex.com/v2/reference#rest-public-candles

Sample request: https://api.bitfinex.com/v2/candles/trade:1m:tBTCUSD/hist?limit=50

CandleHist MQL implementation:

//------------------------------------------------------------------    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 function forms the GET request. Since we are interested in minute bars, the request line eventually looks as follows: https://api.bitfinex.com/v2/candles/trade:1m:tXXXXXX/hist. As a response, we get 1000 last bars from history.

The result of all these manipulations with API requests is a simple EA that is to construct bars and move the price along the chart.

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

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

CBFxPublic bfx;
string mtPair;
//------------------------------------------------------------------    OnInit
int OnInit()
  {
// create name for MT symbol
#ifdef __MQL4__
   mtPair=StringToUpper(Pair);
#endif          
#ifdef __MQL5__
   mtPair=Pair; StringToUpper(mtPair);
#endif
   mtPair+=".bfx";

// select symbol in 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());

// load some history
   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);

// start timer
   EventSetTimer(3);
   return INIT_SUCCEEDED;
  }
//------------------------------------------------------------------    OnDeinit
void OnDeinit(const int reason) { EventKillTimer(); ObjectsDeleteAll(0,Pair,0); if(ShowTimeSales) Comment(""); }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
// get last tick 
   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();
     }

// get last bar
   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);
  }
//+------------------------------------------------------------------+

First, be sure to launch it in debug mode and move along all the calls step by step. Check out what exactly is returned by the API, how responses are parsed and data are displayed.

What exactly does the EA do in MetaTrader 5?

After executing the OnInit function, the BTCUSD.bfx symbol is created in the BITFINEX: section


Then, the last bar and ticks are uploaded according to the timer:

The entire algorithm can be reduced to the following:

  • -> send WebRequest;
  • -> parse the received JSON;
  • -> update the symbol data.

As you can see, working with the public API is not complicated. You can implement its other functions for your needs as well.

At the time of this writing, the platform developers were preparing another MQL5 update featuring the ability to work with symbols by the SYMBOL_FORMULA property. This new function will make it easier to develop new cross rates using simple mathematical expressions.

3. Automation tools

Here, I will show how to perform requests to the exchange private API to work with orders and positions. The functions for working with access point are gathered in a separate class 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*/);
  };

Requests are made in complete analogy with the CBFxPublic class. I believe, the only difficulty for a beginner here is filling in the request headers. Unlike the public API, you need to pass the SHA384-HMAC request body hash signed with a secret key. In the API version 1, the request should be additionally converted to Base64.

According to the documentation, a signed hash is created in the following orders:

  • create the request body in json format => body
  • re-encode body in Base64 => bodyBase64
  • calculate bodyBase64 hash with the secret key => signature
  • send bodyBase64 and signature in the request header

bodyBase64 is received by the CryptEncode standard MQL function having the parameter CRYPT_BASE64. To find signature, use the SHA384 class attached to the article.

Thus, the function of a request to the private API version 1 looks as follows:

//------------------------------------------------------------------    Request
enBFxRequestResult CBFxTrade::Request1(string req,const CJAVal *prm)
  {
   CJAVal js; m_lastjs.Clear();
   if(CheckPointer(prm)!=POINTER_INVALID) js=prm;      // copy additional field
   js["request"]="/v1/"+req;
   js["nonce"]=string(Nonce());
   string body=js.Serialize();                         // serialize to 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;
  }


A request in the private API version 2 occurs in almost the same order, although without Base64 and with other headers' names.

//------------------------------------------------------------------    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;
  }


The Nonce parameter should be mentioned separately. It is not required in the public API, while here its purpose is simple - indicating that we are sending a new request not yet processed by the exchange. Nonce is a simple incremental counter with its value always higher than the previous one in each web request.

If you sent Nonce equal to or less than the previous value by mistake, the API returns the error 10100 — Nonce is too small. Unfortunately, the API does not send the expected value (unlike other exchanges). So, in order to reset to the correct value for the first time, assign the current time to it and increment again in subsequent web requests.

Requests of the private access point we are going to use:

// trading status:
// trading:
// alerts:

Example 1. Receiving data on account positions and orders

Since we are trying to automate trade to the maximum extent, it is very important to see what is happening now to positions and active orders. This is done via API requests:

As we can see, the requests are simple. A single parameter (a ticket of an order we need) is required only in Order Status.

The simplest thing I can do for demonstration is display the data on the chart in the form of tables. I will not dwell on working with graphical objects since this is a trivial task and the current article has a different objective. In a pseudo-code, receiving and displaying the list look as follows:

CBFxTrade bfxt(Key, Secret);                     // initialize private API using access keys
bfxOrders hist;
bfxt.OrdersHist(hist);                           // request orders from history
for (int i=0; i<ArraySize(hist.order); ++i)      // go through the received list and display it on the chart
  {
   // display hist.order[i];
  }

Requests for other arrays — bfxt.Positions and bfxt.ActiveOrders — are performed the same way.

Script execution results:

Now you have all the data on orders and positions at your disposal, and your EA can make decisions according to its trading logic.

Example 2. Working with alerts

Now, let's forget about trading for a moment and consider an example that will be useful to us for testing the private API. The alert sending mechanism allows you to try sending requests for creating/deleting objects without any risk for your account. For instance, you can use alerts instrad of trading. The exchange will send an alert to a PC or a mobile application giving you a signal to action.

The API offers three requests:

I have already implemented each of them as scripts. All of them can be found in the archive attached to the article. Let's consider creating an alert — Alert Set. According to the documentation, we need to pass a symbol and a price to perform a request. In order not to request these parameters from users, we are going to take the symbol from the chart itself, while the price will be taken from the point where the script has been applied to the chart.

#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("Error:\n"+bfxt.Answer(),"Error set alert",MB_ICONERROR);
     return;
    }
   MessageBox("Success:\n"+o.symbol+" on "+DoubleToString(o.price,5)+", current "+DoubleToString(o.curprice,5),"Success");
  }

After all the clarifying questions in MessageBox and preparing the data, we only need to send a request to the exchange bfxt.AlertSet. If sending is successful, we receive a newly generated bfxAlert object.

MetaTrader 5 has an excellent 100% working alternative to the above method:

The MetaTrader 5 built-in alert system allows to activate alerts not only locally but also send them to a mobile platform via email, file or a push notification! In other words, the trading platform offers much more useful functions and settings compared to the method described above.

Example 3. Working with orders and positions

We have finally reached the last and most important example — trading operations on the exchange directly from MetaTrader 5.

I have decided to implement this code as an EA with position management from the chart. The panel development is not our main objective but it is a convenient alternative to a set of scripts. The general EA view of the EA's work is displayed in the screenshot below:

The EA's actions:

  • showing pending orders and positions in the table as well as chart lines;
  • modifying an order price when moving the line;
  • removing a pending order;
  • closing a position;
  • setting a pending Limit order when placing a new horizontal line on a chart.

In the global sense, all processing and API calls are concentrated in OnChartEvent.

  • The OBJECT_CREATE event allows us to detect creation of new chart lines and form a new limit order.
  • The OBJECT_DRAG event enables us to detect the movement of "our" lines and modify a pending order.
  • Using the OBJECT_CLICK event, we detect clicks on the control buttons and update the tables, while either closing a position, or deleting an order.

The code of the EA's event functions looks as follows:

//------------------------------------------------------------------ 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;
 }
}

You can find the full code in the BFxTrading.mq5 file. The short animation below shows the results:

You did it!

Our toolkit has been enhanced with new features. MetaTrader 5 has become the platform for an external exchange. If you have an experience working with custom symbols and integration with external exchanges using web requests, please share it in the comments.

I wish you good luck and big profits!

Attached files

 #  Name Type
 Description
 1  \Experts\BFx\BFxAPI\  
 Exchange API files
 1.1
 \Experts\BFx\BFxAPI\BFxDefine.mqh  include file  API constants and definitions
 1.2  \Experts\BFx\BFxAPI\BFxPublicAPI.mqh  include file  class for working with the public access point
 1.3  \Experts\BFx\BFxAPI\BFxTradeAPI.mqh  include file  class for working with the private access point
 1.4  \Experts\BFx\BFxAPI\JAson.mqh  include file  JSON format serialization class
 1.5  \Experts\BFx\BFxAPI\SHA384.mqh  include file
 SHA + HMAC function hash class
 2  \Experts\BFx\Private\    examples of working with the private API
 2.1  \Experts\BFx\Private\_BFxTradeInfo.mq5  script
 getting the current list of positions and orders
 2.2  \Experts\BFx\Private\Alert\_BFxAlertSet.mq5  script  adding a notification to an account
 2.3  \Experts\BFx\Private\Alert\_BFxAlertList.mq5  script  getting a list of notifications from an account
 2.4  \Experts\BFx\Private\Alert\_BFxAlertDeleteAll.mq5  script  deleting all account notifications
 2.5  \Experts\BFx\Private\Trading\_BFxOrderStatus.mq5  script  getting an order data
 2.6  \Experts\BFx\Private\Trading\_BFxOrderNew.mq5  script  creating an order
 2.7  \Experts\BFx\Private\Trading\_BFxOrderCancel.mq5  script  deleting an order
 2.8  \Experts\BFx\Private\Trading\_BFxTrading.mq5  EA  trading EA able to create, modify and delete account's orders and positions
 3  \Experts\BFx\Public\    examples of working with the public API
 3.1  \Experts\BFx\Public\_BFxCustomSymbol.mq5  EA  


Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/4160

Attached files |
MQL5.zip (31.52 KB)
Last comments | Go to discussion (17)
Chris Mukengeshayi
Chris Mukengeshayi | 6 Jan 2018 at 21:28
rahMAN76:
How to trade..bos

Unfortunately this feature/innovation is for MT5, if you are interested in it, I will suggest you to start trading using MT5 platform.

jorgeog96
jorgeog96 | 14 Jan 2018 at 20:41

Hello guys,

I am new on this platform...Could you give a sample or a few lines related to what I have to do with all codes provided in this article..and test this solution? And for example...following these steps and fitting the API keys...could be possible connect with binance or bittrex right? Thanks

ammaryaseen123
ammaryaseen123 | 15 Jan 2018 at 08:15

Can you please tell me if there are any settings available for BINANCE? 
This actually worked for me on bitfinex.. If I put API keys in the files from BINANCE the same way as I did for bitfinex, than would this work? 

mavios.s7
mavios.s7 | 3 Feb 2018 at 02:07

Hi,

I have tried this code, but there is one problem I am not quite sure how to solve. When ticks are updated they show the wrong time in year 1970 even though I modified these lines

void OnTimer()

{

// get last tick 

bfxTicker2 bt;

MqlTick tick[1]={0};

tick[0].time=TimeCurrent();

tick[0].time_msc=TimeCurrent();


to get real timestamp from server, they still show wrong time.

The chart is updating at the correct time and when I go to check bars data they are all correct, but tick data show times in year 1970

My guess it has something to do with updating SYMBOL_TIME, because it also shows time in year 1970.

How can I fix this problem? or how can I update SYMBOL_TIME? there is no function like CustomSymbolSetDateTime


Thanks in advance

Eleni Anna Branou
Eleni Anna Branou | 3 Feb 2018 at 09:48

Use the </> code button to insert your code.


R-squared as an estimation of quality of the strategy balance curve R-squared as an estimation of quality of the strategy balance curve

This article describes the construction of the custom optimization criterion R-squared. This criterion can be used to estimate the quality of a strategy's balance curve and to select the most smoothly growing and stable strategies. The work discusses the principles of its construction and statistical methods used in estimation of properties and quality of this metric.

Comparing different types of moving averages in trading Comparing different types of moving averages in trading

This article deals with seven types of moving averages (MA) and a trading strategy to work with them. We also test and compare various MAs at a single trading strategy and evaluate the efficiency of each moving average compared to others.

Using the Kalman Filter for price direction prediction Using the Kalman Filter for price direction prediction

For successful trading, we almost always need indicators that can separate the main price movement from noise fluctuations. In this article, we consider one of the most promising digital filters, the Kalman filter. The article provides the description of how to draw and use the filter.

Resolving entries into indicators Resolving entries into indicators

Different situations happen in trader’s life. Often, the history of successful trades allows us to restore a strategy, while looking at a loss history we try to develop and improve it. In both cases, we compare trades with known indicators. This article suggests methods of batch comparison of trades with a number of indicators.