├── .idea ├── other.xml └── vcs.xml ├── Jupyter Notebooks ├── Historical Data Extraction and Comparison.ipynb ├── Testing Subscription Tick Data - MQL5.ipynb ├── Testing Subscription Tick Data.ipynb ├── Testing.ipynb └── testTickData.h5 ├── MQL4 Server Connector ├── DWX_ZeroMQ_Server_v2.0.1_RC8.mq4 └── Server Connector Original.mq4 ├── MQL5 Server Connector └── MQL Server ZeroMQ Connector.mq5 ├── Prototype Python Connector ├── ZeroMQ Python Connector.py ├── dwx_tick_data_io.py └── dwx_tickdata_download.py ├── Python Client Connector ├── Client Testing.py └── python_client_connector.py ├── README.md └── TODO.txt /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Jupyter Notebooks/testTickData.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UmaisZahid/MetaTrader-Python-Tick-Acquisition/d17ef44b926bf9e6da969837e9fcf70de8de1dae/Jupyter Notebooks/testTickData.h5 -------------------------------------------------------------------------------- /MQL4 Server Connector/DWX_ZeroMQ_Server_v2.0.1_RC8.mq4: -------------------------------------------------------------------------------- 1 | //+--------------------------------------------------------------+ 2 | //| DWX_ZeroMQ_Server_v2.0.1_RC8.mq4 3 | //| @author: Darwinex Labs (www.darwinex.com) 4 | //| 5 | //| Last Updated: May 16, 2019 6 | //| 7 | //| Copyright (c) 2017-2019, Darwinex. All rights reserved. 8 | //| 9 | //| Licensed under the BSD 3-Clause License, you may not use this file except 10 | //| in compliance with the License. 11 | //| 12 | //| You may obtain a copy of the License at: 13 | //| https://opensource.org/licenses/BSD-3-Clause 14 | //+--------------------------------------------------------------+ 15 | #property copyright "Copyright 2017-2019, Darwinex Labs." 16 | #property link "https://www.darwinex.com/" 17 | #property version "2.0.1" 18 | #property strict 19 | 20 | // Required: MQL-ZMQ from https://github.com/dingmaotu/mql-zmq 21 | 22 | #include 23 | 24 | #import "kernel32.dll" 25 | void GetSystemTimeAsFileTime(ulong &SystemTimeAsFileTime); // Returns the system time in 100 nsec units in a ulong 26 | #import 27 | 28 | extern string PROJECT_NAME = "DWX_ZeroMQ_MT4_Server"; 29 | extern string ZEROMQ_PROTOCOL = "tcp"; 30 | extern string HOSTNAME = "*"; 31 | extern int PUSH_PORT = 32768; 32 | extern int PULL_PORT = 32769; 33 | extern int PUB_PORT = 32770; 34 | extern int MILLISECOND_TIMER = 1; 35 | 36 | extern string t0 = "--- Trading Parameters ---"; 37 | extern int MagicNumber = 123456; 38 | extern int MaximumOrders = 1; 39 | extern double MaximumLotSize = 0.01; 40 | extern int MaximumSlippage = 3; 41 | extern bool DMA_MODE = true; 42 | 43 | extern string t1 = "--- ZeroMQ Configuration ---"; 44 | extern bool Publish_MarketData = false; 45 | 46 | string Publish_Symbols[] = { 47 | "GBPUSD" 48 | }; 49 | 50 | /* 51 | string Publish_Symbols[28] = { 52 | "EURUSD","EURGBP","EURAUD","EURNZD","EURJPY","EURCHF","EURCAD", 53 | "GBPUSD","AUDUSD","NZDUSD","USDJPY","USDCHF","USDCAD","GBPAUD", 54 | "GBPNZD","GBPJPY","GBPCHF","GBPCAD","AUDJPY","CHFJPY","CADJPY", 55 | "AUDNZD","AUDCHF","AUDCAD","NZDJPY","NZDCHF","NZDCAD","CADCHF" 56 | }; 57 | */ 58 | 59 | // CREATE ZeroMQ Context 60 | Context context(PROJECT_NAME); 61 | 62 | // CREATE ZMQ_PUSH SOCKET 63 | Socket pushSocket(context, ZMQ_PUSH); 64 | 65 | // CREATE ZMQ_PULL SOCKET 66 | Socket pullSocket(context, ZMQ_PULL); 67 | 68 | // CREATE ZMQ_PUB SOCKET 69 | Socket pubSocket(context, ZMQ_PUB); 70 | 71 | // VARIABLES FOR LATER 72 | uchar _data[]; 73 | ZmqMsg request; 74 | ulong timeOne, timeTwo; 75 | 76 | 77 | //+------------------------------------------------------------------+ 78 | //| Expert initialization function | 79 | //+------------------------------------------------------------------+ 80 | int OnInit() 81 | { 82 | //--- 83 | 84 | EventSetMillisecondTimer(MILLISECOND_TIMER); // Set Millisecond Timer to get client socket input 85 | 86 | context.setBlocky(false); 87 | 88 | // Send responses to PULL_PORT that client is listening on. 89 | Print("[PUSH] Binding MT4 Server to Socket on Port " + IntegerToString(PULL_PORT) + ".."); 90 | pushSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PULL_PORT)); 91 | pushSocket.setSendHighWaterMark(1); 92 | pushSocket.setLinger(0); 93 | 94 | // Receive commands from PUSH_PORT that client is sending to. 95 | Print("[PULL] Binding MT4 Server to Socket on Port " + IntegerToString(PUSH_PORT) + ".."); 96 | pullSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUSH_PORT)); 97 | pullSocket.setReceiveHighWaterMark(1); 98 | pullSocket.setLinger(0); 99 | 100 | if (Publish_MarketData == true) 101 | { 102 | // Send new market data to PUB_PORT that client is subscribed to. 103 | Print("[PUB] Binding MT4 Server to Socket on Port " + IntegerToString(PUB_PORT) + ".."); 104 | pubSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUB_PORT)); 105 | } 106 | 107 | //--- 108 | return(INIT_SUCCEEDED); 109 | } 110 | //+------------------------------------------------------------------+ 111 | //| Expert deinitialization function | 112 | //+------------------------------------------------------------------+ 113 | void OnDeinit(const int reason) 114 | { 115 | //--- 116 | 117 | Print("[PUSH] Unbinding MT4 Server from Socket on Port " + IntegerToString(PULL_PORT) + ".."); 118 | pushSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PULL_PORT)); 119 | 120 | Print("[PULL] Unbinding MT4 Server from Socket on Port " + IntegerToString(PUSH_PORT) + ".."); 121 | pullSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUSH_PORT)); 122 | 123 | if (Publish_MarketData == TRUE) 124 | { 125 | Print("[PUB] Unbinding MT4 Server from Socket on Port " + IntegerToString(PUB_PORT) + ".."); 126 | pubSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUB_PORT)); 127 | } 128 | 129 | // Shutdown ZeroMQ Context 130 | context.shutdown(); 131 | context.destroy(0); 132 | 133 | EventKillTimer(); 134 | } 135 | 136 | //+------------------------------------------------------------------+ 137 | //| Expert tick function | 138 | //+------------------------------------------------------------------+ 139 | void OnTick() 140 | { 141 | /* 142 | Use this OnTick() function to send market data to subscribed client. 143 | */ 144 | MqlTick last_tick; 145 | string _tick; 146 | if(!IsStopped() && Publish_MarketData == true) 147 | { 148 | 149 | for(int s = 0; s < ArraySize(Publish_Symbols); s++) 150 | { 151 | 152 | if(SymbolInfoTick(Publish_Symbols[s],last_tick)) 153 | { 154 | _tick = StringFormat("%f|%f|%s %s|%f|%f|%u", last_tick.bid, last_tick.ask, TimeToString(last_tick.time, TIME_DATE), TimeToString(last_tick.time, TIME_SECONDS), last_tick.last, last_tick.volume, last_tick.flags); 155 | } 156 | // Python clients can subscribe to a price feed by setting 157 | // socket options to the symbol name. For example: 158 | //GetSystemTimeAsFileTime(timeOne); 159 | // Print("Sending " + Publish_Symbols[s] + " " + _tick + " to PUB Socket"); 160 | //Print("Tick at time: " + (string)(timeOne/10)); 161 | Print("Message:" + StringFormat("%s;%s", Publish_Symbols[s], _tick)); 162 | ZmqMsg reply(StringFormat("%s;%s", Publish_Symbols[s], _tick)); 163 | pubSocket.send(reply, true); 164 | } 165 | } 166 | } 167 | 168 | //+------------------------------------------------------------------+ 169 | //| Expert timer function | 170 | //+------------------------------------------------------------------+ 171 | void OnTimer() 172 | { 173 | //--- 174 | 175 | /* 176 | Use this OnTimer() function to get and respond to commands 177 | */ 178 | 179 | // Get client's response, but don't block. 180 | pullSocket.recv(request, true); 181 | 182 | if (request.size() > 0) 183 | { 184 | // Wait 185 | // pullSocket.recv(request,false); 186 | 187 | // MessageHandler() should go here. 188 | ZmqMsg reply = MessageHandler(request); 189 | 190 | // Send response, and block 191 | // pushSocket.send(reply); 192 | 193 | // Send response, but don't block 194 | pushSocket.send(reply, true); 195 | } 196 | } 197 | //+------------------------------------------------------------------+ 198 | 199 | ZmqMsg MessageHandler(ZmqMsg &_request) { 200 | 201 | // Output object 202 | ZmqMsg reply; 203 | 204 | // Message components for later. 205 | string components[12]; 206 | 207 | if(_request.size() > 0) { 208 | 209 | // Get the data send to server pull port 210 | ArrayResize(_data, _request.size()); 211 | _request.getData(_data); 212 | string dataStr = CharArrayToString(_data); 213 | 214 | // Process data 215 | ParseZmqMessage(dataStr, components); 216 | 217 | // Interpret data and send response to pull socket 218 | InterpretZmqMessage(&pushSocket, components); 219 | 220 | } 221 | else { 222 | // NO DATA RECEIVED 223 | } 224 | 225 | return(reply); 226 | } 227 | 228 | // Interpret Zmq Message and perform actions 229 | void InterpretZmqMessage(Socket &pSocket, string &compArray[]) { 230 | 231 | // Print("ZMQ: Interpreting Message.."); 232 | 233 | // Message Structures: 234 | 235 | // 1) Trading 236 | // MESSAGEID|TRADE|ACTION|TYPE|SYMBOL|PRICE|SL|TP|COMMENT|TICKET 237 | // e.g. TRADE|OPEN|1|EURUSD|0|50|50|R-to-MetaTrader4|12345678 238 | 239 | // The 12345678 at the end is the ticket ID, for MODIFY and CLOSE. 240 | 241 | // 2) Data Requests 242 | 243 | // 2.1) RATES|SYMBOL -> Returns Current Bid/Ask 244 | 245 | // 2.2) MESSAGEID|DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME 246 | 247 | // NOTE: datetime has format: D'2015.01.01 00:00' 248 | 249 | /* 250 | compArray[1] = TRADE or RATES 251 | If RATES -> 252 | compArray[2] = Symbol 253 | compArray[3] = Timeframe (1 = 1 minute, 1440 = 1 day, etc.) 254 | compArray[4] = Start Time 255 | compArray[5] = Stop Time 256 | 257 | If TRADE -> 258 | compArray[1] = TRADE 259 | compArray[2] = ACTION (e.g. OPEN, MODIFY, CLOSE) 260 | compArray[3] = TYPE (e.g. OP_BUY, OP_SELL, etc - only used when ACTION=OPEN) 261 | 262 | // ORDER TYPES: 263 | // https://docs.mql4.com/constants/tradingconstants/orderproperties 264 | 265 | // OP_BUY = 0 266 | // OP_SELL = 1 267 | // OP_BUYLIMIT = 2 268 | // OP_SELLLIMIT = 3 269 | // OP_BUYSTOP = 4 270 | // OP_SELLSTOP = 5 271 | 272 | compArray[4] = Symbol (e.g. EURUSD, etc.) 273 | compArray[5] = Open/Close Price (ignored if ACTION = MODIFY) 274 | compArray[6] = SL 275 | compArray[7] = TP 276 | compArray[8] = Trade Comment 277 | compArray[9] = Lots 278 | compArray[10] = Magic Number 279 | compArray[11] = Ticket Number (MODIFY/CLOSE) 280 | */ 281 | 282 | int switch_action = 0; 283 | 284 | if(compArray[1] == "TRADE" && compArray[1] == "OPEN") 285 | switch_action = 1; 286 | if(compArray[1] == "TRADE" && compArray[1] == "MODIFY") 287 | switch_action = 2; 288 | if(compArray[1] == "TRADE" && compArray[1] == "CLOSE") 289 | switch_action = 3; 290 | if(compArray[1] == "TRADE" && compArray[1] == "CLOSE_PARTIAL") 291 | switch_action = 4; 292 | if(compArray[1] == "TRADE" && compArray[1] == "CLOSE_MAGIC") 293 | switch_action = 5; 294 | if(compArray[1] == "TRADE" && compArray[1] == "CLOSE_ALL") 295 | switch_action = 6; 296 | if(compArray[1] == "TRADE" && compArray[1] == "GET_OPEN_TRADES") 297 | switch_action = 7; 298 | if(compArray[1] == "DATA") 299 | switch_action = 8; 300 | if(compArray[1] == "DATA_RATES") 301 | switch_action = 9; 302 | 303 | string zmq_ret = ""; 304 | string ret = ""; 305 | int ticket = -1; 306 | bool ans = FALSE; 307 | 308 | switch(switch_action) 309 | { 310 | case 1: // OPEN TRADE 311 | 312 | zmq_ret = "{'_messageID': " + IntegerToString(compArray[0]) + ", "; 313 | 314 | // Function definition: 315 | ticket = DWX_OpenOrder(compArray[4], StrToInteger(compArray[3]), StrToDouble(compArray[9]), 316 | StrToDouble(compArray[5]), StrToInteger(compArray[6]), StrToInteger(compArray[7]), 317 | compArray[8], StrToInteger(compArray[10]), zmq_ret); 318 | 319 | // Send TICKET back as JSON 320 | InformPullClient(pSocket, zmq_ret + "}"); 321 | 322 | break; 323 | 324 | case 2: // MODIFY SL/TP 325 | 326 | zmq_ret = "{'_messageID': " + IntegerToString(compArray[0]) + ", '_action': 'MODIFY'"; 327 | 328 | // Function definition: 329 | ans = DWX_SetSLTP(StrToInteger(compArray[11]), StrToDouble(compArray[6]), StrToDouble(compArray[7]), 330 | StrToInteger(compArray[10]), StrToInteger(compArray[3]), StrToDouble(compArray[5]), 331 | compArray[4], 3, zmq_ret); 332 | 333 | InformPullClient(pSocket, zmq_ret + "}"); 334 | 335 | break; 336 | 337 | case 3: // CLOSE TRADE 338 | 339 | zmq_ret = "{'_messageID': " + IntegerToString(compArray[0]) + ", "; 340 | 341 | // IMPLEMENT CLOSE TRADE LOGIC HERE 342 | DWX_CloseOrder_Ticket(StrToInteger(compArray[11]), zmq_ret); 343 | 344 | InformPullClient(pSocket, zmq_ret + "}"); 345 | 346 | break; 347 | 348 | case 4: // CLOSE PARTIAL 349 | 350 | zmq_ret = "{'_messageID': " + IntegerToString(compArray[0]) + ", "; 351 | 352 | ans = DWX_ClosePartial(StrToDouble(compArray[9]), zmq_ret, StrToInteger(compArray[11])); 353 | 354 | InformPullClient(pSocket, zmq_ret + "}"); 355 | 356 | break; 357 | 358 | case 5: // CLOSE MAGIC 359 | 360 | zmq_ret = "{'_messageID': " + IntegerToString(compArray[0]) + ", "; 361 | 362 | DWX_CloseOrder_Magic(StrToInteger(compArray[10]), zmq_ret); 363 | 364 | InformPullClient(pSocket, zmq_ret + "}"); 365 | 366 | break; 367 | 368 | case 6: // CLOSE ALL ORDERS 369 | 370 | zmq_ret = "{'_messageID': " + IntegerToString(compArray[0]) + ", "; 371 | 372 | DWX_CloseAllOrders(zmq_ret); 373 | 374 | InformPullClient(pSocket, zmq_ret + "}"); 375 | 376 | break; 377 | 378 | case 7: // GET OPEN ORDERS 379 | 380 | zmq_ret = "{'_messageID': " + IntegerToString(compArray[0]) + ", "; 381 | 382 | DWX_GetOpenOrders(zmq_ret); 383 | 384 | InformPullClient(pSocket, zmq_ret + "}"); 385 | 386 | break; 387 | 388 | case 8: // DATA REQUEST 389 | 390 | zmq_ret = "{'_messageID': " + IntegerToString(compArray[0]) + ", "; 391 | 392 | DWX_GetData(compArray, zmq_ret); 393 | 394 | InformPullClient(pSocket, zmq_ret + "}"); 395 | 396 | break; 397 | 398 | case 9: // DATA REQUEST 399 | 400 | zmq_ret = "{'_messageID': " + IntegerToString(compArray[0]) + ", "; 401 | 402 | DWX_GetRatesData(compArray, zmq_ret); 403 | 404 | InformPullClient(pSocket, zmq_ret + "}"); 405 | 406 | break; 407 | 408 | 409 | default: 410 | break; 411 | } 412 | } 413 | 414 | // Parse Zmq Message 415 | void ParseZmqMessage(string& message, string& retArray[]) { 416 | 417 | //Print("Parsing: " + message); 418 | 419 | string sep = ";"; 420 | ushort u_sep = StringGetCharacter(sep,0); 421 | 422 | int splits = StringSplit(message, u_sep, retArray); 423 | 424 | /* 425 | for(int i = 0; i < splits; i++) { 426 | Print(IntegerToString(i) + ") " + retArray[i]); 427 | } 428 | */ 429 | } 430 | 431 | //+------------------------------------------------------------------+ 432 | // Generate string for Bid/Ask by symbol 433 | string GetBidAsk(string symbol) { 434 | 435 | MqlTick last_tick; 436 | 437 | if(SymbolInfoTick(symbol,last_tick)) 438 | { 439 | return(StringFormat("%f;%f", last_tick.bid, last_tick.ask)); 440 | } 441 | 442 | // Default 443 | return ""; 444 | } 445 | 446 | // Get data for request datetime range 447 | void DWX_GetData(string& compArray[], string& zmq_ret) { 448 | 449 | // Format: DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME 450 | 451 | double price_array[]; 452 | datetime time_array[]; 453 | int priceError = 0; 454 | int timeError = 0; 455 | 456 | // Get prices 457 | int price_count = CopyClose(compArray[2], 458 | StrToInteger(compArray[3]), StrToTime(compArray[4]), 459 | StrToTime(compArray[5]), price_array); 460 | 461 | if (price_count == -1){ 462 | priceError = GetLastError(); 463 | } 464 | 465 | // Get timestamps 466 | int time_count = CopyTime(compArray[2], 467 | StrToInteger(compArray[3]), StrToTime(compArray[4]), 468 | StrToTime(compArray[5]), time_array); 469 | 470 | if (price_count == -1){ 471 | timeError = GetLastError(); 472 | } 473 | 474 | zmq_ret = zmq_ret + "'_action': 'DATA'"; 475 | 476 | if (price_count > 0) { 477 | 478 | zmq_ret = zmq_ret + ", '_data': {"; 479 | 480 | // Construct string of price|price|price|.. etc and send to PULL client. 481 | for(int i = 0; i < price_count; i++ ) { 482 | 483 | if(i == 0) 484 | zmq_ret = zmq_ret + "'" + TimeToString(time_array[i]) + "': " + DoubleToString(price_array[i]); 485 | else 486 | zmq_ret = zmq_ret + ", '" + TimeToString(time_array[i]) + "': " + DoubleToString(price_array[i]); 487 | 488 | } 489 | 490 | zmq_ret = zmq_ret + "}"; 491 | Print(zmq_ret); 492 | 493 | } 494 | else if ((price_count == -1)) { 495 | zmq_ret = zmq_ret + ", " + "'_response': '" + IntegerToString(priceError) + "', 'response_value': '" + ErrorDescription(priceError) + "'"; 496 | Print(zmq_ret); 497 | } 498 | else { 499 | zmq_ret = zmq_ret + ", " + "'_response': 'NOT_AVAILABLE'"; 500 | Print(zmq_ret); 501 | } 502 | 503 | } 504 | 505 | // Get rates data for request datetime range 506 | void DWX_GetRatesData(string& compArray[], string& zmq_ret) { 507 | 508 | // Format: MESSAGEID|DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME 509 | MqlRates rates_array[]; 510 | int ratesError = 0; 511 | 512 | // Get prices 513 | int rates_count = CopyRates(compArray[2], 514 | StrToInteger(compArray[3]), StrToTime(compArray[4]), 515 | StrToTime(compArray[5]), rates_array); 516 | 517 | if (rates_count == -1){ 518 | ratesError = GetLastError(); 519 | } 520 | 521 | zmq_ret = zmq_ret + "'_action': 'DATA'"; 522 | 523 | if (rates_count > 0) { 524 | 525 | zmq_ret = zmq_ret + ", '_data': {"; 526 | 527 | // Construct string of price|price|price|.. etc and send to PULL client. 528 | for(int i = 0; i < rates_count; i++ ) { 529 | 530 | if(i == 0){ 531 | zmq_ret = zmq_ret + "'" + TimeToString(rates_array[i].time) + "': [" + DoubleToString(rates_array[i].open) + ", " + DoubleToString(rates_array[i].high) + ", " 532 | + DoubleToString(rates_array[i].low) + ", " + DoubleToString(rates_array[i].close) + ", " + (string)(rates_array[i].tick_volume) + ", " + IntegerToString(rates_array[i].spread) 533 | + ", " + (string)(rates_array[i].real_volume) + ", " + "]"; 534 | 535 | Print((string)(rates_array[i].tick_volume) + ", " + rates_array[i].spread + ", " + rates_array[i].real_volume); 536 | } 537 | else{ 538 | zmq_ret = zmq_ret + ", '" + TimeToString(rates_array[i].time) + "': [" + DoubleToString(rates_array[i].open) + ", " + DoubleToString(rates_array[i].high) + ", " 539 | + DoubleToString(rates_array[i].low) + ", " + DoubleToString(rates_array[i].close) + ", " + (string)(rates_array[i].tick_volume) + ", " + IntegerToString(rates_array[i].spread) 540 | + ", " + (string)(rates_array[i].real_volume) + ", " + "]"; 541 | } 542 | } 543 | 544 | zmq_ret = zmq_ret + "}"; 545 | Print(zmq_ret); 546 | 547 | } 548 | else if ((rates_count == -1)) { 549 | zmq_ret = zmq_ret + ", " + "'_response': '" + IntegerToString(ratesError) + "', 'response_value': '" + ErrorDescription(ratesError) + "'"; 550 | Print(zmq_ret); 551 | } 552 | else { 553 | zmq_ret = zmq_ret + ", " + "'_response': 'NOT_AVAILABLE'"; 554 | Print(zmq_ret); 555 | } 556 | 557 | } 558 | 559 | 560 | // Inform Client 561 | void InformPullClient(Socket& pSocket, string message) { 562 | 563 | ZmqMsg pushReply(StringFormat("%s", message)); 564 | 565 | pSocket.send(pushReply,true); // NON-BLOCKING 566 | 567 | } 568 | 569 | /* 570 | ############################################################################ 571 | ############################################################################ 572 | ############################################################################ 573 | */ 574 | 575 | // OPEN NEW ORDER 576 | int DWX_OpenOrder(string _symbol, int _type, double _lots, double _price, double _SL, double _TP, string _comment, int _magic, string &zmq_ret) { 577 | 578 | int ticket, error; 579 | 580 | zmq_ret = zmq_ret + "'_action': 'EXECUTION'"; 581 | 582 | if(_lots > MaximumLotSize) { 583 | zmq_ret = zmq_ret + ", " + "'_response': 'LOT_SIZE_ERROR', 'response_value': 'MAX_LOT_SIZE_EXCEEDED'"; 584 | return(-1); 585 | } 586 | 587 | double sl = _SL; 588 | double tp = _TP; 589 | 590 | // Else 591 | if(DMA_MODE) { 592 | sl = 0.0; 593 | tp = 0.0; 594 | } 595 | 596 | if(_symbol == "NULL") { 597 | ticket = OrderSend(Symbol(), _type, _lots, _price, MaximumSlippage, sl, tp, _comment, _magic); 598 | } else { 599 | ticket = OrderSend(_symbol, _type, _lots, _price, MaximumSlippage, sl, tp, _comment, _magic); 600 | } 601 | if(ticket < 0) { 602 | // Failure 603 | error = GetLastError(); 604 | zmq_ret = zmq_ret + ", " + "'_response': '" + IntegerToString(error) + "', 'response_value': '" + ErrorDescription(error) + "'"; 605 | return(-1*error); 606 | } 607 | 608 | int tmpRet = OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES); 609 | 610 | zmq_ret = zmq_ret + ", " + "'_magic': " + IntegerToString(_magic) + ", '_ticket': " + IntegerToString(OrderTicket()) + ", '_open_time': '" + TimeToStr(OrderOpenTime(),TIME_DATE|TIME_SECONDS) + "', '_open_price': " + DoubleToString(OrderOpenPrice()); 611 | 612 | if(DMA_MODE) { 613 | 614 | int retries = 3; 615 | while(true) { 616 | retries--; 617 | if(retries < 0) return(0); 618 | 619 | if((_SL == 0 && _TP == 0) || (OrderStopLoss() == _SL && OrderTakeProfit() == _TP)) { 620 | return(ticket); 621 | } 622 | 623 | if(DWX_IsTradeAllowed(30, zmq_ret) == 1) { 624 | if(DWX_SetSLTP(ticket, _SL, _TP, _magic, _type, _price, _symbol, retries, zmq_ret)) { 625 | return(ticket); 626 | } 627 | if(retries == 0) { 628 | zmq_ret = zmq_ret + ", '_response': 'ERROR_SETTING_SL_TP'"; 629 | return(-11111); 630 | } 631 | } 632 | 633 | Sleep(MILLISECOND_TIMER); 634 | } 635 | 636 | zmq_ret = zmq_ret + ", '_response': 'ERROR_SETTING_SL_TP'"; 637 | zmq_ret = zmq_ret + "}"; 638 | return(-1); 639 | } 640 | 641 | // Send zmq_ret to Python Client 642 | zmq_ret = zmq_ret + "}"; 643 | 644 | return(ticket); 645 | } 646 | 647 | // SET SL/TP 648 | bool DWX_SetSLTP(int ticket, double _SL, double _TP, int _magic, int _type, double _price, string _symbol, int retries, string &zmq_ret) { 649 | 650 | if (OrderSelect(ticket, SELECT_BY_TICKET) == true) 651 | { 652 | int dir_flag = -1; 653 | 654 | if (OrderType() == 0 || OrderType() == 2 || OrderType() == 4) 655 | dir_flag = 1; 656 | 657 | double vpoint = MarketInfo(OrderSymbol(), MODE_POINT); 658 | int vdigits = (int)MarketInfo(OrderSymbol(), MODE_DIGITS); 659 | 660 | if(OrderModify(ticket, OrderOpenPrice(), NormalizeDouble(OrderOpenPrice()-_SL*dir_flag*vpoint,vdigits), NormalizeDouble(OrderOpenPrice()+_TP*dir_flag*vpoint,vdigits), 0, 0)) { 661 | zmq_ret = zmq_ret + ", '_sl': " + DoubleToString(_SL) + ", '_tp': " + DoubleToString(_TP); 662 | return(true); 663 | } else { 664 | int error = GetLastError(); 665 | zmq_ret = zmq_ret + ", '_response': '" + IntegerToString(error) + "', '_response_value': '" + ErrorDescription(error) + "', '_sl_attempted': " + DoubleToString(NormalizeDouble(OrderOpenPrice()-_SL*dir_flag*vpoint,vdigits)) + ", '_tp_attempted': " + DoubleToString(NormalizeDouble(OrderOpenPrice()+_TP*dir_flag*vpoint,vdigits)); 666 | 667 | if(retries == 0) { 668 | RefreshRates(); 669 | DWX_CloseAtMarket(-1, zmq_ret); 670 | // int lastOrderErrorCloseTime = TimeCurrent(); 671 | } 672 | 673 | return(false); 674 | } 675 | } 676 | 677 | // return(true); 678 | return(false); 679 | } 680 | 681 | // CLOSE AT MARKET 682 | bool DWX_CloseAtMarket(double size, string &zmq_ret) { 683 | 684 | int error; 685 | 686 | int retries = 3; 687 | while(true) { 688 | retries--; 689 | if(retries < 0) return(false); 690 | 691 | if(DWX_IsTradeAllowed(30, zmq_ret) == 1) { 692 | if(DWX_ClosePartial(size, zmq_ret)) { 693 | // trade successfuly closed 694 | return(true); 695 | } else { 696 | error = GetLastError(); 697 | zmq_ret = zmq_ret + ", '_response': '" + IntegerToString(error) + "', '_response_value': '" + ErrorDescription(error) + "'"; 698 | } 699 | } 700 | 701 | } 702 | 703 | return(false); 704 | } 705 | 706 | // CLOSE PARTIAL SIZE 707 | bool DWX_ClosePartial(double size, string &zmq_ret, int ticket = 0) { 708 | 709 | RefreshRates(); 710 | double priceCP; 711 | 712 | bool close_ret = False; 713 | 714 | if(OrderType() != OP_BUY && OrderType() != OP_SELL) { 715 | return(true); 716 | } 717 | 718 | if(OrderType() == OP_BUY) { 719 | priceCP = DWX_GetBid(OrderSymbol()); 720 | } else { 721 | priceCP = DWX_GetAsk(OrderSymbol()); 722 | } 723 | 724 | // If the function is called directly, setup init() JSON here. 725 | if(ticket != 0) { 726 | zmq_ret = zmq_ret + "'_action': 'CLOSE', '_ticket': " + IntegerToString(ticket); 727 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_PARTIAL'"; 728 | } 729 | 730 | int local_ticket = 0; 731 | 732 | if (ticket != 0) 733 | local_ticket = ticket; 734 | else 735 | local_ticket = OrderTicket(); 736 | 737 | if(size < 0.01 || size > OrderLots()) { 738 | close_ret = OrderClose(local_ticket, OrderLots(), priceCP, MaximumSlippage); 739 | zmq_ret = zmq_ret + ", '_close_price': " + DoubleToString(priceCP) + ", '_close_lots': " + DoubleToString(OrderLots()); 740 | return(close_ret); 741 | } else { 742 | close_ret = OrderClose(local_ticket, size, priceCP, MaximumSlippage); 743 | zmq_ret = zmq_ret + ", '_close_price': " + DoubleToString(priceCP) + ", '_close_lots': " + DoubleToString(size); 744 | return(close_ret); 745 | } 746 | } 747 | 748 | // CLOSE ORDER (by Magic Number) 749 | void DWX_CloseOrder_Magic(int _magic, string &zmq_ret) { 750 | 751 | bool found = false; 752 | 753 | zmq_ret = zmq_ret + "'_action': 'CLOSE_ALL_MAGIC'"; 754 | zmq_ret = zmq_ret + ", '_magic': " + IntegerToString(_magic); 755 | 756 | zmq_ret = zmq_ret + ", '_responses': {"; 757 | 758 | for(int i=OrdersTotal()-1; i >= 0; i--) { 759 | if (OrderSelect(i,SELECT_BY_POS)==true && OrderMagicNumber() == _magic) { 760 | found = true; 761 | 762 | zmq_ret = zmq_ret + IntegerToString(OrderTicket()) + ": {'_symbol':'" + OrderSymbol() + "'"; 763 | 764 | if(OrderType() == OP_BUY || OrderType() == OP_SELL) { 765 | DWX_CloseAtMarket(-1, zmq_ret); 766 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_MARKET'"; 767 | 768 | if (i != 0) 769 | zmq_ret = zmq_ret + "}, "; 770 | else 771 | zmq_ret = zmq_ret + "}"; 772 | 773 | } else { 774 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_PENDING'"; 775 | 776 | if (i != 0) 777 | zmq_ret = zmq_ret + "}, "; 778 | else 779 | zmq_ret = zmq_ret + "}"; 780 | 781 | int tmpRet = OrderDelete(OrderTicket()); 782 | } 783 | } 784 | } 785 | 786 | zmq_ret = zmq_ret + "}"; 787 | 788 | if(found == false) { 789 | zmq_ret = zmq_ret + ", '_response': 'NOT_FOUND'"; 790 | } 791 | else { 792 | zmq_ret = zmq_ret + ", '_response_value': 'SUCCESS'"; 793 | } 794 | 795 | } 796 | 797 | // CLOSE ORDER (by Ticket) 798 | void DWX_CloseOrder_Ticket(int _ticket, string &zmq_ret) { 799 | 800 | bool found = false; 801 | 802 | zmq_ret = zmq_ret + "'_action': 'CLOSE', '_ticket': " + IntegerToString(_ticket); 803 | 804 | for(int i=0; i= 0; i--) { 837 | if (OrderSelect(i,SELECT_BY_POS)==true) { 838 | 839 | found = true; 840 | 841 | zmq_ret = zmq_ret + IntegerToString(OrderTicket()) + ": {'_symbol':'" + OrderSymbol() + "', '_magic': " + IntegerToString(OrderMagicNumber()); 842 | 843 | if(OrderType() == OP_BUY || OrderType() == OP_SELL) { 844 | DWX_CloseAtMarket(-1, zmq_ret); 845 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_MARKET'"; 846 | 847 | if (i != 0) 848 | zmq_ret = zmq_ret + "}, "; 849 | else 850 | zmq_ret = zmq_ret + "}"; 851 | 852 | } else { 853 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_PENDING'"; 854 | 855 | if (i != 0) 856 | zmq_ret = zmq_ret + "}, "; 857 | else 858 | zmq_ret = zmq_ret + "}"; 859 | 860 | int tmpRet = OrderDelete(OrderTicket()); 861 | } 862 | } 863 | } 864 | 865 | zmq_ret = zmq_ret + "}"; 866 | 867 | if(found == false) { 868 | zmq_ret = zmq_ret + ", '_response': 'NOT_FOUND'"; 869 | } 870 | else { 871 | zmq_ret = zmq_ret + ", '_response_value': 'SUCCESS'"; 872 | } 873 | 874 | } 875 | 876 | // GET OPEN ORDERS 877 | void DWX_GetOpenOrders(string &zmq_ret) { 878 | 879 | bool found = false; 880 | 881 | zmq_ret = zmq_ret + "'_action': 'OPEN_TRADES'"; 882 | zmq_ret = zmq_ret + ", '_trades': {"; 883 | 884 | for(int i=OrdersTotal()-1; i>=0; i--) { 885 | found = true; 886 | 887 | if (OrderSelect(i,SELECT_BY_POS)==true) { 888 | 889 | zmq_ret = zmq_ret + IntegerToString(OrderTicket()) + ": {"; 890 | 891 | zmq_ret = zmq_ret + "'_magic': " + IntegerToString(OrderMagicNumber()) + ", '_symbol': '" + OrderSymbol() + "', '_lots': " + DoubleToString(OrderLots()) + ", '_type': " + IntegerToString(OrderType()) + ", '_open_price': " + DoubleToString(OrderOpenPrice()) + ", '_open_time': '" + TimeToStr(OrderOpenTime(),TIME_DATE|TIME_SECONDS) + "', '_SL': " + DoubleToString(OrderStopLoss()) + ", '_TP': " + DoubleToString(OrderTakeProfit()) + ", '_pnl': " + DoubleToString(OrderProfit()) + ", '_comment': '" + OrderComment() + "'"; 892 | 893 | if (i != 0) 894 | zmq_ret = zmq_ret + "}, "; 895 | else 896 | zmq_ret = zmq_ret + "}"; 897 | } 898 | } 899 | zmq_ret = zmq_ret + "}"; 900 | 901 | } 902 | 903 | // CHECK IF TRADE IS ALLOWED 904 | int DWX_IsTradeAllowed(int MaxWaiting_sec, string &zmq_ret) { 905 | 906 | if(!IsTradeAllowed()) { 907 | 908 | int StartWaitingTime = (int)GetTickCount(); 909 | zmq_ret = zmq_ret + ", " + "'_response': 'TRADE_CONTEXT_BUSY'"; 910 | 911 | while(true) { 912 | 913 | if(IsStopped()) { 914 | zmq_ret = zmq_ret + ", " + "'_response_value': 'EA_STOPPED_BY_USER'"; 915 | return(-1); 916 | } 917 | 918 | int diff = (int)(GetTickCount() - StartWaitingTime); 919 | if(diff > MaxWaiting_sec * 1000) { 920 | zmq_ret = zmq_ret + ", '_response': 'WAIT_LIMIT_EXCEEDED', '_response_value': " + IntegerToString(MaxWaiting_sec); 921 | return(-2); 922 | } 923 | // if the trade context has become free, 924 | if(IsTradeAllowed()) { 925 | zmq_ret = zmq_ret + ", '_response': 'TRADE_CONTEXT_NOW_FREE'"; 926 | RefreshRates(); 927 | return(1); 928 | } 929 | 930 | } 931 | } else { 932 | return(1); 933 | } 934 | 935 | return(1); 936 | } 937 | 938 | string ErrorDescription(int error_code) 939 | { 940 | string error_string; 941 | //---- 942 | switch(error_code) 943 | { 944 | //---- codes returned from trade server 945 | case 0: 946 | case 1: error_string="no error"; break; 947 | case 2: error_string="common error"; break; 948 | case 3: error_string="invalid trade parameters"; break; 949 | case 4: error_string="trade server is busy"; break; 950 | case 5: error_string="old version of the client terminal"; break; 951 | case 6: error_string="no connection with trade server"; break; 952 | case 7: error_string="not enough rights"; break; 953 | case 8: error_string="too frequent requests"; break; 954 | case 9: error_string="malfunctional trade operation (never returned error)"; break; 955 | case 64: error_string="account disabled"; break; 956 | case 65: error_string="invalid account"; break; 957 | case 128: error_string="trade timeout"; break; 958 | case 129: error_string="invalid price"; break; 959 | case 130: error_string="invalid stops"; break; 960 | case 131: error_string="invalid trade volume"; break; 961 | case 132: error_string="market is closed"; break; 962 | case 133: error_string="trade is disabled"; break; 963 | case 134: error_string="not enough money"; break; 964 | case 135: error_string="price changed"; break; 965 | case 136: error_string="off quotes"; break; 966 | case 137: error_string="broker is busy (never returned error)"; break; 967 | case 138: error_string="requote"; break; 968 | case 139: error_string="order is locked"; break; 969 | case 140: error_string="long positions only allowed"; break; 970 | case 141: error_string="too many requests"; break; 971 | case 145: error_string="modification denied because order too close to market"; break; 972 | case 146: error_string="trade context is busy"; break; 973 | case 147: error_string="expirations are denied by broker"; break; 974 | case 148: error_string="amount of open and pending orders has reached the limit"; break; 975 | case 149: error_string="hedging is prohibited"; break; 976 | case 150: error_string="prohibited by FIFO rules"; break; 977 | //---- mql4 errors 978 | case 4000: error_string="no error (never generated code)"; break; 979 | case 4001: error_string="wrong function pointer"; break; 980 | case 4002: error_string="array index is out of range"; break; 981 | case 4003: error_string="no memory for function call stack"; break; 982 | case 4004: error_string="recursive stack overflow"; break; 983 | case 4005: error_string="not enough stack for parameter"; break; 984 | case 4006: error_string="no memory for parameter string"; break; 985 | case 4007: error_string="no memory for temp string"; break; 986 | case 4008: error_string="not initialized string"; break; 987 | case 4009: error_string="not initialized string in array"; break; 988 | case 4010: error_string="no memory for array\' string"; break; 989 | case 4011: error_string="too long string"; break; 990 | case 4012: error_string="remainder from zero divide"; break; 991 | case 4013: error_string="zero divide"; break; 992 | case 4014: error_string="unknown command"; break; 993 | case 4015: error_string="wrong jump (never generated error)"; break; 994 | case 4016: error_string="not initialized array"; break; 995 | case 4017: error_string="dll calls are not allowed"; break; 996 | case 4018: error_string="cannot load library"; break; 997 | case 4019: error_string="cannot call function"; break; 998 | case 4020: error_string="expert function calls are not allowed"; break; 999 | case 4021: error_string="not enough memory for temp string returned from function"; break; 1000 | case 4022: error_string="system is busy (never generated error)"; break; 1001 | case 4050: error_string="invalid function parameters count"; break; 1002 | case 4051: error_string="invalid function parameter value"; break; 1003 | case 4052: error_string="string function internal error"; break; 1004 | case 4053: error_string="some array error"; break; 1005 | case 4054: error_string="incorrect series array using"; break; 1006 | case 4055: error_string="custom indicator error"; break; 1007 | case 4056: error_string="arrays are incompatible"; break; 1008 | case 4057: error_string="global variables processing error"; break; 1009 | case 4058: error_string="global variable not found"; break; 1010 | case 4059: error_string="function is not allowed in testing mode"; break; 1011 | case 4060: error_string="function is not confirmed"; break; 1012 | case 4061: error_string="send mail error"; break; 1013 | case 4062: error_string="string parameter expected"; break; 1014 | case 4063: error_string="integer parameter expected"; break; 1015 | case 4064: error_string="double parameter expected"; break; 1016 | case 4065: error_string="array as parameter expected"; break; 1017 | case 4066: error_string="requested history data in update state"; break; 1018 | case 4073: error_string="No history data"; break; 1019 | case 4099: error_string="end of file"; break; 1020 | case 4100: error_string="some file error"; break; 1021 | case 4101: error_string="wrong file name"; break; 1022 | case 4102: error_string="too many opened files"; break; 1023 | case 4103: error_string="cannot open file"; break; 1024 | case 4104: error_string="incompatible access to a file"; break; 1025 | case 4105: error_string="no order selected"; break; 1026 | case 4106: error_string="unknown symbol"; break; 1027 | case 4107: error_string="invalid price parameter for trade function"; break; 1028 | case 4108: error_string="invalid ticket"; break; 1029 | case 4109: error_string="trade is not allowed in the expert properties"; break; 1030 | case 4110: error_string="longs are not allowed in the expert properties"; break; 1031 | case 4111: error_string="shorts are not allowed in the expert properties"; break; 1032 | case 4200: error_string="object is already exist"; break; 1033 | case 4201: error_string="unknown object property"; break; 1034 | case 4202: error_string="object is not exist"; break; 1035 | case 4203: error_string="unknown object type"; break; 1036 | case 4204: error_string="no object name"; break; 1037 | case 4205: error_string="object coordinates error"; break; 1038 | case 4206: error_string="no specified subwindow"; break; 1039 | default: error_string="unknown error"; 1040 | } 1041 | //---- 1042 | return(error_string); 1043 | } 1044 | 1045 | //+------------------------------------------------------------------+ 1046 | 1047 | double DWX_GetAsk(string symbol) { 1048 | if(symbol == "NULL") { 1049 | return(Ask); 1050 | } else { 1051 | return(MarketInfo(symbol,MODE_ASK)); 1052 | } 1053 | } 1054 | 1055 | //+------------------------------------------------------------------+ 1056 | 1057 | double DWX_GetBid(string symbol) { 1058 | if(symbol == "NULL") { 1059 | return(Bid); 1060 | } else { 1061 | return(MarketInfo(symbol,MODE_BID)); 1062 | } 1063 | } 1064 | //+------------------------------------------------------------------+ 1065 | 1066 | 1067 | 1068 | //+------------------------------------------------------------------+ 1069 | -------------------------------------------------------------------------------- /MQL4 Server Connector/Server Connector Original.mq4: -------------------------------------------------------------------------------- 1 | //+--------------------------------------------------------------+ 2 | //| DWX_ZeroMQ_Server_v2.0.1_RC8.mq4 3 | //| @author: Darwinex Labs (www.darwinex.com) 4 | //| 5 | //| Last Updated: May 16, 2019 6 | //| 7 | //| Copyright (c) 2017-2019, Darwinex. All rights reserved. 8 | //| 9 | //| Licensed under the BSD 3-Clause License, you may not use this file except 10 | //| in compliance with the License. 11 | //| 12 | //| You may obtain a copy of the License at: 13 | //| https://opensource.org/licenses/BSD-3-Clause 14 | //+--------------------------------------------------------------+ 15 | #property copyright "Copyright 2017-2019, Darwinex Labs." 16 | #property link "https://www.darwinex.com/" 17 | #property version "2.0.1" 18 | #property strict 19 | 20 | // Required: MQL-ZMQ from https://github.com/dingmaotu/mql-zmq 21 | 22 | #include 23 | 24 | extern string PROJECT_NAME = "DWX_ZeroMQ_MT4_Server"; 25 | extern string ZEROMQ_PROTOCOL = "tcp"; 26 | extern string HOSTNAME = "*"; 27 | extern int PUSH_PORT = 32768; 28 | extern int PULL_PORT = 32769; 29 | extern int PUB_PORT = 32770; 30 | extern int MILLISECOND_TIMER = 1; 31 | 32 | extern string t0 = "--- Trading Parameters ---"; 33 | extern int MagicNumber = 123456; 34 | extern int MaximumOrders = 1; 35 | extern double MaximumLotSize = 0.01; 36 | extern int MaximumSlippage = 3; 37 | extern bool DMA_MODE = true; 38 | 39 | extern string t1 = "--- ZeroMQ Configuration ---"; 40 | extern bool Publish_MarketData = false; 41 | 42 | string Publish_Symbols[7] = { 43 | "EURUSD","GBPUSD","USDJPY","USDCAD","AUDUSD","NZDUSD","USDCHF" 44 | }; 45 | 46 | /* 47 | string Publish_Symbols[28] = { 48 | "EURUSD","EURGBP","EURAUD","EURNZD","EURJPY","EURCHF","EURCAD", 49 | "GBPUSD","AUDUSD","NZDUSD","USDJPY","USDCHF","USDCAD","GBPAUD", 50 | "GBPNZD","GBPJPY","GBPCHF","GBPCAD","AUDJPY","CHFJPY","CADJPY", 51 | "AUDNZD","AUDCHF","AUDCAD","NZDJPY","NZDCHF","NZDCAD","CADCHF" 52 | }; 53 | */ 54 | 55 | // CREATE ZeroMQ Context 56 | Context context(PROJECT_NAME); 57 | 58 | // CREATE ZMQ_PUSH SOCKET 59 | Socket pushSocket(context, ZMQ_PUSH); 60 | 61 | // CREATE ZMQ_PULL SOCKET 62 | Socket pullSocket(context, ZMQ_PULL); 63 | 64 | // CREATE ZMQ_PUB SOCKET 65 | Socket pubSocket(context, ZMQ_PUB); 66 | 67 | // VARIABLES FOR LATER 68 | uchar _data[]; 69 | ZmqMsg request; 70 | 71 | //+------------------------------------------------------------------+ 72 | //| Expert initialization function | 73 | //+------------------------------------------------------------------+ 74 | int OnInit() 75 | { 76 | //--- 77 | 78 | EventSetMillisecondTimer(MILLISECOND_TIMER); // Set Millisecond Timer to get client socket input 79 | 80 | context.setBlocky(false); 81 | 82 | // Send responses to PULL_PORT that client is listening on. 83 | Print("[PUSH] Binding MT4 Server to Socket on Port " + IntegerToString(PULL_PORT) + ".."); 84 | pushSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PULL_PORT)); 85 | pushSocket.setSendHighWaterMark(1); 86 | pushSocket.setLinger(0); 87 | 88 | // Receive commands from PUSH_PORT that client is sending to. 89 | Print("[PULL] Binding MT4 Server to Socket on Port " + IntegerToString(PUSH_PORT) + ".."); 90 | pullSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUSH_PORT)); 91 | pullSocket.setReceiveHighWaterMark(1); 92 | pullSocket.setLinger(0); 93 | 94 | if (Publish_MarketData == true) 95 | { 96 | // Send new market data to PUB_PORT that client is subscribed to. 97 | Print("[PUB] Binding MT4 Server to Socket on Port " + IntegerToString(PUB_PORT) + ".."); 98 | pubSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUB_PORT)); 99 | } 100 | 101 | //--- 102 | return(INIT_SUCCEEDED); 103 | } 104 | //+------------------------------------------------------------------+ 105 | //| Expert deinitialization function | 106 | //+------------------------------------------------------------------+ 107 | void OnDeinit(const int reason) 108 | { 109 | //--- 110 | 111 | Print("[PUSH] Unbinding MT4 Server from Socket on Port " + IntegerToString(PULL_PORT) + ".."); 112 | pushSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PULL_PORT)); 113 | 114 | Print("[PULL] Unbinding MT4 Server from Socket on Port " + IntegerToString(PUSH_PORT) + ".."); 115 | pullSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUSH_PORT)); 116 | 117 | if (Publish_MarketData == TRUE) 118 | { 119 | Print("[PUB] Unbinding MT4 Server from Socket on Port " + IntegerToString(PUB_PORT) + ".."); 120 | pubSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUB_PORT)); 121 | } 122 | 123 | // Shutdown ZeroMQ Context 124 | context.shutdown(); 125 | context.destroy(0); 126 | 127 | EventKillTimer(); 128 | } 129 | 130 | //+------------------------------------------------------------------+ 131 | //| Expert tick function | 132 | //+------------------------------------------------------------------+ 133 | void OnTick() 134 | { 135 | /* 136 | Use this OnTick() function to send market data to subscribed client. 137 | */ 138 | if(!IsStopped() && Publish_MarketData == true) 139 | { 140 | for(int s = 0; s < ArraySize(Publish_Symbols); s++) 141 | { 142 | // Python clients can subscribe to a price feed by setting 143 | // socket options to the symbol name. For example: 144 | 145 | string _tick = GetBidAsk(Publish_Symbols[s]); 146 | Print("Sending " + Publish_Symbols[s] + " " + _tick + " to PUB Socket"); 147 | ZmqMsg reply(StringFormat("%s %s", Publish_Symbols[s], _tick)); 148 | pubSocket.send(reply, true); 149 | } 150 | } 151 | } 152 | 153 | //+------------------------------------------------------------------+ 154 | //| Expert timer function | 155 | //+------------------------------------------------------------------+ 156 | void OnTimer() 157 | { 158 | //--- 159 | 160 | /* 161 | Use this OnTimer() function to get and respond to commands 162 | */ 163 | 164 | // Get client's response, but don't block. 165 | pullSocket.recv(request, true); 166 | 167 | if (request.size() > 0) 168 | { 169 | // Wait 170 | // pullSocket.recv(request,false); 171 | 172 | // MessageHandler() should go here. 173 | ZmqMsg reply = MessageHandler(request); 174 | 175 | // Send response, and block 176 | // pushSocket.send(reply); 177 | 178 | // Send response, but don't block 179 | pushSocket.send(reply, true); 180 | } 181 | } 182 | //+------------------------------------------------------------------+ 183 | 184 | ZmqMsg MessageHandler(ZmqMsg &_request) { 185 | 186 | // Output object 187 | ZmqMsg reply; 188 | 189 | // Message components for later. 190 | string components[11]; 191 | 192 | if(_request.size() > 0) { 193 | 194 | // Get data from request 195 | ArrayResize(_data, _request.size()); 196 | _request.getData(_data); 197 | string dataStr = CharArrayToString(_data); 198 | 199 | // Process data 200 | ParseZmqMessage(dataStr, components); 201 | 202 | // Interpret data 203 | InterpretZmqMessage(&pushSocket, components); 204 | 205 | } 206 | else { 207 | // NO DATA RECEIVED 208 | } 209 | 210 | return(reply); 211 | } 212 | 213 | // Interpret Zmq Message and perform actions 214 | void InterpretZmqMessage(Socket &pSocket, string &compArray[]) { 215 | 216 | // Print("ZMQ: Interpreting Message.."); 217 | 218 | // Message Structures: 219 | 220 | // 1) Trading 221 | // TRADE|ACTION|TYPE|SYMBOL|PRICE|SL|TP|COMMENT|TICKET 222 | // e.g. TRADE|OPEN|1|EURUSD|0|50|50|R-to-MetaTrader4|12345678 223 | 224 | // The 12345678 at the end is the ticket ID, for MODIFY and CLOSE. 225 | 226 | // 2) Data Requests 227 | 228 | // 2.1) RATES|SYMBOL -> Returns Current Bid/Ask 229 | 230 | // 2.2) DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME 231 | 232 | // NOTE: datetime has format: D'2015.01.01 00:00' 233 | 234 | /* 235 | compArray[0] = TRADE or RATES 236 | If RATES -> compArray[1] = Symbol 237 | 238 | If TRADE -> 239 | compArray[0] = TRADE 240 | compArray[1] = ACTION (e.g. OPEN, MODIFY, CLOSE) 241 | compArray[2] = TYPE (e.g. OP_BUY, OP_SELL, etc - only used when ACTION=OPEN) 242 | 243 | // ORDER TYPES: 244 | // https://docs.mql4.com/constants/tradingconstants/orderproperties 245 | 246 | // OP_BUY = 0 247 | // OP_SELL = 1 248 | // OP_BUYLIMIT = 2 249 | // OP_SELLLIMIT = 3 250 | // OP_BUYSTOP = 4 251 | // OP_SELLSTOP = 5 252 | 253 | compArray[3] = Symbol (e.g. EURUSD, etc.) 254 | compArray[4] = Open/Close Price (ignored if ACTION = MODIFY) 255 | compArray[5] = SL 256 | compArray[6] = TP 257 | compArray[7] = Trade Comment 258 | compArray[8] = Lots 259 | compArray[9] = Magic Number 260 | compArray[10] = Ticket Number (MODIFY/CLOSE) 261 | */ 262 | 263 | int switch_action = 0; 264 | 265 | if(compArray[0] == "TRADE" && compArray[1] == "OPEN") 266 | switch_action = 1; 267 | if(compArray[0] == "TRADE" && compArray[1] == "MODIFY") 268 | switch_action = 2; 269 | if(compArray[0] == "TRADE" && compArray[1] == "CLOSE") 270 | switch_action = 3; 271 | if(compArray[0] == "TRADE" && compArray[1] == "CLOSE_PARTIAL") 272 | switch_action = 4; 273 | if(compArray[0] == "TRADE" && compArray[1] == "CLOSE_MAGIC") 274 | switch_action = 5; 275 | if(compArray[0] == "TRADE" && compArray[1] == "CLOSE_ALL") 276 | switch_action = 6; 277 | if(compArray[0] == "TRADE" && compArray[1] == "GET_OPEN_TRADES") 278 | switch_action = 7; 279 | if(compArray[0] == "DATA") 280 | switch_action = 8; 281 | 282 | string zmq_ret = ""; 283 | string ret = ""; 284 | int ticket = -1; 285 | bool ans = FALSE; 286 | 287 | switch(switch_action) 288 | { 289 | case 1: // OPEN TRADE 290 | 291 | zmq_ret = "{"; 292 | 293 | // Function definition: 294 | ticket = DWX_OpenOrder(compArray[3], StrToInteger(compArray[2]), StrToDouble(compArray[8]), 295 | StrToDouble(compArray[4]), StrToInteger(compArray[5]), StrToInteger(compArray[6]), 296 | compArray[7], StrToInteger(compArray[9]), zmq_ret); 297 | 298 | // Send TICKET back as JSON 299 | InformPullClient(pSocket, zmq_ret + "}"); 300 | 301 | break; 302 | 303 | case 2: // MODIFY SL/TP 304 | 305 | zmq_ret = "{'_action': 'MODIFY'"; 306 | 307 | // Function definition: 308 | ans = DWX_SetSLTP(StrToInteger(compArray[10]), StrToDouble(compArray[5]), StrToDouble(compArray[6]), 309 | StrToInteger(compArray[9]), StrToInteger(compArray[2]), StrToDouble(compArray[4]), 310 | compArray[3], 3, zmq_ret); 311 | 312 | InformPullClient(pSocket, zmq_ret + "}"); 313 | 314 | break; 315 | 316 | case 3: // CLOSE TRADE 317 | 318 | zmq_ret = "{"; 319 | 320 | // IMPLEMENT CLOSE TRADE LOGIC HERE 321 | DWX_CloseOrder_Ticket(StrToInteger(compArray[10]), zmq_ret); 322 | 323 | InformPullClient(pSocket, zmq_ret + "}"); 324 | 325 | break; 326 | 327 | case 4: // CLOSE PARTIAL 328 | 329 | zmq_ret = "{"; 330 | 331 | ans = DWX_ClosePartial(StrToDouble(compArray[8]), zmq_ret, StrToInteger(compArray[10])); 332 | 333 | InformPullClient(pSocket, zmq_ret + "}"); 334 | 335 | break; 336 | 337 | case 5: // CLOSE MAGIC 338 | 339 | zmq_ret = "{"; 340 | 341 | DWX_CloseOrder_Magic(StrToInteger(compArray[9]), zmq_ret); 342 | 343 | InformPullClient(pSocket, zmq_ret + "}"); 344 | 345 | break; 346 | 347 | case 6: // CLOSE ALL ORDERS 348 | 349 | zmq_ret = "{"; 350 | 351 | DWX_CloseAllOrders(zmq_ret); 352 | 353 | InformPullClient(pSocket, zmq_ret + "}"); 354 | 355 | break; 356 | 357 | case 7: // GET OPEN ORDERS 358 | 359 | zmq_ret = "{"; 360 | 361 | DWX_GetOpenOrders(zmq_ret); 362 | 363 | InformPullClient(pSocket, zmq_ret + "}"); 364 | 365 | break; 366 | 367 | case 8: // DATA REQUEST 368 | 369 | zmq_ret = "{"; 370 | 371 | DWX_GetData(compArray, zmq_ret); 372 | 373 | InformPullClient(pSocket, zmq_ret + "}"); 374 | 375 | break; 376 | 377 | default: 378 | break; 379 | } 380 | } 381 | 382 | // Parse Zmq Message 383 | void ParseZmqMessage(string& message, string& retArray[]) { 384 | 385 | //Print("Parsing: " + message); 386 | 387 | string sep = ";"; 388 | ushort u_sep = StringGetCharacter(sep,0); 389 | 390 | int splits = StringSplit(message, u_sep, retArray); 391 | 392 | /* 393 | for(int i = 0; i < splits; i++) { 394 | Print(IntegerToString(i) + ") " + retArray[i]); 395 | } 396 | */ 397 | } 398 | 399 | //+------------------------------------------------------------------+ 400 | // Generate string for Bid/Ask by symbol 401 | string GetBidAsk(string symbol) { 402 | 403 | MqlTick last_tick; 404 | 405 | if(SymbolInfoTick(symbol,last_tick)) 406 | { 407 | return(StringFormat("%f;%f", last_tick.bid, last_tick.ask)); 408 | } 409 | 410 | // Default 411 | return ""; 412 | } 413 | 414 | // Get data for request datetime range 415 | void DWX_GetData(string& compArray[], string& zmq_ret) { 416 | 417 | // Format: DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME 418 | 419 | double price_array[]; 420 | datetime time_array[]; 421 | 422 | // Get prices 423 | int price_count = CopyClose(compArray[1], 424 | StrToInteger(compArray[2]), StrToTime(compArray[3]), 425 | StrToTime(compArray[4]), price_array); 426 | 427 | // Get timestamps 428 | int time_count = CopyTime(compArray[1], 429 | StrToInteger(compArray[2]), StrToTime(compArray[3]), 430 | StrToTime(compArray[4]), time_array); 431 | 432 | zmq_ret = zmq_ret + "'_action': 'DATA'"; 433 | 434 | if (price_count > 0) { 435 | 436 | zmq_ret = zmq_ret + ", '_data': {"; 437 | 438 | // Construct string of price|price|price|.. etc and send to PULL client. 439 | for(int i = 0; i < price_count; i++ ) { 440 | 441 | if(i == 0) 442 | zmq_ret = zmq_ret + "'" + TimeToString(time_array[i]) + "': " + DoubleToString(price_array[i]); 443 | else 444 | zmq_ret = zmq_ret + ", '" + TimeToString(time_array[i]) + "': " + DoubleToString(price_array[i]); 445 | 446 | } 447 | 448 | zmq_ret = zmq_ret + "}"; 449 | 450 | } 451 | else { 452 | zmq_ret = zmq_ret + ", " + "'_response': 'NOT_AVAILABLE'"; 453 | } 454 | 455 | } 456 | 457 | // Inform Client 458 | void InformPullClient(Socket& pSocket, string message) { 459 | 460 | ZmqMsg pushReply(StringFormat("%s", message)); 461 | 462 | pSocket.send(pushReply,true); // NON-BLOCKING 463 | 464 | } 465 | 466 | /* 467 | ############################################################################ 468 | ############################################################################ 469 | ############################################################################ 470 | */ 471 | 472 | // OPEN NEW ORDER 473 | int DWX_OpenOrder(string _symbol, int _type, double _lots, double _price, double _SL, double _TP, string _comment, int _magic, string &zmq_ret) { 474 | 475 | int ticket, error; 476 | 477 | zmq_ret = zmq_ret + "'_action': 'EXECUTION'"; 478 | 479 | if(_lots > MaximumLotSize) { 480 | zmq_ret = zmq_ret + ", " + "'_response': 'LOT_SIZE_ERROR', 'response_value': 'MAX_LOT_SIZE_EXCEEDED'"; 481 | return(-1); 482 | } 483 | 484 | double sl = _SL; 485 | double tp = _TP; 486 | 487 | // Else 488 | if(DMA_MODE) { 489 | sl = 0.0; 490 | tp = 0.0; 491 | } 492 | 493 | if(_symbol == "NULL") { 494 | ticket = OrderSend(Symbol(), _type, _lots, _price, MaximumSlippage, sl, tp, _comment, _magic); 495 | } else { 496 | ticket = OrderSend(_symbol, _type, _lots, _price, MaximumSlippage, sl, tp, _comment, _magic); 497 | } 498 | if(ticket < 0) { 499 | // Failure 500 | error = GetLastError(); 501 | zmq_ret = zmq_ret + ", " + "'_response': '" + IntegerToString(error) + "', 'response_value': '" + ErrorDescription(error) + "'"; 502 | return(-1*error); 503 | } 504 | 505 | int tmpRet = OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES); 506 | 507 | zmq_ret = zmq_ret + ", " + "'_magic': " + IntegerToString(_magic) + ", '_ticket': " + IntegerToString(OrderTicket()) + ", '_open_time': '" + TimeToStr(OrderOpenTime(),TIME_DATE|TIME_SECONDS) + "', '_open_price': " + DoubleToString(OrderOpenPrice()); 508 | 509 | if(DMA_MODE) { 510 | 511 | int retries = 3; 512 | while(true) { 513 | retries--; 514 | if(retries < 0) return(0); 515 | 516 | if((_SL == 0 && _TP == 0) || (OrderStopLoss() == _SL && OrderTakeProfit() == _TP)) { 517 | return(ticket); 518 | } 519 | 520 | if(DWX_IsTradeAllowed(30, zmq_ret) == 1) { 521 | if(DWX_SetSLTP(ticket, _SL, _TP, _magic, _type, _price, _symbol, retries, zmq_ret)) { 522 | return(ticket); 523 | } 524 | if(retries == 0) { 525 | zmq_ret = zmq_ret + ", '_response': 'ERROR_SETTING_SL_TP'"; 526 | return(-11111); 527 | } 528 | } 529 | 530 | Sleep(MILLISECOND_TIMER); 531 | } 532 | 533 | zmq_ret = zmq_ret + ", '_response': 'ERROR_SETTING_SL_TP'"; 534 | zmq_ret = zmq_ret + "}"; 535 | return(-1); 536 | } 537 | 538 | // Send zmq_ret to Python Client 539 | zmq_ret = zmq_ret + "}"; 540 | 541 | return(ticket); 542 | } 543 | 544 | // SET SL/TP 545 | bool DWX_SetSLTP(int ticket, double _SL, double _TP, int _magic, int _type, double _price, string _symbol, int retries, string &zmq_ret) { 546 | 547 | if (OrderSelect(ticket, SELECT_BY_TICKET) == true) 548 | { 549 | int dir_flag = -1; 550 | 551 | if (OrderType() == 0 || OrderType() == 2 || OrderType() == 4) 552 | dir_flag = 1; 553 | 554 | double vpoint = MarketInfo(OrderSymbol(), MODE_POINT); 555 | int vdigits = (int)MarketInfo(OrderSymbol(), MODE_DIGITS); 556 | 557 | if(OrderModify(ticket, OrderOpenPrice(), NormalizeDouble(OrderOpenPrice()-_SL*dir_flag*vpoint,vdigits), NormalizeDouble(OrderOpenPrice()+_TP*dir_flag*vpoint,vdigits), 0, 0)) { 558 | zmq_ret = zmq_ret + ", '_sl': " + DoubleToString(_SL) + ", '_tp': " + DoubleToString(_TP); 559 | return(true); 560 | } else { 561 | int error = GetLastError(); 562 | zmq_ret = zmq_ret + ", '_response': '" + IntegerToString(error) + "', '_response_value': '" + ErrorDescription(error) + "', '_sl_attempted': " + DoubleToString(NormalizeDouble(OrderOpenPrice()-_SL*dir_flag*vpoint,vdigits)) + ", '_tp_attempted': " + DoubleToString(NormalizeDouble(OrderOpenPrice()+_TP*dir_flag*vpoint,vdigits)); 563 | 564 | if(retries == 0) { 565 | RefreshRates(); 566 | DWX_CloseAtMarket(-1, zmq_ret); 567 | // int lastOrderErrorCloseTime = TimeCurrent(); 568 | } 569 | 570 | return(false); 571 | } 572 | } 573 | 574 | // return(true); 575 | return(false); 576 | } 577 | 578 | // CLOSE AT MARKET 579 | bool DWX_CloseAtMarket(double size, string &zmq_ret) { 580 | 581 | int error; 582 | 583 | int retries = 3; 584 | while(true) { 585 | retries--; 586 | if(retries < 0) return(false); 587 | 588 | if(DWX_IsTradeAllowed(30, zmq_ret) == 1) { 589 | if(DWX_ClosePartial(size, zmq_ret)) { 590 | // trade successfuly closed 591 | return(true); 592 | } else { 593 | error = GetLastError(); 594 | zmq_ret = zmq_ret + ", '_response': '" + IntegerToString(error) + "', '_response_value': '" + ErrorDescription(error) + "'"; 595 | } 596 | } 597 | 598 | } 599 | 600 | return(false); 601 | } 602 | 603 | // CLOSE PARTIAL SIZE 604 | bool DWX_ClosePartial(double size, string &zmq_ret, int ticket = 0) { 605 | 606 | RefreshRates(); 607 | double priceCP; 608 | 609 | bool close_ret = False; 610 | 611 | if(OrderType() != OP_BUY && OrderType() != OP_SELL) { 612 | return(true); 613 | } 614 | 615 | if(OrderType() == OP_BUY) { 616 | priceCP = DWX_GetBid(OrderSymbol()); 617 | } else { 618 | priceCP = DWX_GetAsk(OrderSymbol()); 619 | } 620 | 621 | // If the function is called directly, setup init() JSON here. 622 | if(ticket != 0) { 623 | zmq_ret = zmq_ret + "'_action': 'CLOSE', '_ticket': " + IntegerToString(ticket); 624 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_PARTIAL'"; 625 | } 626 | 627 | int local_ticket = 0; 628 | 629 | if (ticket != 0) 630 | local_ticket = ticket; 631 | else 632 | local_ticket = OrderTicket(); 633 | 634 | if(size < 0.01 || size > OrderLots()) { 635 | close_ret = OrderClose(local_ticket, OrderLots(), priceCP, MaximumSlippage); 636 | zmq_ret = zmq_ret + ", '_close_price': " + DoubleToString(priceCP) + ", '_close_lots': " + DoubleToString(OrderLots()); 637 | return(close_ret); 638 | } else { 639 | close_ret = OrderClose(local_ticket, size, priceCP, MaximumSlippage); 640 | zmq_ret = zmq_ret + ", '_close_price': " + DoubleToString(priceCP) + ", '_close_lots': " + DoubleToString(size); 641 | return(close_ret); 642 | } 643 | } 644 | 645 | // CLOSE ORDER (by Magic Number) 646 | void DWX_CloseOrder_Magic(int _magic, string &zmq_ret) { 647 | 648 | bool found = false; 649 | 650 | zmq_ret = zmq_ret + "'_action': 'CLOSE_ALL_MAGIC'"; 651 | zmq_ret = zmq_ret + ", '_magic': " + IntegerToString(_magic); 652 | 653 | zmq_ret = zmq_ret + ", '_responses': {"; 654 | 655 | for(int i=OrdersTotal()-1; i >= 0; i--) { 656 | if (OrderSelect(i,SELECT_BY_POS)==true && OrderMagicNumber() == _magic) { 657 | found = true; 658 | 659 | zmq_ret = zmq_ret + IntegerToString(OrderTicket()) + ": {'_symbol':'" + OrderSymbol() + "'"; 660 | 661 | if(OrderType() == OP_BUY || OrderType() == OP_SELL) { 662 | DWX_CloseAtMarket(-1, zmq_ret); 663 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_MARKET'"; 664 | 665 | if (i != 0) 666 | zmq_ret = zmq_ret + "}, "; 667 | else 668 | zmq_ret = zmq_ret + "}"; 669 | 670 | } else { 671 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_PENDING'"; 672 | 673 | if (i != 0) 674 | zmq_ret = zmq_ret + "}, "; 675 | else 676 | zmq_ret = zmq_ret + "}"; 677 | 678 | int tmpRet = OrderDelete(OrderTicket()); 679 | } 680 | } 681 | } 682 | 683 | zmq_ret = zmq_ret + "}"; 684 | 685 | if(found == false) { 686 | zmq_ret = zmq_ret + ", '_response': 'NOT_FOUND'"; 687 | } 688 | else { 689 | zmq_ret = zmq_ret + ", '_response_value': 'SUCCESS'"; 690 | } 691 | 692 | } 693 | 694 | // CLOSE ORDER (by Ticket) 695 | void DWX_CloseOrder_Ticket(int _ticket, string &zmq_ret) { 696 | 697 | bool found = false; 698 | 699 | zmq_ret = zmq_ret + "'_action': 'CLOSE', '_ticket': " + IntegerToString(_ticket); 700 | 701 | for(int i=0; i= 0; i--) { 734 | if (OrderSelect(i,SELECT_BY_POS)==true) { 735 | 736 | found = true; 737 | 738 | zmq_ret = zmq_ret + IntegerToString(OrderTicket()) + ": {'_symbol':'" + OrderSymbol() + "', '_magic': " + IntegerToString(OrderMagicNumber()); 739 | 740 | if(OrderType() == OP_BUY || OrderType() == OP_SELL) { 741 | DWX_CloseAtMarket(-1, zmq_ret); 742 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_MARKET'"; 743 | 744 | if (i != 0) 745 | zmq_ret = zmq_ret + "}, "; 746 | else 747 | zmq_ret = zmq_ret + "}"; 748 | 749 | } else { 750 | zmq_ret = zmq_ret + ", '_response': 'CLOSE_PENDING'"; 751 | 752 | if (i != 0) 753 | zmq_ret = zmq_ret + "}, "; 754 | else 755 | zmq_ret = zmq_ret + "}"; 756 | 757 | int tmpRet = OrderDelete(OrderTicket()); 758 | } 759 | } 760 | } 761 | 762 | zmq_ret = zmq_ret + "}"; 763 | 764 | if(found == false) { 765 | zmq_ret = zmq_ret + ", '_response': 'NOT_FOUND'"; 766 | } 767 | else { 768 | zmq_ret = zmq_ret + ", '_response_value': 'SUCCESS'"; 769 | } 770 | 771 | } 772 | 773 | // GET OPEN ORDERS 774 | void DWX_GetOpenOrders(string &zmq_ret) { 775 | 776 | bool found = false; 777 | 778 | zmq_ret = zmq_ret + "'_action': 'OPEN_TRADES'"; 779 | zmq_ret = zmq_ret + ", '_trades': {"; 780 | 781 | for(int i=OrdersTotal()-1; i>=0; i--) { 782 | found = true; 783 | 784 | if (OrderSelect(i,SELECT_BY_POS)==true) { 785 | 786 | zmq_ret = zmq_ret + IntegerToString(OrderTicket()) + ": {"; 787 | 788 | zmq_ret = zmq_ret + "'_magic': " + IntegerToString(OrderMagicNumber()) + ", '_symbol': '" + OrderSymbol() + "', '_lots': " + DoubleToString(OrderLots()) + ", '_type': " + IntegerToString(OrderType()) + ", '_open_price': " + DoubleToString(OrderOpenPrice()) + ", '_open_time': '" + TimeToStr(OrderOpenTime(),TIME_DATE|TIME_SECONDS) + "', '_SL': " + DoubleToString(OrderStopLoss()) + ", '_TP': " + DoubleToString(OrderTakeProfit()) + ", '_pnl': " + DoubleToString(OrderProfit()) + ", '_comment': '" + OrderComment() + "'"; 789 | 790 | if (i != 0) 791 | zmq_ret = zmq_ret + "}, "; 792 | else 793 | zmq_ret = zmq_ret + "}"; 794 | } 795 | } 796 | zmq_ret = zmq_ret + "}"; 797 | 798 | } 799 | 800 | // CHECK IF TRADE IS ALLOWED 801 | int DWX_IsTradeAllowed(int MaxWaiting_sec, string &zmq_ret) { 802 | 803 | if(!IsTradeAllowed()) { 804 | 805 | int StartWaitingTime = (int)GetTickCount(); 806 | zmq_ret = zmq_ret + ", " + "'_response': 'TRADE_CONTEXT_BUSY'"; 807 | 808 | while(true) { 809 | 810 | if(IsStopped()) { 811 | zmq_ret = zmq_ret + ", " + "'_response_value': 'EA_STOPPED_BY_USER'"; 812 | return(-1); 813 | } 814 | 815 | int diff = (int)(GetTickCount() - StartWaitingTime); 816 | if(diff > MaxWaiting_sec * 1000) { 817 | zmq_ret = zmq_ret + ", '_response': 'WAIT_LIMIT_EXCEEDED', '_response_value': " + IntegerToString(MaxWaiting_sec); 818 | return(-2); 819 | } 820 | // if the trade context has become free, 821 | if(IsTradeAllowed()) { 822 | zmq_ret = zmq_ret + ", '_response': 'TRADE_CONTEXT_NOW_FREE'"; 823 | RefreshRates(); 824 | return(1); 825 | } 826 | 827 | } 828 | } else { 829 | return(1); 830 | } 831 | 832 | return(1); 833 | } 834 | 835 | string ErrorDescription(int error_code) 836 | { 837 | string error_string; 838 | //---- 839 | switch(error_code) 840 | { 841 | //---- codes returned from trade server 842 | case 0: 843 | case 1: error_string="no error"; break; 844 | case 2: error_string="common error"; break; 845 | case 3: error_string="invalid trade parameters"; break; 846 | case 4: error_string="trade server is busy"; break; 847 | case 5: error_string="old version of the client terminal"; break; 848 | case 6: error_string="no connection with trade server"; break; 849 | case 7: error_string="not enough rights"; break; 850 | case 8: error_string="too frequent requests"; break; 851 | case 9: error_string="malfunctional trade operation (never returned error)"; break; 852 | case 64: error_string="account disabled"; break; 853 | case 65: error_string="invalid account"; break; 854 | case 128: error_string="trade timeout"; break; 855 | case 129: error_string="invalid price"; break; 856 | case 130: error_string="invalid stops"; break; 857 | case 131: error_string="invalid trade volume"; break; 858 | case 132: error_string="market is closed"; break; 859 | case 133: error_string="trade is disabled"; break; 860 | case 134: error_string="not enough money"; break; 861 | case 135: error_string="price changed"; break; 862 | case 136: error_string="off quotes"; break; 863 | case 137: error_string="broker is busy (never returned error)"; break; 864 | case 138: error_string="requote"; break; 865 | case 139: error_string="order is locked"; break; 866 | case 140: error_string="long positions only allowed"; break; 867 | case 141: error_string="too many requests"; break; 868 | case 145: error_string="modification denied because order too close to market"; break; 869 | case 146: error_string="trade context is busy"; break; 870 | case 147: error_string="expirations are denied by broker"; break; 871 | case 148: error_string="amount of open and pending orders has reached the limit"; break; 872 | case 149: error_string="hedging is prohibited"; break; 873 | case 150: error_string="prohibited by FIFO rules"; break; 874 | //---- mql4 errors 875 | case 4000: error_string="no error (never generated code)"; break; 876 | case 4001: error_string="wrong function pointer"; break; 877 | case 4002: error_string="array index is out of range"; break; 878 | case 4003: error_string="no memory for function call stack"; break; 879 | case 4004: error_string="recursive stack overflow"; break; 880 | case 4005: error_string="not enough stack for parameter"; break; 881 | case 4006: error_string="no memory for parameter string"; break; 882 | case 4007: error_string="no memory for temp string"; break; 883 | case 4008: error_string="not initialized string"; break; 884 | case 4009: error_string="not initialized string in array"; break; 885 | case 4010: error_string="no memory for array\' string"; break; 886 | case 4011: error_string="too long string"; break; 887 | case 4012: error_string="remainder from zero divide"; break; 888 | case 4013: error_string="zero divide"; break; 889 | case 4014: error_string="unknown command"; break; 890 | case 4015: error_string="wrong jump (never generated error)"; break; 891 | case 4016: error_string="not initialized array"; break; 892 | case 4017: error_string="dll calls are not allowed"; break; 893 | case 4018: error_string="cannot load library"; break; 894 | case 4019: error_string="cannot call function"; break; 895 | case 4020: error_string="expert function calls are not allowed"; break; 896 | case 4021: error_string="not enough memory for temp string returned from function"; break; 897 | case 4022: error_string="system is busy (never generated error)"; break; 898 | case 4050: error_string="invalid function parameters count"; break; 899 | case 4051: error_string="invalid function parameter value"; break; 900 | case 4052: error_string="string function internal error"; break; 901 | case 4053: error_string="some array error"; break; 902 | case 4054: error_string="incorrect series array using"; break; 903 | case 4055: error_string="custom indicator error"; break; 904 | case 4056: error_string="arrays are incompatible"; break; 905 | case 4057: error_string="global variables processing error"; break; 906 | case 4058: error_string="global variable not found"; break; 907 | case 4059: error_string="function is not allowed in testing mode"; break; 908 | case 4060: error_string="function is not confirmed"; break; 909 | case 4061: error_string="send mail error"; break; 910 | case 4062: error_string="string parameter expected"; break; 911 | case 4063: error_string="integer parameter expected"; break; 912 | case 4064: error_string="double parameter expected"; break; 913 | case 4065: error_string="array as parameter expected"; break; 914 | case 4066: error_string="requested history data in update state"; break; 915 | case 4099: error_string="end of file"; break; 916 | case 4100: error_string="some file error"; break; 917 | case 4101: error_string="wrong file name"; break; 918 | case 4102: error_string="too many opened files"; break; 919 | case 4103: error_string="cannot open file"; break; 920 | case 4104: error_string="incompatible access to a file"; break; 921 | case 4105: error_string="no order selected"; break; 922 | case 4106: error_string="unknown symbol"; break; 923 | case 4107: error_string="invalid price parameter for trade function"; break; 924 | case 4108: error_string="invalid ticket"; break; 925 | case 4109: error_string="trade is not allowed in the expert properties"; break; 926 | case 4110: error_string="longs are not allowed in the expert properties"; break; 927 | case 4111: error_string="shorts are not allowed in the expert properties"; break; 928 | case 4200: error_string="object is already exist"; break; 929 | case 4201: error_string="unknown object property"; break; 930 | case 4202: error_string="object is not exist"; break; 931 | case 4203: error_string="unknown object type"; break; 932 | case 4204: error_string="no object name"; break; 933 | case 4205: error_string="object coordinates error"; break; 934 | case 4206: error_string="no specified subwindow"; break; 935 | default: error_string="unknown error"; 936 | } 937 | //---- 938 | return(error_string); 939 | } 940 | 941 | //+------------------------------------------------------------------+ 942 | 943 | double DWX_GetAsk(string symbol) { 944 | if(symbol == "NULL") { 945 | return(Ask); 946 | } else { 947 | return(MarketInfo(symbol,MODE_ASK)); 948 | } 949 | } 950 | 951 | //+------------------------------------------------------------------+ 952 | 953 | double DWX_GetBid(string symbol) { 954 | if(symbol == "NULL") { 955 | return(Bid); 956 | } else { 957 | return(MarketInfo(symbol,MODE_BID)); 958 | } 959 | } 960 | //+------------------------------------------------------------------+ 961 | 962 | 963 | 964 | //+------------------------------------------------------------------+ 965 | -------------------------------------------------------------------------------- /MQL5 Server Connector/MQL Server ZeroMQ Connector.mq5: -------------------------------------------------------------------------------- 1 | //+------------------------------------------------------------------+ 2 | //| Copyright 2019, Umais Zahid | 3 | //| umais.me | 4 | //+------------------------------------------------------------------+ 5 | #property copyright "Copyright 2019, Umais Zahid" 6 | #property version "0.0.1" 7 | //+------------------------------------------------------------------+ 8 | //| Include | 9 | //+------------------------------------------------------------------+ 10 | #include 11 | //+------------------------------------------------------------------+ 12 | //| Properties | 13 | //+------------------------------------------------------------------+ 14 | input string t1 = "--- ZeroMQ Parameters ---"; 15 | input string PROJECT_NAME = "AutoTrader"; 16 | input string ZEROMQ_PROTOCOL = "tcp"; 17 | input string HOSTNAME = "*"; 18 | input int PUSH_PORT = 32768; 19 | input int PULL_PORT = 32769; 20 | input int PUB_PORT = 32770; 21 | input int MILLISECOND_TIMER = 1; 22 | input int SUB_TICK_WINDOW = 30; 23 | input int HIGH_WATER_MARK = 1; 24 | input bool PUBLISH_MARKET_DATA = true; 25 | input int NUM_OF_TICKS = 10; 26 | 27 | input string t2 = "--- Trading Parameters ---"; 28 | input float MINIMUM_LOT = 0.01; 29 | input double MAXIMUM_LOT = 0.02; 30 | input bool DMA_MODE = true; 31 | input int MAX_SLIPPAGE = 3; 32 | 33 | // String array of symbols being published 34 | string Publish_Symbols[1] = { 35 | "GBPUSD" 36 | }; 37 | 38 | // Integer storing current messages ID 39 | int MessageID; 40 | 41 | // Integer storing previous tick's time value and current tick's time value 42 | ulong previousTickTime; 43 | ulong currentTickTime; 44 | 45 | // Latest MQLTick 46 | MqlTick latestMQLTick; 47 | 48 | // CREATE ZeroMQ Context 49 | Context context(PROJECT_NAME); 50 | 51 | // CREATE ZMQ_PUSH SOCKET 52 | Socket pushSocket(context, ZMQ_PUSH); 53 | 54 | // CREATE ZMQ_PULL SOCKET 55 | Socket pullSocket(context, ZMQ_PULL); 56 | 57 | // CREATE ZMQ_PUB SOCKET 58 | Socket pubSocket(context, ZMQ_PUB); 59 | 60 | //Global Variables 61 | ZmqMsg request; 62 | 63 | int OnInit() 64 | { 65 | //--- 66 | previousTickTime = TimeCurrent(); 67 | currentTickTime = TimeCurrent(); 68 | EventSetMillisecondTimer(MILLISECOND_TIMER); // Set Millisecond Timer to get client socket input 69 | 70 | context.setBlocky(false); // Kinda useless since it sets linger time to 0 for all sockets but we've done that already explicitly 71 | 72 | // Send responses to PULL_PORT that client is listening on. 73 | Print("[PUSH] Binding MT4 Server to Socket on Port " + IntegerToString(PULL_PORT) + ".."); 74 | pushSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PULL_PORT)); 75 | pushSocket.setSendHighWaterMark(HIGH_WATER_MARK); // Sets maximum message queue before blocking/timeout 76 | pushSocket.setLinger(0); 77 | 78 | // Receive commands from PUSH_PORT that client is sending to. 79 | Print("[PULL] Binding MT4 Server to Socket on Port " + IntegerToString(PUSH_PORT) + ".."); 80 | pullSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUSH_PORT)); 81 | pullSocket.setReceiveHighWaterMark(HIGH_WATER_MARK); 82 | pullSocket.setLinger(0); 83 | 84 | if (PUBLISH_MARKET_DATA == true) 85 | { 86 | // Send new market data to PUB_PORT that client is subscribed to. 87 | Print("[PUB] Binding MT4 Server to Socket on Port " + IntegerToString(PUB_PORT) + ".."); 88 | pubSocket.bind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUB_PORT)); 89 | } 90 | 91 | //--- 92 | return(INIT_SUCCEEDED); 93 | } 94 | 95 | 96 | //+------------------------------------------------------------------+ 97 | //| Expert tick function | 98 | //+------------------------------------------------------------------+ 99 | void OnTick() 100 | { 101 | /* 102 | Use this OnTick() function to send market data to subscribed client. 103 | */ 104 | // Initialise message and tick variables, get current time 105 | 106 | Print("Tick Received"); 107 | 108 | 109 | } 110 | 111 | //+------------------------------------------------------------------+ 112 | //| Expert deinitialization function | 113 | //+------------------------------------------------------------------+ 114 | void OnDeinit(const int reason) 115 | { 116 | //--- 117 | 118 | Print("[PUSH] Unbinding MT4 Server from Socket on Port " + IntegerToString(PULL_PORT) + ".."); 119 | pushSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PULL_PORT)); 120 | 121 | Print("[PULL] Unbinding MT4 Server from Socket on Port " + IntegerToString(PUSH_PORT) + ".."); 122 | pullSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUSH_PORT)); 123 | 124 | if (PUBLISH_MARKET_DATA == true) 125 | { 126 | Print("[PUB] Unbinding MT4 Server from Socket on Port " + IntegerToString(PUB_PORT) + ".."); 127 | pubSocket.unbind(StringFormat("%s://%s:%d", ZEROMQ_PROTOCOL, HOSTNAME, PUB_PORT)); 128 | } 129 | 130 | // Shutdown ZeroMQ Context 131 | context.shutdown(); 132 | context.destroy(0); 133 | 134 | EventKillTimer(); 135 | } 136 | 137 | 138 | //+------------------------------------------------------------------------------------+ 139 | //| Millisecond timer event function: poll for response and send out subscription data | 140 | //+------------------------------------------------------------------------------------+ 141 | void OnTimer() 142 | { 143 | 144 | 145 | // Get client's response, but don't block. 146 | pullSocket.recv(request, true); 147 | 148 | if (request.size() > 0) 149 | { 150 | // Wait 151 | // pullSocket.recv(request,false); 152 | 153 | // MessageHandler() should go here. 154 | // ZmqMsg reply = MessageHandler(request); 155 | 156 | // Send response, and block 157 | // pushSocket.send(reply); 158 | 159 | // Send response, but don't block 160 | // pushSocket.send(reply, true); 161 | } 162 | 163 | if (TimeCurrent() > currentTickTime){ 164 | previousTickTime = currentTickTime; 165 | currentTickTime = TimeCurrent(); 166 | sendSubscriptions(previousTickTime, currentTickTime); 167 | } 168 | 169 | 170 | 171 | } 172 | 173 | 174 | //+------------------------------------------------------------------+ 175 | //| Send Subscriptions Out | 176 | //+------------------------------------------------------------------+ 177 | void sendSubscriptions(ulong startT, ulong endT) 178 | { 179 | /* 180 | Use this OnTick() function to send market data to subscribed client. 181 | */ 182 | // Initialise message and tick variables, get current time 183 | string returnMsg; 184 | bool sendMessage = false; 185 | Print("Next Second Data:"); 186 | createPrototypeMessage(returnMsg, "['Bid','Ask','Last','Vol','Flag']"); 187 | 188 | if(!IsStopped() && PUBLISH_MARKET_DATA == true){ 189 | returnMsg = returnMsg + "'Data': {"; 190 | 191 | // Append each symbols tick data to the message in the form of SymbolName:DATADICT. 192 | for(int s = 0; s < ArraySize(Publish_Symbols); s++){ 193 | sendMessage = sendMessage || addSymbolTicksToMessage(returnMsg, Publish_Symbols[s], startT, endT); 194 | if (s != (ArraySize(Publish_Symbols)-1)){ 195 | returnMsg = returnMsg + ", "; 196 | } 197 | } 198 | if (sendMessage){ 199 | returnMsg = returnMsg + "}}"; // Once for data dict and once for total message dict 200 | ZmqMsg reply(returnMsg); 201 | pubSocket.send(reply, true); // The boolean is "nowait" parameter. Presumable this means that it doesn't block until it can send 202 | Print(returnMsg); 203 | } 204 | else { 205 | Print("No tick data for this second."); 206 | } 207 | } 208 | } 209 | 210 | 211 | //+---------------------------------------------------------------------------+ 212 | //| Creates prototype message filling in msgid, msgtype and msgsrc parameters | 213 | //+---------------------------------------------------------------------------+ 214 | void createPrototypeMessage(string &msg, string msgType, int msgID = -1){ 215 | msg = "{'MsgID': " + msgID + ", "; 216 | msg = msg + "'MsgSrc': 'MT4', "; 217 | msg = msg + "'MsgType': " + msgType + ", "; 218 | } 219 | 220 | //+---------------------------------------------------------------------------+ 221 | //| Appends a particular symbols tick data to an outgoing message | 222 | //+---------------------------------------------------------------------------+ 223 | bool addSymbolTicksToMessage(string &msg, string symbol, ulong starttime, ulong endtime){ 224 | // Assume message is already in state of 225 | // { .... 226 | // 'Payload':{'SYMBOL':} 227 | // 228 | 229 | // Convert time to milliseconds 230 | starttime = starttime * 1000; 231 | endtime = endtime * 1000; 232 | Print(starttime); 233 | Print(endtime); 234 | MqlTick ticks_array[]; // Array for ticks 235 | 236 | // Copy ticks 237 | int ticksCopied = CopyTicksRange(symbol, ticks_array, COPY_TICKS_ALL, starttime, endtime-1); 238 | 239 | if (ticksCopied == 0){ 240 | return false; 241 | } 242 | else { 243 | 244 | msg = msg + "'" + symbol + "': "; 245 | msg = msg + "{"; // Begin the symbol's data dict 246 | 247 | 248 | // Print((string)ticks_array[ticksCopied-1].bid); 249 | Print(ticksCopied); 250 | 251 | 252 | // Copy each tick to return message 253 | for(int x = 0; x < ticksCopied; x++){ 254 | 255 | // Get the additional number of milliseconds 256 | ulong millisecondTime = ticks_array[x].time_msc - ((ulong)ticks_array[x].time)*1000; 257 | 258 | // Populate return message with the retrieved tick data 259 | msg = msg + "'" + TimeToString(ticks_array[x].time,TIME_DATE|TIME_SECONDS) + ":" + (string)millisecondTime + "': [" + DoubleToString(ticks_array[x].bid) + ", " 260 | + DoubleToString(ticks_array[x].ask) + ", " + DoubleToString(ticks_array[x].last) + ", " + DoubleToString(ticks_array[x].volume_real) + ", " 261 | + IntegerToString(ticks_array[x].flags); 262 | msg = msg + "]"; 263 | 264 | // If not last message then add comma for further data. 265 | if(x != (ticksCopied-1)){ 266 | msg = msg + ", "; 267 | } 268 | } 269 | 270 | msg = msg + "}"; 271 | 272 | // Output Message: 273 | // { .... 274 | // 'Payload':{'SYMBOL': {'TIME1':[TICKDATA],'TIME2':[TICKDATA]}} 275 | // 276 | 277 | return true; 278 | 279 | } 280 | 281 | 282 | 283 | } 284 | 285 | //+---------------------------------------------------------------------------+ 286 | //| Get MT4 Error Message | 287 | //+---------------------------------------------------------------------------+ 288 | string ErrorDescription(int error_code) 289 | { 290 | string error_string; 291 | //---- 292 | switch(error_code) 293 | { 294 | //---- codes returned from trade server 295 | case 0: 296 | case 1: error_string="no error"; break; 297 | case 2: error_string="common error"; break; 298 | case 3: error_string="invalid trade parameters"; break; 299 | case 4: error_string="trade server is busy"; break; 300 | case 5: error_string="old version of the client terminal"; break; 301 | case 6: error_string="no connection with trade server"; break; 302 | case 7: error_string="not enough rights"; break; 303 | case 8: error_string="too frequent requests"; break; 304 | case 9: error_string="malfunctional trade operation (never returned error)"; break; 305 | case 64: error_string="account disabled"; break; 306 | case 65: error_string="invalid account"; break; 307 | case 128: error_string="trade timeout"; break; 308 | case 129: error_string="invalid price"; break; 309 | case 130: error_string="invalid stops"; break; 310 | case 131: error_string="invalid trade volume"; break; 311 | case 132: error_string="market is closed"; break; 312 | case 133: error_string="trade is disabled"; break; 313 | case 134: error_string="not enough money"; break; 314 | case 135: error_string="price changed"; break; 315 | case 136: error_string="off quotes"; break; 316 | case 137: error_string="broker is busy (never returned error)"; break; 317 | case 138: error_string="requote"; break; 318 | case 139: error_string="order is locked"; break; 319 | case 140: error_string="long positions only allowed"; break; 320 | case 141: error_string="too many requests"; break; 321 | case 145: error_string="modification denied because order too close to market"; break; 322 | case 146: error_string="trade context is busy"; break; 323 | case 147: error_string="expirations are denied by broker"; break; 324 | case 148: error_string="amount of open and pending orders has reached the limit"; break; 325 | case 149: error_string="hedging is prohibited"; break; 326 | case 150: error_string="prohibited by FIFO rules"; break; 327 | //---- mql4 errors 328 | case 4000: error_string="no error (never generated code)"; break; 329 | case 4001: error_string="wrong function pointer"; break; 330 | case 4002: error_string="array index is out of range"; break; 331 | case 4003: error_string="no memory for function call stack"; break; 332 | case 4004: error_string="recursive stack overflow"; break; 333 | case 4005: error_string="not enough stack for parameter"; break; 334 | case 4006: error_string="no memory for parameter string"; break; 335 | case 4007: error_string="no memory for temp string"; break; 336 | case 4008: error_string="not initialized string"; break; 337 | case 4009: error_string="not initialized string in array"; break; 338 | case 4010: error_string="no memory for array\' string"; break; 339 | case 4011: error_string="too long string"; break; 340 | case 4012: error_string="remainder from zero divide"; break; 341 | case 4013: error_string="zero divide"; break; 342 | case 4014: error_string="unknown command"; break; 343 | case 4015: error_string="wrong jump (never generated error)"; break; 344 | case 4016: error_string="not initialized array"; break; 345 | case 4017: error_string="dll calls are not allowed"; break; 346 | case 4018: error_string="cannot load library"; break; 347 | case 4019: error_string="cannot call function"; break; 348 | case 4020: error_string="expert function calls are not allowed"; break; 349 | case 4021: error_string="not enough memory for temp string returned from function"; break; 350 | case 4022: error_string="system is busy (never generated error)"; break; 351 | case 4050: error_string="invalid function parameters count"; break; 352 | case 4051: error_string="invalid function parameter value"; break; 353 | case 4052: error_string="string function internal error"; break; 354 | case 4053: error_string="some array error"; break; 355 | case 4054: error_string="incorrect series array using"; break; 356 | case 4055: error_string="custom indicator error"; break; 357 | case 4056: error_string="arrays are incompatible"; break; 358 | case 4057: error_string="global variables processing error"; break; 359 | case 4058: error_string="global variable not found"; break; 360 | case 4059: error_string="function is not allowed in testing mode"; break; 361 | case 4060: error_string="function is not confirmed"; break; 362 | case 4061: error_string="send mail error"; break; 363 | case 4062: error_string="string parameter expected"; break; 364 | case 4063: error_string="integer parameter expected"; break; 365 | case 4064: error_string="double parameter expected"; break; 366 | case 4065: error_string="array as parameter expected"; break; 367 | case 4066: error_string="requested history data in update state"; break; 368 | case 4073: error_string="No history data"; break; 369 | case 4099: error_string="end of file"; break; 370 | case 4100: error_string="some file error"; break; 371 | case 4101: error_string="wrong file name"; break; 372 | case 4102: error_string="too many opened files"; break; 373 | case 4103: error_string="cannot open file"; break; 374 | case 4104: error_string="incompatible access to a file"; break; 375 | case 4105: error_string="no order selected"; break; 376 | case 4106: error_string="unknown symbol"; break; 377 | case 4107: error_string="invalid price parameter for trade function"; break; 378 | case 4108: error_string="invalid ticket"; break; 379 | case 4109: error_string="trade is not allowed in the expert properties"; break; 380 | case 4110: error_string="longs are not allowed in the expert properties"; break; 381 | case 4111: error_string="shorts are not allowed in the expert properties"; break; 382 | case 4200: error_string="object is already exist"; break; 383 | case 4201: error_string="unknown object property"; break; 384 | case 4202: error_string="object is not exist"; break; 385 | case 4203: error_string="unknown object type"; break; 386 | case 4204: error_string="no object name"; break; 387 | case 4205: error_string="object coordinates error"; break; 388 | case 4206: error_string="no specified subwindow"; break; 389 | default: error_string="unknown error"; 390 | } 391 | //---- 392 | return(error_string); 393 | } 394 | -------------------------------------------------------------------------------- /Prototype Python Connector/ZeroMQ Python Connector.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ZeroMQ Python Connector.py 4 | -- 5 | @author: Darwinex Labs (www.darwinex.com) 6 | 7 | Last Updated: May 16, 2019 8 | 9 | Copyright (c) 2017-2019, Darwinex. All rights reserved. 10 | 11 | Licensed under the BSD 3-Clause License, you may not use this file except 12 | in compliance with the License. 13 | 14 | You may obtain a copy of the License at: 15 | https://opensource.org/licenses/BSD-3-Clause 16 | """ 17 | 18 | # IMPORT zmq library 19 | # import zmq, time 20 | import random 21 | import timeit 22 | 23 | import zmq 24 | from time import sleep 25 | from pandas import DataFrame, Timestamp, Timedelta, Series 26 | import pandas as pd 27 | from threading import Thread 28 | 29 | class DWX_ZeroMQ_Connector(): 30 | 31 | """ 32 | Setup ZeroMQ -> MetaTrader Connector 33 | """ 34 | 35 | def __init__(self, 36 | _ClientID='DLabs_Python', # Unique ID for this client 37 | _host='localhost', # Host to connect to 38 | _protocol='tcp', # Connection protocol 39 | _PUSH_PORT=32768, # Port for Sending commands 40 | _PULL_PORT=32769, # Port for Receiving responses 41 | _SUB_PORT=32770, # Port for Subscribing for prices 42 | _delimiter=';', 43 | _verbose=True): # String delimiter 44 | 45 | # Strategy Status (if this is False, ZeroMQ will not listen for data) 46 | self._ACTIVE = True 47 | 48 | # Client ID 49 | self._ClientID = _ClientID 50 | 51 | # ZeroMQ Host 52 | self._host = _host 53 | 54 | # Connection Protocol 55 | self._protocol = _protocol 56 | 57 | # ZeroMQ Context 58 | self._ZMQ_CONTEXT = zmq.Context() 59 | 60 | # TCP Connection URL Template 61 | self._URL = self._protocol + "://" + self._host + ":" 62 | 63 | # Ports for PUSH, PULL and SUB sockets respectively 64 | self._PUSH_PORT = _PUSH_PORT 65 | self._PULL_PORT = _PULL_PORT 66 | self._SUB_PORT = _SUB_PORT 67 | 68 | # Create Sockets 69 | self._PUSH_SOCKET = self._ZMQ_CONTEXT.socket(zmq.PUSH) 70 | self._PUSH_SOCKET.setsockopt(zmq.SNDHWM, 1) 71 | 72 | self._PULL_SOCKET = self._ZMQ_CONTEXT.socket(zmq.PULL) 73 | self._PULL_SOCKET.setsockopt(zmq.RCVHWM, 1) 74 | 75 | self._SUB_SOCKET = self._ZMQ_CONTEXT.socket(zmq.SUB) 76 | 77 | # Bind PUSH Socket to send commands to MetaTrader 78 | self._PUSH_SOCKET.connect(self._URL + str(self._PUSH_PORT)) 79 | print("[INIT] Ready to send commands to METATRADER (PUSH): " + str(self._PUSH_PORT)) 80 | 81 | # Connect PULL Socket to receive command responses from MetaTrader 82 | self._PULL_SOCKET.connect(self._URL + str(self._PULL_PORT)) 83 | print("[INIT] Listening for responses from METATRADER (PULL): " + str(self._PULL_PORT)) 84 | 85 | # Connect SUB Socket to receive market data from MetaTrader 86 | print("[INIT] Listening for market data from METATRADER (SUB): " + str(self._SUB_PORT)) 87 | self._SUB_SOCKET.connect(self._URL + str(self._SUB_PORT)) 88 | 89 | # Initialize POLL set and register PULL and SUB sockets 90 | self._poller = zmq.Poller() 91 | self._poller.register(self._PULL_SOCKET, zmq.POLLIN) 92 | self._poller.register(self._SUB_SOCKET, zmq.POLLIN) 93 | 94 | # Start listening for responses to commands and new market data 95 | self._string_delimiter = _delimiter 96 | 97 | # BID/ASK Market Data Subscription Threads ({SYMBOL: Thread}) 98 | self._MarketData_Thread = None 99 | 100 | # Begin polling for PULL / SUB data 101 | self._MarketData_Thread = Thread(target=self._DWX_ZMQ_Poll_Data_, args=(self._string_delimiter)) 102 | self._MarketData_Thread.start() 103 | 104 | # Market Data Dictionary by Symbol (holds tick data) 105 | self._Market_Data_DB = {} # {SYMBOL: {TIMESTAMP: (BID, ASK)}} 106 | 107 | # Temporary Order STRUCT for convenience wrappers later. 108 | self.temp_order_dict = self._generate_default_order_dict() 109 | 110 | # Thread returns the most recently received DATA block here 111 | self._thread_data_output = {'_messageID': 0} # Default messageID 112 | 113 | # Verbosity 114 | self._verbose = _verbose 115 | 116 | """ 117 | # String to contain market data response message 118 | self._marketDataResponse 119 | """ 120 | ########################################################################## 121 | 122 | """ 123 | Set Status (to enable/disable strategy manually) 124 | """ 125 | def _setStatus(self, _new_status=False): 126 | 127 | self._ACTIVE = _new_status 128 | print("\n**\n[KERNEL] Setting Status to {} - Deactivating Threads.. please wait a bit.\n**".format(_new_status)) 129 | 130 | ########################################################################## 131 | 132 | """ 133 | Function to send commands to MetaTrader (PUSH) 134 | """ 135 | def remote_send(self, _socket, _data): 136 | 137 | try: 138 | _socket.send_string(_data, zmq.DONTWAIT) 139 | except zmq.error.Again: 140 | print("\nResource timeout during send.. please try again.") 141 | sleep(0.000000001) 142 | 143 | ########################################################################## 144 | 145 | def _get_response_(self, messageID = None, maximumTime = 3): 146 | if messageID == None: 147 | return self._thread_data_output 148 | else: 149 | startTime = timeit.default_timer() 150 | 151 | while timeit.default_timer() < (startTime + maximumTime): 152 | if self._thread_data_output['_messageID'] == messageID: 153 | return self._thread_data_output 154 | 155 | print("Message with requested ID could not be found \n") 156 | print("Current message in memory: ") 157 | print(self._thread_data_output) 158 | return {'_messageID':-1} 159 | 160 | 161 | ########################################################################## 162 | 163 | def _set_response_(self, _resp=None): 164 | self._thread_data_output = _resp 165 | 166 | ########################################################################## 167 | 168 | def _valid_response_(self, _input='zmq'): 169 | 170 | # Valid data types 171 | _types = (dict, DataFrame) 172 | 173 | # If _input = 'zmq', assume self._zmq._thread_data_output 174 | if isinstance(_input, str) and _input == 'zmq': 175 | return isinstance(self._get_response_(), _types) 176 | else: 177 | return isinstance(_input, _types) 178 | 179 | # Default 180 | return False 181 | 182 | ########################################################################## 183 | 184 | """ 185 | Function to retrieve data from MetaTrader (PULL or SUB) 186 | """ 187 | def remote_recv(self, _socket): 188 | 189 | try: 190 | msg = _socket.recv_string(zmq.DONTWAIT) 191 | return msg 192 | except zmq.error.Again: 193 | print("\nResource timeout during receive.. please try again.") 194 | sleep(0.000001) 195 | 196 | return None 197 | 198 | ########################################################################## 199 | 200 | ########################################################################## 201 | 202 | """ 203 | Function to generate a random message ID 204 | """ 205 | 206 | def generateMessageID(self): 207 | return random.randint(0, 2000000000) 208 | 209 | ########################################################################## 210 | 211 | # Convenience functions to permit easy trading via underlying functions. 212 | 213 | # OPEN ORDER 214 | def _DWX_MTX_NEW_TRADE_(self, _order=None): 215 | 216 | if _order is None: 217 | _order = self._generate_default_order_dict() 218 | 219 | return self._DWX_MTX_SEND_COMMAND_(**_order) 220 | 221 | # MODIFY ORDER 222 | def _DWX_MTX_MODIFY_TRADE_BY_TICKET_(self, _ticket, _SL, _TP): # in points 223 | 224 | try: 225 | self.temp_order_dict['_action'] = 'MODIFY' 226 | self.temp_order_dict['_SL'] = _SL 227 | self.temp_order_dict['_TP'] = _TP 228 | self.temp_order_dict['_ticket'] = _ticket 229 | 230 | # Execute 231 | return self._DWX_MTX_SEND_COMMAND_(**self.temp_order_dict) 232 | 233 | except KeyError: 234 | print("[ERROR] Order Ticket {} not found!".format(_ticket)) 235 | return -1 236 | 237 | # CLOSE ORDER 238 | def _DWX_MTX_CLOSE_TRADE_BY_TICKET_(self, _ticket): 239 | 240 | try: 241 | self.temp_order_dict['_action'] = 'CLOSE' 242 | self.temp_order_dict['_ticket'] = _ticket 243 | 244 | # Execute 245 | return self._DWX_MTX_SEND_COMMAND_(**self.temp_order_dict) 246 | 247 | except KeyError: 248 | print("[ERROR] Order Ticket {} not found!".format(_ticket)) 249 | return -1 250 | 251 | 252 | # CLOSE PARTIAL 253 | def _DWX_MTX_CLOSE_PARTIAL_BY_TICKET_(self, _ticket, _lots): 254 | 255 | try: 256 | self.temp_order_dict['_action'] = 'CLOSE_PARTIAL' 257 | self.temp_order_dict['_ticket'] = _ticket 258 | self.temp_order_dict['_lots'] = _lots 259 | 260 | # Execute 261 | return self._DWX_MTX_SEND_COMMAND_(**self.temp_order_dict) 262 | 263 | except KeyError: 264 | print("[ERROR] Order Ticket {} not found!".format(_ticket)) 265 | return -1 266 | 267 | # CLOSE MAGIC 268 | def _DWX_MTX_CLOSE_TRADES_BY_MAGIC_(self, _magic): 269 | 270 | try: 271 | self.temp_order_dict['_action'] = 'CLOSE_MAGIC' 272 | self.temp_order_dict['_magic'] = _magic 273 | 274 | # Execute 275 | return self._DWX_MTX_SEND_COMMAND_(**self.temp_order_dict) 276 | 277 | except KeyError: 278 | print("KeyError during temp_order_dict index search for _action or _magic") 279 | return -1 280 | 281 | 282 | # CLOSE ALL TRADES 283 | def _DWX_MTX_CLOSE_ALL_TRADES_(self): 284 | 285 | try: 286 | self.temp_order_dict['_action'] = 'CLOSE_ALL' 287 | 288 | # Execute 289 | return self._DWX_MTX_SEND_COMMAND_(**self.temp_order_dict) 290 | 291 | except KeyError: 292 | print("KeyError during temp_order_dict index search for _action") 293 | return -1 294 | 295 | 296 | 297 | # GET OPEN TRADES 298 | def _DWX_MTX_GET_ALL_OPEN_TRADES_(self): 299 | 300 | try: 301 | self.temp_order_dict['_action'] = 'GET_OPEN_TRADES' 302 | 303 | # Execute 304 | return self._DWX_MTX_SEND_COMMAND_(**self.temp_order_dict) 305 | 306 | except KeyError: 307 | print("KeyError during temp_order_dict index search for _action") 308 | return -1 309 | 310 | 311 | 312 | # DEFAULT ORDER DICT 313 | def _generate_default_order_dict(self): 314 | return({'_action': 'OPEN', 315 | '_type': 0, 316 | '_symbol': 'EURUSD', 317 | '_price': 0.0, 318 | '_SL': 500, # SL/TP in POINTS, not pips. 319 | '_TP': 500, 320 | '_comment': 'DWX_Python_to_MT', 321 | '_lots': 0.01, 322 | '_magic': 123456, 323 | '_ticket': 0}) 324 | 325 | # DEFAULT DATA REQUEST DICT 326 | def _generate_default_data_dict(self): 327 | return({'_action': 'DATA', 328 | '_symbol': 'EURUSD', 329 | '_timeframe': 1440, # M1 = 1, M5 = 5, and so on.. 330 | '_start': '2018.12.21 17:00:00', # timestamp in MT4 recognized format 331 | '_end': '2018.12.21 17:05:00'}) 332 | 333 | ########################################################################## 334 | """ 335 | Function to construct messages for sending DATA commands to MetaTrader 336 | """ 337 | def _DWX_MTX_SEND_MARKETDATA_REQUEST_(self, 338 | _symbol='EURUSD', 339 | _timeframe=1, 340 | _start='2019.01.04 17:00:00', 341 | _end=Timestamp.now().strftime('%Y.%m.%d %H:%M:00')): 342 | #_end='2019.01.04 17:05:00'): 343 | messageID = self.generateMessageID() 344 | _msg = "{};{};{};{};{};{}".format(messageID, 'DATA', 345 | _symbol, 346 | _timeframe, 347 | _start, 348 | _end) 349 | # Send via PUSH Socket 350 | self.remote_send(self._PUSH_SOCKET, _msg) 351 | 352 | return messageID 353 | 354 | ########################################################################## 355 | """ 356 | Function to construct messages for requested rates data from MetaTrader 357 | """ 358 | def _DWX_MTX_SEND_RATESDATA_REQUEST_(self, 359 | _symbol='EURUSD', 360 | _timeframe=1, 361 | _start='2019.01.04 17:00:00', 362 | _end=Timestamp.now().strftime('%Y.%m.%d %H:%M:00')): 363 | #_end='2019.01.04 17:05:00'): 364 | messageID = self.generateMessageID() 365 | _msg = "{};{};{};{};{};{}".format(messageID, 'DATA_RATES', 366 | _symbol, 367 | _timeframe, 368 | _start, 369 | _end) 370 | # Send via PUSH Socket 371 | self.remote_send(self._PUSH_SOCKET, _msg) 372 | 373 | return messageID 374 | 375 | ########################################################################## 376 | """ 377 | Function to construct messages for sending Trade commands to MetaTrader 378 | """ 379 | def _DWX_MTX_SEND_COMMAND_(self, _action='OPEN', _type=0, 380 | _symbol='EURUSD', _price=0.0, 381 | _SL=50, _TP=50, _comment="Python-to-MT", 382 | _lots=0.01, _magic=123456, _ticket=0): 383 | 384 | messageID = self.generateMessageID() 385 | 386 | _msg = "{};{};{};{};{};{};{};{};{};{};{};{}".format(messageID,'TRADE',_action,_type, 387 | _symbol,_price, 388 | _SL,_TP,_comment, 389 | _lots,_magic, 390 | _ticket) 391 | 392 | # Send via PUSH Socket 393 | self.remote_send(self._PUSH_SOCKET, _msg) 394 | 395 | return messageID 396 | 397 | """ 398 | compArray[0] = TRADE or DATA 399 | compArray[1] = ACTION (e.g. OPEN, MODIFY, CLOSE) 400 | compArray[2] = TYPE (e.g. OP_BUY, OP_SELL, etc - only used when ACTION=OPEN) 401 | 402 | For compArray[0] == DATA, format is: 403 | DATA|SYMBOL|TIMEFRAME|START_DATETIME|END_DATETIME 404 | 405 | // ORDER TYPES: 406 | // https://docs.mql4.com/constants/tradingconstants/orderproperties 407 | 408 | // OP_BUY = 0 409 | // OP_SELL = 1 410 | // OP_BUYLIMIT = 2 411 | // OP_SELLLIMIT = 3 412 | // OP_BUYSTOP = 4 413 | // OP_SELLSTOP = 5 414 | 415 | compArray[3] = Symbol (e.g. EURUSD, etc.) 416 | compArray[4] = Open/Close Price (ignored if ACTION = MODIFY) 417 | compArray[5] = SL 418 | compArray[6] = TP 419 | compArray[7] = Trade Comment 420 | compArray[8] = Lots 421 | compArray[9] = Magic Number 422 | compArray[10] = Ticket Number (MODIFY/CLOSE) 423 | """ 424 | 425 | ########################################################################## 426 | 427 | """ 428 | Function to check Poller for new reponses (PULL) and market data (SUB) 429 | """ 430 | 431 | def _DWX_ZMQ_Poll_Data_(self, 432 | string_delimiter=';'): 433 | 434 | while self._ACTIVE: 435 | sockets = dict(self._poller.poll()) 436 | # Process response to commands sent to MetaTrader 437 | if self._PULL_SOCKET in sockets and sockets[self._PULL_SOCKET] == zmq.POLLIN: 438 | try: 439 | msg = self._PULL_SOCKET.recv_string(zmq.DONTWAIT) 440 | 441 | # If data is returned, store as pandas Series 442 | if msg != '' and msg != None: 443 | try: 444 | _data = eval(msg) 445 | self._thread_data_output = _data 446 | if self._verbose: 447 | print(_data) # default logic 448 | 449 | except Exception as ex: 450 | _exstr = "Exception Type {0}. Args:\n{1!r}" 451 | _msg = _exstr.format(type(ex).__name__, ex.args) 452 | print(_msg) 453 | 454 | except zmq.error.Again: 455 | pass # resource temporarily unavailable, nothing to print 456 | except ValueError: 457 | pass # No data returned, passing iteration. 458 | except UnboundLocalError: 459 | pass # _symbol may sometimes get referenced before being assigned. 460 | 461 | # Receive new market data from MetaTrader 462 | if self._SUB_SOCKET in sockets and sockets[self._SUB_SOCKET] == zmq.POLLIN: 463 | try: 464 | msg = self._SUB_SOCKET.recv_string(zmq.DONTWAIT) 465 | 466 | if msg != "": 467 | _symbol, _data = msg.split(";") 468 | _bid, _ask, _timestamp, _lastdeal, _lastvol, _flag = _data.split('|') 469 | 470 | if self._verbose: 471 | print("\n[" + _symbol + "] " + _timestamp + " (" + _bid + "/" + _ask + ") BID/ASK") 472 | 473 | # Update Market Data DB 474 | if _symbol not in self._Market_Data_DB.keys(): 475 | self._Market_Data_DB[_symbol] = [] 476 | 477 | self._Market_Data_DB[_symbol].append( 478 | {'Time':Timestamp.strptime(_timestamp, "%Y.%m.%d %H:%M:%S"), 'Bid': float(_bid), 479 | 'Ask': float(_ask), 'Flag': int(_flag)} 480 | ) 481 | 482 | except zmq.error.Again: 483 | pass # resource temporarily unavailable, nothing to print 484 | except ValueError: 485 | pass # No data returned, passing iteration. 486 | except UnboundLocalError: 487 | pass # _symbol may sometimes get referenced before being assigned. 488 | 489 | ########################################################################## 490 | 491 | """ 492 | Function to subscribe to given Symbol's BID/ASK feed from MetaTrader 493 | """ 494 | def _DWX_MTX_SUBSCRIBE_MARKETDATA_(self, _symbol, _string_delimiter=';'): 495 | 496 | # Subscribe to SYMBOL first. 497 | self._SUB_SOCKET.setsockopt_string(zmq.SUBSCRIBE, _symbol) 498 | 499 | if self._MarketData_Thread is None: 500 | 501 | self._MarketData_Thread = Thread(target=self._DWX_ZMQ_Poll_Data, args=(_string_delimiter)) 502 | self._MarketData_Thread.start() 503 | 504 | print("[KERNEL] Subscribed to {} BID/ASK updates. See self._Market_Data_DB.".format(_symbol)) 505 | 506 | """ 507 | Function to unsubscribe to given Symbol's BID/ASK feed from MetaTrader 508 | """ 509 | def _DWX_MTX_UNSUBSCRIBE_MARKETDATA_(self, _symbol): 510 | 511 | self._SUB_SOCKET.setsockopt_string(zmq.UNSUBSCRIBE, _symbol) 512 | print("\n**\n[KERNEL] Unsubscribing from " + _symbol + "\n**\n") 513 | 514 | 515 | """ 516 | Function to unsubscribe from ALL MetaTrader Symbols 517 | """ 518 | def _DWX_MTX_UNSUBSCRIBE_ALL_MARKETDATA_REQUESTS_(self): 519 | 520 | self._setStatus(False) 521 | self._MarketData_Thread = None 522 | 523 | """ 524 | Function to clear out market data db 525 | """ 526 | 527 | def clearMarketDataDB(self): 528 | 529 | self._Market_Data_DB = {} 530 | 531 | ########################################################################## 532 | -------------------------------------------------------------------------------- /Prototype Python Connector/dwx_tick_data_io.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Sample code: 4 | dwx_tick_data_io.py 5 | -- 6 | @author: Darwinex Labs (www.darwinex.com) 7 | 8 | Copyright (c) 2017-2019, Darwinex. All rights reserved. 9 | 10 | Licensed under the BSD 3-Clause License, you may not use this file except 11 | in compliance with the License. 12 | 13 | You may obtain a copy of the License at: 14 | https://opensource.org/licenses/BSD-3-Clause 15 | """ 16 | 17 | from pathlib import Path 18 | import pandas as pd 19 | import numpy as np 20 | import gzip 21 | 22 | pd.set_option('display.max_columns', 500) 23 | pd.set_option('display.width', 1000) 24 | 25 | class DWX_TICK_DATA_IO(): 26 | 27 | def __init__(self, 28 | _format='{}_{}_{}_{}', 29 | _extension='.log.gz', 30 | _delimiter=',', 31 | _path=''): 32 | 33 | self._format = _format 34 | self._extension = _extension 35 | self._delimiter = _delimiter 36 | self._path = _path 37 | self._symbol_df = None 38 | 39 | ########################################################################## 40 | 41 | # Return list of files for BID and ASK each. 42 | def _find_symbol_files_(self, _symbol='EURUSD', 43 | _date='2017-10-02', 44 | _hour='15'): 45 | 46 | if _date == '': 47 | _fs = [filename.name for filename in Path(self._path).glob('{}/*{}' 48 | .format(_symbol, self._extension))] 49 | else: 50 | if _hour == '': 51 | _fs = [filename.name for filename in Path(self._path).glob('{}/*{}' 52 | .format(_symbol, self._extension)) 53 | if _date in filename.name] 54 | 55 | else: 56 | _fs = [filename.name for filename in Path(self._path).glob('{}/*{}' 57 | .format(_symbol, self._extension)) 58 | if _date in filename.name 59 | and _hour in filename.name] 60 | 61 | if len(_fs) > 0: 62 | 63 | return ['{}/{}/{}'.format(self._path, _symbol, _f) for _f in _fs if 'BID' in _f], ['{}/{}/{}' 64 | .format(self._path, _symbol, _f) for _f in _fs if 'ASK' in _f] 65 | 66 | else: 67 | print('[WARNING] No files found for {} - {} - {}' 68 | .format(_symbol, _date, _hour)) 69 | 70 | return None, None 71 | 72 | ########################################################################## 73 | 74 | def _construct_data_(self, _filename): 75 | 76 | _df = pd.DataFrame([line.strip().decode().split(self._delimiter) 77 | for line in gzip.open(_filename) if len(line) > 10]) 78 | 79 | if 'BID' in _filename: 80 | _df.columns = ['timestamp','bid_price','bid_size'] 81 | elif 'ASK' in _filename: 82 | _df.columns = ['timestamp','ask_price','ask_size'] 83 | 84 | _df.set_index('timestamp', drop=True, inplace=True) 85 | 86 | return _df.apply(pd.to_numeric) 87 | 88 | ########################################################################## 89 | 90 | def _get_symbol_as_dataframe_(self, _symbol='EURUSD', 91 | _date='', 92 | _hour='', 93 | _convert_epochs=True, 94 | _check_integrity=False, 95 | _calc_spread = False, 96 | _reindex=['ask_price','bid_price'], 97 | _precision='tick', 98 | _daily_start=22, 99 | _symbol_digits=5): 100 | 101 | """ 102 | See http://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html 103 | for .resample() rule / frequency strings. 104 | """ 105 | 106 | print('[INFO] Finding symbol files.. please wait..') 107 | 108 | _bid_files, _ask_files = self._find_symbol_files_(_symbol,_date,_hour) 109 | 110 | print('[INFO] Processing BID ({}) / ASK ({}) files.. please wait..'.format(len(_bid_files), len(_ask_files))) 111 | 112 | _bid_dfs = [self._construct_data_(_bid_files[i]) 113 | for i in range(0, len(_bid_files)) if ((print('\rBIDS: {} / {} - {}' 114 | .format(i+1,len(_bid_files),_bid_files[i]), end="", flush=True) or 1==1))] 115 | print(len(_bid_dfs)) 116 | print(type(_bid_dfs[1])) 117 | 118 | # BIDS 119 | _bids = pd.concat(_bid_dfs, axis=0, sort=True) 120 | print('') 121 | 122 | # ASKS 123 | if len(_ask_files) != 0: 124 | _ask_dfs = [self._construct_data_(_ask_files[i]) for i in range(0, len(_ask_files)) if (print('\rASKS: {} / {} - {}' 125 | .format(i+1,len(_ask_files),_ask_files[i]), end="", flush=True) or 1==1)] 126 | _asks = pd.concat(_ask_dfs, axis=0, sort=True) 127 | 128 | _df = _asks.merge(_bids, how='outer', left_index=True, right_index=True, copy=False).fillna(method='ffill').dropna() 129 | else: 130 | _df = _bids 131 | # Calculate spread? 132 | if _calc_spread: 133 | _df['spread'] = abs(np.diff(_df[['ask_price','bid_price']])) 134 | 135 | # Convert timestamps? 136 | if _convert_epochs: 137 | _df.index = pd.to_datetime(_df.index, unit='ms') 138 | 139 | # Reindex to selected columns? 140 | if len(_reindex) > 0: 141 | _df = _df.reindex(_reindex, axis=1) 142 | 143 | # Resample? 144 | if _precision != 'tick': 145 | _df['mid_price'] = round((_df.ask_price + _df.bid_price) / 2, _symbol_digits) 146 | if _precision not in ['B','C','D','W','24H']: 147 | _df = _df.mid_price.resample(rule=_precision, label='left', closed='left').ohlc() 148 | else: 149 | _df = _df.mid_price.resample(rule=_precision, base=_daily_start, label='left', closed='left').ohlc().dropna() 150 | 151 | # Check data integrity? 152 | if _check_integrity: 153 | 154 | print('\n\n[INFO] Checking data integrity..') 155 | self._integrity_check_(_df) 156 | 157 | return _df 158 | 159 | ########################################################################## 160 | 161 | def _integrity_check_(self, _df): 162 | 163 | if isinstance(_df, pd.DataFrame) == False: 164 | 165 | print('[ERROR] Input must be a Pandas DataFrame') 166 | 167 | else: 168 | 169 | _diff = _df.index.to_series().diff() 170 | 171 | print('\n[TEST #1] Data Frequency Statistics\n--') 172 | print(_diff.describe()) 173 | 174 | print('\n[TEST #2] Mode of Gap Distribution\n--') 175 | print(_diff.value_counts().head(1)) 176 | 177 | print('\n[TEST #3] Hourly Spread Distribution\n--') 178 | _df.groupby(_df.index.hour).spread.mean().plot( 179 | xticks=range(0,24), 180 | title='Average Spread by Hour (UTC)') 181 | 182 | ########################################################################## 183 | -------------------------------------------------------------------------------- /Prototype Python Connector/dwx_tickdata_download.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Created on Mon Oct 29 17:24:20 2018 5 | 6 | Script: dwx_tickdata_download.py (Python 3) 7 | -- 8 | Downloads tick data from the Darwinex tick data server. This code demonstrates 9 | how to download data for one specific date/hour combination, but can be 10 | extended easily to downloading entire assets over user-specified start/end 11 | datetime ranges. 12 | 13 | Requirements: Your Darwinex FTP credentials. 14 | 15 | Result: Dictionary of pandas DataFrame objects by date/hour. 16 | (columns: float([ask, size]), index: millisecond timestamp) 17 | 18 | Example code: 19 | 20 | > td = DWX_Tick_Data(dwx_ftp_user='very_secure_username', 21 | dwx_ftp_pass='extremely_secure_password', 22 | dwx_ftp_hostname='mystery_ftp.server.com', 23 | dwx_ftp_port=21) 24 | 25 | > td._download_hour_(_asset='EURNOK', _date='2018-10-22', _hour='00') 26 | 27 | > td._asset_db['EURNOK-2018-10-22-00'] 28 | 29 | ask size 30 | 2018-10-22 00:00:07.097000+00:00 9.47202 1000000.0 31 | 2018-10-22 00:00:07.449000+00:00 9.47188 750000.0 32 | 2018-10-22 00:01:08.123000+00:00 9.47201 250000.0 33 | 2018-10-22 00:01:10.576000+00:00 9.47202 1000000.0 34 | ... ... 35 | 36 | @author: Darwinex Labs 37 | @twitter: https://twitter.com/darwinexlabs 38 | @web: http://blog.darwinex.com/category/labs 39 | 40 | """ 41 | 42 | from ftplib import FTP 43 | from io import BytesIO 44 | import pandas as pd 45 | import gzip 46 | 47 | class DWX_Tick_Data(): 48 | 49 | def __init__(self, dwx_ftp_user='', 50 | dwx_ftp_pass='', 51 | dwx_ftp_hostname='', 52 | dwx_ftp_port=21): 53 | 54 | # Dictionary DB to hold dictionary objects in FX/Hour format 55 | self._asset_db = {} 56 | 57 | self._ftpObj = FTP(dwx_ftp_hostname) 58 | self._ftpObj.login(dwx_ftp_user, dwx_ftp_pass) 59 | 60 | self._virtual_dl = None 61 | 62 | ######################################################################### 63 | # Function: Downloads and stored currency tick data from Darwinex FTP 64 | # Server. Object stores data in a dictionary, keys being of the 65 | # format: CURRENCYPAIR-YYYY-MM-DD-HH 66 | ######################################################################### 67 | 68 | def _download_hour_(self, _asset='EURUSD', _date='2017-10-01', _hour='22', 69 | _ftp_loc_format='{}/{}_ASK_{}_{}.log.gz', 70 | _verbose=False): 71 | 72 | _file = _ftp_loc_format.format(_asset, _asset, _date, _hour) 73 | _key = '{}-{}-{}'.format(_asset, _date, _hour) 74 | 75 | self._virtual_dl = BytesIO() 76 | 77 | if _verbose is True: 78 | print("\n[INFO] Retrieving file \'{}\' from Darwinex Tick Data Server..".format(_file)) 79 | 80 | try: 81 | self._ftpObj.retrbinary("RETR {}".format(_file), self._virtual_dl.write) 82 | 83 | self._virtual_dl.seek(0) 84 | _log = gzip.open(self._virtual_dl) 85 | 86 | # Get bytes into local DB as list of lists 87 | self._asset_db[_key] = [line.strip().decode().split(',') for line in _log] 88 | 89 | # Construct DataFrame 90 | _temp = self._asset_db[_key] 91 | self._asset_db[_key] = pd.DataFrame({'ask': [l[1] for l in _temp], 92 | 'size': [l[2] for l in _temp]}, 93 | index=[pd.to_datetime(l[0], unit='ms', utc=True) for l in _temp]) 94 | 95 | # Sanitize types 96 | self._asset_db[_key] = self._asset_db[_key].astype(float) 97 | 98 | if _verbose is True: 99 | print('\n[SUCCESS] {} tick data for {} (hour {}) stored in self._asset_db dict object.\n'.format(_asset, _date, _hour)) 100 | 101 | # Case: if file not found 102 | except Exception as ex: 103 | _exstr = "Exception Type {0}. Args:\n{1!r}" 104 | _msg = _exstr.format(type(ex).__name__, ex.args) 105 | print(_msg) 106 | 107 | ######################################################################### 108 | -------------------------------------------------------------------------------- /Python Client Connector/Client Testing.py: -------------------------------------------------------------------------------- 1 | import python_client_connector as pcc 2 | -------------------------------------------------------------------------------- /Python Client Connector/python_client_connector.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python Client Connector V1.py 3 | -- 4 | @author: Umais Zahid (umais.me) 5 | 6 | Copyright (c) 2019, Umais Zahid. All rights reserved. 7 | """ 8 | 9 | ########################## 10 | # Import statements # 11 | ########################## 12 | import random 13 | import timeit 14 | 15 | import zmq 16 | from time import sleep 17 | from pandas import DataFrame, Timestamp, Timedelta, Series 18 | from threading import Thread 19 | 20 | 21 | #################################### 22 | # MTConnector Class Definition # 23 | #################################### 24 | class MTConnector(): 25 | 26 | def __init__(self, 27 | CLIENT_ID='PythonClient1', # Unique ID for this client 28 | HOST='localhost', # Host to connect to 29 | PROTOCOL='tcp', # Connection protocol 30 | PUSH_PORT=32768, # Port for Sending commands 31 | PULL_PORT=32769, # Port for Receiving responses 32 | SUB_PORT=32770, # Port for Subscribing for prices 33 | VERBOSE=True): # Determines whether statements should be printed 34 | 35 | # Strategy Status (if this is False, ZeroMQ will not listen for data) 36 | self.ACTIVE = True 37 | 38 | # Client ID 39 | self.CLIENT_ID = CLIENT_ID 40 | 41 | # ZeroMQ Host 42 | self.HOST = HOST 43 | 44 | # Connection Protocol 45 | self.PROTOCOL = PROTOCOL 46 | 47 | # Initialise ZeroMQ Context 48 | self.ZMQ_CONTEXT = zmq.Context() 49 | 50 | # TCP Connection URL Template 51 | self._URL = self.PROTOCOL + "://" + self.HOST + ":" 52 | 53 | # Ports for PUSH, PULL and SUB sockets respectively 54 | self.PUSH_PORT = PUSH_PORT 55 | self.PULL_PORT = PULL_PORT 56 | self.SUB_PORT = SUB_PORT 57 | 58 | # Create Sockets 59 | self.PUSH_SOCKET = self.ZMQ_CONTEXT.socket(zmq.PUSH) 60 | self.PUSH_SOCKET.setsockopt(zmq.SNDHWM, 1) 61 | 62 | self.PULL_SOCKET = self.ZMQ_CONTEXT.socket(zmq.PULL) 63 | self.PULL_SOCKET.setsockopt(zmq.RCVHWM, 1) 64 | 65 | self.SUB_SOCKET = self.ZMQ_CONTEXT.socket(zmq.SUB) 66 | 67 | # Bind PUSH Socket to send commands to MetaTrader 68 | self.PUSH_SOCKET.connect(self._URL + str(self.PUSH_PORT)) 69 | print("[INIT] Ready to send commands to METATRADER (PUSH): " + str(self.PUSH_PORT)) 70 | 71 | # Connect PULL Socket to receive command responses from MetaTrader 72 | self.PULL_SOCKET.connect(self._URL + str(self.PULL_PORT)) 73 | print("[INIT] Listening for responses from METATRADER (PULL): " + str(self.PULL_PORT)) 74 | 75 | # Connect SUB Socket to receive market data from MetaTrader 76 | print("[INIT] Listening for market data from METATRADER (SUB): " + str(self.SUB_PORT)) 77 | self.SUB_SOCKET.connect(self._URL + str(self.SUB_PORT)) 78 | 79 | # Initialize POLL set and register PULL and SUB sockets 80 | self.POLLER = zmq.Poller() 81 | self.POLLER.register(self.PULL_SOCKET, zmq.POLLIN) 82 | self.POLLER.register(self.SUB_SOCKET, zmq.POLLIN) 83 | 84 | ################################################################# 85 | # Start listening for responses to commands and new market data 86 | # self._string_delimiter = _delimiter 87 | ################################################################# 88 | 89 | # BID/ASK Market Data Subscription Threads ({SYMBOL: Thread}) 90 | 91 | 92 | # BID/ASK Market Data Subscription Threads ({SYMBOL: Thread}) 93 | self.MARKET_DATA_THREAD = None 94 | 95 | # Begin polling for PULL / SUB data 96 | self.MARKET_DATA_THREAD = Thread(target=self.pollData) 97 | self.MARKET_DATA_THREAD.start() 98 | 99 | # Market Data Dictionary by Symbol (holds tick data) 100 | self.MARKET_DATA_DB = {} # dict of Pandas dataframe containing tick data 101 | 102 | # Market Data Request Response 103 | self.REQUEST_RESPONSE_DB = {} # dict of Pandas dataframe containing tick data 104 | 105 | # Verbosity 106 | self.VERBOSE = VERBOSE 107 | 108 | ############################################ 109 | # Retrieve data from MetaTrader via ZMQ # 110 | ############################################ 111 | def remoteReceive(self, socket): 112 | 113 | try: 114 | msg = socket.recv_string(zmq.DONTWAIT) 115 | return msg 116 | except zmq.error.Again: 117 | print("\nResource timeout during receive.. please try again.") 118 | sleep(0.000001) 119 | 120 | return None 121 | 122 | ################################################## 123 | # Function to send commands to MetaTrader (PUSH) # 124 | ################################################## 125 | 126 | def remoteSend(self, socket, message): 127 | try: 128 | socket.send_string(message, zmq.DONTWAIT) 129 | except zmq.error.Again: 130 | print("\nResource timeout during send.. please try again.") 131 | sleep(0.000000001) 132 | 133 | ################################ 134 | # Check poller for new data # 135 | ################################ 136 | def pollData(self): 137 | 138 | while self.ACTIVE: 139 | sockets = dict(self.POLLER.poll()) 140 | 141 | # Process response to commands sent to MetaTrader 142 | # Receive new market data from MetaTrader 143 | if self.SUB_SOCKET in sockets and sockets[self.SUB_SOCKET] == zmq.POLLIN: 144 | try: 145 | msg = self.SUB_SOCKET.recv_string(zmq.DONTWAIT) 146 | if msg != "": 147 | msgDict = eval(msg) 148 | if self.VERBOSE: 149 | print(msgDict['Data']) 150 | 151 | try: 152 | for incomingSymbol in msgDict['Data'].keys(): 153 | print(incomingSymbol) 154 | # Update Market Data DB 155 | if incomingSymbol not in self.MARKET_DATA_DB.keys(): 156 | self.MARKET_DATA_DB[incomingSymbol] = DataFrame() 157 | 158 | # Append data to DataFrame corresponding to that symbol 159 | self.MARKET_DATA_DB[incomingSymbol] = self.MARKET_DATA_DB[incomingSymbol].append( 160 | DataFrame.from_dict(msgDict['Data'][incomingSymbol], orient='index', 161 | columns=msgDict['MsgType']) 162 | ) 163 | 164 | except Exception as ex: 165 | _exstr = "Exception Type {0}. Args:\n{1!r}" 166 | _msg = _exstr.format(type(ex).__name__, ex.args) 167 | print(_msg) 168 | 169 | except zmq.error.Again: 170 | pass # resource temporarily unavailable, nothing to print 171 | except ValueError: 172 | pass # No data returned, passing iteration. 173 | except UnboundLocalError: 174 | pass # _symbol may sometimes get referenced before being assigned. 175 | 176 | if self.PULL_SOCKET in sockets and sockets[self.PULL_SOCKET] == zmq.POLLIN: 177 | try: 178 | msg = self.PULL_SOCKET.recv_string(zmq.DONTWAIT) 179 | if msg != "": 180 | msgDict = eval(msg) 181 | if self.VERBOSE: 182 | print(msgDict['Data']) 183 | 184 | try: 185 | for incomingSymbol in msgDict['Data'].keys(): 186 | print(incomingSymbol) 187 | # Update Market Data DB 188 | if incomingSymbol not in self.REQUEST_RESPONSE_DB.keys(): 189 | self.REQUEST_RESPONSE_DB[incomingSymbol] = DataFrame() 190 | 191 | # Append data to DataFrame corresponding to that symbol 192 | self.REQUEST_RESPONSE_DB[incomingSymbol] = self.REQUEST_RESPONSE_DB[incomingSymbol].append( 193 | DataFrame.from_dict(msgDict['Data'][incomingSymbol], orient='index', 194 | columns=msgDict['MsgType']) 195 | ) 196 | 197 | except Exception as ex: 198 | _exstr = "Exception Type {0}. Args:\n{1!r}" 199 | _msg = _exstr.format(type(ex).__name__, ex.args) 200 | print(_msg) 201 | 202 | except zmq.error.Again: 203 | pass # resource temporarily unavailable, nothing to print 204 | except ValueError: 205 | pass # No data returned, passing iteration. 206 | except UnboundLocalError: 207 | pass # _symbol may sometimes get referenced before being assigned. 208 | 209 | 210 | 211 | ################################### 212 | # Subscribe to ticks from symbol # 213 | ################################### 214 | def subscribeToSymbolTicks(self, symbol="{"): 215 | 216 | # Subscribe to SYMBOL first. 217 | if symbol != "": 218 | self.SUB_SOCKET.setsockopt_string(zmq.SUBSCRIBE, symbol) 219 | 220 | if self.MARKET_DATA_THREAD is None: 221 | self.MARKET_DATA_THREAD = Thread(target=self.pollData) 222 | self.MARKET_DATA_THREAD.start() 223 | 224 | print("[KERNEL] Subscribed to {} BID/ASK updates. See self.MARKET_DATA_DB.".format(symbol)) 225 | 226 | ############################################# 227 | # Function to generate a random message ID # 228 | ############################################# 229 | def generateMessageID(self): 230 | return random.randint(0, 2000000000) 231 | 232 | 233 | ########################################################################################################### 234 | # CONVENIENCE FUNCTIONS # 235 | ########################################################################################################### 236 | 237 | ########################################################################### 238 | # Function to construct messages for requested rates data from MetaTrader # 239 | ########################################################################### 240 | 241 | def getTicksRange(self,symbol,startTime,endTime): 242 | 243 | messageID = self.generateMessageID() 244 | 245 | msg = "{}|{}|{}|{}|{}|{}|{}".format(messageID, self.CLIENT_ID, 'TICKS', 'RANGE', symbol, startTime, endTime) 246 | print(msg) 247 | # Send via PUSH Socket 248 | self.remoteSend(self.PUSH_SOCKET, msg) 249 | 250 | return messageID 251 | 252 | ########################################################################################################### 253 | # Debugging # 254 | ########################################################################################################### 255 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MetaTrader-Python-Tick-Acquisition 2 | MetaTrader5 to Python Bridge, with millisecond level tick precision. 3 | Based off Darwinex ZeroMQ MT4 bridge. 4 | 5 | # To Do: 6 | - Complete transition to MT5 7 | - Create pipeline to store live ticks into database storage 8 | 9 | 10 | # Completed: 11 | - Added millisecond level tick precision. 12 | - Converting message format to JSON 13 | - Convert tick data to OHLC (candlestick) on pandas and compare with original broker historical data. 14 | Note: MT4/5 seems to be dropping a non-insignificant portion of the ticks. Unfortunately, this seems to be a limitation of MetaTrader itself. 15 | 16 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | // TO DO 2 | 3 | - Add message ID parameter to both client and server messages so that you can verify responses 4 | - Create function to obtain response to a certain message (with unique message ID), with timeout and error print if longer 5 | response time parameter (seconds) 6 | 7 | 8 | --------------------------------------------------------------------------------