Exemple #1
0
    def validate_model_from_mode(
        self, parsed_response: Union[dict, list, str], mode: Optional[str],
        mode_to_model: Dict[str,
                            BaseModel]) -> Union[OKResponse, ErrorResponse]:
        """Handle validation for all possible modes present in mode_to_model dict

        Args:
            parsed_response: object to validate
            mode: mode of request
            mode_to_model: dict mapping mode to pydantic model to validate against

        Returns:
            Union[OKResponse, ErrorResponse]: according to success/error
        """

        pydantic_model = mode_to_model[mode]

        try:

            validated = pydantic_model(**parsed_response)
            defined_fields = list(validated.dict().keys())

            # handle different cases, where we define data only, or multiple fields
            if defined_fields == ["data"]:
                value = validated.dict()["data"]
            else:
                value = validated.dict()

            return OKResponse(status_code=status.HTTP_200_OK, value=value)

        except ValidationError as e:
            logger.error(str(e))
            return ErrorResponse(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                value=e.errors())
Exemple #2
0
    async def publish_data_orderbook(self, msg, redis_pool):
        try:
            # with current logic parser needs to return a dict
            # that has bool values for keys is_snapshot and is_update
            parsed = self.stream_parser.orderbook(msg)
            # should return dict that we validates vs Trade Model
            validated = OrderBook(**parsed)
            # then we want to return a response

            if validated.is_snapshot:
                self.full_orderbook["asks"] = Counter(validated.asks)
                self.full_orderbook["bids"] = Counter(validated.bids)
                update_chan = f"ws:public:data:orderbook:snapshot:{self.exchange}:{validated.symbol}"
            else:
                self.full_orderbook["asks"] += Counter(validated.asks)
                self.full_orderbook["bids"] += Counter(validated.bids)
                update_chan = f"ws:public:data:orderbook:update:{self.exchange}:{validated.symbol}"

            resp = OKResponse(status_code=200, value=self.full_orderbook)

            await redis_pool.publish(update_chan, ujson.dumps(resp.value))

        except ValidationError as e:
            logger.error(e)
            return ErrorResponse(status_code=404, value=str(e))
        except Exception as e:
            log_exception(logger, e)
Exemple #3
0
    async def publish_data_trade(self, msg, redis_pool):
        # public trades
        # no snapshots
        try:
            parsed = self.stream_parser.trade(msg)
            # should return dict that we validates vs Trade Model
            validated = TradesList(data=parsed, last=None)
            # then we want to return a response

            value = validated.data

            resp = OKResponse(status_code=200, value=value)

            # resp value is a list of pydantic Trade Models
            # we need to check symbol for each item of list and dispatch accordingly
            for item in resp.value:
                # logger.info(ujson.dumps(item.dict()))
                update_chan = f"ws:public:data:trade:update:{self.exchange}:{item.symbol}"
                await redis_pool.publish(update_chan, ujson.dumps(item.dict()))

        except ValidationError as e:
            logger.error(e)
            return ErrorResponse(status_code=404, value=str(e))
        except Exception as e:
            log_exception(logger, e)
Exemple #4
0
    async def publish_data_trade(self, msg, redis_pool):
        # public trades
        # ignore snapshot as it will only give us past 50 trades (useless)
        try:
            try:
                self.feed_counters["trade"] += 1
                parsed = self.stream_parser.trade(msg)
            except KeyError:
                self.feed_counters["trade"] = 0

            parsed = self.stream_parser.user_trade(msg)
            # should return dict that we validates vs Trade Model
            validated = TradesList(data=parsed)
            # then we want to return a response

            value = validated.data

            resp = OKResponse(status_code=200, value=value)

            # resp value is a list of pydantic Trade Models
            # we need to check symbol for each item of list and dispatch accordingly
            for item in resp.value:
                logger.info(item)
                update_chan = f"ws:private:data:trade:update:{self.exchange}:{item.symbol}"
                await redis_pool.publish(update_chan, ujson.dumps(item.dict()))

        except ValidationError as e:
            logger.error(e)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=404, value=str(e))
        except Exception as e:
            log_exception(logger, e)
            await log_exc_to_db(logger, e)
Exemple #5
0
    async def publish_data_order(self, msg, redis_pool):
        try:
            #! we need to sort between snapshot / new order update / order status update
            #! like for orderbook we want to return a full image of current orders, not just a single status update for ex
            #! ==> ideally we want to publish to two redis channels, one with the full list of current orders, one with just the incoming updates
            try:
                self.feed_counters["order"] += 1

                # this should return a dict with 2 keys:
                # new_orders and status_changes each being a dict
                parsed = self.stream_parser.order_update(msg)
                # updating the dict will override the value if the key is already present
                self.all_orders.update(parsed["insert"])
                for order_id, info in parsed["update"].items():
                    # e.g info = {"status": "filled", "leavesQty": 0}
                    for key, value in info.items():
                        self.all_orders[order_id][key] = value

            # first message == it's a snapshot (!! this is true for kraken but we have not checked for other exchanges)
            except KeyError:
                self.feed_counters["order"] = 0
                parsed = self.stream_parser.order_snapshot(msg)
                self.all_orders.update(parsed)

            # should return dict that we validates vs order Model
            validated = OrdersByID(data=self.all_orders)
            # then we want to return a response

            value = validated.data

            resp = OKResponse(status_code=200, value=value)

            # resp value is a dict of pydantic Order Models
            # we need to check symbol for each item of dict and dispatch accordingly
            for key, value in resp.value.items():
                logger.info(value)
                update_chan = f"ws:private:data:order:update:{self.exchange}:{value.symbol}"
                await redis_pool.publish(update_chan,
                                         ujson.dumps(value.dict()))

        except ValidationError as e:
            logger.error(e)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=404, value=str(e))
        except Exception as e:
            log_exception(logger, e)
            await log_exc_to_db(logger, e)
Exemple #6
0
    async def publish_data_spread(self, msg, redis_pool):
        try:
            parsed = self.stream_parser.spread(msg)

            validated = Spread(**parsed)

            resp = OKResponse(status_code=200, value=validated)
            logger.info(resp.value)
            update_chan = f"ws:public:data:spread:update:{self.exchange}:{resp.value.symbol}"
            await redis_pool.publish(update_chan,
                                     ujson.dumps(resp.value.dict()))

        except ValidationError as e:
            logger.error(e)
            return ErrorResponse(status_code=404, value=str(e))
        except Exception as e:
            log_exception(logger, e)
Exemple #7
0
    async def publish_data_instrument(self, msg, redis_pool):
        # no snapshots
        try:
            parsed = self.stream_parser.instrument(msg)
            # should return dict that we validates vs Trade Model
            validated = Instrument(**parsed)
            # then we want to return a response

            resp = OKResponse(status_code=200, value=validated)
            logger.info(resp.value)
            update_chan = f"ws:public:data:instrument:update:{self.exchange}:{resp.value.symbol}"
            await redis_pool.publish(update_chan,
                                     ujson.dumps(resp.value.dict()))

        except ValidationError as e:
            logger.error(e)
            return ErrorResponse(status_code=404, value=str(e))
        except Exception as e:
            log_exception(logger, e)
Exemple #8
0
    async def get_websocket_auth_token(self,
                                       validity: int = None,
                                       permissions: list = None,
                                       retries: int = 0):
        """Get auth token to subscribe to private websocket feed.

        Args:
            validity (int) : number of minutes that token is valid
                (optional / default (max): 60 minutes)
            permissions (list) : comma separated list of allowed feeds
                (optional / default: all)

        Returns:
            dict
            keys:
            token (str) : token to authenticate private websocket subscription
            expires (int) : time to expiry

        Note:

            The API client must request an authentication "token" via the following REST API endpoint "GetWebSocketsToken"
            to connect to WebSockets Private endpoints.
            The token should be used within 15 minutes of creation.
            The token does not expire once a connection to a WebSockets API private message (openOrders or ownTrades) is maintained.


        This should be called at startup, we get a token, then subscribe to a ws feed
        We receive all the updates for our orders and send it to redis
        That way we can track position changes almost at tick speed without needing to make rest calls
        We will need to check the token creation time on every tick and a get new token every 30/40 minutes
        """

        data = {"validity": validity, "permissions": permissions}
        result = await self.query_private(method="ws_token",
                                          data=data,
                                          retries=retries)
        if result.is_ok:
            return OKResponse(status_code=status.HTTP_200_OK,
                              value=result.value)
        else:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)