557 lines
40 KiB
Plaintext
557 lines
40 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
//
|
|
//+------------------------------------------------------------------+
|
|
|
|
// TODO: SymbolExist
|
|
// TODO: Rise error on wrong TF
|
|
// TODO: Check chart symbol -> change
|
|
// Universal serialializer
|
|
// RETURN OK or error
|
|
// Write comment about sockets
|
|
// Changing TF
|
|
// Order return info
|
|
// Add defauld request dict
|
|
|
|
#property copyright "Copyright 2019, Nikolai Khramkov."
|
|
#property link "https://github"
|
|
#property version "0.7"
|
|
|
|
#include <Zmq/Zmq.mqh>
|
|
#include <json.mqh>
|
|
|
|
extern string PROJECT_NAME="Backtrader <-> Metatrader 5 interface";
|
|
extern string PROTOCOL="tcp";
|
|
extern string HOST="*";
|
|
extern int SYS_PORT=5556;
|
|
extern int DATA_PORT=5557;
|
|
extern int LIVE_PORT=5558;
|
|
extern int MILLISECOND_TIMER=1; // 1 millisecond
|
|
|
|
// ZeroMQ Cnnections
|
|
Context context(PROJECT_NAME);
|
|
Socket sysSocket(context,ZMQ_REP);
|
|
Socket dataSocket(context,ZMQ_PUSH);
|
|
Socket liveSocket(context,ZMQ_PUSH);
|
|
|
|
// Global variables
|
|
ZmqMsg request;
|
|
bool debug = true;
|
|
datetime LastBar = 0;
|
|
string symbol = _Symbol;
|
|
ENUM_TIMEFRAMES period = _Period;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
/*
|
|
Bindinig ZMQ ports on init
|
|
*/
|
|
|
|
// Set Millisecond Timer to get client socket input
|
|
EventSetMillisecondTimer(MILLISECOND_TIMER);
|
|
|
|
Print("[REP] Binding System Socket on port "+IntegerToString(SYS_PORT)+"...");
|
|
sysSocket.bind(StringFormat("%s://%s:%d",PROTOCOL,HOST,SYS_PORT));
|
|
|
|
Print("[PUSH] Binding Data Socket on port "+IntegerToString(DATA_PORT)+"...");
|
|
dataSocket.bind(StringFormat("%s://%s:%d",PROTOCOL,HOST,DATA_PORT));
|
|
|
|
Print("[PUSH] Binding Live Socket on port "+IntegerToString(LIVE_PORT)+"...");
|
|
liveSocket.bind(StringFormat("%s://%s:%d",PROTOCOL,HOST,LIVE_PORT));
|
|
|
|
// Maximum amount of time in milliseconds that the thread will try to send messages
|
|
// after its socket has been closed (the default value of -1 means to linger forever):
|
|
sysSocket.setLinger(1000);
|
|
|
|
// If we initiate socket.send() without having a corresponding socket draining the queue,
|
|
// we'll eat up memory as the socket just keeps enqueueing messages.
|
|
// So how many messages do we want ZeroMQ to buffer in RAM before blocking the socket?
|
|
// 3 messages only.
|
|
sysSocket.setSendHighWaterMark(3);
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit()
|
|
{
|
|
/*
|
|
Closing ports on denit
|
|
*/
|
|
Print("[REP] Unbinding socket on port "+IntegerToString(SYS_PORT)+"..");
|
|
sysSocket.unbind(StringFormat("%s://%s:%d",PROTOCOL,HOST,SYS_PORT));
|
|
|
|
Print("[PUSH] Unbinding socket on port "+IntegerToString(DATA_PORT)+"..");
|
|
dataSocket.unbind(StringFormat("%s://%s:%d",PROTOCOL,HOST,DATA_PORT));
|
|
|
|
Print("[PUSH] Unbinding socket on port "+IntegerToString(LIVE_PORT)+"..");
|
|
liveSocket.unbind(StringFormat("%s://%s:%d",PROTOCOL,HOST,LIVE_PORT));
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Expert timer function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTimer()
|
|
{
|
|
// Get client's response, but don't wait.
|
|
sysSocket.recv(request,true);
|
|
|
|
// Generating reply by passing request to ZmqMsg MessageHandler() function.
|
|
ZmqMsg reply=MessageHandler();
|
|
|
|
// Reply to client via REP socket.
|
|
sysSocket.send(reply);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
ZmqMsg MessageHandler()
|
|
{
|
|
ZmqMsg reply;
|
|
CJAVal message;
|
|
|
|
if(request.size()>0)
|
|
{
|
|
|
|
string msg=request.getData();
|
|
|
|
if(debug==true) {Print("Processing request:"+msg);}
|
|
|
|
if(!message.Deserialize(msg))
|
|
{
|
|
Alert("Deserialization Error");
|
|
|
|
//ExpertRemove();
|
|
//return;
|
|
}
|
|
|
|
string action = message["action"].ToStr();
|
|
|
|
if(action=="CHECK") {CheckConnection(&dataSocket, &liveSocket);}
|
|
else if(action=="CONFIG") {ConfigScript(&dataSocket, message);}
|
|
else if(action=="ACCOUNT") {AccountInfo(&dataSocket);}
|
|
else if(action=="BALANCE") {BalanceInfo(&dataSocket);}
|
|
else if(action=="HISTORY") {HistoryInfo(&dataSocket, message);}
|
|
else if(action=="POSITIONS") {GetPositionsInfo(&dataSocket);}
|
|
else if(action=="ORDERS") {GetOrdersInfo(&dataSocket);}
|
|
else if(action=="TRADE") {TradingModule(&dataSocket, message);}
|
|
else {} // error processing
|
|
|
|
// Construct response
|
|
ZmqMsg ret("OK");
|
|
reply=ret;
|
|
|
|
}
|
|
else
|
|
{
|
|
// NO DATA RECEIVED
|
|
ZmqMsg ret("FALSE");
|
|
reply=ret;
|
|
}
|
|
|
|
return(reply);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check sockets connection |
|
|
//+------------------------------------------------------------------+
|
|
void CheckConnection (Socket &dataSocket, Socket &liveSocket)
|
|
{
|
|
InformClientSocket(dataSocket,"OK");
|
|
InformClientSocket(liveSocket,"OK");
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reconfigure the script params |
|
|
//+------------------------------------------------------------------+
|
|
void ConfigScript(Socket &dataSocket, CJAVal &dataObject)
|
|
{
|
|
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Change chart timeframe |
|
|
//+------------------------------------------------------------------+
|
|
void CheckTimeframe(Socket &dataSocket, CJAVal &dataObject)
|
|
{
|
|
string chartTF=dataObject["chartTF"].ToStr();
|
|
|
|
if(chartTF=="1m") {period=PERIOD_M1;}
|
|
else if(chartTF=="5m") {period=PERIOD_M5;}
|
|
else if(chartTF=="15m") {period=PERIOD_M15;}
|
|
else if(chartTF=="30m") {period=PERIOD_M30;}
|
|
else if(chartTF=="1h") {period=PERIOD_H1;}
|
|
else if(chartTF=="2h") {period=PERIOD_H2;}
|
|
else if(chartTF=="3h") {period=PERIOD_H3;}
|
|
else if(chartTF=="4h") {period=PERIOD_H4;}
|
|
else if(chartTF=="6h") {period=PERIOD_H6;}
|
|
else if(chartTF=="8h") {period=PERIOD_H8;}
|
|
else if(chartTF=="12h") {period=PERIOD_H12;}
|
|
else if(chartTF=="1d") {period=PERIOD_D1;}
|
|
else if(chartTF=="1w") {period=PERIOD_W1;}
|
|
else if(chartTF=="1M") {period=PERIOD_MN1;}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Account information |
|
|
//+------------------------------------------------------------------+
|
|
void AccountInfo(Socket &dataSocket)
|
|
{
|
|
CJAVal info;
|
|
|
|
info["brocker"] = AccountInfoString(ACCOUNT_COMPANY);
|
|
info["currency"] = AccountInfoString(ACCOUNT_CURRENCY);
|
|
info["server"] = AccountInfoString(ACCOUNT_SERVER);
|
|
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);
|
|
info["bot_trading"] = AccountInfoInteger(ACCOUNT_TRADE_EXPERT);
|
|
string t=info.Serialize();
|
|
if(debug==true) {Print(t);}
|
|
InformClientSocket(dataSocket,t);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Balance information |
|
|
//+------------------------------------------------------------------+
|
|
void BalanceInfo(Socket &dataSocket)
|
|
{
|
|
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==true) {Print(t);}
|
|
InformClientSocket(dataSocket,t);
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get historical data |
|
|
//+------------------------------------------------------------------+
|
|
void HistoryInfo(Socket &dataSocket, CJAVal &dataObject)
|
|
{
|
|
CJAVal candles;
|
|
MqlRates rates[];
|
|
|
|
int copied;
|
|
string actionType=dataObject["actionType"].ToStr();
|
|
string symbol=dataObject["symbol"].ToStr();
|
|
string chartTF=dataObject["chartTF"].ToStr();
|
|
datetime startTime=dataObject["startTime"].ToInt();
|
|
|
|
if(debug==true)
|
|
{
|
|
Print("Fetching HISTORY");
|
|
Print("1) Symbol:"+symbol);
|
|
Print("2) Timeframe:"+chartTF);
|
|
Print("3) Date from:"+TimeToString(startTime));
|
|
}
|
|
copied=CopyRates(symbol,period,startTime,TimeCurrent(),rates);
|
|
if(copied)
|
|
|
|
{
|
|
for(int i=0;i<copied;i++)
|
|
{
|
|
candles[i].Add(rates[i].time,TIME_DATE|TIME_MINUTES|TIME_SECONDS);
|
|
candles[i].Add(rates[i].open);
|
|
candles[i].Add(rates[i].high);
|
|
candles[i].Add(rates[i].low);
|
|
candles[i].Add(rates[i].close);
|
|
candles[i].Add(rates[i].tick_volume);
|
|
}
|
|
string t=candles.Serialize();
|
|
if(debug==true) {Print(t);}
|
|
InformClientSocket(dataSocket,t);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Fetch positions information |
|
|
//+------------------------------------------------------------------+
|
|
void GetPositionsInfo(Socket &dataSocket)
|
|
{
|
|
|
|
if(debug==true) {Print("Fetching positions...");}
|
|
|
|
CJAVal data, position;
|
|
|
|
// get positions
|
|
int positions=PositionsTotal();
|
|
// go through positions in a loop
|
|
for(int i=0;i<positions;i++)
|
|
{
|
|
ResetLastError();
|
|
// copy into the cache, the position by its number in the list
|
|
// the position was copied into the cache, work with it
|
|
if(PositionGetSymbol(i)!="")
|
|
{
|
|
position["id"] = PositionGetInteger(POSITION_IDENTIFIER);
|
|
position["magic"] = PositionGetInteger(POSITION_MAGIC);
|
|
position["symbol"] = PositionGetString(POSITION_SYMBOL);
|
|
position["type"] = EnumToString(ENUM_POSITION_TYPE(PositionGetInteger(POSITION_TYPE)));
|
|
position["time_setup"]=PositionGetInteger(POSITION_TIME);
|
|
position["open"] = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
position["stoploss"] = PositionGetDouble(POSITION_SL);
|
|
position["takeprofit"] = PositionGetDouble(POSITION_TP);
|
|
position["volume"] = PositionGetDouble(POSITION_VOLUME);
|
|
|
|
data["positions"].Add(position);
|
|
}
|
|
else
|
|
{
|
|
// call PositionGetSymbol() was completed unsuccessfully
|
|
data["error"]= GetLastError();
|
|
PrintFormat("Error when obtaining an positions from the list to the cache. Error code: %d",GetLastError());
|
|
}
|
|
}
|
|
|
|
|
|
// if no open positions/orders
|
|
if(data["positions"]==0)
|
|
{
|
|
data["positions"].Add(0);
|
|
}
|
|
|
|
string t=data.Serialize();
|
|
if(debug==true) {Print(t);}
|
|
InformClientSocket(dataSocket,t);
|
|
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Fetch orders information |
|
|
//+------------------------------------------------------------------+
|
|
void GetOrdersInfo(Socket &dataSocket)
|
|
{
|
|
|
|
if(debug==true) {Print("Fetching orders...");}
|
|
|
|
CJAVal data, order;
|
|
|
|
// get orders
|
|
int orders=OrdersTotal();
|
|
// go through orders in a loop
|
|
for(int i=0;i<orders;i++)
|
|
{
|
|
ResetLastError();
|
|
// copy into the cache, the order by its number in the list
|
|
ulong ticket=OrderGetTicket(i);
|
|
// if the order was successfully copied into the cache, work with it
|
|
if(ticket!=0)
|
|
{
|
|
order["id"] = IntegerToString(ticket);
|
|
order["magic"] = OrderGetInteger(ORDER_MAGIC);
|
|
order["symbol"] = OrderGetString(ORDER_SYMBOL);
|
|
order["type"] = EnumToString(ENUM_ORDER_TYPE(OrderGetInteger(ORDER_TYPE)));
|
|
order["time_setup"]=OrderGetInteger(ORDER_TIME_SETUP);
|
|
order["open"] = OrderGetDouble(ORDER_PRICE_OPEN);
|
|
order["stoploss"] = OrderGetDouble(ORDER_SL);
|
|
order["takeprofit"] = OrderGetDouble(ORDER_TP);
|
|
order["volume"] = OrderGetDouble(ORDER_VOLUME_INITIAL);
|
|
|
|
data["orders"].Add(order);
|
|
|
|
}
|
|
else
|
|
{
|
|
// call OrderGetTicket() was completed unsuccessfully
|
|
data["error"]= GetLastError();
|
|
PrintFormat("Error when obtaining an order from the list to the cache. Error code: %d",GetLastError());
|
|
}
|
|
}
|
|
|
|
// if no open positions/orders
|
|
if(data["orders"]==0)
|
|
{
|
|
data["orders"].Add(1);
|
|
}
|
|
|
|
string t=data.Serialize();
|
|
if(debug==true) {Print(t);}
|
|
InformClientSocket(dataSocket,t);
|
|
|
|
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Trading module |
|
|
//+------------------------------------------------------------------+
|
|
void TradingModule(Socket &dataSocket, CJAVal &dataObject)
|
|
{
|
|
|
|
string actionType = dataObject["actionType"].ToStr();
|
|
|
|
if(actionType=="ORDER_TYPE_BUY" || actionType=="ORDER_TYPE_SELL")
|
|
{
|
|
ResetLastError();
|
|
MqlTradeRequest order={0};
|
|
MqlTradeResult result={0};
|
|
|
|
order.action=TRADE_ACTION_DEAL;
|
|
order.symbol=dataObject["symbol"].ToStr();
|
|
order.volume=dataObject["volume"].ToDbl();
|
|
|
|
if(actionType=="ORDER_TYPE_BUY")
|
|
{
|
|
order.type=ORDER_TYPE_BUY;
|
|
order.price=SymbolInfoDouble(symbol,SYMBOL_ASK);
|
|
}
|
|
else if(actionType=="ORDER_TYPE_SELL")
|
|
{
|
|
order.type=ORDER_TYPE_SELL;
|
|
order.price=SymbolInfoDouble(symbol,SYMBOL_BID);
|
|
}
|
|
else Alert("Something wrong with market orders...");
|
|
|
|
order.sl=dataObject["stoploss"].ToDbl();
|
|
order.tp=dataObject["takeprofit"].ToDbl();
|
|
order.deviation=dataObject["deviation"].ToInt();
|
|
|
|
bool success=OrderSend(order,result);
|
|
if(!success) {InformClientSocket(dataSocket,StringFormat("OrderSend error %d",GetLastError()));}
|
|
else {
|
|
CJAVal conf;
|
|
|
|
conf["retcode"] = (int) result.retcode;
|
|
conf["deal"] = (string)result.deal;
|
|
conf["order"] = "null";
|
|
conf["volume"] = result.volume;
|
|
conf["price"] = result.price;
|
|
conf["bid"] = result.bid;
|
|
conf["ask"] = result.ask;
|
|
conf["comment"] = result.comment;
|
|
|
|
string t=conf.Serialize();
|
|
if(debug==true) { Print("Order conformation: "+t);}
|
|
InformClientSocket(dataSocket,t);
|
|
}
|
|
}
|
|
|
|
else if(actionType=="ORDER_TYPE_BUY_LIMIT" || actionType=="ORDER_TYPE_SELL_LIMIT" || actionType=="ORDER_TYPE_BUY_STOP" || actionType=="ORDER_TYPE_SELL_STOP")
|
|
{
|
|
|
|
ResetLastError();
|
|
MqlTradeRequest order={0};
|
|
MqlTradeResult result={0};
|
|
|
|
order.action=TRADE_ACTION_PENDING;
|
|
order.symbol=dataObject["symbol"].ToStr();
|
|
order.volume=dataObject["volume"].ToDbl();
|
|
|
|
// setting order type
|
|
if(actionType=="ORDER_TYPE_BUY_LIMIT") {order.type=ORDER_TYPE_BUY_LIMIT;}
|
|
else if(actionType=="ORDER_TYPE_SELL_LIMIT"){order.type=ORDER_TYPE_SELL_LIMIT;}
|
|
else if(actionType=="ORDER_TYPE_BUY_STOP"){order.type=ORDER_TYPE_BUY_STOP;}
|
|
else if(actionType=="ORDER_TYPE_SELL_STOP"){order.type=ORDER_TYPE_SELL_STOP;}
|
|
else Alert("Something wrong with pending orders...");
|
|
|
|
order.price=NormalizeDouble(dataObject["price"].ToDbl(),_Digits);
|
|
order.sl=dataObject["stoploss"].ToDbl();
|
|
order.tp=dataObject["takeprofit"].ToDbl();
|
|
order.deviation=dataObject["deviation"].ToInt();
|
|
order.type_time=ORDER_TIME_DAY;
|
|
order.type_filling=ORDER_FILLING_RETURN;
|
|
|
|
bool success=OrderSend(order,result);
|
|
if(!success) {InformClientSocket(dataSocket,StringFormat("OrderSend error %d",GetLastError()));}
|
|
else {
|
|
CJAVal conf;
|
|
|
|
conf["retcode"] = (int) result.retcode;
|
|
conf["deal"] = "null";
|
|
conf["order"] = (string)result.order;
|
|
conf["volume"] = result.volume;
|
|
conf["price"] = result.price;
|
|
conf["bid"] = result.bid;
|
|
conf["ask"] = result.ask;
|
|
conf["comment"] = result.comment;
|
|
|
|
string t=conf.Serialize();
|
|
if(debug==true) { Print("Order conformation: "+t);}
|
|
InformClientSocket(dataSocket,t);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Inform Client via socket |
|
|
//+------------------------------------------------------------------+
|
|
void InformClientSocket(Socket &workingSocket,string replyMessage)
|
|
{
|
|
ZmqMsg pushReply(StringFormat("%s",replyMessage));
|
|
|
|
// workingSocket.send(pushReply,false); // BLOCKING
|
|
workingSocket.send(pushReply,true); // NON-BLOCKING
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Trade function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTrade()
|
|
{
|
|
//---
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Push candle to liveSocket on close |
|
|
//+------------------------------------------------------------------+
|
|
datetime ThisBar=(datetime)SeriesInfoInteger(symbol,period,SERIES_LASTBAR_DATE);
|
|
|
|
if(LastBar!=ThisBar)
|
|
{
|
|
MqlRates rates[1];
|
|
CJAVal candle;
|
|
|
|
if(CopyRates(symbol,period,1,1,rates)!=1) { /*error processing */ };
|
|
|
|
candle[0] = (long) rates[0].time;
|
|
candle[1] = (double) rates[0].open;
|
|
candle[2] = (double) rates[0].high;
|
|
candle[3] = (double) rates[0].low;
|
|
candle[4] = (double) rates[0].close;
|
|
candle[5] = (double) rates[0].tick_volume;
|
|
|
|
string t=candle.Serialize();
|
|
if(debug==true)
|
|
{
|
|
PrintFormat("New candle: %s",TimeToString(rates[0].time));
|
|
}
|
|
InformClientSocket(liveSocket,t);
|
|
|
|
LastBar=ThisBar;
|
|
}
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|