├── .ipynb_checkpoints └── example-zmq-checkpoint.ipynb ├── example-zmq.ipynb └── zmq-server.mq5 /.ipynb_checkpoints/example-zmq-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import zmq" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 2, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "def remote_send(socket, data):\n", 19 | " try:\n", 20 | " socket.send_string(data)\n", 21 | " msg = socket.recv_string()\n", 22 | " return (msg)\n", 23 | " except zmq.Again as e:\n", 24 | " print (\"Waiting for PUSH from MetaTrader 4..\")" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 3, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "data": { 34 | "text/plain": [ 35 | "['24.77', '24.79', '0', '0', '2700', '2700', '0', '0']" 36 | ] 37 | }, 38 | "execution_count": 3, 39 | "metadata": {}, 40 | "output_type": "execute_result" 41 | } 42 | ], 43 | "source": [ 44 | "# Get zmq context\n", 45 | "context = zmq.Context()\n", 46 | "\n", 47 | "# Create REQ Socket\n", 48 | "reqSocket = context.socket(zmq.REQ)\n", 49 | "reqSocket.connect(\"tcp://localhost:5555\")\n", 50 | "\n", 51 | "# Send RATES command to ZeroMQ MT4 EA\n", 52 | "petr4 = remote_send(reqSocket, \"RATES|PETR4\")\n", 53 | "\n", 54 | "petr4.split(',')\n", 55 | "# bid, ask, buy_volume, sell_volume, tick_volume, real_volume, buy_volume_market, sell_volume_market" 56 | ] 57 | } 58 | ], 59 | "metadata": { 60 | "kernelspec": { 61 | "display_name": "Python 3", 62 | "language": "python", 63 | "name": "python3" 64 | }, 65 | "language_info": { 66 | "codemirror_mode": { 67 | "name": "ipython", 68 | "version": 3 69 | }, 70 | "file_extension": ".py", 71 | "mimetype": "text/x-python", 72 | "name": "python", 73 | "nbconvert_exporter": "python", 74 | "pygments_lexer": "ipython3", 75 | "version": "3.7.1" 76 | } 77 | }, 78 | "nbformat": 4, 79 | "nbformat_minor": 2 80 | } 81 | -------------------------------------------------------------------------------- /example-zmq.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import zmq" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 2, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "def remote_send(socket, data):\n", 19 | " try:\n", 20 | " socket.send_string(data)\n", 21 | " msg = socket.recv_string()\n", 22 | " return (msg)\n", 23 | " except zmq.Again as e:\n", 24 | " print (\"Waiting for PUSH from MetaTrader 4..\")" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 3, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "data": { 34 | "text/plain": [ 35 | "['24.77', '24.79', '0', '0', '2700', '2700', '0', '0']" 36 | ] 37 | }, 38 | "execution_count": 3, 39 | "metadata": {}, 40 | "output_type": "execute_result" 41 | } 42 | ], 43 | "source": [ 44 | "# Get zmq context\n", 45 | "context = zmq.Context()\n", 46 | "\n", 47 | "# Create REQ Socket\n", 48 | "reqSocket = context.socket(zmq.REQ)\n", 49 | "reqSocket.connect(\"tcp://localhost:5555\")\n", 50 | "\n", 51 | "# Send RATES command to ZeroMQ MT4 EA\n", 52 | "petr4 = remote_send(reqSocket, \"RATES|PETR4\")\n", 53 | "\n", 54 | "petr4.split(',')\n", 55 | "# bid, ask, buy_volume, sell_volume, tick_volume, real_volume, buy_volume_market, sell_volume_market" 56 | ] 57 | } 58 | ], 59 | "metadata": { 60 | "kernelspec": { 61 | "display_name": "Python 3", 62 | "language": "python", 63 | "name": "python3" 64 | }, 65 | "language_info": { 66 | "codemirror_mode": { 67 | "name": "ipython", 68 | "version": 3 69 | }, 70 | "file_extension": ".py", 71 | "mimetype": "text/x-python", 72 | "name": "python", 73 | "nbconvert_exporter": "python", 74 | "pygments_lexer": "ipython3", 75 | "version": "3.7.1" 76 | } 77 | }, 78 | "nbformat": 4, 79 | "nbformat_minor": 2 80 | } 81 | -------------------------------------------------------------------------------- /zmq-server.mq5: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern string PROJECT_NAME = "TradeServer"; 5 | extern string ZEROMQ_PROTOCOL = "tcp"; 6 | extern string HOSTNAME = "*"; 7 | extern int REP_PORT = 5555; 8 | extern int MILLISECOND_TIMER = 1; // 1 millisecond 9 | 10 | extern string t0 = "--- Trading Parameters ---"; 11 | extern int MagicNumber = 123456; 12 | 13 | // CREATE ZeroMQ Context 14 | Context context(PROJECT_NAME); 15 | 16 | // CREATE ZMQ_REP SOCKET 17 | Socket repSocket(context,ZMQ_REP); 18 | 19 | // VARIABLES FOR LATER 20 | uchar myData[]; 21 | ZmqMsg request; 22 | 23 | 24 | //+------------------------------------------------------------------+ 25 | //| Expert initialization function | 26 | //+------------------------------------------------------------------+ 27 | int OnInit() 28 | { 29 | EventSetMillisecondTimer(MILLISECOND_TIMER); // Set Millisecond Timer to get client socket input 30 | 31 | Print("[REP] Binding MT4 Server to Socket on Port " + IntegerToString(REP_PORT) + ".."); 32 | 33 | repSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, REP_PORT)); 34 | 35 | /* 36 | Maximum amount of time in milliseconds that the thread will try to send messages 37 | after its socket has been closed (the default value of -1 means to linger forever): 38 | */ 39 | 40 | repSocket.setLinger(1000); // 1000 milliseconds 41 | 42 | /* 43 | If we initiate socket.send() without having a corresponding socket draining the queue, 44 | we'll eat up memory as the socket just keeps enqueueing messages. 45 | 46 | So how many messages do we want ZeroMQ to buffer in RAM before blocking the socket? 47 | */ 48 | 49 | repSocket.setSendHighWaterMark(5); // 5 messages only. 50 | 51 | return(INIT_SUCCEEDED); 52 | } 53 | 54 | //+------------------------------------------------------------------+ 55 | //| Expert deinitialization function | 56 | //+------------------------------------------------------------------+ 57 | void OnDeinit(const int reason) 58 | { 59 | Print("[REP] Unbinding MT4 Server from Socket on Port " + IntegerToString(REP_PORT) + ".."); 60 | repSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, REP_PORT)); 61 | } 62 | //+------------------------------------------------------------------+ 63 | //| Expert timer function | 64 | //+------------------------------------------------------------------+ 65 | void OnTimer() 66 | { 67 | // Get client's response, but don't wait. 68 | repSocket.recv(request,true); 69 | 70 | // MessageHandler() should go here. 71 | MessageHandler(request); 72 | } 73 | //+------------------------------------------------------------------+ 74 | 75 | void MessageHandler(ZmqMsg &localRequest) 76 | { 77 | // Output object 78 | ZmqMsg reply; 79 | 80 | // Message components for later. 81 | string components[]; 82 | 83 | if(localRequest.size() > 0) { 84 | // Get data from request 85 | ArrayResize(myData, localRequest.size()); 86 | localRequest.getData(myData); 87 | string dataStr = CharArrayToString(myData); 88 | 89 | // Process data 90 | ParseZmqMessage(dataStr, components); 91 | 92 | // Interpret data 93 | InterpretZmqMessage(components); 94 | } 95 | } 96 | 97 | //+------------------------------------------------------------------+ 98 | ENUM_TIMEFRAMES TFMigrate(int tf) 99 | { 100 | switch(tf) 101 | { 102 | case 0: return(PERIOD_CURRENT); 103 | case 1: return(PERIOD_M1); 104 | case 5: return(PERIOD_M5); 105 | case 15: return(PERIOD_M15); 106 | case 30: return(PERIOD_M30); 107 | case 60: return(PERIOD_H1); 108 | case 240: return(PERIOD_H4); 109 | case 1440: return(PERIOD_D1); 110 | case 10080: return(PERIOD_W1); 111 | case 43200: return(PERIOD_MN1); 112 | 113 | case 2: return(PERIOD_M2); 114 | case 3: return(PERIOD_M3); 115 | case 4: return(PERIOD_M4); 116 | case 6: return(PERIOD_M6); 117 | case 10: return(PERIOD_M10); 118 | case 12: return(PERIOD_M12); 119 | case 16385: return(PERIOD_H1); 120 | case 16386: return(PERIOD_H2); 121 | case 16387: return(PERIOD_H3); 122 | case 16388: return(PERIOD_H4); 123 | case 16390: return(PERIOD_H6); 124 | case 16392: return(PERIOD_H8); 125 | case 16396: return(PERIOD_H12); 126 | case 16408: return(PERIOD_D1); 127 | case 32769: return(PERIOD_W1); 128 | case 49153: return(PERIOD_MN1); 129 | default: return(PERIOD_CURRENT); 130 | } 131 | } 132 | 133 | //+------------------------------------------------------------------+ 134 | // Interpret Zmq Message and perform actions 135 | void InterpretZmqMessage(string& compArray[]) 136 | { 137 | Print("ZMQ: Interpreting Message.."); 138 | 139 | // 1) Trading 140 | // TRADE|ACTION|TYPE|SYMBOL|PRICE|SL|TP|COMMENT|TICKET 141 | // e.g. TRADE|OPEN|1|EURUSD|0|50|50|R-to-MetaTrader4|12345678 142 | 143 | // The 12345678 at the end is the ticket ID, for MODIFY and CLOSE. 144 | 145 | // 2) Data Requests 146 | 147 | // 2.1) RATES|SYMBOL -> Returns Current Bid/Ask 148 | 149 | // 2.2) DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME 150 | 151 | // NOTE: datetime has format: D'2015.01.01 00:00' 152 | 153 | /* 154 | compArray[0] = TRADE or RATES 155 | If RATES -> compArray[1] = Symbol 156 | 157 | If TRADE -> 158 | compArray[0] = TRADE 159 | compArray[1] = ACTION (e.g. OPEN, MODIFY, CLOSE) 160 | compArray[2] = TYPE (e.g. OP_BUY, OP_SELL, etc - only used when ACTION=OPEN) 161 | 162 | // ORDER TYPES: 163 | // https://docs.mql4.com/constants/tradingconstants/orderproperties 164 | 165 | // OP_BUY = 0 166 | // OP_SELL = 1 167 | // OP_BUYLIMIT = 2 168 | // OP_SELLLIMIT = 3 169 | // OP_BUYSTOP = 4 170 | // OP_SELLSTOP = 5 171 | 172 | compArray[3] = Symbol (e.g. EURUSD, etc.) 173 | compArray[4] = Open/Close Price (ignored if ACTION = MODIFY) 174 | compArray[5] = SL 175 | compArray[6] = TP 176 | compArray[7] = Trade Comment 177 | */ 178 | 179 | int switch_action = 0; 180 | string volume; 181 | 182 | if (compArray[0] == "TRADE" && compArray[1] == "OPEN") 183 | switch_action = 1; 184 | else if (compArray[0] == "RATES") 185 | switch_action = 2; 186 | else if (compArray[0] == "TRADE" && compArray[1] == "CLOSE") 187 | switch_action = 3; 188 | else if (compArray[0] == "DATA") 189 | switch_action = 4; 190 | 191 | string ret = ""; 192 | int ticket = -1; 193 | bool ans = false; 194 | 195 | MqlRates rates[]; 196 | ArraySetAsSeries(rates, true); 197 | 198 | int price_count = 0; 199 | 200 | ZmqMsg msg("[SERVER] Processing"); 201 | 202 | switch(switch_action) 203 | { 204 | case 1: 205 | repSocket.send(msg, false); 206 | // IMPLEMENT OPEN TRADE LOGIC HERE 207 | break; 208 | case 2: 209 | ret = "N/A"; 210 | if(ArraySize(compArray) > 1) 211 | ret = GetCurrent(compArray[1]); 212 | repSocket.send(ret, false); 213 | break; 214 | case 3: 215 | repSocket.send(msg, false); 216 | // IMPLEMENT CLOSE TRADE LOGIC HERE 217 | 218 | break; 219 | case 4: 220 | ret = ""; 221 | // Format: DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME 222 | price_count = CopyRates(compArray[1], TFMigrate(StringToInteger(compArray[2])), 223 | StringToTime(compArray[3]), StringToTime(compArray[4]), 224 | rates); 225 | 226 | if (price_count > 0) 227 | { 228 | // Construct string of price|price|price|.. etc and send to PULL client. 229 | for(int i = 0; i < price_count; i++ ) { 230 | ret = ret + "|" + StringFormat("%.2f,%.2f,%.2f,%.2f,%d,%d", rates[i].open, rates[i].low, rates[i].high, rates[i].close, rates[i].tick_volume, rates[i].real_volume); 231 | } 232 | 233 | Print("Sending: " + ret); 234 | repSocket.send(ret, false); 235 | } 236 | break; 237 | default: 238 | break; 239 | } 240 | } 241 | //+------------------------------------------------------------------+ 242 | // Parse Zmq Message 243 | void ParseZmqMessage(string& message, string& retArray[]) 244 | { 245 | Print("Parsing: " + message); 246 | 247 | string sep = "|"; 248 | ushort u_sep = StringGetCharacter(sep,0); 249 | 250 | int splits = StringSplit(message, u_sep, retArray); 251 | 252 | for(int i = 0; i < splits; i++) { 253 | Print(IntegerToString(i) + ") " + retArray[i]); 254 | } 255 | } 256 | 257 | //+------------------------------------------------------------------+ 258 | string GetVolume(string symbol, datetime start_time, datetime stop_time) 259 | { 260 | long volume_array[1]; 261 | CopyRealVolume(symbol, PERIOD_M1, start_time, stop_time, volume_array); 262 | 263 | return(StringFormat("%d", volume_array[0])); 264 | } 265 | 266 | //+------------------------------------------------------------------+ 267 | string GetCurrent(string symbol) 268 | { 269 | MqlTick Last_tick; 270 | SymbolInfoTick(symbol,Last_tick); 271 | double bid = Last_tick.bid; 272 | double ask = Last_tick.ask; 273 | MqlBookInfo bookArray[]; 274 | bool getBook = MarketBookGet(symbol,bookArray); 275 | long buy_volume = 0; 276 | long sell_volume = 0; 277 | long buy_volume_market = 0; 278 | long sell_volume_market = 0; 279 | if (getBook) { 280 | for (int i =0; i < ArraySize(bookArray); i++ ) 281 | { 282 | if (bookArray[i].type == BOOK_TYPE_SELL) 283 | sell_volume += bookArray[i].volume_real; 284 | else if (bookArray[i].type == BOOK_TYPE_BUY) 285 | buy_volume += bookArray[i].volume_real; 286 | else if (bookArray[i].type == BOOK_TYPE_BUY_MARKET) 287 | buy_volume_market += bookArray[i].volume_real; 288 | else 289 | sell_volume_market += bookArray[i].volume_real; 290 | } 291 | } 292 | long tick_volume = Last_tick.volume; 293 | long real_volume = Last_tick.volume_real; 294 | MarketBookAdd(symbol); 295 | return(StringFormat("%.2f,%.2f,%d,%d,%d,%d,%d,%d", bid, ask, buy_volume, sell_volume, tick_volume, real_volume, buy_volume_market, sell_volume_market)); 296 | } --------------------------------------------------------------------------------