├── .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 |
5 |
6 |
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 |
--------------------------------------------------------------------------------