コード例 #1
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_user_trade_by_id(self,
                                   mode: Literal["to_list", "by_id"],
                                   trdMatchID: str,
                                   symbol: Optional[PAIR] = None,
                                   retries: int = 1):
        """Get info on a single trade
        """
        try:
            data = self.request_parser.user_trades(trdMatchID=trdMatchID)
        except Exception as e:
            msg = repr(e)
            logger.error(msg)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=400, value=msg)

        result = await self.query_private(method="trades_info",
                                          data=data,
                                          retries=retries)

        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.user_trades(
                response=result.value, mode=mode)
            # not all pydantic models have <data> as only field
            # so we need to specify the field here to make it work for all models
            return self.user_trade_validate_and_serialize(
                mode, {"data": parsed_response})
コード例 #2
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_instrument(
        self,
        symbol: PAIR,
        retries: int = 1,
    ) -> NoobitResponse:
        """Get data for instrument. Depending on exchange this will aggregate ticker, spread data
        """
        params = self.validate_params(model=InstrumentRequest, symbol=symbol)
        if params.is_error:
            return ErrorResponse(status_code=400, value=params.value)

        try:
            data = self.request_parser.instrument(params.value["symbol"])
        except Exception as e:
            msg = repr(e)
            logger.error(msg)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=400, value=msg)

        result = await self.query_public(method="instrument",
                                         data=data,
                                         retries=retries)

        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.instrument(
                response=result.value)
            return self.instrument_validate_and_serialize(parsed_response)
コード例 #3
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_open_positions(self,
                                 mode: Literal["to_list", "by_id"],
                                 symbol: Optional[PAIR] = None,
                                 retries: int = 1) -> NoobitResponse:
        """For kraken there is no <closed positions> endpoint, but we can simulate it by querying <closed orders> and then filtering for margin orders
        Or even better filter out <trades history> and <type==closed position>
        """
        if symbol is not None:
            symbol = symbol.upper()

        try:
            data = self.request_parser.open_positions(symbol)
        except Exception as e:
            msg = repr(e)
            logger.error(msg)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=400, value=msg)

        result = await self.query_private(method="open_positions",
                                          data=data,
                                          retries=retries)

        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.open_positions(
                response=result.value, mode=mode)
            # not all pydantic models have <data> as only field
            # so we need to specify the field here to make it work for all models
            return self.positions_validate_and_serialize(
                mode, {"data": parsed_response})
コード例 #4
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_orderbook(
        self,
        symbol: PAIR,
        retries: int = 1,
    ) -> NoobitResponse:
        """
        """
        params = self.validate_params(model=OrderBookRequest, symbol=symbol)
        if params.is_error:
            return ErrorResponse(status_code=400, value=params.value)

        try:
            data = self.request_parser.orderbook(params.value["symbol"])
        except Exception as e:
            msg = repr(e)
            logger.error(msg)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=400, value=msg)

        result = await self.query_public(method="orderbook",
                                         data=data,
                                         retries=retries)

        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.orderbook(
                response=result.value)
            return self.orderbook_validate_and_serialize(parsed_response)
コード例 #5
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_closed_positions(self,
                                   mode: Literal["to_list", "by_id"],
                                   symbol: Optional[PAIR] = None,
                                   retries: int = 1) -> NoobitResponse:

        if symbol is not None:
            symbol = symbol.upper()

        try:
            data = self.request_parser.closed_positions(symbol)
        except Exception as e:
            msg = repr(e)
            logger.error(msg)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=400, value=msg)

        result = await self.query_private(method="closed_positions",
                                          data=data,
                                          retries=retries)

        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.closed_positions(
                response=result.value, mode=mode)
            return self.positions_validate_and_serialize(
                mode, {"data": parsed_response})
コード例 #6
0
ファイル: public.py プロジェクト: maxima-us/noobit-backend
    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)
コード例 #7
0
ファイル: public.py プロジェクト: maxima-us/noobit-backend
    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)
コード例 #8
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    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())
コード例 #9
0
ファイル: private.py プロジェクト: maxima-us/noobit-backend
    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)
コード例 #10
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_ohlc(self,
                       symbol: PAIR,
                       timeframe: TIMEFRAME,
                       retries: int = 1) -> NoobitResponse:
        """
        """
        #TODO validate kwargs by passing them to OhlcParameters Pydantic Model
        params = self.validate_params(model=OhlcRequest,
                                      symbol=symbol,
                                      timeframe=timeframe)
        if params.is_error:
            return ErrorResponse(status_code=400, value=params.value)

        # TODO Handle request errors (for ex if we pass invalid symbol, or pair that does not exist)
        try:
            data = self.request_parser.ohlc(
                symbol=params.value["symbol"],
                timeframe=params.value["timeframe"])
        except Exception as e:
            msg = repr(e)
            logger.error(msg)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=400, value=msg)

        # data = self.request_parser.ohlc(symbol=symbol.upper(), timeframe=timeframe)
        # if isinstance(data, BaseException):
        #     return ErrorResponse(status_code=400, value=data)

        #! vvvvvvvvvvvvvvvvvvvvvvv HANDLE REQUEST PARSING ERRORS
        # make request parser return a RequestResult object
        # but this leaves responsability to the user, which is bad
        # ==> Basic Example:
        # if request.is_error:
        #   return ErrorResponse(status_code=bad_request, value=request.value)

        result = await self.query_public(method="ohlc",
                                         data=data,
                                         retries=retries)
        # parse to order response model and validate
        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.ohlc(response=result.value)
            # not all pydantic models have <data> as only field
            # so we need to specify the field here to make it work for all models
            return self.ohlc_validate_and_serialize({"data": parsed_response})
コード例 #11
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_closed_orders(self,
                                mode: Literal["to_list", "by_id"],
                                symbol: Optional[PAIR] = None,
                                clOrdID: Optional[int] = None,
                                orderID=None,
                                retries: int = 1):
        """Get closed orders.

        Args:
            symbol (str): Instrument symbol
            clOrdID (str): Restrict results to given ID
            mode (str): Parse response to list or index by order id

        Returns:
            closed orders
        """

        if symbol is not None:
            symbol = symbol.upper()

        try:
            data = self.request_parser.orders("closed",
                                              symbol=symbol,
                                              clOrdID=clOrdID)
        except Exception as e:
            msg = repr(e)
            logger.error(msg)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=400, value=msg)

        result = await self.query_private(method="closed_orders",
                                          data=data,
                                          retries=retries)
        # parse to order response model and validate
        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.orders(
                response=result.value, symbol=symbol, mode=mode)
            # not all pydantic models have <data> as only field
            # so we need to specify the field here to make it work for all models
            return self.order_validate_and_serialize(mode,
                                                     {"data": parsed_response})
コード例 #12
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_public_trades(self,
                                symbol: PAIR,
                                since: TIMESTAMP = None,
                                retries: int = 1) -> NoobitResponse:
        """Get data on public trades. Response value is a list with each item being a dict that
        corresponds to the data for a single trade.
        """
        params = self.validate_params(model=TradesRequest,
                                      symbol=symbol,
                                      since=since)
        if params.is_error:
            return ErrorResponse(status_code=400, value=params.value)

        try:
            data = self.request_parser.public_trades(
                symbol=params.value["symbol"], since=params.value["since"])
        except Exception as e:
            msg = repr(e)
            logger.error(msg)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=400, value=msg)

        result = await self.query_public(method="trades",
                                         data=data,
                                         retries=retries)

        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.trades(
                response=result.value)
            # not all pydantic models have <data> as only field
            # so we need to specify the field here to make it work for all models
            return self.public_trade_validate_and_serialize({
                "data":
                parsed_response["data"],
                "last":
                parsed_response["last"]
            })
コード例 #13
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_order(self,
                        mode: Literal["to_list", "by_id"],
                        orderID: str,
                        clOrdID: Optional[int] = None,
                        symbol=None,
                        retries: int = 1) -> Union[list, dict, str]:
        """Get a single order
            mode (str): Parse response to list, or index by order id
            orderID: ID of the order to query (ID as assigned by broker)
            clOrdID (str): Restrict results to given ID
        """
        try:
            data = self.request_parser.orders(mode="by_id",
                                              orderID=orderID,
                                              clOrdID=clOrdID)
        except Exception as e:
            msg = repr(e)
            logger.error(msg)
            await log_exc_to_db(logger, e)
            return ErrorResponse(status_code=400, value=msg)

        # returns ErrorHandlerResult object (OkResult or ErrorResult)
        result = await self.query_private(method="order_info",
                                          data=data,
                                          retries=retries)

        # if its an error we just want the error message with no parsing
        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            # parse to order response model if error handler has not returned None or ValidationError
            parsed_response = self.response_parser.orders(
                response=result.value, mode=mode)
            # not all pydantic models have <data> as only field
            # so we need to specify the field here to make it work for all models
            return self.order_validate_and_serialize(mode,
                                                     {"data": parsed_response})
コード例 #14
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_exposure(self, retries: int = 1):

        data = {}
        result = await self.query_private(method="exposure",
                                          data=data,
                                          retries=retries)

        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.exposure(
                response=result.value)
            return self.exposure_validate_and_serialize(parsed_response)
コード例 #15
0
ファイル: private.py プロジェクト: maxima-us/noobit-backend
    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)
コード例 #16
0
ファイル: public.py プロジェクト: maxima-us/noobit-backend
    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)
コード例 #17
0
ファイル: public.py プロジェクト: maxima-us/noobit-backend
    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)
コード例 #18
0
ファイル: api.py プロジェクト: maxima-us/noobit-backend
    async def get_balances(self,
                           symbol: Optional[PAIR] = None,
                           retries: int = 1) -> NoobitResponse:
        if symbol is not None:
            symbol = symbol.upper()
        data = {}

        result = await self.query_private(method="balances",
                                          data=data,
                                          retries=retries)

        if not result.is_ok:
            return ErrorResponse(status_code=result.status_code,
                                 value=result.value)
        else:
            parsed_response = self.response_parser.balances(
                response=result.value)
            return self.balances_validate_and_serialize(
                {"data": parsed_response})
コード例 #19
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)