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