CCXT Application Server Showcase: access crypto exchanges via MQL5 library from MetaTrader 5

First, include the headers.
#include "ccxtjsmtlib.mqh" // import ccxtappsrvlib.ex5 #include "ccxtutil.mqh"
In the inputs, the Node server setup should be done (by default, it's localhost and port 8124).
input group "Connection settings" input string NodeServer = "http://127.0.0.1:8124"; input string NodeAuth = ""; // NodeAuth (for non-localhost connection)
Next, provide a specific exchange you want to work with. Leave the input empty to view a printout with a list of all supported exchanges.
input string Exchange = ""; // Exchange (empty - list exchanges)
Also provide a ticker you're interested in. If you don't know the name, run the script first time and look at MQL5/Files/CCXT/ folder where all received data is dumped by default, so you can find json-files with full markets info.
input string Ticker = "BCH/USDT";
For watching tests (subscriptions via websockets) specify their duration:
input uint WatchingDuration = 10; // WatchingDuration (seconds, 0 - infinite)
The script demonstrates how to setup credentials for private APIs, but will not use any private function.
input group "Exchange settings (Private API)" input string ApiKey = ""; input string Secret = ""; input string Uid = ""; input string Login = ""; input string Password = "";
Additional settings allow you to control the logging level, dumping of all received data, and timeouts.
input group "Auxiliary settings" input ushort Logging = 1; // Logging (0 - no, 1 - minimal, 2 - headers, 3 - full) input bool Dumping = true; input uint Timeout = 5; // Timeout (seconds)
If the script is running very first time, it will ask to unpack (manually) CCXT Application Server (extracted as ccxtappsrvbundle.jsc from a built-in resource), and run Node.js with it.
void OnStart() { // I. Initialization // performed once after the product download PrintFormat("CCXT AppSrvLibrary version: %.2f", AppSrvLibraryVersion()); const static string status[] = {"Can't deploy", "App server ZIP is deployed, but not extracted", "App server files are deployed"}; const int d = DeployCcxtAppServer(); Print(status[d + 1]); if(d <= 0) { return; // CCXT Application Server is not yet unpacked (manually) } // II. Library and application server setup // prepare all global/static settings SetNodeServer(NodeServer, NodeAuth); CcxtLink::Settings settings = {Logging, Dumping, Testing, Timeout, 0}; CcxtLink *link = GetLink(); // NB: the link is used by all further calls link.applySettings(settings); // get list of exchanges and component versions if(!StringLen(Exchange)) { Print("Complete list of exchanges:"); Print(ListExchanges().stringify()); Print("Pro list of exchanges with websockets support:"); Print(ListExchanges(true).stringify()); Print("App Server Version: ", AppSrvVersion().stringify()); Print("CCXT lib version: ", CcxtVersion()["version"].stringify()); return; } // III. Specific exchange setup // settings per exchange CCXT::Credentials credentials = {ApiKey, Secret, Uid, Login, Password}; // initialization (variants), will request and create an exchange object on Node and cache its properties locally in MQL5 object CcxtJsExchangeProIntf *ccxt = CreateExchangePro(Exchange, credentials, false/* true by default - load markets on Node */); AutoPtr<CcxtJsExchangeProIntf> auto(ccxt); // or // CcxtJsExchangeProIntf *ccxt = CreateExchangePro(Exchange, false); // AutoPtr<JsValue> c = credentials.toJSON(); // ccxt.set(c[]); if(link.getLastHttpCode() != 200) // it's recommended to check for errors after every call and exit { return; } // show main properties right from the local object (no requests performed) const bool isPro = !!*ccxt["pro"]; if(ShowExchangeProperties) { Print("Is pro: ", isPro); Print("Required Credentials:"); ccxt["requiredCredentials"].print(); Print("Supported features:"); // as declared in the exchange description ccxt["has"].print(); AutoPtr<JsValue> f = ccxt.get("facilities"); // as implemented in JavaScript (may contain function stubs) Print("Facilities: ", f[].stringify()); } // IV. Load markets // get whole (built-in) settings of the exchange from Node, can check if the provided above credentials are applied // NB! if Node was already running and this exchange loaded markets before, you'll still get object without markets, // to read markets locally, call .loadMarkets(true/false, *true*) AutoPtr<JsValue> check = ccxt.get(); if(Dumping) DumpJsonToFile("CCXT/check-" + Exchange, check[]); // lets load markets from the exchange, if they are not loaded on Node yet // (this is true in this test during very first run, because the exchange is constructed with false for loading markets) // any call will fail if markets not loaded, normally 'now' returns a number of milliseconds elapsed since 1970 // NB: failure will throw Alert! if(ccxt.call("now").t != JS_PRIMITIVE) { /* empty object */ccxt.loadMarkets(false /* don't reload on Node */, false /*don't parse and cache whole object in MQL5*/); JsValue *info = ccxt.get("markets"); // request only "markets" branch/dictionary to use less memory if(Dumping) DumpJsonToFile("CCXT/onlymarkets-" + Exchange, info); // or (to get a big object with markets and etc) // CcxtJsExchange ccxt(Exchange, credentials /*, true by default - load on Node */); // JsValue *info = ccxt.loadMarkets(false, true); // DumpJsonToFile(...) // NB: 'info' will be cleaned up below along with other orphan objects by calling ccxt.fetchAnything(NULL) } else { Print("Markets are already loaded on Node"); } // V. Fetching data through WebRequests // Fetches make this thread busy for some time, // so if you'd started websockets before (and they keep working on Node in parallel), // new incoming messages go into a queue for handing over to MQL5 after fetches JsValue *orderbook = ccxt.fetchOrderBook(Ticker, 10); if(Dumping) DumpJsonToFile("CCXT/orderbook-" + Exchange + "-" + Escape(Ticker), orderbook); JsValue *ticker = ccxt.fetchTicker(Ticker); if(Dumping) DumpJsonToFile("CCXT/ticker-" + Exchange + "-" + Escape(Ticker), ticker); JsValue *ohlcv = ccxt.fetchOHLCV(Ticker, "1m", t ? t - 1000 * 60 * 10 : 0, 10); if(Dumping) DumpJsonToFile("CCXT/ohlcv-" + Exchange + "-" + Escape(Ticker), ohlcv); JsValue *trades = ccxt.fetchTrades(Ticker, t ? t - 10000 : 0, 10); if(Dumping) DumpJsonToFile("CCXT/trades-" + Exchange + "-" + Escape(Ticker), trades); if(!!*ccxt["has"]["fetchBidsAsks"]) // dereference object by * for operator! to check if something is supported { string array[] = {Ticker}; JsValue *bidsasks = ccxt.fetchBidsAsks(array); if(Dumping) DumpJsonToFile("CCXT/bidsasks-" + Exchange + "-" + Escape(Ticker), bidsasks); } // periodically clean-up cached json objects after fetches when they're not used anymore, such as 'ticker' above, // the program will clean them up automatically on exit, but they consume memory, // markets and currencies are not stored in this cache ccxt.fetchAnything(NULL); ...
And now goes the CCXT PRO part based on websockets and live notification subscriptions.
// VI. Subscribing for online data changes notifications via websockets // start websockets connection (if the exchange supports it) if(isPro && ccxt.upgrade()) { // subscribe for watching something ccxt.watchOrderBook(Ticker); ccxt.watchTrades(Ticker); string tickers[] = {Ticker}; ccxt.watchBidsAsks(tickers); ccxt.watchTrades(Ticker); // increment counter to 2, no new subscription const uint start = GetTickCount(); while(!IsStopped() && ccxt.isConnected() && (!WatchingDuration || GetTickCount() - start < WatchingDuration * 1000)) { AutoPtr<JsValue> j = ccxt.readMessage(); // blocking until new message or timeout if(j[]) { Comment(j[].stringify()); // pretty print on chart } else { // timeout } } } else { if(isPro && ccxt.isConnected()) { Print("Can't upgrade to websockets"); string headers[][2]; if(ccxt.ws().getHeaders(headers)) { // TODO: analyze status and solve it } ccxt.ws().close(); // close to prevent further attempts to read/write from non-upgraded socket } } // if still connected check for new watches until unsubscription takes effect if(ccxt.isConnected()) { Print("Unsubscribing..."); // try to unsubscribe and wait a bit ccxt.un().watchOrderBook(Ticker); ccxt.un().watchTrades(Ticker); ccxt.un().watchBidsAsks(); // this will not be unwatched because we do not pass exact parameters ccxt.un().watchTrades(Ticker); // try again to decrement counter to 0 and release const uint start = GetTickCount(); // because unsubscription may fail, setup a small timeout while(!IsStopped() && ccxt.isConnected() && (GetTickCount() - start < 5 * 1000)) { Print("reading..."); AutoPtr<JsValue> j = ccxt.readMessage(); if(j[]) { Comment(j[].stringify()); } else { break; } } Print("Closing..."); // trying to close gracefully ccxt.close(); while(!IsStopped()) // probably something left in buffer { AutoPtr<JsValue> j = ccxt.readMessage(); if(j[]) { Comment(j[].stringify()); } else { break; } } } }