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("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("Data port connection ERROR") return socket
def __init__(self, host=None, debug=None): self.HOST = host or 'localhost' self.SYS_PORT = 15555 # REP/REQ port self.DATA_PORT = 15556 # PUSH/PULL port self.LIVE_PORT = 15557 # PUSH/PULL port self.EVENTS_PORT = 15558 # PUSH/PULL port self.INDICATOR_DATA_PORT = 15559 # REP/REQ port self.CHART_DATA_PORT = 15560 # PUSH self.debug = debug or True # ZeroMQ timeout in seconds sys_timeout = 100 data_timeout = 1000 # initialise ZMQ context context = zmq.Context() # connect to server sockets try: self.sys_socket = context.socket(zmq.REQ) # set port timeout self.sys_socket.RCVTIMEO = sys_timeout * 1000 self.sys_socket.connect( 'tcp://{}:{}'.format(self.HOST, self.SYS_PORT)) self.data_socket = context.socket(zmq.PULL) # set port timeout self.data_socket.RCVTIMEO = data_timeout * 1000 self.data_socket.connect( 'tcp://{}:{}'.format(self.HOST, self.DATA_PORT)) self.indicator_data_socket = context.socket(zmq.PULL) # set port timeout self.indicator_data_socket.RCVTIMEO = data_timeout * 1000 self.indicator_data_socket.connect( "tcp://{}:{}".format(self.HOST, self.INDICATOR_DATA_PORT) ) self.chart_data_socket = context.socket(zmq.PUSH) # set port timeout # TODO check if port is listening and error handling self.chart_data_socket.connect( "tcp://{}:{}".format(self.HOST, self.CHART_DATA_PORT) ) except zmq.ZMQError: raise zmq.ZMQBindError("Binding ports ERROR") except KeyboardInterrupt: self.sys_socket.close() self.sys_socket.term() self.data_socket.close() self.data_socket.term() self.indicator_data_socket.close() self.indicator_data_socket.term() self.chart_data_socket.close() self.chart_data_socket.term() pass
def __init__(self, *args, **kwargs): self.HOST = kwargs["host"] self.SYS_PORT = 15555 # REP/REQ port self.DATA_PORT = 15556 # PUSH/PULL port self.LIVE_PORT = 15557 # PUSH/PULL port self.EVENTS_PORT = 15558 # PUSH/PULL port self.INDICATOR_DATA_PORT = 15559 # REP/REQ port self.CHART_DATA_PORT = 15560 # PUSH port self.debug = kwargs["debug"] # ZeroMQ timeout in seconds sys_timeout = 1 data_timeout = kwargs["datatimeout"] # initialise ZMQ context context = zmq.Context() # connect to server sockets try: self.sys_socket = context.socket(zmq.REQ) # set port timeout self.sys_socket.RCVTIMEO = sys_timeout * 1000 self.sys_socket.set_hwm(1000) self.sys_socket.connect("tcp://{}:{}".format( self.HOST, self.SYS_PORT)) self.data_socket = context.socket(zmq.PULL) # set port timeout self.data_socket.RCVTIMEO = data_timeout * 1000 self.data_socket.set_hwm(1000) self.data_socket.connect("tcp://{}:{}".format( self.HOST, self.DATA_PORT)) self.indicator_data_socket = context.socket(zmq.PULL) # set port timeout self.indicator_data_socket.RCVTIMEO = data_timeout * 1000 self.indicator_data_socket.connect("tcp://{}:{}".format( self.HOST, self.INDICATOR_DATA_PORT)) self.chart_data_socket = context.socket(zmq.PUSH) # set port timeout # TODO check if port is listening and error handling self.chart_data_socket.connect("tcp://{}:{}".format( self.HOST, self.CHART_DATA_PORT)) except zmq.ZMQError: raise zmq.ZMQBindError("Binding ports ERROR")
def bind_to_random_port(socket, addr, min_port=49152, max_port=65536, max_tries=100): "We can't just use the zmq.Socket.bind_to_random_port, as we wan't to set the identity before binding" for i in range(max_tries): try: port = random.randrange(min_port, max_port) socket.identity = bytes(('%s:%s' % (addr, port)).encode("utf-8")) socket.bind(bytes(('tcp://*:%s' % port).encode("utf-8"))) # socket.bind('%s:%s' % (addr, port)) except zmq.ZMQError as exception: en = exception.errno if en == zmq.EADDRINUSE: continue else: raise else: return socket.identity raise zmq.ZMQBindError("Could not bind socket to random port.")
def bind_ports(self, ip, ports): #{ """Try to bind a socket to the first available tcp port. The ports argument can either be an integer valued port or a list of ports to try. This attempts the following logic: * If ports==0, we bind to a random port. * If ports > 0, we bind to port. * If ports is a list, we bind to the first free port in that list. In all cases we save the eventual url that we bind to. This raises zmq.ZMQBindError if no free port can be found. """ if isinstance(ports, int): ports = [ports] for p in ports: try: if p == 0: port = self.socket.bind_to_random_port("tcp://%s" % ip) else: self.socket.bind("tcp://%s:%i" % (ip, p)) port = p except zmq.ZMQError: # bind raises this if the port is not free continue except zmq.ZMQBindError: # bind_to_random_port raises this if no port could be found continue else: break else: raise zmq.ZMQBindError('Could not find an available port') url = 'tcp://%s:%i' % (ip, port) self.bound.add(url) self._ready = True return port
class MT5Store(with_metaclass(MetaSingleton, object)): """ Backtrader <-> Metatrader5 API. The MT5store class uses three ZeroMQ sockets to communicate with the server. 1. (sys) First one sends commands to server. It receives back "ok" if the command was accomplished. 2. (data) Second one is used to receive data from server. 3. (live) Third socket is waiting for live data from the server on candle close. """ HOST = 'localhost' SYS_PORT = 15555 # REP/REQ port DATA_PORT = 15556 # PUSH/PULL port LIVE_PORT = 15557 # PUSH/PULL port def __init__(self, currency, debug=False): self.currency = currency self.debug = debug self._cash = 0 self._value = 0 # ZeroMQ poller timeout in seconds # http://zguide.zeromq.org/py:mspoller poller_timeout = 10 # initialise ZMQ context context = zmq.Context() # connect to server sockets try: sys_socket = context.socket(zmq.REQ) sys_socket.connect('tcp://{}:{}'.format(HOST, SYS_PORT)) data_socket = context.socket(zmq.PULL) data_socket.connect('tcp://{}:{}'.format(HOST, DATA_PORT)) live_socket = context.socket(zmq.PULL) live_socket.connect('tcp://{}:{}'.format(HOST, LIVE_PORT)) except zmq.error as err: raise zmq.ZMQBindError("Error while ports binding...", err) # Supported granularities _GRANULARITIES = { (bt.TimeFrame.Minutes, 1): '1m', (bt.TimeFrame.Minutes, 5): '5m', (bt.TimeFrame.Minutes, 15): '15m', (bt.TimeFrame.Minutes, 30): '30m', (bt.TimeFrame.Minutes, 60): '1h', (bt.TimeFrame.Minutes, 120): '2h', (bt.TimeFrame.Minutes, 180): '3h', (bt.TimeFrame.Minutes, 240): '4h', (bt.TimeFrame.Minutes, 360): '6h', (bt.TimeFrame.Minutes, 480): '8h', (bt.TimeFrame.Minutes, 720): '12h', (bt.TimeFrame.Days, 1): '1d', (bt.TimeFrame.Weeks, 1): '1w', (bt.TimeFrame.Months, 1): '1M', } BrokerCls = None # broker class will auto register DataCls = None # data class will auto register def _send_request(self, data) -> None: """ Send request to ZeroMQ system socket """ try: self.sys_socket.send_json(data) msg = self.sys_socket.recv_string() assert msg == 'OK', 'Something wrong on server side...' except: raise zmq.ZMQError("Something wrong while sending request...") def _pull_reply(self): """ Get reply from data socket with timeout """ poller = zmq.Poller() poller.register(self.data_socket, zmq.POLLIN) # timeout in milliseconds if poller.poll(self.poller_timeout * 1000): msg = self.data_socket.recv_json() else: raise zmq.NotDone('Timeout was reached...') return msg def _live_data(self) -> list: """ Catch candles from server""" msg = self.live_socket.recv_json() return msg def _construct_and_send(self, **kwargs) -> dict: """ Construct JSON request dictionary from default """ # default request dict request = { "action": None, "actionType": None, "symbol": None, "chartTF": None, "startTime": None, "id": None, "price": None, "volume": None, "stoploss": None, "takeprofit": None, "deviation": None } # update dict values if exist for key, value in kwargs.items(): if key in request: request[key] = value else: raise KeyError('Strange key passed in kwargs...') self._send_request(request) reply = self._pull_reply() return reply def _check_sockets(self): """ Check sockets connection """ request = self._create_dict(action="CHECK") self.sys_socket.send_json(request) sys = self.sys_socket.recv_json() data = self.data_socket.recv_json() live = self.live_socket.recv_json() print(sys, data, live) assert sys == 'OK', 'System socket is broken...' assert data == 'OK', 'Data socket is broken...' assert live == 'OK', 'Live socket is broken...' @classmethod def getdata(cls, *args, **kwargs): '''Returns ``DataCls`` with args, kwargs''' return cls.DataCls(*args, **kwargs) @classmethod def getbroker(cls, *args, **kwargs): '''Returns broker with *args, **kwargs from registered ``BrokerCls``''' return cls.BrokerCls(*args, **kwargs) def get_granularity(self, timeframe, compression): granularity = self._GRANULARITIES.get((timeframe, compression)) if granularity is None: raise ValueError("Metatrader 5 doesn't support fetching OHLCV " "data for time frame %s, comression %s" % \ (bt.TimeFrame.getname(timeframe), compression)) return granularity def get_balance(self): balance = self._construct_and_send(action="BALANCE") if self.debug: print('Fetching balance: {}, Free: {}.'.format( balance['balance'], balance['equity'])) self._cash = balance['equity'] self._value = balance['balance'] return balance def getposition(self): #return self._value try: positions = self._construct_and_send(action="POSITIONS_INFO") except: return None poslist = positions.get('positions', []) return poslist def create_order(self, symbol, order_type, side, amount, price=None, params=None): assert side in ['buy', 'sell'], 'Something wrong with order side...' # Convert Backtrader order names to MQL5 style # I use C style names as in the MQL5 script if order_type == 'market': actionType = 'ORDER_TYPE_BUY' if side == 'buy' else 'ORDER_TYPE_SELL' elif order_type == 'limit': actionType = 'ORDER_TYPE_BUY_LIMIT' if side == 'buy' else 'ORDER_TYPE_SELL_LIMIT' elif order_type == 'stop': actionType = 'ORDER_TYPE_BUY_STOP' if side == 'buy' else 'ORDER_TYPE_SELL_STOP' elif order_type == 'stop limit': actionType = 'ORDER_TYPE_BUY_STOP_LIMIT' if side == 'buy' else 'ORDER_TYPE_SELL_STOP_LIMIT' else: raise ValueError('Wrong order_type value...') confirmation = self._construct_and_send(action="TRADE", actionType=actionType, symbol=symbol, price=price, volume=amount, stoploss=None, takeprofit=None) if self.debug: print('Order confirmation: {}.'.format(confirmation)) # returns the order conformation return confirmation def cancel_order(self, order_id, symbol): confirmation = self._construct_and_send(action="TRADE", actionType="CANCEL_ORDER", id=order_id, symbol=symbol) return confirmation def fetch_trades(self): return self._construct_and_send(action="FETCH_TRADES") def fetch_ohlcv(self, symbol, timeframe, since): candles = self._construct_and_send(action="HISTORY", symbol=symbol, chartTF=timeframe, startTime=since) if self.debug: print('Fetching: {}, TF: {}, Since: {}'.format( symbol, timeframe, since)) return candles def fetch_open_orders(self): orders = self._construct_and_send(action="ORDERS_INFO") orders_list = orders.get('orders', []) if self.debug: print('Open orders: {}.'.format(orders)) return orders_list