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