From c9cc0423c0f5d2754af0be8d571aeff1bbff6088 Mon Sep 17 00:00:00 2001 From: Nikolai Date: Mon, 17 Jun 2019 14:57:40 +0300 Subject: [PATCH] 1.20 --- Experts/JsonAPI.mq5 | Bin 60678 -> 57338 bytes README.md | 104 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 85 insertions(+), 19 deletions(-) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index bb3f2556c20d5b887bb1086e5915a87cde8822d7..d2d4f1acaba80fff213ce03148de7617b7a83142 100644 GIT binary patch delta 4734 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 None: - """ Send request to server via ZeroMQ System socket """ + """Send request to server via ZeroMQ System socket""" try: self.sys_socket.send_json(data) msg = self.sys_socket.recv_string() @@ -138,7 +140,7 @@ class MTraderAPI: raise zmq.NotDone("Sending request ERROR") def _pull_reply(self): - """ Get reply from server via Data socket with timeout """ + """Get reply from server via Data socket with timeout""" try: msg = self.data_socket.recv_json() except zmq.ZMQError: @@ -146,25 +148,27 @@ class MTraderAPI: return msg def live_socket(self, context=None): + """Connect to socket in a ZMQ context""" try: context = context or zmq.Context.instance() socket = context.socket(zmq.PULL) socket.connect('tcp://{}:{}'.format(self.HOST, self.LIVE_PORT)) except zmq.ZMQError: - raise zmq.ZMQBindError("Binding ports ERROR") + raise zmq.ZMQBindError("Live port connection ERROR") return socket def streaming_socket(self, context=None): + """Connect to socket in a ZMQ context""" try: context = context or zmq.Context.instance() socket = context.socket(zmq.PULL) socket.connect('tcp://{}:{}'.format(self.HOST, self.EVENTS_PORT)) except zmq.ZMQError: - raise zmq.ZMQBindError("Binding ports ERROR") + raise zmq.ZMQBindError("Data port connection ERROR") return socket def construct_and_send(self, **kwargs) -> dict: - """ Construct request dictionary from default """ + """Construct a request dictionary from default and send it to server""" # default dictionary request = { @@ -219,15 +223,22 @@ rep = api.construct_and_send(action="ACCOUNT") print(rep) ``` -Get historical data. `fromDate` should be in timestamp format. There are some issues: +Get historical data. `fromDate` should be in timestamp format. If `toDate` is `None` There are some issues: - MetaTrader keeps historical data in cache. But when you make a request for the first time, MetaTrader downloads data from a broker. This operation can exceed `Data socket` timeout. It depends on your broker. Second request will be handeled quickly. -- Historical data processing code is not optimal. It takes too much time to process more than `50000` candles. Under refactoring now. +- It takes 6-7 seconds to process `50000` M1 candles. It was tested on Windows 10 in Parallels Desktop container with 4 cores and 4GB RAM. So if you need more data there are three ways to handle it. 1) Increase `Data socket` timeout. 2) You can load data partially using `fromDate` and `toDate`. 3) You can use more powerfull hardware. ``` python rep = api.construct_and_send(action="HISTORY", actionType="DATA", symbol="EURUSD", chartTF="M5", fromDate=1555555555) print(rep) ``` + +History data reply example: + +``` +{'data': [[1560782340, 1.12271, 1.12288, 1.12269, 1.12277, 46.0],[1560782400, 1.12278, 1.12299, 1.12276, 1.12297, 43.0],[1560782460, 1.12296, 1.12302, 1.12293, 1.123, 23.0]]} +``` + Buy market order. ``` python @@ -235,7 +246,7 @@ rep = api.construct_and_send(action="TRADE", actionType="ORDER_TYPE_BUY", symbol print(rep) ``` -Sell limit order. Remember to switch SL/TP depending on BUY/SELL, or you will get 'invalid stops' error. +Sell limit order. Remember to switch SL/TP depending on BUY/SELL, or you will get `invalid stops` error. - BUY: SL < price < TP - SELL: SL > price > TP @@ -244,6 +255,7 @@ Sell limit order. Remember to switch SL/TP depending on BUY/SELL, or you will ge rep = api.construct_and_send(action="TRADE", actionType="ORDER_TYPE_SELL_LIMIT", symbol="EURUSD", "volume": 0.1, "price": 1.2, "stoploss": 1.3, "takeprofit": 1.1) print(rep) ``` +## Live data and streaming events Event handler example for `Live socket` and `Data socket`. @@ -276,11 +288,11 @@ def _t_streaming_events(): print(reply) -for i in range(3): +for _ in range(3): t = threading.Thread(target=_t_livedata, daemon=True) t.start() -for i in range(3): +for _ in range(3): t = threading.Thread(target=_t_streaming_events, daemon=True) t.start() @@ -288,7 +300,61 @@ while True: pass ``` + +There are only two variants of `Live socket` data. When everything is ok, the script sends candle data on close: + +``` +{"status":"CONNECTED","data":[1560780120,1.12186,1.12194,1.12186,1.12191,15.00000]} +``` + +If the terminal has lost connection to market: + +``` +{"status":"DISCONNECTED"} +``` + +When the terminal reconnects to market, it sends last closed candle again. So you should update historical data. Make the `action="HISTORY"` request with `fromDate` equal to your last candle timestamp. + +`OnTradeTransaction` function is called when the trade transaction event occurs. `Streaming socket` sends `TRADE_TRANSACTION_REQUEST` data every time it happens. You can create and modify orders/positions in terminal manually and check expert logging tub for better understanding. Also see [MQL5 docs](https://www.mql5.com/en/docs/event_handlers/ontradetransaction). + +`TRADE_TRANSACTION_REQUEST` request data: + +``` +{ + 'action': 'TRADE_ACTION_DEAL', + 'order': 501700843, + 'symbol': 'EURUSD', + 'volume': 0.1, + 'price': 1.12181, + 'stoplimit': 0.0, + 'sl': 1.1, + 'tp': 1.13, + 'deviation': 10, + 'type': 'ORDER_TYPE_BUY', + 'type_filling': 'ORDER_FILLING_FOK', + 'type_time': 'ORDER_TIME_GTC', + 'expiration': 0, + 'comment': None, + 'position': 0, + 'position_by': 0 +} +``` +`TRADE_TRANSACTION_REQUEST` result data: + +``` +{ + 'retcode': 10009, + 'result': 'TRADE_RETCODE_DONE', + 'deal': 501700843, + 'order': 501700843, + 'volume': 0.1, + 'price': 1.12181, + 'comment': None, + 'request_id': 8, + 'retcode_external': 0 +} +``` + ## License This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See `LICENSE` for more information. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See `LICENSE` for more information. \ No newline at end of file