class RegistrationOnlyEmitter(object): def __init__(self): self.emitter = EventEmitter() def on(self, event, f): allow_events_to_execute = True if allow_events_to_execute: # don't filter events, just run them all print "Event: "+str(event) self.emitter.on(event, f) else: # filter to just the registration events, # preventing them from actually executing if event in [ 'register_intent', 'register_vocab', 'recognizer_loop:utterance' ]: print "Event: " + str(event) self.emitter.on(event, f) def emit(self, event, *args, **kwargs): event_name = event.type self.emitter.emit(event_name, event, *args, **kwargs) def once(self, event, f): self.emitter.once(event, f) def remove(self, event_name, func): pass
class InterceptEmitter(object): """ This class intercepts and allows emitting events between the skill_tester and the skill being tested. When a test is running emitted communication is intercepted for analysis """ def __init__(self): self.emitter = EventEmitter() self.q = None def on(self, event, f): # run all events print("Event: ", event) self.emitter.on(event, f) def emit(self, event, *args, **kwargs): event_name = event.type if self.q: self.q.put(event) self.emitter.emit(event_name, event, *args, **kwargs) def once(self, event, f): self.emitter.once(event, f) def remove(self, event_name, func): pass def remove_all_listeners(self, event_name): pass
class RegistrationOnlyEmitter(object): def __init__(self): self.emitter = EventEmitter() def on(self, event, f): allow_events_to_execute = True if allow_events_to_execute: # don't filter events, just run them all print "Event: " + str(event) self.emitter.on(event, f) else: # filter to just the registration events, # preventing them from actually executing if event in [ 'register_intent', 'register_vocab', 'recognizer_loop:utterance' ]: print "Event: " + str(event) self.emitter.on(event, f) def emit(self, event, *args, **kwargs): event_name = event.type self.emitter.emit(event_name, event, *args, **kwargs) def once(self, event, f): self.emitter.once(event, f) def remove(self, event_name, func): pass
class InterceptEmitter(object): """ This class intercepts and allows emitting events between the skill_tester and the skill being tested. When a test is running emitted communication is intercepted for analysis """ def __init__(self): self.emitter = EventEmitter() self.q = None def on(self, event, f): # run all events print("Event: ", event) self.emitter.on(event, f) def emit(self, event, *args, **kwargs): event_name = event.msg_type if self.q: self.q.put(event) self.emitter.emit(event_name, event, *args, **kwargs) def wait_for_response(self, event, reply_type=None, *args, **kwargs): """Simple single thread implementation of wait_for_response.""" message_type = reply_type or event.msg_type + '.response' response = None def response_handler(msg): nonlocal response response = msg self.emitter.once(message_type, response_handler) self.emitter.emit(event.msg_type, event) return response def once(self, event, f): self.emitter.once(event, f) def remove(self, event_name, func): pass def remove_all_listeners(self, event_name): pass
class MockWebsocket(): def __init__(self): self.events = EventEmitter(scheduler=asyncio.ensure_future) self.saved_items = [] self.emitted_items = [] def on(self, *args, **kwargs): self.events.on(*args, **kwargs) def once(self, *args, **kwargs): self.events.once(*args, **kwargs) def _emit(self, event, *args, **kwargs): # save published items for testing self.emitted_items += [{ 'time': int(round(time.time() * 1000)), 'data': { 'event': event, 'args': args, 'kwargs': kwargs } }] self.events.emit(event, *args, **kwargs) def remove_all_listeners(self, *args, **kwargs): self.events.remove_all_listeners(*args, **kwargs) def cancel_order(self, *args, **kawargs): pass def submit_order(self, *args, **kawargs): pass def get_emitted_items(self): return self.emitted_items def get_last_emitted_item(self): return self.emitted_items[-1:][0] def get_emitted_items_count(self): return len(self.emitted_items)
def test_once(): """Test that `once()` method works propers. """ # very similar to "test_emit" but also makes sure that the event # gets removed afterwards call_me = Mock() ee = EventEmitter() def once_handler(data): assert data == 'emitter is emitted!' call_me() # Tests to make sure that after event is emitted that it's gone. ee.once('event', once_handler) ee.emit('event', 'emitter is emitted!') call_me.assert_called_once() assert ee._events['event'] == OrderedDict()
def test_once_removal(): """Removal of once functions works """ ee = EventEmitter() def once_handler(data): pass handle = ee.once('event', once_handler) assert handle == once_handler ee.remove_listener('event', handle) assert ee._events['event'] == OrderedDict()
class WebsocketClient(object): def __init__(self, host=None, port=None, route=None, ssl=None): config = Configuration.get().get("websocket") host = host or config.get("host") port = port or config.get("port") route = route or config.get("route") ssl = ssl or config.get("ssl") validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.url = WebsocketClient.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 @staticmethod def build_url(host, port, route, ssl): scheme = "wss" if ssl else "ws" return scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: LOG.error(repr(e)) LOG.warning("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async(self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if (not self.client or not self.client.sock or not self.client.sock.connected): return if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.client.run_forever() def close(self): self.client.close()
class WebsocketClient(object): def __init__(self, host=None, port=None, route=None, ssl=None): config = Configuration.get().get("websocket") host = host or config.get("host") port = port or config.get("port") route = route or config.get("route") ssl = ssl or config.get("ssl") validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.url = WebsocketClient.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 self.connected_event = Event() self.started_running = False @staticmethod def build_url(host, port, route, ssl): scheme = "wss" if ssl else "ws" return scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.connected_event.set() self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: LOG.error(repr(e)) LOG.warning("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async( self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if not self.connected_event.wait(10): if not self.started_running: raise ValueError('You must execute run_forever() ' 'before emitting messages') self.connected_event.wait() if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def wait_for_response(self, message, reply_type=None, timeout=None): """Send a message and wait for a response. Args: message (Message): message to send reply_type (str): the message type of the expected reply. Defaults to "<message.type>.response". timeout: seconds to wait before timeout, defaults to 3 Returns: The received message or None if the response timed out """ response = [] def handler(message): """Receive response data.""" response.append(message) # Setup response handler self.once(reply_type or message.type + '.response', handler) # Send request self.emit(message) # Wait for response start_time = monotonic.monotonic() while len(response) == 0: time.sleep(0.2) if monotonic.monotonic() - start_time > (timeout or 3.0): try: self.remove(reply_type, handler) except (ValueError, KeyError): # ValueError occurs on pyee 1.0.1 removing handlers # registered with once. # KeyError may theoretically occur if the event occurs as # the handler is removbed pass return None return response[0] def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.started_running = True self.client.run_forever() def close(self): self.client.close() self.connected_event.clear()
class GenericWebsocket: """ Websocket object used to contain the base functionality of a websocket. Inlcudes an event emitter and a standard websocket client. """ def __init__(self, host, logLevel='INFO', loop=None): self.host = host self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel) self.loop = loop or asyncio.get_event_loop() self.events = EventEmitter( scheduler=asyncio.ensure_future, loop=self.loop) self.ws = None def run(self): """ Run the websocket connection indefinitely """ self.loop.run_until_complete(self._main(self.host)) def get_task_executable(self): """ Get the run indefinitely asyncio task """ return self._main(self.host) async def _main(self, host): async with websockets.connect(host) as websocket: self.ws = websocket self.logger.info("Wesocket connectedt to {}".format(self.host)) while True: await asyncio.sleep(0) message = await websocket.recv() await self.on_message(message) def remove_all_listeners(self, event): """ Remove all listeners from event emitter """ self.events.remove_all_listeners(event) def on(self, event, func=None): """ Add a new event to the event emitter """ if not func: return self.events.on(event) self.events.on(event, func) def once(self, event, func=None): """ Add a new event to only fire once to the event emitter """ if not func: return self.events.once(event) self.events.once(event, func) def _emit(self, event, *args, **kwargs): self.events.emit(event, *args, **kwargs) async def on_error(self, error): """ On websocket error print and fire event """ self.logger.error(error) self.events.emit('error', error) async def on_close(self): """ On websocket close print and fire event """ self.logger.info("Websocket closed.") await self.ws.close() self._emit('done') async def on_open(self): """ On websocket open """ pass async def on_message(self, message): """ On websocket message """ pass
class Strategy(PositionManager): """ This class is the base of the HF framework and is used to help easily maintain position on the market. This class also exposes function from the PositionManager which are used to open/update/close orders. An event emitter is available which triggers on price updates and positions updates, here is a full list of the available events: *Note: price udates occur whenever that is a new candle or a new public trade has been matched on the orderbook @event on_error: an error has occured @event on_enter: there is no open position and the price is updated @event on_update: there is a price update @event on_update_long: you have a long position open and the price has been updated @event on_update_short: you have a short position open and the price has been updated @event on_order_fill: a new order is filled @event on_position_updated: you have a position open and the price has been updated @event on_position_close: you had a position open and it has now been closed @event on_position_stop_reached: your open position has just reached its stop price @event on_position_target_reached: your open position has just reached its target price """ ExchangeType = ExchangeType() def __init__(self, backtesting=False, symbol='tBTCUSD', indicators={}, logLevel='INFO', exchange_type=ExchangeType.EXCHANGE): self.exchange_type = exchange_type self.marketData = {} self.positions = {} self.lastPrice = {} self.closedPositions = [] self.is_ready = False self.indicators = indicators self.candle_price_key = 'close' self.backtesting = backtesting self.symbol = symbol self.events = EventEmitter(scheduler=asyncio.ensure_future) # initialise custom logger self.logLevel = logLevel self.logger = CustomLogger('HFStrategy', logLevel=logLevel) super(Strategy, self).__init__() async def _emit(self, event, *args, **kwargs): await self._execute_events(event, *args, **kwargs) async def _execute_events(self, event, *args, **kwargs): # get all coroutines that are listening to the event listeners = self.events.listeners(event) # execute them now to avoid pyee scheduling them await asyncio.gather(*[f(*args, **kwargs) for f in listeners]) def _add_indicator_data(self, dataType, data): for key in self.indicators: i = self.indicators[key] dt = i.get_data_type() dk = i.get_data_key() if dt == '*' or dt == dataType: if dk == '*': i.add(data) else: d = data.get(dk) if d: i.add(d) def _update_indicator_data(self, dataType, data): for key in self.indicators: i = self.indicators[key] dt = i.get_data_type() dk = i.get_data_key() if dt == '*' or dt == dataType: if dk == '*': i.update(data) else: d = data.get(dk) if d: i.update(d) def _add_candle_data(self, candle): dataKey = candleMarketDataKey(candle) if dataKey in self.marketData: self.marketData[dataKey].append(candle) else: self.marketData[dataKey] = [] def _update_candle_data(self, candle): dataKey = candleMarketDataKey(candle) if dataKey in self.marketData: self.marketData[dataKey][-1] = candle else: self.marketData[dataKey] = [candle] ############################# # Private events # ############################# async def _process_new_candle(self, candle): self._add_indicator_data('candle', candle) if self.is_indicators_ready(): price = candle[self.candle_price_key] pu = PriceUpdate(price, candle['symbol'], candle['mts'], PriceUpdate.CANDLE, candle=candle) pu.set_indicator_values(self.get_indicator_values()) await self._process_price_update(pu) async def _process_new_trade(self, trade): price = trade['price'] self._update_indicator_data('trade', trade) if self.is_indicators_ready(): pu = PriceUpdate(price, trade['symbol'], trade['mts'], PriceUpdate.TRADE, trade=trade) pu.set_indicator_values(self.get_indicator_values()) await self._process_price_update(pu) def _process_new_seed_candle(self, candle): self._add_indicator_data('candle', candle) candle['iv'] = self.get_indicator_values() self._add_candle_data(candle) def _process_new_seed_trade(self, trade): self._update_indicator_data('trade', trade) async def _process_price_update(self, update): self.lastPrice[update.symbol] = update # TODO: Handle stops/targets if update.symbol not in self.positions: await self._execute_events(Events.ON_ENTER, update) else: symPosition = self.positions[update.symbol] amount = symPosition.amount symPosition.update_with_price(update.price) await self._execute_events(Events.ON_UPDATE, update, symPosition) # Check if stop or target price has been reached if symPosition.has_reached_stop(update): self.logger.info( "Stop price reached for position: {}".format(symPosition)) if symPosition.exit_order.is_stop_market(): await self.close_position_market(mtsCreate=update.mts, tag="Stop price reached") return await self._execute_events( Events.ON_POSITION_STOP_REACHED, update, symPosition) if symPosition.has_reached_target(update): self.logger.info( "Target price reached for position: {}".format( symPosition)) if symPosition.exit_order.is_target_market(): await self.close_position_market( mtsCreate=update.mts, tag="Target price reached") return await self._execute_events( Events.ON_POSITION_TARGET_REACHED, update, symPosition) if amount > 0: await self._execute_events(Events.ON_UPDATE_LONG, update, symPosition) else: await self._execute_events(Events.ON_UPDATE_SHORT, update, symPosition) def _connected(self): # check if there are any positions open if len(self.positions.keys()) > 0: self.logger.info( "New connection detected, resetting strategy positions.") self._reset() async def _ready(self, *args, **kwargs): self.is_ready = True await self._execute_events(Events.ON_READY) def _reset(self): """ Resets the state of the strategy to have no open positions. This is called by default when the websocket disconnects and the dead_man_switch kicks in. """ self.logger.info("Reset called. Moving all positions to closed.") # set all positions to closed for key in self.positions.keys(): self.positions[key].close() self.closedPositions += [self.positions[key]] self.positions = {} def _add_position(self, position): self.positions[position.symbol] = position def _remove_position(self, position): self.logger.debug("Archiving closed position {}".format(position)) self.closedPositions += [position] del self.positions[position.symbol] ############################ # Public Functions # ############################ def get_last_price_update(self, symbol): """ Get the last received price update @param symbol: string currency pair i.e 'tBTCUSD' @return PriceUpdate """ update = self.lastPrice.get(symbol, None) return update def get_position(self, symbol): """ Get the position of the given symbol. If it is not open then return None @param symbol: string currency pair i.e 'tBTCUSD' @return Position """ return self.positions.get(symbol) def get_indicator_values(self): values = {} for key in self.indicators: values[key] = self.indicators[key].v() return values def is_indicators_ready(self): for key in self.indicators: if not self.indicators[key].ready(): return False return True def on(self, event, func=None): """ Subscribe to the given event func can be either an asyncio coroutine or a function. @param event: string event name @param func: called when event name emitted """ if not func: return self.events.on(event) self.events.on(event, func) def once(self, event, func=None): """ Subscribe to the given event but only fire once. func can be either an asyncio coroutine or a function @param event: string event name @param func: called when event name emitted """ if not func: return self.events.once(event) self.events.once(event, func) def get_indicators(self): """ Get all indicatios @return dict """ return self.indicators def is_backtesting(self): """ Get the mode of the strategy. @return True if in backtesting mode """ return self.backtesting ############################ # Event Hooks # ############################ def on_error(self, func=None): """ Subscribe to the on error event This event is fired whenever an error occurs from either the websocket or the strategy class. func can be either an asyncio coroutine or a function. @event Exception @param func: called when an error is emitted """ if not func: return self.events.on(Events.ERROR) self.events.on(Events.ERROR, func) def on_ready(self, func=None): """ Subscribe to the on ready event This event is fired whenever the strategy is ready to begin execution. This could be triggered either by webscoket authentication, backtest websocket connection or backtest data loaded. @param func: called when the strategy is ready """ if not func: return self.events.on(Events.ON_READY) self.events.on(Events.ON_READY, func) def on_enter(self, func=None): """ Subscribe to the on enter event This event is fired whenever a price update is received but there are no open positions. Once a position is opened then this event will stop being called once again until all positions are closed. func can be either an asyncio coroutine or a function. @event PriceUpdate @param func: called when a price update is emitted """ if not func: return self.events.on(Events.ON_ENTER) self.events.on(Events.ON_ENTER, func) def on_update(self, func=None): """ Subscribe to the on update event This event is fired whenever a price update is received. func can be either an asyncio coroutine or a function. @event PriceUpdate, Position @param func: called when update event emitted """ if not func: return self.events.on(Events.ON_UPDATE) self.events.on(Events.ON_UPDATE, func) def on_update_long(self, func=None): """ Subscribe to the on update long event This event fires whenever there is a price update and there is an open long position. func can be either an asyncio coroutine or a function. @event PriceUpdate, Position @param func: called when update long emitted """ if not func: return self.events.on(Events.ON_UPDATE_LONG) self.events.on(Events.ON_UPDATE_LONG, func) def on_update_short(self, func=None): """ Subscribe to the on update short event This event fires whenever there is a price update and there is an open short position. func can be either an asyncio coroutine or a function. @event PriceUpdate, Position @param func: called when update short emitted """ if not func: return self.events.on(Events.ON_UPDATE_SHORT) self.events.on(Events.ON_UPDATE_SHORT, func) def on_order_fill(self, func=None): """ Subscribe to the on order fill event This event firest whenever a submitted order has been filled. func can be either an asyncio coroutine or a function. @event Order @param func: called when order fill emitted """ if not func: return self.events.on(Events.ON_ORDER_FILL) self.events.on(Events.ON_ORDER_FILL, func) def on_position_update(self, func=None): """ Subscribe to the on position update event This event fired whenever the position is updated with a new order. func can be either an asyncio coroutine or a function. @event Position @param func: called when position update emitted """ if not func: return self.events.on(Events.ON_POSITION_UPDATE) self.events.on(Events.ON_POSITION_UPDATE, func) def on_position_close(self, func=None): """ Subscribe to the on position close event This event is fired whenever an open position is closed. func can be either an asyncio coroutine or a function. @event Position @param func: called when position close emitted """ if not func: return self.events.on(Events.ON_POSITION_CLOSE) self.events.on(Events.ON_POSITION_CLOSE, func) def on_position_stop_reached(self, func=None): """ Subscribe to the on position stop reached event This event is fired whenever an open position reaches its specified stop price. Please be aware that the closing of the position will have already been handled at the time of the event being fired. func can be either an asyncio coroutine or a function. @event PriceUpdate, Position @param func: called when position stop reached emitted """ if not func: return self.events.on(Events.ON_POSITION_STOP_REACHED) self.events.on(Events.ON_POSITION_STOP_REACHED, func) def on_position_target_reached(self, func=None): """ Subscribe to the on position target reached event This event is fired whenever an open position reaches its specified target price. Please be aware that the closing of the position will have already been handled at the time of the event being fired. func can be either an asyncio coroutine or a function. @event PriceUpdate, Position @param func: called when position target reached emitted """ if not func: return self.events.on(Events.ON_POSITION_TARGET_REACHED) self.events.on(Events.ON_POSITION_TARGET_REACHED, func)
class WebsocketClient(object): def __init__(self, url): self.url = url self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: print(repr(e)) print("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async(self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if (not self.client or not self.client.sock or not self.client.sock.connected): return if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.client.run_forever() def close(self): self.client.close()
class WebsocketClient: def __init__(self, host=None, port=None, route=None, ssl=None): config = Configuration.get().get("websocket") host = host or config.get("host") port = port or config.get("port") route = route or config.get("route") ssl = ssl or config.get("ssl") validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.url = WebsocketClient.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 self.connected_event = Event() self.started_running = False @staticmethod def build_url(host, port, route, ssl): scheme = "wss" if ssl else "ws" return scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.connected_event.set() self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): """ On error start trying to reconnect to the websocket. """ if isinstance(error, WebSocketConnectionClosedException): LOG.warning('Could not send message because connection has closed') else: LOG.exception('=== ' + repr(error) + ' ===') try: self.emitter.emit('error', error) if self.client.keep_running: self.client.close() except Exception as e: LOG.error('Exception closing websocket: ' + repr(e)) LOG.warning("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) try: self.emitter.emit('reconnecting') self.client = self.create_client() self.run_forever() except WebSocketException: pass def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async( self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if not self.connected_event.wait(10): if not self.started_running: raise ValueError('You must execute run_forever() ' 'before emitting messages') self.connected_event.wait() try: if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) except WebSocketConnectionClosedException: LOG.warning('Could not send {} message because connection ' 'has been closed'.format(message.type)) def wait_for_response(self, message, reply_type=None, timeout=None): """Send a message and wait for a response. Args: message (Message): message to send reply_type (str): the message type of the expected reply. Defaults to "<message.type>.response". timeout: seconds to wait before timeout, defaults to 3 Returns: The received message or None if the response timed out """ response = [] def handler(message): """Receive response data.""" response.append(message) # Setup response handler self.once(reply_type or message.type + '.response', handler) # Send request self.emit(message) # Wait for response start_time = time.monotonic() while len(response) == 0: time.sleep(0.2) if time.monotonic() - start_time > (timeout or 3.0): try: self.remove(reply_type, handler) except (ValueError, KeyError): # ValueError occurs on pyee 1.0.1 removing handlers # registered with once. # KeyError may theoretically occur if the event occurs as # the handler is removed pass return None return response[0] def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): try: if event_name in self.emitter._events: LOG.debug("Removing found '"+str(event_name)+"'") else: LOG.debug("Not able to find '"+str(event_name)+"'") self.emitter.remove_listener(event_name, func) except ValueError as e: LOG.warning('Failed to remove event {}: {}'.format(event_name, str(func))) for line in traceback.format_stack(): LOG.warning(line.strip()) if event_name in self.emitter._events: LOG.debug("Removing found '"+str(event_name)+"'") else: LOG.debug("Not able to find '"+str(event_name)+"'") LOG.warning("Existing events: " + str(self.emitter._events)) for evt in self.emitter._events: LOG.warning(" "+str(evt)) LOG.warning(" "+str(self.emitter._events[evt])) if event_name in self.emitter._events: LOG.debug("Removing found '"+str(event_name)+"'") else: LOG.debug("Not able to find '"+str(event_name)+"'") LOG.warning('----- End dump -----') def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.started_running = True self.client.run_forever() def close(self): self.client.close() self.connected_event.clear()
class WebsocketClient(object): def __init__(self, host=config.get("host"), port=config.get("port"), route=config.get("route"), ssl=config.get("ssl")): validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 def build_url(self, host, port, route, ssl): scheme = "wss" if ssl else "ws" self.url = scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.emitter.emit("open") def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: LOG.error(repr(e)) LOG.warn("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async(self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if (not self.client or not self.client.sock or not self.client.sock.connected): return if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def run_forever(self): self.client.run_forever() def close(self): self.client.close()
class WebsocketClient(object): def __init__(self, host=config.get("Websocket", "host"), port=int(config.get("Websocket", "port")), route=config.get("Websocket", "route"), ssl=config.getboolean("Websocket", "ssl")): validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 def build_url(self, host, port, route, ssl): scheme = "wss" if ssl else "ws" self.url = scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.emitter.emit("open") def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: LOG.error(repr(e)) LOG.warn("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async( self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if (not self.client or not self.client.sock or not self.client.sock.connected): return if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def run_forever(self): self.client.run_forever() def close(self): self.client.close()
class Strategy(PositionManager): def __init__(self, backtesting = False, symbol='tBTCUSD', indicators={}, logLevel='INFO'): self.marketData = {} self.positions = {} self.lastPrice = {} self.closedPositions = [] self.indicators = indicators self.candle_price_key = 'close' self.backtesting = backtesting self.symbol = symbol self.events = EventEmitter(scheduler=asyncio.ensure_future) # initialise custom logger self.logger = CustomLogger('HFStrategy', logLevel=logLevel) super(Strategy, self).__init__() async def _emit(self, event, *args, **kwargs): await self._execute_events(event, *args, **kwargs) async def _execute_events(self, event, *args, **kwargs): # get all coroutines that are listening to the event listeners = self.events.listeners(event) # execute them now to avoid pyee scheduling them await asyncio.gather(*[f(*args, **kwargs) for f in listeners]) def _add_indicator_data(self, dataType, data): for key in self.indicators: i = self.indicators[key] dt = i.get_data_type() dk = i.get_data_key() if dt == '*' or dt == dataType: if dk == '*': i.add(data) else: i.add(data[dk]) def _update_indicator_data(self, dataType, data): for key in self.indicators: i = self.indicators[key] dt = i.get_data_type() dk = i.get_data_key() if dt == '*' or dt == dataType: t = type(data) if t is float or t is int: if math.isfinite(data): i.update(data) return if dk == '*': i.update(data) else: i.update(data[dk]) def _add_candle_data(self, candle): dataKey = candleMarketDataKey(candle) if dataKey in self.marketData: self.marketData[dataKey].append(candle) else: self.marketData[dataKey] = [] def _update_candle_data(self, candle): dataKey = candleMarketDataKey(candle) if dataKey in self.marketData: self.marketData[dataKey][-1] = candle else: self.marketData[dataKey] = [candle] ############################# # Private events # ############################# async def _process_new_candle(self, candle): self._add_indicator_data('candle', candle) if self.is_indicators_ready(): price = candle[self.candle_price_key] pu = PriceUpdate( price, candle['symbol'], candle['mts'], PriceUpdate.CANDLE, candle=candle) pu.set_indicator_values(self.get_indicator_values()) await self._process_price_update(pu) async def _process_new_trade(self, trade): price = trade['price'] self._update_indicator_data('trade', price) if self.is_indicators_ready(): pu = PriceUpdate( price, trade['symbol'], trade['mts'], PriceUpdate.TRADE, trade=trade) pu.set_indicator_values(self.get_indicator_values()) await self._process_price_update(pu) def _process_new_seed_candle(self, candle): self._add_indicator_data('candle', candle) candle['iv'] = self.get_indicator_values() self._add_candle_data(candle) def _process_new_seed_trade(self, trade): self._update_indicator_data('trade', trade['price']) async def _process_price_update(self, update): self.lastPrice[update.symbol] = update # TODO: Handle stops/targets if update.symbol not in self.positions: await self._execute_events(Events.ON_ENTER, update) else: symPosition = self.positions[update.symbol] amount = symPosition.amount await self._execute_events(Events.ON_UPDATE, update) if amount > 0: await self._execute_events(Events.ON_UPDATE_LONG, update) else: await self._execute_events(Events.ON_UPDATE_SHORT, update) ############################ # Public Functions # ############################ def get_last_price_update(self, symbol): update = self.lastPrice[symbol] return update def get_position(self, symbol): return self.positions.get(symbol) def add_position(self, position): self.positions[position.symbol] = position def remove_position(self, position): self.logger.debug("Archiving closed position {}".format(position)) self.closedPositions += [position] del self.positions[position.symbol] def get_indicator_values(self): values = {} for key in self.indicators: values[key] = self.indicators[key].v() return values def is_indicators_ready(self): for key in self.indicators: if not self.indicators[key].ready(): return False return True def on(self, event, func=None): if not func: return self.events.on(event) self.events.on(event, func) def once(self, event, func=None): if not func: return self.events.once(event) self.events.once(event, func) def set_indicators(self, indicators): self.indicators = indicators def get_indicators(self): return self.indicators ############################ # Event Hooks # ############################ def on_error(self, func=None): if not func: return self.events.on(Events.ERROR) self.events.on(Events.ERROR, func) def on_enter(self, func=None): if not func: return self.events.on(Events.ON_ENTER) self.events.on(Events.ON_ENTER, func) def on_update(self, func=None): if not func: return self.events.on(Events.ON_UPDATE) self.events.on(Events.ON_UPDATE, func) def on_update_long(self, func=None): if not func: return self.events.on(Events.ON_UPDATE_LONG) self.events.on(Events.ON_UPDATE_LONG, func) def on_update_short(self, func=None): if not func: return self.events.on(Events.ON_UPDATE_SHORT) self.events.on(Events.ON_UPDATE_SHORT, func) def on_order_fill(self, func=None): if not func: return self.events.on(Events.ON_ORDER_FILL) self.events.on(Events.ON_ORDER_FILL, func) def on_position_update(self, func=None): if not func: return self.events.on(Events.ON_POSITION_UPDATE) self.events.on(Events.ON_POSITION_UPDATE, func) def on_position_close(self, func=None): if not func: return self.events.on(Events.ON_POSITION_CLOSE) self.events.on(Events.ON_POSITION_CLOSE, func)
class GenericWebsocket: """ Websocket object used to contain the base functionality of a websocket. Inlcudes an event emitter and a standard websocket client. """ def __init__(self, host, logLevel='INFO', loop=None, max_retries=5, create_event_emitter=_start_event_worker): self.host = host self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel) self.loop = loop or asyncio.get_event_loop() # overide 'error' event to stop it raising an exception # self.events.on('error', self.on_error) self.ws = None self.max_retries = max_retries self.attempt_retry = True self.sockets = {} # start seperate process for the even emitter eventLoop = create_event_emitter() self.events = EventEmitter(scheduler=asyncio.ensure_future, loop=eventLoop) def run(self): """ Starte the websocket connection. This functions spawns the initial socket thread and connection. """ self._start_new_socket() def get_task_executable(self): """ Get the run indefinitely asyncio task """ return self._run_socket() def _start_new_socket(self, socketId=None): if not socketId: socketId = len(self.sockets) def start_loop(loop): asyncio.set_event_loop(loop) loop.run_until_complete(self._run_socket()) worker_loop = asyncio.new_event_loop() worker = Thread(target=start_loop, args=(worker_loop, )) worker.start() return socketId def _wait_for_socket(self, socket_id): """ Block until the given socket connection is open """ while True: socket = self.sockets.get(socket_id, False) if socket: if socket.isConnected and socket.ws: return time.sleep(0.01) async def _connect(self, socket): async with websockets.connect(self.host) as websocket: self.sockets[socket.id].set_websocket(websocket) self.sockets[socket.id].set_connected() self.logger.info("Wesocket connected to {}".format(self.host)) while True: await asyncio.sleep(0) message = await websocket.recv() await self.on_message(socket.id, message) def get_socket(self, socketId): return self.sockets[socketId] def get_authenticated_socket(self): for socketId in self.sockets: if self.sockets[socketId].isAuthenticated: return self.sockets[socketId] return None async def _run_socket(self): retries = 0 sId = len(self.sockets) s = Socket(sId) self.sockets[sId] = s while retries < self.max_retries and self.attempt_retry: try: await self._connect(s) retries = 0 except (ConnectionClosed, socket.error) as e: self.sockets[sId].set_disconnected() self._emit('disconnected') if (not self.attempt_retry): return self.logger.error(str(e)) retries += 1 # wait 5 seconds befor retrying self.logger.info("Waiting 5 seconds before retrying...") await asyncio.sleep(5) self.logger.info("Reconnect attempt {}/{}".format( retries, self.max_retries)) self.logger.info("Unable to connect to websocket.") self._emit('stopped') def remove_all_listeners(self, event): """ Remove all listeners from event emitter """ self.events.remove_all_listeners(event) def on(self, event, func=None): """ Add a new event to the event emitter """ if not func: return self.events.on(event) self.events.on(event, func) def once(self, event, func=None): """ Add a new event to only fire once to the event emitter """ if not func: return self.events.once(event) self.events.once(event, func) def _emit(self, event, *args, **kwargs): self.events.emit(event, *args, **kwargs) async def on_error(self, error): """ On websocket error print and fire event """ self.logger.error(error) async def on_close(self): """ On websocket close print and fire event. This is used by the data server. """ self.logger.info("Websocket closed.") self.attempt_retry = False await self.ws.close() self._emit('done') async def on_open(self): """ On websocket open """ pass async def on_message(self, message): """ On websocket message """ pass
class GenericWebsocket: """ Websocket object used to contain the base functionality of a websocket. Inlcudes an event emitter and a standard websocket client. """ def __init__(self, host, logLevel='INFO', loop=None, max_retries=5): self.host = host self.logger = CustomLogger('BfxWebsocket', logLevel=logLevel) self.loop = loop or asyncio.get_event_loop() self.events = EventEmitter(scheduler=asyncio.ensure_future, loop=self.loop) # overide 'error' event to stop it raising an exception # self.events.on('error', self.on_error) self.ws = None self.max_retries = max_retries self.attempt_retry = True def run(self): """ Run the websocket connection indefinitely """ self.loop.run_until_complete(self._main(self.host)) def get_task_executable(self): """ Get the run indefinitely asyncio task """ return self._main(self.host) async def _connect(self, host): async with websockets.connect(host) as websocket: self.ws = websocket self.logger.info("Wesocket connected to {}".format(host)) while True: await asyncio.sleep(0) message = await websocket.recv() await self.on_message(message) def get_ws(self): return self.ws async def _main(self, host): retries = 0 while retries < self.max_retries and self.attempt_retry: try: await self._connect(host) retries = 0 except (ConnectionClosed, socket.error) as e: self._emit('disconnected') if (not self.attempt_retry): return self.logger.error(str(e)) retries += 1 # wait 5 seconds befor retrying self.logger.info("Waiting 5 seconds before retrying...") await asyncio.sleep(5) self.logger.info("Reconnect attempt {}/{}".format( retries, self.max_retries)) self.logger.info("Unable to connect to websocket.") self._emit('stopped') def remove_all_listeners(self, event): """ Remove all listeners from event emitter """ self.events.remove_all_listeners(event) def on(self, event, func=None): """ Add a new event to the event emitter """ if not func: return self.events.on(event) self.events.on(event, func) def once(self, event, func=None): """ Add a new event to only fire once to the event emitter """ if not func: return self.events.once(event) self.events.once(event, func) def _emit(self, event, *args, **kwargs): self.events.emit(event, *args, **kwargs) async def on_error(self, error): """ On websocket error print and fire event """ self.logger.error(error) async def on_close(self): """ On websocket close print and fire event. This is used by the data server. """ self.logger.info("Websocket closed.") self.attempt_retry = False await self.ws.close() self._emit('done') async def on_open(self): """ On websocket open """ pass async def on_message(self, message): """ On websocket message """ pass
class WebsocketClient(object): def __init__(self, host=None, port=None, route=None, ssl=None): config = Configuration.get().get("websocket") host = host or config.get("host") port = port or config.get("port") route = route or config.get("route") ssl = ssl or config.get("ssl") validate_param(host, "websocket.host") validate_param(port, "websocket.port") validate_param(route, "websocket.route") self.url = WebsocketClient.build_url(host, port, route, ssl) self.emitter = EventEmitter() self.client = self.create_client() self.pool = ThreadPool(10) self.retry = 5 @staticmethod def build_url(host, port, route, ssl): scheme = "wss" if ssl else "ws" return scheme + "://" + host + ":" + str(port) + route def create_client(self): return WebSocketApp(self.url, on_open=self.on_open, on_close=self.on_close, on_error=self.on_error, on_message=self.on_message) def on_open(self, ws): LOG.info("Connected") self.emitter.emit("open") # Restore reconnect timer to 5 seconds on sucessful connect self.retry = 5 def on_close(self, ws): self.emitter.emit("close") def on_error(self, ws, error): try: self.emitter.emit('error', error) self.client.close() except Exception as e: LOG.error(repr(e)) LOG.warning("WS Client will reconnect in %d seconds." % self.retry) time.sleep(self.retry) self.retry = min(self.retry * 2, 60) self.client = self.create_client() self.run_forever() def on_message(self, ws, message): self.emitter.emit('message', message) parsed_message = Message.deserialize(message) self.pool.apply_async( self.emitter.emit, (parsed_message.type, parsed_message)) def emit(self, message): if (not self.client or not self.client.sock or not self.client.sock.connected): return if hasattr(message, 'serialize'): self.client.send(message.serialize()) else: self.client.send(json.dumps(message.__dict__)) def on(self, event_name, func): self.emitter.on(event_name, func) def once(self, event_name, func): self.emitter.once(event_name, func) def remove(self, event_name, func): self.emitter.remove_listener(event_name, func) def remove_all_listeners(self, event_name): ''' Remove all listeners connected to event_name. Args: event_name: event from which to remove listeners ''' if event_name is None: raise ValueError self.emitter.remove_all_listeners(event_name) def run_forever(self): self.client.run_forever() def close(self): self.client.close()