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