//+------------------------------------------------------------------+ // // 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 "1.30" #property description "MQL5 JSON API" #property description "See github link for documentation" #include #include #include #include #include #include string HOST="*"; int SYS_PORT=15555; int DATA_PORT=15556; int LIVE_PORT=15557; int STR_PORT=15558; // 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); // Global variables bool debug = false; bool liveStream = true; bool connectedFlag= true; datetime lastBar = 0; int deInitReason = -1; string tickSymbols[]; string chartSymbols[]; int chartSymbolCount = 0; string chartSymbolSettings[][3]; //+------------------------------------------------------------------+ //| 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; result = dataSocket.bind(StringFormat("tcp://%s:%d", HOST,DATA_PORT)); if (result == false) return result; result = liveSocket.bind(StringFormat("tcp://%s:%d", HOST,LIVE_PORT)); if (result == false) return result; result = streamSocket.bind(StringFormat("tcp://%s:%d", HOST,STR_PORT)); if (result == false) return result; Print("Bound 'System' socket on port ", SYS_PORT); Print("Bound 'Data' socket on port ", DATA_PORT); Print("Bound 'Live' socket on port ", LIVE_PORT); Print("Bound 'Streaming' socket on port ", STR_PORT); sysSocket.setLinger(1000); dataSocket.setLinger(1000); liveSocket.setLinger(1000); streamSocket.setLinger(1000); // Number of messages to buffer in RAM. sysSocket.setSendHighWaterMark(1); dataSocket.setSendHighWaterMark(5); liveSocket.setSendHighWaterMark(1); streamSocket.setSendHighWaterMark(50); return result; } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ /* Bindinig ZMQ ports on init */ EventSetMillisecondTimer(1); int bindSocketsDelay = 65; // Seconds to wait if binding of sockets fails. if (deInitReason != REASON_CHARTCHANGE){ bool result = false; Print("Binding sockets..."); result = BindSockets(); if (result==false){ // Print("Binding of sockets failed permanently."); Print("Binding sockets failed. Waiting ", bindSocketsDelay, " seconds to try again..."); Sleep(bindSocketsDelay*1000); result = BindSockets(); if (result==false){ Print("Binding of sockets failed permanently."); return(INIT_FAILED); } } } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ /* Unbinding ZMQ ports on denit */ // TODO Ports do not get freed immediately under Wine. How to properly close ports? There is a timeout of about 60 sec. // https://forum.winehq.org/viewtopic.php?t=22758 // https://github.com/zeromq/cppzmq/issues/139 deInitReason = reason; // Skip reloading of the EA script when the reason to reload is a chart change. It is not necessary. if (reason != REASON_CHARTCHANGE && reason != REASON_TEMPLATE){ Print(__FUNCTION__," Deinitialization reason: ", getUninitReasonText(reason)); // release all symbol "spys" UnloadAllSymbols(); Print("Unbinding 'System' socket on port ", SYS_PORT,".."); sysSocket.unbind(StringFormat("tcp://%s:%d", HOST,SYS_PORT)); Print("Unbinding 'Data' socket on port ", DATA_PORT,".."); dataSocket.unbind(StringFormat("tcp://%s:%d", HOST,DATA_PORT)); Print("Unbinding 'Live' socket on port ", LIVE_PORT, ".."); liveSocket.unbind(StringFormat("tcp://%s:%d", HOST,LIVE_PORT)); Print("Unbinding 'Streaming' socket on port ", STR_PORT, ".."); streamSocket.unbind(StringFormat("tcp://%s:%d", HOST,STR_PORT)); // Shutdown ZeroMQ Context context.shutdown(); context.destroy(0); EventKillTimer(); } } //+------------------------------------------------------------------+ //| Check if subscribed to Symbol | //+------------------------------------------------------------------+ bool hasSymbol(string symbol) { for(int i=0;i0){ // Pull request to RequestHandler(). RequestHandler(request); } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(string symbol){ bool run = hasSymbol(symbol); if(run){ // If liveStream == true, push last tick to liveSocket. if(liveStream){ CJAVal tickData, last; // Check if terminal connected to market if(TerminalInfoInteger(TERMINAL_CONNECTED)){ MqlTick tick; datetime timeS = TimeCurrent(); if(SymbolInfoTick(symbol,tick)){ tickData[0] = (long) tick.time; tickData[1] = (double) tick.bid; tickData[2] = (double) tick.ask; last["status"] = (string) "CONNECTED"; last["symbol"] = (string) symbol; last["timeframe"] = (string) "TICKS"; last["data"].Set(tickData); string t=last.Serialize(); if(debug) Print(t); InformClientSocket(liveSocket,t); } connectedFlag=true; } //If disconnected from market else { // send disconnect message only once if(connectedFlag){ last["status"] = (string) "DISCONNECTED"; string t=last.Serialize(); if(debug) Print("Tick ", symbol, t); InformClientSocket(liveSocket,t); connectedFlag=false; } } } } } //+------------------------------------------------------------------+ //| ChartEvent function | //| This function must be declared, even if it empty. | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // event id const long& lparam, // event param of long type const double& dparam, // event param of double type const string& sparam) // event param of string type { //--- Add your code here... } //+------------------------------------------------------------------+ //| Request handler | //+------------------------------------------------------------------+ void RequestHandler(ZmqMsg &request){ CJAVal message; ResetLastError(); // Get data from reguest string msg=request.getData(); if(debug) Print("Processing:"+msg); // Deserialize msg to CJAVal array if(!message.Deserialize(msg)){ ActionDoneOrError(65537, __FUNCTION__); Alert("Deserialization Error"); ExpertRemove(); } // Send response to System socket that request was received // Some historical data requests can take a lot of time InformClientSocket(sysSocket, "OK"); // Process action command string action = message["action"].ToStr(); if(action=="CONFIG") {ScriptConfiguration(message);} else if(action=="ACCOUNT") {GetAccountInfo();} else if(action=="BALANCE") {GetBalanceInfo();} else if(action=="HISTORY") {HistoryInfo(message);} else if(action=="TRADE") {TradingModule(message);} else if(action=="POSITIONS") {GetPositions(message);} else if(action=="ORDERS") {GetOrders(message);} else if(action=="RESET") {ResetSubscriptions(message);} // Action command error processing else ActionDoneOrError(65538, __FUNCTION__); } //+------------------------------------------------------------------+ //| Reconfigure the script params | //+------------------------------------------------------------------+ void ScriptConfiguration(CJAVal &dataObject){ string symbol=dataObject["symbol"].ToStr(); string chartTF = dataObject["chartTF"].ToStr(); string actionType = dataObject["actionType"].ToStr(); string symbArr[1]; symbArr[0]= symbol; if (chartTF=="TICKS" && !hasSymbol(symbol)){ ArrayInsert(tickSymbols,symbArr,0); } else if (!hasChartSymbol(symbol)) { ArrayInsert(chartSymbols,symbArr,0); ArrayResize(chartSymbolSettings,chartSymbolCount+1); chartSymbolSettings[chartSymbolCount][0]=symbol; chartSymbolSettings[chartSymbolCount][1]=chartTF; chartSymbolCount++; } // loading symbol "spys" LoadSymbol(symbol); if(SymbolInfoInteger(symbol, SYMBOL_EXIST)){ ActionDoneOrError(ERR_SUCCESS, __FUNCTION__); } else ActionDoneOrError(ERR_MARKET_UNKNOWN_SYMBOL, __FUNCTION__); } //+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+ //| Get historical data | //+------------------------------------------------------------------+ void HistoryInfo(CJAVal &dataObject){ string actionType = dataObject["actionType"].ToStr(); string chartTF = dataObject["chartTF"].ToStr(); string symbol=dataObject["symbol"].ToStr(); if(actionType=="WRITE" && chartTF=="TICKS"){ CJAVal data, d; //, msg; MqlTick tickArray[]; string fileName=symbol + "-" + chartTF + ".csv"; // file name string directoryName="Data"; // directory name string outputFileName=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(); //if(debug){ 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){ ActionDoneOrError(ERR_SUCCESS , __FUNCTION__); } else ActionDoneOrError(65541 , __FUNCTION__); Print("Preparing tick data of ", tickCount, " ticks for ", symbol); int file_handle=FileOpen(outputFileName, FILE_WRITE | FILE_CSV); if(file_handle!=INVALID_HANDLE){ // TODO live updates //msg["status"] = (string) "CONNECTED"; //msg["type"] = (string) "NORMAL"; //msg["data"] = (string) StringFormat("Writing to: %s\\%s", TerminalInfoString(TERMINAL_DATA_PATH), outputFileName); //if(liveStream) InformClientSocket(liveSocket, msg.Serialize()); ActionDoneOrError(ERR_SUCCESS , __FUNCTION__); 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;i 0 && tickArray[i].time > tickArray[i-1].time ){ data[j][0]=(long) tickArray[i].time; data[j][1]=(double) tickArray[i].bid; data[j][2]=(double) tickArray[i].ask; j++; } } d["data"].Set(data); } else {d["data"].Add(data);} Print("Finished preparing tick data"); d["symbol"]=symbol; d["timeframe"]="TICKS"; string t=d.Serialize(); if(debug) Print(t); Print(t); InformClientSocket(dataSocket,t); } else if(actionType=="DATA" && chartTF!="TICKS"){ CJAVal c, d; MqlRates r[]; int barCount; ENUM_TIMEFRAMES period=GetTimeframe(chartTF); datetime fromDate=(datetime)dataObject["fromDate"].ToInt(); datetime toDate=TimeCurrent(); if(dataObject["toDate"].ToInt()!=NULL) toDate=(datetime)dataObject["toDate"].ToInt(); if(debug){ Print("Fetching HISTORY"); Print("1) Symbol :"+symbol); Print("2) Timeframe :"+EnumToString(period)); Print("3) Date from :"+TimeToString(fromDate)); if(dataObject["toDate"].ToInt()!=NULL)Print("4) Date to:"+TimeToString(toDate)); } barCount=CopyRates(symbol,period,fromDate,toDate,r); if(barCount){ 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); } // Error wrong action type else ActionDoneOrError(65538, __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