//+------------------------------------------------------------------+ // // 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 // 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 IND_DATA_PORT=15559; // 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); // Global variables bool debug = false; bool liveStream = true; bool connectedFlag= true; int deInitReason = -1; // Variables for handling price data stream string chartSymbols[]; int chartSymbolCount = 0; string chartSymbolSettings[][3]; // Variables for controlling indicators int indicatorCount = 0; double indicators[]; string indicatorIds[]; int indicatorParamCount[]; int indicatorBufferCount[]; //+------------------------------------------------------------------+ //| 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; result = indicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,IND_DATA_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); Print("Bound 'Indicator Data' socket on port ", IND_DATA_PORT); sysSocket.setLinger(1000); dataSocket.setLinger(1000); liveSocket.setLinger(1000); streamSocket.setLinger(1000); indicatorDataSocket.setLinger(1000); // Number of messages to buffer in RAM. sysSocket.setSendHighWaterMark(1); dataSocket.setSendHighWaterMark(5); liveSocket.setSendHighWaterMark(1); streamSocket.setSendHighWaterMark(50); indicatorDataSocket.setSendHighWaterMark(5); return result; } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ /* Bindinig ZMQ ports on init */ Print(GetIndicatorConstantValue("VOLUME_TICK"), " ",VOLUME_TICK); // 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); } } /* //+------------------------------------------------------------------+ //| 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") ResetSubscriptionsAndIndicators(); else if(action=="INDICATOR") IndicatorControl(message); // Action command error processing else ActionDoneOrError(65538, __FUNCTION__); } //+------------------------------------------------------------------+ //| Reconfigure the script params | //+------------------------------------------------------------------+ void ScriptConfiguration(CJAVal &dataObject){ //testDraw(); string symbol=dataObject["symbol"].ToStr(); string chartTF=dataObject["chartTF"].ToStr(); string actionType=dataObject["actionType"].ToStr(); string symbArr[1]; symbArr[0]= symbol; if (!HasChartSymbol(symbol, chartTF)) { ArrayInsert(chartSymbols,symbArr,0); ArrayResize(chartSymbolSettings,chartSymbolCount+1); chartSymbolSettings[chartSymbolCount][0]=symbol; chartSymbolSettings[chartSymbolCount][1]=chartTF; // lastBar chartSymbolSettings[chartSymbolCount][2]=0; // to initialze with value 0 skips the first price chartSymbolCount++; } if(SymbolInfoInteger(symbol, SYMBOL_EXIST)){ ActionDoneOrError(ERR_SUCCESS, __FUNCTION__); } else ActionDoneOrError(ERR_MARKET_UNKNOWN_SYMBOL, __FUNCTION__); } //+------------------------------------------------------------------+ //| Start new indicator or request indicator data | //+------------------------------------------------------------------+ void IndicatorControl(CJAVal &dataObject){ string actionType=dataObject["actionType"].ToStr(); if(actionType=="REQUEST") { GetIndicatorResult(dataObject); } else if(actionType=="START") { StartIndicator(dataObject); } } //+------------------------------------------------------------------+ //| Start new indicator instance | //+------------------------------------------------------------------+ void StartIndicator(CJAVal &dataObject){ string symbol=dataObject["symbol"].ToStr(); string chartTF=dataObject["chartTF"].ToStr(); string id=dataObject["id"].ToStr(); string indicatorName=dataObject["name"].ToStr(); indicatorCount++; ArrayResize(indicators,indicatorCount); ArrayResize(indicatorIds,indicatorCount); ArrayResize(indicatorParamCount,indicatorCount); ArrayResize(indicatorBufferCount,indicatorCount); int idx = indicatorCount-1; indicatorIds[idx] = id; indicatorBufferCount[idx] = dataObject["linecount"].ToInt(); double params[]; indicatorParamCount[idx] = dataObject["params"].Size(); for(int i=0;i= 0) { if(CopyBuffer(indicators[idx], i, fromDate, 1, values) < 0) {/* Error handling */} results[i] = DoubleToString(values[0]); } else { // TODO error handling } } CJAVal message; message["id"] = (string) id; message["data"].Set(results); string t=message.Serialize(); if(debug) Print(t); InformClientSocket(indicatorDataSocket,t); // TODO more error handling //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); } //+------------------------------------------------------------------+ //| 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){ ActionDoneOrError(ERR_SUCCESS , __FUNCTION__); } else ActionDoneOrError(65541 , __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__); 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); } // 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=0)return r; ENUM_APPLIED_VOLUME av; r = StringToEnum(indicatorConstantString,av); if(r>=0)return r; ENUM_STO_PRICE sp; r = StringToEnum(indicatorConstantString,sp); if(r>=0)return r; ENUM_MA_METHOD mm; r = StringToEnum(indicatorConstantString,mm); if(r>=0)return r; return(-1); } #define MIN_ENUM_VALUES 0 #define MAX_ENUM_VALUES 255 //+------------------------------------------------------------------+ //| StringToEnum : Convert a string to an ENUM value, | //| it loop between min(0) and max(255), adjustable if needed. | //| Non existing enum value defined as -1. If -1 is used as an | //| enum value, code need to be adjusted to an other default. | //| Parameters : | //| in - string to convert | //| out - ENUM value | //| @return - int if conversion succeed, false otherwise | //| | //| Based on: | //| https://www.mql5.com/en/forum/61741/page3#comment_5491344 | //+------------------------------------------------------------------+ template int StringToEnum(string in,ENUM &out) { out=-1; //--- for(int i=MIN_ENUM_VALUES;i<=MAX_ENUM_VALUES;i++) { ENUM enumValue=(ENUM)i; if(in==EnumToString(enumValue)) { out=enumValue; break; } } //--- return(out); } //+------------------------------------------------------------------+ //| Trade confirmation | //+------------------------------------------------------------------+ void OrderDoneOrError(bool error, string funcName, CTrade &trade){ CJAVal conf; conf["error"]=(bool) error; conf["retcode"]=(int) trade.ResultRetcode(); conf["desription"]=(string) GetRetcodeID(trade.ResultRetcode()); // conf["deal"]=(int) trade.ResultDeal(); conf["order"]=(int) trade.ResultOrder(); conf["volume"]=(double) trade.ResultVolume(); conf["price"]=(double) trade.ResultPrice(); conf["bid"]=(double) trade.ResultBid(); conf["ask"]=(double) trade.ResultAsk(); conf["function"]=(string) funcName; string t=conf.Serialize(); if(debug) Print(t); InformClientSocket(dataSocket,t); } //+------------------------------------------------------------------+ //| Action confirmation | //+------------------------------------------------------------------+ void ActionDoneOrError(int lastError, string funcName){ CJAVal conf; conf["error"]=(bool)true; if(lastError==0) conf["error"]=(bool)false; conf["lastError"]=(string) lastError; conf["description"]=GetErrorID(lastError); conf["function"]=(string) funcName; string t=conf.Serialize(); if(debug) Print(t); InformClientSocket(dataSocket,t); } //+------------------------------------------------------------------+ //| Inform Client via socket | //+------------------------------------------------------------------+ void InformClientSocket(Socket &workingSocket,string replyMessage){ // non-blocking workingSocket.send(replyMessage,true); // TODO: Array out of range error ResetLastError(); } //+------------------------------------------------------------------+ //| Get retcode message by retcode id | //+------------------------------------------------------------------+ string GetRetcodeID(int retcode){ switch(retcode){ case 10004: return("TRADE_RETCODE_REQUOTE"); break; case 10006: return("TRADE_RETCODE_REJECT"); break; case 10007: return("TRADE_RETCODE_CANCEL"); break; case 10008: return("TRADE_RETCODE_PLACED"); break; case 10009: return("TRADE_RETCODE_DONE"); break; case 10010: return("TRADE_RETCODE_DONE_PARTIAL"); break; case 10011: return("TRADE_RETCODE_ERROR"); break; case 10012: return("TRADE_RETCODE_TIMEOUT"); break; case 10013: return("TRADE_RETCODE_INVALID"); break; case 10014: return("TRADE_RETCODE_INVALID_VOLUME"); break; case 10015: return("TRADE_RETCODE_INVALID_PRICE"); break; case 10016: return("TRADE_RETCODE_INVALID_STOPS"); break; case 10017: return("TRADE_RETCODE_TRADE_DISABLED"); break; case 10018: return("TRADE_RETCODE_MARKET_CLOSED"); break; case 10019: return("TRADE_RETCODE_NO_MONEY"); break; case 10020: return("TRADE_RETCODE_PRICE_CHANGED"); break; case 10021: return("TRADE_RETCODE_PRICE_OFF"); break; case 10022: return("TRADE_RETCODE_INVALID_EXPIRATION"); break; case 10023: return("TRADE_RETCODE_ORDER_CHANGED"); break; case 10024: return("TRADE_RETCODE_TOO_MANY_REQUESTS"); break; case 10025: return("TRADE_RETCODE_NO_CHANGES"); break; case 10026: return("TRADE_RETCODE_SERVER_DISABLES_AT"); break; case 10027: return("TRADE_RETCODE_CLIENT_DISABLES_AT"); break; case 10028: return("TRADE_RETCODE_LOCKED"); break; case 10029: return("TRADE_RETCODE_FROZEN"); break; case 10030: return("TRADE_RETCODE_INVALID_FILL"); break; case 10031: return("TRADE_RETCODE_CONNECTION"); break; case 10032: return("TRADE_RETCODE_ONLY_REAL"); break; case 10033: return("TRADE_RETCODE_LIMIT_ORDERS"); break; case 10034: return("TRADE_RETCODE_LIMIT_VOLUME"); break; case 10035: return("TRADE_RETCODE_INVALID_ORDER"); break; case 10036: return("TRADE_RETCODE_POSITION_CLOSED"); break; case 10038: return("TRADE_RETCODE_INVALID_CLOSE_VOLUME");break; case 10039: return("TRADE_RETCODE_CLOSE_ORDER_EXIST"); break; case 10040: return("TRADE_RETCODE_LIMIT_POSITIONS"); break; case 10041: return("TRADE_RETCODE_REJECT_CANCEL"); break; case 10042: return("TRADE_RETCODE_LONG_ONLY"); break; case 10043: return("TRADE_RETCODE_SHORT_ONLY"); break; case 10044: return("TRADE_RETCODE_CLOSE_ONLY"); break; default: return("TRADE_RETCODE_UNKNOWN="+IntegerToString(retcode)); break; } } //+------------------------------------------------------------------+ //| Get error message by error id | //+------------------------------------------------------------------+ string GetErrorID(int error){ switch(error){ case 0: return("ERR_SUCCESS"); break; case 4301: return("ERR_MARKET_UNKNOWN_SYMBOL"); break; case 4303: return("ERR_MARKET_WRONG_PROPERTY"); break; case 4752: return("ERR_TRADE_DISABLED"); break; case 4753: return("ERR_TRADE_POSITION_NOT_FOUND"); break; case 4754: return("ERR_TRADE_ORDER_NOT_FOUND"); break; // Custom errors case 65537: return("ERR_DESERIALIZATION"); break; case 65538: return("ERR_WRONG_ACTION"); break; case 65539: return("ERR_WRONG_ACTION_TYPE"); break; //case 65540: return("ERR_CLEAR_SUBSCRIPTIONS_FAILED"); break; case 65541: return("ERR_RETRIEVE_DATA_FAILED"); break; case 65542: return("ERR_CFILE_CREATION_FAILED"); break; default: return("ERR_CODE_UNKNOWN="+IntegerToString(error)); break; } } //+------------------------------------------------------------------+ //| Return a textual description of the deinitialization reason code | //+------------------------------------------------------------------+ string getUninitReasonText(int reasonCode) { string text=""; //--- switch(reasonCode) { case REASON_ACCOUNT: text="Account was changed";break; case REASON_CHARTCHANGE: text="Symbol or timeframe was changed";break; case REASON_CHARTCLOSE: text="Chart was closed";break; case REASON_PARAMETERS: text="Input-parameter was changed";break; case REASON_RECOMPILE: text="Program "+__FILE__+" was recompiled";break; case REASON_REMOVE: text="Program "+__FILE__+" was removed from chart";break; case REASON_TEMPLATE: text="New template was applied to chart";break; default:text="Another reason"; } //--- return text; }