//+------------------------------------------------------------------+ // // Copyright (C) 2019 Nikolai Khramkov // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // //+------------------------------------------------------------------+ // TODO: Deviation #property copyright "Copyright 2019, Nikolai Khramkov." #property link "https://github.com/khramkov" #property version "2.00" #property description "MQL5 JSON API" #property description "See github link for documentation" #include #include #include #include #include #include #include // Set ports and host for ZeroMQ string HOST="*"; int SYS_PORT=15555; int DATA_PORT=15556; int LIVE_PORT=15557; int STR_PORT=15558; int INDICATOR_DATA_PORT=15559; int CHART_DATA_PORT=15560; int CHART_INDICATOR_DATA_PORT=15562; // ZeroMQ Cnnections Context context("MQL5 JSON API"); Socket sysSocket(context,ZMQ_REP); Socket dataSocket(context,ZMQ_PUSH); Socket liveSocket(context,ZMQ_PUSH); Socket streamSocket(context,ZMQ_PUSH); Socket indicatorDataSocket(context,ZMQ_PUSH); Socket chartDataSocket(context,ZMQ_PULL); Socket chartIndicatorDataSocket(context,ZMQ_PUB); // Global variables \\ bool debug = false; bool liveStream = true; bool connectedFlag = true; int deInitReason = -1; double chartAttached = ChartID(); // Chart id where the expert is attached to // Variables for handling price data stream struct SymbolSubscription { string symbol; string chartTf; datetime lastBar; }; SymbolSubscription symbolSubscriptions[]; int symbolSubscriptionCount = 0; // Variables for controlling indicators struct Indicator { long id; // Internal id string indicatorId; // UUID int indicatorHandle; // Internal id/handle int indicatorParamCount; // Number of parameters to be passed to the indicator int indicatorBufferCount; // Numnber of buffers to be returned bythe indicator }; Indicator indicators[]; int indicatorCount = 0; // Variables for controlling chart struct ChartWindow { long id; // Internal id string chartId; // UUID }; ChartWindow chartWindows[]; int chartWindowCount = 0; struct ChartWindowIndicator { long id; // Internal id string indicatorId; // UUID int indicatorHandle; // Internal id/handle }; ChartWindowIndicator chartWindowIndicators[]; int chartWindowIndicatorCount = 0; // Refresh chart window interval for JsonAPIIndicator int chartWindowTimerInterval = 1; // Cycles of the globally set EventSetMillisecondTimer interval int chartWindowTimerCounter = 0; // Keeps track of the current cycle // Error handling ControlErrors mControl; //+------------------------------------------------------------------+ //| Bind ZMQ sockets to ports | //+------------------------------------------------------------------+ bool BindSockets() { bool result = false; result = sysSocket.bind(StringFormat("tcp://%s:%d", HOST,SYS_PORT)); if(result == false) { return result; } else { Print("Bound 'System' socket on port ", SYS_PORT); } result = dataSocket.bind(StringFormat("tcp://%s:%d", HOST,DATA_PORT)); if(result == false) { return result; } else { Print("Bound 'Data' socket on port ", DATA_PORT); } result = liveSocket.bind(StringFormat("tcp://%s:%d", HOST,LIVE_PORT)); if(result == false) { return result; } else { Print("Bound 'Live' socket on port ", LIVE_PORT); } result = streamSocket.bind(StringFormat("tcp://%s:%d", HOST,STR_PORT)); if(result == false) { return result; } else { Print("Bound 'Streaming' socket on port ", STR_PORT); } result = indicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,INDICATOR_DATA_PORT)); if(result == false) { return result; } else { Print("Bound 'Indicator Data' socket on port ", INDICATOR_DATA_PORT); } result = chartDataSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_DATA_PORT)); if(result == false) { return result; } else { Print("Bound 'Chart Data' socket on port ", CHART_DATA_PORT); } result = chartIndicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_INDICATOR_DATA_PORT)); if(result == false) { return result; } else { Print("Bound 'JsonAPIIndicator Data' socket on port ", CHART_INDICATOR_DATA_PORT); } sysSocket.setLinger(1000); dataSocket.setLinger(1000); liveSocket.setLinger(1000); streamSocket.setLinger(1000); indicatorDataSocket.setLinger(1000); chartDataSocket.setLinger(1000); chartIndicatorDataSocket.setLinger(1000); // Number of messages to buffer in RAM. sysSocket.setSendHighWaterMark(1); dataSocket.setSendHighWaterMark(5); liveSocket.setSendHighWaterMark(1); streamSocket.setSendHighWaterMark(50); indicatorDataSocket.setSendHighWaterMark(5); chartDataSocket.setReceiveHighWaterMark(1); // TODO confirm settings chartIndicatorDataSocket.setReceiveHighWaterMark(1); return result; } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Setting up error reporting mControl.SetAlert(true); mControl.SetSound(false); mControl.SetWriteFlag(false); /* Bindinig ZMQ ports on init */ // Skip reloading of the EA script when the reason to reload is a chart timeframe change if(deInitReason != REASON_CHARTCHANGE) { EventSetMillisecondTimer(1); int bindSocketsDelay = 65; // Seconds to wait if binding of sockets fails. int bindAttemtps = 2; // Number of binding attemtps Print("Binding sockets..."); for(int i=0; i0) { // Pull request to RequestHandler(). RequestHandler(request); } // Publish indicator values for the JsonAPIIndicator indicator ZmqMsg chartMsg; chartDataSocket.recv(chartMsg, true); if(chartMsg.size()>0) { double values[]; // Ensure that all indicators have finished intitailisation for(int i=0; i= chartWindowTimerInterval) { for(int i=0; i= 0) { if(CopyBuffer(indicators[idx].indicatorHandle, i, fromDate, 1, values) < 0) { if(mControl.mGetLastError()) { CJAVal message; int lastError = mControl.mGetLastError(); string desc = mControl.mGetDesc(); mControl.Check(); message["error"]=(bool) true; message["lastError"]=(string) lastError; message["description"]=desc; message["function"]=(string) __FUNCTION__; string t=message.Serialize(); if(debug) Print(t); InformClientSocket(indicatorDataSocket,t); } } results[i] = DoubleToString(values[0]); } } CJAVal message; message["error"]=(bool) false; message["id"] = (string) id; message["data"].Set(results); string t=message.Serialize(); if(debug) Print(t); InformClientSocket(indicatorDataSocket,t); } //+------------------------------------------------------------------+ //| Open new chart or add indicator to chart | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ChartControl(CJAVal &dataObject) { string actionType=dataObject["actionType"].ToStr(); if(actionType=="ADDINDICATOR") { AddChartIndicator(dataObject); } else if(actionType=="OPEN") { OpenChart(dataObject); } } //+------------------------------------------------------------------+ //| Open new chart | //+------------------------------------------------------------------+ void OpenChart(CJAVal &dataObject) { string chartId=dataObject["chartId"].ToStr(); string symbol=dataObject["symbol"].ToStr(); string chartTF=dataObject["chartTF"].ToStr(); chartWindowCount++; ArrayResize(chartWindows,chartWindowCount); int idx = chartWindowCount-1; chartWindows[idx].chartId = chartId; ENUM_TIMEFRAMES period = GetTimeframe(chartTF); chartWindows[idx].id = ChartOpen(symbol, period); ChartSetInteger(chartWindows[idx].id, CHART_AUTOSCROLL, false); CJAVal message; message["error"]=(bool) false; message["chartId"] = (string) chartId; string t=message.Serialize(); if(debug) Print(t); InformClientSocket(dataSocket,t); } //+------------------------------------------------------------------+ //| Add JsonAPIIndicator indicator to chart | //+------------------------------------------------------------------+ void AddChartIndicator(CJAVal &dataObject) { string chartIdStr=dataObject["chartId"].ToStr(); string chartIndicatorId=dataObject["indicatorChartId"].ToStr(); int chartIndicatorSubWindow=dataObject["chartIndicatorSubWindow"].ToInt(); string shortName = dataObject["shortName"].ToStr(); int chartIdx = GetChartWindowIdxByChartWindowId(chartIdStr); long ChartId = chartWindows[chartIdx].id; double chartIndicatorHandle = iCustom(ChartSymbol(ChartId),ChartPeriod(ChartId),"JsonAPIIndicator",chartIndicatorId,shortName); //linelabel,colorstyle,linetype,linestyle,linewidth); if(ChartIndicatorAdd(ChartId, chartIndicatorSubWindow, chartIndicatorHandle)) { chartWindowIndicatorCount++; ArrayResize(chartWindowIndicators,chartWindowIndicatorCount); int indicatorIdx = chartWindowIndicatorCount-1; chartWindowIndicators[indicatorIdx].indicatorId = chartIndicatorId; chartWindowIndicators[indicatorIdx].indicatorHandle = chartIndicatorHandle; } if(!CheckError(__FUNCTION__)) { CJAVal message; message["error"]=(bool) false; message["chartId"] = (string) chartIdStr; string t=message.Serialize(); if(debug) Print(t); InformClientSocket(dataSocket,t); } } //+------------------------------------------------------------------+ //| Account information | //+------------------------------------------------------------------+ void GetAccountInfo() { CJAVal info; info["error"] = false; info["broker"] = AccountInfoString(ACCOUNT_COMPANY); info["currency"] = AccountInfoString(ACCOUNT_CURRENCY); info["server"] = AccountInfoString(ACCOUNT_SERVER); info["trading_allowed"] = TerminalInfoInteger(TERMINAL_TRADE_ALLOWED); info["bot_trading"] = AccountInfoInteger(ACCOUNT_TRADE_EXPERT); info["balance"] = AccountInfoDouble(ACCOUNT_BALANCE); info["equity"] = AccountInfoDouble(ACCOUNT_EQUITY); info["margin"] = AccountInfoDouble(ACCOUNT_MARGIN); info["margin_free"] = AccountInfoDouble(ACCOUNT_MARGIN_FREE); info["margin_level"] = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); string t=info.Serialize(); if(debug) Print(t); InformClientSocket(dataSocket,t); } //+------------------------------------------------------------------+ //| Balance information | //+------------------------------------------------------------------+ void GetBalanceInfo() { CJAVal info; info["balance"] = AccountInfoDouble(ACCOUNT_BALANCE); info["equity"] = AccountInfoDouble(ACCOUNT_EQUITY); info["margin"] = AccountInfoDouble(ACCOUNT_MARGIN); info["margin_free"] = AccountInfoDouble(ACCOUNT_MARGIN_FREE); string t=info.Serialize(); if(debug) Print(t); InformClientSocket(dataSocket,t); } //+------------------------------------------------------------------+ //| Push historical data to ZMQ socket | //+------------------------------------------------------------------+ bool PushHistoricalData(CJAVal &data) { string t=data.Serialize(); if(debug) Print(t); InformClientSocket(dataSocket,t); return true; } //+------------------------------------------------------------------+ //| Get historical data | //+------------------------------------------------------------------+ void HistoryInfo(CJAVal &dataObject) { string actionType = dataObject["actionType"].ToStr(); string chartTF = dataObject["chartTF"].ToStr(); string symbol=dataObject["symbol"].ToStr(); // Write CVS fle to local directory if(actionType=="WRITE" && chartTF=="TICK") { CJAVal data, d, msg; MqlTick tickArray[]; string fileName=symbol + "-" + chartTF + ".csv"; // file name string directoryName="Data"; // directory name string outputFile=directoryName+"\\"+fileName; ENUM_TIMEFRAMES period=GetTimeframe(chartTF); datetime fromDate=(datetime)dataObject["fromDate"].ToInt(); datetime toDate=TimeCurrent(); if(dataObject["toDate"].ToInt()!=NULL) toDate=(datetime)dataObject["toDate"].ToInt(); Print("Fetching HISTORY"); Print("1) Symbol: "+symbol); Print("2) Timeframe: Ticks"); Print("3) Date from: "+TimeToString(fromDate)); if(dataObject["toDate"].ToInt()!=NULL) Print("4) Date to:"+TimeToString(toDate)); int tickCount = 0; ulong fromDateM = StringToTime(fromDate); ulong toDateM = StringToTime(toDate); tickCount=CopyTicksRange(symbol,tickArray,COPY_TICKS_ALL,1000*(ulong)fromDateM,1000*(ulong)toDateM); if(tickCount < 0) { mControl.mSetUserError(65541, GetErrorID(65541)); } CheckError(__FUNCTION__); Print("Preparing data of ", tickCount, " ticks for ", symbol); int file_handle=FileOpen(outputFile, FILE_WRITE | FILE_CSV); if(file_handle!=INVALID_HANDLE) { msg["status"] = (string) "CONNECTED"; msg["type"] = (string) "NORMAL"; msg["data"] = (string) StringFormat("Writing to: %s\\%s", TerminalInfoString(TERMINAL_DATA_PATH), outputFile); if(liveStream) InformClientSocket(liveSocket, msg.Serialize()); ActionDoneOrError(ERR_SUCCESS, __FUNCTION__, "ERR_SUCCESS"); //ActionDoneOrError(ERR_SUCCESS , __FUNCTION__, dataSocket); // Inform client that file is avalable for writing PrintFormat("%s file is available for writing",fileName); PrintFormat("File path: %s\\Files\\",TerminalInfoString(TERMINAL_DATA_PATH)); //--- write the time and values of signals to the file for(int i=0; i0) { tradeInfo.Ticket(ticket); data["ticket"]=(long) tradeInfo.Ticket(); data["time"]=(long) tradeInfo.Time(); data["price"]=(double) tradeInfo.Price(); data["volume"]=(double) tradeInfo.Volume(); data["symbol"]=(string) tradeInfo.Symbol(); data["type"]=(string) tradeInfo.TypeDescription(); data["entry"]=(long) tradeInfo.Entry(); data["profit"]=(double) tradeInfo.Profit(); trades["trades"].Add(data); } } } else { trades["trades"].Add(data); } string t=trades.Serialize(); if(debug) Print(t); InformClientSocket(dataSocket,t); } else { mControl.mSetUserError(65538, GetErrorID(65538)); CheckError(__FUNCTION__); } } //+------------------------------------------------------------------+ //| Fetch positions information | //+------------------------------------------------------------------+ void GetPositions(CJAVal &dataObject) { CPositionInfo myposition; CJAVal data, position; // Get positions int positionsTotal=PositionsTotal(); // Create empty array if no positions if(!positionsTotal) data["positions"].Add(position); // Go through positions in a loop for(int i=0; i