MetaTrader 5 herunterladen

Wie man an einer externen Börse für Kryptowährungen über MetaTrader 5 handelt

20 Dezember 2017, 15:25
o_o
0
610
Vor kurzem präsentierten die Entwickler der MQL5-Sprache die aktualisierte Funktionalität, zu welcher die Erstellung benutzerdefinierter Symbole und Charts hinzugefügt wurde. Die breite Öffentlichkeit hatte noch keine Zeit, die Wichtigkeit dieser Neuerung einzuschätzen, aber wenn man darüber nachdenkt, wird es offensichtlich, dass benutzerdefinierte Symbole enorme Möglichkeiten bieten. Zusammen mit den anderen MQL-Mitteln erlauben sie es, eine Vielzahl gewagter und interessanter Ideen umzusetzen.

Jetzt ist MetaTrader 5 nicht bloß ein Terminal, das nur mit einem Dealing-Center zusammenarbeitet. Heute ist das eine eigenständige analytische Plattform, die sich über API zu verschiedenen Börsen verbinden sowie Preisbewegungen und Handelsströme visualisieren kann. Ein kleiner Set neuer Funktionen wandelt das Terminal in ein offenes System um, das über eine unbegrenzte Anzahl von Symbolen verfügt. Ich bin der Ansicht, dass benutzerdefinierte Symbole zu einem leistungsstarken analytischen Tool werden können. 

Stellen wir die neuen Möglichkeiten der Programmiersprache anhand eines aktuellen Themas — Kryptowährungen — dar. Ich glaube, die ausgewählte Animation steigert das Interesse der Gemeinschaft an benutzerdefinierten Symbolen.

Für wen kann dieser Artikel interessant sein:

  • für Händler, die an Börsen für Kryptowährungen handeln;
  • für Investoren, die sich mit MetaTrader 5 und Portfolioinvestitionen auskennen;
  • für freischaffende Programmierer, die dann Kunden eine Vereinfachung (sprich Verbilligung) der Umsetzung ihrer Aufträge im Bereich des Handels mit Kryptowährungen anbieten können;
  • und für alle, die Neuheiten des MetaTrader 5 und der MQL5-Programmiersprache verfolgen.

Zunächst einmal müssen wir eine Börse auswählen, die eine Web-API bietet.

Bei der Entwicklung eines meiner Produkte habe ich die BTC-e Plattform verwendet, die heute nicht mehr aktuell ist. Deswegen habe ich mich entschieden, diese durch eine alternative zu ersetzen — . Die Möglichkeiten ihrer API genügen, um die neuen und die bereits existierenden MQL-Funktionen zu demonstrieren: Herunterladen von Balken, Price Flow, Markttiefe, Ansicht aktueller Orders und Positionen des Kontos, Historie von Orders und Abschlüssen.

Wir werden uns an den folgenden Plan halten:

  1. Beschreiben wir alle Datenstrukturen, die durch Web-Anfragen zurückgegeben werden.
  2. Erstellen wir Klassen für die Verbindung zur Börse, die WebRequest zu ihren Zugangspunkten implementieren.
  3. Erstellen wir einen Expert Advisor, der Balken erhält und die Preise eines Krypto-Symbols in MetaTrader 5 aktualisiert.
  4. Erstellen wir Skripte und einen Expert Advisor, der mit Orders und Positionen arbeitet.

1. API-Strukturen von Börsendaten


BITFINEX hat, wie auch einige andere Börsen für Kryptowährungen, zwei Zugangspunkte.

  • Der erste Zugangspunkt (und zwar der Set und das Format von Anfragen) gibt Preisdaten der Börse — Bars, Ticks und Markttiefe — aus. Die Daten sind verallgemeinert und anonym, aus diesem Grund werden sie ohne Signaturen und Autorisierung abgerufen.
  • Der zweite Zugangspunkt liefert Daten zu Ihrem Konto — Kontostand, Daten über eröffnete Positionen, Pending Orders und Historie. Diese Daten sind privat, aus diesem Grund muss man im Header der Anfrage die Datensignatur SHA384-HMAC durch den API-Schlüssel senden, den Sie in Ihrem Benutzerkonto der Börse erhalten. Das garantiert, dass kein Verbrecher sondern Sie die Anfrage gemacht haben.

Beide Zugangspunkte funktionieren nach dem REST-Stil (Anmerkung: WebSockets werden im Artikel nicht betrachtet), mit dem JSON-Format. Das ist sowohl für das Lesen von Daten, als auch für das Schreiben der API ganz praktisch.

Die Börse verfügt über zwei Protokollversionen. Die zweite Version ist momentan im Beta-Modus. Dafür gibt es zwei Gründe. Erstens wurde eine neue Funktionalität hinzugefügt (Abfrage von Balken, Arbeit mit Alerts, Magic Numbers von Orders usw.). Der zweite Grund geht aus dem Format von JSON-Strukturen hervor — das ist das Sparen des Traffics. Ich schlage vor, die Antwort auf die Ticker-Anfrage (aktuelle Daten des Symbol) in der ersten und der zweiten Versionen des Protokolls zu vergleichen:

 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
]

Wie Sie sehen, vermeidet diese Version die Benennung der Felder, aber deren Lage ist fest, d.h. statt Objekte werden Arrays verwendet. In diesem Artikel führe ich Beispiele für beide API-Versionen der zu betrachtenden Plattform an.

Befassen wir uns zunächst damit, welche Anfragen und Strukturen des öffentlichen Zugangspunktes wir für jede Version des Protokolls verwenden werden.

Für die Version Nr.1:

Für die Version Nr.2:

Zunächst einmal möchte ich anhand der Anfrage SymbolDetails erklären, wie diese Strukturen erstellt werden. Die anderen Strukturen können Sie in der Datei BFxDefine.mqh finden. Wenn Sie andere API-Anfragen in der Zukunft benötigen werden, die nicht im Artikel beschrieben werden, können Sie sie auf die gleiche Weise implementieren.

Wie es in der Dokumentation aussieht
Wie es in BFxDefine.mqh aussieht
[
  {
    "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[];
};

Aus der Dokumentation geht hervor, dass die Anfrage ein Array von Objekten liefert. Aus diesem Grund habe ich zwei Strukturen erstellt. Die erste — bfxSymbolDetails — parst und speichert die Daten eines Objekts des Arrays. Die zweite — bfxSymbolsDetails — speichert das erhaltene Array von Objekten bfxSymbolDetails und wird in der Web-Anfrage verwendet. D.h. das Format der Übereinstimmung JSON <-> MQL ist einfach, umgekehrt und gilt für die ganze Dokumentation.

Für die Arbeit mit der API der Börse verwenden wir zwei Klassen mit der gemeinsamen Klasse CBFx, deren Aufgabe darin besteht, allgemeine Datenfelder und die allgemeine Funktion der Web-Anfrage zu kapseln.

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

Von der Klasse CBFx wurden zwei Klassen abgeleitet:

  • CBFxPublic — Anfragen an die öffentliche API (Balken, Kurse, Markttiefe);
  • CBFxTrade — Anfragen an die private API (Handel, Kontodaten).

Die abgeleiteten Klassen werden in den zwei nächsten Abschnitten beschrieben.

Wir haben Vorarbeiten mit der API der Börse durchgeführt, nun beschäftigen wir uns mit deren Implementierung im MetaTrader 5 Terminal.

2. CustomSymbol

Wie sieht die Arbeit mit benutzerdefinierten Symbolen aus? Fast genauso wie die Arbeit mit einfachen Symbolen. Man kommt mit einem Minimum an Funktionen aus.

Das Ziel unserer Aufgabe besteht darin, ein Symbol zu erstellen, Balken-Historie hochzuladen und Kurse regelmäßig zu übertragen. Deswegen ist es offensichtlich, dass wir folgende MQL-Funktionen benötigen:

Leider kann WebRequest nicht aus dem Indikator aufgerufen werden. Deswegen erstellen wir einen Expert Advisor, der Balken und aktuelle Preise des Symbols regelmäßig abfragt und diese dann in MetaTrader 5 aktualisiert.

Das Zusammenspiel zwischen der Börse und dem MetaTrader 5 ist wie folgt:

OnInit

  • Prüfen, ob das Symbol SymbolSelect in MetaTrader 5 vorhanden ist.
    • Wenn das Symbol nicht gefunden wurde -> SymbolDetails des angegebenen Symbols abrufen.
    • Das Symbol CustomSymbolCreate erstellen und seine Eigenschaften CustomSymbolSetХХХ ausfüllen.
  • Die М1-Historie prüfen.
    • Wenn Download benötigt -> CandleHist mit der gewünschten Zahl der letzten Balken abrufen
    • Die Balken CustomRatesUpdate hinzufügen
  • Timer starten

OnTimer

  • Ticker Preis abrufen -> CustomTicksAdd aktualisieren
  • Den letzten Balken CandleLast abrufen -> CustomRatesUpdate aktualisieren

Das ist der eigentliche Arbeitsablauf.

Um mehr Beispiele zu bekommen, machen wir die Arbeit ein wenig komplizierter. Dafür rufen wir die Markttiefe (OrderBook) und Time & Sales (TradesHist) ab und zeigen wir diese an. Die Time & Sales zeigen wir als Kommentar im Chart an, die Markttiefe stellen wir als Strecken auf den entsprechenden Preis-Levels dar.

Die Implementierung der öffentlichen API ist in der Klasse CBFxPublic enthalten.

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

Betrachten wir CandleHist, um ihre Funktionen zu erklären.

Beschreibung der Dokumentation:

Allgemeine Anfrage: https://api.bitfinex.com/v2/candles/trade::TimeFrame::Symbol/Section

Die Antwort:

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

Die Parameter der Anfrage sind hier zu finden https://docs.bitfinex.com/v2/reference#rest-public-candles

Beispiel für eine Anfrage: https://api.bitfinex.com/v2/candles/trade:1m:tBTCUSD/hist?limit=50

Die MQL-Implementierung von CandleHist:

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

Die Funktion CandleHist bildet eine GET-Anfrage. Da wir Minuten-Balken brauchen, sieht die Zeile der Anfrage wie folgt aus: https://api.bitfinex.com/v2/candles/trade:1m:tXXXXXX/hist. Wir erhalten die letzten 1000 Balken aus der Historie.

Der Höhepunkt aller Manipulationen mit diesen API-Anfragen ist ein einfacher Expert Advisor, der Balken zeichnet und den Preis im Chart verschiebt.

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

Erstes Mal starten Sie ihn unbedingt im Debug-Modus und durchlaufen Sie alle Aufrufe. Überprüfen Sie, was die API zurückgibt, wie die Antworten geparst und die Daten ausgegeben werden.

Was genau macht der Expert Advisot in MetaTrader 5?

Nach der Ausführung der OnInit Funktion wird das Symbol BTCUSD.bfx in BITFINEX erstellt:


Anschließend werden nach Timer der letzte Balken und Ticks hochgeladen:

Wie wir sehen, läuft das Ganze sich auf das folgende Schema hinaus:

  • -> WebRequest senden;
  • -> JSON parsen;
  • -> Symboldaten aktualisieren.

Mit anderen Worten ist die Arbeit mit der öffentlichen API nicht kompliziert. Sie können auch andere ihre Funktionen für Ihren Bedarf implementieren.

Zum Zeitpunkt des Schreibens des Artikels wurde ein Release von Updates für MQL5 erwartet, zu welchen auch die Arbeit mit Symbolen anhand der Eigenschaft SYMBOL_FORMULA gehört. Diese neue Funktion erlaubt es, neue Kreuzkurse mittels einfacher mathematischer Ausdrücke zu erstellen.

3. Automatisierungstools

In diesem Teil zeige ich, wie man Anfragen an die private API der Börse sendet, um mit Orders und Positionen zu arbeiten. Die Funktionen für die Arbeit mit diesem Zugangspunkt wurden in die separate Klasse CBFxTrade verschoben.

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

Die Anfragen wurden analog zur Klasse CBFxPublic ausgeführt. Die einzige Schwierigkeit für Anfänger könnte meiner Meinung nach das Ausfüllen der Headers der Anfrage bereiten. Im Vergleich zur öffentlichen API, muss dem Header Body-Hash der Anfrage SHA384-HMAC übergeben werden, signiert mit einem geheimen Schlüssel. In der API der ersten Version muss die Anfrage zusätzlich in Base64 umgewandelt werden.

Laut der Dokumentation, sieht die Erstellung eines signierten Hash wie folgt aus:

  • Body der Anfrage im JSON-Format erstellen => body
  • body mit Base64 enkodieren => bodyBase64
  • Hash bodyBase64 mit einem geheimen Schlüssel berechnen => signature
  • bodyBase64 und signature im Header senden

Die Abfrage von bodyBase64 führt die MQL-Funktion CryptEncode mit dem Parameter CRYPT_BASE64 aus. Um signature festzustellen, verwenden wir die Klasse SHA384.

Die Funktion einer Anfrage an die öffentliche API Version 1 wird wie folgt aussehen:

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


Eine Anfrage an die private API Version 2 erfolgt fast gleich, aber ohne Base64 und mit den anderen Header-Namen.

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


Ich möchte einzeln auf den Parameter Nonce eingehen. Er wurde in der privaten API nicht benötigt, sein Zweck ist ganz einfach — angeben, dass wir eine neue Anfrage senden, die die Börse noch nicht verarbeitet hat. Nonce — ein einfacher Zähler von Inkrementen, dessen Wert bei jeder Web-Anfrage immer größer als der vorherige Wert sein soll.

Wenn Sie einen falschen Nonce gesendet haben, der gleich oder kleiner als der vorherige Wert ist, gibt die API einen Fehler zurück: 10100 — Nonce is too small. Leider sendet die API nicht den erwarteten Wert (auf den anderen Börsen gibt das). Aus diesem Grund, um den richtigen Wert gleich zu erhalten, ist es am einfachsten ihm die aktuelle Zeit zuzuweisen und in den weiteren Web-Anfragen zu erhöhen.

Wir verwenden folgende Anfragen des Zugangspunktes:

// Handelsstatus:
// Handel:
// Alerts:

Beispiel 1. Abfragen von Informationen über Positionen und Orders

Da wir darauf abzielen, den Handel maximal zu automatisieren, ist es sehr wichtig zu sehen, was momentan in Positionen und aktiven Orders geschieht. Das wird über API-Anfragen getan:

Wie wir sehen, sind die Anfragen nicht kompliziert. Ein einziger Parameter (das Ticket der gewünschten Order) wird nur in Order Status benötigt.

Das einfachste, was ich für die Demonstration tun kann, ist die erhaltenen Daten als Tabellen im Chart anzuzeigen. Ich werde nicht auf die Arbeit mit den grafischen Objekten eingehen, denn das ist eine banale Aufgabe, deren Lösung nicht zu den Zielen des Artikels gehört. Die Abfrage und Anzeige der Liste sieht in einem Pseudocode wie folgt aus:

CBFxTrade bfxt(Key, Secret);                     // private API durch die Zugangsschlüssel initialisieren
bfxOrders hist;
bfxt.OrdersHist(hist);                           // Orders aus der Historie abfragen
for (int i=0; i<ArraySize(hist.order); ++i)      // die erhaltene Liste durchlaufen und diese im Chart anzeigen
  {
   // hist.order[i] ausgeben;
  }


Gleich führen wir Anfragen für die Arrays bfxt.Positions und bfxt.ActiveOrders aus.

Das Ergebnis der Ausführung des Skriptes

Nun stehen Ihnen alle Informationen über Orders und Positionen zur Verfügung, und Ihr Expert Advisor kann Entscheidungen entsprechend seiner Handelslogik treffen.

Beispiel 2. Arbeiten mit Alerts

Wenden wir uns kurz vom Handel ab und betrachten wir ein Beispiel, das für das Testen der privaten API hilfreich ist. Mit Alerts kann man das Senden von Anfragen wegen des Erstellens und Löschens von Objekten ausprobieren, ohne Geld zu riskieren. Alerts können zum Beispiel statt des eigentlichen Handels verwendet werden. Die Börse sendet Alerts auf den PC oder in die mobile Anwendung, und dies gibt Ihnen ein Signal zum Handeln.

Die API bietet drei Anfragen:

Jede von ihnen habe ich als Skript umgesetzt. Sie sind in der Zip-Datei im Anhang zu finden. Als Beispiel betrachten wir die Erstellung eines Alerts — Alert Set. Laut der Dokumentation muss man für die Ausführung einer Anfrage ein Symbol und einen Preis übergeben. Um diese Parameter nicht beim User abzurufen, wird das Symbol vom Chart genommen und der Preis — von der Stelle, an welcher das Skript auf den Chart gezogen wurde.

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

Wir sehen, dass es uns nach allen klärenden Fragen und nach der Aufbereitung von Daten in MessageBox nur bleibt, eine Anfrage an die Börse bfxt.AlertSet zu senden. Wenn die Anfrage erfolgreich gesendet wurde, erhalten wir das neu erstellte Objekt bfxAlert.

In MetaTrader 5 gibt es eine ausgezeichnete arbeitsfähige Alternative der Methode des Sendens von Alerts:

Bitte beachten Sie: das in MetaTrader 5 integrierte Alert-System erlaubt es, Alerts nicht nur lokal im Terminal zu aktivieren, sondern auch diese per E-Mail oder als Push ins mobile Terminal zu senden. Die Handelsplattform bietet quasi mehr hilfreiche Funktionen und Einstellungen, als die oben beschriebene Option.

Beispiel 3. Arbeiten mit Orders und Positionen

Nun kommen wir zum letzten und wichtigsten Beispiel — Handelsoperationen an der Börse direkt aus dem Metatrader 5.

Diesen Code habe ich als einen Expert Advisor mit der Verwaltung von Positionen im Chart implementiert. Die Erstellung eines Panels haben wir nicht geplant, aber das ist eine einfache Alternative zu einem Set von Skripts. Die Arbeit des Expert Advisors ist auf dem Screenshot unten dargestellt:

Wie der Expert Advisor vorgeht:

  • zeigt Pending Orders und Positionen in der Tabelle und als Linien im Chart an;
  • ändert den Preis einer Order beim Verschieben der Linie;
  • löscht eine Pending Order;
  • schließt eine Position;
  • platziert eine Limit Pending Order beim Ziehen einer neuen horizontalen Linie auf den Chart.

Im globalen Sinne sind die Verarbeitung und die API-Aufrufe in OnChartEvent enthalten.

  • Nach dem Ereignis OBJECT_CREATE fangen wir die Erstellung neuer Linien im Chart ab und bilden eine neue Limit Order.
  • Nach dem Ereignis OBJECT_DRAG fangen wir die Verschiebung unserer Linien ab und modifizieren wir die Pending Order.
  • Nach dem Ereignis OBJECT_CLICK fangen wir den Klick auf den Steuertasten ab und aktualisieren die Tabellen, entweder Schließen einer Position oder Löschen einer Order.

Der Code der Event-Funktionen des Expert Advisors sieht wie folgt aus:

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

Der komplette Code ist in der Datei BFxTrading.mq5 zu finden. Die kurze Animation unten zeigt die Ergebnisse:

Sie haben es geschafft!

Der Set von Werkzeugen wurde durch neue Möglichkeiten erweitert. MetaTrader 5 ist zur Plafform für eine externe Börse geworden. Wenn Sie Erfahrung in der Arbeit mit benutzerdefinierten Symbolen und Integration mit externen Börsen mittels Web-Anfragen haben, lade ich Sie ein, sich an der Diskussion in den Kommentaren zu beteiligen.

Viel Erfolg und Gewinne!

Angehängte Dateien

 Nr. NameTyp
 Beschreibung
 1 \Experts\BFx\BFxAPI\ 
 Ordner mit API-Dateien der Börse
 1.1
 \Experts\BFx\BFxAPI\BFxDefine.mqh include-Datei API-Konstanten und Definitionen
 1.2 \Experts\BFx\BFxAPI\BFxPublicAPI.mqh include-Datei Klasse für die Arbeit mit dem öffentlichen Zugangspunkt
 1.3 \Experts\BFx\BFxAPI\BFxTradeAPI.mqh include-Datei Klasse für die Arbeit mit dem privaten Zugangspunkt
 1.4 \Experts\BFx\BFxAPI\JAson.mqh include-Datei Klasse für die Serialisierung des JSON-Formats
 1.5 \Experts\BFx\BFxAPI\SHA384.mqh include-Datei
 Klasse der Hash-Funktion SHA + HMAC
 2 \Experts\BFx\Private\  Ordner mit Beispielen für die Arbeit mit der privaten API
 2.1 \Experts\BFx\Private\_BFxTradeInfo.mq5 Skript
 Abfragen der aktuellen Liste von Positionen und Orders
 2.2 \Experts\BFx\Private\Alert\_BFxAlertSet.mq5 Skript Hinzufügen eines Alerts zum Konto
 2.3 \Experts\BFx\Private\Alert\_BFxAlertList.mq5 Skript Abfragen der Liste von Alerts beim Konto
 2.4 \Experts\BFx\Private\Alert\_BFxAlertDeleteAll.mq5 Skript Löschen aller Alerts des Kontos
 2.5 \Experts\BFx\Private\Trading\_BFxOrderStatus.mq5 Skript Erhalten von Informationen über Orders
 2.6 \Experts\BFx\Private\Trading\_BFxOrderNew.mq5 Skript Erstellung einer Order
 2.7 \Experts\BFx\Private\Trading\_BFxOrderCancel.mq5 Skript Löschen einer Order
 2.8 \Experts\BFx\Private\Trading\_BFxTrading.mq5 Expert Advisor Expert Advisor, der Orders und Positionen erstellen, modifizieren und löschen kann
 3 \Experts\BFx\Public\  Ordner mit Beispielen für die Arbeit mit der öffentlichen API
 3.1 \Experts\BFx\Public\_BFxCustomSymbol.mq5 Expert Advisor 


Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/4160

Beigefügte Dateien |
MQL5.zip (31.8 KB)
Die Eröffnung durch Indikatoren bestimmen lassen Die Eröffnung durch Indikatoren bestimmen lassen

Im Leben eines Händlers gibt es verschiedene Situationen. Häufig wünschen wir uns, die Strategie von geschlossen, erfolgreichen Positionen fortzusetzen, während wir versuchen, die der Verlust bringenden Positionen weiterzuentwickeln und zu verbessern. In beiden Fällen vergleichen wir Positionen mit bekannten Indikatoren. Dieser Artikel schlägt die Methoden eines Batch-Vergleichs von Positionen mit einer Reihe von Indikatoren vor.

Verwendung des Kalman-Filters für die Prognose der Preisrichtung Verwendung des Kalman-Filters für die Prognose der Preisrichtung

Für einen erfolgreichen Handel benötigen wir fast immer Indikatoren, die die Hauptpreisbewegung vom Hintergrundrauschen trennen können. In diesem Artikel betrachten wir einen der vielversprechendsten digitalen Filter, den Kalman-Filter. Der Artikel beschreibt, wie Sie den Filter zeichnen und verwenden können.

Das Erstellen einer neuen Handelsstrategie und sich die Positionseröffnungen durch Indikatoren bestimmen lassen Das Erstellen einer neuen Handelsstrategie und sich die Positionseröffnungen durch Indikatoren bestimmen lassen

Der Artikel schlägt eine Technologie vor, die jedem helfen kann, eine eigene Handelsstrategie durch die individuelle Auswahl von Indikatoren sowie den zu entwickelnden Signalen für die Positionseröffnung zu entwickeln.

Nachthandel während der asiatischen Handelszeit: wie man im Plus bleibt Nachthandel während der asiatischen Handelszeit: wie man im Plus bleibt

Der Artikel beschäftigt sich mit dem Begriff des Nachthandels, Handelsstrategien und deren Implementierung in MQL5. Es wurden Tests durchgeführt und Schlussfolgerungen gezogen.