Example #1
0
async def main():
    """doc"""
    channel = Channel(host="127.0.0.1", port=8788)
    puppet = PuppetStub(channel)

    event_stream = AsyncIOEventEmitter()
    event_stream.on('EVENT_TYPE_DONG', \
        lambda payload: print('on(dong) %s' % payload))

    await asyncio.gather(
        loop(lambda: puppet.ding(data='haha')),
        init_event_stream(event_stream, puppet),
    )

    channel.close()
Example #2
0
class MockWebsocket():
    def __init__(self):
        self.events = AsyncIOEventEmitter()
        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)
async def test_event(emitter: AsyncIOEventEmitter):
    async def stream_event(data: str):
        assert data == '1'

    emitter.on('stream', stream_event)
    emitter.emit('stream', '2')
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 = AsyncIOEventEmitter()
    # 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)
Example #5
0
class PuppetMock(Puppet):
    """mock for puppet"""
    def __init__(self, options: PuppetMockOptions, name: str = 'puppet-mock'):
        super().__init__(options, name)

        if not options.mocker:
            raise WechatyPuppetMockError('mocker in options is required')
        self.mocker: Mocker = options.mocker

        self.started: bool = False
        self.emitter = AsyncIOEventEmitter()

    async def message_image(self, message_id: str,
                            image_type: ImageType) -> FileBox:
        """get image from message"""

    async def ding(self, data: Optional[str] = None):
        pass

    def on(self, event_name: str, caller):
        """listen event"""
        self.emitter.on(event_name, caller)

    def listener_count(self, event_name: str) -> int:
        """get the event count of the specific event"""
        listeners = self.emitter.listeners(event=event_name)
        return len(listeners)

    async def start(self) -> None:
        """star the account"""
        self.started = True
        if not self.mocker:
            raise WechatyPuppetMockError(
                'PuppetMock should not start without mocker'
            )

        def _emit_events(response: MockerResponse):
            """emit the events from the mocker"""
            payload_data = json.loads(response.payload)

            if response.type == int(EventType.EVENT_TYPE_MESSAGE):
                log.debug('receive message info <%s>', payload_data)
                event_message_payload = EventMessagePayload(
                    message_id=payload_data['messageId'])
                self.emitter.emit('message', event_message_payload)

        self.mocker.on('stream', _emit_events)

    async def stop(self):
        """stop the account"""
        self.started = False

    async def contact_list(self) -> List[str]:
        """get all of the contact"""
        return self.mocker.get_contact_ids()

    async def tag_contact_delete(self, tag_id: str) -> None:
        pass

    async def tag_favorite_delete(self, tag_id: str) -> None:
        pass

    async def tag_contact_add(self, tag_id: str, contact_id: str):
        pass

    async def tag_favorite_add(self, tag_id: str, contact_id: str):
        pass

    async def tag_contact_remove(self, tag_id: str, contact_id: str):
        pass

    async def tag_contact_list(self,
                               contact_id: Optional[str] = None) -> List[str]:
        pass

    async def message_send_text(self, conversation_id: str, message: str,
                                mention_ids: List[str] = None) -> str:
        """send the text message to the specific contact/room"""

        conversation: Union[Room, Contact]
        if conversation_id.startswith('room-'):
            conversation = self.mocker.Room.load(conversation_id)
        else:
            conversation = self.mocker.Contact.load(conversation_id)
        message_id = self.mocker.send_message(
            talker=self.mocker.login_user,
            conversation=conversation,
            msg=message
        )
        return message_id

    async def message_send_contact(self, contact_id: str,
                                   conversation_id: str) -> str:
        pass

    async def message_send_file(self, conversation_id: str,
                                file: FileBox) -> str:
        pass

    async def message_send_url(self, conversation_id: str, url: str) -> str:
        pass

    async def message_send_mini_program(self,
                                        conversation_id: str,
                                        mini_program: MiniProgramPayload
                                        ) -> str:
        pass

    async def message_search(self, query: Optional[MessageQueryFilter] = None
                             ) -> List[str]:
        pass

    async def message_recall(self, message_id: str) -> bool:
        pass

    async def message_payload(self, message_id: str) -> MessagePayload:
        """get the message payload"""
        return self.mocker.environment.get_message_payload(
            message_id=message_id)

    async def message_forward(self, to_id: str, message_id: str):
        pass

    async def message_file(self, message_id: str) -> FileBox:
        """get the file-box from message instance

        save the file-box data in message_payload.text field to avoid creating a
        new structure to support this feature
        """
        message_payload = self.mocker.environment.get_message_payload(
            message_id=message_id
        )
        return FileBox.from_json(message_payload.text)

    async def message_contact(self, message_id: str) -> str:
        """get the message Contact id info

            text field save the message contact_id info
        """
        message_payload = self.mocker.environment.get_message_payload(
            message_id=message_id
        )
        return message_payload.text

    async def message_url(self, message_id: str) -> UrlLinkPayload:
        """get the url link """

    async def message_mini_program(self, message_id: str) -> MiniProgramPayload:
        pass

    async def contact_alias(self, contact_id: str,
                            alias: Optional[str] = None) -> str:
        """get/save the contact alias"""
        contact_payload = self.mocker.environment.\
            get_contact_payload(contact_id)
        if not alias:
            return contact_payload.alias
        contact_payload.alias = alias
        self.mocker.environment.update_contact_payload(contact_payload)
        return alias

    async def contact_payload_dirty(self, contact_id: str):
        pass

    async def contact_payload(self, contact_id: str) -> ContactPayload:
        """get the contact payload"""
        return self.mocker.environment.get_contact_payload(contact_id)

    async def contact_avatar(self, contact_id: str,
                             file_box: Optional[FileBox] = None) -> FileBox:
        """get the contact avatar"""
        contact_payload = self.mocker.environment.\
            get_contact_payload(contact_id)
        if not file_box:
            return FileBox.from_base64(
                contact_payload.avatar,
                name=f'{contact_payload.name}.png'
            )
        contact_payload.avatar = file_box.base64
        self.mocker.environment.update_contact_payload(contact_payload)

    async def contact_tag_ids(self, contact_id: str) -> List[str]:
        pass

    def self_id(self) -> str:
        return self.mocker.login_user.contact_id

    async def friendship_search(self, weixin: Optional[str] = None,
                                phone: Optional[str] = None) -> Optional[str]:
        pass

    async def friendship_add(self, contact_id: str, hello: str):
        pass

    async def friendship_payload(self, friendship_id: str,
                                 payload: Optional[FriendshipPayload] = None
                                 ) -> FriendshipPayload:
        pass

    async def friendship_accept(self, friendship_id: str):
        pass

    async def room_list(self) -> List[str]:
        """get the room id list"""
        rooms = self.mocker.environment.get_room_payloads()
        return [room.id for room in rooms]

    async def room_create(self, contact_ids: List[str],
                          topic: str = None) -> str:
        """create the room"""
        room_payload = self.mocker.environment.new_room_payload(
            member_ids=contact_ids,
            topic=topic
        )
        return room_payload.id

    async def room_search(self, query: RoomQueryFilter = None) -> List[str]:
        pass

    async def room_invitation_payload(self,
                                      room_invitation_id: str,
                                      payload: Optional[
                                          RoomInvitationPayload] = None
                                      ) -> RoomInvitationPayload:
        pass

    async def room_invitation_accept(self, room_invitation_id: str):
        pass

    async def contact_self_qr_code(self) -> str:
        pass

    async def contact_self_name(self, name: str):
        pass

    async def contact_signature(self, signature: str):
        pass

    async def room_payload(self, room_id: str) -> RoomPayload:
        """get the room payload"""
        return self.mocker.environment.get_room_payload(room_id)

    async def room_members(self, room_id: str) -> List[str]:
        """get the room member ids from environment

        Args:
            room_id (str): the union identification for room

        Returns:
            List[str]: room member ids
        """
        room_payload: RoomPayload = self.mocker.environment.get_room_payload(
            room_id)
        return room_payload.member_ids

    async def room_add(self, room_id: str, contact_id: str):
        """add a contact to a room"""
        self.mocker.add_contact_to_room(
            contact_ids=[contact_id],
            room_id=room_id
        )

    async def room_delete(self, room_id: str, contact_id: str):
        pass

    async def room_quit(self, room_id: str):
        pass

    async def room_topic(self, room_id: str, new_topic: str):
        pass

    async def room_announce(self, room_id: str,
                            announcement: str = None) -> str:
        pass

    async def room_qr_code(self, room_id: str) -> str:
        pass

    async def room_member_payload(self, room_id: str,
                                  contact_id: str) -> RoomMemberPayload:
        pass

    async def room_avatar(self, room_id: str) -> FileBox:
        pass

    async def logout(self):
        pass

    async def login(self, user_id: str):
        """login the user data"""
        self.mocker.login(user_id=user_id)
Example #6
0
class WebSocket:
    """The Binance DEX WebSocket Manager."""
    def __init__(
        self,
        address: str = None,
        testnet: bool = False,
        keepalive: bool = True,
        loop: asyncio.AbstractEventLoop = None,
        url: str = None,
    ) -> None:
        if not url:
            self.url = TESTNET_URL if testnet else MAINNET_URL
        else:
            self.url = url
        self.address = address
        self._session = aiohttp.ClientSession()
        self._ws: Optional[aiohttp.ClientWebSocketResponse] = None
        self._loop = loop or asyncio.get_event_loop()
        self._events = AsyncIOEventEmitter(loop=self._loop)
        self._sub_queue: List[Tuple[str, dict]] = []
        self._keepalive = keepalive
        self._keepalive_task: Optional[asyncio.Future] = None
        self._open = False
        self._testnet = testnet

    def on(self, event: str, func: Optional[Callable] = None, **kwargs):
        """Register an event, and optional handler.

        This can be used as a decorator or as a normal method.
        See `examples/websockets_decorator.py` for usage.
        """
        # Queue up most events from startup-time decorators until after we are open
        if not self._open and event not in ("open", "error", "new_listener"):
            self._sub_queue.append((event, kwargs))
        if func:
            self._events.on(event, func)
            return None
        else:
            return self._events.on(event)

    def start(
        self,
        on_open: Optional[Callable[[], None]] = None,
        on_error: Optional[Callable[[dict], None]] = None,
        loop: asyncio.AbstractEventLoop = None,
    ) -> None:
        """The main blocking call to start the WebSocket connection."""
        loop = loop or asyncio.get_event_loop()
        return loop.run_until_complete(self.start_async(on_open, on_error))

    async def start_async(
        self,
        on_open: Optional[Callable[[], None]] = None,
        on_error: Optional[Callable[[dict], None]] = None,
    ) -> None:
        """Processes all websocket messages."""
        if self.address:  # address-specific socket
            url = f"{self.url}/{self.address}"
        else:
            url = self.url

        async with self._session.ws_connect(url) as ws:
            self._ws = ws
            self._events.emit("open")
            while self._sub_queue:
                event, kwargs = self._sub_queue.pop()
                self.subscribe(event, **kwargs)
            if on_open:
                on_open()

            # Schedule keepalive calls every 30 minutes
            if self._keepalive:
                self._keepalive_task = asyncio.ensure_future(
                    self._auto_keepalive())

            async for msg in ws:
                if msg.type == aiohttp.WSMsgType.TEXT:
                    try:
                        data = msg.json(loads=orjson.loads)
                    except Exception as e:
                        log.error(f"Unable to decode msg: {msg}")
                        continue
                    if not data:
                        log.error(f"Got empty msg: {msg}")
                        continue
                    if "error" in data:
                        self._events.emit("error", data)
                        if on_error:
                            on_error(data)
                        else:
                            log.error(f"Unhandled error msg: {data}")
                        continue
                    if "stream" not in data:
                        log.error(f"Got msg without stream: {data}")
                        continue
                    if "data" not in data:
                        log.error(f"Got msg without data: {data}")
                        continue

                    self._events.emit(data["stream"], data)

                elif msg.type == aiohttp.WSMsgType.ERROR:
                    log.error(msg)
                    self._events.emit("error", msg)
                    break

    async def send(self, data: dict) -> None:
        """Send data to the WebSocket"""
        if not self._ws:
            log.error("Error: Cannot send to uninitialized websocket")
            return
        await self._ws.send_bytes(orjson.dumps(data))

    def subscribe(
        self,
        stream: str,
        symbols: Optional[List[str]] = None,
        address: Optional[str] = None,
        callback: Optional[Callable[[dict], None]] = None,
    ):
        """Subscribe to a WebSocket stream.

        See the documentation for more details on the available streams
        https://docs.binance.org/api-reference/dex-api/ws-streams.html
        """
        payload: Dict[Any, Any] = {"method": "subscribe", "topic": stream}
        if symbols:
            payload["symbols"] = symbols
        if address:
            payload["address"] = address
        elif self.address:
            payload["address"] = self.address
        self._events.on(stream, callback)
        asyncio.ensure_future(self.send(payload))

    def unsubscribe(self, stream, symbols=None) -> None:
        payload = {"method": "unsubscribe", "topic": stream}
        if symbols:
            payload["symbols"] = symbols
        asyncio.ensure_future(self.send(payload))

    def subscribe_user_orders(self,
                              callback: Callable[[dict], None],
                              address: Optional[str] = None) -> None:
        """Subscribe to individual order updates."""
        self.subscribe("orders", address=address, callback=callback)

    def subscribe_user_accounts(self,
                                callback: Callable[[dict], None],
                                address: Optional[str] = None) -> None:
        """Subscribe to account updates."""
        self.subscribe("accounts", address=address, callback=callback)

    def subscribe_user_transfers(self,
                                 callback: Callable[[dict], None],
                                 address: Optional[str] = None) -> None:
        """
        Subscribe to transfer updates if `address` is involved (as sender or
        receiver) in a transfer. Multisend is also covered.
        """
        self.subscribe("transfers", address=address, callback=callback)

    def subscribe_trades(self, symbols: List[str],
                         callback: Callable[[dict], None]) -> None:
        """Subscribe to individual trade updates."""
        self.subscribe("trades", symbols=symbols, callback=callback)

    def subscribe_market_diff(self, symbols: List[str],
                              callback: Callable[[dict], None]) -> None:
        "Order book price and quantity depth updates used to locally keep an order book." ""
        self.subscribe("marketDiff", symbols=symbols, callback=callback)

    def subscribe_market_depth(self, symbols: List[str],
                               callback: Callable[[dict], None]) -> None:
        """Top 20 levels of bids and asks."""
        self.subscribe("marketDepth", symbols=symbols, callback=callback)

    def subscribe_kline(self, interval: str, symbols: List[str],
                        callback: Callable[[dict], None]) -> None:
        """
        The kline/candlestick stream pushes updates to the current
        klines/candlestick every second.

        Kline/Candlestick chart intervals:
            m -> minutes; h -> hours; d -> days; w -> weeks; M -> months
            1m 3m 5m 15m 30m 1h 2h 4h 6h 8h 12h 1d 3d 1w 1M
        """
        self.subscribe(f"kline_{interval}", symbols=symbols, callback=callback)

    def subscribe_ticker(self, symbols: List[str],
                         callback: Callable[[dict], None]) -> None:
        """24hr Ticker statistics for a single symbol are pushed every second."""
        self.subscribe("ticker", symbols=symbols, callback=callback)

    def subscribe_all_tickers(self, callback: Callable[[dict], None]) -> None:
        """24hr Ticker statistics for a all symbols are pushed every second."""
        self.subscribe("allTickers", symbols=["$all"], callback=callback)

    def subscribe_mini_ticker(self, symbols: List[str],
                              callback: Callable[[dict], None]) -> None:
        """A ticker for a single symbol is pushed every second."""
        self.subscribe("miniTicker", symbols=symbols, callback=callback)

    def subscribe_all_mini_tickers(self, callback: Callable[[dict],
                                                            None]) -> None:
        """Array of 24hr Mini Ticker statistics for a all symbols pushed every second."""
        self.subscribe("allMiniTickers", symbols=["$all"], callback=callback)

    def subscribe_blockheight(self, callback: Callable[[dict], None]) -> None:
        """Streams the latest block height."""
        self.subscribe("blockheight", symbols=["$all"], callback=callback)

    def keepalive(self) -> None:
        """Extend the connection time by another 30 minutes"""
        asyncio.ensure_future(self.send({"method": "keepAlive"}))

    async def _auto_keepalive(self):
        while True:
            await asyncio.sleep(30 * 60)
            self.keepalive()

    def close(self) -> None:
        """Close the websocket session"""
        asyncio.ensure_future(self.send({"method": "close"}))
        if self._session:
            asyncio.ensure_future(self._session.close())
        if self._keepalive_task:
            self._keepalive_task.cancel()
Example #7
0
def addEventListener(emitter: AsyncIOEventEmitter, eventName: str,
                     handler: Callable) -> Dict[str, Any]:
    """Add handler to the emitter and return emitter/handler."""
    emitter.on(eventName, handler)
    return {'emitter': emitter, 'eventName': eventName, 'handler': handler}